Skip to main content

gpu_handle_types/
gpu_resource.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! `GpuResource` — the single source of truth for GPU resource handles
4//! crossing API boundaries. Used by decoders, encoders, interop layers and
5//! renderers.
6//!
7//! The variant payloads are **typed newtypes**
8//! (see [`handles`]). Each newtype owns its raw payload privately and
9//! exposes a single `try_from_raw` constructor that asserts invariants
10//! once. The enum here is a thin sum type over those newtypes.
11//!
12//! # Lifetime model
13//!
14//! Each newtype variant carries either:
15//! - `Option<Arc<dyn ResourceKeepAlive>>` — borrowed raw handle, lifetime
16//!   anchored by the producer's `Arc` if attached.
17//! - An `Arc<...>` refcount wrapper — for OS-handle / refcounted-foreign
18//!   payloads (`OwnedFd`, `OwnedHandle`, `IoSurfaceRetain`,
19//!   `CvBufferRetain`, `AHbInner`). Clone bumps the Arc; last drop runs
20//!   the wrapped destructor.
21//!
22//! Cloning a `GpuResource` is at most one atomic per attached Arc — no
23//! driver round-trip, no `CFRetain` / `AHardwareBuffer_acquire` per
24//! clone.
25//!
26//! # Thread safety
27//!
28//! `Send + Sync` is **auto-derived** on the enum (not a blanket
29//! `unsafe impl`). Each newtype carries its own `unsafe impl Send + Sync`
30//! documented on the type — see [`handles`]. If a future variant lands
31//! a non-`Send` payload, the compiler refuses to derive on the enum,
32//! forcing the author to either fix the payload or refactor consumers
33//! explicitly.
34//!
35//! **wasm carve-out.** On `target_family = "wasm"` `GpuResource` is
36//! `!Send + !Sync` on BOTH the non-atomics and threaded `atomics` targets.
37//! The pin that holds on both is the `wgpu` `WgpuTexture` variant's
38//! `Option<Arc<dyn ResourceKeepAlive>>` keep-alive: `ResourceKeepAlive`'s
39//! [`crate::MaybeSendSync`] supertrait is the *empty* marker on wasm, so
40//! that `Arc` trait object is `!Send + !Sync`, and the variant — unlike the
41//! native newtypes — carries no `unsafe impl` to override it. This is
42//! independent of the threading model and of whether a wgpu web backend is
43//! even compiled: with no backend selected, `wgpu::Texture` is an
44//! uninhabited, vacuously-`Send` dispatch enum, so it is the keep-alive
45//! field, not `wgpu::Texture`'s own auto-traits, that pins the variant.
46//!
47//! The `atomics` target adds a second, independent pin: the
48//! [`GpuResource::Web`] variant's `web_sys` payload is `!Send` there,
49//! because wasm-bindgen gates `unsafe impl Send/Sync for JsValue` on
50//! `cfg(not(target_feature = "atomics"))`. On the non-atomics target that
51//! same payload *is* `Send + Sync` (wasm-bindgen's fragile impl) — a
52//! property we deliberately do **not** rely on, which is exactly why the
53//! keep-alive pin above is written to stand on its own. The `Web` variant
54//! carries **no** `unsafe impl`, so it stays `!Send` on the atomics target;
55//! do not add one "for symmetry".
56//!
57//! This carve-out is intentional and non-restrictive: one interop instance
58//! per Worker, cross-Worker handoff via `postMessage` / `Transferable`s,
59//! never Rust thread movement.
60
61mod handles;
62
63pub use handles::*;
64
65/// Zero-copy GPU resource handle.
66///
67/// `#[non_exhaustive]` — future versions may add variants; existing matchers stay
68/// valid.
69#[derive(Clone, Debug)]
70#[non_exhaustive]
71pub enum GpuResource {
72    // ── D3D11 / D3D12 ─────────────────────────────────────────────────
73    D3D11Texture(D3D11Texture),
74    D3D12Resource(D3D12Resource),
75    D3D12BufferHandle(D3D12BufferHandle),
76
77    // ── Vulkan ────────────────────────────────────────────────────────
78    VkImage(VkImage),
79    VkBufferHandle(VkBufferHandle),
80    /// `VK_KHR_external_memory_fd` opaque-fd export (Linux / Android).
81    #[cfg(any(unix, target_os = "wasi", target_os = "hermit"))]
82    VkOpaqueFd(VkOpaqueFd),
83    /// `VK_KHR_external_memory_win32` opaque-NT-handle export (Windows).
84    #[cfg(windows)]
85    VkOpaqueWin32(VkOpaqueWin32),
86
87    // ── CUDA ──────────────────────────────────────────────────────────
88    CudaPtr2D(CudaPtr2D),
89    CudaBufferHandle(CudaBufferHandle),
90    /// Opaque CUDA `CUsurfObject` bound to a `CUarray` — a `surf2Dwrite`
91    /// kernel write target (typically a foreign-API texture imported as
92    /// a CUDA array). Gated on the `wgpu` feature because the handle
93    /// carries a `wgpu::TextureFormat`; see [`CudaSurface`].
94    #[cfg(feature = "wgpu")]
95    CudaSurface(CudaSurface),
96
97    // ── OpenCL ────────────────────────────────────────────────────────
98    OpenClMem(OpenClMem),
99
100    // ── Metal / Apple ─────────────────────────────────────────────────
101    MetalTextureHandle(MetalTextureHandle),
102    MetalBufferHandle(MetalBufferHandle),
103    #[cfg(target_vendor = "apple")]
104    IoSurface(IoSurface),
105    #[cfg(target_vendor = "apple")]
106    VideoToolboxFrame(VideoToolboxFrame),
107
108    // ── OpenGL ────────────────────────────────────────────────────────
109    GlTextureHandle(GlTextureHandle),
110    GlBufferHandle(GlBufferHandle),
111
112    // ── Linux ─────────────────────────────────────────────────────────
113    /// Bare Linux DMA-BUF descriptor.
114    #[cfg(any(target_os = "linux", target_os = "android"))]
115    DmaBufHandle(DmaBufHandle),
116    /// VAAPI surface — kept as an importer path.
117    #[cfg(target_os = "linux")]
118    VaapiSurface(VaapiSurface),
119
120    // ── Android ───────────────────────────────────────────────────────
121    #[cfg(target_os = "android")]
122    AHardwareBufferHandle(AHardwareBufferHandle),
123    #[cfg(target_os = "android")]
124    MediaCodecFrame(MediaCodecFrame),
125    #[cfg(target_os = "android")]
126    AImageFrame(AImageFrame),
127    #[cfg(target_os = "android")]
128    AndroidNativeWindowHandle(AndroidNativeWindowHandle),
129    #[cfg(target_os = "android")]
130    AndroidSurfaceControlHandle(AndroidSurfaceControlHandle),
131
132    // ── Windows NT handles ────────────────────────────────────────────
133    #[cfg(windows)]
134    NtHandle(NtHandle),
135    #[cfg(windows)]
136    KmtToken(KmtToken),
137
138    // ── CPU ───────────────────────────────────────────────────────────
139    CpuBytes(CpuBytes),
140    CpuPlanes(CpuPlaneSet),
141    CpuSharedSlot(CpuSharedSlot),
142
143    // ── Native wgpu (same-device passthrough) ─────────────────────────
144    /// A `wgpu::Texture` already on the consumer's own wgpu device — the
145    /// degenerate "nothing to import" case. The interop import path
146    /// returns the texture verbatim (no foreign-handle import, no copy).
147    /// Gated on the `wgpu` feature; see [`WgpuTexture`].
148    #[cfg(feature = "wgpu")]
149    WgpuTexture(WgpuTexture),
150
151    // ── Web (wasm) ────────────────────────────────────────────────────
152    /// Browser GPU / media handle (`GPUTexture`, `WebGLTexture`,
153    /// `<video>`, `VideoFrame`, …). wasm-only. See the module
154    /// `# Thread safety` note and [`crate::web::WebGpuResource`].
155    #[cfg(all(target_family = "wasm", feature = "web"))]
156    Web(crate::web::WebGpuResource),
157}
158
159// Compile-time enforcement of the wasm thread-affinity carve-out.
160// On wasm `GpuResource` is `!Send + !Sync`:
161//   - with the `wgpu` feature (always on for the interop stack) the
162//     `WgpuTexture` variant's `Option<Arc<dyn ResourceKeepAlive>>`
163//     keep-alive is `!Send + !Sync` on BOTH wasm targets — its
164//     `MaybeSendSync` bound is the empty marker on wasm — and the variant
165//     has no `unsafe impl` to force it `Send`. This is the pin on the
166//     *non-atomics* target (where the `Web` payload below is still `Send`),
167//     which is why the assertion is gated on `wgpu`.
168//   - on the threaded (`atomics`) target the `Web(WebGpuResource)`
169//     variant's `web_sys` payload is additionally `!Send` (wasm-bindgen
170//     gates `Send for JsValue` on non-atomics).
171// The guarantee therefore holds independent of the wasm threading model,
172// which is why nothing here relies on wgpu's
173// `fragile-send-sync-non-atomic-wasm`.
174#[cfg(all(target_family = "wasm", feature = "web", feature = "wgpu"))]
175static_assertions::assert_not_impl_any!(GpuResource: Send, Sync);
176
177/// Up-to-four-plane CPU buffer set. `count` ∈ `1..=4`; unused slots are
178/// `None`. Zero heap allocation — the `[Option<CpuPlane>; 4]` is inline.
179#[derive(Debug, Copy, Clone)]
180pub struct CpuPlaneSet {
181    pub format: crate::PixelFormat,
182    pub count: u8,
183    pub planes: [Option<CpuPlane>; 4],
184}
185
186#[derive(Debug, Copy, Clone)]
187pub struct CpuPlane {
188    pub data: *mut u8,
189    pub size: usize,
190    /// Row pitch in bytes.
191    pub row_pitch: u32,
192}
193
194// SAFETY: caller-asserted contract on the byte ranges. Same shape as
195// the typed-newtype payloads in `handles`.
196unsafe impl Send for CpuPlaneSet {}
197unsafe impl Sync for CpuPlaneSet {}
198unsafe impl Send for CpuPlane {}
199unsafe impl Sync for CpuPlane {}
200
201// ─────────────────────────────────────────────────────────────────────────────
202// GL bind targets / formats
203// ─────────────────────────────────────────────────────────────────────────────
204
205/// GL bind target the texture was allocated with. Maps 1:1 onto the
206/// matching `GL_TEXTURE_*` enum value.
207///
208/// `GL_TEXTURE_EXTERNAL_OES` is intentionally absent — those are
209/// surfaced through the Android `MediaCodec` / `AImage` paths or the
210/// Linux EGL_EXT_image_external bridge, not directly as a
211/// `GpuResource::GlTextureHandle`. Importers take the OES target through
212/// their own import description instead.
213#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
214#[non_exhaustive]
215pub enum GlTextureTarget {
216    /// `GL_TEXTURE_2D`.
217    Texture2D,
218    /// `GL_TEXTURE_2D_ARRAY`.
219    Texture2DArray,
220    /// `GL_TEXTURE_3D`.
221    Texture3D,
222    /// `GL_TEXTURE_CUBE_MAP`.
223    CubeMap,
224    /// `GL_TEXTURE_RECTANGLE` (desktop GL only).
225    Rectangle,
226    /// `GL_RENDERBUFFER`.
227    Renderbuffer,
228}
229
230impl GlTextureTarget {
231    /// The matching `GL_TEXTURE_*` / `GL_RENDERBUFFER` enum value.
232    pub const fn to_gl_enum(self) -> u32 {
233        match self {
234            Self::Texture2D => 0x0DE1,
235            Self::Texture2DArray => 0x8C1A,
236            Self::Texture3D => 0x806F,
237            Self::CubeMap => 0x8513,
238            Self::Rectangle => 0x84F5,
239            Self::Renderbuffer => 0x8D41,
240        }
241    }
242}
243
244/// GL buffer bind target. Non-exhaustive — covers the targets a video /
245/// VFX caller realistically allocates exportable buffers under.
246#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
247#[non_exhaustive]
248pub enum GlBufferTarget {
249    /// `GL_ARRAY_BUFFER` — vertex attribute storage.
250    ArrayBuffer,
251    /// `GL_ELEMENT_ARRAY_BUFFER` — index storage.
252    ElementArrayBuffer,
253    /// `GL_PIXEL_UNPACK_BUFFER` — async upload PBO.
254    PixelUnpackBuffer,
255    /// `GL_PIXEL_PACK_BUFFER` — async download PBO.
256    PixelPackBuffer,
257    /// `GL_SHADER_STORAGE_BUFFER` — SSBO (read-write storage).
258    ShaderStorageBuffer,
259    /// `GL_UNIFORM_BUFFER` — UBO.
260    UniformBuffer,
261    /// `GL_TRANSFORM_FEEDBACK_BUFFER` — transform-feedback output.
262    TransformFeedbackBuffer,
263    /// `GL_COPY_READ_BUFFER` / `GL_COPY_WRITE_BUFFER` — generic
264    /// copy-only binding. Useful when the caller doesn't know the final
265    /// use up-front.
266    CopyReadBuffer,
267    CopyWriteBuffer,
268}
269
270impl GlBufferTarget {
271    /// The matching `GL_*_BUFFER` enum value.
272    pub const fn to_gl_enum(self) -> u32 {
273        match self {
274            Self::ArrayBuffer => 0x8892,
275            Self::ElementArrayBuffer => 0x8893,
276            Self::PixelUnpackBuffer => 0x88EC,
277            Self::PixelPackBuffer => 0x88EB,
278            Self::ShaderStorageBuffer => 0x90D2,
279            Self::UniformBuffer => 0x8A11,
280            Self::TransformFeedbackBuffer => 0x8C8E,
281            Self::CopyReadBuffer => 0x8F36,
282            Self::CopyWriteBuffer => 0x8F37,
283        }
284    }
285}
286
287/// GL sized internal-format enum the texture was created with — only the
288/// subset that a video pipeline ever surfaces.
289#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
290#[non_exhaustive]
291pub enum GlInternalFormat {
292    Unspecified,
293    R8,
294    R8Snorm,
295    R8Ui,
296    R8I,
297    R16,
298    R16F,
299    R16Ui,
300    R16I,
301    R32F,
302    Rg8,
303    Rg8Snorm,
304    Rg16,
305    Rg16F,
306    Rgba8,
307    Srgb8Alpha8,
308    Rgba8Snorm,
309    Rgb10A2,
310    Rgba16,
311    Rgba16F,
312    Rgba32F,
313    R11fG11fB10f,
314    Bgra8,
315    Depth16,
316    Depth24Stencil8,
317}
318
319impl GlInternalFormat {
320    /// The matching `GL_*` enum value, or `0` for `Unspecified`.
321    pub const fn to_gl_enum(self) -> u32 {
322        match self {
323            Self::Unspecified => 0,
324            Self::R8 => 0x8229,
325            Self::R8Snorm => 0x8F94,
326            Self::R8Ui => 0x8232,
327            Self::R8I => 0x8231,
328            Self::R16 => 0x822A,
329            Self::R16F => 0x822D,
330            Self::R16Ui => 0x8234,
331            Self::R16I => 0x8233,
332            Self::R32F => 0x822E,
333            Self::Rg8 => 0x822B,
334            Self::Rg8Snorm => 0x8F95,
335            Self::Rg16 => 0x822C,
336            Self::Rg16F => 0x822F,
337            Self::Rgba8 => 0x8058,
338            Self::Srgb8Alpha8 => 0x8C43,
339            Self::Rgba8Snorm => 0x8F97,
340            Self::Rgb10A2 => 0x8059,
341            Self::Rgba16 => 0x805B,
342            Self::Rgba16F => 0x881A,
343            Self::Rgba32F => 0x8814,
344            Self::R11fG11fB10f => 0x8C3A,
345            Self::Bgra8 => 0x93A1, // GL_BGRA8_EXT
346            Self::Depth16 => 0x81A5,
347            Self::Depth24Stencil8 => 0x88F0,
348        }
349    }
350}
351
352// ─────────────────────────────────────────────────────────────────────────────
353// Convenience constructors
354// ─────────────────────────────────────────────────────────────────────────────
355
356impl GpuResource {
357    /// Convenience constructor for the common case — single GL context,
358    /// `GL_TEXTURE_2D`, format left `Unspecified` for the importer to
359    /// take from its own import description. No keep-alive (caller owns
360    /// the texture name).
361    pub fn opengl_texture_simple(name: u32) -> Self {
362        // SAFETY: caller-supplied name in current context; the `0`
363        // sentinel is the only failure mode `try_from_raw` rejects.
364        let h = unsafe {
365            GlTextureHandle::try_from_raw(
366                name,
367                GlTextureTarget::Texture2D,
368                GlInternalFormat::Unspecified,
369                core::ptr::null_mut(),
370                None,
371                None,
372            )
373        }
374        .expect("opengl_texture_simple: name must be non-zero");
375        Self::GlTextureHandle(h)
376    }
377
378    /// Convenience constructor for the common case — single GL context,
379    /// `GL_ARRAY_BUFFER`. No keep-alive (caller owns the buffer name).
380    pub fn opengl_buffer_simple(name: u32, size: u64) -> Self {
381        // SAFETY: caller-supplied name in current context; the `0`
382        // sentinel is the only failure mode `try_from_raw` rejects.
383        let h = unsafe {
384            GlBufferHandle::try_from_raw(name, GlBufferTarget::ArrayBuffer, size, core::ptr::null_mut(), None, None)
385        }
386        .expect("opengl_buffer_simple: name and size must be non-zero");
387        Self::GlBufferHandle(h)
388    }
389}