1use scenix_core::ValidationError;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub enum TextureFormat {
7 Rgba8Unorm,
9 Rgba8UnormSrgb,
11 Rgba16Float,
13 Depth32Float,
15 Bc7RgbaUnorm,
17 Astc4x4RgbaUnorm,
19 Etc2Rgba8Unorm,
21}
22
23impl TextureFormat {
24 #[inline]
26 pub const fn is_compressed(self) -> bool {
27 matches!(
28 self,
29 Self::Bc7RgbaUnorm | Self::Astc4x4RgbaUnorm | Self::Etc2Rgba8Unorm
30 )
31 }
32
33 #[inline]
35 pub const fn bytes_per_pixel(self) -> Option<usize> {
36 match self {
37 Self::Rgba8Unorm | Self::Rgba8UnormSrgb | Self::Depth32Float => Some(4),
38 Self::Rgba16Float => Some(8),
39 Self::Bc7RgbaUnorm | Self::Astc4x4RgbaUnorm | Self::Etc2Rgba8Unorm => None,
40 }
41 }
42
43 #[inline]
45 pub const fn block_dimensions(self) -> Option<(u32, u32)> {
46 if self.is_compressed() {
47 Some((4, 4))
48 } else {
49 None
50 }
51 }
52
53 #[inline]
55 pub const fn bytes_per_block(self) -> Option<usize> {
56 if self.is_compressed() { Some(16) } else { None }
57 }
58
59 #[inline]
61 pub const fn mip_dimensions(width: u32, height: u32, level: u32) -> (u32, u32) {
62 let w = shr_clamped(width, level);
63 let h = shr_clamped(height, level);
64 (if w == 0 { 1 } else { w }, if h == 0 { 1 } else { h })
65 }
66
67 #[inline]
69 pub fn expected_2d_len(self, width: u32, height: u32) -> Result<usize, ValidationError> {
70 self.expected_3d_len(width, height, 1)
71 }
72
73 pub fn expected_3d_len(
75 self,
76 width: u32,
77 height: u32,
78 depth: u32,
79 ) -> Result<usize, ValidationError> {
80 if width == 0 || height == 0 || depth == 0 {
81 return Err(ValidationError::OutOfRange);
82 }
83
84 if let Some(bytes_per_pixel) = self.bytes_per_pixel() {
85 checked_area(width, height, depth)?
86 .checked_mul(bytes_per_pixel)
87 .ok_or(ValidationError::OutOfRange)
88 } else {
89 let (block_w, block_h) = self.block_dimensions().unwrap_or((4, 4));
90 let blocks_x = width.div_ceil(block_w);
91 let blocks_y = height.div_ceil(block_h);
92 checked_area(blocks_x, blocks_y, depth)?
93 .checked_mul(self.bytes_per_block().unwrap_or(16))
94 .ok_or(ValidationError::OutOfRange)
95 }
96 }
97}
98
99#[inline]
100const fn shr_clamped(value: u32, shift: u32) -> u32 {
101 if shift >= u32::BITS {
102 0
103 } else {
104 value >> shift
105 }
106}
107
108#[inline]
109fn checked_area(width: u32, height: u32, depth: u32) -> Result<usize, ValidationError> {
110 (width as usize)
111 .checked_mul(height as usize)
112 .and_then(|value| value.checked_mul(depth as usize))
113 .ok_or(ValidationError::OutOfRange)
114}