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
// 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 any code we
// own, and not catchable as a Rust error).
//
// We cannot fix closed-source driver code, so we make 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.
use ;
static CREATION_LOCK: = 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.
/// Run `f` (a wgpu device-creation sequence) under the process-global
/// device-creation lock. See the module docs for why this exists.