rsx_reader/gcm.rs
1//! The 24-byte `CellGcmTexture` descriptor libgcm uses to describe a texture, read as data.
2//!
3//! Every field is big endian. The descriptor carries GPU address metadata (`offset`, `location`); both are kept as
4//! stored and neither says anything about where a payload lies in a file. Whether a descriptor describes storage
5//! that can be decoded is a separate question for the caller: this module reads the fields and refuses only a
6//! descriptor of the wrong length.
7//!
8//! References for libgcm's vocabulary:
9//! <https://github.com/ps3dev/PSL1GHT/blob/master/ppu/include/rsx/gcm_sys.h> (`gcmTexture`).
10
11/// A descriptor of the wrong size.
12#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
13pub enum Error {
14 /// A `CellGcmTexture` is exactly 24 bytes.
15 #[error("CellGcmTexture requires 24 bytes, got {0}")]
16 DescriptorLength(usize),
17}
18
19/// The 24-byte descriptor, including GPU address metadata.
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub struct CellGcmTexture {
22 /// The pixel format byte. Bit `0x20` (LN) selects linear storage with a constant base pitch and bit `0x40`
23 /// (UN) selects unnormalized coordinates; the format proper is `format & !0x60`.
24 pub format: u8,
25 /// The mip count.
26 pub mipmap: u8,
27 /// 1, 2 or 3 (a volume).
28 pub dimension: u8,
29 /// 1 for a cube map.
30 pub cubemap: u8,
31 /// The libgcm component remap word; identity is `0xaae4`.
32 pub remap: u32,
33 /// The width in texels.
34 pub width: u16,
35 /// The height in texels.
36 pub height: u16,
37 /// The depth in texels (1 unless a volume).
38 pub depth: u16,
39 /// The memory location code, as stored.
40 pub location: u8,
41 /// The padding byte after the location, as stored.
42 pub pad: u8,
43 /// The base row pitch in bytes, meaningful for linear storage.
44 pub pitch: u32,
45 /// The GPU address offset, as stored.
46 pub offset: u32,
47}
48
49impl CellGcmTexture {
50 /// Parse exactly one descriptor as data.
51 ///
52 /// # Errors
53 /// [`Error::DescriptorLength`] when `data` is not 24 bytes.
54 pub fn parse(data: &[u8]) -> Result<Self, Error> {
55 let &[
56 format,
57 mipmap,
58 dimension,
59 cubemap,
60 r0,
61 r1,
62 r2,
63 r3,
64 w0,
65 w1,
66 h0,
67 h1,
68 d0,
69 d1,
70 location,
71 pad,
72 p0,
73 p1,
74 p2,
75 p3,
76 o0,
77 o1,
78 o2,
79 o3,
80 ] = <&[u8; 24]>::try_from(data).map_err(|_| Error::DescriptorLength(data.len()))?;
81 Ok(Self {
82 format,
83 mipmap,
84 dimension,
85 cubemap,
86 remap: u32::from_be_bytes([r0, r1, r2, r3]),
87 width: u16::from_be_bytes([w0, w1]),
88 height: u16::from_be_bytes([h0, h1]),
89 depth: u16::from_be_bytes([d0, d1]),
90 location,
91 pad,
92 pitch: u32::from_be_bytes([p0, p1, p2, p3]),
93 offset: u32::from_be_bytes([o0, o1, o2, o3]),
94 })
95 }
96
97 /// LN: linear storage with a constant base pitch.
98 pub fn linear(self) -> bool {
99 self.format & 0x20 != 0
100 }
101
102 /// UN: unnormalized sampling coordinates. It does not change pixel storage.
103 pub fn unnormalized_coordinates(self) -> bool {
104 self.format & 0x40 != 0
105 }
106}