Skip to main content

skia_safe/gpu/vk/
vulkan_backend_context.rs

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