use scenix_core::ValidationError;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TextureFormat {
Rgba8Unorm,
Rgba8UnormSrgb,
Rgba16Float,
Depth32Float,
Bc7RgbaUnorm,
Astc4x4RgbaUnorm,
Etc2Rgba8Unorm,
}
impl TextureFormat {
#[inline]
pub const fn is_compressed(self) -> bool {
matches!(
self,
Self::Bc7RgbaUnorm | Self::Astc4x4RgbaUnorm | Self::Etc2Rgba8Unorm
)
}
#[inline]
pub const fn bytes_per_pixel(self) -> Option<usize> {
match self {
Self::Rgba8Unorm | Self::Rgba8UnormSrgb | Self::Depth32Float => Some(4),
Self::Rgba16Float => Some(8),
Self::Bc7RgbaUnorm | Self::Astc4x4RgbaUnorm | Self::Etc2Rgba8Unorm => None,
}
}
#[inline]
pub const fn block_dimensions(self) -> Option<(u32, u32)> {
if self.is_compressed() {
Some((4, 4))
} else {
None
}
}
#[inline]
pub const fn bytes_per_block(self) -> Option<usize> {
if self.is_compressed() { Some(16) } else { None }
}
#[inline]
pub const fn mip_dimensions(width: u32, height: u32, level: u32) -> (u32, u32) {
let w = shr_clamped(width, level);
let h = shr_clamped(height, level);
(if w == 0 { 1 } else { w }, if h == 0 { 1 } else { h })
}
#[inline]
pub fn expected_2d_len(self, width: u32, height: u32) -> Result<usize, ValidationError> {
self.expected_3d_len(width, height, 1)
}
pub fn expected_3d_len(
self,
width: u32,
height: u32,
depth: u32,
) -> Result<usize, ValidationError> {
if width == 0 || height == 0 || depth == 0 {
return Err(ValidationError::OutOfRange);
}
if let Some(bytes_per_pixel) = self.bytes_per_pixel() {
checked_area(width, height, depth)?
.checked_mul(bytes_per_pixel)
.ok_or(ValidationError::OutOfRange)
} else {
let (block_w, block_h) = self.block_dimensions().unwrap_or((4, 4));
let blocks_x = width.div_ceil(block_w);
let blocks_y = height.div_ceil(block_h);
checked_area(blocks_x, blocks_y, depth)?
.checked_mul(self.bytes_per_block().unwrap_or(16))
.ok_or(ValidationError::OutOfRange)
}
}
}
#[inline]
const fn shr_clamped(value: u32, shift: u32) -> u32 {
if shift >= u32::BITS {
0
} else {
value >> shift
}
}
#[inline]
fn checked_area(width: u32, height: u32, depth: u32) -> Result<usize, ValidationError> {
(width as usize)
.checked_mul(height as usize)
.and_then(|value| value.checked_mul(depth as usize))
.ok_or(ValidationError::OutOfRange)
}