Skip to main content

rsx_reader/
cgbinary.rs

1//! Big-endian revision-6 CgBinaryProgram containers, with offsets relative to
2//! their own header. See the crate README for the layout sources and scope.
3use primase_foundations::bytes::{Endian::Big, View};
4use std::ops::Range;
5
6/// What is wrong with a container, and where.
7#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
8#[error("CgBinaryProgram at byte {offset}: {detail}")]
9pub struct Error {
10    /// The byte offset in the container where the problem is.
11    pub offset: usize,
12    /// What was expected there, or what was found instead.
13    pub detail: String,
14}
15
16fn error(offset: usize, detail: impl Into<String>) -> Error {
17    Error {
18        offset,
19        detail: detail.into(),
20    }
21}
22
23/// The number of components of a numeric `CGtype` (Cg's `cg_datatypes.h`, numbered from
24/// `CG_TYPE_START_ENUM` = 1024): its half, float, fixed, int and bool scalars (1),
25/// vectors of 1 to 4 and matrices of 1 to 4 rows by 1 to 4 columns. `None` for the
26/// samplers, strings, programs and textures, and for a number outside the table.
27pub fn components(ty: u32) -> Option<usize> {
28    // the count of a type `n` places after the first of a run: a vector run counts from `first` components, a run of
29    // 16 matrix types goes 1x1 to 4x4 in row-major order of their shapes
30    let vector = |start: u32, first: u32| ty.saturating_sub(start).saturating_add(first);
31    let matrix = |start: u32| {
32        let m = ty.saturating_sub(start);
33        (m / 4)
34            .saturating_add(1)
35            .saturating_mul((m % 4).saturating_add(1))
36    };
37    let n = match ty {
38        1025..=1028 => vector(1025, 1), // half, half2, half3, half4
39        1029..=1044 => matrix(1029),    // half1x1 .. half4x4
40        1045..=1048 => vector(1045, 1), // float .. float4
41        1049..=1064 => matrix(1049),    // float1x1 .. float4x4
42        1070..=1073 => vector(1070, 1), // fixed .. fixed4
43        1074..=1089 => matrix(1074),    // fixed1x1 .. fixed4x4
44        1090..=1094 => 1,               // half1, float1, fixed1, int, int1
45        1095..=1097 => vector(1095, 2), // int2 .. int4
46        1098..=1113 => matrix(1098),    // int1x1 .. int4x4
47        1114 | 1115 => 1,               // bool, bool1
48        1116..=1118 => vector(1116, 2), // bool2 .. bool4
49        1119..=1134 => matrix(1119),    // bool1x1 .. bool4x4
50        _ => return None, // samplers 1065-1069, string 1135, program 1136, texture 1137, unknown
51    };
52    usize::try_from(n).ok()
53}
54
55/// The offset `delta` bytes after `at`, which the caller has checked to lie in the container.
56fn plus(at: usize, delta: usize) -> Result<usize, Error> {
57    at.checked_add(delta)
58        .ok_or_else(|| error(at, "offset overflows the address space"))
59}
60
61/// The stage-specific header of a vertex program.
62#[derive(Clone, Debug, PartialEq, Eq)]
63pub struct Vertex {
64    /// The number of 16-byte microcode slots.
65    pub instruction_count: u32,
66    /// The slot the program starts at.
67    pub instruction_slot: u32,
68    /// The number of registers it uses.
69    pub register_count: u32,
70    /// The vertex attributes it reads, a mask.
71    pub attribute_input_mask: u32,
72    /// The attributes it writes, a mask.
73    pub attribute_output_mask: u32,
74    /// The user clip planes it writes, a mask.
75    pub user_clip_mask: u32,
76}
77
78/// The stage-specific header of a fragment program.
79#[derive(Clone, Debug, PartialEq, Eq)]
80pub struct Fragment {
81    /// Number of 16-byte microcode slots, including embedded constants.
82    pub instruction_count: u32,
83    /// The interpolated attributes it reads, a mask.
84    pub attribute_input_mask: u32,
85    /// Two bits per texture unit, kept as encoded.
86    pub partial_tex_type: u32,
87    /// The texture coordinate sets it reads, a mask.
88    pub tex_coords_input_mask: u16,
89    /// The sets that are two-component, a mask.
90    pub tex_coords_2d: u16,
91    /// The sets interpolated at the centroid, a mask.
92    pub tex_coords_centroid: u16,
93    /// The number of registers it uses.
94    pub register_count: u8,
95    /// Whether the colour output is written from the half-precision register 0.
96    pub output_from_h0: bool,
97    /// Whether the program writes depth.
98    pub depth_replace: bool,
99    /// Whether the program can kill the pixel.
100    pub pixel_kill: bool,
101    /// The two padding bytes after the flags, as stored.
102    pub padding: [u8; 2],
103}
104
105/// A program's stage and its stage-specific header.
106#[derive(Clone, Debug, PartialEq, Eq)]
107pub enum Stage {
108    /// A vertex program.
109    Vertex(Vertex),
110    /// A fragment program.
111    Fragment(Fragment),
112}
113
114/// One parameter record of the container.
115#[derive(Clone, Debug, PartialEq, Eq)]
116pub struct Parameter<'a> {
117    /// Original CGtype, CGresource and CGenum values, including unknown values.
118    pub ty: u32,
119    /// The CGresource (a texture unit, an attribute, ...).
120    pub resource: u32,
121    /// The CGenum variability (varying, uniform, ...).
122    pub variability: u32,
123    /// The index within the resource, as a signed value.
124    pub resource_index: i32,
125    /// NUL-terminated source strings, excluding their terminator; no encoding conversion.
126    pub name: Option<&'a [u8]>,
127    /// The semantic string, where the parameter has one.
128    pub semantic: Option<&'a [u8]>,
129    /// The CGenum direction (in, out, ...).
130    pub direction: u32,
131    /// -1 denotes a global parameter.
132    pub parameter_number: i32,
133    /// Whether the program references the parameter.
134    pub referenced: bool,
135    /// Whether the parameter is shared between programs.
136    pub shared: bool,
137    /// The default value's words as stored (big-endian, NaNs and signed zeros
138    /// kept): one per component of the parameter's numeric type (half, float,
139    /// fixed, int and bool scalars, vectors and matrices; [`components`]). The
140    /// container types the field `CgBinaryFloatOffset`, so the words are float
141    /// bits whatever the type. A default on another type is refused.
142    pub default_value: Option<Vec<u32>>,
143    /// Byte offsets of 16-byte constants in this program's microcode.
144    pub embedded_constants: Vec<u32>,
145}
146
147impl Parameter<'_> {
148    /// CG_TEXUNIT0 through CG_TEXUNIT15. A resource index alone is not a texunit.
149    pub fn texture_unit(&self) -> Option<u32> {
150        self.resource.checked_sub(2048).filter(|&n| n < 16)
151    }
152}
153
154/// A container as read.
155#[derive(Clone, Debug, PartialEq, Eq)]
156pub struct Program<'a> {
157    /// The profile: 7003 vertex, 7004 fragment.
158    pub profile: u32,
159    /// The binary revision (6).
160    pub revision: u32,
161    /// The stage-specific header.
162    pub stage: Stage,
163    /// The parameter records, in order.
164    pub parameters: Vec<Parameter<'a>>,
165    /// Original byte order and inline constants retained verbatim.
166    pub microcode: &'a [u8],
167    /// Exactly the advertised container size, excluding any caller's padding.
168    pub bytes: &'a [u8],
169}
170
171struct Reader<'a> {
172    bytes: &'a [u8],
173    fixed: Vec<Range<usize>>,
174}
175
176impl<'a> Reader<'a> {
177    fn bytes(&self, at: usize, n: usize) -> Result<&'a [u8], Error> {
178        View::new(self.bytes)
179            .slice(at, n)
180            .map_err(|e| error(at, e.to_string()))
181    }
182    fn word(&self, at: usize) -> Result<u32, Error> {
183        View::new(self.bytes)
184            .u32(at, Big)
185            .map_err(|e| error(at, e.to_string()))
186    }
187    fn offset(&self, at: usize) -> Result<usize, Error> {
188        usize::try_from(self.word(at)?).map_err(|_| error(at, "offset outside address space"))
189    }
190    fn boolean(&self, at: usize, byte: bool) -> Result<bool, Error> {
191        let n = if byte {
192            u32::from(
193                *self
194                    .bytes(at, 1)?
195                    .first()
196                    .ok_or_else(|| error(at, "a boolean byte"))?,
197            )
198        } else {
199            self.word(at)?
200        };
201        match n {
202            0 => Ok(false),
203            1 => Ok(true),
204            _ => Err(error(at, "boolean is neither zero nor one")),
205        }
206    }
207    fn range(&self, at: usize, count: usize, stride: usize) -> Result<Range<usize>, Error> {
208        let n = count
209            .checked_mul(stride)
210            .ok_or_else(|| error(at, "array size overflow"))?;
211        self.bytes(at, n)?;
212        Ok(at..plus(at, n)?)
213    }
214    fn reserve(&mut self, r: Range<usize>) -> Result<(), Error> {
215        if !r.start.is_multiple_of(4) {
216            return Err(error(r.start, "unaligned structure"));
217        }
218        self.metadata(r.clone())?;
219        if !r.is_empty() {
220            self.fixed.push(r);
221        }
222        Ok(())
223    }
224    fn metadata(&self, r: Range<usize>) -> Result<(), Error> {
225        if !r.is_empty() && self.fixed.iter().any(|f| r.start < f.end && f.start < r.end) {
226            return Err(error(
227                r.start,
228                "overlapping structures or metadata inside a fixed structure",
229            ));
230        }
231        Ok(())
232    }
233    fn string(&self, field: usize) -> Result<Option<&'a [u8]>, Error> {
234        let at = self.offset(field)?;
235        if at == 0 {
236            return Ok(None);
237        }
238        let tail = self
239            .bytes
240            .get(at..)
241            .ok_or_else(|| error(field, "string offset outside container"))?;
242        let len = tail
243            .iter()
244            .position(|&b| b == 0)
245            .ok_or_else(|| error(at, "unterminated string"))?;
246        let r = self.range(at, plus(len, 1)?, 1)?;
247        self.metadata(r)?;
248        Ok(Some(self.bytes(at, len)?))
249    }
250    fn floats(&self, field: usize, ty: u32) -> Result<Option<Vec<u32>>, Error> {
251        let at = self.offset(field)?;
252        if at == 0 {
253            return Ok(None);
254        }
255        let count = components(ty).ok_or_else(|| {
256            error(
257                field,
258                format!("a default value for CGtype {ty}, which is not a numeric type"),
259            )
260        })?;
261        if !at.is_multiple_of(4) {
262            return Err(error(field, "unaligned default value"));
263        }
264        let r = self.range(at, count, 4)?;
265        self.metadata(r.clone())?;
266        r.step_by(4)
267            .map(|p| self.word(p))
268            .collect::<Result<Vec<_>, _>>()
269            .map(Some)
270    }
271    fn embedded(&self, field: usize, ucode_size: usize) -> Result<Vec<u32>, Error> {
272        let at = self.offset(field)?;
273        if at == 0 {
274            return Ok(Vec::new());
275        }
276        if !at.is_multiple_of(4) {
277            return Err(error(field, "unaligned embedded constant table"));
278        }
279        let count = self.offset(at)?;
280        let r = self.range(
281            at,
282            count
283                .checked_add(1)
284                .ok_or_else(|| error(at, "constant count overflow"))?,
285            4,
286        )?;
287        self.metadata(r.clone())?;
288        let mut offsets = Vec::new();
289        for field in r.step_by(4).skip(1) {
290            let offset = self.word(field)?;
291            let n = usize::try_from(offset).map_err(|_| error(field, "constant outside address space"))?;
292            if !n.is_multiple_of(16) || n.checked_add(16).is_none_or(|end| end > ucode_size) {
293                return Err(error(
294                    field,
295                    "embedded constant is unaligned or outside microcode",
296                ));
297            }
298            offsets.push(offset);
299        }
300        Ok(offsets)
301    }
302}
303
304/// Read one container with no trailing bytes. Padding and concatenation belong
305/// to the calling file format; use [`parse_prefix`] for those.
306pub fn parse(bytes: &[u8]) -> Result<Program<'_>, Error> {
307    let p = parse_prefix(bytes)?;
308    if p.bytes.len() != bytes.len() {
309        return Err(error(p.bytes.len(), "trailing bytes after container"));
310    }
311    Ok(p)
312}
313
314/// Read the advertised first container, keeping every offset relative to it.
315/// Refuses unsupported profiles/revisions, out-of-bounds or overlapping fixed
316/// structures, invalid booleans, unterminated strings, and invalid constants.
317pub fn parse_prefix(bytes: &[u8]) -> Result<Program<'_>, Error> {
318    let header = Reader {
319        bytes,
320        fixed: Vec::new(),
321    };
322    header.bytes(0, 32)?;
323    let profile = header.word(0)?;
324    if !matches!(profile, 7003 | 7004) {
325        return Err(error(
326            0,
327            "unsupported profile (expected vertex 7003 or fragment 7004)",
328        ));
329    }
330    let revision = header.word(4)?;
331    if revision != 6 {
332        return Err(error(4, "unsupported binary revision (expected 6)"));
333    }
334    let total = header.offset(8)?;
335    let mut r = Reader {
336        bytes: header.bytes(0, total)?,
337        fixed: Vec::new(),
338    };
339    r.bytes(0, 32)?;
340    r.fixed.push(0..32);
341    let count = r.offset(12)?;
342    let params = r.offset(16)?;
343    let stage_at = r.offset(20)?;
344    let ucode_size = r.offset(24)?;
345    let ucode_at = r.offset(28)?;
346    let param_range = r.range(params, count, 48)?;
347    let params_at = param_range.clone().step_by(48);
348    r.reserve(param_range)?;
349    let stage_range = r.range(stage_at, 1, 24)?;
350    r.reserve(stage_range)?;
351    let ucode_range = r.range(ucode_at, ucode_size, 1)?;
352    r.reserve(ucode_range)?;
353    if !ucode_at.is_multiple_of(16) || !ucode_size.is_multiple_of(16) {
354        return Err(error(28, "microcode offset or size is not 16-byte aligned"));
355    }
356    let stage_bytes = r.bytes(stage_at, 24)?;
357    let s = Reader {
358        bytes: stage_bytes,
359        fixed: Vec::new(),
360    };
361    let instruction_count = s.word(0)?;
362    if usize::try_from(instruction_count)
363        .ok()
364        .and_then(|slots| slots.checked_mul(16))
365        != Some(ucode_size)
366    {
367        return Err(error(
368            stage_at,
369            "instruction slot count disagrees with microcode size",
370        ));
371    }
372    let stage = if profile == 7003 {
373        Stage::Vertex(Vertex {
374            instruction_count,
375            instruction_slot: s.word(4)?,
376            register_count: s.word(8)?,
377            attribute_input_mask: s.word(12)?,
378            attribute_output_mask: s.word(16)?,
379            user_clip_mask: s.word(20)?,
380        })
381    } else {
382        let stage_view = View::new(stage_bytes);
383        let half = |at| {
384            stage_view
385                .u16(at, Big)
386                .map_err(|e| error(stage_at.saturating_add(at), e.to_string()))
387        };
388        let flag = |at| -> Result<bool, Error> { r.boolean(plus(stage_at, at)?, true) };
389        let byte = |at| {
390            stage_view
391                .u8(at)
392                .map_err(|e| error(stage_at.saturating_add(at), e.to_string()))
393        };
394        Stage::Fragment(Fragment {
395            instruction_count,
396            attribute_input_mask: s.word(4)?,
397            partial_tex_type: s.word(8)?,
398            tex_coords_input_mask: half(12)?,
399            tex_coords_2d: half(14)?,
400            tex_coords_centroid: half(16)?,
401            register_count: byte(18)?,
402            output_from_h0: flag(19)?,
403            depth_replace: flag(20)?,
404            pixel_kill: flag(21)?,
405            padding: [byte(22)?, byte(23)?],
406        })
407    };
408    let mut parameters = Vec::new();
409    for at in params_at {
410        let field = |delta| plus(at, delta);
411        let ty = r.word(at)?;
412        // The source signed fields are two's-complement 32-bit values.
413        parameters.push(Parameter {
414            ty,
415            resource: r.word(field(4)?)?,
416            variability: r.word(field(8)?)?,
417            resource_index: r.word(field(12)?)? as i32,
418            name: r.string(field(16)?)?,
419            default_value: r.floats(field(20)?, ty)?,
420            embedded_constants: r.embedded(field(24)?, ucode_size)?,
421            semantic: r.string(field(28)?)?,
422            direction: r.word(field(32)?)?,
423            parameter_number: r.word(field(36)?)? as i32,
424            referenced: r.boolean(field(40)?, false)?,
425            shared: r.boolean(field(44)?, false)?,
426        });
427    }
428    Ok(Program {
429        profile,
430        revision,
431        stage,
432        parameters,
433        microcode: r.bytes(ucode_at, ucode_size)?,
434        bytes: r.bytes,
435    })
436}