Skip to main content

primase_core/
texformat.rs

1//! `TexFormat`: the one enum for how a texture's bytes are laid out, for every game and target.
2//!
3//! Only the layout lives here (block size, bytes per block or pixel). Colour space is not part of the format: a
4//! DXGI `_SRGB` variant holds the same bits as its linear twin, so `Texture::color_space` carries it. Decoders and
5//! encoders live in `primase-texcodec`; conversions from DXGI, D3DFMT FourCCs and game enums live in the crates that
6//! read those (DDS in `primase-dds`, a game's own enum in its world crate).
7
8/// How a texture's bytes are laid out.
9///
10/// Closed on purpose, as `SurfaceClass` is: a classification of every format (`rgba8_exact`, a decoder's table) matches
11/// it without a wildcard, so adding a format fails to compile until each one says what it does with it.
12#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
13pub enum TexFormat {
14    // ---- uncompressed, 8 bits per channel
15    R8Unorm,
16    R8Snorm,
17    A8Unorm,
18    Rg8Unorm,
19    Rg8Snorm,
20    Rgba8Unorm,
21    Rgba8Snorm,
22    Bgra8Unorm,
23    /// B8G8R8X8: alpha byte ignored (decodes to 255).
24    Bgrx8Unorm,
25    // ---- uncompressed, packed
26    /// B5G6R5 in a little-endian u16 (blue in the low bits).
27    B5g6r5Unorm,
28    /// B5G5R5A1 in a little-endian u16 (blue in the low bits, alpha the top bit).
29    B5g5r5a1Unorm,
30    /// B4G4R4A4 in a little-endian u16.
31    B4g4r4a4Unorm,
32    /// R10G10B10A2 in a little-endian u32 (red in the low bits).
33    Rgb10a2Unorm,
34    // ---- uncompressed, 16 and 32 bits per channel
35    R16Unorm,
36    R16Float,
37    Rg16Unorm,
38    Rg16Snorm,
39    Rg16Float,
40    Rgba16Unorm,
41    Rgba16Float,
42    R32Float,
43    Rg32Float,
44    Rgba32Float,
45    // ---- BCn (4x4 blocks)
46    /// BC1 (DXT1). 3-colour blocks (c0 <= c1) decode index 3 as transparent black (D3D10).
47    Bc1,
48    /// BC2 (DXT3): explicit 4-bit alpha.
49    Bc2,
50    /// BC3 (DXT5): interpolated alpha.
51    Bc3,
52    Bc4Unorm,
53    Bc4Snorm,
54    Bc5Unorm,
55    Bc5Snorm,
56    Bc6hUfloat,
57    Bc6hSfloat,
58    Bc7,
59    // ---- mobile/console block formats (decoded only behind a gate)
60    Etc1,
61    Etc2Rgb,
62    Etc2Rgba,
63    Etc2RgbA1,
64    EacR11Unorm,
65    EacRg11Unorm,
66    /// ASTC LDR with the given block footprint (4x4 to 12x12).
67    Astc {
68        block_w: u8,
69        block_h: u8,
70    },
71}
72
73impl TexFormat {
74    /// Block footprint in pixels: (1, 1) for uncompressed formats.
75    pub fn block_dims(self) -> (u32, u32) {
76        use TexFormat::*;
77        match self {
78            Bc1 | Bc2 | Bc3 | Bc4Unorm | Bc4Snorm | Bc5Unorm | Bc5Snorm | Bc6hUfloat | Bc6hSfloat | Bc7
79            | Etc1 | Etc2Rgb | Etc2Rgba | Etc2RgbA1 | EacR11Unorm | EacRg11Unorm => (4, 4),
80            Astc { block_w, block_h } => (block_w as u32, block_h as u32),
81            _ => (1, 1),
82        }
83    }
84
85    /// Bytes per block (per pixel for uncompressed formats).
86    pub fn block_bytes(self) -> u32 {
87        use TexFormat::*;
88        match self {
89            R8Unorm | R8Snorm | A8Unorm => 1,
90            Rg8Unorm | Rg8Snorm | B5g6r5Unorm | B5g5r5a1Unorm | B4g4r4a4Unorm | R16Unorm | R16Float => 2,
91            Rgba8Unorm | Rgba8Snorm | Bgra8Unorm | Bgrx8Unorm | Rgb10a2Unorm | Rg16Unorm | Rg16Snorm
92            | Rg16Float | R32Float => 4,
93            Rgba16Unorm | Rgba16Float | Rg32Float => 8,
94            Rgba32Float => 16,
95            Bc1 | Bc4Unorm | Bc4Snorm | Etc1 | Etc2Rgb | Etc2RgbA1 | EacR11Unorm => 8,
96            Bc2
97            | Bc3
98            | Bc5Unorm
99            | Bc5Snorm
100            | Bc6hUfloat
101            | Bc6hSfloat
102            | Bc7
103            | Etc2Rgba
104            | EacRg11Unorm
105            | Astc { .. } => 16,
106        }
107    }
108
109    pub fn is_block_compressed(self) -> bool {
110        self.block_dims() != (1, 1)
111    }
112
113    /// Size in bytes of one level of `width` x `height` pixels: whole blocks, at least one per axis.
114    pub fn level_size(self, width: u32, height: u32) -> usize {
115        self.checked_level_size(width, height)
116            .expect("a level size that fits in memory")
117    }
118
119    /// [`TexFormat::level_size`], `None` when it doesn't fit in a `usize` (dimensions a file states).
120    pub fn checked_level_size(self, width: u32, height: u32) -> Option<usize> {
121        let (bw, bh) = self.block_dims();
122        let blocks_x = usize::try_from(width.div_ceil(bw).max(1)).ok()?;
123        let blocks_y = usize::try_from(height.div_ceil(bh).max(1)).ok()?;
124        blocks_x
125            .checked_mul(blocks_y)?
126            .checked_mul(usize::try_from(self.block_bytes()).ok()?)
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::TexFormat::*;
133
134    #[test]
135    fn level_sizes_round_up_to_whole_blocks() {
136        assert_eq!(Bc1.level_size(1, 1), 8);
137        assert_eq!(Bc1.level_size(5, 3), 2 * 8);
138        assert_eq!(Bc7.level_size(1024, 512), 256 * 128 * 16);
139        assert_eq!(Rgba8Unorm.level_size(3, 2), 24);
140        assert_eq!(
141            (Astc {
142                block_w: 6,
143                block_h: 5
144            })
145            .level_size(13, 10),
146            3 * 2 * 16
147        );
148    }
149}