use crate::{mip_size, Quality, SurfaceError};
use half::f16;
use super::{
Bc1, Bc2, Bc3, Bc4, Bc5, Bc6, Bc7, BLOCK_HEIGHT, BLOCK_WIDTH, CHANNELS, ELEMENTS_PER_BLOCK,
};
impl From<Quality> for intel_tex_2::bc6h::EncodeSettings {
fn from(value: Quality) -> Self {
match value {
Quality::Fast => intel_tex_2::bc6h::very_fast_settings(),
Quality::Normal => intel_tex_2::bc6h::basic_settings(),
Quality::Slow => intel_tex_2::bc6h::slow_settings(),
}
}
}
impl From<Quality> for intel_tex_2::bc7::EncodeSettings {
fn from(value: Quality) -> Self {
match value {
Quality::Fast => intel_tex_2::bc7::alpha_ultra_fast_settings(),
Quality::Normal => intel_tex_2::bc7::alpha_very_fast_settings(),
Quality::Slow => intel_tex_2::bc7::alpha_fast_settings(),
}
}
}
pub trait BcnEncode<T> {
fn compress_surface(
width: u32,
height: u32,
rgba_data: &[T],
quality: Quality,
) -> Result<Vec<u8>, SurfaceError>;
}
impl BcnEncode<u8> for Bc1 {
fn compress_surface(
width: u32,
height: u32,
rgba8_data: &[u8],
_: Quality,
) -> Result<Vec<u8>, SurfaceError> {
let surface = intel_tex_2::RgbaSurface {
width,
height,
stride: width * CHANNELS as u32,
data: rgba8_data,
};
Ok(intel_tex_2::bc1::compress_blocks(&surface))
}
}
impl BcnEncode<u8> for Bc2 {
fn compress_surface(
width: u32,
height: u32,
rgba8_data: &[u8],
_: Quality,
) -> Result<Vec<u8>, SurfaceError> {
let surface = intel_tex_2::RgbaSurface {
width,
height,
stride: width * CHANNELS as u32,
data: rgba8_data,
};
let bc3_data = intel_tex_2::bc3::compress_blocks(&surface);
let mut data = Vec::new();
let mut block_index = 0;
for y in (0..height).step_by(BLOCK_HEIGHT) {
for x in (0..width).step_by(BLOCK_WIDTH) {
let bc2_block =
encode_bc2_block(x, y, width, height, rgba8_data, &bc3_data, block_index);
data.extend_from_slice(&bc2_block.to_le_bytes());
block_index += 1;
}
}
Ok(data)
}
}
fn encode_bc2_block(
x: u32,
y: u32,
width: u32,
height: u32,
rgba8_data: &[u8],
bc3_data: &[u8],
block_index: usize,
) -> u128 {
let alpha = sharp_alpha_block(x, y, width, height, rgba8_data);
let block_size = 16;
let block_start = block_index * block_size;
let bc3_block_data = bc3_data[block_start..block_start + block_size]
.try_into()
.unwrap();
let bc3_color_block = u128::from_le_bytes(bc3_block_data) >> u64::BITS;
(bc3_color_block << u64::BITS) | alpha as u128
}
fn sharp_alpha_block(x: u32, y: u32, width: u32, height: u32, rgba8_data: &[u8]) -> u64 {
let mut alpha = 0u64;
for i in 0..BLOCK_HEIGHT {
for j in 0..BLOCK_WIDTH {
let x_final = (x as usize + i).min(width.saturating_sub(1) as usize);
let y_final = (y as usize + j).min(height.saturating_sub(1) as usize);
let input_index = (y_final * width as usize + x_final) * CHANNELS + 3;
let output_index = j * BLOCK_HEIGHT + i;
alpha |= ((rgba8_data[input_index] / 17) as u64) << (output_index * 4);
}
}
alpha
}
impl BcnEncode<u8> for Bc3 {
fn compress_surface(
width: u32,
height: u32,
rgba8_data: &[u8],
_: Quality,
) -> Result<Vec<u8>, SurfaceError> {
let surface = intel_tex_2::RgbaSurface {
width,
height,
stride: width * CHANNELS as u32,
data: rgba8_data,
};
Ok(intel_tex_2::bc3::compress_blocks(&surface))
}
}
impl BcnEncode<u8> for Bc4 {
fn compress_surface(
width: u32,
height: u32,
rgba8_data: &[u8],
_: Quality,
) -> Result<Vec<u8>, SurfaceError> {
let r8_data: Vec<_> = rgba8_data.chunks_exact(4).map(|p| p[0]).collect();
let surface = intel_tex_2::RSurface {
width,
height,
stride: width,
data: &r8_data,
};
Ok(intel_tex_2::bc4::compress_blocks(&surface))
}
}
impl BcnEncode<u8> for Bc5 {
fn compress_surface(
width: u32,
height: u32,
rgba8_data: &[u8],
_: Quality,
) -> Result<Vec<u8>, SurfaceError> {
let rg8_data: Vec<_> = rgba8_data
.chunks_exact(4)
.flat_map(|p| [p[0], p[1]])
.collect();
let surface = intel_tex_2::RgSurface {
width,
height,
stride: width * 2,
data: &rg8_data,
};
Ok(intel_tex_2::bc5::compress_blocks(&surface))
}
}
impl BcnEncode<f32> for Bc6 {
fn compress_surface(
width: u32,
height: u32,
rgba8_data: &[f32],
quality: Quality,
) -> Result<Vec<u8>, SurfaceError> {
let f16_data: Vec<f16> = rgba8_data.iter().copied().map(f16::from_f32).collect();
let surface = intel_tex_2::RgbaSurface {
width,
height,
stride: width * 4 * std::mem::size_of::<f16>() as u32,
data: bytemuck::cast_slice(&f16_data),
};
Ok(intel_tex_2::bc6h::compress_blocks(
&quality.into(),
&surface,
))
}
}
impl BcnEncode<u8> for Bc6 {
fn compress_surface(
width: u32,
height: u32,
rgba8_data: &[u8],
quality: Quality,
) -> Result<Vec<u8>, SurfaceError> {
let f16_data: Vec<f16> = rgba8_data
.iter()
.map(|v| f16::from_f32(*v as f32 / 255.0))
.collect();
let surface = intel_tex_2::RgbaSurface {
width,
height,
stride: width * 4 * std::mem::size_of::<f16>() as u32,
data: bytemuck::cast_slice(&f16_data),
};
Ok(intel_tex_2::bc6h::compress_blocks(
&quality.into(),
&surface,
))
}
}
impl BcnEncode<u8> for Bc7 {
fn compress_surface(
width: u32,
height: u32,
rgba8_data: &[u8],
quality: Quality,
) -> Result<Vec<u8>, SurfaceError> {
let surface = intel_tex_2::RgbaSurface {
width,
height,
stride: width * CHANNELS as u32,
data: rgba8_data,
};
Ok(intel_tex_2::bc7::compress_blocks(&quality.into(), &surface))
}
}
pub fn encode_bcn<F, T>(
width: u32,
height: u32,
data: &[T],
quality: Quality,
) -> Result<Vec<u8>, SurfaceError>
where
F: BcnEncode<T>,
{
let expected_size = mip_size(
width as usize,
height as usize,
1,
BLOCK_WIDTH,
BLOCK_HEIGHT,
1,
ELEMENTS_PER_BLOCK,
)
.ok_or(SurfaceError::PixelCountWouldOverflow {
width,
height,
depth: 1,
})?;
if data.len() < expected_size {
return Err(SurfaceError::NotEnoughData {
expected: expected_size,
actual: data.len(),
});
}
F::compress_surface(width, height, data, quality)
}
#[cfg(test)]
mod tests {
use super::*;
fn check_compress_bcn<T: BcnEncode<u8>>(rgba: &[u8], quality: Quality) {
encode_bcn::<T, u8>(4, 4, rgba, quality).unwrap();
}
#[test]
fn bc1_compress() {
let rgba = vec![64u8; ELEMENTS_PER_BLOCK];
check_compress_bcn::<Bc1>(&rgba, Quality::Fast);
check_compress_bcn::<Bc1>(&rgba, Quality::Normal);
check_compress_bcn::<Bc1>(&rgba, Quality::Slow);
}
#[test]
fn bc2_compress() {
let rgba = vec![64u8; ELEMENTS_PER_BLOCK];
check_compress_bcn::<Bc2>(&rgba, Quality::Fast);
check_compress_bcn::<Bc2>(&rgba, Quality::Normal);
check_compress_bcn::<Bc2>(&rgba, Quality::Slow);
}
#[test]
fn bc3_compress() {
let rgba = vec![64u8; ELEMENTS_PER_BLOCK];
check_compress_bcn::<Bc3>(&rgba, Quality::Fast);
check_compress_bcn::<Bc3>(&rgba, Quality::Normal);
check_compress_bcn::<Bc3>(&rgba, Quality::Slow);
}
#[test]
fn bc4_compress() {
let rgba = vec![64u8; ELEMENTS_PER_BLOCK];
check_compress_bcn::<Bc4>(&rgba, Quality::Fast);
check_compress_bcn::<Bc4>(&rgba, Quality::Normal);
check_compress_bcn::<Bc4>(&rgba, Quality::Slow);
}
#[test]
fn bc5_compress() {
let rgba = vec![64u8; ELEMENTS_PER_BLOCK];
check_compress_bcn::<Bc5>(&rgba, Quality::Fast);
check_compress_bcn::<Bc5>(&rgba, Quality::Normal);
check_compress_bcn::<Bc5>(&rgba, Quality::Slow);
}
#[test]
fn bc6_compress() {
let rgba = vec![64u8; ELEMENTS_PER_BLOCK];
check_compress_bcn::<Bc6>(&rgba, Quality::Fast);
check_compress_bcn::<Bc6>(&rgba, Quality::Normal);
check_compress_bcn::<Bc6>(&rgba, Quality::Slow);
}
#[test]
fn bc7_compress() {
let rgba = vec![64u8; ELEMENTS_PER_BLOCK];
check_compress_bcn::<Bc7>(&rgba, Quality::Fast);
check_compress_bcn::<Bc7>(&rgba, Quality::Normal);
check_compress_bcn::<Bc7>(&rgba, Quality::Slow);
}
}