gpu-handle-types 0.2.0

Typed, owned native GPU resource handles (Vulkan, D3D11/12, Metal, OpenGL, CUDA, OpenCL, DMA-BUF, IOSurface, AHardwareBuffer, WebGPU, ...), cross-API sync points and video pixel formats, for passing GPU resources between libraries.
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0

//! Typed newtypes for device-context handles.
//!
//! Rather than a bare `*mut c_void` payload behind one blanket
//! `unsafe impl Send` on an enum, each handle kind gets its own newtype
//! with one typed `try_from_raw(unsafe { ... })` constructor, so the
//! caller-side invariants (null rejection, Send/Sync contract) are
//! asserted once at construction rather than on every pattern-match arm.
//! [`crate::ExternalQueue`] carries them, as can an interop layer's
//! sync-target enum.
//!
//! All payloads stay raw (`*mut c_void`). Pulling `ash` / `objc2` /
//! `windows` into `gpu-handle-types` would force every downstream crate
//! to link the transitive toolchains regardless of platform.
//!
//! Each newtype is `Copy` — the wrapper carries no ownership, just the
//! pointer's bit-pattern. Borrowed via `&'a CudaContext` etc. into
//! [`crate::ExternalQueue`] (or a sync target); the caller's
//! producer-side `Arc<...>` (or equivalent) keeps the underlying device
//! alive for the duration of the borrow.

use core::ffi::c_void;
use core::fmt;

use crate::gpu_resource::InvalidHandleError;

macro_rules! define_raw_handle {
    (
        $(#[$attr:meta])*
        $name:ident, $payload_label:expr
    ) => {
        $(#[$attr])*
        #[derive(Copy, Clone)]
        #[repr(transparent)]
        pub struct $name {
            ptr: *mut c_void,
        }

        impl $name {
            #[doc = concat!("Wrap a raw `", $payload_label, "` in the typed newtype, rejecting null.")]
            ///
            /// This is a **borrow**, not a transfer of ownership. The
            /// newtype is `Copy` and stores nothing but the pointer's
            /// bit-pattern — no `Arc`, no keep-alive, no `Drop`. It never
            /// retains, releases, destroys or closes the underlying
            /// object, so its lifetime stays entirely the caller's (or
            /// the caller's own producer's) to manage.
            ///
            /// # Safety
            ///
            #[doc = concat!(
                "* `ptr` must be a live `", $payload_label, "` — that exact API kind, not \
                 merely a non-null pointer. The only check performed is the null rejection \
                 below; the value is otherwise stored verbatim and later handed to the bridge \
                 dispatcher, which calls into the owning API through it."
            )]
            /// * The referenced object must stay alive, and must not be
            ///   destroyed, for as long as this value or **any** `Copy` of
            ///   it exists — in particular for the whole of every
            ///   [`crate::ExternalQueue`] (or sync-target) borrow that
            ///   names it. Because `Copy` duplicates the pointer with no
            ///   refcount bump, destroying the object while a copy
            ///   survives is a use-after-free the borrow checker cannot
            ///   see. Callers anchor this with the `Arc<…>` (or
            ///   equivalent) they already hold on the producer side.
            /// * The object must tolerate use from a thread other than the
            ///   one that created it, and concurrent use from several:
            #[doc = concat!("  `", stringify!($name), "` is `Send + Sync`, so a copy may be moved to \
                 and shared with the interop worker threads. Where the API forbids that — \
                 recording into one `id<MTLCommandBuffer>` from two threads, or a GL context \
                 that is current on exactly one thread — the caller must confine the value \
                 itself; nothing here enforces it.")]
            /// * `ptr` may be null: that is reported as
            ///   [`InvalidHandleError::NullPointer`] rather than being
            ///   undefined behaviour, so a null check is *not* part of
            ///   the caller's obligation.
            #[inline]
            pub unsafe fn try_from_raw(ptr: *mut c_void) -> Result<Self, InvalidHandleError> {
                if ptr.is_null() {
                    return Err(InvalidHandleError::NullPointer($payload_label));
                }
                Ok(Self { ptr })
            }

            #[inline]
            pub fn as_raw(&self) -> *mut c_void {
                self.ptr
            }
        }

        impl fmt::Debug for $name {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                f.debug_struct(stringify!($name)).finish_non_exhaustive()
            }
        }

        // SAFETY: caller asserts the platform permits cross-thread
        // sharing of the handle (see `try_from_raw`'s `# Safety`). Not
        // enforced statically.
        unsafe impl Send for $name {}
        unsafe impl Sync for $name {}
    };
}

define_raw_handle! {
    /// CUDA `CUcontext` borrowed for the duration of a CUDA sync-target
    /// dispatch.
    CudaContext, "CUcontext"
}

define_raw_handle! {
    /// CUDA `CUstream` borrowed by `ExternalQueue::CudaStream`. The
    /// stream's lifetime is anchored by the caller's CUDA context.
    CudaStreamRef, "CUstream"
}

define_raw_handle! {
    /// OpenCL `cl_context` borrowed for an OpenCL sync-target
    /// dispatch. The platform/device parentage is rederived inside the
    /// bridge via `clGetContextInfo`.
    ClContext, "cl_context"
}

define_raw_handle! {
    /// OpenCL `cl_command_queue` borrowed by `ExternalQueue::OpenClQueue`.
    ClQueueRef, "cl_command_queue"
}

define_raw_handle! {
    /// `ID3D11Device*` borrowed for a D3D11 sync-target dispatch.
    /// Caller has set `D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX` or the
    /// multithread-protect flag — that's the cross-thread contract.
    D3D11Device, "ID3D11Device*"
}

define_raw_handle! {
    /// `ID3D12Device*` borrowed for a D3D12 sync-target dispatch.
    /// LUID-matched against the source `VkDevice` / Vulkan instance by
    /// the bridge dispatcher.
    D3D12Device, "ID3D12Device*"
}

define_raw_handle! {
    /// `VkInstance` borrowed for a Vulkan sync-target dispatch.
    /// Required even for device-level work because ash resolves device
    /// fn pointers through `vkGetDeviceProcAddr`, which is itself an
    /// instance-level command (`vkGetInstanceProcAddr(NULL, ...)`
    /// returns NULL).
    VkInstance, "VkInstance"
}

define_raw_handle! {
    /// `VkDevice` borrowed for a Vulkan sync-target dispatch.
    VkDevice, "VkDevice"
}

define_raw_handle! {
    /// `id<MTLDevice>` borrowed for a Metal sync-target dispatch.
    MtlDevice, "id<MTLDevice>"
}

define_raw_handle! {
    /// `id<MTLCommandBuffer>` borrowed by
    /// `ExternalQueue::MetalCommandBuffer`. Recording into the same
    /// command buffer from multiple threads is undefined, and nothing
    /// here prevents it — the borrow is shared and the newtype is `Copy`,
    /// so confining recording to one thread is the caller's obligation.
    MtlCommandBufferRef, "id<MTLCommandBuffer>"
}

define_raw_handle! {
    /// Platform-native OpenGL context handle (`EGLContext` / `HGLRC` /
    /// `CGLContextObj`). The `GlBackend` flavour travels alongside it in
    /// an OpenGL sync target so the bridge dispatcher resolves the
    /// matching proc-address loader without re-probing.
    GlContextRef, "EGLContext/HGLRC/CGLContextObj"
}