My own little DirectX FAQ

Thursday, May 30, 2002
How do Texture Transform matrices work?

The answer is - slightly oddly. If you are familiar with the OpenGL ones, forget them. These are subtly and confusingly different. So best just to forget them and start again. I am using the DirectX8 terminology here, but it translates fairly easily to DX7 or DX9.

The basic operation is in four steps:

(1) Coordinates (1-4 components) are fed into the texture transformation unit.
(2) They are transformed by the matrix (i.e. a matrix multiply operation).
(3) The components of the result may be divided by one of the components (used for projective texturing), and that component is discarded.
(4) The results are fed to the texture units as texture coordinates.

Step (1) is controlled by SetTextureStageState ( n, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_xxx ), where xxx is either:
- PASSTHRU - you supply 1 to 4 values in the vertex itself. The number of components is determined by the FVF of the vertex, and the coordinate set is determined by the number order with D3DTSS_TCI_PASSTHRU. So passing in D3DTSS_TCI_PASSTHRU | 2 means that the coordinates should be taken from the third set (they are numbered from 0) of coordinates specified in the vertex/FVF.
- One of the CAMERASPACEyyy enumerations - 3 values are always used, and they are the result of calculations using other components of the vertex such as position or normal.

However they are generated, these values are padded to a 4-component vector in the following ways:
-1 component (x) -> (x,1,0,0)
-2 components (x,y) -> (x,y,1,0)
-3 components (x,y,z) -> (x,y,z,1)
-4 components (x,y,z,w) -> (x,y,z,w)

Also note that TEXCOORDINDEX requires an FVF input field, even when not using D3DTSS_TCI_PASSTHRU. This is because the behaviour of the D3DRS_WRAPzzz renderstates operates on vertex inputs, _not_ texture coordinates. So the value of zzz corresponds to the number ORed with CAMERASPACEyyy. This behaviour is slightly more rational under DX9 fortunately.

Step (2) is controlled by the texture transform matrix set using SetTransform ( D3DTS_TEXTUREn, &matrix ), where n is the appropriate Texture Stage State stage. The 4-component input defined by step (1) is transformed by the 4x4 matrix to produce a 4-component output vector.

Step (3) is controlled by SetTextureStateStage ( n, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNTxxx ). The value of xxx determines how many components are required by the rasteriser. Also, the argument may be ORed with D3DTTFF_PROJECTED. This causes the result of step (2) to have three its components divided by the other one. Which component is the divisor is determined by the value of D3DTTFF_COUNTxxx:
-D3DTTFF_COUNT1 | D3DTTFF_PROJECTED: invalid combination
-D3DTTFF_COUNT2 | D3DTTFF_PROJECTED: (x,y,z,w) -> (x/y,*,*,*)
-D3DTTFF_COUNT3 | D3DTTFF_PROJECTED: (x,y,z,w) -> (x/z,y/z,*,*)
-D3DTTFF_COUNT4 | D3DTTFF_PROJECTED: (x,y,z,w) -> (x/w,y/w,z/w,*)

An asterisk(*) means that this value is not well-defined by the docs, and trying to actually use the value generated here is unwise, as it may vary between different bits of hardware. For example, do not use D3DTTFF_COUNT2 | D3DTTFF_PROJECTED when the texture used is a 2D one.

For more details, look at the DX8.1 docs section entitled "Texture Coordinate Processing", and all its sub-sections. Especially note the one called "Special Effects", as it shows the correct matrix for scrolling a 2D texture around.

Tuesday, May 28, 2002
What is the difference between D3DCAPS8::MaxTextureBlendStages and D3DCAPS8::MaxSimultaneousTextures?

The first is how many TSS blend stages the driver understands. The second is how many different textures you can use at a time. These two are not the same things - you may be able to use extra blend stages without using extra textures. In fact one of the commonest blends on 2-texture hardware (e.g. Radeon 1/7x00, GeForce1/2, Permedia3) is this one for bump mapping:

Stage0: DotProduct3 ( Texture, Diffuse )
Stage1: Modulate ( Texture, Current )
Stage2: Modulate ( Tfactor, Current )
Stage3: Disable

Here, the Diffuse register holds the light vector, and the Tfactor register holds the light's colour. As you can see, we are using only two textures, but three blend stages. So something like the GeForce 1 and 2 usually have MaxSimultaneousTextures=2, but MaxTextureBlendStages=8. Note that this does not mean you can always use 8 stages - in practice you can rarely use more than 3 on the GF1&2 - you must always use ValidateDevice() at the start of day to check which of your blend modes actually works on the current hardware.

(note that the alpha channel pipeline is not shown above for clarity - but if you want to use this blend on real hardware, you need to put a sensible one in - see the FAQ about disabling alpha before colour below)

Monday, May 27, 2002

A common question is - what do all those arguments actually mean?

The DX9 version of DrawIndexedPrimitive is probably the best model. The DX8 one does not have a BaseVertexIndex argument in the DIP call itself - it is supplied when you call SetIndices. But both do the same thing, the argument is just in a different call on DX8.

HRESULT DrawIndexedPrimitive(

INT BaseVertexIndex,
UINT MinIndex,
UINT NumVertices,
UINT StartIndex,
UINT PrimitiveCount

StartIndex - the number of the first index used to draw primitives, numbered from the start of the index buffer. So if this is 20, then the first twenty entries in the index buffer are skipped, and the first primitive starts rendering from the 21st index.

PrimitiveCount - the number of primitives drawn.

BaseVertexIndex - this is added to all indices before being used to look up a vertex in a vertex buffer. So if this is 100, then the indexed triangle 1,2,4 will actually use vertices 101, 102, 104 from the VB.

MinIndex - this is the lowest-numbered vertex used by the call. Note that BaseVertexIndex is added before this is used. So the first actual vertex used will be (MinIndex+BaseVertexIndex).

NumVertices - the number of vertices in the VB after MinIndex used by this call. So the last vertex in the VB used by the call must be less than (MinIndex+NumVertices+BaseVertexIndex).

Note that MinIndex and NumVertices are not used by hardware pipelines - they transform and light vertices only when referenced by an index. But software pipelines are far more efficient when then can start at a place in the VB and T&L the next n vertices. That is what these numbers are for - they are to help the software pipeline. For this reason, you should avoid spreading vertices all over your VB - try to keep all the vertices for a single model in one continuous block withing VBs. This will also help the hardware T&L pipe to some extent, simply because most hardware has a pre-T&L vertex cache, and this will work best if vertices are close to each other.

So, to recap. This call:

HRESULT DrawIndexedPrimitive(

20, // INT BaseVertexIndex,
3, // UINT MinIndex,
5, // UINT NumVertices,
6, // UINT StartIndex,
3 // UINT PrimitiveCount

With this index buffer:

1,2,3, 3,2,4, 3,4,5, 4,5,6, 6,5,7, 5,7,8

It will draw PrimitiveCount=3 triangles, starting with index number StartIndex=6. So it ignores the first two and the last one triangles in the index buffer and only uses these:

3,4,5, 4,5,6, 6,5,7

You have told it that you are only using vertices from MinIndex=3 to (MinIndex+NumVertices)=8, not including the last one, so you say you are only using vertices 3 to 7. Which is true - only the numbers 3 to 7 appear in this draw, even though other vertices are used elsewhere in the index buffer.

Then BaseVertexIndex is added to the indices before the vertices are fetched from the vertex buffer. So the vertex numbers actually used will be:

23,24,25, 24,25,26, 26,25,27

...and the software T&L pipe will know it only needs to transform vertices 23 to 27 inclusive.

Many ask what the point of the BaseVertexIndex value is. This is useful when you have dynamic data that you are streaming through a dynamic vertex buffer. Because you are using the NOOVERWRITE/DISCARD model (you are, aren't you? Good, just checking), you will get a portion of vertex buffer to write vertices to that will probably change every frame. So you don't know ahead of time where the first vertex is going to be. But if you simply change BaseVertexIndex to be the number of this vertex, then you can precompute the index list at start of day, and then you don't need to change the indices each frame. Which can be a useful speedup.

Thursday, May 23, 2002
When to disable the TextureStateState pipeline.

Drivers can get very confused if you don't set D3DTOP_DISABLE in both pipes at the same time. In fact according to the docs in DX8 (this was not true in DX7), it's just plain Wrong:

Disables output from this texture stage and all stages with a higher index. To disable texture mapping, set this as the color operation for the first texture stage (stage 0). Alpha operations cannot be disabled when color operations are enabled. Setting the alpha operation to D3DTOP_DISABLE when color blending is enabled causes undefined behavior.

In theory the driver should know what to do. In practice it mostly works, but every now and then drivers get confused and do the wrong thing. Which is probably why the DX8 docs were changed to forbid it. It's evil because it works 90% of the time (i.e. when you're coding it), and then falls over horribly and without warning on 10% of cards/drivers (i.e. when you're trying to ship the game).

The first FAQ is of course, "how do I unsubscribe". The answer is at the bottom of every post: http://DISCUSS.MICROSOFT.COM/archives/DIRECTXDEV.html

This is my own little version of the DX FAQ. OK, there might be some non-DX FAQ entries in here (especially on VIPM or something), but it's mainly for the benefit of people on the DXDev mailing list:

MSDN DirectX Developer Centre:
Web Interface: http://DISCUSS.MICROSOFT.COM/archives/DIRECTXDEV.html
Use the Web Interface (above) to unsubscribe from the list.
Use plain-text only. HTML is not accepted. Attachments are removed

Note that this is not the official FAQ, that is here. And it's pretty good, has quite a bit of stuff in it, and gets updated fairly frequently. Annoyingly, it also moves around fairly frequently, so the above link might not work, and you may need to go to MSDN and search for it.

TomF - tomf AT radgametools DOT com