Skip to main content

pixelflux/encoders/
blit_semaphore.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5 */
6
7//! A GPU semaphore an X server's renderer signals and the encoder's CUDA stream waits on, so the
8//! encoder's work on a frame is queued behind the server's blit on the GPU itself instead of after
9//! the CPU has learnt the blit landed ([`crate::x11`]'s DRI3 path, through the SELKIES-SEMAPHORE
10//! extension of the images' Xvfb).
11//!
12//! Neither CUDA nor GL can make a semaphore another API imports, so Vulkan makes it, a binary one
13//! exported as an OPAQUE_FD, on the device whose UUID is the CUDA device's. Two fds of it go out,
14//! one for CUDA's import and one for the server's GL, and the device and the semaphore are gone
15//! again once they are: the imports hold the payload between them. The instance the device is
16//! made from stays for the life of the process ([`Vulkan`]), since destroying one leaks
17//! descriptors in NVIDIA's driver.
18
19use std::ffi::{c_char, c_void};
20use std::os::fd::{FromRawFd, OwnedFd};
21use std::ptr;
22use std::sync::OnceLock;
23
24use libloading::Library;
25
26type VkResult = i32;
27type VkInstance = *mut c_void;
28type VkPhysicalDevice = *mut c_void;
29type VkDevice = *mut c_void;
30type VkSemaphore = u64;
31type VoidFn = Option<unsafe extern "C" fn()>;
32
33const VK_API_VERSION_1_1: u32 = (1 << 22) | (1 << 12);
34const STRUCTURE_TYPE_APPLICATION_INFO: i32 = 0;
35const STRUCTURE_TYPE_INSTANCE_CREATE_INFO: i32 = 1;
36const STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO: i32 = 2;
37const STRUCTURE_TYPE_DEVICE_CREATE_INFO: i32 = 3;
38const STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO: i32 = 9;
39const STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2: i32 = 1000059001;
40const STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES: i32 = 1000071004;
41const STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO: i32 = 1000077000;
42const STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR: i32 = 1000079001;
43const EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD: u32 = 0x1;
44
45#[repr(C)]
46struct ApplicationInfo {
47    s_type: i32,
48    p_next: *const c_void,
49    application_name: *const c_char,
50    application_version: u32,
51    engine_name: *const c_char,
52    engine_version: u32,
53    api_version: u32,
54}
55
56#[repr(C)]
57struct InstanceCreateInfo {
58    s_type: i32,
59    p_next: *const c_void,
60    flags: u32,
61    application_info: *const ApplicationInfo,
62    enabled_layer_count: u32,
63    enabled_layer_names: *const *const c_char,
64    enabled_extension_count: u32,
65    enabled_extension_names: *const *const c_char,
66}
67
68#[repr(C)]
69struct DeviceQueueCreateInfo {
70    s_type: i32,
71    p_next: *const c_void,
72    flags: u32,
73    queue_family_index: u32,
74    queue_count: u32,
75    queue_priorities: *const f32,
76}
77
78#[repr(C)]
79struct DeviceCreateInfo {
80    s_type: i32,
81    p_next: *const c_void,
82    flags: u32,
83    queue_create_info_count: u32,
84    queue_create_infos: *const DeviceQueueCreateInfo,
85    enabled_layer_count: u32,
86    enabled_layer_names: *const *const c_char,
87    enabled_extension_count: u32,
88    enabled_extension_names: *const *const c_char,
89    enabled_features: *const c_void,
90}
91
92#[repr(C)]
93struct PhysicalDeviceIdProperties {
94    s_type: i32,
95    p_next: *mut c_void,
96    device_uuid: [u8; 16],
97    driver_uuid: [u8; 16],
98    device_luid: [u8; 8],
99    device_node_mask: u32,
100    device_luid_valid: u32,
101}
102
103/// `VkPhysicalDeviceProperties2`, with the core properties (824 bytes, 8-aligned) left opaque:
104/// only the ID properties chained after them are read.
105#[repr(C)]
106struct PhysicalDeviceProperties2 {
107    s_type: i32,
108    p_next: *mut c_void,
109    properties: [u64; 103],
110}
111
112#[repr(C)]
113struct ExportSemaphoreCreateInfo {
114    s_type: i32,
115    p_next: *const c_void,
116    handle_types: u32,
117}
118
119#[repr(C)]
120struct SemaphoreCreateInfo {
121    s_type: i32,
122    p_next: *const c_void,
123    flags: u32,
124}
125
126#[repr(C)]
127struct SemaphoreGetFdInfo {
128    s_type: i32,
129    p_next: *const c_void,
130    semaphore: VkSemaphore,
131    handle_type: u32,
132}
133
134type GetInstanceProcAddr = unsafe extern "C" fn(VkInstance, *const c_char) -> VoidFn;
135type CreateInstance =
136    unsafe extern "C" fn(*const InstanceCreateInfo, *const c_void, *mut VkInstance) -> VkResult;
137type DestroyInstance = unsafe extern "C" fn(VkInstance, *const c_void);
138type EnumeratePhysicalDevices =
139    unsafe extern "C" fn(VkInstance, *mut u32, *mut VkPhysicalDevice) -> VkResult;
140type GetPhysicalDeviceProperties2 =
141    unsafe extern "C" fn(VkPhysicalDevice, *mut PhysicalDeviceProperties2);
142type CreateDevice = unsafe extern "C" fn(
143    VkPhysicalDevice,
144    *const DeviceCreateInfo,
145    *const c_void,
146    *mut VkDevice,
147) -> VkResult;
148type GetDeviceProcAddr = unsafe extern "C" fn(VkDevice, *const c_char) -> VoidFn;
149type DestroyDevice = unsafe extern "C" fn(VkDevice, *const c_void);
150type CreateSemaphore = unsafe extern "C" fn(
151    VkDevice,
152    *const SemaphoreCreateInfo,
153    *const c_void,
154    *mut VkSemaphore,
155) -> VkResult;
156type DestroySemaphore = unsafe extern "C" fn(VkDevice, VkSemaphore, *const c_void);
157type GetSemaphoreFd =
158    unsafe extern "C" fn(VkDevice, *const SemaphoreGetFdInfo, *mut i32) -> VkResult;
159
160/// The Vulkan loader and the one instance every semaphore's device is made from, kept for the life
161/// of the process. NVIDIA's driver leaves a `/dev/nvidiactl` and a render-node descriptor open for
162/// every instance it destroys while `DISPLAY` names an X server without NV-GLX, as the images' Xvfb
163/// is, so an instance per capture start runs a server that streams to reconnecting clients out of
164/// descriptors; the devices made from a kept instance are destroyed without leaking any.
165struct Vulkan {
166    _lib: Library,
167    instance: VkInstance,
168    enumerate: EnumeratePhysicalDevices,
169    properties: GetPhysicalDeviceProperties2,
170    create_device: CreateDevice,
171    get_device_proc: GetDeviceProcAddr,
172}
173
174// SAFETY: the instance is never destroyed, and the instance-level commands called on it
175// (enumerating its physical devices, reading their properties, creating a device) need no external
176// synchronization.
177unsafe impl Send for Vulkan {}
178unsafe impl Sync for Vulkan {}
179
180impl Vulkan {
181    /// The process's instance, made by the first call; a host whose loader or driver refused it
182    /// gets the same refusal every time.
183    fn get() -> Result<&'static Vulkan, String> {
184        static VULKAN: OnceLock<Result<Vulkan, String>> = OnceLock::new();
185        VULKAN
186            .get_or_init(|| unsafe { Self::open() })
187            .as_ref()
188            .map_err(Clone::clone)
189    }
190
191    /// Load the loader and create the instance, with no layers or extensions.
192    ///
193    /// # Safety
194    ///
195    /// `libvulkan.so.1` has to be the Vulkan loader, whose exported entry points have the
196    /// signatures this module declares.
197    unsafe fn open() -> Result<Self, String> {
198        unsafe {
199            let lib = Library::new("libvulkan.so.1")
200                .map_err(|e| format!("no Vulkan loader (libvulkan.so.1): {e}"))?;
201            let get_instance_proc: GetInstanceProcAddr = *lib
202                .get(b"vkGetInstanceProcAddr\0")
203                .map_err(|e| format!("no vkGetInstanceProcAddr: {e}"))?;
204            macro_rules! instance_fn {
205                ($instance:expr, $ty:ty, $name:literal) => {
206                    match get_instance_proc($instance, $name.as_ptr()) {
207                        Some(f) => Ok(std::mem::transmute::<unsafe extern "C" fn(), $ty>(f)),
208                        None => Err(format!("no {}", $name.to_string_lossy())),
209                    }
210                };
211            }
212            let create_instance: CreateInstance =
213                instance_fn!(ptr::null_mut(), CreateInstance, c"vkCreateInstance")?;
214            let app = ApplicationInfo {
215                s_type: STRUCTURE_TYPE_APPLICATION_INFO,
216                p_next: ptr::null(),
217                application_name: c"pixelflux".as_ptr(),
218                application_version: 1,
219                engine_name: ptr::null(),
220                engine_version: 0,
221                api_version: VK_API_VERSION_1_1,
222            };
223            let instance_info = InstanceCreateInfo {
224                s_type: STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
225                p_next: ptr::null(),
226                flags: 0,
227                application_info: &app,
228                enabled_layer_count: 0,
229                enabled_layer_names: ptr::null(),
230                enabled_extension_count: 0,
231                enabled_extension_names: ptr::null(),
232            };
233            let mut instance: VkInstance = ptr::null_mut();
234            let r = create_instance(&instance_info, ptr::null(), &mut instance);
235            if r != 0 {
236                return Err(format!("vkCreateInstance: {r}"));
237            }
238            let destroy_instance: DestroyInstance =
239                instance_fn!(instance, DestroyInstance, c"vkDestroyInstance")?;
240            let entry_points = (|| {
241                Ok((
242                    instance_fn!(
243                        instance,
244                        EnumeratePhysicalDevices,
245                        c"vkEnumeratePhysicalDevices"
246                    )?,
247                    instance_fn!(
248                        instance,
249                        GetPhysicalDeviceProperties2,
250                        c"vkGetPhysicalDeviceProperties2"
251                    )?,
252                    instance_fn!(instance, CreateDevice, c"vkCreateDevice")?,
253                    instance_fn!(instance, GetDeviceProcAddr, c"vkGetDeviceProcAddr")?,
254                ))
255            })();
256            match entry_points {
257                Ok((enumerate, properties, create_device, get_device_proc)) => Ok(Self {
258                    _lib: lib,
259                    instance,
260                    enumerate,
261                    properties,
262                    create_device,
263                    get_device_proc,
264                }),
265                Err(e) => {
266                    destroy_instance(instance, ptr::null());
267                    Err(e)
268                }
269            }
270        }
271    }
272}
273
274/// Two fds of one new binary semaphore exportable as OPAQUE_FD, made on the Vulkan device whose
275/// UUID is `uuid` (the CUDA device's, as `cuDeviceGetUuid` reports it).
276pub(crate) fn opaque_fd_pair(uuid: [u8; 16]) -> Result<(OwnedFd, OwnedFd), String> {
277    let vk = Vulkan::get()?;
278    let instance = vk.instance;
279    unsafe {
280        let mut count = 0u32;
281        (vk.enumerate)(instance, &mut count, ptr::null_mut());
282        let mut devices: Vec<VkPhysicalDevice> = Vec::with_capacity(count as usize);
283        (vk.enumerate)(instance, &mut count, devices.as_mut_ptr());
284        devices.set_len(count as usize);
285        let physical = devices
286            .into_iter()
287            .find(|&d| {
288                let mut id = PhysicalDeviceIdProperties {
289                    s_type: STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES,
290                    p_next: ptr::null_mut(),
291                    device_uuid: [0; 16],
292                    driver_uuid: [0; 16],
293                    device_luid: [0; 8],
294                    device_node_mask: 0,
295                    device_luid_valid: 0,
296                };
297                let mut props = PhysicalDeviceProperties2 {
298                    s_type: STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
299                    p_next: (&mut id as *mut PhysicalDeviceIdProperties).cast(),
300                    properties: [0; 103],
301                };
302                (vk.properties)(d, &mut props);
303                id.device_uuid == uuid
304            })
305            .ok_or("no Vulkan device has the CUDA device's UUID")?;
306
307        let priority = 1.0f32;
308        let queue = DeviceQueueCreateInfo {
309            s_type: STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
310            p_next: ptr::null(),
311            flags: 0,
312            queue_family_index: 0,
313            queue_count: 1,
314            queue_priorities: &priority,
315        };
316        let extensions = [c"VK_KHR_external_semaphore_fd".as_ptr()];
317        let device_info = DeviceCreateInfo {
318            s_type: STRUCTURE_TYPE_DEVICE_CREATE_INFO,
319            p_next: ptr::null(),
320            flags: 0,
321            queue_create_info_count: 1,
322            queue_create_infos: &queue,
323            enabled_layer_count: 0,
324            enabled_layer_names: ptr::null(),
325            enabled_extension_count: 1,
326            enabled_extension_names: extensions.as_ptr(),
327            enabled_features: ptr::null(),
328        };
329        let mut device: VkDevice = ptr::null_mut();
330        let r = (vk.create_device)(physical, &device_info, ptr::null(), &mut device);
331        if r != 0 {
332            return Err(format!("vkCreateDevice: {r}"));
333        }
334        macro_rules! device_fn {
335            ($ty:ty, $name:literal) => {
336                match (vk.get_device_proc)(device, $name.as_ptr()) {
337                    Some(f) => std::mem::transmute::<unsafe extern "C" fn(), $ty>(f),
338                    None => return Err(format!("no {}", $name.to_string_lossy())),
339                }
340            };
341        }
342        let destroy_device: DestroyDevice = device_fn!(DestroyDevice, c"vkDestroyDevice");
343        let fds = (|| {
344            let create_semaphore = device_fn!(CreateSemaphore, c"vkCreateSemaphore");
345            let destroy_semaphore = device_fn!(DestroySemaphore, c"vkDestroySemaphore");
346            let get_fd = device_fn!(GetSemaphoreFd, c"vkGetSemaphoreFdKHR");
347            let export = ExportSemaphoreCreateInfo {
348                s_type: STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO,
349                p_next: ptr::null(),
350                handle_types: EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD,
351            };
352            let info = SemaphoreCreateInfo {
353                s_type: STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
354                p_next: (&export as *const ExportSemaphoreCreateInfo).cast(),
355                flags: 0,
356            };
357            let mut semaphore: VkSemaphore = 0;
358            let r = create_semaphore(device, &info, ptr::null(), &mut semaphore);
359            if r != 0 {
360                return Err(format!("vkCreateSemaphore: {r}"));
361            }
362            let fd_info = SemaphoreGetFdInfo {
363                s_type: STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR,
364                p_next: ptr::null(),
365                semaphore,
366                handle_type: EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD,
367            };
368            let export_one = || -> Result<OwnedFd, String> {
369                let mut fd = -1;
370                let r = get_fd(device, &fd_info, &mut fd);
371                if r != 0 || fd < 0 {
372                    return Err(format!("vkGetSemaphoreFdKHR: {r}"));
373                }
374                Ok(OwnedFd::from_raw_fd(fd))
375            };
376            let pair = export_one().and_then(|a| Ok((a, export_one()?)));
377            destroy_semaphore(device, semaphore, ptr::null());
378            pair
379        })();
380        destroy_device(device, ptr::null());
381        fds
382    }
383}