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 this crate deliberately does **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]`, so adding a variant is not a breaking change.
68#[derive(Clone, Debug)]
69#[non_exhaustive]
70pub enum GpuResource {
71    // ── D3D11 / D3D12 ─────────────────────────────────────────────────
72    D3D11Texture(D3D11Texture),
73    D3D12Resource(D3D12Resource),
74    D3D12BufferHandle(D3D12BufferHandle),
75
76    // ── Vulkan ────────────────────────────────────────────────────────
77    VkImage(VkImage),
78    VkBufferHandle(VkBufferHandle),
79    /// `VK_KHR_external_memory_fd` opaque-fd export (Linux / Android).
80    #[cfg(any(unix, target_os = "wasi", target_os = "hermit"))]
81    VkOpaqueFd(VkOpaqueFd),
82    /// `VK_KHR_external_memory_win32` opaque-NT-handle export (Windows).
83    #[cfg(windows)]
84    VkOpaqueWin32(VkOpaqueWin32),
85
86    // ── CUDA ──────────────────────────────────────────────────────────
87    CudaPtr2D(CudaPtr2D),
88    CudaBufferHandle(CudaBufferHandle),
89    /// Opaque CUDA `CUsurfObject` bound to a `CUarray` — a `surf2Dwrite`
90    /// kernel write target (typically a foreign-API texture imported as
91    /// a CUDA array). Gated on the `wgpu` feature because the handle
92    /// carries a `wgpu::TextureFormat`; see [`CudaSurface`].
93    #[cfg(feature = "wgpu")]
94    CudaSurface(CudaSurface),
95
96    // ── OpenCL ────────────────────────────────────────────────────────
97    OpenClMem(OpenClMem),
98
99    // ── Metal / Apple ─────────────────────────────────────────────────
100    MetalTextureHandle(MetalTextureHandle),
101    MetalBufferHandle(MetalBufferHandle),
102    #[cfg(target_vendor = "apple")]
103    IoSurface(IoSurface),
104    #[cfg(target_vendor = "apple")]
105    VideoToolboxFrame(VideoToolboxFrame),
106
107    // ── OpenGL ────────────────────────────────────────────────────────
108    GlTextureHandle(GlTextureHandle),
109    GlBufferHandle(GlBufferHandle),
110
111    // ── Linux ─────────────────────────────────────────────────────────
112    /// Bare Linux DMA-BUF descriptor.
113    #[cfg(any(target_os = "linux", target_os = "android"))]
114    DmaBufHandle(DmaBufHandle),
115    /// VA-API surface (`VASurfaceID` + its `VADisplay`).
116    #[cfg(target_os = "linux")]
117    VaapiSurface(VaapiSurface),
118
119    // ── Android ───────────────────────────────────────────────────────
120    #[cfg(target_os = "android")]
121    AHardwareBufferHandle(AHardwareBufferHandle),
122    #[cfg(target_os = "android")]
123    MediaCodecFrame(MediaCodecFrame),
124    #[cfg(target_os = "android")]
125    AImageFrame(AImageFrame),
126    #[cfg(target_os = "android")]
127    AndroidNativeWindowHandle(AndroidNativeWindowHandle),
128    #[cfg(target_os = "android")]
129    AndroidSurfaceControlHandle(AndroidSurfaceControlHandle),
130
131    // ── Windows NT handles ────────────────────────────────────────────
132    #[cfg(windows)]
133    NtHandle(NtHandle),
134    #[cfg(windows)]
135    KmtToken(KmtToken),
136
137    // ── CPU ───────────────────────────────────────────────────────────
138    CpuBytes(CpuBytes),
139    CpuPlanes(CpuPlaneSet),
140    CpuSharedSlot(CpuSharedSlot),
141
142    // ── Native wgpu (same-device passthrough) ─────────────────────────
143    /// A `wgpu::Texture` already on the consumer's own wgpu device — the
144    /// degenerate "nothing to import" case. The interop import path
145    /// returns the texture verbatim (no foreign-handle import, no copy).
146    /// Gated on the `wgpu` feature; see [`WgpuTexture`].
147    #[cfg(feature = "wgpu")]
148    WgpuTexture(WgpuTexture),
149
150    // ── Web (wasm) ────────────────────────────────────────────────────
151    /// Browser GPU / media handle (`GPUTexture`, `WebGLTexture`,
152    /// `<video>`, `VideoFrame`, …). wasm-only. See the module
153    /// `# Thread safety` note and [`crate::web::WebGpuResource`].
154    #[cfg(all(target_family = "wasm", feature = "web"))]
155    Web(crate::web::WebGpuResource),
156}
157
158// Compile-time enforcement of the wasm thread-affinity carve-out.
159// On wasm `GpuResource` is `!Send + !Sync`:
160//   - with the `wgpu` feature (a default feature) the
161//     `WgpuTexture` variant's `Option<Arc<dyn ResourceKeepAlive>>`
162//     keep-alive is `!Send + !Sync` on BOTH wasm targets — its
163//     `MaybeSendSync` bound is the empty marker on wasm — and the variant
164//     has no `unsafe impl` to force it `Send`. This is the pin on the
165//     *non-atomics* target (where the `Web` payload below is still `Send`),
166//     which is why the assertion is gated on `wgpu`.
167//   - on the threaded (`atomics`) target the `Web(WebGpuResource)`
168//     variant's `web_sys` payload is additionally `!Send` (wasm-bindgen
169//     gates `Send for JsValue` on non-atomics).
170// The guarantee therefore holds independent of the wasm threading model,
171// which is why nothing here relies on wgpu's
172// `fragile-send-sync-non-atomic-wasm`.
173#[cfg(all(target_family = "wasm", feature = "web", feature = "wgpu"))]
174static_assertions::assert_not_impl_any!(GpuResource: Send, Sync);
175
176/// Up-to-four-plane CPU buffer set. `count` ∈ `1..=4`; unused slots are
177/// `None`. Zero heap allocation — the `[Option<CpuPlane>; 4]` is inline.
178#[derive(Debug, Copy, Clone)]
179pub struct CpuPlaneSet {
180    pub format: crate::PixelFormat,
181    pub count: u8,
182    pub planes: [Option<CpuPlane>; 4],
183}
184
185#[derive(Debug, Copy, Clone)]
186pub struct CpuPlane {
187    pub data: *mut u8,
188    pub size: usize,
189    /// Row pitch in bytes.
190    pub row_pitch: u32,
191}
192
193// SAFETY: caller-asserted contract on the byte ranges. Same shape as
194// the typed-newtype payloads in `handles`.
195unsafe impl Send for CpuPlaneSet {}
196unsafe impl Sync for CpuPlaneSet {}
197unsafe impl Send for CpuPlane {}
198unsafe impl Sync for CpuPlane {}
199
200// ─────────────────────────────────────────────────────────────────────────────
201// GL bind targets / formats
202// ─────────────────────────────────────────────────────────────────────────────
203
204/// GL bind target the texture was allocated with. Maps 1:1 onto the
205/// matching `GL_TEXTURE_*` enum value.
206///
207/// `GL_TEXTURE_EXTERNAL_OES` is intentionally absent — those are
208/// surfaced through the Android `MediaCodec` / `AImage` paths or the
209/// Linux EGL_EXT_image_external bridge, not directly as a
210/// `GpuResource::GlTextureHandle`. Importers take the OES target through
211/// their own import description instead.
212#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
213#[non_exhaustive]
214pub enum GlTextureTarget {
215    /// `GL_TEXTURE_2D`.
216    Texture2D,
217    /// `GL_TEXTURE_2D_ARRAY`.
218    Texture2DArray,
219    /// `GL_TEXTURE_3D`.
220    Texture3D,
221    /// `GL_TEXTURE_CUBE_MAP`.
222    CubeMap,
223    /// `GL_TEXTURE_RECTANGLE` (desktop GL only).
224    Rectangle,
225    /// `GL_RENDERBUFFER`.
226    Renderbuffer,
227}
228
229impl GlTextureTarget {
230    /// The matching `GL_TEXTURE_*` / `GL_RENDERBUFFER` enum value.
231    pub const fn to_gl_enum(self) -> u32 {
232        match self {
233            Self::Texture2D => 0x0DE1,
234            Self::Texture2DArray => 0x8C1A,
235            Self::Texture3D => 0x806F,
236            Self::CubeMap => 0x8513,
237            Self::Rectangle => 0x84F5,
238            Self::Renderbuffer => 0x8D41,
239        }
240    }
241}
242
243/// GL buffer bind target. Non-exhaustive — covers the targets a video /
244/// VFX caller realistically allocates exportable buffers under.
245#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
246#[non_exhaustive]
247pub enum GlBufferTarget {
248    /// `GL_ARRAY_BUFFER` — vertex attribute storage.
249    ArrayBuffer,
250    /// `GL_ELEMENT_ARRAY_BUFFER` — index storage.
251    ElementArrayBuffer,
252    /// `GL_PIXEL_UNPACK_BUFFER` — async upload PBO.
253    PixelUnpackBuffer,
254    /// `GL_PIXEL_PACK_BUFFER` — async download PBO.
255    PixelPackBuffer,
256    /// `GL_SHADER_STORAGE_BUFFER` — SSBO (read-write storage).
257    ShaderStorageBuffer,
258    /// `GL_UNIFORM_BUFFER` — UBO.
259    UniformBuffer,
260    /// `GL_TRANSFORM_FEEDBACK_BUFFER` — transform-feedback output.
261    TransformFeedbackBuffer,
262    /// `GL_COPY_READ_BUFFER` / `GL_COPY_WRITE_BUFFER` — generic
263    /// copy-only binding. Useful when the caller doesn't know the final
264    /// use up-front.
265    CopyReadBuffer,
266    CopyWriteBuffer,
267}
268
269impl GlBufferTarget {
270    /// The matching `GL_*_BUFFER` enum value.
271    pub const fn to_gl_enum(self) -> u32 {
272        match self {
273            Self::ArrayBuffer => 0x8892,
274            Self::ElementArrayBuffer => 0x8893,
275            Self::PixelUnpackBuffer => 0x88EC,
276            Self::PixelPackBuffer => 0x88EB,
277            Self::ShaderStorageBuffer => 0x90D2,
278            Self::UniformBuffer => 0x8A11,
279            Self::TransformFeedbackBuffer => 0x8C8E,
280            Self::CopyReadBuffer => 0x8F36,
281            Self::CopyWriteBuffer => 0x8F37,
282        }
283    }
284}
285
286/// GL sized internal-format enum the texture was created with — only the
287/// subset that a video pipeline ever surfaces.
288#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
289#[non_exhaustive]
290pub enum GlInternalFormat {
291    Unspecified,
292    R8,
293    R8Snorm,
294    R8Ui,
295    R8I,
296    R16,
297    R16F,
298    R16Ui,
299    R16I,
300    R32F,
301    Rg8,
302    Rg8Snorm,
303    Rg16,
304    Rg16F,
305    Rgba8,
306    Srgb8Alpha8,
307    Rgba8Snorm,
308    Rgb10A2,
309    Rgba16,
310    Rgba16F,
311    Rgba32F,
312    R11fG11fB10f,
313    Bgra8,
314    Depth16,
315    Depth24Stencil8,
316}
317
318impl GlInternalFormat {
319    /// The matching `GL_*` enum value, or `0` for `Unspecified`.
320    pub const fn to_gl_enum(self) -> u32 {
321        match self {
322            Self::Unspecified => 0,
323            Self::R8 => 0x8229,
324            Self::R8Snorm => 0x8F94,
325            Self::R8Ui => 0x8232,
326            Self::R8I => 0x8231,
327            Self::R16 => 0x822A,
328            Self::R16F => 0x822D,
329            Self::R16Ui => 0x8234,
330            Self::R16I => 0x8233,
331            Self::R32F => 0x822E,
332            Self::Rg8 => 0x822B,
333            Self::Rg8Snorm => 0x8F95,
334            Self::Rg16 => 0x822C,
335            Self::Rg16F => 0x822F,
336            Self::Rgba8 => 0x8058,
337            Self::Srgb8Alpha8 => 0x8C43,
338            Self::Rgba8Snorm => 0x8F97,
339            Self::Rgb10A2 => 0x8059,
340            Self::Rgba16 => 0x805B,
341            Self::Rgba16F => 0x881A,
342            Self::Rgba32F => 0x8814,
343            Self::R11fG11fB10f => 0x8C3A,
344            Self::Bgra8 => 0x93A1, // GL_BGRA8_EXT
345            Self::Depth16 => 0x81A5,
346            Self::Depth24Stencil8 => 0x88F0,
347        }
348    }
349}
350
351// ─────────────────────────────────────────────────────────────────────────────
352// Convenience constructors
353// ─────────────────────────────────────────────────────────────────────────────
354
355impl GpuResource {
356    /// Convenience constructor for the common case — single GL context,
357    /// `GL_TEXTURE_2D`, format left `Unspecified` for the importer to
358    /// take from its own import description. No keep-alive (caller owns
359    /// the texture name).
360    pub fn opengl_texture_simple(name: u32) -> Self {
361        // SAFETY: caller-supplied name in current context; the `0`
362        // sentinel is the only failure mode `try_from_raw` rejects.
363        let h = unsafe {
364            GlTextureHandle::try_from_raw(
365                name,
366                GlTextureTarget::Texture2D,
367                GlInternalFormat::Unspecified,
368                core::ptr::null_mut(),
369                None,
370                None,
371            )
372        }
373        .expect("opengl_texture_simple: name must be non-zero");
374        Self::GlTextureHandle(h)
375    }
376
377    /// Convenience constructor for the common case — single GL context,
378    /// `GL_ARRAY_BUFFER`. No keep-alive (caller owns the buffer name).
379    pub fn opengl_buffer_simple(name: u32, size: u64) -> Self {
380        // SAFETY: caller-supplied name in current context; the `0`
381        // sentinel is the only failure mode `try_from_raw` rejects.
382        let h = unsafe {
383            GlBufferHandle::try_from_raw(name, GlBufferTarget::ArrayBuffer, size, core::ptr::null_mut(), None, None)
384        }
385        .expect("opengl_buffer_simple: name and size must be non-zero");
386        Self::GlBufferHandle(h)
387    }
388}