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
23pub fn components(ty: u32) -> Option<usize> {
28 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), 1029..=1044 => matrix(1029), 1045..=1048 => vector(1045, 1), 1049..=1064 => matrix(1049), 1070..=1073 => vector(1070, 1), 1074..=1089 => matrix(1074), 1090..=1094 => 1, 1095..=1097 => vector(1095, 2), 1098..=1113 => matrix(1098), 1114 | 1115 => 1, 1116..=1118 => vector(1116, 2), 1119..=1134 => matrix(1119), _ => return None, };
52 usize::try_from(n).ok()
53}
54
55fn 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#[derive(Clone, Debug, PartialEq, Eq)]
63pub struct Vertex {
64 pub instruction_count: u32,
66 pub instruction_slot: u32,
68 pub register_count: u32,
70 pub attribute_input_mask: u32,
72 pub attribute_output_mask: u32,
74 pub user_clip_mask: u32,
76}
77
78#[derive(Clone, Debug, PartialEq, Eq)]
80pub struct Fragment {
81 pub instruction_count: u32,
83 pub attribute_input_mask: u32,
85 pub partial_tex_type: u32,
87 pub tex_coords_input_mask: u16,
89 pub tex_coords_2d: u16,
91 pub tex_coords_centroid: u16,
93 pub register_count: u8,
95 pub output_from_h0: bool,
97 pub depth_replace: bool,
99 pub pixel_kill: bool,
101 pub padding: [u8; 2],
103}
104
105#[derive(Clone, Debug, PartialEq, Eq)]
107pub enum Stage {
108 Vertex(Vertex),
110 Fragment(Fragment),
112}
113
114#[derive(Clone, Debug, PartialEq, Eq)]
116pub struct Parameter<'a> {
117 pub ty: u32,
119 pub resource: u32,
121 pub variability: u32,
123 pub resource_index: i32,
125 pub name: Option<&'a [u8]>,
127 pub semantic: Option<&'a [u8]>,
129 pub direction: u32,
131 pub parameter_number: i32,
133 pub referenced: bool,
135 pub shared: bool,
137 pub default_value: Option<Vec<u32>>,
143 pub embedded_constants: Vec<u32>,
145}
146
147impl Parameter<'_> {
148 pub fn texture_unit(&self) -> Option<u32> {
150 self.resource.checked_sub(2048).filter(|&n| n < 16)
151 }
152}
153
154#[derive(Clone, Debug, PartialEq, Eq)]
156pub struct Program<'a> {
157 pub profile: u32,
159 pub revision: u32,
161 pub stage: Stage,
163 pub parameters: Vec<Parameter<'a>>,
165 pub microcode: &'a [u8],
167 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
304pub 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
314pub 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 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}