|
|
|
|
| |
DirectX® 8 on ATI Radeon®
- The ATI Radeon® is the most complete implementation of
DirectX® 8 available today, with several key Radeon® features
exposed for the first time by DirectX® 8:
- Vertex Streams (Radeon® supports up to 8)
- Fixed-Function Tweening
- Pixel-pipeline enhancements, including pixel shaders
- Volume Textures and Volume Texture Compression
- Mirror Once Texture Addressing
- Multisample Antialiasing
- Color Masking
Vertex Streams
In DirectX® 8, it is possible to take advantage of Radeon®'s ability to fetch vertex data from multiple streams in memory. What this means is that an application can now allocate vertex buffers and fill them with different vertex components, allowing the hardware to assemble them into vertices when rendering. A common example might be to allocate one vertex buffer to hold positions and normals while allocating separate vertex streams to hold texture coordinates. Then, when multipass rendering, you can feed only the necessary texture coordinates to the hardware on the appropriate pass.
In the figure below, we have three vertex buffers. The position-normal buffer will be used on both passes of a two-pass rendering effect. Only one of the two texture coordinate buffers will be used at a time.
- In general, there are four key steps to using vertex streams:
- Allocate Vertex Buffers
- Create Vertex shader declaration
- Set up current streams
- Draw
In the sections below, we will describe how to render the using the multipass scenario described above.
Allocate Vertex Buffers
Naturally, the first thing we do is allocate vertex buffers. Here is DirectX® 8 code for allocating the vertex buffer containing the position and normal for the vertices. The texture coordinate vertex arrays are not shown but are handled similarly. This code also locks, fills and unlocks the vertex buffer.
|
pd3dDevice->CreateVertexBuffer( dwNumVerts*sizeof(POSNORM_VERTEX), D3DUSAGE_WRITEONLY, 0, D3DPOOL_MANAGED, &myVB );
myVB->Lock( 0, myVB*sizeof(POSNORM_VERTEX), (BYTE**)&pPosNormVertices, 0 );
for(i=0; i<dwNumVerts; i++)> { pPosNormVertices[i].p = myVertices[i].p; pPosNormVertices[i].n = myVertices[i].n; }
myVB->Unlock();
|
One interesting thing to note about the vertex buffer allocation is that 0 is passed in to CreateVertexBuffer() instead of an FVF code (the third parameter). Strictly speaking, it is not necessary to attach any semantic meaning to a vertex buffer by using an FVF code here. The semantics come from the use of a vertex shader declaration. Note also that the first parameter to CreateVertexBuffer() specifies an explicit vertex buffer stride. The upshot of this is that vertex buffer strides in DirectX® 8 are not implied from FVFs but are specified directly in this manner.
Create Vertex Shader Declaration
It is possible to use the CreateVertexShader() entry point without using an actual programmable vertex shader. In fact, it is necessary to do this if you hope to use streams with the fixed-function transformation pipeline.
The following code creates a vertex shader called myShader, which tells Direct3D® to expect the position and normal data to be present in the first two float-triples of stream zero (the actual stride of stream 0 is specified when the vertex buffer is created) and to expect the first three float pairs in stream 1 to contain three 2D texture coordinates. |
DWORD dwDecl[] = { D3DVSD_STREAM(0), D3DVSD_REG(D3DVSDE_POSITION, D3DVSDT_FLOAT3), D3DVSD_REG(D3DVSDE_NORMAL, D3DVSDT_FLOAT3), D3DVSD_STREAM(1), D3DVSD_REG(D3DVSDE_TEXCOORD0, D3DVSDT_FLOAT2), D3DVSD_REG(D3DVSDE_TEXCOORD1, D3DVSDT_FLOAT2), D3DVSD_REG(D3DVSDE_TEXCOORD2, D3DVSDT_FLOAT2), D3DVSD_END() };
// Create the vertex shader pd3dDevice->CreateVertexShader( dwDecl, NULL, &myShader, 0 );
|
When this vertex shader is passed to SetVertexShader(), it defines a logical vertex which has a position, a normal and three 2D texture coordinates and that this logical vertex is assembled from two independent streams. For every mapping of streams to logical vertex structures that you plan to use in your application, you will need a vertex shader whose declaration provides the semantics.
- Note that Direct3D® enforces the following two requirements:
- The order of components in a given stream must maintain FVF rules
- The components themselves must conform to FVF types on fixed-function devices
As you become more familiar with vertex streams and vertex shader declarations, you will see that these requirements are not very limiting, particularly in light of Radeon®'s support for up to 8 streams. Keep in mind that the FVF orderings need only be maintained within a given stream (vertex buffer). Also, here is a quick table summarizing the legal types for vertex components on a fixed-function device:

|
Set Current Streams and Draw
Now that we have stored our data in vertex buffers in memory and defined how these vertex buffers are to be logically assembled into a vertex by the hardware, we can render. The following code sets the current vertex shader, sets the current streams and draws using multipass rendering.
|
// Set the current Index Buffer pd3dDevice->SetIndices( myIB, 0 );
// Set the current streams pd3dDevice->SetStreamSource( 0, myVB, sizeof(POSNORM_VERTEX) ); pd3dDevice->SetStreamSource( 1, myTex0VB, sizeof(TEXCOORD3x2D_VERTEX) );
// Pull from two position-normal meshes and one tex coord mesh pd3dDevice->SetVertexShader( myShader );
// Render the first pass pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, dwNumVerts, 0, (UINT)dwNumFaces);
// Set the stream for the second set of tex coords pd3dDevice->SetStreamSource( 1, myTex1VB, sizeof(TEXCOORD3x2D_VERTEX) );
// Render the second pass pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, dwNumVerts, 0, (UINT)dwNumFaces);
|
Naturally, lots of other state setting would be going on between rendering passes, but those are the key functions needed when using vertex streams. For usage of vertex streams in context, grep for any of the entry points discussed here in the source directories of the DirectX® 8 and Radeon® SDKs. Specifically, vertex streams are used in the RadeonFaceMorph and RadeonDolphinTween sample applications on the Radeon® SDK. Both of these applications use a technique called fixed-function tweening, which we will illustrate next.
Fixed-Function Tweening
Fixed function tweening refers to Quake-style keyframe animation where a series of mesh (usually character) keyframes are present in memory and the position and normal of the mesh drawn at any given point in time is a linear blend between any two keyframe positions and normals. This concept and the name "tweening" comes from traditional cel animation, in which senior animators drew the key frames in a scene and the junior animators did "in-betweening" to complete the animation.
In Direct3D® we allocate the positions and normals of each of a mesh's keyframes in separate vertex buffers. Any other attributes are usually allocated in additional vertex buffers. For the case shown in the figure below, we have n keyframes and a single additional vertex buffer to hold the texture coordinates.
 For tweening with these buffers, we'll set up a vertex shader declaration like so:
|
DWORD dwDecl[] = { D3DVSD_STREAM(0), D3DVSD_REG(D3DVSDE_POSITION, D3DVSDT_FLOAT3), D3DVSD_REG(D3DVSDE_NORMAL, D3DVSDT_FLOAT3), D3DVSD_STREAM(1), D3DVSD_REG(D3DVSDE_POSITION2, D3DVSDT_FLOAT3), D3DVSD_REG(D3DVSDE_NORMAL2, D3DVSDT_FLOAT3), D3DVSD_STREAM(2), D3DVSD_REG(D3DVSDE_TEXCOORD0, D3DVSDT_FLOAT2), D3DVSD_END() };
// Create the vertex shader pd3dDevice->CreateVertexShader( dwDecl, NULL, &tweenShader, 0 );
|
Note the the D3DVSDE_POSITION, D3DVSDE_NORMAL, D3DVSDE_POSITION2 and D3DVSDE_NORMAL2 tokens, which identify the position and normal streams to be 'tweened.
Tweening is enabled by setting the D3DRS_VERTEXBLEND render state to D3DVBF_TWEENING. As a result, tweening and skinning are mutually exclusive in Direct3D®. Although the Radeon® has a more flexible definition of tweening and skinning, this is a clean API. In the future, more freeform hybrid skinning and tweening techniques will be expressed using programmable vertex shaders.
How much of each of the keyframes to blend in is determined by the new D3DRS_TWEENFACTOR render state.
As mentioned above, tweening is demonstrated in the RadeonFaceMorph and RadeonDolphinTween sample applications.
Pixel Pipeline Enhancements
DirectX® 8 exposes two means for programming the pixel pipeline. The traditional SetTextureStageState() entry point is still available and has been enhanced by adding additional arguments, triadic operators and a temporary storage register. The Radeon® also supports a form of pixel shaders.
SetTextureStateState() Additions
- There are two new triadic operators in DirectX 8®, which are available through SetTextureStateState():
- D3DTOP_MULTIPLYADD - Arg0 + Arg1*Arg2
- D3DTOP_LERP - (Arg0)*Arg1 + (1-Arg0)*Arg2
- Naturally, these triadic operations require the addition of Arg0 texture stage states for color and alpha. These states are only used with the new triadic operations.
- D3DTSS_COLORARG0
- D3DTSS_ALPHAARG0
- A temporary storage register has also been introduced to the fixed-function pixel pipeline in DirectX 8. This TEMP can be written at the end of each texture stage. If stage n writes into TEMP, it does not write into CURRENT. Thus CURRENT in stage n+1 is the output of stage n-1 (or DIFFUSE if n = 0). The routing of the result of a stage is controlled with the following new texture stage state:
- D3DTSS_RESULTARG - can be set to D3DTA_CURRENT or D3DTA_TEMP
- As for the input arguments, now the D3DTSS_ALPHAARGx and D3DTSS_COLORARGx states can be any of the following:
- D3DTA_DIFFUSE
- D3DTA_CURRENT
- D3DTA_TEXTURE
- D3DTA_TFACTOR
- D3DTA_SPECULAR
- D3DTA_TEMP
Radeon® Pixel Shaders
A detailed spec is forthcoming.
Volume Textures and Compression
The Radeon® supports volumetric texture maps in DirectX 8. A volume texture is created with the CreateVolumeTexture() like so:
|
m_pd3dDevice->CreateVolumeTexture (32, 32, 32, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &m_pDebugVolumeTexture);
Once the texture is created, it can be locked in and filled with data:
D3DLOCKED_BOX LockedBox; hr = m_pDebugVolumeTexture->LockBox (0, &LockedBox, 0, 0);
// Fill with data
m_pDebugVolumeTexture->UnlockBox (0);
|
On Radeon®, volume textures can be any format, including compressed.
Keep in mind that a 3D texture effectively counts as two textures on Radeon®, though all three texture blenders are available. This can be determined at run time with ValidateDevice().
The Radeon® does not support multiresolution 3D textures (i.e. volume mip maps) or quadrilinear filtering.
D3DTADDRESS_MIRRORONCE
DirectX® 8 exposes the new texture addressing mode D3DTADDRESS_MIRRORONCE, which is sort of a hybrid of mirror and clamp. Within the range -1 to 1, the texture is mirrored. Outside of this range, it is clamped. The following illustration shows a sample texture and a quad (pair of triangles) drawn using D3DTADDRESS_MIRRORONCE with texture coordinates ranging from -2.0 to 2.0:
|
|
Original texture
|
Quad using texture coordinates from
-2.0 to 2.0
|
The D3DTADDRESS_MIRRORONCE addressing mode was motivated by axial symmetry typically exhibited by volumetric light maps. This is discussed further in the 3D Texture Tutorial.
Multisample Antialiasing
The Radeon® supports 2-, 3- or 4-sample buffers for full-scene antialiasing. Note that the Radeon® exports the D3DPRASTERCAPS_STRETCHBLTMULTISAMPLE caps bit, which indicates that the multisample support is somewhat limited. In particular, this cap indicates that the D3DRS_MULTISAMPLEANTIALIAS render state cannot be turned on and off in the middle of a given frame and that multisample masking (D3DRS_MULTISAMPLEMASK) cannot be used.
Color Masking
The ability to mask writes to different channels of the render target was unfortunately removed in DirectX® 7, but has been reintegrated into DirectX® 8. It was originally removed due to the fact that it was overly general in that it allowed the app to mask each individual bit of the render target. In DirectX® 8, the application can individually mask each channel (any combination of R, G, B and A) of the render target. This is nice because it allows the application to "protect" some channels while writing into the others. The new render state D3DRS_COLORWRITEENABLE is used to mask writes to render target channels. For example, to protect the alpha channel, one would do:
|
d3dDevice->SetRenderState( D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE ); To render only into alpha:
d3dDevice->SetRenderState( D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALPHA );
|
| |
 |
|
|
|