ctt 0.3.0

Compress images to GPU texture formats
Documentation
//! Top-level load entry points for each pipeline variant.
//!
//! Each loader:
//! 1. Classifies the surface's format.
//! 2. Dispatches to the matching kernel in [`super::load_kernels`].
//! 3. Premultiplies alpha on float pipelines if the source is
//!    [`AlphaMode::Straight`].

use crate::alpha::AlphaMode;
use crate::error::{Error, Result};
use crate::format_kind::{FormatFamily, FormatKind, classify};
use crate::surface::{ColorSpace, Surface};

use super::alpha;
use super::buffer::Buffer;
use super::load_kernels as k;

/// Scalar sRGB EOTF for the post-pass on non-sRGB-native FormatKinds whose
/// `Surface::color_space` is nonetheless `Srgb`.
fn srgb_eotf_scalar(c: f32) -> f32 {
    if c <= 0.04045 {
        c / 12.92
    } else {
        ((c + 0.055) / 1.055).powf(2.4)
    }
}

/// Load a surface into the f32 pipeline (linear + premultiplied).
pub fn load_f32(surface: &Surface) -> Result<Buffer<f32>> {
    profiling::scope!("load_f32");
    let info = classify(surface.format, surface.color_space).ok_or_else(|| {
        Error::UnsupportedFormat(format!(
            "float pipeline: unsupported input format {:?}",
            surface.format
        ))
    })?;

    if info.family.is_integer() {
        return Err(Error::UnsupportedConversion(format!(
            "cannot load integer format {:?} into float pipeline",
            surface.format,
        )));
    }

    let (mut buf, srgb_decoded_by_kernel) = match info.kind {
        FormatKind::U8 => (k::load_u8_unorm_f32(surface, info.channels)?, false),
        FormatKind::I8 => (k::load_i8_snorm_f32(surface, info.channels)?, false),
        FormatKind::Srgb8 => (k::load_srgb8_f32(surface, info.channels)?, true),
        FormatKind::Bgra8 => (k::load_bgra8_unorm_f32(surface)?, false),
        FormatKind::Bgra8Srgb => (k::load_bgra8_srgb_f32(surface)?, true),
        FormatKind::Bgr8 => (k::load_bgr8_unorm_f32(surface)?, false),
        FormatKind::Bgr8Srgb => (k::load_bgr8_srgb_f32(surface)?, true),
        FormatKind::U16 => (k::load_u16_unorm_f32(surface, info.channels)?, false),
        FormatKind::I16 => (k::load_i16_snorm_f32(surface, info.channels)?, false),
        FormatKind::F16 => (k::load_f16_f32(surface, info.channels)?, false),
        FormatKind::F32 => (k::load_f32_f32(surface, info.channels)?, false),
        other => {
            return Err(Error::UnsupportedFormat(format!(
                "float pipeline: unsupported format kind {other:?}"
            )));
        }
    };

    // Respect Surface::color_space for FormatKinds that don't encode sRGB in
    // the format itself (e.g. R16G16B16A16_UNORM + ColorSpace::Srgb). 8-bit
    // kinds are promoted to their sRGB variants by `classify` and decoded
    // through the LUT inside the kernel.
    if !srgb_decoded_by_kernel && surface.color_space == ColorSpace::Srgb {
        profiling::scope!("srgb_eotf_scalar_f32");
        for p in buf.pixels.iter_mut() {
            p[0] = srgb_eotf_scalar(p[0]);
            p[1] = srgb_eotf_scalar(p[1]);
            p[2] = srgb_eotf_scalar(p[2]);
        }
    }

    if surface.alpha == AlphaMode::Straight {
        alpha::premultiply_f32(&mut buf);
    }

    Ok(buf)
}

/// Load a surface into the f64 pipeline (float only, no sRGB / no integer).
pub fn load_f64(surface: &Surface) -> Result<Buffer<f64>> {
    profiling::scope!("load_f64");
    let info = classify(surface.format, surface.color_space).ok_or_else(|| {
        Error::UnsupportedFormat(format!(
            "f64 pipeline: unsupported input format {:?}",
            surface.format
        ))
    })?;

    if !matches!(info.family, FormatFamily::Float) {
        return Err(Error::UnsupportedConversion(format!(
            "f64 pipeline requires a float-family source, got {:?}",
            surface.format,
        )));
    }

    let mut buf = match info.kind {
        FormatKind::F32 => k::load_f32_f64(surface, info.channels)?,
        FormatKind::F64 => k::load_f64_f64(surface, info.channels)?,
        other => {
            return Err(Error::UnsupportedFormat(format!(
                "f64 pipeline: unsupported format kind {other:?}"
            )));
        }
    };

    if surface.alpha == AlphaMode::Straight {
        alpha::premultiply_f64(&mut buf);
    }

    Ok(buf)
}

/// Load a surface into the u32 pipeline (UINT/SINT only, sign-extended bit-cast).
pub fn load_u32(surface: &Surface) -> Result<Buffer<u32>> {
    profiling::scope!("load_u32");
    let info = classify(surface.format, surface.color_space).ok_or_else(|| {
        Error::UnsupportedFormat(format!(
            "u32 pipeline: unsupported input format {:?}",
            surface.format
        ))
    })?;

    if !info.family.is_integer() {
        return Err(Error::UnsupportedConversion(format!(
            "u32 pipeline requires an integer-family source, got {:?}",
            surface.format,
        )));
    }

    match info.kind {
        FormatKind::U8 => k::load_u8_uint_u32(surface, info.channels),
        FormatKind::I8 => k::load_i8_sint_u32(surface, info.channels),
        FormatKind::U16 => k::load_u16_uint_u32(surface, info.channels),
        FormatKind::I16 => k::load_i16_sint_u32(surface, info.channels),
        FormatKind::U32 => k::load_u32_uint_u32(surface, info.channels),
        FormatKind::I32 => k::load_i32_sint_u32(surface, info.channels),
        other => Err(Error::UnsupportedFormat(format!(
            "u32 pipeline: unsupported format kind {other:?}"
        ))),
    }
}

/// Load a surface into the u64 pipeline (R64_UINT / R64_SINT only).
pub fn load_u64(surface: &Surface) -> Result<Buffer<u64>> {
    profiling::scope!("load_u64");
    let info = classify(surface.format, surface.color_space).ok_or_else(|| {
        Error::UnsupportedFormat(format!(
            "u64 pipeline: unsupported input format {:?}",
            surface.format
        ))
    })?;

    if !info.family.is_integer() {
        return Err(Error::UnsupportedConversion(format!(
            "u64 pipeline requires an integer-family source, got {:?}",
            surface.format,
        )));
    }

    match info.kind {
        FormatKind::U64 => k::load_u64_uint_u64(surface, info.channels),
        FormatKind::I64 => k::load_i64_sint_u64(surface, info.channels),
        other => Err(Error::UnsupportedFormat(format!(
            "u64 pipeline: unsupported format kind {other:?}"
        ))),
    }
}