GPU/Shader Instruction Set

< GPU
Revision as of 23:01, 11 December 2014 by Smea (talk | contribs) (→‎Registers)


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 negation bit (NEGY)
0x19 0x1 X negation bit (NEGX)
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 0x2 Primitive ID (PRIMID)
0x18 0x2 Vertex ID (VTXID)
0x1A 0x6 Opcode

Format 5 : (used for MAD and LRP)

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

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).
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 1 END2 ?
0x22 1 END1 ?
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 FORLOOP Loops over the code between itself and DST. First sets aL to INT.y, then increments aL by INT.z after each loop. Loops until aL reaches INT.y+INT.x, inclusive (that is : for(aL=INT.y;aL<=INT.y+INT.x;aL+=INT.z)). (INT is i0-i3, an integer vector uniform)
0x2A 0 (no param) EMIT (geometry shader only) Emits a vertex (and primitive if PRIMID is non-zero). SETEMIT must be called before this.
0x2B 4 SETEMIT (geometry shader only) Sets VTXID and PRIMID for the next EMIT instruction. VTXID is the ID of the vertex about to be emitted within the primitive, while PRIMID is zero if we are just emitting a single vertex and non-zero if are emitting a vertex and primitive simultaneously. 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 5 LRP Does linear interpolation between two vectors, using a third as the interpolation factor, component by component; DST[i] = SRC1[i].(1.0 - SRC3[i]) + SRC2[i].(SRC3[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, NEGX and NEGY. NEGX and NEGY determine whether the conditional expression will use cmp.x or !cmp.x, and cmp.y or !cmp.y respectively (NEGX set means we use cmp.x, and NEGX not set means we use !cmp.x). CONDOP describes the actual expression. There are four conditional expression formats :

CONDOP raw value Expression Description
0x0 [!]cmp.x || [!]cmp.y OR
0x1 [!]cmp.x && [!]cmp.y AND
0x2 [!]cmp.X X
0x3 [!]cmp.y Y

For example, with CONDOP=1, NEGX=1 and NEGY=0, the resulting expression would be (cmp.x && !cmp.y).

Registers

Most registers (all the ones within the 0x00-0x7F range) are float[4] vectors. There are also boolean registers (b0-b7) and integer registers (i0-i7). How the latter ones are set is as of yet unknown.

Attribute (input, RO) registers are located within the 0x0-0xF range. What data they are fed is specified by the CPU.

Output (WO) registers are also located within the 0x0-0xF range. What type of data they are contain is specified by the CPU.

Temporary (RW) register are located within the 0x10-0x1F range. They can contain any type of data.

Uniform (RO) registers are located within the 0x20-0x7F range. Their content is set by the CPU.

SRC2 being only 5 bits long rather than 7 bits like its friend SRC1, it can only access v (input attribute) and r (temporary) registers.

Registers in the 0x88-0x97 range are uniform booleans.

It appears that writing twice to the same output register can cause problems, such as the GPU hanging.

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.

Note that 5bit SRC registers (SRC2 in format 1 for example) can't access c0-c95 because they don't have enough bits.