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
//
// Canonical on-device byte estimator for `wgpu::Texture` resources — the
// single source of truth for:
//   * budget reservations (e.g. an import cache's), and
//   * a frame pool's out-of-memory `required_bytes` reporting and
//     transient / metrics accounting.
//
// It handles every shape axis (mip chain, array layers / depth slices,
// sample count) and both multi-planar (NV12 / P010) and block-compressed
// formats. The estimate is honest enough to drive coarse budget pressure
// and Tier-2 reclaim accounting; flat-bpp fallbacks would over-estimate
// NV12 by ~2.6× and P010 by ~1.3×, leaving the budget thinking it freed
// twice what it really did.

/// Per-pixel byte cost summed across every plane of `format` at
/// `(width, height)`.
///
/// Multi-planar YCbCr formats follow the 4:2:0 convention (chroma plane
/// is half-width × half-height, rounded up for odd extents — wgpu
/// requires even dims for NV12 / P010 but we stay defensive). Every other
/// format is `block_count × block_copy_size`, which collapses to
/// `bpp × w × h` for the uncompressed single-cell formats and stays
/// correct for block-compressed layouts. Formats without a single linear
/// cell size (`Depth24Plus`, combined depth-stencil) get a 4-bpp upper
/// bound — an over-estimate, but those rarely ride the budget and the
/// bound stays conservative.
fn plane_bytes_2d(format: wgpu::TextureFormat, w: u64, h: u64) -> u64 {
    match format {
        // NV12: plane 0 = R8 (1 bpp); plane 1 = Rg8 at half-res (2 bpp).
        wgpu::TextureFormat::NV12 => w * h + w.div_ceil(2) * h.div_ceil(2) * 2,
        // P010: plane 0 = R16 (2 bpp); plane 1 = Rg16 at half-res (4 bpp).
        wgpu::TextureFormat::P010 => w * h * 2 + w.div_ceil(2) * h.div_ceil(2) * 4,
        other => {
            let (bw, bh) = other.block_dimensions();
            let bs = other.block_copy_size(None).unwrap_or(4) as u64;
            w.div_ceil(bw as u64) * h.div_ceil(bh as u64) * bs
        }
    }
}

/// Estimate the on-device byte footprint of a 2D / 3D texture.
///
/// Charges every mip level and every array layer / depth slice. Mip
/// dimensions follow the wgpu convention (`max(1, dim >> level)`). Sample
/// count multiplies the base level only (mip levels above 0 of an MSAA
/// texture are not allocated by any current driver — sample expansion on
/// level > 0 is a validation error in wgpu and every hardware spec we
/// care about).
pub fn estimate_texture_bytes(
    format: wgpu::TextureFormat,
    width: u32,
    height: u32,
    depth_or_array_layers: u32,
    sample_count: u32,
    mip_level_count: u32,
) -> u64 {
    let layers = depth_or_array_layers.max(1) as u64;
    let samples = sample_count.max(1) as u64;
    let levels = mip_level_count.max(1);

    let mut total: u64 = 0;
    for level in 0..levels {
        let w = (width >> level).max(1) as u64;
        let h = (height >> level).max(1) as u64;
        let plane = plane_bytes_2d(format, w, h);
        let level_bytes = plane.saturating_mul(layers);
        // Sample count multiplies level 0 only — see fn doc.
        let level_bytes = if level == 0 { level_bytes.saturating_mul(samples) } else { level_bytes };
        total = total.saturating_add(level_bytes);
    }
    total
}

/// Convenience wrapper around [`estimate_texture_bytes`] for callers that
/// already hold a `wgpu::TextureDescriptor`.
pub fn estimate_descriptor_bytes(d: &wgpu::TextureDescriptor<'_>) -> u64 {
    estimate_texture_bytes(
        d.format,
        d.size.width,
        d.size.height,
        d.size.depth_or_array_layers,
        d.sample_count,
        d.mip_level_count,
    )
}