Locking Our Buffers

The locking mechanism seems to be one of the most misunderstood calls in Direct3D, particularly in Managed DirectX. How to do the locks, and do them fast seems to be a particularly tough question for people to grasp.

Before we get to that, though, what exactly is "locking" the buffer? It is simply the act of allowing your CPU to access a range of data in your resource. Since you cannot talk directly to the graphics hardware, there needs to be a way to allow you to manipulate vertex data from your application, and the locking mechanism is it. Look at the various lock overloads that exist for our vertex and index buffers (they take the same parameters):

 public System.Array Lock ( System.Int32 offsetToLock ,     Microsoft.DirectX.Direct3D.LockFlags flags ) public System.Array Lock ( System.Int32 offsetToLock , System.Type typeVertex ,     Microsoft.DirectX.Direct3D.LockFlags flags , params int[] ranks ) public Microsoft.DirectX.Direct3D.GraphicsStream Lock ( System.Int32 offsetToLock ,     System.Int32 sizeToLock , Microsoft.DirectX.Direct3D.LockFlags flags ) 

As you can see, there are three overloads available when attempting to lock our buffers. We should start with the first one, since it is the simplest. This overload is only available if you created your buffer via the constructor that takes a System.Type and a number of vertices or indices. In actuality, this overload really just calls the next overload with the data you passed in from the constructor.

The next two overloads are where things get interesting. The first parameter of each is the offset (in bytes) at which you want to start the lock. If you wish to lock the entire buffer, you will naturally want to start this member at zero. You'll notice the first two overloads each return an array, which can be extremely useful since it will return the data to you in an easy-to-use form. In the second overload, the second parameter is the base type of the array you wish to be returned. The last parameter is used to determine the size of the array that is returned.

This "ranks" parameter seems to throw everyone for a loop for some reason, so let's look at it in depth for a moment. Let's assume you had a vertex buffer that had nothing but position data in it (Vector3), and it had 1,200 vertices in it. If you wanted to lock this buffer into a Vector3 array of 1,200 items, the lock call would look like this:

 Vector3[] data = (Vector3[])vb.Lock(0, typeof(Vector3), LockFlags.None, 1200); 

This call will result in a single dimension array of 1,200 Vector3s. Now let's say for some reason you wanted to have a two-dimensional array of 600 vertices each. That lock call would look like

 Vector3[,] data = (Vector3[,])vb.Lock(0, typeof(Vector3), LockFlags.None, 600, 600); 

Notice the extra parameter? The ranks parameter is actually a parameter array. It can and will create up to a three-dimensional array of data to return back to you. You should use this parameter to specify exactly how large of an array you wish to get back.

SHOP TALK: LOCKING BUFFERS WITH HIGH PERFORMANCE

LockFlags will be covered with the last overload, but first I want to point out some of the drawbacks of using either of the overloads that return an array. First and foremost, we should talk about performance. Assuming you have created a vertex buffer with the "default" options, and no lock flags, when this method is called, the following things happen:

  • Vertex data is locked; memory location of data is stored.

  • A new array of the correct type is allocated, given the size specified in the ranks parameter.

  • The data is copied from the locked memory location to our new buffer.

  • This buffer is returned to the user, who then modifies it as needed.

  • When the Unlock method is finally called, there is another copy of data from the array back into the locked memory location.

  • Finally, the vertex data is unlocked.

It's not hard to understand how and why this method could be quite a bit slower than you might expect given all that it's doing. It's possible to remove one of the two copies, by either specifying Usage.WriteOnly when creating the buffer (eliminates the first copy), or by specifying the LockFlags.ReadOnly flag when locking (eliminates the second copy), but it isn't possible to remove both. What could you do with a ReadOnly/WriteOnly buffer anyway?

The last overload is the most powerful. It also has a new parameter the others don't, namely the size of the data we want to lock. In the other overloads, this value is calculated during the call (sizeof(type) * NumberRanks). If you are using this overload, simply pass in the size of the data you wish to lock (in bytes). If you want to lock the entire buffer, pass in zero for the first two parameters of this method.

This overload will return a GraphicsStream class that will allow you to directly manipulate the locked data, without any extra memory allocations for the arrays, without any extra memory copies. You can do exactly what it is you want to do with the memory. There are multiple methods on the GraphicsStream object that allow you to "write" your data to this memory, but this isn't where you can get the big speed boosts. You get the big speed boosts manipulating the memory directly.

I don't want to sound like I'm suggesting that the locking functions that return arrays are amazingly slow. They are very convenient and when used properly, almost as fast as the overloads that return the stream. However, when you are looking to pull every ounce of performance out of your system, using the graphics stream is the way to go.



Managed DirectX 9 Graphics and Game Programming, Kick Start
Managed DirectX 9 Kick Start: Graphics and Game Programming
ISBN: B003D7JUW6
EAN: N/A
Year: 2002
Pages: 180
Authors: Tom Miller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net