1use primase_foundations::bytes::{Endian::Big, View};
4use std::ops::Range;
5
6#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
8#[error("CgBinaryProgram at byte {offset}: {detail}")]
9pub struct Error {
10 pub offset: usize,
12 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
23fn plus(at: usize, delta: usize) -> Result<usize, Error> {
25 at.checked_add(delta)
26 .ok_or_else(|| error(at, "offset overflows the address space"))
27}
28
29#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct Vertex {
32 pub instruction_count: u32,
34 pub instruction_slot: u32,
36 pub register_count: u32,
38 pub attribute_input_mask: u32,
40 pub attribute_output_mask: u32,
42 pub user_clip_mask: u32,
44}
45
46#[derive(Clone, Debug, PartialEq, Eq)]
48pub struct Fragment {
49 pub instruction_count: u32,
51 pub attribute_input_mask: u32,
53 pub partial_tex_type: u32,
55 pub tex_coords_input_mask: u16,
57 pub tex_coords_2d: u16,
59 pub tex_coords_centroid: u16,
61 pub register_count: u8,
63 pub output_from_h0: bool,
65 pub depth_replace: bool,
67 pub pixel_kill: bool,
69 pub padding: [u8; 2],
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
75pub enum Stage {
76 Vertex(Vertex),
78 Fragment(Fragment),
80}
81
82#[derive(Clone, Debug, PartialEq, Eq)]
84pub struct Parameter<'a> {
85 pub ty: u32,
87 pub resource: u32,
89 pub variability: u32,
91 pub resource_index: i32,
93 pub name: Option<&'a [u8]>,
95 pub semantic: Option<&'a [u8]>,
97 pub direction: u32,
99 pub parameter_number: i32,
101 pub referenced: bool,
103 pub shared: bool,
105 pub default_value: Option<Vec<u32>>,
108 pub embedded_constants: Vec<u32>,
110}
111
112impl Parameter<'_> {
113 pub fn texture_unit(&self) -> Option<u32> {
115 self.resource.checked_sub(2048).filter(|&n| n < 16)
116 }
117}
118
119#[derive(Clone, Debug, PartialEq, Eq)]
121pub struct Program<'a> {
122 pub profile: u32,
124 pub revision: u32,
126 pub stage: Stage,
128 pub parameters: Vec<Parameter<'a>>,
130 pub microcode: &'a [u8],
132 pub bytes: &'a [u8],
134}
135
136struct Reader<'a> {
137 bytes: &'a [u8],
138 fixed: Vec<Range<usize>>,
139}
140
141impl<'a> Reader<'a> {
142 fn bytes(&self, at: usize, n: usize) -> Result<&'a [u8], Error> {
143 View::new(self.bytes)
144 .slice(at, n)
145 .map_err(|e| error(at, e.to_string()))
146 }
147 fn word(&self, at: usize) -> Result<u32, Error> {
148 View::new(self.bytes)
149 .u32(at, Big)
150 .map_err(|e| error(at, e.to_string()))
151 }
152 fn offset(&self, at: usize) -> Result<usize, Error> {
153 usize::try_from(self.word(at)?).map_err(|_| error(at, "offset outside address space"))
154 }
155 fn boolean(&self, at: usize, byte: bool) -> Result<bool, Error> {
156 let n = if byte {
157 u32::from(
158 *self
159 .bytes(at, 1)?
160 .first()
161 .ok_or_else(|| error(at, "a boolean byte"))?,
162 )
163 } else {
164 self.word(at)?
165 };
166 match n {
167 0 => Ok(false),
168 1 => Ok(true),
169 _ => Err(error(at, "boolean is neither zero nor one")),
170 }
171 }
172 fn range(&self, at: usize, count: usize, stride: usize) -> Result<Range<usize>, Error> {
173 let n = count
174 .checked_mul(stride)
175 .ok_or_else(|| error(at, "array size overflow"))?;
176 self.bytes(at, n)?;
177 Ok(at..plus(at, n)?)
178 }
179 fn reserve(&mut self, r: Range<usize>) -> Result<(), Error> {
180 if !r.start.is_multiple_of(4) {
181 return Err(error(r.start, "unaligned structure"));
182 }
183 self.metadata(r.clone())?;
184 if !r.is_empty() {
185 self.fixed.push(r);
186 }
187 Ok(())
188 }
189 fn metadata(&self, r: Range<usize>) -> Result<(), Error> {
190 if !r.is_empty() && self.fixed.iter().any(|f| r.start < f.end && f.start < r.end) {
191 return Err(error(
192 r.start,
193 "overlapping structures or metadata inside a fixed structure",
194 ));
195 }
196 Ok(())
197 }
198 fn string(&self, field: usize) -> Result<Option<&'a [u8]>, Error> {
199 let at = self.offset(field)?;
200 if at == 0 {
201 return Ok(None);
202 }
203 let tail = self
204 .bytes
205 .get(at..)
206 .ok_or_else(|| error(field, "string offset outside container"))?;
207 let len = tail
208 .iter()
209 .position(|&b| b == 0)
210 .ok_or_else(|| error(at, "unterminated string"))?;
211 let r = self.range(at, plus(len, 1)?, 1)?;
212 self.metadata(r)?;
213 Ok(Some(self.bytes(at, len)?))
214 }
215 fn floats(&self, field: usize, ty: u32) -> Result<Option<Vec<u32>>, Error> {
216 let at = self.offset(field)?;
217 if at == 0 {
218 return Ok(None);
219 }
220 let count = match ty {
221 1045 | 1091 => 1,
222 1046..=1048 => (ty.saturating_sub(1044)) as usize,
223 1049..=1064 => {
224 let m = ty.saturating_sub(1049);
225 ((m / 4)
226 .saturating_add(1)
227 .saturating_mul((m % 4).saturating_add(1))) as usize
228 }
229 _ => {
230 return Err(error(
231 field,
232 format!("default values for CGtype {ty} are unsupported"),
233 ));
234 }
235 };
236 if !at.is_multiple_of(4) {
237 return Err(error(field, "unaligned default value"));
238 }
239 let r = self.range(at, count, 4)?;
240 self.metadata(r.clone())?;
241 r.step_by(4)
242 .map(|p| self.word(p))
243 .collect::<Result<Vec<_>, _>>()
244 .map(Some)
245 }
246 fn embedded(&self, field: usize, ucode_size: usize) -> Result<Vec<u32>, Error> {
247 let at = self.offset(field)?;
248 if at == 0 {
249 return Ok(Vec::new());
250 }
251 if !at.is_multiple_of(4) {
252 return Err(error(field, "unaligned embedded constant table"));
253 }
254 let count = self.offset(at)?;
255 let r = self.range(
256 at,
257 count
258 .checked_add(1)
259 .ok_or_else(|| error(at, "constant count overflow"))?,
260 4,
261 )?;
262 self.metadata(r.clone())?;
263 let mut offsets = Vec::new();
264 for field in r.step_by(4).skip(1) {
265 let offset = self.word(field)?;
266 let n = usize::try_from(offset).map_err(|_| error(field, "constant outside address space"))?;
267 if !n.is_multiple_of(16) || n.checked_add(16).is_none_or(|end| end > ucode_size) {
268 return Err(error(
269 field,
270 "embedded constant is unaligned or outside microcode",
271 ));
272 }
273 offsets.push(offset);
274 }
275 Ok(offsets)
276 }
277}
278
279pub fn parse(bytes: &[u8]) -> Result<Program<'_>, Error> {
282 let p = parse_prefix(bytes)?;
283 if p.bytes.len() != bytes.len() {
284 return Err(error(p.bytes.len(), "trailing bytes after container"));
285 }
286 Ok(p)
287}
288
289pub fn parse_prefix(bytes: &[u8]) -> Result<Program<'_>, Error> {
293 let header = Reader {
294 bytes,
295 fixed: Vec::new(),
296 };
297 header.bytes(0, 32)?;
298 let profile = header.word(0)?;
299 if !matches!(profile, 7003 | 7004) {
300 return Err(error(
301 0,
302 "unsupported profile (expected vertex 7003 or fragment 7004)",
303 ));
304 }
305 let revision = header.word(4)?;
306 if revision != 6 {
307 return Err(error(4, "unsupported binary revision (expected 6)"));
308 }
309 let total = header.offset(8)?;
310 let mut r = Reader {
311 bytes: header.bytes(0, total)?,
312 fixed: Vec::new(),
313 };
314 r.bytes(0, 32)?;
315 r.fixed.push(0..32);
316 let count = r.offset(12)?;
317 let params = r.offset(16)?;
318 let stage_at = r.offset(20)?;
319 let ucode_size = r.offset(24)?;
320 let ucode_at = r.offset(28)?;
321 let param_range = r.range(params, count, 48)?;
322 let params_at = param_range.clone().step_by(48);
323 r.reserve(param_range)?;
324 let stage_range = r.range(stage_at, 1, 24)?;
325 r.reserve(stage_range)?;
326 let ucode_range = r.range(ucode_at, ucode_size, 1)?;
327 r.reserve(ucode_range)?;
328 if !ucode_at.is_multiple_of(16) || !ucode_size.is_multiple_of(16) {
329 return Err(error(28, "microcode offset or size is not 16-byte aligned"));
330 }
331 let stage_bytes = r.bytes(stage_at, 24)?;
332 let s = Reader {
333 bytes: stage_bytes,
334 fixed: Vec::new(),
335 };
336 let instruction_count = s.word(0)?;
337 if usize::try_from(instruction_count)
338 .ok()
339 .and_then(|slots| slots.checked_mul(16))
340 != Some(ucode_size)
341 {
342 return Err(error(
343 stage_at,
344 "instruction slot count disagrees with microcode size",
345 ));
346 }
347 let stage = if profile == 7003 {
348 Stage::Vertex(Vertex {
349 instruction_count,
350 instruction_slot: s.word(4)?,
351 register_count: s.word(8)?,
352 attribute_input_mask: s.word(12)?,
353 attribute_output_mask: s.word(16)?,
354 user_clip_mask: s.word(20)?,
355 })
356 } else {
357 let stage_view = View::new(stage_bytes);
358 let half = |at| {
359 stage_view
360 .u16(at, Big)
361 .map_err(|e| error(stage_at.saturating_add(at), e.to_string()))
362 };
363 let flag = |at| -> Result<bool, Error> { r.boolean(plus(stage_at, at)?, true) };
364 let byte = |at| {
365 stage_view
366 .u8(at)
367 .map_err(|e| error(stage_at.saturating_add(at), e.to_string()))
368 };
369 Stage::Fragment(Fragment {
370 instruction_count,
371 attribute_input_mask: s.word(4)?,
372 partial_tex_type: s.word(8)?,
373 tex_coords_input_mask: half(12)?,
374 tex_coords_2d: half(14)?,
375 tex_coords_centroid: half(16)?,
376 register_count: byte(18)?,
377 output_from_h0: flag(19)?,
378 depth_replace: flag(20)?,
379 pixel_kill: flag(21)?,
380 padding: [byte(22)?, byte(23)?],
381 })
382 };
383 let mut parameters = Vec::new();
384 for at in params_at {
385 let field = |delta| plus(at, delta);
386 let ty = r.word(at)?;
387 parameters.push(Parameter {
389 ty,
390 resource: r.word(field(4)?)?,
391 variability: r.word(field(8)?)?,
392 resource_index: r.word(field(12)?)? as i32,
393 name: r.string(field(16)?)?,
394 default_value: r.floats(field(20)?, ty)?,
395 embedded_constants: r.embedded(field(24)?, ucode_size)?,
396 semantic: r.string(field(28)?)?,
397 direction: r.word(field(32)?)?,
398 parameter_number: r.word(field(36)?)? as i32,
399 referenced: r.boolean(field(40)?, false)?,
400 shared: r.boolean(field(44)?, false)?,
401 });
402 }
403 Ok(Program {
404 profile,
405 revision,
406 stage,
407 parameters,
408 microcode: r.bytes(ucode_at, ucode_size)?,
409 bytes: r.bytes,
410 })
411}