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] Please note that this page is being written as the instruction set is reverse engineered; as such it may very well contain mistakes.
Instruction formats
Format 1 : (used for register instructions)
Offset | Size (bits) | Description |
---|---|---|
0x0 | 0x7 | Operand descriptor ID (DESC) |
0x7 | 0x5 | Source 2 register (SRC2) |
0xC | 0x7 | Source 1 register (SRC1) |
0x13 | 0x2 | Flags (FLAG) |
0x15 | 0x5 | Destination register (DST) |
0x1A | 0x6 | 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) |
0x1A | 0x6 | Opcode |
Format 3 : (used for conditional flow control instructions)
Offset | Size (bits) | Description |
---|---|---|
0x0 | 0x8 | Number of instructions ? (NUM) |
0xA | 0xC | Destination offset (in words) (DST) |
0x16 | 0x4 | Uniform boolean ID (BOOL) |
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 |
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 |
0x08 | 1 | MUL | Multiplies two vectors component by component; DST[i] = SRC1[i].SRC2[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 max of two vectors, component by component; DST[i] = MIN(SRC1[i], SRC2[i]) for all i (modulo destination component masking) |
0x0E | 1 | RCP | Computes the reciprocal of the vector, component by component; DST[i] = 1/SRC1[i] for all i (modulo destination component masking) |
0x0F | 1 | 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) |
0x13 | 1 | MOV | Moves value from one register to another; DST = SRC1. |
0x21 | 1 | END2 | ? |
0x22 | 1 | END1 | ? |
0x24 | 2 | CALL | Jumps to DST and executes instructions until it reaches DST+NUM instructions |
0x26 | 3 | CALLC | 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 | IF? | If condition (don't know how condition flags work yet) is true, then executes instructions until DST, then jumps to DST+NUM; else, jumps to DST |
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) |
0x2E | 1 | CMP1 | Presumably compares two vectors component by component and sets the appropriate flags. (unknown exactly how this works as of yet) |
0x2F | 1 | CMP2 | Presumably compares two vectors component by component and sets the appropriate flags. (unknown exactly how this works as of yet) |
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. |
0x5 | 0x8 | Source 1 component selector |
0xE | 0x8 | Source 2 component selector |
0x1F | 0x1 | Flag |
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.
Registers
It is not yet fully understood how registers are organized. It does however seem that registers are separated into various banks, some RO, some WO and some RW. Because of this separation, a given register ID may not refer to the same register value when it is used as SRC or as DST.
Attribute (input, RO) registers are located within the 0x0-0x10 range. What data they are fed is specified by the CPU. Output (WO) registers are also located within the 0x0-0x10 range. What data they are contain is specified by the CPU. Registers within the 0x20-0x40 ranges seem to be RW. They contain uniforms, such as matrix data.
It appears that SRC1 and SRC2 operands don't map to registers in the same way. SRC1 is mapped to RO input registers (attributes and uniforms), while SRC2 is mapped to RW register and some RO input registers (attributes). DST is mapped to WO output registers and RW registers. As such, a register written to by an instruction cannot be referenced by SRC1, but it can be referenced by SRC2.
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.