1use std::fmt::Write;
8
9#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
11#[error("vertex program at byte {offset}: {detail}")]
12pub struct Error {
13 pub offset: usize,
15 pub detail: String,
17}
18
19fn error(offset: usize, detail: impl Into<String>) -> Error {
20 Error {
21 offset,
22 detail: detail.into(),
23 }
24}
25
26fn bits(n: u32, shift: u32, width: u32) -> u32 {
28 let mask = 1u32.checked_shl(width).map_or(u32::MAX, |m| m.wrapping_sub(1));
29 n.checked_shr(shift).unwrap_or(0) & mask
30}
31fn bits8(n: u32, shift: u32, width: u32) -> u8 {
33 let [byte, ..] = bits(n, shift, width.min(8)).to_le_bytes();
34 byte
35}
36fn bits16(n: u32, shift: u32, width: u32) -> u16 {
38 let [a, b, ..] = bits(n, shift, width.min(16)).to_le_bytes();
39 u16::from_le_bytes([a, b])
40}
41fn flag(n: u32, bit: u32) -> bool {
42 bits(n, bit, 1) != 0
43}
44
45macro_rules! opcodes {
46 ($kind:ident, $doc:literal; $($name:ident = $code:literal;)+) => {
47 #[doc = $doc]
48 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
49 #[repr(u8)]
50 pub enum $kind {
51 $(
52 #[doc = concat!("`", stringify!($name), "`, opcode ", stringify!($code), ".")]
53 $name = $code,
54 )+
55 }
56 impl $kind {
57 fn read(code: u8) -> Option<Self> { match code { $($code => Some(Self::$name),)+ _ => None } }
58 pub fn mnemonic(self) -> &'static str { match self { $(Self::$name => stringify!($name),)+ } }
60 }
61 }
62}
63
64opcodes! { VectorOpcode, "A vector operation: four lanes at once, up to three sources.";
65 NOP=0x00; MOV=0x01; MUL=0x02; ADD=0x03; MAD=0x04; DP3=0x05; DPH=0x06; DP4=0x07;
66 DST=0x08; MIN=0x09; MAX=0x0a; SLT=0x0b; SGE=0x0c; ARL=0x0d; FRC=0x0e; FLR=0x0f;
67 SEQ=0x10; SFL=0x11; SGT=0x12; SLE=0x13; SNE=0x14; STR=0x15; SSG=0x16; ARR=0x17;
68 ARA=0x18; TXL=0x19;
69}
70
71opcodes! { ScalarOpcode, "A scalar operation: one source, its result replicated to the lanes the write mask names.";
72 NOP=0x00; MOV=0x01; RCP=0x02; RCC=0x03; RSQ=0x04; EXP=0x05; LOG=0x06; LIT=0x07;
73 LG2=0x0d; EX2=0x0e; SIN=0x0f; COS=0x10; PUSHA=0x13; POPA=0x14;
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum SourceKind {
79 None,
81 Temporary(u8),
83 Input,
86 Constant,
89}
90
91#[derive(Clone, Copy, Debug, PartialEq, Eq)]
93pub struct Source {
94 pub kind: SourceKind,
96 pub swizzle: [u8; 4],
98 pub negate: bool,
100 pub absolute: bool,
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub enum Output {
107 Position,
109 Color0,
111 Color1,
113 BackColor0,
115 BackColor1,
117 FogCoord,
119 PointSize,
122 TexCoord(u8),
124 Other(u8),
126}
127
128impl Output {
129 fn from_field(n: u8) -> Self {
130 match n {
131 0 => Self::Position,
132 1 => Self::Color0,
133 2 => Self::Color1,
134 3 => Self::BackColor0,
135 4 => Self::BackColor1,
136 5 => Self::FogCoord,
137 6 => Self::PointSize,
138 7..=16 => Self::TexCoord(n.saturating_sub(7)),
139 n => Self::Other(n),
140 }
141 }
142 pub fn field(self) -> u8 {
144 match self {
145 Self::Position => 0,
146 Self::Color0 => 1,
147 Self::Color1 => 2,
148 Self::BackColor0 => 3,
149 Self::BackColor1 => 4,
150 Self::FogCoord => 5,
151 Self::PointSize => 6,
152 Self::TexCoord(n) => n.saturating_add(7),
153 Self::Other(n) => n,
154 }
155 }
156}
157
158#[derive(Clone, Copy, Debug, PartialEq, Eq)]
160pub enum Destination {
161 None,
163 Temporary(u8),
165 Output(Output),
167}
168
169#[derive(Clone, Copy, Debug, PartialEq, Eq)]
171pub struct Condition {
172 pub test: bool,
174 pub code: u8,
176 pub swizzle: [u8; 4],
178 pub register: u8,
180 pub update: bool,
182}
183
184#[derive(Clone, Debug, PartialEq, Eq)]
186pub struct Instruction {
187 pub offset: usize,
189 pub words: [u32; 4],
191 pub vector: VectorOpcode,
193 pub scalar: ScalarOpcode,
195 pub sources: [Source; 3],
197 pub input: u8,
199 pub constant: u16,
201 pub input_relative: bool,
203 pub constant_relative: bool,
205 pub address: (u8, u8),
207 pub vector_destination: Destination,
209 pub scalar_destination: Destination,
211 pub vector_mask: u8,
213 pub scalar_mask: u8,
215 pub saturate: bool,
217 pub condition: Condition,
219 pub last: bool,
221 pub unassigned: [u32; 4],
223}
224
225impl Instruction {
226 pub fn outputs(&self) -> Vec<Output> {
229 let mut out = Vec::new();
230 for (destination, mask, present) in [
231 (
232 self.vector_destination,
233 self.vector_mask,
234 self.vector != VectorOpcode::NOP,
235 ),
236 (
237 self.scalar_destination,
238 self.scalar_mask,
239 self.scalar != ScalarOpcode::NOP,
240 ),
241 ] {
242 if let Destination::Output(o) = destination
243 && mask != 0
244 && present
245 && !out.contains(&o)
246 {
247 out.push(o);
248 }
249 }
250 out
251 }
252}
253
254#[derive(Clone, Debug, PartialEq, Eq)]
256pub struct Program {
257 pub instructions: Vec<Instruction>,
259}
260
261impl Program {
262 pub fn input_mask(&self) -> Option<u32> {
265 let mut mask = 0u32;
266 for i in &self.instructions {
267 if i.sources.iter().any(|s| s.kind == SourceKind::Input) {
268 if i.input_relative {
269 return None;
270 }
271 mask |= 1u32.checked_shl(u32::from(i.input)).unwrap_or(0);
272 }
273 }
274 Some(mask)
275 }
276
277 pub fn output_mask(&self) -> u32 {
283 let mut mask = 0u32;
284 let mut slot6 = 0u8;
285 for i in &self.instructions {
286 for (destination, lanes, present) in [
287 (i.vector_destination, i.vector_mask, i.vector != VectorOpcode::NOP),
288 (i.scalar_destination, i.scalar_mask, i.scalar != ScalarOpcode::NOP),
289 ] {
290 let Destination::Output(output) = destination else {
291 continue;
292 };
293 if lanes == 0 || !present {
294 continue;
295 }
296 match output.field() {
297 0 => {}
298 6 => slot6 |= lanes,
299 n @ 1..=5 => mask |= 1u32.checked_shl(u32::from(n).saturating_sub(1)).unwrap_or(0),
300 n => mask |= 1u32.checked_shl(u32::from(n).saturating_add(7)).unwrap_or(0),
301 }
302 }
303 }
304 if slot6 != 0 {
305 mask |= if slot6 == 1 { 1 << 5 } else { 1 << 13 };
306 }
307 mask
308 }
309
310 pub fn register_count(&self) -> u32 {
312 let mut highest = 0u32;
313 for i in &self.instructions {
314 let written = [i.vector_destination, i.scalar_destination]
315 .into_iter()
316 .filter_map(|d| match d {
317 Destination::Temporary(n) => Some(u32::from(n)),
318 _ => None,
319 });
320 let read = i.sources.iter().filter_map(|s| match s.kind {
321 SourceKind::Temporary(n) => Some(u32::from(n)),
322 _ => None,
323 });
324 for n in written.chain(read) {
325 highest = highest.max(n.saturating_add(1));
326 }
327 }
328 highest.max(1)
329 }
330}
331
332const UNASSIGNED: [u32; 4] = [(1 << 31) | (1 << 28), 0, 0, 0];
335
336fn source(raw: u32) -> Source {
337 let kind = match bits(raw, 0, 2) {
338 0 => SourceKind::None,
339 1 => SourceKind::Temporary(bits8(raw, 2, 5)),
340 2 => SourceKind::Input,
341 _ => SourceKind::Constant,
342 };
343 Source {
344 kind,
345 swizzle: [14, 12, 10, 8].map(|shift| bits8(raw, shift, 2)),
347 negate: flag(raw, 16),
348 absolute: false,
349 }
350}
351
352fn destination(result: bool, temporary: u8, none: u8, field: u8) -> Destination {
353 if result {
354 Destination::Output(Output::from_field(field))
355 } else if temporary == none {
356 Destination::None
357 } else {
358 Destination::Temporary(temporary)
359 }
360}
361
362pub fn decode(bytes: &[u8]) -> Result<Program, Error> {
364 if bytes.is_empty() || !bytes.len().is_multiple_of(16) {
365 return Err(error(0, "vertex microcode must contain complete 16-byte slots"));
366 }
367 let (slots, _) = bytes.as_chunks::<16>();
368 let mut instructions = Vec::with_capacity(slots.len());
369 for (n, slot) in slots.iter().enumerate() {
370 let offset = n.saturating_mul(16);
371 let (words, _) = slot.as_chunks::<4>();
372 let [w0, w1, w2, w3] = words else {
373 return Err(error(offset, "a slot is four words"));
374 };
375 let [w0, w1, w2, w3] = [w0, w1, w2, w3].map(|w| u32::from_be_bytes(*w));
376 let vec_code = bits8(w1, 22, 5);
377 let sca_code = bits8(w1, 27, 5);
378 let vector = VectorOpcode::read(vec_code)
379 .ok_or_else(|| error(offset, format!("vector opcode {vec_code:#04x} has no entry")))?;
380 let scalar = ScalarOpcode::read(sca_code)
381 .ok_or_else(|| error(offset, format!("scalar opcode {sca_code:#04x} has no entry")))?;
382 let raw0 = bits(w1, 0, 8).wrapping_shl(9) | bits(w2, 23, 9);
383 let raw1 = bits(w2, 6, 17);
384 let raw2 = bits(w2, 0, 6).wrapping_shl(11) | bits(w3, 21, 11);
385 let mut sources = [raw0, raw1, raw2].map(source);
386 for (s, bit) in sources.iter_mut().zip([21, 22, 23]) {
387 s.absolute = flag(w0, bit);
388 }
389 let dest_field = bits8(w3, 2, 5);
390 let mask = |w: u32, top: u32| -> u8 {
391 (0..4).fold(0u8, |m, lane| {
393 let set = flag(w, top.saturating_sub(lane));
394 m | u8::from(set).wrapping_shl(lane)
395 })
396 };
397 instructions.push(Instruction {
398 offset,
399 words: [w0, w1, w2, w3],
400 vector,
401 scalar,
402 sources,
403 input: bits8(w1, 8, 4),
404 constant: bits16(w1, 12, 10),
405 input_relative: flag(w0, 27),
406 constant_relative: flag(w3, 1),
407 address: (bits8(w0, 24, 1), bits8(w0, 0, 2)),
408 vector_destination: destination(flag(w0, 30), bits8(w0, 15, 6), 0x3f, dest_field),
409 scalar_destination: destination(flag(w3, 12), bits8(w3, 7, 5), 0x1f, dest_field),
410 vector_mask: mask(w3, 16),
411 scalar_mask: mask(w3, 20),
412 saturate: flag(w0, 26),
413 condition: Condition {
414 test: flag(w0, 13),
415 code: bits8(w0, 10, 3),
416 swizzle: [8, 6, 4, 2].map(|shift| bits8(w0, shift, 2)),
417 register: bits8(w0, 25, 1),
418 update: flag(w0, 29),
419 },
420 last: flag(w3, 0),
421 unassigned: [
422 w0 & UNASSIGNED[0],
423 w1 & UNASSIGNED[1],
424 w2 & UNASSIGNED[2],
425 w3 & UNASSIGNED[3],
426 ],
427 });
428 }
429 for (n, i) in instructions.iter().enumerate() {
430 let is_final = n.saturating_add(1) == instructions.len();
431 if i.last != is_final {
432 return Err(error(
433 i.offset,
434 if is_final {
435 "the last slot is not flagged as the program's last"
436 } else {
437 "a slot before the end is flagged as the program's last"
438 },
439 ));
440 }
441 }
442 Ok(Program { instructions })
443}
444
445fn channels(mask: u8) -> String {
446 "xyzw"
447 .chars()
448 .enumerate()
449 .filter(|&(n, _)| bits(u32::from(mask), u32::try_from(n).unwrap_or(0), 1) != 0)
450 .map(|(_, c)| c)
451 .collect()
452}
453
454fn lanes(s: [u8; 4]) -> String {
455 s.into_iter()
456 .map(|n| "xyzw".chars().nth(usize::from(n)).unwrap_or('?'))
457 .collect()
458}
459
460impl Program {
461 pub fn disassemble(&self) -> String {
463 let mut out = String::new();
464 for i in &self.instructions {
465 let _ = write!(out, "{:04x}:", i.offset);
466 let render = |d: Destination| match d {
467 Destination::None => "-".to_owned(),
468 Destination::Temporary(n) => format!("R{n}"),
469 Destination::Output(o) => format!("o[{o:?}]"),
470 };
471 if i.vector != VectorOpcode::NOP {
472 let _ = write!(
473 out,
474 " {} {}.{}",
475 i.vector.mnemonic(),
476 render(i.vector_destination),
477 channels(i.vector_mask)
478 );
479 }
480 if i.scalar != ScalarOpcode::NOP {
481 let _ = write!(
482 out,
483 " {} {}.{}",
484 i.scalar.mnemonic(),
485 render(i.scalar_destination),
486 channels(i.scalar_mask)
487 );
488 }
489 for s in &i.sources {
490 let text = match s.kind {
491 SourceKind::None => continue,
492 SourceKind::Temporary(n) => format!("R{n}"),
493 SourceKind::Input if i.input_relative => {
494 format!("v[A{}.{}+{}]", i.address.0, i.address.1, i.input)
495 }
496 SourceKind::Input => format!("v[{}]", i.input),
497 SourceKind::Constant if i.constant_relative => {
498 format!("c[A{}.{}+{}]", i.address.0, i.address.1, i.constant)
499 }
500 SourceKind::Constant => format!("c[{}]", i.constant),
501 };
502 let _ = write!(
503 out,
504 ", {}{}{}.{}",
505 if s.negate { "-" } else { "" },
506 if s.absolute { "|" } else { "" },
507 text,
508 lanes(s.swizzle)
509 );
510 }
511 let _ = writeln!(
512 out,
513 " ; sat={} cond={}{} last={}",
514 i.saturate,
515 if i.condition.test { "on " } else { "off " },
516 i.condition.code,
517 i.last
518 );
519 }
520 out
521 }
522}