Geometry processing in DirectX can be achieved in a variety of ways. In fact, different methods have been used as the API has evolved through several versions. Luckily, this has led to a stable, intuitive method, which is the one featured in DirectX 9, and the one we will be focusing on. The method consists of using vertex buffers, which are just user-provided arrays of data (vertex coordinates, texture coordinates, colors, and so on) batched together for rendering. A vertex buffer can be defined in user or graphics/AGP memory, be assigned a vertex shader for custom procedural effects, and so on.
We will begin by analyzing how to declare a vertex buffer. This is achieved by using the CreateVertexBuffer call, which has the following interface:
HRESULT CreateVertexBuffer(UINT Length,DWORD Usage,DWORD FVF, D3DPOOL Pool,IDirect3DVertexBuffer8** ppVertexBuffer);
There are many calls that share this same (or relatively close) syntax in Direct3D. Creating indices, textures, and so on is all handled in a similar manner. Here is the run-down of the parameter list: The first parameter should contain the length of the buffer to be created, in bytes. The second parameter can be used as a hint to Direct3D about the use we are creating the index array for. Items like point sprites and N-patches are specified here, but if we just want to render regular geometry, we can store a zero value here. The third parameter declares the Flexible Vertex Format (FVF) to be used with this specific vertex buffer. FVFs are used to declare the data structure that will hold your vertices. Will you store vertex coordinates only, or will colors be added? Will you be incorporating texture coordinates and, if so, which data type will be used to encode them? All this is encapsulated in the FVF. A complete example is provided later in this section. After that, we need to state the kind of memory management we want for our buffer. Our vertex buffer can be stored in regular system RAM or, if our hardware supports it, directly in the video card. By doing this, we can ensure fast access to the rendering core and increased performance. Valid parameters are D3DPOOL_DEFAULT, which should be used to let Direct3D choose the most adequate storage options; D3DPOOL_MANAGED, which is used to store data in device memory to ensure optimal performance; or D3DPOOL_SYSTEMMEM, which stores data in regular system RAM. See the next section on geometry optimization for more on setting these parameters. The last parameter is just a pointer to the newly declared vertex buffer.
Now, we will use the DrawPrimitive to actually render the geometry. But we need to start by setting the stream source so any subsequent DrawPrimitive is coupled with the vertex buffer and the call knows where to get data from. This is achieved with the SetStreamSource call. The following is a complete example:
g_pd3dDevice->SetStreamSource( 0, g_pVB, sizeof(CUSTOMVERTEX) ); g_pd3dDevice->DrawPrimitive( D3DPT_POINTLIST, 0, NUMVTX );
The first parameter to SetStreamSource is the identifier of the logical stream to Direct3D, starting from 0 and reaching a value of numstreams-1. The second parameter is the vertex buffer, whereas the third parameter is used to specify the stride between two contiguous elements. In this case, it's the size of the CUSTOMVERTEX structure that is defined by our FVF. Once we execute this line, subsequent DrawPrimitive calls will know where to gather data from. The syntax for the DrawPrimitive call is
HRESULT DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount);
The first parameter states the type of primitive to render. Valid types are
The second and third parameter to DrawPrimitive tell Direct3D which position in the vertex buffer we should start streaming data from and how many primitives to send down the pipeline. This is useful to render not the complete primitive, but part of it (for example, the part that uses a unique material identifier).