pub use ctt_astcenc as astc;
use crate::encoders::Quality;
use crate::encoders::backend::Encoder;
use crate::error::Result;
use crate::surface::{ColorSpace, Surface};
use crate::vk_format::FormatExt as _;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum NormalSwizzle {
#[default]
AstcDefault,
Bc5Compat,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum AstcencUsage {
#[default]
Color,
NormalMap { swizzle: NormalSwizzle },
SingleChannel,
TwoChannel,
HdrRgb,
HdrRgba,
Rgbm,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct AstcencSettings {
pub usage: AstcencUsage,
pub preset: Option<astc::Preset>,
pub use_alpha_weight: bool,
pub perceptual: bool,
pub decode_unorm8: bool,
pub channel_weights: Option<[f32; 4]>,
pub rgbm_m_scale: Option<f32>,
}
const SUPPORTED_FORMATS: &[ktx2::Format] = &[
ktx2::Format::ASTC_4x4_UNORM_BLOCK,
ktx2::Format::ASTC_5x4_UNORM_BLOCK,
ktx2::Format::ASTC_5x5_UNORM_BLOCK,
ktx2::Format::ASTC_6x5_UNORM_BLOCK,
ktx2::Format::ASTC_6x6_UNORM_BLOCK,
ktx2::Format::ASTC_8x5_UNORM_BLOCK,
ktx2::Format::ASTC_8x6_UNORM_BLOCK,
ktx2::Format::ASTC_8x8_UNORM_BLOCK,
ktx2::Format::ASTC_10x5_UNORM_BLOCK,
ktx2::Format::ASTC_10x6_UNORM_BLOCK,
ktx2::Format::ASTC_10x8_UNORM_BLOCK,
ktx2::Format::ASTC_10x10_UNORM_BLOCK,
ktx2::Format::ASTC_12x10_UNORM_BLOCK,
ktx2::Format::ASTC_12x12_UNORM_BLOCK,
];
pub struct AstcencEncoder;
impl Encoder for AstcencEncoder {
type Settings = AstcencSettings;
fn name() -> &'static str {
"astcenc"
}
fn supported_formats() -> &'static [ktx2::Format] {
SUPPORTED_FORMATS
}
fn required_input_format(_format: ktx2::Format, settings: &AstcencSettings) -> ktx2::Format {
match settings.usage {
AstcencUsage::HdrRgb | AstcencUsage::HdrRgba => ktx2::Format::R16G16B16A16_SFLOAT,
_ => ktx2::Format::R8G8B8A8_UNORM,
}
}
fn compress(
surface: &Surface,
format: ktx2::Format,
quality: Quality,
settings: &AstcencSettings,
) -> Result<Vec<u8>> {
let (base, color_space) = format.normalize();
let (block_width, block_height) =
base.block_size().expect("ASTC format must have block size");
let profile = profile_for(settings.usage, color_space);
let preset = settings.preset.unwrap_or_else(|| default_preset(quality));
let flags = flags_for(settings);
let swizzle = swizzle_for(settings.usage);
let data_type = data_type_for(settings.usage);
let mut config = astc::config_init(
profile,
block_width as u32,
block_height as u32,
1,
preset,
flags,
)
.map_err(|e| crate::error::Error::Compression(e.to_string()))?;
if let Some([r, g, b, a]) = settings.channel_weights {
config.cw_r_weight = r;
config.cw_g_weight = g;
config.cw_b_weight = b;
config.cw_a_weight = a;
}
if let Some(scale) = settings.rgbm_m_scale {
config.rgbm_m_scale = scale;
}
let mut ctx = astc::Context::new(&config)
.map_err(|e| crate::error::Error::Compression(e.to_string()))?;
let mut data_ptr = surface.data.as_ptr() as *mut std::ffi::c_void;
let mut img = astc::bindings::astcenc_image {
dim_x: surface.width,
dim_y: surface.height,
dim_z: 1,
data_type,
data: &mut data_ptr,
};
let blocks_x = surface.width.div_ceil(block_width as u32);
let blocks_y = surface.height.div_ceil(block_height as u32);
let output_size = (blocks_x * blocks_y * 16) as usize;
let mut output = vec![0u8; output_size];
ctx.compress(&mut img, swizzle, &mut output)
.map_err(|e| crate::error::Error::Compression(e.to_string()))?;
Ok(output)
}
}
fn profile_for(usage: AstcencUsage, color_space: ColorSpace) -> astc::Profile {
match usage {
AstcencUsage::HdrRgb => astc::Profile::HdrRgbLdrA,
AstcencUsage::HdrRgba => astc::Profile::Hdr,
AstcencUsage::NormalMap { .. } | AstcencUsage::SingleChannel | AstcencUsage::TwoChannel => {
astc::Profile::Ldr
}
AstcencUsage::Color | AstcencUsage::Rgbm => match color_space {
ColorSpace::Srgb => astc::Profile::LdrSrgb,
ColorSpace::Linear => astc::Profile::Ldr,
},
}
}
fn flags_for(settings: &AstcencSettings) -> astc::Flags {
let mut flags = astc::Flags::SELF_DECOMPRESS_ONLY;
match settings.usage {
AstcencUsage::NormalMap { .. } => flags |= astc::Flags::MAP_NORMAL,
AstcencUsage::Rgbm => flags |= astc::Flags::MAP_RGBM,
_ => {}
}
if settings.use_alpha_weight {
flags |= astc::Flags::USE_ALPHA_WEIGHT;
}
if settings.perceptual {
flags |= astc::Flags::USE_PERCEPTUAL;
}
if settings.decode_unorm8 {
flags |= astc::Flags::USE_DECODE_UNORM8;
}
flags
}
fn swizzle_for(usage: AstcencUsage) -> astc::Swizzle {
match usage {
AstcencUsage::NormalMap {
swizzle: NormalSwizzle::AstcDefault,
} => astc::Swizzle::RRRG,
AstcencUsage::NormalMap {
swizzle: NormalSwizzle::Bc5Compat,
} => astc::Swizzle::GGGR,
AstcencUsage::SingleChannel => astc::Swizzle::RRR1,
AstcencUsage::TwoChannel => astc::Swizzle::RRRG,
AstcencUsage::Color | AstcencUsage::HdrRgb | AstcencUsage::HdrRgba | AstcencUsage::Rgbm => {
astc::Swizzle::IDENTITY
}
}
}
fn data_type_for(usage: AstcencUsage) -> astc::bindings::astcenc_type {
use astc::bindings::{astcenc_type_ASTCENC_TYPE_F16, astcenc_type_ASTCENC_TYPE_U8};
match usage {
AstcencUsage::HdrRgb | AstcencUsage::HdrRgba => astcenc_type_ASTCENC_TYPE_F16,
_ => astcenc_type_ASTCENC_TYPE_U8,
}
}
fn default_preset(quality: Quality) -> astc::Preset {
match quality {
Quality::UltraFast => astc::Preset::Fastest,
Quality::VeryFast => astc::Preset::Fast,
Quality::Fast => astc::Preset::Medium,
Quality::Basic => astc::Preset::Medium,
Quality::Slow => astc::Preset::Thorough,
Quality::VerySlow => astc::Preset::Exhaustive,
}
}