Changes

Jump to navigation Jump to search
4,270 bytes added ,  21:41, 29 October 2022
no edit summary
Line 2: Line 2:     
== 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].
Line 12: Line 12:  
== Nomenclature ==
 
== 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 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 constant registers) 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 U appended to them are the same as their non-U version, except they are executed conditionally based on the value of a constant boolean register.
    
* 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.
 
* 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.
Line 124: 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 180: Line 180:  
|}
 
|}
   −
Format 3 : (used for uniform-based conditional flow control instructions)
+
Format 3 : (used for constant-based conditional flow control instructions)
 
{| class="wikitable" border="1"
 
{| class="wikitable" border="1"
 
|-
 
|-
Line 197: Line 197:  
|  0x16
 
|  0x16
 
|  0x4
 
|  0x4
Uniform ID (BOOL/INT)
+
Constant ID (BOOL/INT)
 
|-
 
|-
 
|  0x1A
 
|  0x1A
Line 248: 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 280: 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 325: Line 333:  
|  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
Line 340: Line 348:  
|  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 396: Line 404:  
|  1u
 
|  1u
 
|  MOVA
 
|  MOVA
|  Move to address register; Casts the float uniform given by SRC1 to an integer (truncating the fractional part) and assigns the result to (a0.x, a0.y, _, _), respecting the destination component mask.
+
|  Move to address register; Casts the float value given by SRC1 to an integer (truncating the fractional part) and assigns the result to (a0.x, a0.y, _, _), respecting the destination component mask.
 
|-
 
|-
 
|  0x13
 
|  0x13
Line 430: Line 438:  
|  0x19
 
|  0x19
 
|  1i
 
|  1i
???
+
DSTI
?
+
DST with sources swapped.
 
|-
 
|-
 
|  0x1A
 
|  0x1A
Line 464: 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 531: 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 541: 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 635: Line 643:  
== 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.
+
{| class="wikitable" border="1"
 +
|-
 +
!  IDX raw value
 +
!  Register name
 +
|-
 +
|  0x0
 +
|  None
 +
|-
 +
|  0x1
 +
|  a0.x
 +
|-
 +
|  0x2
 +
|  a0.y
 +
|-
 +
|  0x3
 +
|  aL
 +
|}
 +
 
 +
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 on constant registers, attempting to use them on 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 714: Line 740:     
== Registers ==
 
== 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]].
+
{| class="wikitable" border="1"
 +
|-
 +
!  Name
 +
!  Format
 +
!  Type
 +
!  Access
 +
!  Written by
 +
!  Description
 +
|-
 +
|  v0-v15
 +
|  vector
 +
|  float
 +
|  Read only
 +
|  Application/Vertex-stream
 +
|  Input registers.
 +
|-
 +
|  o0-o15
 +
|  vector
 +
|  float
 +
|  Write only
 +
|  Vertex shader
 +
Output registers.
 +
|-
 +
|  r0-r15
 +
|  vector
 +
|  float
 +
|  Read/Write
 +
|  Vertex shader
 +
|  Temporary registers.
 +
|-
 +
|  c0-c95
 +
|  vector
 +
|  float
 +
|  Read only
 +
|  Application/Vertex-stream
 +
|  Floating-point Constant registers.
 +
|-
 +
|  i0-i3
 +
|  vector
 +
|  integer
 +
|  Read only
 +
|  Application
 +
|  Integer Constant registers. (special purpose)
 +
|-
 +
|  b0-b15
 +
|  scalar
 +
|  boolean
 +
|  Read only
 +
|  Application
 +
|  Boolean Constant registers. (special purpose)
 +
|-
 +
|  a0.x & a0.y
 +
|  scalar
 +
|  integer
 +
|  Use/Write
 +
|  Vertex shader
 +
|  Address registers.
 +
|-
 +
|  aL
 +
|  scalar
 +
|  integer
 +
|  Use
 +
|  Vertex shader
 +
|  Loop count register.
 +
|}
   −
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).
+
Input attribute registers store the per-vertex data given by the CPU and hence are read-only.
   −
Temporary registers (r0-r15) can be used for intermediate calculations and can both be read and written.
+
Output registers hold the data to be passed to the later GPU stages and are write-only. Each of the output register is assigned a semantic by setting the corresponding [[GPU_Internal_Registers]]. Output registers o7-o15 are only available in vertex shaders.
 +
Keep in mind that writing to the same output register/component more than once appears appears to cause problems (e.g. GPU hangs).
   −
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.
+
Temporary registers can be used for intermediate calculations and can be both read and written.
   −
It appears that writing twice to the same output register can cause problems (e.g. GPU hangs).
+
Constant registers hold data uploaded by the application which remain constant throughout all processed vertices. There are 96 float[4] constant registers (c0-c95), eight boolean constant registers (b0-b7), and four int[4] constant registers (i0-i3).
 +
Many shader instructions which take float arguments can only provide the full 7 bits for one SRC operand. All other source operands can only be used to refer to input attributes or temporary registers and cannot be passed Floating-point Constant registers.
 +
 
 +
Address registers and the Loop count register can be used to to provide relative addressing for the designated SRC operand. For more information, see the section on [[#Relative_addressing|relative addressing]].
    
DST mapping :
 
DST mapping :
Line 734: Line 827:  
!  Description
 
!  Description
 
|-
 
|-
|  0x0-0x6
+
|  0x0-0xF
|  o0-o6
+
|  o0-o15
 
|  Output registers.
 
|  Output registers.
 
|-
 
|-
Line 747: Line 840:  
{| class="wikitable" border="1"
 
{| class="wikitable" border="1"
 
|-
 
|-
SRC1 raw value
+
SRC raw value
 
!  Register name
 
!  Register name
 
!  Description
 
!  Description
 
|-
 
|-
|  0x0-0x7
+
|  0x0-0xF
|  v0-v7
+
|  v0-v15
 
|  Input attribute registers.
 
|  Input attribute registers.
 
|-
 
|-
Line 761: Line 854:  
|  0x20-0x7F
 
|  0x20-0x7F
 
|  c0-c95
 
|  c0-c95
Vector uniform registers.
+
Constant registers.
 
|}
 
|}
    
== Floating-Point Behavior ==
 
== 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. Several instructions also have behavior that differs from the IEEE functions. Here are the results from some tests done on hardware:
+
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"
 
{| class="wikitable" border="1"
Line 774: Line 867:  
!  Notes
 
!  Notes
 
|-
 
|-
|  inf/NaN * 0
+
|  inf * 0
 
|  0
 
|  0
 
|  Including inside MUL, MAD, DP4, etc.
 
|  Including inside MUL, MAD, DP4, etc.
 +
|-
 +
|  NaN * 0
 +
|  NaN
 +
 
|-
 
|-
 
|  +inf - +inf
 
|  +inf - +inf
Line 787: Line 884:     
|- style="border-top: double"
 
|- style="border-top: double"
 +
|  rcp(-0)
 +
|  +inf
 +
|  no -0 so differs from IEEE where rcp(-0) = -inf
 +
|-
 
|  rcp(0)
 
|  rcp(0)
 
|  +inf
 
|  +inf
Line 802: Line 903:  
|  rsq(-0)
 
|  rsq(-0)
 
|  +inf
 
|  +inf
|   
+
no -0 so differs from IEEE where rsq(-0) = -inf
 
|-
 
|-
 
|  rsq(-2)
 
|  rsq(-2)
Line 831: Line 932:  
|  max(0, NaN)
 
|  max(0, NaN)
 
|  NaN
 
|  NaN
|  max and min violate IEEE but match GLSL spec
+
|  max violates IEEE but match GLSL spec
 
|-
 
|-
 
|  max(NaN, 0)
 
|  max(NaN, 0)
Line 852: Line 953:  
|  min(0, NaN)
 
|  min(0, NaN)
 
|  NaN
 
|  NaN
|   
+
min violates IEEE but match GLSL spec
 
|-
 
|-
 
|  min(NaN, 0)
 
|  min(NaN, 0)
Line 860: Line 961:  
|  min(-inf, +inf)
 
|  min(-inf, +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.
35

edits

Navigation menu