Skip to main content

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}