Vertex and Index Buffers

  CU_Buffers.zip (31.9 KiB, 3,795 hits)

Previously, I stored geometry data in a user-defined pointer. While this is ok for little demos, in real applications, we would want to use vertex buffers because transfering data over the system bus is too slow. Using vertex buffers allows the video card to store vertices in local video memory, which can be rendered quickly by the GPU. An index buffer lets us reuse vertices that we have already defined. Take a quad for example. A quad is formed by two triangles. These two triangles share two vertices. Without an index buffer, we would have to specify six separate vertices even though two of the vertices are shared. With an index buffer, we would only have to specify four vertices and use indices to access them.

Vertex and Index Buffers

I’ve created a couple of classes, CVertexBuffer and CIndexBuffer, that will simplify the process of working with vertex and index buffers so that using them in future projects will be easier. It’s interesting to note that Managed DirectX already provides similar functionality to these wrappers.

#ifndef CVERTEXBUFFER_H
#define CVERTEXBUFFER_H 

#include "stdafx.h"
#include "CIndexBuffer.h" 

class CVertexBuffer
{
public:
    CVertexBuffer();
    ~CVertexBuffer() { Release(); } 

    BOOL CreateBuffer( LPDIRECT3DDEVICE9 pDevice, UINT numVertices, DWORD FVF, UINT vertexSize,
        BOOL dynamic = FALSE );
    void Release();
    BOOL SetData( UINT numVertices, void *pVertices, DWORD flags = D3DLOCK_DISCARD );
    void SetIndexBuffer( CIndexBuffer* pIB );
    void Render( LPDIRECT3DDEVICE9 pDevice, UINT numPrimitives, D3DPRIMITIVETYPE primitiveType ); 

private:
    LPDIRECT3DVERTEXBUFFER9 m_pVB;
    CIndexBuffer*           m_pIB;
    UINT                    m_numVertices;
    UINT                    m_vertexSize;
    DWORD                   m_FVF;
}; 

#endif

Since CVertexBuffer is a vertex buffer wrapper, it needs its own IDirect3DVertexBuffer9 instance. We’ll also store some options in the class as well as a pointer to a CIndexBuffer instance so we can have the choice of rendering the vertex buffer using indices.

#include “..\include\stdafx.h”
#include “..\include\CVertexBuffer.h”

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Default Constructor
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
CVertexBuffer::CVertexBuffer() Toggle

{

m_pVB = NULL;
m_pIB = NULL;
m_numVertices = 0;
m_FVF = 0;
m_vertexSize = 0;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Creates the vertex buffer.
Parameters:
[in] pDevice – Pointer to IDirect3DDevice9
[in] numVertices – Max number of vertices allowed in the buffer
[in] FVF – Flexible Vertex Format
[in] vertexSize – Size of the vertex structure
[in] dynamic – TRUE for dynamic buffer, FALSE for static buffer
Returns: TRUE on success, FALSE on failure
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BOOL CVertexBuffer::CreateBuffer( LPDIRECT3DDEVICE9 pDevice, UINT numVertices, DWORD FVF, UINT vertexSize, BOOL dynamic ) Toggle

{

Release();
m_numVertices = numVertices;
m_FVF = FVF;
m_vertexSize = vertexSize;

// Dynamic buffers can’t be in D3DPOOL_MANAGED
D3DPOOL pool = dynamic ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED;
DWORD usage = dynamic ? D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC : D3DUSAGE_WRITEONLY;
if ( FAILED( pDevice->CreateVertexBuffer( m_numVertices * m_vertexSize, usage, m_FVF, pool, &m_pVB, NULL ) ) )
{

SHOWERROR( “CreateVertexBuffer failed.”, __FILE__, __LINE__ );
return FALSE;

}

return TRUE;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Cleans up resources
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CVertexBuffer::Release() Toggle

{

SAFE_RELEASE( m_pVB );
m_numVertices = 0;
m_FVF = 0;
m_vertexSize = 0;
// IndexBuffer is released in CIndexBuffer
m_pIB = NULL;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Fill up the vertex buffer
Parameters:
[in] numVertices – Number of vertices being put in the buffer.
[in] pVertices – Pointer to the vertex data
[in] flags – Lock flags
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BOOL CVertexBuffer::SetData( UINT numVertices, void *pVertices, DWORD flags ) Toggle

{

if ( m_pVB == NULL )
{

return FALSE;

}

char *pData;
// Lock the vertex buffer
if ( FAILED( m_pVB->Lock( 0, 0, (void**)&pData, flags ) ) )
{

SHOWERROR( “IDirect3DVertexBuffer9::Lock failed.”, __FILE__, __LINE__ );
return FALSE;

}

// Copy vertices to vertex buffer
memcpy( pData, pVertices, numVertices * m_vertexSize );

// Unlock vertex buffer
if ( FAILED( m_pVB->Unlock() ) )
{

SHOWERROR( “IDirect3DVertexBuffer9::Unlock failed.”, __FILE__, __LINE__ );
return FALSE;

}

return TRUE;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Assigns an index buffer to the vertex buffer.
Parameters:
[in] pIB – Pointer to CIndexBuffer
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CVertexBuffer::SetIndexBuffer( CIndexBuffer* pIB ) Toggle

{

m_pIB = pIB;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Purpose: Render the buffer
Parameters:
[in] pDevice – Pointer to IDirect3DDevice9
[in] numPrimitives – Number of primitives being rendered
[in] primitiveType – D3DPRIMITIVETYPE
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CVertexBuffer::Render( LPDIRECT3DDEVICE9 pDevice, UINT numPrimitives, D3DPRIMITIVETYPE primitiveType ) Toggle

{

if ( pDevice == NULL )
{

return;

}

pDevice->SetStreamSource( 0, m_pVB, 0, m_vertexSize );
pDevice->SetFVF( m_FVF );

if ( m_pIB )
{

pDevice->SetIndices( m_pIB->GetBuffer() );
pDevice->DrawIndexedPrimitive( primitiveType, 0, 0, m_numVertices, 0, numPrimitives );

}
else
{

pDevice->DrawPrimitive( primitiveType, 0, numPrimitives );

}

}

Vertex buffers are created simply by calling IDirect3DDevice9::CreateVertexBuffer. You’ll need to know the number of vertices in order to create the corresponding buffer so that DirectX knows how big the buffer should be.

To put fill up the vertex buffer with data, we first need to lock it with IDirect3DVertexBuffer9::Lock. This will give us a pointer into the memory of the buffer. Once we have that pointer we can fill up the buffer with the vertex data. Once we’re done filling up the buffer, we need to unlock it by calling IDirect3DVertexBuffer9::Unlock.

To render the vertex data, we first tell DirectX where to read the vertex data from by calling IDirect3DDevice9::SetStreamSource. This tells DirectX from which vertex buffer to render vertices. We also need to specify the vertex format of the vertices to be rendered by calling IDirect3DDevice9::SetFVF. Note that since the CreateVertexBuffer function takes a FVF, we can just use SetFVF instead of specifying a new vertex format with D3DVERTEXELEMENT. If we are rendering without an index buffer, we call IDirect3DDevice9::DrawPrimitive. If we are using an index buffer, we first tell DirectX to use the indices by calling IDirect3DDevice9::SetIndices. Then, to render the indexed geometry, call IDirect3DDevice9::DrawIndexedPrimitive.

Note in CreateBuffer, there is some code about creating a dynamic vertex buffer. Since we haven’t gone over the uses of dynamic buffers, you can ignore that part since the dynamic parameter is FALSE by default.

The vertex buffer wrapper is done, so now we’ll move on to the index buffer wrapper.

#ifndef CINDEXBUFFER_H
#define CINDEXBUFFER_H 

#include "stdafx.h" 

class CIndexBuffer
{
public:
    CIndexBuffer();
    ~CIndexBuffer() { Release(); }
    BOOL CreateBuffer( LPDIRECT3DDEVICE9 pDevice, UINT numIndices, D3DFORMAT format,  BOOL dynamic = FALSE );
    void Release();
    BOOL SetData( UINT numIndices, void *pIndices, DWORD flags = D3DLOCK_DISCARD );
    LPDIRECT3DINDEXBUFFER9 GetBuffer() { return m_pIB; } 

private:
    LPDIRECT3DINDEXBUFFER9  m_pIB;
    UINT                    m_numIndices;
}; 

#endif

The CIndexBuffer class looks pretty similar to the CVertexBuffer class. This time, we’re wrapping up a IDirect3DIndexBuffer9 object.

#include “..\include\stdafx.h”
#include “..\include\CIndexBuffer.h”

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Default Constructor
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
CIndexBuffer::CIndexBuffer() Toggle

{

m_pIB = NULL;
m_numIndices = 0;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Free up resources
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CIndexBuffer::Release() Toggle

{

SAFE_RELEASE( m_pIB );
m_numIndices = 0;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Creates an index buffer.
Parameters:
[in] pDevice – Device to create the buffer with
[in] numIndices – Number of indices to put in the index buffer
[in] format – D3DFMT_INDEX32 for 32-bit indices, D3DFMT_INDEX16 for 16-bit indices
[in] dynamic – TRUE for dynamic buffer, FALSE for static buffer
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BOOL CIndexBuffer::CreateBuffer( LPDIRECT3DDEVICE9 pDevice, UINT numIndices, D3DFORMAT format, BOOL dynamic ) Toggle

{

Release();
m_numIndices = numIndices;

// Dynamic buffers can’t be in D3DPOOL_MANAGED
D3DPOOL pool = dynamic ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED;
UINT size = (format == D3DFMT_INDEX32) ? sizeof( UINT ) : sizeof( USHORT );
DWORD usage = dynamic ? D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC : D3DUSAGE_WRITEONLY;

if( FAILED( pDevice->CreateIndexBuffer( m_numIndices * size, usage, format, pool, &m_pIB, NULL ) ) )
{

SHOWERROR( “CreateIndexBuffer failed.”, __FILE__, __LINE__ );
return FALSE;

}
return TRUE;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Fill up the index buffer
Parameters:
[in] numIndices – Number of indices being put in the buffer.
[in] pIndices – Pointer to the vertex data
[in] dwFlags – Lock flags
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BOOL CIndexBuffer::SetData( UINT numIndices, void *pIndices, DWORD flags ) Toggle

{

if ( m_pIB == NULL )
{

return FALSE;

}

char *pData;

D3DINDEXBUFFER_DESC desc;
m_pIB->GetDesc( &desc );
UINT size = (desc.Format == D3DFMT_INDEX32) ? sizeof( UINT ) : sizeof( USHORT );

// Lock the index buffer
if ( FAILED( m_pIB->Lock( 0, 0, (void**)&pData, flags ) ) )
{

return FALSE;

}

memcpy( pData, pIndices, numIndices * size );

// Unlock index buffer
if ( FAILED( m_pIB->Unlock() ) )
{

return FALSE;

}

return TRUE;

}

The IDirect3DIndexBuffer9 methods are very similar to the IDirect3DVertexBuffer9 methods. To create the buffer, call IDirect3DDevice9::CreateIndexBuffer. Note however, that when we create the index_ buffer, we can specify either 32-bit or 16-bit indices. You should stick to 16-bit indices unless you really need the range of 32-bit indices because not all hardware supports 32-bit indices.

Just like the vertex buffer, when we fill up the index buffer, we need to lock and unlock the buffer by calling IDirect3DIndexBuffer9::Lock and IDirect3DIndexBuffer9::Unlock.

struct CUSTOMVERTEX
{

float x, y, z; // Position in 3d space
DWORD color; // Color

};

CUSTOMVERTEX g_4Vertices[] =
{

{-1.0f, –1.0f, 5.0f, D3DCOLOR_XRGB( 255, 0, 0 )}, // 0
{-1.0f, 1.0f, 5.0f, D3DCOLOR_XRGB( 255, 255, 0 )}, // 1
{ 1.0f, –1.0f, 5.0f, D3DCOLOR_XRGB( 0, 255, 0 )}, // 2
{ 1.0f, 1.0f, 5.0f, D3DCOLOR_XRGB( 0, 0, 255 )} // 3

};

USHORT g_indices[] = { 0, 1, 2, 1, 3, 2 };

CUSTOMVERTEX g_6Vertices[] =
{

{-1.0f, –1.0f, 5.0f, D3DCOLOR_XRGB( 255, 0, 0 )},
{-1.0f, 1.0f, 5.0f, D3DCOLOR_XRGB( 255, 255, 0 )},
{ 1.0f, –1.0f, 5.0f, D3DCOLOR_XRGB( 0, 255, 0 )},
{-1.0f, 1.0f, 5.0f, D3DCOLOR_XRGB( 255, 0, 255 )},
{ 1.0f, 1.0f, 5.0f, D3DCOLOR_XRGB( 0, 0, 255 )},
{ 1.0f, –1.0f, 5.0f, D3DCOLOR_XRGB( 0, 255, 255 )}

};

When we specify the vertex data, notice that one declaration contains 4 vertices and the other contains 6 vertices. These 2 different declaration will be used to render the same geometry (Usually this information is imported from some 3d program like Maya or 3ds max).

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary:
This callback function will be called immediately after the Direct3D device has been created. This is
the best location to create D3DPOOL_MANAGED resources. Resources created here should be released in
the OnDestroyDevice callback.
Parameters:
[in] pDevice – Pointer to a DIRECT3DDEVICE9 instance
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnCreateDevice( LPDIRECT3DDEVICE9 pDevice )
{

m_VB4.CreateBuffer( pDevice, 4, D3DFVF_XYZ | D3DFVF_DIFFUSE, sizeof( CUSTOMVERTEX ) );
m_VB4.SetData( 4, g_4Vertices );
m_IB.CreateBuffer( pDevice, 6, D3DFMT_INDEX16 );
m_IB.SetData( 6, g_indices );
m_VB4.SetIndexBuffer( &m_IB );

m_VB6.CreateBuffer( pDevice, 6, D3DFVF_XYZ | D3DFVF_DIFFUSE, sizeof( CUSTOMVERTEX ) );
m_VB6.SetData( 6, g_6Vertices );

}

Creating and filling up the buffers is easy with the wrapper classes, CVertexBuffer and CIndexBuffer. Just call CVertexBuffer/CIndexBuffer::CreateBuffer and CVertexBuffer/CIndexBuffer::SetData.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Renders the current frame.
Parameters:
[in] pDevice – Pointer to a DIRECT3DDEVICE9 instance
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnRenderFrame( LPDIRECT3DDEVICE9 pDevice )
{

pDevice->Clear( 0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB( 0, 0, 0 ), 1.0f, 0 );
pDevice->BeginScene();

D3DXMATRIX world;
D3DXMatrixTranslation( &world, –1.5f, 1.0f, 0.0f );
pDevice->SetTransform( D3DTS_WORLD, &world );
m_VB4.Render( pDevice, 2, D3DPT_TRIANGLELIST );

D3DXMatrixTranslation( &world, 1.5f, –1.0f, 0.0f );
pDevice->SetTransform( D3DTS_WORLD, &world );
m_VB6.Render( pDevice, 2, D3DPT_TRIANGLELIST );

pDevice->EndScene();
pDevice->Present( 0, 0, 0, 0 );

}

We can render the buffers simply by calling CVertexBuffer::Render(). The D3DXMatrixTranslation function creates a matrix that moves the quads over a bit so they don’t render right on top of each other.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary:
This callback function will be called immediately after the Direct3D device has been destroyed.
Resources created in the OnCreateDevice callback should be released here, which generally includes
all D3DPOOL_MANAGED resources.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnDestroyDevice()
{

m_VB6.Release();
m_VB4.Release();
m_IB.Release();

}

Don’t forget to release the buffers!

We’re slowly building up our code library. On to the next tutorial!

2 thoughts on “Vertex and Index Buffers”

Holosim January 29, 2015 at 9:55 am

Great tutorial. Really breaks down the components to make a heck of a lot more sense of the foundation concepts. One problem. I can’t get the Vertex Buffer to lock.

When I load the sample solution you provide above into MS Visual Studio 2010 with DirectX release June2010, I change “dxerr9.lib” to “dxerr.lib” in the properties under “Additional dependencies”, and no other changes. It builds without errors and only a few warnings about ‘sprintf’ being deprecated. But when I run the app in debug mode, it triggers a break point at line 88 of “CVertexBuffer.cpp”, which is where the vertex buffer lock is being attempted. When I hit “Continue” on the dialog, it continues to the SHOWERROR dialog that says the Vertex buffer lock failed. The program continues normally after that, it just doesn’t show anything in the window. If I try to run the executable directly in Windows, it crashes (no dialog).

I’m wondering if something may have changed in how the Lock() function call works between 2005 and 2010 that would cause this? I’m really hoping to continue through these tutorials because they are really helping me to see both the forest and trees when it comes to DirectX.

Thanks
Kyle Simmons

Comments are closed.

Leave A Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

<code> For inline code.
[sourcecode] For multiline code.