gpu-handle-types 0.1.0

Typed, owned native GPU resource handles (Vulkan, D3D11/12, Metal, OpenGL, CUDA, OpenCL, DMA-BUF, IOSurface, AHardwareBuffer, WebGPU, ...), cross-API sync points and video pixel formats, for passing GPU resources between libraries.
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0

//! `WebGpuResource` — the browser-platform arm of [`crate::GpuResource`].
//!
//! Every variant wraps a `web_sys::*` handle. Those handles are thin
//! `wasm_bindgen::JsValue` smart wrappers around a JS-side object:
//!
//! - **`Clone` is cheap** — it bumps a JS-side reference count (the
//!   `JsValue` ABI), never a driver round-trip. Cloning a
//!   [`WebGpuResource`] therefore does the right thing automatically:
//!   no explicit `Retain` / `Release` choreography as with
//!   `CVPixelBuffer`, `AHardwareBuffer`, or a Win32 `HANDLE`.
//! - **`Drop` is the single release mechanism** — the field-by-field
//!   drop decrements the JS reference count synchronously on the
//!   current thread (which on wasm is the only thread). JS GC reclaims
//!   the underlying object once nothing else references it. There is no
//!   `CVPixelBufferRelease` / `CloseHandle` / `AHardwareBuffer_release`
//!   equivalent to run.
//!
//! A Rust `Clone` of a `web_sys` handle references the **same** JS
//! object — it does not survive `ImageBitmap.close()` / `VideoFrame.close()`
//! and must never be treated as a lifetime extension. `close()` is the
//! caller's explicit lifecycle action, distinct from GC.
//!
//! # Thread safety
//!
//! `web_sys::*` handles are thin `JsValue` wrappers, and `JsValue`'s
//! thread-safety is target-dependent: wasm-bindgen provides
//! `unsafe impl Send/Sync for JsValue` **only** on the non-atomics target
//! (gated `cfg(not(target_feature = "atomics"))`) and withholds it on the
//! threaded `atomics` target, where a JS handle is genuinely realm-affine.
//! So on `atomics` `web_sys::*` — and this [`WebGpuResource`] — are
//! `!Send + !Sync`; on non-atomics they are (fragile-)`Send + Sync`.
//!
//! [`WebGpuResource`] carries **no** `unsafe impl Send + Sync` (unlike the
//! native newtypes in [`crate::gpu_resource`]), so it simply inherits that
//! payload behaviour. This is deliberate: on `atomics` the missing impl is
//! a real pin keeping [`crate::GpuResource`] `!Send + !Sync`; on
//! non-atomics this variant *is* `Send`, and the crate's `!Send` guarantee
//! rests instead on the `Arc<dyn ResourceKeepAlive>` keep-alive in the
//! sibling `WgpuTexture` variant (its `MaybeSendSync` bound is empty on
//! wasm) — see the [`crate::gpu_resource`] `# Thread safety` note. We do
//! **not** rely on wasm-bindgen's fragile non-atomics `Send`; do **not**
//! add an `unsafe impl` "for symmetry".

/// Browser GPU / media resource handle — the payload of
/// [`crate::GpuResource::Web`].
///
/// `#[non_exhaustive]` — future browser sources (e.g. an
/// `importExternalTexture` fast path) can land additively.
///
/// Routing summary: same-device `GpuTexture` and same-context `WebGlTexture`
/// import zero-copy; every `copyExternalImageToTexture` source is a
/// [`crate::ExecutionPath::BrowserGpuCopy`]; `ImageData` is CPU-resident.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum WebGpuResource {
    /// Direct WebGPU texture from another wgpu / WebGPU pipeline.
    ///
    /// Same device → `ZeroCopy` (wrapped via
    /// `Device::create_texture_from_webgpu_handle`); foreign device →
    /// `BrowserGpuCopy` — there is no cross-device texture share in the
    /// WebGPU spec, so a foreign device always requires a copy.
    ///
    /// The public API keeps the consumer's own `web_sys::GpuTexture`
    /// (consistent with every other variant). wgpu 30's accessors speak
    /// wgpu's *vendored* `wgpu::webgpu::GpuTexture`; the import boundary
    /// re-wraps this handle into that type with
    /// `wasm_bindgen::JsCast::unchecked_into()` — a zero-cost re-wrap of
    /// the same JS object.
    GpuTexture {
        /// The foreign `GPUTexture`.
        texture: web_sys::GpuTexture,
        /// The `GPUDevice` `texture` was created by, when the caller
        /// tracked it. Lets the importer answer "same device as ours?"
        /// via JS-value identity (`Object.is`) — the WebGPU spec exposes
        /// no `GPUTexture.device` reflection. `None` asserts same-device
        /// (the faster path; identical result when the assertion holds).
        device: Option<web_sys::GpuDevice>,
    },

    /// WebGL texture handle. Only meaningful when wgpu runs the GLES
    /// backend on the WebGL platform; on WebGPU builds an importer
    /// reports it as an unsupported format.
    ///
    /// Same context → `ZeroCopy` (via
    /// `Device::create_texture_from_webgl_handle`); cross-context → CPU
    /// bounce (`gl.readPixels` + `queue.write_texture`) — there is no
    /// cross-context share group on the web.
    ///
    /// # Caller-metadata contract (soundness)
    ///
    /// A `WebGlTexture` is opaque — it exposes no queryable size, format,
    /// or owning context — so the import trusts the caller's import
    /// size and this `context` the same way the native
    /// foreign-handle imports (`VkImage`, `IOSurface`, a D3D11 shared
    /// `HANDLE`) trust their caller-supplied metadata: a safe import
    /// entry point cannot verify them against the handle.
    /// Consistent with those importers, this variant stays part of the
    /// **safe** API rather than being marked `unsafe`.
    ///
    /// On the same-context `ZeroCopy` path the contract is load-bearing
    /// for **memory safety**: the wrap flows into the `unsafe`
    /// `Device::create_texture_from_webgl_handle`, and unlike WebGPU there
    /// is no browser bounds-check layer behind the GLES backend. A wrong
    /// `size` (larger than the real texture), a mismatched `format`, or a
    /// `context` mislabeled as same-context is therefore GL-driver
    /// undefined behaviour, not a catchable validation error. Callers must
    /// supply metadata that matches the real texture; when a same-context
    /// wrap cannot be *proven* (no reflectable context), the importer
    /// downgrades to the always-correct cross-context bounce rather than
    /// risk the `unsafe` wrap.
    WebGlTexture {
        /// The foreign `WebGLTexture`.
        texture: web_sys::WebGlTexture,
        /// The `WebGL2RenderingContext` `texture` was created by, for the
        /// same-context identity check.
        context: web_sys::WebGl2RenderingContext,
    },

    /// `<video>` element. The browser may already have hardware-decoded
    /// frames on the GPU; the API surface is `copyExternalImageToTexture`
    /// (`BrowserGpuCopy`) but the actual data motion is browser-internal.
    HtmlVideoElement(web_sys::HtmlVideoElement),

    /// `<img>` element after load. `BrowserGpuCopy`.
    HtmlImageElement(web_sys::HtmlImageElement),

    /// 2D or WebGPU `<canvas>`. A WebGPU canvas on the same device wraps
    /// zero-copy; a 2D canvas is `BrowserGpuCopy`.
    HtmlCanvasElement(web_sys::HtmlCanvasElement),

    /// `OffscreenCanvas` (Worker-friendly canvas). Same routing as
    /// [`Self::HtmlCanvasElement`].
    OffscreenCanvas(web_sys::OffscreenCanvas),

    /// `createImageBitmap()` result. Always `BrowserGpuCopy` at the API
    /// surface.
    ImageBitmap(web_sys::ImageBitmap),

    /// `ctx2d.getImageData()` result — CPU-resident
    /// (`Uint8ClampedArray`-backed). Reports `CpuBounceFallback` even
    /// though wgpu accepts it via `ExternalImageSource`.
    ImageData(web_sys::ImageData),

    /// WebCodecs decoded frame. Importable directly; gated on the
    /// `web-codecs` feature. A frame the caller has `close()`d surfaces
    /// as an import error (`coded_width() == 0`) rather than a panic.
    #[cfg(feature = "web-codecs")]
    VideoFrame(web_sys::VideoFrame),
}