Input Registers

The input registers are how data gets passed to the vertex shader. Direct3D provides input vertex registers, which will contain input data to the vertex shader. What this data is depends upon the format of the vertex stream(s) that are currently active.

 

The other set of input register are the constant registers, which are set up prior to running the vertex shader. It’s up to you to correctly set up these registers. For example, you probably want to pass in the current modelview matrix, light positions, etc. prior to calling the shader.

c[n] – the Constant Registers

Aside from the actual instructions that you enter in to your shader, you will most probably need to get values into those instructions, and you do it through the constant registers. They are called the constant registers simply because they are read-only once they are passed to the shader and the shader is running. However, you’ll frequently find that you’ll be setting values of the constant registers prior to the beginning of each frame of rendering (for example, setting the world/model/projection matrix values), or even prior to each render primitive call (to set a primitive or shader specific value).

 

One optimization made in the assembly of vertex shaders is that there can be only one constant register referenced from any single instruction. However, you may use that constant more than once, and each reference to the constant can independently swizzle or negate the constant’s vector elements.

 


add  r5,  v0,  v0   // fine

add  r5,  c1,  c2   // Error! 3 different registers

add  r5,  c5,  c5   // fine – doubles r5

add  r5,  c5, -c5   // fine – zero in r5

add  r5,  c5.xxxx,  c5.yyyy // OK fills r5 with x+y


 

The syntax that allowed for constant registers is also a little more complex. You can reference them like any other or you can place the numeric part inside square brackets. DirectX 8.1 introduced the address register, a0.x, which allows you to have a programmable index into the constant registers. Some examples are shown below.

 


mov  oD1, c5    // fine – regular way

mov  oD1, c[5]  // using brackets

mov  oD1, c[5+a0.x]  // fine for DX8.1+


 

There are two ways to get the constant registers loaded. One way is to use the def statement and then (in DirectX 8) post process this to add the generated shader code fragment to the shader. The preferred (and easier) way is to just set the values using SetVertexShaderConstant() to take a vector of four floats and store them in the specified register. You can set an array of constants using this call.

 

There are at least 96 float constant registers in DirectX 8 shaders, while there are at least 256 float constants in DirectX 9 vertex shaders. You could check the number specified in the MaxVertexShaderConst member of the D3DCAPS structure.

 

DirectX 9 Constant update

DirectX 9 also introduced 16 integer constants and 16 bool constants as well as modifying the way that constants can be set. In addition to the SetVertexShaderConstant() call, the def instruction in vertex shaders 2.0 now actually does something. When the def instruction is encountered in a shader, the current value of the temporary register specified is pushed then set immediately to that value for the life of the shader. These are called immediate constants. When the shader exits the constant register is popped back to its original value. Any call to get a constant’s value will necessarily be done after the shader. The post processing step required in DirectX 8 shaders is no longer supported.

 

.

vn – the Input Vertex Registers

The input vertex registers contain the vertex information that the shader is going to work on. There are a total of 16 input registers designated as v0 through v15. Each vertex register is read-only and consists of a four element floating-point vector. A vertex shader instruction may only access one input register at a time, but may use that register more than once in that instruction.

 

The vertex information is taken from the current vertex stream(s) and loaded into the vertex registers and then the shader is run. What is stored in them and the order is completely up to you, and will consist of things like the vertex position, normal, color values (diffuse, specular), texture coordinates, blending values, etc. You specify what’s in each input vertex register when you declare a shader register and use it in SetStreamSource(). This is where the mapping between your input vertex stream and the input vertex shader registers occurs.

 

If you are using the fixed function pipeline then the input vertex register are given to the shader in the following order.

 

Vector Component

FVF tag

Shader Declaration Name

Register

Position

D3DFVF_XYZ

D3DVSDE_POSITION

v0

Blend Weight

D3DFVF_XYZRHW

D3DVSDE_BLENDWEIGHT

v1

Blend Indices 1 through 5

D3DFVF_XYZB1 through

D3DFVF_XYZB5

D3DVSDE_BLENDINDICES

v2

Normal

D3DFVF_NORMAL

D3DVSDE_NORMAL

v3

Point Size

D3DFVF_PSIZE

D3DVSDE_PSIZE

v4

Diffuse

D3DFVF_DIFFUSE

D3DVSDE_DIFFUSE

v5

Specular

D3DFVF_SPECULAR

D3DVSDE_SPECULAR

v6

Texture Coordinates

D3DFVF_TEX0 though D3DFVF_TEX7

D3DVSDE_TEXCOORD0 though D3DVSDE_TEXCOORD7

v7 through v14

Position 2

 

D3DVSDE_POSITION2

v15

Normal 2

 

D3DVSDE_NORMAL2

v16

 

Internal Temporary Registers

The DirectX documentation places these in the input register section, but I feel that is really misleading since these registers must to be initialized before they can be used and since the results are lost between each shader invocation, they can’t be used to pass any information between shader calls.

rn – the Temporary Registers

There are 12 four-element vectors available as temporary registers. Temporary registers are unique in that you can, unlike constant registers, use as many as you like as arguments for shader instructions. Temporary registers are considered uninitialized when a vertex shader starts, and an attempt to read from a temporary register before it is written to will cause the creation of a vertex shader to fail. This also means the temporary register memory is volatile – once the shader returns, whatever values were in the temporary registers is lost.

an – the Address Registers

NOTE: Not available in DX8.0 shaders.

 

These are the address registers and are designed to make it easy to index into the array of constant registers. The address registers allow you to provide a signed integer offset into the constant registers. These registers may only be written to by the mov instruction, and are write-only, that is – they can only be used for indexing into the constant register array, and you can’t use them any other way.

There were no address registers available in VS 1.0 (DX8.0) vertex shaders, and only one address register, a0.x, was made available in VS 1.1 ( DX8.1). All four elements were available in VS 2.0 (DX9.0)

 

If the calculated offset is outside the legal range for a valid constant register, then the value returned will be a register of zeros. The address register can contain a signed integer offset. The value in the register stored as largest floating-point integer value not greater than the original value. This means that for positive values the fraction part is truncated, while for negative values the value is modified to the next larger integer value – that is, it rounds towards negative infinity.

 

Note: The address register is initialized to 0,0,0,0 when a shader in entered, but DirectX 8.1 shader assembler requires you to set the value in a0.x before using it.  DX9 does not force you to initialize the shader before you use it.

 

mov  a0.x, c1.x    // fails if vs.1.0, ok for vs.1.1

mov a0  , c1      // error, only a0.x is OK

mov a0.x, c1      // OK only a0.x is written

mov a0.x, v0.x    // OK

mov a0.x, r0.x    // OK

mov a0.y, r0.x    // fails if vs.1.0 or vs.1.1

 

To use the address register you can use is by itself as an index or in conjunction with an offset. You can not use it more than once or with another register. You can use it with a positive integer constant but only if they are being added, any negative sign will cause the compiler to give you a syntax error. However, the value that you mov into the address register can be negative.

 

mov  r1  , c[a0.x]      // as index

mov  r1  , c[5+a0.x]    // as offset

mov  r1  , c[a0.x-5]    // Error! Can’t subtract

mov  r1  , c[-a0.x+5]   // Error! Can’t negate

m4x4 oPos, v0, c[5+a0.x] // Can use even in macros

 

Finally, while it’s not an instruction per-se, it’s useful to understand the pseudo code that you would use in the emulation of the address register assignment. Here’s an example of the mov instruction might be written is a simulator.

 

SetSourceRegisters();

 

// Simulate the mov a0.x, Source0.x instruction

a0.x = (int) ::floor( Source0.x  );

 

WriteDestinationRegisters();

 

You might use the address register as an index into a series of transformation matrices for a ‘tweening rendering or through some levels of texture maps.

 

aL – the Loop Counter

The Loop Counter is a scalar register that was added for control of loop blocks. It’s initialized from the parameters passed to the loop instruction and is a valid reference only inside a loop block – this includes inside a subroutine called from inside a loop block.

 

The largest change with vertex shader 2.0 is the addition of flow control. This gives the ability for iterative vertex shaders, but also the potential to create slow shaders as well. If possible you should unroll any loops inside shaders.