Skip to main content

gpu_handle_types/
device_handles.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Typed newtypes for device-context handles.
4//!
5//! Rather than a bare `*mut c_void` payload behind one blanket
6//! `unsafe impl Send` on an enum, each handle kind gets its own newtype
7//! with one typed `try_from_raw(unsafe { ... })` constructor, so the
8//! caller-side invariants (null rejection, Send/Sync contract) are
9//! asserted once at construction rather than on every pattern-match arm.
10//! [`crate::ExternalQueue`] carries them, as can an interop layer's
11//! sync-target enum.
12//!
13//! All payloads stay raw (`*mut c_void`). Pulling `ash` / `objc2` /
14//! `windows` into `gpu-handle-types` would force every downstream crate
15//! to link the transitive toolchains regardless of platform.
16//!
17//! Each newtype is `Copy` — the wrapper carries no ownership, just the
18//! pointer's bit-pattern. Borrowed via `&'a CudaContext` etc. into
19//! [`crate::ExternalQueue`] (or a sync target); the caller's
20//! producer-side `Arc<...>` (or equivalent) keeps the underlying device
21//! alive for the duration of the borrow.
22
23use core::ffi::c_void;
24use core::fmt;
25
26use crate::gpu_resource::InvalidHandleError;
27
28macro_rules! define_raw_handle {
29    (
30        $(#[$attr:meta])*
31        $name:ident, $payload_label:expr
32    ) => {
33        $(#[$attr])*
34        #[derive(Copy, Clone)]
35        #[repr(transparent)]
36        pub struct $name {
37            ptr: *mut c_void,
38        }
39
40        impl $name {
41            #[doc = concat!("Wrap a raw `", $payload_label, "` in the typed newtype, rejecting null.")]
42            ///
43            /// This is a **borrow**, not a transfer of ownership. The
44            /// newtype is `Copy` and stores nothing but the pointer's
45            /// bit-pattern — no `Arc`, no keep-alive, no `Drop`. It never
46            /// retains, releases, destroys or closes the underlying
47            /// object, so its lifetime stays entirely the caller's (or
48            /// the caller's own producer's) to manage.
49            ///
50            /// # Safety
51            ///
52            #[doc = concat!(
53                "* `ptr` must be a live `", $payload_label, "` — that exact API kind, not \
54                 merely a non-null pointer. The only check performed is the null rejection \
55                 below; the value is otherwise stored verbatim and later handed to the bridge \
56                 dispatcher, which calls into the owning API through it."
57            )]
58            /// * The referenced object must stay alive, and must not be
59            ///   destroyed, for as long as this value or **any** `Copy` of
60            ///   it exists — in particular for the whole of every
61            ///   [`crate::ExternalQueue`] (or sync-target) borrow that
62            ///   names it. Because `Copy` duplicates the pointer with no
63            ///   refcount bump, destroying the object while a copy
64            ///   survives is a use-after-free the borrow checker cannot
65            ///   see. Callers anchor this with the `Arc<…>` (or
66            ///   equivalent) they already hold on the producer side.
67            /// * The object must tolerate use from a thread other than the
68            ///   one that created it, and concurrent use from several:
69            #[doc = concat!("  `", stringify!($name), "` is `Send + Sync`, so a copy may be moved to \
70                 and shared with the interop worker threads. Where the API forbids that — \
71                 recording into one `id<MTLCommandBuffer>` from two threads, or a GL context \
72                 that is current on exactly one thread — the caller must confine the value \
73                 itself; nothing here enforces it.")]
74            /// * `ptr` may be null: that is reported as
75            ///   [`InvalidHandleError::NullPointer`] rather than being
76            ///   undefined behaviour, so a null check is *not* part of
77            ///   the caller's obligation.
78            #[inline]
79            pub unsafe fn try_from_raw(ptr: *mut c_void) -> Result<Self, InvalidHandleError> {
80                if ptr.is_null() {
81                    return Err(InvalidHandleError::NullPointer($payload_label));
82                }
83                Ok(Self { ptr })
84            }
85
86            #[inline]
87            pub fn as_raw(&self) -> *mut c_void {
88                self.ptr
89            }
90        }
91
92        impl fmt::Debug for $name {
93            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94                f.debug_struct(stringify!($name)).finish_non_exhaustive()
95            }
96        }
97
98        // SAFETY: caller asserts the platform permits cross-thread
99        // sharing of the handle (see `try_from_raw`'s `# Safety`). Not
100        // enforced statically.
101        unsafe impl Send for $name {}
102        unsafe impl Sync for $name {}
103    };
104}
105
106define_raw_handle! {
107    /// CUDA `CUcontext` borrowed for the duration of a CUDA sync-target
108    /// dispatch.
109    CudaContext, "CUcontext"
110}
111
112define_raw_handle! {
113    /// CUDA `CUstream` borrowed by `ExternalQueue::CudaStream`. The
114    /// stream's lifetime is anchored by the caller's CUDA context.
115    CudaStreamRef, "CUstream"
116}
117
118define_raw_handle! {
119    /// OpenCL `cl_context` borrowed for an OpenCL sync-target
120    /// dispatch. The platform/device parentage is rederived inside the
121    /// bridge via `clGetContextInfo`.
122    ClContext, "cl_context"
123}
124
125define_raw_handle! {
126    /// OpenCL `cl_command_queue` borrowed by `ExternalQueue::OpenClQueue`.
127    ClQueueRef, "cl_command_queue"
128}
129
130define_raw_handle! {
131    /// `ID3D11Device*` borrowed for a D3D11 sync-target dispatch.
132    /// Caller has set `D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX` or the
133    /// multithread-protect flag — that's the cross-thread contract.
134    D3D11Device, "ID3D11Device*"
135}
136
137define_raw_handle! {
138    /// `ID3D12Device*` borrowed for a D3D12 sync-target dispatch.
139    /// LUID-matched against the source `VkDevice` / Vulkan instance by
140    /// the bridge dispatcher.
141    D3D12Device, "ID3D12Device*"
142}
143
144define_raw_handle! {
145    /// `VkInstance` borrowed for a Vulkan sync-target dispatch.
146    /// Required even for device-level work because ash resolves device
147    /// fn pointers through `vkGetDeviceProcAddr`, which is itself an
148    /// instance-level command (`vkGetInstanceProcAddr(NULL, ...)`
149    /// returns NULL).
150    VkInstance, "VkInstance"
151}
152
153define_raw_handle! {
154    /// `VkDevice` borrowed for a Vulkan sync-target dispatch.
155    VkDevice, "VkDevice"
156}
157
158define_raw_handle! {
159    /// `id<MTLDevice>` borrowed for a Metal sync-target dispatch.
160    MtlDevice, "id<MTLDevice>"
161}
162
163define_raw_handle! {
164    /// `id<MTLCommandBuffer>` borrowed by
165    /// `ExternalQueue::MetalCommandBuffer`. Recording into the same
166    /// command buffer from multiple threads is undefined, and nothing
167    /// here prevents it — the borrow is shared and the newtype is `Copy`,
168    /// so confining recording to one thread is the caller's obligation.
169    MtlCommandBufferRef, "id<MTLCommandBuffer>"
170}
171
172define_raw_handle! {
173    /// Platform-native OpenGL context handle (`EGLContext` / `HGLRC` /
174    /// `CGLContextObj`). The `GlBackend` flavour travels alongside it in
175    /// an OpenGL sync target so the bridge dispatcher resolves the
176    /// matching proc-address loader without re-probing.
177    GlContextRef, "EGLContext/HGLRC/CGLContextObj"
178}