skia_safe/gpu/vk/
vulkan_backend_context.rs

1use std::{
2    cell::RefCell,
3    ffi::{self, CString},
4    fmt, mem,
5    ops::Deref,
6    os::raw::{self, c_char},
7    ptr,
8};
9
10use super::{Device, GetProc, GetProcOf, Instance, PhysicalDevice, Queue, Version};
11use crate::gpu;
12use skia_bindings as sb;
13
14// Note: the GrBackendContext's layout generated by bindgen does not match in size,
15// so we do need to use a pointer here for now.
16pub struct BackendContext<'a> {
17    pub(crate) native: ptr::NonNull<ffi::c_void>,
18    get_proc: &'a dyn GetProc,
19}
20
21impl Drop for BackendContext<'_> {
22    fn drop(&mut self) {
23        unsafe { sb::C_VulkanBackendContext_delete(self.native.as_ptr()) }
24    }
25}
26
27impl fmt::Debug for BackendContext<'_> {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        f.debug_struct("BackendContext")
30            .field("native", &self.native)
31            .finish()
32    }
33}
34
35// TODO: add some accessor functions to the public fields.
36// TODO: may support Clone (note the original structure holds a smartpointer!)
37// TODO: think about making this safe in respect to the lifetime of the handles
38//       it refers to.
39impl BackendContext<'_> {
40    /// # Safety
41    /// `instance`, `physical_device`, `device`, and `queue` must outlive the `BackendContext`
42    /// returned.
43    pub unsafe fn new(
44        instance: Instance,
45        physical_device: PhysicalDevice,
46        device: Device,
47        (queue, queue_index): (Queue, usize),
48        get_proc: &impl GetProc,
49    ) -> BackendContext {
50        Self::new_with_extensions(
51            instance,
52            physical_device,
53            device,
54            (queue, queue_index),
55            get_proc,
56            &[],
57            &[],
58        )
59    }
60
61    /// # Safety
62    /// `instance`, `physical_device`, `device`, and `queue` must outlive the `BackendContext`
63    /// returned.
64    pub unsafe fn new_with_extensions<'a>(
65        instance: Instance,
66        physical_device: PhysicalDevice,
67        device: Device,
68        (queue, queue_index): (Queue, usize),
69        get_proc: &'a impl GetProc,
70        instance_extensions: &[&str],
71        device_extensions: &[&str],
72    ) -> BackendContext<'a> {
73        // pin the extensions string in memory and provide pointers to the NewWithExtension function,
74        // but there is no need to retain them, because because the implementations copies these strings, too.
75        let instance_extensions: Vec<CString> = instance_extensions
76            .iter()
77            .map(|str| CString::new(*str).unwrap())
78            .collect();
79        let instance_extensions: Vec<*const c_char> =
80            instance_extensions.iter().map(|cs| cs.as_ptr()).collect();
81        let device_extensions: Vec<CString> = device_extensions
82            .iter()
83            .map(|str| CString::new(*str).unwrap())
84            .collect();
85        let device_extensions: Vec<*const c_char> =
86            device_extensions.iter().map(|cs| cs.as_ptr()).collect();
87
88        let resolver = Self::begin_resolving_proc(get_proc);
89        let native = sb::C_VulkanBackendContext_new(
90            instance as _,
91            physical_device as _,
92            device as _,
93            queue as _,
94            queue_index.try_into().unwrap(),
95            Some(global_get_proc),
96            instance_extensions.as_ptr(),
97            instance_extensions.len(),
98            device_extensions.as_ptr(),
99            device_extensions.len(),
100        );
101        drop(resolver);
102        BackendContext {
103            native: ptr::NonNull::new(native).unwrap(),
104            get_proc,
105        }
106    }
107
108    pub fn set_protected_context(&mut self, protected_context: gpu::Protected) -> &mut Self {
109        unsafe {
110            sb::C_VulkanBackendContext_setProtectedContext(
111                self.native.as_ptr() as _,
112                protected_context,
113            )
114        }
115        self
116    }
117
118    pub fn set_max_api_version(&mut self, version: impl Into<Version>) -> &mut Self {
119        unsafe {
120            sb::C_VulkanBackendContext_setMaxAPIVersion(
121                self.native.as_ptr() as _,
122                *version.into().deref(),
123            )
124        }
125        self
126    }
127
128    pub(crate) unsafe fn begin_resolving(&self) -> impl Drop {
129        Self::begin_resolving_proc(self.get_proc)
130    }
131
132    // The idea here is to set up a thread local variable with the GetProc function trait
133    // and reroute queries to global_get_proc as long the caller does not invoke the Drop
134    // impl trait that is returned.
135    // This is an attempt to support Rust Closures / Functions that resolve function pointers instead
136    // of relying on a global extern "C" function.
137    // TODO: This is a mess, highly unsafe, and needs to be simplified / rewritten
138    //       by someone who understands Rust better.
139    unsafe fn begin_resolving_proc(get_proc_trait_object: &dyn GetProc) -> impl Drop {
140        THREAD_LOCAL_GET_PROC.with(|get_proc| {
141            *get_proc.borrow_mut() = Some(mem::transmute::<&dyn GetProc, TraitObject>(
142                get_proc_trait_object,
143            ))
144        });
145
146        EndResolving {}
147    }
148}
149
150struct EndResolving {}
151
152impl Drop for EndResolving {
153    fn drop(&mut self) {
154        THREAD_LOCAL_GET_PROC.with(|get_proc| *get_proc.borrow_mut() = None)
155    }
156}
157
158thread_local! {
159    static THREAD_LOCAL_GET_PROC: RefCell<Option<TraitObject>> = const { RefCell::new(None) };
160}
161
162// https://doc.rust-lang.org/1.19.0/std/raw/struct.TraitObject.html
163#[repr(C)]
164// Copy & Clone are required for the *get_proc.borrow() below. And std::raw::TraitObject
165// can not be used, because it's unstable (last checked 1.36).
166#[derive(Copy, Clone)]
167struct TraitObject {
168    pub data: *mut (),
169    pub vtable: *mut (),
170}
171
172// The global resolvement function passed to Skia.
173unsafe extern "C" fn global_get_proc(
174    name: *const raw::c_char,
175    instance: Instance,
176    device: Device,
177) -> *const raw::c_void {
178    THREAD_LOCAL_GET_PROC.with(|get_proc| {
179        match *get_proc.borrow() {
180            Some(get_proc) => {
181                let get_proc_trait_object: &dyn GetProc = mem::transmute(get_proc);
182                if !device.is_null() {
183                    get_proc_trait_object(GetProcOf::Device(device, name))
184                } else {
185                    // note: instance may be null here!
186                    get_proc_trait_object(GetProcOf::Instance(instance, name))
187                }
188            }
189            None => panic!("Vulkan GetProc called outside of a thread local resolvement context."),
190        }
191    })
192}