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 for the
32 /// `cl_khr_gl_sharing` route into a wgpu-GL device — without it, a
33 /// consumer has to fall back to `cl_khr_external_memory`
34 /// cross-context sharing (more setup per import) or a CPU copy.
35 ///
36 /// `None` is correct when the caller's CL context is not
37 /// GL-shared (compute-only work that never feeds wgpu-GL). A
38 /// consumer that feeds wgpu-GL may refuse a non-GL-shared CL
39 /// context.
40 gl_sharing: Option<OpenClGlSharing>,
41 },
42 /// External CUDA context + stream owned by the caller.
43 ///
44 /// Lifetime contract: `context` and `stream` must remain valid for
45 /// the lifetime of every resource/handle minted through an interop
46 /// / decoder / encoder built on this context — including
47 /// `Drop`-time teardown, which may run on arbitrary threads long
48 /// after the mint. Keep-alives minted against this context capture the
49 /// raw `CUcontext` and re-bind it to destroy per-context tokens
50 /// (e.g. `CUsurfObject`); a context destroyed early cannot be
51 /// detected — the driver may recycle the address, resolving those
52 /// tokens against an unrelated context.
53 Cuda {
54 context: *mut c_void,
55 stream: *mut c_void,
56 device_id: i32,
57 uuid: Option<[u8; 16]>,
58 },
59 Metal {
60 device: *mut c_void,
61 queue: *mut c_void,
62 registry_id: Option<u64>,
63 },
64 D3D11 {
65 device: *mut c_void,
66 device_context: *mut c_void,
67 luid: Option<(i32, u32)>,
68 },
69 D3D12 {
70 device: *mut c_void,
71 queue: *mut c_void,
72 luid: Option<(i32, u32)>,
73 },
74 Vulkan {
75 instance: *mut c_void,
76 physical_device: *mut c_void,
77 device: *mut c_void,
78 queue: u64,
79 queue_family_index: u32,
80 uuid: Option<[u8; 16]>,
81 },
82 /// External OpenGL / OpenGL ES context owned by the caller.
83 ///
84 /// `display` / `context` are the caller's platform-native identifiers
85 /// — `EGLDisplay`+`EGLContext` on EGL, `HDC`+`HGLRC` on WGL,
86 /// `CGLContextObj` on CGL (with `display` mirroring `context`).
87 ///
88 /// `share_group` is an opaque identifier the caller uses to stamp
89 /// share-group identity. Two `OpenGL` contexts with the same non-
90 /// `None` `share_group` are assumed to share GL object names;
91 /// `None` means "solo context, no sharing guarantees". There is no
92 /// runtime query for this on EGL or WGL, so the value is purely
93 /// caller-supplied.
94 OpenGL {
95 display: *mut c_void,
96 context: *mut c_void,
97 share_group: Option<u64>,
98 backend: GlBackend,
99 },
100 /// Caller-supplied `wgpu` device, queue, and the adapter the device
101 /// was created from.
102 ///
103 /// `adapter` is load-bearing for per-format capability probes:
104 /// `wgpu::Adapter::get_texture_format_features(...)` is the only
105 /// reliable way to ask whether a given format supports
106 /// `STORAGE_BINDING`, `RENDER_ATTACHMENT`, etc. on the caller's
107 /// physical device. `wgpu::Device::features()` reports the
108 /// device-creation-time *requested* feature set, which is a strict
109 /// subset of what the adapter can actually do per format —
110 /// inadequate for the probe (e.g. on a desktop Vulkan adapter R8
111 /// storage is available without any feature flag, so `Device::features()`
112 /// can't tell you).
113 ///
114 /// External-context-mode factories run their per-format capability
115 /// detection against the borrowed `adapter`; the alternative —
116 /// enumerating fresh adapters from a new `wgpu::Instance` and
117 /// guessing which one matches the device — is incorrect on
118 /// multi-GPU systems, which is why this field exists.
119 ///
120 /// The three wgpu handles are internally reference-counted, so the
121 /// caller passes clones of its own and keeps using them.
122 #[cfg(feature = "wgpu")]
123 Wgpu {
124 device: wgpu::Device,
125 queue: wgpu::Queue,
126 adapter: wgpu::Adapter,
127 },
128
129 /// Caller-supplied `wgpu` device + queue running the **WebGPU**
130 /// backend (`wgpu::Backend::BrowserWebGpu`). wasm-only.
131 ///
132 /// `raw_device` is the caller's own `web_sys::GpuDevice`, retained so
133 /// same-device `GPUTexture` imports can be identity-compared
134 /// (`Object.is`) against it — the WebGPU spec exposes no
135 /// `GPUTexture.device` reflection. `None` when the device was created
136 /// by the library this context is handed to (it already knows the
137 /// identity); `Some` when the host supplies a device of its own.
138 #[cfg(all(target_family = "wasm", feature = "wgpu", feature = "web"))]
139 WebGpu {
140 device: wgpu::Device,
141 queue: wgpu::Queue,
142 raw_device: Option<web_sys::GpuDevice>,
143 },
144
145 /// Caller-supplied `wgpu` device + queue running the **WebGL2**
146 /// backend (`wgpu::Backend::Gl` on wasm). wasm-only.
147 ///
148 /// `raw_context` is the caller's own `web_sys::WebGl2RenderingContext`,
149 /// retained so same-context `WebGLTexture` imports and exports can be
150 /// identity-compared against it. `None` when the device was created by
151 /// the library this context is handed to; `Some` when the host supplies
152 /// a context of its own.
153 ///
154 /// Gated on `webgl` rather than `web`, so a WebGPU-only build carries no
155 /// WebGL2 context variant.
156 #[cfg(all(target_family = "wasm", feature = "wgpu", feature = "webgl"))]
157 WebGl {
158 device: wgpu::Device,
159 queue: wgpu::Queue,
160 raw_context: Option<web_sys::WebGl2RenderingContext>,
161 },
162}
163
164/// Caller-asserted GL-sharing handles for an `OpenCl` context.
165///
166/// Mirrors the (display, context, backend) triple from
167/// [`GpuContext::OpenGL`] — these are the same values the caller
168/// passed (or would pass) into `clCreateContext`'s
169/// `cl_context_properties[]` array as the values of
170/// `CL_GL_CONTEXT_KHR` and the matching platform property.
171///
172/// **No share-group**: `cl_khr_gl_sharing`'s property array binds the
173/// CL context to a specific (display, context) pair, not to a share
174/// group. Cross-share-group import is not standardised at the CL spec
175/// level — the right escape hatch is `cl_khr_external_memory` (an
176/// interop bridge layer's job), not a share-group field here.
177#[derive(Copy, Clone)]
178pub struct OpenClGlSharing {
179 pub display: *mut c_void,
180 pub context: *mut c_void,
181 pub backend: GlBackend,
182}
183
184impl core::fmt::Debug for OpenClGlSharing {
185 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
186 f.debug_struct("OpenClGlSharing").field("backend", &self.backend).finish_non_exhaustive()
187 }
188}
189
190// Same Send/Sync stance as the parent `GpuContext` — the handles are
191// opaque pointers + plain data, caller owns lifecycle.
192unsafe impl Send for OpenClGlSharing {}
193unsafe impl Sync for OpenClGlSharing {}
194
195/// Which GL context flavour [`GpuContext::OpenGL::context`] points at.
196///
197/// `Desktop` / `Egl` / `Angle(_)` distinguish which procedure-loader an
198/// interop layer uses (`wglGetProcAddress` + `opengl32.dll`, `eglGetProcAddress`,
199/// `dlsym` on the relevant `.framework`). `Web` is reserved for wasm /
200/// WebGL2 callers; cross-API interop isn't reachable there.
201#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
202#[non_exhaustive]
203pub enum GlBackend {
204 Desktop,
205 Egl,
206 Angle(AngleBackend),
207 Web,
208}
209
210/// Underlying implementation of an ANGLE "GL" context. Probed via
211/// `glGetString(GL_RENDERER)` + the matching `EGL_ANGLE_*` extension
212/// when `GlBackend::Angle(_)` is selected.
213#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
214#[non_exhaustive]
215pub enum AngleBackend {
216 Vulkan,
217 D3D11,
218 D3D12,
219 Metal,
220}
221
222/// Host-supplied scheduler that posts a closure onto the thread that
223/// owns a particular OpenGL context.
224///
225/// ## Why this exists
226///
227/// `GLuint` deletion (`glDeleteSemaphoresEXT`, `glDeleteTextures`, etc.)
228/// MUST run on a thread where the owning GL context is current — the GL
229/// namespace is thread-local and a delete dispatched against a foreign
230/// or null context is rejected as `GL_INVALID_OPERATION`. An interop
231/// layer's GL bridges produce GL-side
232/// objects whose `Drop` may run on arbitrary threads in finalizer-driven
233/// hosts (JNI / .NET / GC-managed wrappers): producer-side
234/// `Arc<dyn SyncWaiter>` keep-alives, retained imports and process-wide
235/// producer-identity caches all hold strong references that are
236/// routinely released by threads with no GL context current.
237///
238/// Without an executor, those Drops can only queue the GL name onto a
239/// pending-delete bucket that is drained when:
240///
241/// - any thread that currently holds the matching GL context current
242/// reaches one of the GL-side bridge entry points (an opportunistic
243/// drain), or
244/// - the host explicitly asks the interop layer to prune its GL bridge
245/// objects from a thread that holds the matching context current.
246///
247/// Hosts whose pipeline cannot guarantee either path (e.g. a shutdown
248/// flow where the bridge layer has already stopped accepting work but
249/// the language-runtime GC keeps releasing the parked Arcs) should
250/// implement this trait and register an instance with the interop
251/// layer. Once registered, off-thread `Drop`s for that context are
252/// routed through the executor and run on the GL-owning thread directly.
253///
254/// ## Contract
255///
256/// `execute(target_ctx, task)` MUST eventually run `task` on a thread
257/// where the GL context identified by `target_ctx` is current. The
258/// implementation is allowed to:
259///
260/// - drop the closure unrun if the GL context is being torn down
261/// (the namespace dies with the context, so any leaked GL name is
262/// reclaimed); the only consequence is a small bump in the deferred-
263/// delete bucket until the next bridge call drains it.
264/// - run the closure synchronously when the calling thread already
265/// holds the right context current. The closure does not depend on
266/// any state outside its capture.
267///
268/// `target_ctx` is the raw `EGLContext` / `HGLRC` / `CGLContextObj`
269/// cast to `usize`. Hosts running a single GL thread can ignore the
270/// argument and post unconditionally; multi-context hosts use it to
271/// route to the correct thread/queue.
272pub trait GlContextExecutor: Send + Sync + 'static {
273 /// Schedule `task` to run on the GL-owning thread for the context
274 /// identified by `target_ctx`. See trait docs for the contract.
275 fn execute(&self, target_ctx: usize, task: Box<dyn FnOnce() + Send + 'static>);
276}
277
278// Caller asserts host platform permits cross-thread sharing.
279//
280// `Sync` is asserted on the same grounds as `Send`: once a `GpuContext`
281// is constructed, the handles inside are immutable
282// pointers + plain data. Cross-thread access to those pointers is
283// safe — what is NOT safe is calling the *underlying device* from
284// multiple threads simultaneously, which is the caller's
285// responsibility regardless of this type's `Sync` stance. Without `Sync`,
286// `Arc<GpuContext>` would not be `Send`, breaking the `Send` invariant
287// of any decoder / encoder / resampler that holds an
288// `Option<Arc<GpuContext>>` field and must cross a thread handoff
289// (a prefetch thread, say).
290//
291// **wasm carve-out.** These blanket impls are `cfg(not(target_family =
292// "wasm"))`: on wasm the `WebGpu` / `WebGl` variants carry
293// `web_sys::GpuDevice` / `web_sys::WebGl2RenderingContext` (and
294// `wgpu::Device`, which is itself `!Send` on wasm) — all
295// inherently thread-affine `JsValue` wrappers — so asserting `Send` /
296// `Sync` would be **unsound**. Removing the blanket impls leaves
297// `GpuContext` structurally `!Send + !Sync` on wasm (the raw-pointer
298// variants are `!Send` too). That is correct for the web target: it is
299// single-threaded, one interop instance per Worker, and such holders'
300// `Option<Arc<GpuContext>>` fields become `!Send` there as well — no
301// Rust thread handoff exists to break (cross-Worker handoff is
302// `postMessage` / `Transferable`, not thread movement). Native builds
303// keep `Send + Sync`.
304#[cfg(not(target_family = "wasm"))]
305unsafe impl Send for GpuContext {}
306#[cfg(not(target_family = "wasm"))]
307unsafe impl Sync for GpuContext {}
308
309impl GpuContext {
310 /// A [`GpuContext::Wgpu`] over the caller's device, queue and the
311 /// adapter the device was created from. The wgpu handles are
312 /// internally reference-counted; pass clones.
313 #[cfg(feature = "wgpu")]
314 pub fn from_wgpu(device: wgpu::Device, queue: wgpu::Queue, adapter: wgpu::Adapter) -> 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. Lets a CUDA consumer read the pair without
359 /// pattern-matching the `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}