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