skia_safe/gpu/vk/
vulkan_backend_context.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
use std::{
    cell::RefCell,
    ffi::{self, CString},
    fmt, mem,
    ops::Deref,
    os::raw::{self, c_char},
    ptr,
};

use skia_bindings as sb;

use super::{Device, GetProc, GetProcOf, Instance, PhysicalDevice, Queue, Version};
use crate::gpu;

// Note: the GrBackendContext's layout generated by bindgen does not match in size,
// so we do need to use a pointer here for now.
pub struct BackendContext<'a> {
    pub(crate) native: ptr::NonNull<ffi::c_void>,
    get_proc: &'a dyn GetProc,
}

impl Drop for BackendContext<'_> {
    fn drop(&mut self) {
        unsafe { sb::C_VulkanBackendContext_delete(self.native.as_ptr()) }
    }
}

impl fmt::Debug for BackendContext<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("BackendContext")
            .field("native", &self.native)
            .finish()
    }
}

// TODO: add some accessor functions to the public fields.
// TODO: may support Clone (note the original structure holds a smartpointer!)
// TODO: think about making this safe in respect to the lifetime of the handles
//       it refers to.
impl BackendContext<'_> {
    /// # Safety
    /// `instance`, `physical_device`, `device`, and `queue` must outlive the `BackendContext`
    /// returned.
    pub unsafe fn new(
        instance: Instance,
        physical_device: PhysicalDevice,
        device: Device,
        (queue, queue_index): (Queue, usize),
        get_proc: &impl GetProc,
    ) -> BackendContext {
        Self::new_with_extensions(
            instance,
            physical_device,
            device,
            (queue, queue_index),
            get_proc,
            &[],
            &[],
        )
    }

    /// # Safety
    /// `instance`, `physical_device`, `device`, and `queue` must outlive the `BackendContext`
    /// returned.
    pub unsafe fn new_with_extensions<'a>(
        instance: Instance,
        physical_device: PhysicalDevice,
        device: Device,
        (queue, queue_index): (Queue, usize),
        get_proc: &'a impl GetProc,
        instance_extensions: &[&str],
        device_extensions: &[&str],
    ) -> BackendContext<'a> {
        // pin the extensions string in memory and provide pointers to the NewWithExtension function,
        // but there is no need to retain them, because because the implementations copies these strings, too.
        let instance_extensions: Vec<CString> = instance_extensions
            .iter()
            .map(|str| CString::new(*str).unwrap())
            .collect();
        let instance_extensions: Vec<*const c_char> =
            instance_extensions.iter().map(|cs| cs.as_ptr()).collect();
        let device_extensions: Vec<CString> = device_extensions
            .iter()
            .map(|str| CString::new(*str).unwrap())
            .collect();
        let device_extensions: Vec<*const c_char> =
            device_extensions.iter().map(|cs| cs.as_ptr()).collect();

        let resolver = Self::begin_resolving_proc(get_proc);
        let native = sb::C_VulkanBackendContext_new(
            instance as _,
            physical_device as _,
            device as _,
            queue as _,
            queue_index.try_into().unwrap(),
            Some(global_get_proc),
            instance_extensions.as_ptr(),
            instance_extensions.len(),
            device_extensions.as_ptr(),
            device_extensions.len(),
        );
        drop(resolver);
        BackendContext {
            native: ptr::NonNull::new(native).unwrap(),
            get_proc,
        }
    }

    pub fn set_protected_context(&mut self, protected_context: gpu::Protected) -> &mut Self {
        unsafe {
            sb::C_VulkanBackendContext_setProtectedContext(
                self.native.as_ptr() as _,
                protected_context,
            )
        }
        self
    }

    pub fn set_max_api_version(&mut self, version: impl Into<Version>) -> &mut Self {
        unsafe {
            sb::C_VulkanBackendContext_setMaxAPIVersion(
                self.native.as_ptr() as _,
                *version.into().deref(),
            )
        }
        self
    }

    pub(crate) unsafe fn begin_resolving(&self) -> impl Drop {
        Self::begin_resolving_proc(self.get_proc)
    }

    // The idea here is to set up a thread local variable with the GetProc function trait
    // and reroute queries to global_get_proc as long the caller does not invoke the Drop
    // impl trait that is returned.
    // This is an attempt to support Rust Closures / Functions that resolve function pointers instead
    // of relying on a global extern "C" function.
    // TODO: This is a mess, highly unsafe, and needs to be simplified / rewritten
    //       by someone who understands Rust better.
    unsafe fn begin_resolving_proc(get_proc_trait_object: &dyn GetProc) -> impl Drop {
        THREAD_LOCAL_GET_PROC.with(|get_proc| {
            *get_proc.borrow_mut() = Some(mem::transmute::<&dyn GetProc, TraitObject>(
                get_proc_trait_object,
            ))
        });

        EndResolving {}
    }
}

struct EndResolving {}

impl Drop for EndResolving {
    fn drop(&mut self) {
        THREAD_LOCAL_GET_PROC.with(|get_proc| *get_proc.borrow_mut() = None)
    }
}

thread_local! {
    static THREAD_LOCAL_GET_PROC: RefCell<Option<TraitObject>> = const { RefCell::new(None) };
}

// https://doc.rust-lang.org/1.19.0/std/raw/struct.TraitObject.html
#[repr(C)]
// Copy & Clone are required for the *get_proc.borrow() below. And std::raw::TraitObject
// can not be used, because it's unstable (last checked 1.36).
#[derive(Copy, Clone)]
struct TraitObject {
    pub data: *mut (),
    pub vtable: *mut (),
}

// The global resolvement function passed to Skia.
unsafe extern "C" fn global_get_proc(
    name: *const raw::c_char,
    instance: Instance,
    device: Device,
) -> *const raw::c_void {
    THREAD_LOCAL_GET_PROC.with(|get_proc| {
        match *get_proc.borrow() {
            Some(get_proc) => {
                let get_proc_trait_object: &dyn GetProc = mem::transmute(get_proc);
                if !device.is_null() {
                    get_proc_trait_object(GetProcOf::Device(device, name))
                } else {
                    // note: instance may be null here!
                    get_proc_trait_object(GetProcOf::Instance(instance, name))
                }
            }
            None => panic!("Vulkan GetProc called outside of a thread local resolvement context."),
        }
    })
}