gpu_handle_types/gpu_context.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! `GpuContext` — host-provided GPU context. The same value can feed a
4//! decoder's, an encoder's and an interop layer's external-context
5//! constructor.
6//!
7//! `GpuContext` is **not `Clone`**. Share it with `Arc<GpuContext>` — the
8//! identity of the context is meaningful, and cloning a raw D3D11 device
9//! pointer without matching `AddRef` is a use-after-free waiting to
10//! happen. Wrapping in `Arc` gives reference semantics without a clone
11//! footgun.
12
13use std::ffi::c_void;
14
15use crate::{BackendKind, DeviceId};
16
17#[non_exhaustive]
18pub enum GpuContext {
19 Cpu,
20 OpenCl {
21 context: *mut c_void,
22 queue: *mut c_void,
23 device: *mut c_void,
24 /// Caller-asserted GL-sharing properties for `context`.
25 ///
26 /// When set, the caller asserts that `context` was created with
27 /// `CL_GL_CONTEXT_KHR` + the matching platform property
28 /// (`CL_WGL_HDC_KHR` on Windows desktop, `CL_GLX_DISPLAY_KHR`
29 /// on Linux X11, `CL_EGL_DISPLAY_KHR` on EGL/ANGLE,
30 /// `CL_CGL_SHAREGROUP_KHR` on macOS CGL) pointing at the GL
31 /// display + context recorded here. Required by the wgpu-GL
32 /// import path (`cl_khr_gl_sharing` route) — without it, the
33 /// downstream consumer would have to take the
34 /// `cl_khr_external_memory` cross-context fallback (with its
35 /// ~250–400 µs extra setup cost) or the explicit CPU-bounce
36 /// opt-in.
37 ///
38 /// `None` is correct when the caller's CL context is not
39 /// GL-shared (compute-only pipelines that never feed wgpu-GL).
40 /// SDK decoders that feed wgpu-GL can refuse to start with a
41 /// non-GL-shared CL context.
42 gl_sharing: Option<OpenClGlSharing>,
43 },
44 /// External CUDA context + stream owned by the caller.
45 ///
46 /// Lifetime contract: `context` and `stream` must remain valid for
47 /// the lifetime of every resource/handle minted through an interop
48 /// / decoder / encoder built on this context — including
49 /// `Drop`-time teardown, which may run on arbitrary threads long
50 /// after the mint. Downstream keep-alives capture the raw
51 /// `CUcontext` and re-bind it to destroy per-context tokens
52 /// (e.g. `CUsurfObject`); a context destroyed early cannot be
53 /// detected — the driver may recycle the address, resolving those
54 /// tokens against an unrelated context.
55 Cuda {
56 context: *mut c_void,
57 stream: *mut c_void,
58 device_id: i32,
59 uuid: Option<[u8; 16]>,
60 },
61 Metal {
62 device: *mut c_void,
63 queue: *mut c_void,
64 registry_id: Option<u64>,
65 },
66 D3D11 {
67 device: *mut c_void,
68 device_context: *mut c_void,
69 luid: Option<(i32, u32)>,
70 },
71 D3D12 {
72 device: *mut c_void,
73 queue: *mut c_void,
74 luid: Option<(i32, u32)>,
75 },
76 Vulkan {
77 instance: *mut c_void,
78 physical_device: *mut c_void,
79 device: *mut c_void,
80 queue: u64,
81 queue_family_index: u32,
82 uuid: Option<[u8; 16]>,
83 },
84 /// External OpenGL / OpenGL ES context owned by the caller.
85 ///
86 /// `display` / `context` are the caller's platform-native identifiers
87 /// — `EGLDisplay`+`EGLContext` on EGL, `HDC`+`HGLRC` on WGL,
88 /// `CGLContextObj` on CGL (with `display` mirroring `context`).
89 ///
90 /// `share_group` is an opaque identifier the caller uses to stamp
91 /// share-group identity. Two `OpenGL` contexts with the same non-
92 /// `None` `share_group` are assumed to share GL object names;
93 /// `None` means "solo context, no sharing guarantees". There is no
94 /// runtime query for this on EGL or WGL, so the value is purely
95 /// caller-supplied.
96 OpenGL {
97 display: *mut c_void,
98 context: *mut c_void,
99 share_group: Option<u64>,
100 backend: GlBackend,
101 },
102 /// Caller-supplied `wgpu` device, queue, and the adapter the device
103 /// was created from.
104 ///
105 /// `adapter` is load-bearing for per-format capability probes:
106 /// `wgpu::Adapter::get_texture_format_features(...)` is the only
107 /// reliable way to ask whether a given format supports
108 /// `STORAGE_BINDING`, `RENDER_ATTACHMENT`, etc. on the caller's
109 /// physical device. `wgpu::Device::features()` reports the
110 /// device-creation-time *requested* feature set, which is a strict
111 /// subset of what the adapter can actually do per format —
112 /// inadequate for the probe (e.g. on a desktop Vulkan adapter R8
113 /// storage is available without any feature flag, so `Device::features()`
114 /// can't tell you).
115 ///
116 /// External-context-mode factories run their per-format capability
117 /// detection against the borrowed `adapter`; the alternative —
118 /// enumerating fresh adapters from a new `wgpu::Instance` and
119 /// guessing which one matches the device — is incorrect on
120 /// multi-GPU systems, which is why this field exists.
121 #[cfg(feature = "wgpu")]
122 Wgpu {
123 device: std::sync::Arc<wgpu::Device>,
124 queue: std::sync::Arc<wgpu::Queue>,
125 adapter: std::sync::Arc<wgpu::Adapter>,
126 },
127
128 /// Caller-supplied `wgpu` device + queue running the **WebGPU**
129 /// backend (`wgpu::Backend::BrowserWebGpu`). wasm-only.
130 ///
131 /// `raw_device` is the caller's own `web_sys::GpuDevice`, retained so
132 /// same-device `GPUTexture` imports can be identity-compared
133 /// (`Object.is`) against it — the WebGPU spec exposes no
134 /// `GPUTexture.device` reflection. `None` on owned-device mode (we
135 /// created the device, the identity check is automatic); `Some` on
136 /// external-context mode (the host hands us their handle).
137 #[cfg(all(target_family = "wasm", feature = "wgpu", feature = "web"))]
138 WebGpu {
139 device: std::sync::Arc<wgpu::Device>,
140 queue: std::sync::Arc<wgpu::Queue>,
141 raw_device: Option<web_sys::GpuDevice>,
142 },
143
144 /// Caller-supplied `wgpu` device + queue running the **WebGL2**
145 /// backend (`wgpu::Backend::Gl` on wasm). wasm-only.
146 ///
147 /// `raw_context` is the caller's own `web_sys::WebGl2RenderingContext`,
148 /// retained so same-context `WebGLTexture` imports and exports can be
149 /// identity-compared against it. `None` on owned-device mode; `Some`
150 /// on external-context mode.
151 ///
152 /// Gated on `webgl` (not `web`): this variant exists to construct a
153 /// WebGL2-backed interop, which needs the wgpu WebGL accessors — merged
154 /// upstream, but in no wgpu release yet, so a git `[patch]` on wgpu trunk.
155 #[cfg(all(target_family = "wasm", feature = "wgpu", feature = "webgl"))]
156 WebGl {
157 device: std::sync::Arc<wgpu::Device>,
158 queue: std::sync::Arc<wgpu::Queue>,
159 raw_context: Option<web_sys::WebGl2RenderingContext>,
160 },
161}
162
163/// Caller-asserted GL-sharing handles for an `OpenCl` context.
164///
165/// Mirrors the (display, context, backend) triple from
166/// [`GpuContext::OpenGL`] — these are the same values the caller
167/// passed (or would pass) into `clCreateContext`'s
168/// `cl_context_properties[]` array as the values of
169/// `CL_GL_CONTEXT_KHR` and the matching platform property.
170///
171/// **No share-group**: `cl_khr_gl_sharing`'s property array binds the
172/// CL context to a specific (display, context) pair, not to a share
173/// group. Cross-share-group import is not standardised at the CL spec
174/// level — the right escape hatch is `cl_khr_external_memory` (an
175/// interop bridge layer's job), not a share-group field here.
176#[derive(Copy, Clone)]
177pub struct OpenClGlSharing {
178 pub display: *mut c_void,
179 pub context: *mut c_void,
180 pub backend: GlBackend,
181}
182
183impl core::fmt::Debug for OpenClGlSharing {
184 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
185 f.debug_struct("OpenClGlSharing").field("backend", &self.backend).finish_non_exhaustive()
186 }
187}
188
189// Same Send/Sync stance as the parent `GpuContext` — the handles are
190// opaque pointers + plain data, caller owns lifecycle.
191unsafe impl Send for OpenClGlSharing {}
192unsafe impl Sync for OpenClGlSharing {}
193
194/// Which GL context flavour [`GpuContext::OpenGL::context`] points at.
195///
196/// `Desktop` / `Egl` / `Angle(_)` distinguish which procedure-loader an
197/// interop layer uses (`wglGetProcAddress` + `opengl32.dll`, `eglGetProcAddress`,
198/// `dlsym` on the relevant `.framework`). `Web` is reserved for wasm /
199/// WebGL2 callers; cross-API interop isn't reachable there.
200#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
201#[non_exhaustive]
202pub enum GlBackend {
203 Desktop,
204 Egl,
205 Angle(AngleBackend),
206 Web,
207}
208
209/// Underlying implementation of an ANGLE "GL" context. Probed via
210/// `glGetString(GL_RENDERER)` + the matching `EGL_ANGLE_*` extension
211/// when `GlBackend::Angle(_)` is selected.
212#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
213#[non_exhaustive]
214pub enum AngleBackend {
215 Vulkan,
216 D3D11,
217 D3D12,
218 Metal,
219}
220
221/// Host-supplied scheduler that posts a closure onto the thread that
222/// owns a particular OpenGL context.
223///
224/// ## Why this exists
225///
226/// `GLuint` deletion (`glDeleteSemaphoresEXT`, `glDeleteTextures`, etc.)
227/// MUST run on a thread where the owning GL context is current — the GL
228/// namespace is thread-local and a delete dispatched against a foreign
229/// or null context is rejected as `GL_INVALID_OPERATION`. An interop
230/// layer's GL bridges (`wgpu-interop`'s, for instance) produce GL-side
231/// objects whose `Drop` may run on arbitrary threads in finalizer-driven
232/// hosts (JNI / .NET / GC-managed wrappers): producer-side
233/// `Arc<dyn SyncWaiter>` keep-alives, retained imports and process-wide
234/// producer-identity caches all hold strong references that are
235/// routinely released by threads with no GL context current.
236///
237/// Without an executor, those Drops can only queue the GL name onto a
238/// pending-delete bucket that is drained when:
239///
240/// - any thread that currently holds the matching GL context current
241/// reaches one of the GL-side bridge entry points (an opportunistic
242/// drain), or
243/// - the host explicitly asks the interop layer to prune its GL bridge
244/// objects from a thread that holds the matching context current.
245///
246/// Hosts whose pipeline cannot guarantee either path (e.g. a shutdown
247/// flow where the bridge layer has already stopped accepting work but
248/// the language-runtime GC keeps releasing the parked Arcs) should
249/// implement this trait and register an instance with the interop
250/// layer. Once registered, off-thread `Drop`s for that context are
251/// routed through the executor and run on the GL-owning thread directly.
252///
253/// ## Contract
254///
255/// `execute(target_ctx, task)` MUST eventually run `task` on a thread
256/// where the GL context identified by `target_ctx` is current. The
257/// implementation is allowed to:
258///
259/// - drop the closure unrun if the GL context is being torn down
260/// (the namespace dies with the context, so any leaked GL name is
261/// reclaimed); the only consequence is a small bump in the deferred-
262/// delete bucket until the next bridge call drains it.
263/// - run the closure synchronously when the calling thread already
264/// holds the right context current. The closure does not depend on
265/// any state outside its capture.
266///
267/// `target_ctx` is the raw `EGLContext` / `HGLRC` / `CGLContextObj`
268/// cast to `usize`. Hosts running a single GL thread can ignore the
269/// argument and post unconditionally; multi-context hosts use it to
270/// route to the correct thread/queue.
271pub trait GlContextExecutor: Send + Sync + 'static {
272 /// Schedule `task` to run on the GL-owning thread for the context
273 /// identified by `target_ctx`. See trait docs for the contract.
274 fn execute(&self, target_ctx: usize, task: Box<dyn FnOnce() + Send + 'static>);
275}
276
277// Caller asserts host platform permits cross-thread sharing.
278//
279// `Sync` is asserted on the same grounds as `Send`: once an external
280// caller hands us a `GpuContext`, the handles inside are immutable
281// pointers + plain data. Cross-thread access to those pointers is
282// safe — what is NOT safe is calling the *underlying device* from
283// multiple threads simultaneously, which is the caller's
284// responsibility regardless of our Sync stance. Without `Sync`,
285// `Arc<GpuContext>` would not be `Send`, breaking the `Send` invariant
286// of any decoder / encoder / resampler that holds an
287// `Option<Arc<GpuContext>>` field and must cross a thread handoff
288// (a prefetch thread, say).
289//
290// **wasm carve-out.** These blanket impls are `cfg(not(target_family =
291// "wasm"))`: on wasm the `WebGpu` / `WebGl` variants carry
292// `web_sys::GpuDevice` / `web_sys::WebGl2RenderingContext` (and
293// `Arc<wgpu::Device>`, which is itself `!Send` on wasm) — all
294// inherently thread-affine `JsValue` wrappers — so asserting `Send` /
295// `Sync` would be **unsound**. Removing the blanket impls leaves
296// `GpuContext` structurally `!Send + !Sync` on wasm (the raw-pointer
297// variants are `!Send` too). That is correct for the web target: it is
298// single-threaded, one interop instance per Worker, and such holders'
299// `Option<Arc<GpuContext>>` fields become `!Send` there as well — no
300// Rust thread handoff exists to break (cross-Worker handoff is
301// `postMessage` / `Transferable`, not thread movement). Native builds
302// keep `Send + Sync`.
303#[cfg(not(target_family = "wasm"))]
304unsafe impl Send for GpuContext {}
305#[cfg(not(target_family = "wasm"))]
306unsafe impl Sync for GpuContext {}
307
308impl GpuContext {
309 #[cfg(feature = "wgpu")]
310 pub fn from_wgpu(
311 device: std::sync::Arc<wgpu::Device>,
312 queue: std::sync::Arc<wgpu::Queue>,
313 adapter: std::sync::Arc<wgpu::Adapter>,
314 ) -> Self {
315 Self::Wgpu { device, queue, adapter }
316 }
317
318 pub fn backend(&self) -> BackendKind {
319 match self {
320 Self::Cpu => BackendKind::Cpu,
321 #[cfg(feature = "wgpu")]
322 Self::Wgpu { .. } => BackendKind::Wgpu,
323 // The web variants front a `wgpu::Device` (WebGPU / WebGL2
324 // backend); report `Wgpu` like the generic `Wgpu` context. `WebGl`
325 // is gated on `webgl`, `WebGpu` on `web`.
326 #[cfg(all(target_family = "wasm", feature = "wgpu", feature = "web"))]
327 Self::WebGpu { .. } => BackendKind::Wgpu,
328 #[cfg(all(target_family = "wasm", feature = "wgpu", feature = "webgl"))]
329 Self::WebGl { .. } => BackendKind::Wgpu,
330 #[cfg(feature = "opencl")]
331 Self::OpenCl { .. } => BackendKind::OpenCl,
332 #[cfg(not(feature = "opencl"))]
333 Self::OpenCl { .. } => BackendKind::Cpu,
334 #[cfg(feature = "cuda")]
335 Self::Cuda { .. } => BackendKind::Cuda,
336 #[cfg(not(feature = "cuda"))]
337 Self::Cuda { .. } => BackendKind::Cpu,
338 // Metal / D3D11 / D3D12 / Vulkan / OpenGL have no dedicated
339 // BackendKind (the wgpu backend fronts them all).
340 // Report Cpu when wgpu isn't compiled in; otherwise the host
341 // would have provided `Wgpu` for GPU work.
342 #[cfg(feature = "wgpu")]
343 Self::Metal { .. }
344 | Self::D3D11 { .. }
345 | Self::D3D12 { .. }
346 | Self::Vulkan { .. }
347 | Self::OpenGL { .. } => BackendKind::Wgpu,
348 #[cfg(not(feature = "wgpu"))]
349 Self::Metal { .. }
350 | Self::D3D11 { .. }
351 | Self::D3D12 { .. }
352 | Self::Vulkan { .. }
353 | Self::OpenGL { .. } => BackendKind::Cpu,
354 }
355 }
356
357 /// Returns `(cuCtx, cuStream)` for the `Cuda` variant; `Err`
358 /// otherwise. SDK decoders use this to avoid pattern-matching the
359 /// `GpuContext` enum at every call site.
360 pub fn cuda_context_and_stream(&self) -> Result<(*mut c_void, *mut c_void), GpuContextError> {
361 match self {
362 Self::Cuda { context, stream, .. } => Ok((*context, *stream)),
363 other => {
364 Err(GpuContextError::LaneMismatch { actual: other.backend(), requested: "cuda_context_and_stream" })
365 }
366 }
367 }
368
369 /// Returns `(cl_context, cl_command_queue)` for the `OpenCl`
370 /// variant; `Err` otherwise.
371 pub fn opencl_context_and_queue(&self) -> Result<(*mut c_void, *mut c_void), GpuContextError> {
372 match self {
373 Self::OpenCl { context, queue, .. } => Ok((*context, *queue)),
374 other => {
375 Err(GpuContextError::LaneMismatch { actual: other.backend(), requested: "opencl_context_and_queue" })
376 }
377 }
378 }
379
380 /// Returns the `MTLCommandQueue` handle for the `Metal` variant;
381 /// `Err` otherwise.
382 pub fn metal_command_queue(&self) -> Result<*mut c_void, GpuContextError> {
383 match self {
384 Self::Metal { queue, .. } => Ok(*queue),
385 other => Err(GpuContextError::LaneMismatch { actual: other.backend(), requested: "metal_command_queue" }),
386 }
387 }
388
389 /// Returns the `MTLDevice` handle for the `Metal` variant; `Err`
390 /// otherwise.
391 pub fn metal_device(&self) -> Result<*mut c_void, GpuContextError> {
392 match self {
393 Self::Metal { device, .. } => Ok(*device),
394 other => Err(GpuContextError::LaneMismatch { actual: other.backend(), requested: "metal_device" }),
395 }
396 }
397
398 /// CUDA device UUID for the `Cuda` variant — populated when the
399 /// caller filled the `uuid` field. Used to stamp
400 /// `GpuResource::CudaPtr2D` for cross-API import identity matching.
401 pub fn cuda_uuid(&self) -> Result<[u8; 16], GpuContextError> {
402 match self {
403 Self::Cuda { uuid: Some(u), .. } => Ok(*u),
404 Self::Cuda { uuid: None, .. } => Err(GpuContextError::MissingField("Cuda::uuid")),
405 other => Err(GpuContextError::LaneMismatch { actual: other.backend(), requested: "cuda_uuid" }),
406 }
407 }
408
409 /// CUDA device id (ordinal) for the `Cuda` variant.
410 pub fn cuda_device_id(&self) -> Result<i32, GpuContextError> {
411 match self {
412 Self::Cuda { device_id, .. } => Ok(*device_id),
413 other => Err(GpuContextError::LaneMismatch { actual: other.backend(), requested: "cuda_device_id" }),
414 }
415 }
416
417 /// Metal IORegistry id for the `Metal` variant — populated when
418 /// the caller filled the `registry_id` field.
419 pub fn metal_registry_id(&self) -> Result<u64, GpuContextError> {
420 match self {
421 Self::Metal { registry_id: Some(id), .. } => Ok(*id),
422 Self::Metal { registry_id: None, .. } => Err(GpuContextError::MissingField("Metal::registry_id")),
423 other => Err(GpuContextError::LaneMismatch { actual: other.backend(), requested: "metal_registry_id" }),
424 }
425 }
426
427 /// OpenCL `cl_device_id` for the `OpenCl` variant.
428 pub fn opencl_device(&self) -> Result<*mut c_void, GpuContextError> {
429 match self {
430 Self::OpenCl { device, .. } => Ok(*device),
431 other => Err(GpuContextError::LaneMismatch { actual: other.backend(), requested: "opencl_device" }),
432 }
433 }
434
435 /// GL-sharing handles for the `OpenCl` variant, if the caller set
436 /// them at construction. `Ok(None)` means the variant matches but
437 /// no sharing was declared; `Err(LaneMismatch)` means the caller
438 /// asked for OpenCL on a non-OpenCL context.
439 pub fn opencl_gl_sharing(&self) -> Result<Option<OpenClGlSharing>, GpuContextError> {
440 match self {
441 Self::OpenCl { gl_sharing, .. } => Ok(*gl_sharing),
442 other => Err(GpuContextError::LaneMismatch { actual: other.backend(), requested: "opencl_gl_sharing" }),
443 }
444 }
445
446 /// Stable identity of the underlying device, when the context carries
447 /// enough information to construct one. `Wgpu` returns `None`
448 /// here — wgpu-side device lookup belongs to the interop layer.
449 pub fn device_id(&self) -> Option<DeviceId> {
450 match self {
451 Self::Cpu => None,
452 Self::Cuda { uuid: Some(u), .. } => Some(DeviceId::CudaUuid(*u)),
453 Self::Cuda { .. } => None,
454 Self::D3D11 { luid: Some((h, l)), .. } => Some(DeviceId::DxgiLuid { high: *h, low: *l }),
455 Self::D3D12 { luid: Some((h, l)), .. } => Some(DeviceId::DxgiLuid { high: *h, low: *l }),
456 Self::D3D11 { .. } | Self::D3D12 { .. } => None,
457 Self::Metal { registry_id: Some(id), .. } => Some(DeviceId::MetalRegistryId(*id)),
458 Self::Metal { .. } => None,
459 Self::Vulkan { uuid: Some(u), .. } => Some(DeviceId::VulkanUuid(*u)),
460 Self::Vulkan { .. } => None,
461 Self::OpenCl { .. } => None, // Interop layer: platform/device string probe.
462 // OpenGL has no cross-driver stable device identity in core
463 // — `GL_EXT_memory_object` exposes `GL_DEVICE_UUID_EXT` but
464 // that's the underlying Vulkan/D3D UUID. Callers who need
465 // device identity for a GL context should pair with
466 // `GpuContext::Vulkan` / `::D3D12` on the same physical GPU.
467 Self::OpenGL { .. } => None,
468 #[cfg(feature = "wgpu")]
469 Self::Wgpu { .. } => None,
470 // Web device identity lives in the interop layer (raw
471 // `GpuDevice` / `WebGl2RenderingContext` JS-value identity),
472 // not in this stable `DeviceId` enum — same posture as `Wgpu`.
473 // `WebGl` is gated on `webgl`, `WebGpu` on `web`.
474 #[cfg(all(target_family = "wasm", feature = "wgpu", feature = "web"))]
475 Self::WebGpu { .. } => None,
476 #[cfg(all(target_family = "wasm", feature = "wgpu", feature = "webgl"))]
477 Self::WebGl { .. } => None,
478 }
479 }
480}
481
482/// Failure modes for the typed `GpuContext` unpackers above.
483///
484/// Errors here are caller-fault (mismatched lane, missing optional
485/// field) — they don't indicate device loss or runtime trouble.
486#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
487#[non_exhaustive]
488pub enum GpuContextError {
489 /// Caller asked for a per-lane accessor that doesn't match the
490 /// `GpuContext` variant the caller supplied (e.g. asking for
491 /// `cuda_context_and_stream` on a `Metal` context).
492 #[error("GpuContext lane {actual:?} cannot satisfy a {requested} accessor")]
493 LaneMismatch { actual: BackendKind, requested: &'static str },
494 /// The variant matched but an optional field (`uuid`,
495 /// `registry_id`, …) was left `None` by the caller.
496 #[error("optional field {0} not populated on this GpuContext")]
497 MissingField(&'static str),
498}
499
500impl core::fmt::Debug for GpuContext {
501 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
502 // Do not print raw pointers.
503 match self {
504 Self::Cpu => f.write_str("GpuContext::Cpu"),
505 Self::OpenCl { gl_sharing, .. } => {
506 f.debug_struct("OpenCl").field("gl_sharing", gl_sharing).finish_non_exhaustive()
507 }
508 Self::Cuda { device_id, uuid, .. } => {
509 f.debug_struct("Cuda").field("device_id", device_id).field("uuid", uuid).finish()
510 }
511 Self::Metal { registry_id, .. } => f.debug_struct("Metal").field("registry_id", registry_id).finish(),
512 Self::D3D11 { luid, .. } => f.debug_struct("D3D11").field("luid", luid).finish(),
513 Self::D3D12 { luid, .. } => f.debug_struct("D3D12").field("luid", luid).finish(),
514 Self::Vulkan { queue_family_index, uuid, .. } => {
515 f.debug_struct("Vulkan").field("queue_family_index", queue_family_index).field("uuid", uuid).finish()
516 }
517 Self::OpenGL { backend, share_group, .. } => {
518 f.debug_struct("OpenGL").field("backend", backend).field("share_group", share_group).finish()
519 }
520 #[cfg(feature = "wgpu")]
521 Self::Wgpu { .. } => f.write_str("GpuContext::Wgpu"),
522 #[cfg(all(target_family = "wasm", feature = "wgpu", feature = "web"))]
523 Self::WebGpu { raw_device, .. } => {
524 f.debug_struct("WebGpu").field("raw_device", &raw_device.is_some()).finish_non_exhaustive()
525 }
526 #[cfg(all(target_family = "wasm", feature = "wgpu", feature = "webgl"))]
527 Self::WebGl { raw_context, .. } => {
528 f.debug_struct("WebGl").field("raw_context", &raw_context.is_some()).finish_non_exhaustive()
529 }
530 }
531 }
532}