Skip to main content

primase_core/
texture_layers.rs

1//! A texture with more than one image: an array of 2D images, a cube map (or an array of them) or a volume. Beside
2//! [`Texture`](crate::Texture), which stays one 2D image and is what the image tools and the glTF writer know: a
3//! tool that handles only [`Texture`](crate::Texture) never meets a layered one. A shader binding may name either
4//! ([`crate::shader::TextureSource`]), and the slot's dimension has to agree with the shape
5//! ([`TextureShape::dimension`]).
6//!
7//! The pixels are `levels[level][layer]`: each level holds its layers in the shape's order, every layer exactly the
8//! format's size for the level's width and height. How a source orders the same bytes (slice after slice, or mip
9//! after mip, which of the six faces comes first) is the reader's business: it puts them in this order once.
10
11use 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/// What the layers of a [`TextureLayers`] are.
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize)]
21#[serde(rename_all = "snake_case")]
22pub enum TextureShape {
23    /// `layers` images of the same size; every level has all of them.
24    Array { layers: u32 },
25    /// `cubes` cube maps (1 for a plain one), six faces each in Direct3D's order: +X, -X, +Y, -Y, +Z, -Z.
26    Cube { cubes: u32 },
27    /// A volume `depth` texels deep; each level halves the depth as it halves the width and height (down to 1).
28    Volume { depth: u32 },
29}
30
31impl TextureShape {
32    /// The dimension a shader resource reads this shape as, and whether the resource is arrayed: an array is an
33    /// arrayed 2D texture, one cube a cube and several an arrayed cube, a volume a 3D texture.
34    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    /// The images each level of this shape holds: the layers, six for each cube, or the depth halved `level` times.
43    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/// Pixels or encoded blocks of a texture with several images ([`TextureShape`]).
57#[derive(Clone, Debug, PartialEq)]
58pub struct TextureLayers {
59    pub shape: TextureShape,
60    pub width: u32,
61    pub height: u32,
62    pub format: TexFormat,
63    /// `levels[level][layer]`, largest level first; each layer exactly `format.level_size(w >> level, h >> level)`
64    /// bytes (axes floor at 1) and each level [`TextureShape::layers_at`] layers.
65    pub levels: Vec<Vec<Vec<u8>>>,
66    pub color_space: ColorSpace,
67    pub role: TexRole,
68    /// For `TexRole::Normal`.
69    pub normal: Option<NormalConvention>,
70}
71
72impl TextureLayers {
73    /// The layer `layer` of level `level`.
74    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    /// Every layer of every level.
83    fn payload_bytes(&self) -> u64 {
84        self.levels.iter().flatten().map(|l| l.len() as u64).sum()
85    }
86    /// A size, a shape with something in it, at least one level and no more than the size has, each level the shape's
87    /// number of layers and each layer exactly its format's size for the level's dimensions.
88    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    /// A summary, not the pixels.
138    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        // a volume's levels may run to its depth too: 2 x 2 x 8 has four levels
226        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}