1use serde_json::{Value, json};
12
13use crate::components::{ColorSpace, NormalConvention, TexRole};
14use crate::json::JsonError;
15use crate::texformat::TexFormat;
16use crate::world::{Component, Entity};
17use iridia_interface::TextureDim;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize)]
21#[serde(rename_all = "snake_case")]
22pub enum TextureShape {
23 Array { layers: u32 },
25 Cube { cubes: u32 },
27 Volume { depth: u32 },
29}
30
31impl TextureShape {
32 pub fn dimension(self) -> (TextureDim, bool) {
35 match self {
36 TextureShape::Array { .. } => (TextureDim::D2, true),
37 TextureShape::Cube { cubes } => (TextureDim::Cube, cubes > 1),
38 TextureShape::Volume { .. } => (TextureDim::D3, false),
39 }
40 }
41
42 pub fn layers_at(self, level: usize) -> u32 {
44 match self {
45 TextureShape::Array { layers } => layers,
46 TextureShape::Cube { cubes } => cubes.saturating_mul(6),
47 TextureShape::Volume { depth } => u32::try_from(level)
48 .ok()
49 .and_then(|l| depth.checked_shr(l))
50 .unwrap_or(0)
51 .max(1),
52 }
53 }
54}
55
56#[derive(Clone, Debug, PartialEq)]
58pub struct TextureLayers {
59 pub shape: TextureShape,
60 pub width: u32,
61 pub height: u32,
62 pub format: TexFormat,
63 pub levels: Vec<Vec<Vec<u8>>>,
66 pub color_space: ColorSpace,
67 pub role: TexRole,
68 pub normal: Option<NormalConvention>,
70}
71
72impl TextureLayers {
73 pub fn layer(&self, level: usize, layer: usize) -> Option<&[u8]> {
75 self.levels.get(level)?.get(layer).map(Vec::as_slice)
76 }
77}
78
79impl Component for TextureLayers {
80 const NAME: &'static str = "core.texture_layers";
81 const GEOMETRY: crate::Geometry = crate::Geometry::None;
82 fn payload_bytes(&self) -> u64 {
84 self.levels.iter().flatten().map(|l| l.len() as u64).sum()
85 }
86 fn verify(&self, _e: Entity, _w: &crate::World) -> Result<(), String> {
89 if self.width == 0 || self.height == 0 {
90 return Err(format!("{} x {} pixels", self.width, self.height));
91 }
92 let (count, name, extent) = match self.shape {
93 TextureShape::Array { layers } => (layers, "layers", 1),
94 TextureShape::Cube { cubes } => (cubes, "cubes", 1),
95 TextureShape::Volume { depth } => (depth, "depth", depth),
96 };
97 if count == 0 {
98 return Err(format!("0 {name}"));
99 }
100 let most = self.width.max(self.height).max(extent).ilog2() + 1;
101 if self.levels.is_empty() || self.levels.len() > most as usize {
102 return Err(format!(
103 "{} levels for {} x {} {:?} (1 to {most})",
104 self.levels.len(),
105 self.width,
106 self.height,
107 self.shape
108 ));
109 }
110 for (i, level) in self.levels.iter().enumerate() {
111 let shift = u32::try_from(i).unwrap_or(u32::MAX);
112 let (w, h) = (
113 self.width.checked_shr(shift).unwrap_or(0).max(1),
114 self.height.checked_shr(shift).unwrap_or(0).max(1),
115 );
116 let layers = self.shape.layers_at(i);
117 if level.len() != layers as usize {
118 return Err(format!(
119 "level {i} has {} layers, a {:?} has {layers} there",
120 level.len(),
121 self.shape
122 ));
123 }
124 let Some(size) = self.format.checked_level_size(w, h) else {
125 return Err(format!("level {i}: {w} x {h} doesn't fit in memory"));
126 };
127 if let Some((l, bytes)) = level.iter().enumerate().find(|(_, b)| b.len() != size) {
128 return Err(format!(
129 "level {i} layer {l} ({w} x {h} {:?}): {} bytes, not {size}",
130 self.format,
131 bytes.len()
132 ));
133 }
134 }
135 Ok(())
136 }
137 fn to_json(&self) -> Result<Option<Value>, JsonError> {
139 Ok(Some(json!({
140 "shape": self.shape,
141 "width": self.width,
142 "height": self.height,
143 "format": format!("{:?}", self.format),
144 "levels": self.levels.len(),
145 "color_space": self.color_space,
146 "role": self.role,
147 "normal": self.normal,
148 })))
149 }
150}
151
152#[cfg(test)]
153#[allow(clippy::unwrap_used, reason = "hand-worked fixtures")]
154mod tests {
155 use super::*;
156 use crate::World;
157
158 fn layers(shape: TextureShape, w: u32, h: u32, levels: usize) -> TextureLayers {
159 let format = TexFormat::Rgba8Unorm;
160 TextureLayers {
161 shape,
162 width: w,
163 height: h,
164 format,
165 levels: (0..levels)
166 .map(|i| {
167 let (lw, lh) = ((w >> i).max(1), (h >> i).max(1));
168 (0..shape.layers_at(i))
169 .map(|_| vec![0u8; format.level_size(lw, lh)])
170 .collect()
171 })
172 .collect(),
173 color_space: ColorSpace::Linear,
174 role: TexRole::Other,
175 normal: None,
176 }
177 }
178
179 fn verify(t: &TextureLayers) -> Result<(), String> {
180 let mut w = World::new();
181 let e = w.spawn();
182 t.verify(e, &w)
183 }
184
185 #[test]
186 fn a_shape_says_how_many_images_each_level_holds() {
187 assert_eq!(TextureShape::Array { layers: 5 }.layers_at(3), 5);
188 assert_eq!(TextureShape::Cube { cubes: 1 }.layers_at(2), 6);
189 assert_eq!(TextureShape::Cube { cubes: 3 }.layers_at(0), 18);
190 let v = TextureShape::Volume { depth: 33 };
191 assert_eq!([0, 1, 2, 5, 6, 40].map(|l| v.layers_at(l)), [33, 16, 8, 1, 1, 1]);
192 }
193
194 #[test]
195 fn a_shape_is_the_dimension_a_shader_resource_reads() {
196 assert_eq!(
197 TextureShape::Array { layers: 1 }.dimension(),
198 (TextureDim::D2, true)
199 );
200 assert_eq!(
201 TextureShape::Cube { cubes: 1 }.dimension(),
202 (TextureDim::Cube, false)
203 );
204 assert_eq!(
205 TextureShape::Cube { cubes: 2 }.dimension(),
206 (TextureDim::Cube, true)
207 );
208 assert_eq!(
209 TextureShape::Volume { depth: 8 }.dimension(),
210 (TextureDim::D3, false)
211 );
212 }
213
214 #[test]
215 fn each_level_holds_the_shapes_layers_of_the_formats_size() {
216 assert_eq!(
217 verify(&layers(TextureShape::Array { layers: 3 }, 8, 4, 4)),
218 Ok(())
219 );
220 assert_eq!(verify(&layers(TextureShape::Cube { cubes: 1 }, 4, 4, 3)), Ok(()));
221 assert_eq!(
222 verify(&layers(TextureShape::Volume { depth: 5 }, 4, 4, 3)),
223 Ok(())
224 );
225 assert_eq!(
227 verify(&layers(TextureShape::Volume { depth: 8 }, 2, 2, 4)),
228 Ok(())
229 );
230
231 let mut t = layers(TextureShape::Array { layers: 3 }, 8, 4, 2);
232 t.levels[1].pop();
233 assert!(verify(&t).unwrap_err().contains("level 1 has 2 layers"));
234 let mut t = layers(TextureShape::Array { layers: 2 }, 8, 4, 2);
235 t.levels[0][1].push(0);
236 assert!(verify(&t).unwrap_err().contains("level 0 layer 1"));
237 let mut v = layers(TextureShape::Volume { depth: 4 }, 4, 4, 2);
238 v.levels[1].push(vec![0; 16]);
239 assert!(verify(&v).unwrap_err().contains("level 1 has 3 layers"));
240 }
241
242 #[test]
243 fn a_texture_with_nothing_in_it_or_too_many_levels_is_refused() {
244 assert!(
245 verify(&layers(TextureShape::Array { layers: 0 }, 4, 4, 1))
246 .unwrap_err()
247 .contains("0 layers")
248 );
249 assert!(
250 verify(&layers(TextureShape::Cube { cubes: 0 }, 4, 4, 1))
251 .unwrap_err()
252 .contains("0 cubes")
253 );
254 assert!(
255 verify(&layers(TextureShape::Array { layers: 1 }, 0, 4, 1))
256 .unwrap_err()
257 .contains("0 x 4")
258 );
259 let mut t = layers(TextureShape::Array { layers: 1 }, 4, 4, 3);
260 t.levels.push(vec![vec![0; 4]]);
261 assert!(verify(&t).unwrap_err().contains("4 levels for 4 x 4"));
262 t.levels.clear();
263 assert!(verify(&t).unwrap_err().contains("0 levels"));
264 }
265
266 #[test]
267 fn a_layer_is_found_by_level_and_index() {
268 let mut t = layers(TextureShape::Array { layers: 2 }, 2, 2, 1);
269 t.levels[0][1][0] = 7;
270 assert_eq!(t.layer(0, 1).map(|l| l[0]), Some(7));
271 assert_eq!(t.layer(0, 2), None);
272 assert_eq!(t.layer(1, 0), None);
273 assert_eq!(t.payload_bytes(), 32);
274 }
275}