dds 0.2.0

DDS de/encoder written in 100% safe Rust
Documentation
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use dds::{header::*, *};
use rand::{seq::SliceRandom, Rng, RngCore};

fn random_bytes(len: usize) -> Vec<u8> {
    let mut out = vec![0; len];
    let mut rng = rand::thread_rng();
    rng.fill_bytes(&mut out);
    out
}

type DataModifier = Box<dyn FnMut(&mut [u8])>;
struct BenchConfig {
    data_modifier: DataModifier,
    size: Size,
    name: String,
}
impl Default for BenchConfig {
    fn default() -> Self {
        Self {
            data_modifier: Box::new(|_| {}),
            size: Size::new(4096, 4096),
            name: String::new(),
        }
    }
}
fn bench_decoder(c: &mut Criterion, format: Format, channels: Channels, precision: Precision) {
    bench_decoder_with(c, format, channels, precision, |_| {});
}
fn bench_decoder_with(
    c: &mut Criterion,
    format: Format,
    channels: Channels,
    precision: Precision,
    create_config: impl FnOnce(&mut BenchConfig),
) {
    let mut config = BenchConfig::default();
    create_config(&mut config);

    let color = ColorFormat::new(channels, precision);
    let mut name = format!("{format:?} -> {color}");
    if !config.name.is_empty() {
        name += " - ";
        name += &config.name;
    }

    c.bench_function(&name, |b| {
        let header = Header::new_image(config.size.width, config.size.height, format);

        let format = Format::from_header(&header).unwrap();
        let layout = DataLayout::from_header(&header).unwrap();

        let surface = layout.texture().unwrap().main();
        let mut bytes = random_bytes(surface.data_len() as usize).into_boxed_slice();
        (config.data_modifier)(&mut bytes);
        let mut output: Vec<u8> =
            vec![0; surface.size().pixels() as usize * color.bytes_per_pixel() as usize];
        b.iter(|| {
            let image = ImageViewMut::new(output.as_mut_slice(), surface.size(), color).unwrap();
            let result = decode(
                black_box(&mut bytes.as_ref()),
                black_box(image),
                format,
                &DecodeOptions::default(),
            );
            black_box(result).unwrap();
        });
    });
}

/// This sets the BC7 block modes such that each mode is equally likely.
///
/// This is necessary, because the block mode is decided by the number of
/// leading zeros, meaning that for random bytes, 50% of the blocks will be
/// mode 0. This does NOT represent real-world data at all, hence this function.
///
/// Note that this is not a perfect solution either, but it should be good
/// enough.
fn random_bc7_modes(data: &mut [u8]) {
    let mut rng = rand::thread_rng();
    for i in (0..data.len()).step_by(16) {
        let mode: u8 = rng.gen_range(0..8);
        let mut byte = data[i];
        byte |= 1;
        byte <<= mode;
        data[i] = byte;
    }
}
fn set_bc7_modes(data: &mut [u8], mode: u8) {
    for i in (0..data.len()).step_by(16) {
        let mut byte = data[i];
        byte |= 1;
        byte <<= mode;
        data[i] = byte;
    }
}

const BC6_MODES: [(u8, u8); 15] = [
    (0b00, 2),
    (0b01, 2),
    (0b00010, 5),
    (0b00110, 5),
    (0b01010, 5),
    (0b01110, 5),
    (0b10010, 5),
    (0b10110, 5),
    (0b11010, 5),
    (0b11110, 5),
    (0b00011, 5),
    (0b00111, 5),
    (0b01011, 5),
    (0b01111, 5),
    (0b11111, 5), // reserved
];
/// This sets the BC6 block modes such that each mode is equally likely.
fn random_bc6_modes(data: &mut [u8]) {
    let mut rng = rand::thread_rng();
    for i in (0..data.len()).step_by(16) {
        let (mode, mode_bits): (u8, u8) = *BC6_MODES.choose(&mut rng).unwrap();
        let mut byte = data[i];
        byte <<= mode_bits;
        byte |= mode;
        data[i] = byte;
    }
}

pub fn uncompressed(c: &mut Criterion) {
    use Channels::*;
    use Precision::*;

    // uncompressed formats
    bench_decoder(c, Format::R8G8B8A8_UNORM, Rgba, U8);
    bench_decoder(c, Format::R8G8B8A8_UNORM, Rgba, U16);
    bench_decoder(c, Format::R8G8B8A8_UNORM, Rgba, F32);
    bench_decoder(c, Format::R8G8B8A8_UNORM, Rgb, U8);
    bench_decoder(c, Format::R8G8B8A8_UNORM, Rgb, U16);
    bench_decoder(c, Format::R8G8B8A8_UNORM, Rgb, F32);

    bench_decoder(c, Format::R8G8B8A8_SNORM, Rgba, U8);
    bench_decoder(c, Format::R8G8B8A8_SNORM, Rgba, U16);
    bench_decoder(c, Format::R8G8B8A8_SNORM, Rgba, F32);
    bench_decoder(c, Format::R8G8B8A8_SNORM, Rgb, U8);
    bench_decoder(c, Format::R8G8B8A8_SNORM, Rgb, U16);
    bench_decoder(c, Format::R8G8B8A8_SNORM, Rgb, F32);

    bench_decoder(c, Format::R16G16_SNORM, Rgba, U8);
    bench_decoder(c, Format::B8G8R8X8_UNORM, Rgba, U8);
    bench_decoder(c, Format::R9G9B9E5_SHAREDEXP, Rgb, U8);

    bench_decoder(c, Format::R16G16B16A16_FLOAT, Rgba, U8);
    bench_decoder(c, Format::R16G16B16A16_FLOAT, Rgba, U16);
    bench_decoder(c, Format::R16G16B16A16_FLOAT, Rgba, F32);

    bench_decoder(c, Format::R32G32B32A32_FLOAT, Rgba, U8);
    bench_decoder(c, Format::R32G32B32A32_FLOAT, Rgba, U16);
    bench_decoder(c, Format::R32G32B32A32_FLOAT, Rgba, F32);

    bench_decoder(c, Format::R11G11B10_FLOAT, Rgba, U8);
    bench_decoder(c, Format::R11G11B10_FLOAT, Rgba, U16);
    bench_decoder(c, Format::R11G11B10_FLOAT, Rgba, F32);

    // sub-sampled formats
    bench_decoder(c, Format::R8G8_B8G8_UNORM, Rgb, U8);

    // block-compressed formats
    bench_decoder(c, Format::BC1_UNORM, Rgba, U8);
    bench_decoder_with(c, Format::BC1_UNORM, Rgba, U8, |c| {
        c.size = Size::new(4095, 4095);
    });
    bench_decoder(c, Format::BC1_UNORM, Rgb, U8);
    bench_decoder_with(c, Format::BC1_UNORM, Rgb, U8, |c| {
        c.size = Size::new(4095, 4095);
    });
    bench_decoder(c, Format::BC4_UNORM, Grayscale, U8);
    bench_decoder(c, Format::BC4_SNORM, Grayscale, U8);
    bench_decoder_with(c, Format::BC7_UNORM, Rgba, U8, |c| {
        c.data_modifier = Box::new(random_bc7_modes);
    });
    bench_decoder_with(c, Format::BC6H_SF16, Rgb, U8, |c| {
        c.data_modifier = Box::new(random_bc6_modes);
        c.size = Size::new(1024, 1024);
    });
    bench_decoder_with(c, Format::BC6H_SF16, Rgb, U16, |c| {
        c.data_modifier = Box::new(random_bc6_modes);
        c.size = Size::new(1024, 1024);
    });
    bench_decoder_with(c, Format::BC6H_SF16, Rgb, F32, |c| {
        c.data_modifier = Box::new(random_bc6_modes);
        c.size = Size::new(1024, 1024);
    });
    bench_decoder_with(c, Format::BC6H_UF16, Rgb, U8, |c| {
        c.data_modifier = Box::new(random_bc6_modes);
        c.size = Size::new(1024, 1024);
    });
    bench_decoder_with(c, Format::BC6H_UF16, Rgb, U16, |c| {
        c.data_modifier = Box::new(random_bc6_modes);
        c.size = Size::new(1024, 1024);
    });
    bench_decoder_with(c, Format::BC6H_UF16, Rgb, F32, |c| {
        c.data_modifier = Box::new(random_bc6_modes);
        c.size = Size::new(1024, 1024);
    });
}

pub fn bc7_modes(c: &mut Criterion) {
    use Channels::*;
    use Precision::*;

    let modes = [0, 1, 2, 3, 4, 5, 6, 7];
    for mode in modes {
        bench_decoder_with(c, Format::BC7_UNORM, Rgba, U8, |c| {
            c.data_modifier = Box::new(move |data| {
                set_bc7_modes(data, mode);
            });
            c.name = format!("mode {mode}");
            c.size = Size::new(1024, 1024);
        });
    }
}

criterion_group!(benches, uncompressed, bc7_modes);
criterion_main!(benches);