Changes

Jump to navigation Jump to search
4,802 bytes added ,  00:59, 12 July 2018
m
Line 1: Line 1: −
[[Category:GFX]]
+
[[Category:GPU]]
    
== Overview ==
 
== 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.
+
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/Internal_Registers|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.
 
Both instructions and descriptors are coded in little endian.
 
Basic implementations of the following specification can be found at [https://github.com/smealum/aemstro] and [https://github.com/neobrain/nihstro].
 
Basic implementations of the following specification can be found at [https://github.com/smealum/aemstro] and [https://github.com/neobrain/nihstro].
The instruction set seems to have been heavily inspired by Microsoft's vs_3_0 [http://msdn.microsoft.com/en-us/library/windows/desktop/bb172938%28v=vs.85%29.aspx].
+
The instruction set seems to have been heavily inspired by Microsoft's vs_3_0 [http://msdn.microsoft.com/en-us/library/windows/desktop/bb172938%28v=vs.85%29.aspx] and the Direct3D shader code [https://msdn.microsoft.com/en-us/library/windows/hardware/ff552891%28v=vs.85%29.aspx].
 
Please note that this page is being written as the instruction set is reverse engineered; as such it may very well contain mistakes.
 
Please note that this page is being written as the instruction set is reverse engineered; as such it may very well contain mistakes.
 +
 +
Debug information found in the code.bin of "Ironfall: Invasion" suggests that there may not be more than 512 instructions and 128 operand descriptors in a shader.
    
== Nomenclature ==
 
== Nomenclature ==
Line 122: Line 124:  
|-
 
|-
 
|  0x7
 
|  0x7
 +
|  0x5
 +
|  Source 2 register (SRC2)
 +
|-
 +
|  0xC
 
|  0x7
 
|  0x7
 
|  Source 1 register (SRC1)
 
|  Source 1 register (SRC1)
|-
  −
|  0xE
  −
|  0x5
  −
|  Source 2 register (SRC2)
   
|-
 
|-
 
|  0x13
 
|  0x13
Line 246: Line 248:  
|-
 
|-
 
|  0x11
 
|  0x11
0x7
+
0x5
 
|  Source 1 register (SRC1)
 
|  Source 1 register (SRC1)
 +
|-
 +
|  0x16
 +
|  0x2
 +
|  Address register index for SRC2 (IDX_2)
 
|-
 
|-
 
|  0x18
 
|  0x18
Line 278: Line 284:  
|-
 
|-
 
|  0x11
 
|  0x11
0x7
+
0x5
 
|  Source 1 register (SRC1)
 
|  Source 1 register (SRC1)
 +
|-
 +
|  0x16
 +
|  0x2
 +
|  Address register index for SRC3 (IDX_3)
 
|-
 
|-
 
|  0x18
 
|  0x18
Line 319: Line 329:  
|  1
 
|  1
 
|  DPH
 
|  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)
+
|  Computes dot product on a 3-component vector with 1.0 appended to it and a 4-component vector; DST = SRC1.SRC2 (with SRC1 homogenous)
 
|-
 
|-
 
|  0x04
 
|  0x04
 
|  1
 
|  1
???
+
DST
?
+
Equivalent to Microsoft's [https://msdn.microsoft.com/en-us/library/windows/desktop/bb219790.aspx dst] instruction: DST = {1, SRC1[1]*SRC2[1], SRC1[2], SRC2[3]}
 
|-
 
|-
 
|  0x05
 
|  0x05
 
|  1u
 
|  1u
 
|  EX2
 
|  EX2
|  Computes SRC1's per-component exponent with base 2; DST[i] = EXP(SRC1[i]) for all i
+
|  Computes SRC1's first component exponent with base 2; DST[i] = EXP2(SRC1[0]) for all i
 
|-
 
|-
 
|  0x06
 
|  0x06
 
|  1u
 
|  1u
 
|  LG2
 
|  LG2
|  Computes SRC1's log2 component by component; DST[i] = LOG2(SRC1[i]) for all i
+
|  Computes SRC1's first component logarithm with base 2; DST[i] = LOG2(SRC1[0]) for all i
 
|-
 
|-
 
|  0x07
 
|  0x07
 
|  1u
 
|  1u
???
+
LITP
?
+
Appears to be related to Microsoft's [https://msdn.microsoft.com/en-us/library/windows/desktop/bb174703.aspx lit] instruction; DST = clamp(SRC1, min={0, -127.9961, 0, 0}, max={inf, 127.9961, 0, inf}); n.b.: 127.9961 = 0x7FFF / 0x100
 
|-
 
|-
 
|  0x08
 
|  0x08
Line 374: Line 384:  
|  1u
 
|  1u
 
|  RCP
 
|  RCP
|  Computes the reciprocal of the vector, component by component; DST[i] = 1/SRC1[i] for all i
+
|  Computes the reciprocal of the vector's first component; DST[i] = 1/SRC1[0] for all i
 
|-
 
|-
 
|  0x0F
 
|  0x0F
 
|  1u
 
|  1u
 
|  RSQ
 
|  RSQ
|  Computes the reciprocal of the square root of the vector, component by component; DST[i] = 1/sqrt(SRC1[i]) for all i
+
|  Computes the reciprocal of the square root of the vector's first component; DST[i] = 1/sqrt(SRC1[0]) for all i
 
|-
 
|-
 
| 0x10
 
| 0x10
Line 424: Line 434:  
|  1i
 
|  1i
 
|  DPHI
 
|  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)
+
|  Computes dot product on a 3-component vector with 1.0 appended to it and a 4-component vector; DST = SRC1.SRC2 (with SRC1 homogenous)
 
|-
 
|-
 
|  0x19
 
|  0x19
 
|  1i
 
|  1i
???
+
DSTI
?
+
DST with sources swapped.
 
|-
 
|-
 
|  0x1A
 
|  0x1A
Line 462: Line 472:  
|-
 
|-
 
|  0x20
 
|  0x20
?
+
0
???
+
BREAK
?
+
Breaks out of LOOP block; do not use while in nested IF/CALL block inside LOOP block.
 
|-
 
|-
 
|  0x21
 
|  0x21
Line 529: Line 539:  
|  3
 
|  3
 
|  JMPU
 
|  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.
+
|  If condition BOOL is true, then jumps to DST, else does nothing. Having bit 0 of NUM = 1 will invert the test, jumping if BOOL is false instead.
 
|-
 
|-
 
|  0x2E-0x2F
 
|  0x2E-0x2F
Line 539: Line 549:  
|  5i
 
|  5i
 
|  MADI
 
|  MADI
|  Multiplies two vectors and adds a third one component by component; DST[i] = SRC3[i] + SRC2[i].SRC1[i] for all i
+
|  Multiplies two vectors and adds a third one component by component; DST[i] = SRC3[i] + SRC2[i].SRC1[i] for all i; this is not an FMA, the intermediate result is rounded
 
|-
 
|-
 
|  0x38-0x3F
 
|  0x38-0x3F
 
|  5
 
|  5
 
|  MAD
 
|  MAD
|  Multiplies two vectors and adds a third one component by component; DST[i] = SRC3[i] + SRC2[i].SRC1[i] for all i
+
|  Multiplies two vectors and adds a third one component by component; DST[i] = SRC3[i] + SRC2[i].SRC1[i] for all i; this is not an FMA, the intermediate result is rounded
 
|}
 
|}
   Line 628: Line 638:     
The component selector enables swizzling. For example, component selector 0x1B is equivalent to .xyzw, while 0x55 is equivalent to .yyyy.
 
The component selector enables swizzling. For example, component selector 0x1B is equivalent to .xyzw, while 0x55 is equivalent to .yyyy.
 +
 +
Depending on the current shader opcode, source components are disabled implicitly by setting the destination component mask. For example, ADD o0.xy, r0.xyzw, r1.xyzw will not make use of r0's or r1's z/w components, while DP4 o0.xy, r0.xyzw, r1.xyzw will use all input components regardless of the used destination component mask.
    
== Relative addressing ==
 
== Relative addressing ==
   −
There are 3 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.
+
There are 3 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. It is only possible to use address registers with vector uniform registers, attempting to use them with input attribute or temporary registers results in the address register being ignored (i.e. read as zero).
    
a0.x and a0.y are set manually through the MOVA instruction by rounding a float value to integer precision. Hence, they may take negative values.
 
a0.x and a0.y are set manually through the MOVA instruction by rounding a float value to integer precision. Hence, they may take negative values.
Line 759: Line 771:  
|  Vector uniform registers.
 
|  Vector uniform registers.
 
|}
 
|}
 +
 +
== Floating-Point Behavior ==
 +
 +
The PICA200 is not IEEE-compliant. It has positive and negative infinities and NaN, but does not seem to have negative 0. Input and output subnormals are flushed to +0. The internal floating point format seems to be the same as used in shader binaries: 1 sign bit, 7 exponent bits, 16 (explicit) mantissa bits. Several instructions also have behavior that differs from the IEEE functions. Here are the results from some tests done on hardware (s = largest subnormal, n = smallest positive normal):
 +
 +
{| class="wikitable" border="1"
 +
|-
 +
!  Computation
 +
!  Result
 +
!  Notes
 +
|-
 +
|  inf * 0
 +
|  0
 +
|  Including inside MUL, MAD, DP4, etc.
 +
|-
 +
|  NaN * 0
 +
|  NaN
 +
 +
|-
 +
|  +inf - +inf
 +
|  NaN
 +
|  Indicates +inf is real inf, not FLT_MAX
 +
|-
 +
|  rsq(rcp(-inf))
 +
|  +inf
 +
|  Indicates that there isn't -0.0.
 +
 +
|- style="border-top: double"
 +
|  rcp(-0)
 +
|  +inf
 +
|  no -0 so differs from IEEE where rcp(-0) = -inf
 +
|-
 +
|  rcp(0)
 +
|  +inf
 +
 +
|-
 +
|  rcp(+inf)
 +
|  0
 +
 +
|-
 +
|  rcp(NaN)
 +
|  NaN
 +
 +
 +
|- style="border-top: double"
 +
|  rsq(-0)
 +
|  +inf
 +
|  no -0 so differs from IEEE where rsq(-0) = -inf
 +
|-
 +
|  rsq(-2)
 +
|  NaN
 +
 +
|-
 +
|  rsq(+inf)
 +
|  0
 +
 +
|-
 +
|  rsq(-inf)
 +
|  NaN
 +
 +
|-
 +
|  rsq(NaN)
 +
|  NaN
 +
 +
 +
|- style="border-top: double"
 +
|  max(0, +inf)
 +
|  +inf
 +
 +
|-
 +
|  max(0, -inf)
 +
|  -inf
 +
 +
|-
 +
|  max(0, NaN)
 +
|  NaN
 +
|  max violates IEEE but match GLSL spec
 +
|-
 +
|  max(NaN, 0)
 +
|  0
 +
 +
|-
 +
|  max(-inf, +inf)
 +
|  +inf
 +
 +
 +
|- style="border-top: double"
 +
|  min(0, +inf)
 +
|  0
 +
 +
|-
 +
|  min(0, -inf)
 +
|  -inf
 +
 +
|-
 +
|  min(0, NaN)
 +
|  NaN
 +
|  min violates IEEE but match GLSL spec
 +
|-
 +
|  min(NaN, 0)
 +
|  0
 +
 +
|-
 +
|  min(-inf, +inf)
 +
|  -inf
 +
|
 +
 +
|- style="border-top: double"
 +
|  cmp(s, 0)
 +
|  false
 +
|  cmp does not flush input subnormals
 +
|-
 +
|  max(s, 0)
 +
|  s
 +
|  max does not flush input or output subnormals
 +
|-
 +
|  mul(s, 2)
 +
|  0
 +
|  input subnormals are flushed in arithmetic instructions
 +
|-
 +
|  mul(n, 0.5)
 +
|  0
 +
|  output subnormals are flushed in arithmetic instructions
 +
|}
 +
 +
1.0 can be multiplied 63 times by 0.5 until the result compares equal zero. This is consistent with a 7-bit exponent and output subnormal flushing.
 +
 +
== Control Flow ==
 +
 +
Control flow is implemented using four independent stacks:
 +
 +
* 4-deep CALL stack
 +
* 8-deep IF stack
 +
* 4-deep LOOP stack
 +
 +
All stacks are initially empty. After every instruction but before JMP takes effect, the PC is incremented and a copy is sent to each stack. Each stack is checked against its copy of the PC. If an entry is popped from the stack, the copied PC is updated and used for the next check of this stack, although the IF/LOOP stacks can each only pop one entry per instruction, whereas the CALL stack is checked again until it doesn't match or the stack is empty. The updated PC copy with the highest priority wins: LOOP (highest), IF, CALL, JMP, original PC (lowest).
 +
 +
Special cases:
 +
* JMP overwrites the PC *after* the stacks checks (and only if no stack was popped).
 +
* Executing a BREAK on an empty LOOP stack hangs the GPU.
 +
* A stack overflow discards the oldest element, so you could think of it as a queue or a ring buffer.
 +
* If the CALL stack is popped four times in a row, the fourth update to its copy of the PC is missed (the third PC update will be propagated). Probably a hardware bug.
8

edits

Navigation menu