Skip to main content

rsx_reader/
vertex.rs

1//! RSX vertex instructions: 128-bit slots of four big-endian 32-bit words, each holding up to two operations (a vector
2//! operation and a scalar one) with three shared source operands, an output or temporary destination for each, a
3//! condition code test and an address register. This reads instructions; it neither executes nor lowers them.
4//!
5//! The layout is the NV40 vertex program format the RSX shares. Opcodes this reader has no entry for are refused with
6//! their number.
7use std::fmt::Write;
8
9/// What is wrong with a vertex program, and where.
10#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
11#[error("vertex program at byte {offset}: {detail}")]
12pub struct Error {
13    /// The byte offset in the microcode where the problem is.
14    pub offset: usize,
15    /// What was expected there, or what was found instead.
16    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
26/// The `width` bits of `n` from bit `shift` up.
27fn 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}
31/// [`bits`] of at most 8 bits.
32fn bits8(n: u32, shift: u32, width: u32) -> u8 {
33    let [byte, ..] = bits(n, shift, width.min(8)).to_le_bytes();
34    byte
35}
36/// [`bits`] of at most 16 bits.
37fn 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            /// The opcode's name in upper case (`MOV`, `MAD`, ...).
59            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/// What a source operand reads.
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum SourceKind {
79    /// No register: the operand is unused (register type 0).
80    None,
81    /// A temporary register `R<n>`, 0 to 31.
82    Temporary(u8),
83    /// A vertex attribute: the instruction's one input index (`v[n]`), or that index plus the address register where
84    /// [`Instruction::input_relative`] is set.
85    Input,
86    /// A float constant: the instruction's one constant index (`c[n]`), or that index plus the address register where
87    /// [`Instruction::constant_relative`] is set.
88    Constant,
89}
90
91/// One of an instruction's three source operands.
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
93pub struct Source {
94    /// What it reads.
95    pub kind: SourceKind,
96    /// The source lane (0 x to 3 w) each result lane takes.
97    pub swizzle: [u8; 4],
98    /// Whether the value is negated (after the absolute value, if both).
99    pub negate: bool,
100    /// Whether the absolute value is taken.
101    pub absolute: bool,
102}
103
104/// An output the program writes.
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub enum Output {
107    /// The clip-space position (destination 0).
108    Position,
109    /// The first colour (1).
110    Color0,
111    /// The second colour (2).
112    Color1,
113    /// The first back-face colour (3).
114    BackColor0,
115    /// The second back-face colour (4).
116    BackColor1,
117    /// The fog coordinate (5).
118    FogCoord,
119    /// Destination 6, the point size in the nouveau driver's naming. On the programs of Tony Hawk's SHRED it is written
120    /// with all four lanes by 48 programs (their output mask has bit 13 set) and with the x lane alone by one (bit 5).
121    PointSize,
122    /// Texture coordinate set `n` (destination `7 + n`, 0 to 9).
123    TexCoord(u8),
124    /// A destination field value nothing here names (17 to 30).
125    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    /// The destination field value.
143    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/// Where an operation's result goes.
159#[derive(Clone, Copy, Debug, PartialEq, Eq)]
160pub enum Destination {
161    /// Nowhere: the operation is absent or its result is discarded (the destination field is all ones).
162    None,
163    /// A temporary register `R<n>` (a 6-bit field for the vector operation, 5 for the scalar one).
164    Temporary(u8),
165    /// An output register.
166    Output(Output),
167}
168
169/// The condition code test and register of an instruction.
170#[derive(Clone, Copy, Debug, PartialEq, Eq)]
171pub struct Condition {
172    /// Whether the test is enabled (the operation runs per lane where it holds).
173    pub test: bool,
174    /// The test, 3 bits as stored (NV_vertex_program2: 0 FL, 1 LT, 2 EQ, 3 LE, 4 GT, 5 NE, 6 GE, 7 TR).
175    pub code: u8,
176    /// The condition register lane each test lane reads.
177    pub swizzle: [u8; 4],
178    /// Which of the two condition registers is tested.
179    pub register: u8,
180    /// Whether the operation updates the condition register.
181    pub update: bool,
182}
183
184/// One 128-bit slot.
185#[derive(Clone, Debug, PartialEq, Eq)]
186pub struct Instruction {
187    /// The slot's byte offset in the microcode.
188    pub offset: usize,
189    /// The four words as stored (big-endian read), most significant first.
190    pub words: [u32; 4],
191    /// The vector operation.
192    pub vector: VectorOpcode,
193    /// The scalar operation.
194    pub scalar: ScalarOpcode,
195    /// The three source operands, shared by both operations.
196    pub sources: [Source; 3],
197    /// The attribute index of every source of kind [`SourceKind::Input`].
198    pub input: u8,
199    /// The constant index (ten bits) of every source of kind [`SourceKind::Constant`].
200    pub constant: u16,
201    /// Whether attribute sources add the address register to [`Self::input`].
202    pub input_relative: bool,
203    /// Whether constant sources add the address register to [`Self::constant`].
204    pub constant_relative: bool,
205    /// Which address register and which of its lanes a relative operand adds.
206    pub address: (u8, u8),
207    /// Where the vector operation's result goes.
208    pub vector_destination: Destination,
209    /// Where the scalar operation's result goes.
210    pub scalar_destination: Destination,
211    /// The vector operation's write mask, bit 0 x to bit 3 w.
212    pub vector_mask: u8,
213    /// The scalar operation's write mask, bit 0 x to bit 3 w.
214    pub scalar_mask: u8,
215    /// Whether results are clamped to 0..1.
216    pub saturate: bool,
217    /// The condition test.
218    pub condition: Condition,
219    /// Whether this is the program's last instruction.
220    pub last: bool,
221    /// The bits of the four words that no field here covers.
222    pub unassigned: [u32; 4],
223}
224
225impl Instruction {
226    /// The outputs this instruction writes: the destination of each operation that has an output destination and a
227    /// non-empty write mask.
228    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/// A decoded vertex program.
255#[derive(Clone, Debug, PartialEq, Eq)]
256pub struct Program {
257    /// The instructions in order.
258    pub instructions: Vec<Instruction>,
259}
260
261impl Program {
262    /// The vertex attributes the program reads, bit `n` for attribute `n`, as a program header's `attribute_input_mask`
263    /// states them. An attribute read relative to the address register can be any, so `None` where one is.
264    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    /// The outputs the program writes (the position excepted, which every program writes), as a program header's
278    /// `attribute_output_mask` states them: destinations 1 to 5 are bits 0 to 4; destination 6 is bit 13, or bit 5 where
279    /// only its x lane is written; destination `n` from 7 is bit `n + 7`. Measured on the 170 distinct vertex programs
280    /// of Tony Hawk's SHRED, where destinations 1 and 6 to 14 occur; the others follow the nouveau driver's output
281    /// numbering and no program of that game writes them.
282    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    /// The number of temporary registers the program uses: one more than the highest it reads or writes, and at least one.
311    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
332/// The bits no field covers: bits 31 and 28 of word 0. (Word 1's bits 12 to 21 are the constant index, ten bits wide; the
333/// values seen reach 467, the last of the RSX's 468 vertex constants.)
334const 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        // the swizzle fields run x at bit 14 down to w at bit 8
346        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
362/// Decode a whole vertex program: complete 16-byte slots, the last instruction flagged as last and no other.
363pub 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            // bit `top` is x, the three below are y, z and w
392            (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    /// Stable inspection text: one line per slot, its operations, destinations and operands.
462    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}