GPU/Shader Instruction Set
Overview
A compiled shader binary is comprised of two parts : the main instruction sequence and the operand descriptor table. These are both sent to the GPU around the same time but using separate GPU Commands. Instructions (such as format 1 instruction) may reference operand descriptors. When such is the case, the operand descriptor ID is the offset, in words, of the descriptor within the table. Both instructions and descriptors are coded in little endian. Basic implementations of the following specification can be found at [1] and [2]. The instruction set seems to have been heavily inspired by Microsoft's vs_3_0 [3]. Please note that this page is being written as the instruction set is reverse engineered; as such it may very well contain mistakes.
Nomenclature
- opcode names with I appended to them are the same as their non-I version, except they use the inverted instruction format, giving 7 bits to SRC2 (and access to uniforms) and 5 bits to SRC1
- opcode names with U appended to them are the same as their non-U version, except they are executed conditionally based on the value of a uniform boolean.
- opcode names with C appended to them are the same as their non-C version, except they are executed conditionally based on a logical expression specified in the instruction.
Instruction formats
Format 1 : (used for register operations)
Offset | Size (bits) | Description |
---|---|---|
0x0 | 0x7 | Operand descriptor ID (DESC) |
0x7 | 0x5 | Source 2 register (SRC2) |
0xC | 0x7 | Source 1 register (SRC1) |
0x13 | 0x2 | Address register index for SRC1 (IDX_1) |
0x15 | 0x5 | Destination register (DST) |
0x1A | 0x6 | Opcode |
Format 1i : (used for register operations)
Offset | Size (bits) | Description |
---|---|---|
0x0 | 0x7 | Operand descriptor ID (DESC) |
0x7 | 0x7 | Source 2 register (SRC2) |
0xE | 0x5 | Source 1 register (SRC1) |
0x13 | 0x2 | Address register index for SRC2 (IDX_2) |
0x15 | 0x5 | Destination register (DST) |
0x1A | 0x6 | Opcode |
Format 1u : (used for unary register operations)
Offset | Size (bits) | Description |
---|---|---|
0x0 | 0x7 | Operand descriptor ID (DESC) |
0xC | 0x7 | Source 1 register (SRC1) |
0x13 | 0x2 | Address register index for SRC1 (IDX_1) |
0x15 | 0x5 | Destination register (DST) |
0x1A | 0x6 | Opcode |
Format 1c : (used for comparison operations)
Offset | Size (bits) | Description |
---|---|---|
0x0 | 0x7 | Operand descriptor ID (DESC) |
0x7 | 0x7 | Source 1 register (SRC1) |
0xE | 0x5 | Source 2 register (SRC2) |
0x13 | 0x2 | Address register index for SRC1 (IDX_1) |
0x15 | 0x3 | Comparison operator for Y (CMPY) |
0x18 | 0x3 | Comparison operator for X (CMPX) |
0x1B | 0x5 | Opcode |
Format 2 : (used for flow control instructions)
Offset | Size (bits) | Description |
---|---|---|
0x0 | 0x8 | Number of instructions (NUM) |
0xA | 0xC | Destination offset (in words) (DST) |
0x16 | 0x2 | Condition boolean operator (CONDOP) |
0x18 | 0x1 | Y reference bit (REFY) |
0x19 | 0x1 | X reference bit (REFX) |
0x1A | 0x6 | Opcode |
Format 3 : (used for uniform-based conditional flow control instructions)
Offset | Size (bits) | Description |
---|---|---|
0x0 | 0x8 | Number of instructions ? (NUM) |
0xA | 0xC | Destination offset (in words) (DST) |
0x16 | 0x4 | Uniform ID (BOOL/INT) |
0x1A | 0x6 | Opcode |
Format 4 : (used for SETEMIT)
Offset | Size (bits) | Description |
---|---|---|
0x16 | 0x1 | Winding flag (FLAG_WINDING) |
0x17 | 0x1 | Primitive emit flag (FLAG_PRIMEMIT) |
0x18 | 0x2 | Vertex ID (VTXID) |
0x1A | 0x6 | Opcode |
Format 5 : (used for MAD)
Offset | Size (bits) | Description |
---|---|---|
0x0 | 0x5 | Operand descriptor ID (DESC) |
0x5 | 0x5 | Source 3 register (SRC3) |
0xA | 0x7 | Source 2 register (SRC2) |
0x11 | 0x7 | Source 1 register (SRC1) |
0x18 | 0x5 | Destination register (DST) |
0x1D | 0x3 | Opcode |
Format 5i : (used for MADI)
Offset | Size (bits) | Description |
---|---|---|
0x0 | 0x5 | Operand descriptor ID (DESC) |
0x5 | 0x7 | Source 3 register (SRC3) |
0xC | 0x5 | Source 2 register (SRC2) |
0x11 | 0x7 | Source 1 register (SRC1) |
0x18 | 0x5 | Destination register (DST) |
0x1D | 0x3 | Opcode |
Instructions
Opcode | Format | Name | Description |
---|---|---|---|
0x00 | 1 | ADD | Adds two vectors component by component; DST[i] = SRC1[i]+SRC2[i] for all i (modulo destination component masking) |
0x01 | 1 | DP3 | Computes dot product on 3-component vectors; DST = SRC1.SRC2 |
0x02 | 1 | DP4 | Computes dot product on 4-component vectors; DST = SRC1.SRC2 |
0x03 | 1 | DPH | Computes dot product on a 4-component vector and a 3-component one with 1.0 appended to it; DST = SRC1.SRC2 (with SRC2 homogenous) |
0x04 | 1 | ??? | ? |
0x05 | 1u | EX2 | Computes SRC1's exp component by component; DST[i] = EXP(SRC1[i]) for all i (modulo destination component masking) (base 2) |
0x06 | 1u | LG2 | Computes SRC1's log2 component by component; DST[i] = LOG2(SRC1[i]) for all i (modulo destination component masking) (base 2) |
0x07 | 1u | ??? | ? |
0x08 | 1 | MUL | Multiplies two vectors component by component; DST[i] = SRC1[i].SRC2[i] for all i (modulo destination component masking) |
0x09 | 1 | SGE | Sets output if SRC1 is greater than or equal to SRC2; DST[i] = (SRC1[i] >= SRC2[i]) ? 1.0 : 0.0 for all i (modulo destination component masking) |
0x0A | 1 | SLT | Sets output if SRC1 is strictly less than SRC2; DST[i] = (SRC1[i] < SRC2[i]) ? 1.0 : 0.0 for all i (modulo destination component masking) |
0x0B | 1u | FLR | Computes SRC1's floor component by component; DST[i] = FLOOR(SRC1[i]) for all i (modulo destination component masking) |
0x0C | 1 | MAX | Takes the max of two vectors, component by component; DST[i] = MAX(SRC1[i], SRC2[i]) for all i (modulo destination component masking) |
0x0D | 1 | MIN | Takes the min of two vectors, component by component; DST[i] = MIN(SRC1[i], SRC2[i]) for all i (modulo destination component masking) |
0x0E | 1u | RCP | Computes the reciprocal of the vector, component by component; DST[i] = 1/SRC1[i] for all i (modulo destination component masking) |
0x0F | 1u | RSQ | Computes the reciprocal of the square root of the vector, component by component; DST[i] = 1/sqrt(SRC1[i]) for all i (modulo destination component masking) |
0x10 | ? | ??? | ? |
0x11 | ? | ??? | ? |
0x12 | 1u | MOVA | Address Register Load; sets (a0.x, a0.y, _, _) to SRC1 (cast to integer by truncating the fractional part). |
0x13 | 1u | MOV | Moves value from one register to another; DST = SRC1. |
0x14 | ? | ??? | ? |
0x15 | ? | ??? | ? |
0x16 | ? | ??? | ? |
0x17 | ? | ??? | ? |
0x18 | 1i | DPHI | Computes dot product on a 4-component vector and a 3-component one with 1.0 appended to it; DST = SRC1.SRC2 (with SRC2 homogenous) |
0x19 | 1i | ??? | ? |
0x1A | 1i | SGEI | Sets output if SRC1 is greater than or equal to SRC2; DST[i] = (SRC1[i] >= SRC2[i]) ? 1.0 : 0.0 for all i (modulo destination component masking) |
0x1B | 1i | SLTI | Sets output if SRC1 is strictly less than SRC2; DST[i] = (SRC1[i] < SRC2[i]) ? 1.0 : 0.0 for all i (modulo destination component masking) |
0x1C | ? | ??? | ? |
0x1D | ? | ??? | ? |
0x1E | ? | ??? | ? |
0x1F | ? | ??? | ? |
0x20 | ? | ??? | ? |
0x21 | 0 | NOP | Does literally nothing. |
0x22 | 0 | END | Signals the shader unit that processing for this vertex/primitive is done. |
0x23 | 2 | BREAKC | If condition (see below for details) is true, then breaks out of LOOP block. |
0x24 | 2 | CALL | Jumps to DST and executes instructions until it reaches DST+NUM instructions |
0x25 | 2 | CALLC | If condition (see below for details) is true, then jumps to DST and executes instructions until it reaches DST+NUM instructions, else does nothing. |
0x26 | 3 | CALLU | Jumps to DST and executes instructions until it reaches DST+NUM instructions if BOOL is true |
0x27 | 3 | IFU | If condition BOOL is true, then executes instructions until DST, then jumps to DST+NUM; else, jumps to DST. |
0x28 | 2 | IFC | If condition (see below for details) is true, then executes instructions until DST, then jumps to DST+NUM; else, jumps to DST |
0x29 | 3 | LOOP | Loops over the code between itself and DST (inclusive), performing INT.x+1 iterations in total. First, aL is initialized to INT.y. After each iteration, aL is incremented by INT.z. |
0x2A | 0 (no param) | EMIT | (geometry shader only) Emits a vertex (and primitive if FLAG_PRIMEMIT was set in the corresponding SETEMIT). SETEMIT must be called before this. |
0x2B | 4 | SETEMIT | (geometry shader only) Sets VTXID, FLAG_WINDING and FLAG_PRIMEMIT for the next EMIT instruction. VTXID is the ID of the vertex about to be emitted within the primitive, while FLAG_PRIMEMIT is zero if we are just emitting a single vertex and non-zero if are emitting a vertex and primitive simultaneously. FLAG_WINDING controls the output primitive's winding. Note that the output vertex buffer (which holds 4 vertices) is not cleared when the primitive is emitted, meaning that vertices from the previous primitive can be reused for the current one. (this is still a working hypothesis and unconfirmed) |
0x2C | 2 | JMPC | If condition (see below for details) is true, then jumps to DST, else does nothing. |
0x2D | 3 | JMPU | If condition BOOL is true, then jumps to DST, else does nothing. It seems possible that having NUM = 1 will jump if BOOL is false instead, though this is unconfirmed. |
0x2E-0x2F | 1c | CMP | Sets booleans cmp.x and cmp.y based on the operand's x and y components and the CMPX and CMPY comparison operators respectively. See below for details about operators. |
0x30-0x37 | 5i | MADI | Multiplies two vectors and adds a third one component by component; DST[i] = SRC3[i] + SRC2[i].SRC1[i] for all i (modulo destination component masking) |
0x38-0x3F | 5 | MAD | Multiplies two vectors and adds a third one component by component; DST[i] = SRC3[i] + SRC2[i].SRC1[i] for all i (modulo destination component masking) |
Operand descriptors
Sizes below are in bits, not bytes.
Offset | Size | Description |
---|---|---|
0x0 | 0x4 | Destination component mask. Bit 3 = x, 2 = y, 1 = z, 0 = w. |
0x4 | 0x1 | Source 1 negation bit |
0x5 | 0x8 | Source 1 component selector |
0xD | 0x1 | Source 2 negation bit |
0xE | 0x8 | Source 2 component selector |
0x16 | 0x1 | Source 3 negation bit |
0x17 | 0x8 | Source 3 component selector |
Component selector :
Offset | Size | Description |
---|---|---|
0x0 | 0x2 | Component 3 value |
0x2 | 0x2 | Component 2 value |
0x4 | 0x2 | Component 1 value |
0x6 | 0x2 | Component 0 value |
Value | Component |
---|---|
0x0 | x |
0x1 | y |
0x2 | z |
0x3 | w |
The component selector enables swizzling. For example, component selector 0x1B is equivalent to .xyzw, while 0x55 is equivalent to .yyyy.
Relative addressing
There are 3 global address registers : a0.x, a0.y and aL (loop counter). For format 1 instructions, when IDX != 0, the value of the corresponding address register is added to SRC1's value.
For example, if IDX = 2, a0.y = 3 and SRC1 = c8, then instead SRC1+a0.y = c11 will be used for the instruction.
a0.x and a0.y can be set manually through the MOVA instruction. aL is set automatically by the LOOP instruction. Note that aL is still accessible and valid after exiting a LOOP block.
Comparison operator
CMPX/CMPY raw value | Operator name | Expression |
---|---|---|
0x0 | EQ | src1 == src2 |
0x1 | NE | src1 != src2 |
0x2 | LT | src1 < src2 |
0x3 | LE | src1 <= src2 |
0x4 | GT | src1 > src2 |
0x5 | GE | src1 >= src2 |
0x6 | ?? | true ? |
0x7 | ?? | true ? |
6 and 7 seem to always return true.
Conditions
A number of format 2 instructions are executed conditionally. These conditions are based on two boolean registers which can be set with CMP : cmp.x and cmp.y.
Conditional instructions include 3 parameters : CONDOP, REFX and REFY. REFX and REFY are reference values which are tested for equality against cmp.x and cmp.y, respectively. CONDOP describes how the final truth value is constructed from the results of the two tests. There are four conditional expression formats :
CONDOP raw value | Expression | Description |
---|---|---|
0x0 | cmp.x == REFX || cmp.y == REFY | OR |
0x1 | cmp.x == REFX && cmp.y == REFY | AND |
0x2 | cmp.x == REFX | X |
0x3 | cmp.y == REFY | Y |
Registers
Input attribute registers (v0-v7?) store the per-vertex data given by the CPU and hence are read-only.
Output attribute registers (o0-o6) hold the data to be passed to the later GPU stages and are write-only. Each of the output attribute register components is assigned a semantic by setting the corresponding GPU_Internal_Registers.
Uniform registers hold user-specified data which is constant throughout all processed vertices. There are 96 float[4] uniform registers (c0-c95), eight boolean registers (b0-b7), and four int[4] registers (i0-i3).
Temporary registers (r0-r15) can be used for intermediate calculations and can both be read and written.
Many shader instructions which take float arguments have only 5 bits available for the second argument. They may hence only refer to input attributes or temporary registers. In particular, it's not possible to pass two float[4] uniforms to these instructions.
It appears that writing twice to the same output register can cause problems (e.g. GPU hangs).
DST mapping :
DST raw value | Register name | Description |
---|---|---|
0x0-0x6 | o0-o6 | Output registers. |
0x10-0x1F | r0-r15 | Temporary registers. |
SRC mapping :
SRC1 raw value | Register name | Description |
---|---|---|
0x0-0x7 | v0-v7 | Input attribute registers. |
0x10-0x1F | r0-r15 | Temporary registers. |
0x20-0x7F | c0-c95 | Vector uniform registers. |