gpu-handle-types 0.2.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

use gpu_handle_types::{PixelFormat, PlaneFormat};
use video_types::{ColorFamily, ColorRange, ResolvedRange};

#[test]
fn nv12_plane_sizes() {
    let fmt = PixelFormat::NV12;
    assert_eq!(fmt.plane_size(1920, 1080, 0), (1920, 1080));
    // UV plane: W/2 × H/2 RG8 *texels* (each RG-pair = 2 bytes, so the
    // byte stride equals the Y plane's W; size_scale describes texel
    // count, not byte stride — see PlaneLayout doc-comment).
    assert_eq!(fmt.plane_size(1920, 1080, 1), (960, 540));
}

#[test]
fn yuv420p_plane_sizes() {
    assert_eq!(PixelFormat::YUV420P.plane_size(1920, 1080, 0), (1920, 1080));
    assert_eq!(PixelFormat::YUV420P.plane_size(1920, 1080, 1), (960, 540));
    assert_eq!(PixelFormat::YUV420P.plane_size(1920, 1080, 2), (960, 540));
}

#[test]
fn hdr_native_classification() {
    assert!(!PixelFormat::NV12.is_hdr_native());
    assert!(!PixelFormat::RgbaU8.is_hdr_native());
    assert!(PixelFormat::P010LE.is_hdr_native());
    assert!(PixelFormat::RgbaF16.is_hdr_native());
    assert!(PixelFormat::RgbaU16.is_hdr_native());
}

#[test]
fn bits_per_sample_spot_checks() {
    assert_eq!(PixelFormat::NV12.bits_per_sample(), 8);
    assert_eq!(PixelFormat::P010LE.bits_per_sample(), 10);
    assert_eq!(PixelFormat::YUV422P12LE.bits_per_sample(), 12);
    assert_eq!(PixelFormat::RgbaF16.bits_per_sample(), 16);
    assert_eq!(PixelFormat::RgbaF32.bits_per_sample(), 32);
}

#[test]
fn storage_sample_scale_alignment_bridge() {
    // LSB-aligned triplanar `YUV*P{10,12,14}LE` → 2^(16-bit_depth).
    assert_eq!(PixelFormat::YUV420P10LE.storage_sample_scale(), 64.0); // 1 << 6
    assert_eq!(PixelFormat::YUV422P12LE.storage_sample_scale(), 16.0); // 1 << 4
    assert_eq!(PixelFormat::YUV444P14LE.storage_sample_scale(), 4.0); // 1 << 2
    // Write side is the exact reciprocal (powers of two → exact in f32).
    assert_eq!(PixelFormat::YUV420P10LE.storage_sample_scale_inv(), 1.0 / 64.0);

    // No-op (1.0) everywhere else: MSB-aligned biplanar, true 16-bit
    // P16LE, 8-bit triplanar, and RGB.
    assert_eq!(PixelFormat::P010LE.storage_sample_scale(), 1.0);
    assert_eq!(PixelFormat::YUV420P16LE.storage_sample_scale(), 1.0);
    assert_eq!(PixelFormat::YUV420P.storage_sample_scale(), 1.0);
    assert_eq!(PixelFormat::NV12.storage_sample_scale(), 1.0);
    assert_eq!(PixelFormat::RgbaU16.storage_sample_scale(), 1.0);
    assert_eq!(PixelFormat::RgbaU16.storage_sample_scale_inv(), 1.0);
}

/// `YUVA444P10LE` — planar 4:4:4 + alpha, a 4-plane format.
/// Contract: four full-resolution R16 planes, 10-bit LSB-aligned
/// (`storage_sample_scale == 64`), HDR-native, not biplanar.
#[test]
fn yuva444p10le_layout() {
    let f = PixelFormat::YUVA444P10LE;
    let l = f.plane_layout();
    assert_eq!(l.count(), 4);
    for i in 0..4u8 {
        let info = l.plane(i).expect("YUVA444P10LE has 4 planes");
        assert_eq!(info.format, PlaneFormat::R16, "plane {i} storage");
        assert_eq!(info.size_scale, (1, 1), "plane {i} is full-res (4:4:4 + full-res alpha)");
    }
    assert_eq!(f.plane_size(1920, 1080, 3), (1920, 1080));
    assert_eq!(f.bits_per_sample(), 10);
    assert!(!f.is_biplanar());
    assert!(f.is_hdr_native());
    // LSB-aligned like the alpha-less triplanar 10-bit family.
    assert!(!f.is_msb_aligned_storage());
    assert_eq!(f.storage_sample_scale(), 64.0);
    assert_eq!(f.storage_sample_scale_inv(), 1.0 / 64.0);

    // The 12-bit decode-side sibling shares the layout at 12 bits.
    let f12 = PixelFormat::YUVA444P12LE;
    assert_eq!(f12.plane_layout(), l);
    assert_eq!(f12.bits_per_sample(), 12);
    assert!(!f12.is_msb_aligned_storage());
    assert_eq!(f12.storage_sample_scale(), 16.0); // 1 << 4
}

/// At an odd frame dimension, a chroma-subsampled plane rounds its
/// dimension UP, matching FFmpeg's `av_image_fill_pointers`
/// (`AV_CEIL_RSHIFT(dim, log2_chroma)` == `(dim + (1<<s) - 1) >> s`).
/// Truncating would drop the trailing half-covered chroma row/column.
#[test]
fn odd_dim_chroma_plane_size_rounds_up() {
    // Reference: ffmpeg's per-axis ceil-rshift, the exact rule
    // ffmpeg-next's `Video::plane_width` / `plane_height` implement.
    fn ff_ceil(dim: u32, scale: u32) -> u32 {
        let s = scale.trailing_zeros(); // log2 of the (power-of-two) scale
        (dim + (1 << s) - 1) >> s
    }

    // 321×241 is odd on both axes.
    let (w, h) = (321u32, 241u32);

    // 4:2:0 — both axes subsampled by 2 → chroma 161×121, NOT 160×120.
    for fmt in [PixelFormat::NV12, PixelFormat::P010LE, PixelFormat::YUV420P, PixelFormat::YUV420P10LE] {
        // Luma plane is always full-res.
        assert_eq!(fmt.plane_size(w, h, 0), (w, h), "{fmt:?} luma");
        let chroma = fmt.plane_size(w, h, 1);
        assert_eq!(chroma, (ff_ceil(w, 2), ff_ceil(h, 2)), "{fmt:?} chroma ceil");
        assert_eq!(chroma, (161, 121), "{fmt:?} chroma 4:2:0 @ 321x241");
    }

    // 4:2:2 — horizontal-only subsampling → chroma 161×241.
    for fmt in [PixelFormat::P210LE, PixelFormat::YUV422P, PixelFormat::YUV422P10LE] {
        let chroma = fmt.plane_size(w, h, 1);
        assert_eq!(chroma, (ff_ceil(w, 2), ff_ceil(h, 1)), "{fmt:?} chroma ceil");
        assert_eq!(chroma, (161, 241), "{fmt:?} chroma 4:2:2 @ 321x241");
    }

    // Even dimensions are unaffected (regression guard against changing
    // the common path).
    assert_eq!(PixelFormat::NV12.plane_size(1920, 1080, 1), (960, 540));
    assert_eq!(PixelFormat::YUV420P.plane_size(1920, 1080, 2), (960, 540));
}

#[test]
fn ayuv64le_layout() {
    let l = PixelFormat::AYUV64LE.plane_layout();
    assert_eq!(l.count(), 1);
    assert_eq!(l.plane0.format, PlaneFormat::RGBA16);
    assert!(l.extra.iter().all(Option::is_none));
    assert_eq!(PixelFormat::AYUV64LE.bits_per_sample(), 16);
    assert!(PixelFormat::AYUV64LE.is_hdr_native());
}

// ─────────────────────────────────────────────────────────────────────────────
// The format's half of the levels guess. `ColorRange::resolve` keys on a
// `ColorFamily`; `color_family` is what turns a carrier into one, so a
// mis-classified format would mis-resolve every unsignalled source on it.
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn color_family_drives_the_levels_guess() {
    // One format per family, so a mis-wired classification fails here, not in
    // a backend.
    assert_eq!(PixelFormat::NV12.color_family(), ColorFamily::Yuv);
    assert_eq!(PixelFormat::RgbaU8.color_family(), ColorFamily::Rgb);
    assert_eq!(PixelFormat::R16U.color_family(), ColorFamily::Scalar);
    assert_eq!(ColorRange::Unspecified.resolve(PixelFormat::NV12.color_family()), ResolvedRange::Limited);
    assert_eq!(ColorRange::Unspecified.resolve(PixelFormat::RgbaU8.color_family()), ResolvedRange::Full);
    assert_eq!(ColorRange::Unspecified.resolve(PixelFormat::R16U.color_family()), ResolvedRange::Full);
    // An explicit tag survives the format-keyed spelling too.
    assert_eq!(ColorRange::Full.resolve(PixelFormat::NV12.color_family()), ResolvedRange::Full);
    assert_eq!(ColorRange::Limited.resolve(PixelFormat::RgbaU8.color_family()), ResolvedRange::Limited);
}

// ─────────────────────────────────────────────────────────────────────────────
// `PixelFormat::packed_expand_depth` — the resolved swing composed with "which
// code space". The ONE derivation of the levels decode a packed source owes;
// conversion code calls it rather than re-deriving it, so the truth table is
// pinned here.
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn packed_expand_depth_owes_a_decode_only_for_limited_integer_rgb() {
    use PixelFormat as P;
    use ResolvedRange as R;

    // Explicit `Limited` on an integer packed-RGB carrier: the natural
    // normalisation depth of that code space.
    assert_eq!(P::RgbaU8.packed_expand_depth(R::Limited), 8);
    assert_eq!(P::BgraU8.packed_expand_depth(R::Limited), 8);
    assert_eq!(P::Rgb10A2.packed_expand_depth(R::Limited), 10);
    assert_eq!(P::RgbaU16.packed_expand_depth(R::Limited), 16);
    assert_eq!(P::BgraU16.packed_expand_depth(R::Limited), 16);
    assert_eq!(P::RgbU16.packed_expand_depth(R::Limited), 16);

    // Float carriers have no integer code space, so a narrow *swing* is
    // meaningless on them even when a caller insists on the tag.
    for fmt in [P::RgbaF16, P::BgraF16, P::RgbaF32, P::BgraF32] {
        assert_eq!(fmt.packed_expand_depth(R::Limited), 0, "{fmt:?}");
    }

    // Scalar raw-channel fields carry no luma pedestal.
    for fmt in [P::R16U, P::R32F, P::Rgba16U] {
        assert_eq!(fmt.packed_expand_depth(R::Limited), 0, "{fmt:?}");
    }

    // YUV swing belongs to the YUV path's own dequantiser — answering a
    // depth here would invite a second, wrong decode on top of the right
    // one. True for both the explicit tag and the resolved guess.
    for fmt in [P::NV12, P::P010LE, P::YUV420P, P::YUV444P16LE, P::AYUV64LE, P::V216, P::Y210LE, P::NV12A] {
        assert_eq!(fmt.packed_expand_depth(R::Limited), 0, "{fmt:?}");
        assert_eq!(fmt.packed_expand_depth(ColorRange::Unspecified.resolve(fmt.color_family())), 0, "{fmt:?}");
    }
}

#[test]
fn packed_expand_depth_is_resolve_composed_with_the_code_space() {
    use ColorRange as W;
    use PixelFormat as P;
    use ResolvedRange as R;

    // Full swing owes nothing, whatever the carrier.
    for fmt in [P::RgbaU8, P::Rgb10A2, P::RgbaU16, P::NV12, P::R16U] {
        assert_eq!(fmt.packed_expand_depth(R::Full), 0, "{fmt:?}");
    }

    // …and an UNSIGNALLED packed-RGB source resolves FULL (libplacebo's
    // `pl_color_levels_guess`), so it owes nothing either. This is the
    // whole point of the tri-state: a flat `Limited` default would decode a
    // narrow swing out of an RGB frame that never had one.
    for fmt in [P::RgbaU8, P::BgraU8, P::Rgb10A2, P::RgbaU16, P::BgraU16, P::RgbU16] {
        assert_eq!(fmt.packed_expand_depth(W::Unspecified.resolve(fmt.color_family())), 0, "{fmt:?}");
    }

    // The sentinel is exactly "resolves limited AND has an integer RGB
    // code space" — never a stand-in for "unknown".
    for fmt in [P::RgbaU8, P::BgraU8, P::Rgb10A2, P::RgbaU16, P::BgraU16, P::RgbU16] {
        assert_ne!(fmt.packed_expand_depth(W::Limited.resolve(fmt.color_family())), 0, "{fmt:?}");
    }
}