Vertex Buffers

Introduction

Creating a Vertex Buffer

Filling Vertex Buffers

Rendering With Vertex Buffers

Lesson Downloads

Introduction

In the last few lessons we've called the DrawUserPrimitives method for all of our rendering. This works well for examples because it's very easy to use and very little preparation is required to use it. It isn't very efficient though.

Since our vertex array is in system memory, every time we call DrawUserPrimitives that array has to be copied into device memory so it can be rendered. A better solution is to allocate our vertex array in memory that the graphics card has access to. A Vertex Buffer allows us to allocate memory from a variety of memory pools, device accessible memory among them.

As a code example, I'll convert part of the example used in the Triangle lesson to show how easy it is to switch to using Vertex Buffers. Only the Triangle List portion will be converted. Showing the others would be redundant, but they could also be used as an exercise to see if you understood this lesson.

Creating a Vertex Buffer

Creating a Vertex Buffer is done in the same way as most classes, using "new". There are 3 overloaded versions of the VertexBuffer construtor. 1 is not supported and 1 is for more advanced use. The remaining one will be used here.

VertexBuffer ( 
   System.Type typeVertexType,
   System.Int32 numVerts,
   Microsoft.DirectX.Direct3D.Device device,
   Microsoft.DirectX.Direct3D.Usage usage,
   Microsoft.DirectX.Direct3D.VertexFormats vertexFormat,
   Microsoft.DirectX.Direct3D.Pool pool 
)
Public Sub New(
   ByVal typeVertexType As System.Type,
   ByVal numVerts As Integer,
   ByVal device As Microsoft.DirectX.Direct3D.Device,
   ByVal usage As Microsoft.DirectX.Direct3D.Usage,
   ByVal vertexFormat As Microsoft.DirectX.Direct3D.VertexFormats,
   ByVal pool As Microsoft.DirectX.Direct3D.Pool
)
typeVertexType
This is the type of the vertex buffer. Passing the type of the Vertex allows Direct3D to query it for important information such as the size of each vertex.
numVerts
The number of Vertices the buffer can hold.
device
The Direct3D device to associate with the Vertex Buffer
usage
Zero or more Usage flags to indicate how the Vertex Buffer will be used. If no flags are used, 0 should be used. The common flags are:
  • Dynamic - this vertex buffer is designed to be over-written on a regular (frame-by-frame) basis.
  • WriteOnly - this should almost always be set since it allows the video driver to store the vertices in a format designed for rendering without worrying about making it possible to read back.
  • SoftwareProcessing - should be set when you create your device with the software processing flag. If you create the device with hardware processing flag do not set this flag.
vertexFormat
VertexFormats flags that describe the format of the vertices to be stored in the VertexBuffer. When using predefined vertex types, you can use the Format property to get this value. This is shown in the accompanying example code.
pool
The memory pool to allocate the VertexBuffer from. The 2 commonly used values are:
  • Default - the buffer is placed in video-accessible memory. Generally this is in local video card memory or AGP memory. This kind of buffer can require extra management on your part. A default pool resource needs to be re-allocated when the device is Reset.
  • Managed - the buffer is created in system memory and a copy is created in device-accessible memory as needed. Direct3D will take care of all the issues involved in swapping resources in and out so you don't have to worry about managing them. In general you should place your resources in the managed pool unless you have a good reason not to. This also means that no special effort is required when calling Reset on your device as Direct3D will re-allocate and re-initialize these resources for you.

And here is an example based on the lesson's code.

   protected D3D.VertexBuffer m_vb;
   Type vb_type = typeof(D3D.CustomVertex.TransformedColored);
   D3D.VertexFormats vb_format = D3D.CustomVertex.TransformedColored.Format;
   D3D.Usage usage = D3D.Usage.WriteOnly | D3D.Usage.SoftwareProcessing;
   
   //Allocate the VertexBuffer

   m_vb=new D3D.VertexBuffer(vb_type,              //The Type of our VertexBuffer

                             m_list.Length,        //Number of Vertices that will be stored

                             m_device,             //The device to associate with the VertexBuffer

                             usage,                //Usage Flags

                             vb_format,            //The VertexFormat of the VertexBuffer

                             D3D.Pool.Managed);    //The memory Pool that should be used for the Vertex Buffer

   Protected _vb As D3D.VertexBuffer
   Dim vbType As Type = GetType(D3D.CustomVertex.TransformedColored)
   Dim vbFormat As D3D.VertexFormats = D3D.CustomVertex.TransformedColored.Format
   Dim usage As D3D.Usage = usage.WriteOnly Or usage.SoftwareProcessing

   'Allocate the VertexBuffer

   '(arguments:

   '             vbType:  The Type of our VertexBuffer

   '       _list.Length:  Number of Vertices that will be stored

   '            _device:  The device to associate with the VertexBuffer

   '              usage:  Usage Flags

   '           vbFormat:  The VertexFormat of the VertexBuffer

   '   D3D.Pool.Managed:  The memory Pool that should be used for the Vertex Buffer

   ')

   _vb = New D3D.VertexBuffer(vbType, _list.Length, _device, usage, vbFormat, _
         D3D.Pool.Managed)

Here we allocate enough room for our data, and specify that it should be in the Managed Pool and that it's WriteOnly. Using (and respecting) the WriteOnly flag can give nice performance boosts so I recommend using it whenever possible.

Filling Vertex Buffers

Now that we've created a Vertex Buffer we have to fill it with our data. Vertex Buffers are an "opaque" container. The internal data could be stored in a variety of different ways. This means the video driver can allocate the memory and store the data in a way that makes the most sense for its use. This can mean that it's stored in a way that would be difficult for us to deal with.

The SetData method takes an array and copies it into the VertexBuffer starting at a provided offset. This method copies all of the data from the source array, it cannot copy a subset. Another method of writing to VertexBuffers is to Lock the VertexBuffer but that will be covered in a later lesson.

void SetData(
   System.Object data,
   System.Int32 lockAtOffset,
   Microsoft.DirectX.Direct3D.LockFlags flags
)
Public Sub SetData(
   ByVal data As Object,
   ByVal lockAtOffset As Integer,
   ByVal flags As Microsoft.DirectX.Direct3D.LockFlags
)
data
The object that contains the source data. Typically this will be an array.
lockAtOffset
This is the start location for the write to the VertexBuffer. Set this to 0 to start at the beginning of the buffer.
Flags
Flags to describe how the Vertex Buffer should be locked. Here are the 2 most common ones:
  • Discard - All data will be overwritten. Typically a new memory buffer is allocated behind the scenes and you are handed a pointer to that buffer to fill. This way the video card can finish using the original data while you generate new data. This flag can only be used on a Vertex Buffer created with the Dynamic flag.
  • None - No special locking will be done.

When we use it to initially fill our Vertex Buffer, none of the flags are required and we want to fill the entire buffer. These calls will typically look like this.

m_vb.SetData(m_list,             //The data array to copy from

             0,                  //Start location in the VB, this is where the data is copied to

             D3D.LockFlags.None);//No special flags

   '              _list:  The data array to copy from

   '                  0:  Start location in the VB, this is where the data is copied to

   ' D3D.LockFlags.None:  No special flags

   _vb.SetData(_list, 0, D3D.LockFlags.None)

Using a VertexBuffer is really not much harder than just using a system memory array. Now that you know how to manage the Vertex Buffer, let's look at how to use it for rendering.

Rendering With Vertex Buffers

To render the geometry stored in a Vertex Buffer we use the DrawPrimitives method of the Direct3D device. It's very similar to the DrawUserPrimitives method we've used in the past. The biggest difference is that you don't pass the geometry data to the method. Instead you first call SetStreamSource to indicate where data should be read from.

Direct3D can render from multiple streams of data. The simplified interface of DrawUserPrimitives always sets your data as stream 0 and doesn't allow you to set other streams at all. Using SetStreamSource you can set multiple streams and then DrawPrimitives will read from all of them. I will not go into any detail on using multiple streams in this lesson, that will be saved for later lessons. There are 2 overloaded versions of this method. The simpler one will be covered here.

void SetStreamSource(
   System.Int32 streamNumber,
   Microsoft.DirectX.Direct3D.VertexBuffer streamData,
   System.Int32 offsetInBytes
)
Public Sub SetStreamSource(
   ByVal streamNumber As Integer,
   ByVal streamData As Microsoft.DirectX.Direct3D.VertexBuffer,
   ByVal offsetInBytes As Integer
)
streamNumber
Which stream to bind the data to. Since we only use a single stream, we set this to 0.
streamData
This is the Vertex Buffer we wish to bind to the stream.
offsetInBytes
This sets the initial read location and is given in bytes. Typically this is set to 0.

This is the DrawPrimitives prototype.

void DrawPrimitives(
   Microsoft.DirectX.Direct3D.PrimitiveType primitiveType,
   System.Int32 startVertex,
   System.Int32 primitiveCount
)
Public Sub DrawPrimitives(
   ByVal primitiveType As Microsoft.DirectX.Direct3D.PrimitiveType,
   ByVal startVertex As Integer,
   ByVal primitiveCount As Integer
)
primitiveType
The type of primitive to render. TriangleList for example.
startVertex
The first vertex to be read from the Vertex Buffer. Often this will be 0.
primitiveCount
The number of primitives (not vertices) to be rendered.

There's very little here that we haven't seen in previous lessons. Though the methods are slightly different, the concepts are the same as when we used DrawUserPrimitives. To help pull it all together here is a code sample showing both SetStreamSource and DrawPrimitives.

   //Set m_vb to be the active Stream Source

   m_device.SetStreamSource(0,      //Stream Number

                            m_vb,   //VertexBuffer to be bound

                            0);     //Offset in bytes

   
   //Draw the TriangleList defined in m_vb

   m_device.DrawPrimitives(D3D.PrimitiveType.TriangleList,  //Primitive Type

                           0,                               //Start Vertex

                           m_list.Length / 3);              //Number of Primitives

   'Set _vb to be the active Stream Source

   '(arguments:

   '                  0:  Stream Number

   '                _vb:  VertexBuffer to be bound

   '                  0:  Offset in bytes

   ')

   _device.SetStreamSource(0, _vb, 0)
   
   'Draw the TriangleList defined in _vb

   '(arguments:

   '       TriangleList:  Primitive Type

   '                  0:  Start Vertex

   '     _list.Length\3:  Number of Primitives

   ')

   _device.DrawPrimitives(D3D.PrimitiveType.TriangleList, 0, _list.Length \ 3)

Lesson Downloads

Back