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
//
// Process-global serialization of wgpu device CREATION.
//
// Opening a wgpu device — `Instance::new(Backends::all())` →
// `request_adapter` → `request_device` — spins up and initializes the
// native driver(s) for every backend on every adapter. On a multi-GPU host
// (e.g. an integrated AMD adapter alongside a discrete NVIDIA one) this
// touches BOTH vendors' kernel-mode drivers. Doing it from many threads at
// once — as a test process that opens one device per test thread does, or a
// consumer that spins up an N-device pipeline — drives the vendor drivers'
// device-initialization paths concurrently, and at least one of them is not
// thread-safe under that load: the process dies with a native
// `STATUS_ACCESS_VIOLATION` deep inside the driver DLL (not in Rust code,
// and not catchable as a Rust error).
//
// Closed-source driver code cannot be fixed from here, so this module makes
// the one operation that provokes it — device creation — mutually exclusive
// process-wide. Creation
// is a one-time, ~milliseconds setup step (never a per-frame path), so
// serializing it costs nothing meaningful while everything downstream
// (rendering, readback, decode) stays fully parallel. Every device-creation
// site — a library's device factory, or a consumer that builds a
// `wgpu::Device` directly — should run its driver-initialising calls under
// [`with_device_creation_lock`] so the drivers never see concurrent
// initialization.
//
// The lock is **reentrant**: a consumer holding it around its own
// instance → adapter → device sequence may call into a library factory that
// takes it again for its own calls, on the same thread, without deadlocking.

#![cfg(feature = "wgpu")]

use parking_lot::{ReentrantMutex, ReentrantMutexGuard, const_reentrant_mutex};

static CREATION_LOCK: ReentrantMutex<()> = const_reentrant_mutex(());

/// Acquire the process-global wgpu device-creation lock. Hold the returned
/// guard across the whole `Instance::new → request_adapter → request_device`
/// sequence (and drop it once the device exists). Reentrant on the holding
/// thread, so a library call made inside the sequence that takes the lock
/// itself proceeds instead of deadlocking.
///
/// Prefer [`with_device_creation_lock`] for the common closure shape; this
/// guard form exists for call sites that interleave fallible `?` steps and
/// cannot wrap the whole sequence in one closure.
///
/// Never hold the guard across an `.await`: it is `!Send`, and on a
/// single-threaded executor a second task taking the lock would block the
/// thread. wgpu's native backend does its creation work when
/// `request_adapter` / `request_device` are *called* and returns an
/// already-resolved future, so locking the call alone is sufficient.
pub fn lock_device_creation() -> ReentrantMutexGuard<'static, ()> {
    CREATION_LOCK.lock()
}

/// Run `f` (a wgpu device-creation sequence) under the process-global
/// device-creation lock. See the module docs for why this exists.
pub fn with_device_creation_lock<R>(f: impl FnOnce() -> R) -> R {
    let _guard = lock_device_creation();
    f()
}