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 )
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 )
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 )
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 )
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)