physis/
tex.rs

1// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#![allow(clippy::needless_range_loop)]
5
6use std::io::{Cursor, Read, Seek, SeekFrom};
7
8use crate::ByteSpan;
9use crate::bcn::decode_bc1;
10use crate::bcn::decode_bc3;
11use crate::bcn::decode_bc5;
12use crate::bcn::decode_bc7;
13use binrw::BinRead;
14use binrw::binrw;
15use bitflags::bitflags;
16
17#[binrw]
18#[derive(Debug)]
19struct TextureAttribute(u32);
20
21// Attributes and Format are adapted from Lumina (https://github.com/NotAdam/Lumina/blob/master/src/Lumina/Data/Files/TexFile.cs)
22bitflags! {
23    impl TextureAttribute : u32 {
24        const DISCARD_PER_FRAME = 0x1;
25        const DISCARD_PER_MAP = 0x2;
26
27        const MANAGED = 0x4;
28        const USER_MANAGED = 0x8;
29        const CPU_READ = 0x10;
30        const LOCATION_MAIN = 0x20;
31        const NO_GPU_READ = 0x40;
32        const ALIGNED_SIZE = 0x80;
33        const EDGE_CULLING = 0x100;
34        const LOCATION_ONION = 0x200;
35        const READ_WRITE = 0x400;
36        const IMMUTABLE = 0x800;
37
38        const TEXTURE_RENDER_TARGET = 0x100000;
39        const TEXTURE_DEPTH_STENCIL = 0x200000;
40        const TEXTURE_TYPE1_D = 0x400000;
41        const TEXTURE_TYPE2_D = 0x800000;
42        const TEXTURE_TYPE3_D = 0x1000000;
43        const TEXTURE_TYPE2_D_ARRAY = 0x10000000;
44        const TEXTURE_TYPE_CUBE = 0x2000000;
45        const TEXTURE_TYPE_MASK = 0x3C00000;
46        const TEXTURE_SWIZZLE = 0x4000000;
47        const TEXTURE_NO_TILED = 0x8000000;
48        const TEXTURE_NO_SWIZZLE = 0x80000000;
49    }
50}
51
52// From https://github.com/aers/FFXIVClientStructs/blob/344f5d488197e9c9d5fd78e92439e7104f25e2e0/FFXIVClientStructs/FFXIV/Client/Graphics/Kernel/Texture.cs#L97
53#[binrw]
54#[brw(repr = u32)]
55#[derive(Debug)]
56#[allow(non_camel_case_types)] // NOTE: It's currently allowed to make updating this list not a giant pain
57enum TextureFormat {
58    L8_UNORM = 0x1130,
59    A8_UNORM = 0x1131,
60    R8_UNORM = 0x1132,
61    R8_UINT = 0x1133,
62    R16_UINT = 0x1140,
63    R32_UINT = 0x1150,
64    R8G8_UNORM = 0x1240,
65    B4G4R4A4_UNORM = 0x1440,
66    B5G5R5A1_UNORM = 0x1441,
67    B8G8R8A8_UNORM = 0x1450,
68    B8G8R8X8_UNORM = 0x1451,
69    R16_FLOAT = 0x2140,
70    R32_FLOAT = 0x2150,
71    R16G16_FLOAT = 0x2250,
72    R32G32_FLOAT = 0x2260,
73    R11G11B10_FLOAT = 0x2350,
74    R16G16B16A16_FLOAT = 0x2460,
75    R32G32B32A32_FLOAT = 0x2470,
76    BC1_UNORM = 0x3420,
77    BC2_UNORM = 0x3430,
78    BC3_UNORM = 0x3431,
79    D16_UNORM = 0x4140,
80    D24_UNORM_S8_UINT = 0x4250,
81    D16_UNORM_2 = 0x5140,
82    D24_UNORM_S8_UINT_2 = 0x5150,
83    BC4_UNORM = 0x6120,
84    BC5_UNORM = 0x6230,
85    BC6H_SF16 = 0x6330,
86    BC7_UNORM = 0x6432,
87    R16_UNORM = 0x7140,
88    R16G16_UNORM = 0x7250,
89    R10G10B10A2_UNORM_2 = 0x7350,
90    R10G10B10A2_UNORM = 0x7450,
91    D24_UNORM_S8_UINT_3 = 0x8250,
92}
93
94#[binrw]
95#[derive(Debug)]
96#[allow(dead_code)]
97#[brw(little)]
98struct TexHeader {
99    attribute: TextureAttribute,
100    format: TextureFormat,
101
102    width: u16,
103    height: u16,
104    depth: u16,
105    mip_levels: u16,
106
107    lod_offsets: [u32; 3],
108    offset_to_surface: [u32; 13],
109}
110
111#[repr(C)]
112#[derive(Clone, Copy)]
113pub enum TextureType {
114    TwoDimensional,
115    ThreeDimensional,
116}
117
118pub struct Texture {
119    /// Type of texture
120    pub texture_type: TextureType,
121    /// Width of the texture in pixels
122    pub width: u32,
123    /// Height of the texture in pixels
124    pub height: u32,
125    /// Depth of the texture in pixels
126    pub depth: u32,
127    /// Raw RGBA data
128    pub rgba: Vec<u8>,
129}
130
131type DecodeFunction = fn(&[u8], usize, usize, &mut [u32]) -> Result<(), &'static str>;
132
133impl Texture {
134    /// Reads an existing TEX file
135    pub fn from_existing(buffer: ByteSpan) -> Option<Texture> {
136        let mut cursor = Cursor::new(buffer);
137        let header = TexHeader::read(&mut cursor).ok()?;
138
139        cursor
140            .seek(SeekFrom::Start(std::mem::size_of::<TexHeader>() as u64))
141            .ok()?;
142
143        let mut src = vec![0u8; buffer.len() - std::mem::size_of::<TexHeader>()];
144        cursor.read_exact(src.as_mut_slice()).ok()?;
145
146        let mut dst: Vec<u8>;
147
148        match header.format {
149            TextureFormat::B4G4R4A4_UNORM => {
150                dst =
151                    vec![
152                        0u8;
153                        header.width as usize * header.height as usize * header.depth as usize * 4
154                    ];
155
156                let mut offset = 0;
157                let mut dst_offset = 0;
158
159                for _ in 0..header.width as usize * header.height as usize {
160                    let short: u16 = ((src[offset] as u16) << 8) | src[offset + 1] as u16;
161
162                    let src_b = short & 0xF;
163                    let src_g = (short >> 4) & 0xF;
164                    let src_r = (short >> 8) & 0xF;
165                    let src_a = (short >> 12) & 0xF;
166
167                    dst[dst_offset] = (17 * src_r) as u8;
168                    dst[dst_offset + 1] = (17 * src_g) as u8;
169                    dst[dst_offset + 2] = (17 * src_b) as u8;
170                    dst[dst_offset + 3] = (17 * src_a) as u8;
171
172                    offset += 2;
173                    dst_offset += 4;
174                }
175            }
176            TextureFormat::B8G8R8A8_UNORM => {
177                dst =
178                    vec![
179                        0u8;
180                        header.width as usize * header.height as usize * header.depth as usize * 4
181                    ];
182
183                let mut offset = 0;
184
185                for _ in 0..header.width as usize * header.height as usize * header.depth as usize {
186                    let src_b = src[offset];
187                    let src_g = src[offset + 1];
188                    let src_r = src[offset + 2];
189                    let src_a = src[offset + 3];
190
191                    dst[offset] = src_r;
192                    dst[offset + 1] = src_g;
193                    dst[offset + 2] = src_b;
194                    dst[offset + 3] = src_a;
195
196                    offset += 4;
197                }
198            }
199            TextureFormat::BC1_UNORM => {
200                dst = Texture::decode(
201                    &src,
202                    header.width as usize,
203                    header.height as usize * header.depth as usize,
204                    decode_bc1,
205                );
206            }
207            TextureFormat::BC3_UNORM => {
208                dst = Texture::decode(
209                    &src,
210                    header.width as usize,
211                    header.height as usize * header.depth as usize,
212                    decode_bc3,
213                );
214            }
215            TextureFormat::BC5_UNORM => {
216                dst = Texture::decode(
217                    &src,
218                    header.width as usize,
219                    header.height as usize * header.depth as usize,
220                    decode_bc5,
221                );
222            }
223            TextureFormat::BC7_UNORM => {
224                dst = Texture::decode(
225                    &src,
226                    header.width as usize,
227                    header.height as usize * header.depth as usize,
228                    decode_bc7,
229                );
230            }
231            _ => panic!("Unsupported texture format {:?}!", header.format),
232        }
233
234        Some(Texture {
235            texture_type: if header.attribute.contains(TextureAttribute::TEXTURE_TYPE3_D) {
236                TextureType::ThreeDimensional
237            } else {
238                TextureType::TwoDimensional
239            },
240            width: header.width as u32,
241            height: header.height as u32,
242            depth: header.depth as u32,
243            rgba: dst,
244        })
245    }
246
247    fn decode(src: &[u8], width: usize, height: usize, decode_func: DecodeFunction) -> Vec<u8> {
248        let mut image: Vec<u32> = vec![0; width * height];
249        decode_func(src, width, height, &mut image).unwrap();
250
251        image
252            .iter()
253            .flat_map(|x| {
254                let v = x.to_le_bytes();
255                [v[2], v[1], v[0], v[3]]
256            })
257            .collect::<Vec<u8>>()
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use std::fs::read;
264    use std::path::PathBuf;
265
266    use super::*;
267
268    #[test]
269    fn test_invalid() {
270        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
271        d.push("resources/tests");
272        d.push("random");
273
274        // Feeding it invalid data should not panic
275        Texture::from_existing(&read(d).unwrap());
276    }
277}