Direct3D Programming Tip #7: Use Smart Resource Locks

In Direct3D9, direct CPU access to a resource is obtained by obtaining a lock on the resource. Every time you obtain the lock, you must make sure that you release it. In Direct3D10 and Direct3D11, a similar situation is faced with resources, but the terminology has changed to mapping the resource. Using a helper class to handle the lock and unlock operations on a resource helps you create exception safe code in C++.

Exception Handling With Locked Resources

So why should we bother using a helper class? Why not just call Lock and Unlock directly? Consider the following code:

void FillIndexBuffer(IDirect3DIndexBuffer9 *indices)
{
    void *ptr = 0;
    THR(indices->Lock(0, 0, &ptr, D3DLOCK_DISCARD));
    WORD *indexData = static_cast(ptr);
    ReadIndicesFromFile(indexData);
    THR(indices->Unlock());
}

(THR turns a failed HRESULT into a C++ exception.) What would happen if ReadIndicesFromFile threw some sort of exception? The call stack would be unwound until we found an appropriate exception handler and we would leave FillIndexBuffer without unlocking the index buffer. A similar problem can arise if FillIndexBuffer is a long method and has an alternate return path in the middle where we might forget to unlock the buffer. Even worse, you may have already called Unlock and still attempt to use the indexData or ptr variables because they are still in scope. If you’re lucky, this will give you an access violation right away. If you’re unlucky, strange things will start to happen and you’ll have no idea why.

If we treat the buffer lock as a resource and use the Resource Acquisition Is Initialization (RAII) pattern, then we can make sure that no matter how we leave the execution context of FillIndexBuffer that we have unlocked the buffer before we return.

There’s another bit of ugliness up there that we will end up repeating every time we call Lock manually on an index buffer. I’m talking about the whole void * and casting business. I generally consider the appearance of void * in my own code to be a code smell. We can push this casting smell into our helper class that performs the lock for us.

A Locking Helper Class

A simple class that satisfies these requirements looks like this:

template <typename Index>
class index_lock
{
public:
    // lock in constructor, unlock in destructor
    index_lock(IDirect3DIndexBuffer9 *ib,
        DWORD flags = 0,
        UINT offset = 0,
        UINT size = 0)
        : m_ib(ib)
    {
        void *data = 0;
        THR(m_ib->Lock(offset, size, &data, flags));
        m_data = static_cast<Index *>(data);
    }
    ~index_lock()
    {
        const HRESULT hr = m_ib->Unlock();
        assert(SUCCEEDED(hr));
    }

    // type safe accessors to vertex data
    const Index *data() const { return m_data; }
    Index *data() { return m_data; }

private:
    IDirect3DIndexBuffer9Ptr m_ib;
    Index *m_data;
};

The constructor for this helper performs the lock and stashes away the index buffer in a _com_ptr style smart pointer. (The declaration of IDirect3DIndexBuffer9Ptr is provided by the d3d9.h header file when _COM_SMARTPTR_TYPEDEF is defined.) The flags argument was moved so that the more common case of locking the entire buffer doesn’t require extra arguments. Attempting to construct the smart lock index_lock will fail if the call to Lock fails, since THR will throw on failure. The casting business is hidden from consumers of index_lock and the saved data pointer is appropriately typed.

All of that is fairly straightforward. The destructor is a little more interesting. You don’t want to throw an exception from a destructor. Instead, we assert that the Unlock succeeded. Its important to have the assert as a separate statement because in release builds, the argument to assert may be removed entirely from the source. The data members give us access to the underlying data pointer in a typesafe manner.

Example Revisited

Our little example from above would now look like this:

void FillIndexBuffer(IDirect3DIndexBuffer9 *indices)
{
    ReadIndicesFromFile(index_lock<WORD>(indices, D3DLOCK_DISCARD).data());
}

Wow, that’s quite a bit shorter! But is it really safe? Let’s look closely at what happens. First, an instance of the index_lock class is constructed while evaluating the arguments to ReadIndicesFromFile. That will call Lock on the index buffer and obtain the data pointer. If Lock fails, an exception will be thrown and we will unwind the call stack, starting with FillIndexBuffer (ReadIndicesFromFile hasn’t been called yet). When the lock succeeds, we have an instance of index_lock<WORD> and we call the data method on that to obtain the data pointer which is passed to ReadIndicesFromFile.

ReadIndicesFromFile does its work and returns. What happens to the instance of index_lock? C++ guarantees that the temporary instance created by the compiler persists until ReadIndicesFromFile returns or the stack is unwound due to an exception. In the normal case, ReadIndicesFromFile returns, the temporary instance is destroyed. This invokes the destructor for index_lock and the index buffer is unlocked. Since we didn’t keep around a pointer to the locked data in a variable, its impossible for us to misuse this pointer after the lock has been released. If ReadIndicesFromFile throws an exception, then the stack will be unwound and the temporary created by the compiler will be destroyed. Again, this will unlock the index buffer before the stack is unwound any further.

By using a helper class for resource locking, we get cleaner code that is easier to maintain. We also get the benefit of cleaning up the resource lock properly when an exception is thrown.

kick it on GameDevKicks.com

5 Responses to “Direct3D Programming Tip #7: Use Smart Resource Locks”

  1. Caswal Says:

    Interesting read, I am also a big fan of using RAII to do techniques such as this, allows for very tidy implementations and systems with minimum fuss.

    In the second paragraph threw is mispelt, and RAII is spelt RIAA in bracks where you link to wiki.

    Anyway, informative read. Thanks!

  2. legalize Says:

    Thanks for the sharp eye! I have corrected both spelling mistakes.

  3. C++ Code Smells « Legalize Adulthood! Says:

    [...] from Direct3D: locking an index buffer gives you a typeless pointer to the locked data. Using a smart resource lock class hides the typeless pointer inside the resource [...]

  4. NightCreature Says:

    Wouldn’t this index_lock class leak your Index* whenever it gets deleted? Just wondering why that pointer doesn’t get cleaned up in this class.

    Next to that if you are doing this for a game you will most like not want to use exceptions as they are still considered to be to slow.

    • legalize Says:

      No, the Index pointer is not leaked because it is cleaned up by calling Unlock.

      Exceptions are for error handling. Error handling is not a time critical path. Obviously handling errors costs more than not handling errors. Also obviously, not handling detectable errors makes for code that is difficult to debug.

      Talking about “X is too slow for games” is meaningless without context. Not every game needs every single CPU cycle dedicated exclusively to their game. Do you think a game like “SceneIt!” is CPU bound? Is a game like Hexic CPU bound? Nonsense. Premature optimization is the root of all evil.

      Yes, there are some games that might be penalized by using exceptions for error handling, but it is my feeling that for all the games out there, it is very few. Certainly if you are new to game programming you should be focused on creating correct code first. You can profile for execution speed later. Again, we’re talking about error handling here, the exceptional case, not the typical case.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: