Windows with C++

A Modern C++ Library for DirectX Programming

Kenny Kerr

Download the Code Sample

Kenny KerrI’ve written a lot of DirectX code and I’ve written about DirectX extensively. I’ve even produced online training courses on DirectX. It really isn’t as hard as some developers make it out to be. There’s definitely a learning curve, but once you get over that, it’s not hard to understand how and why DirectX works the way it does. Still, I’ll admit that the DirectX family of APIs could be easier to use.

A few nights ago, I decided to remedy this. I stayed up all night and wrote a little header file. A few nights later, it approached 5,000 lines of code. My goal was to provide something that you can use to build apps more easily with Direct2D and to challenge all of the “C++ is hard” or “DirectX is hard” arguments that are so prevalent today. I didn’t want to produce yet another heavy wrapper around DirectX. Instead, I decided to leverage C++11 to produce a simpler API for DirectX without imposing any space and time overheard to the core DirectX API. You can find this library I developed at dx.codeplex.com.

The library itself consists entirely of a single header file called dx.h—the rest of the source code on CodePlex provides examples of its use.

In this column, I’ll show you how you can use the library to perform various common DirectX-related activities more easily. I’ll also describe the design of this library to give you an idea of how C++11 can help to make classic COM APIs more palatable without resorting to high-impact wrappers such as the Microsoft .NET Framework.

Obviously, the focus is on Direct2D. It remains the simplest and most effective way of leveraging DirectX for the broadest class of applications and games. Many developers seem to fall into two opposing camps. There are the hardcore DirectX developers who have cut their teeth on various versions of the DirectX API. They’re hardened by years of DirectX evolution and are happy to be in an exclusive club with a high bar of entry, a club that very few developers may join. In the other camp are those developers who heard the message that DirectX is hard and don’t want anything to do with it. They also tend to reject C++ as a matter of course.

I don’t belong to either of these camps. I believe C++ and DirectX don’t have to be hard. In last month’s column ( msdn.microsoft.com/magazine/dn198239), I introduced Direct2D 1.1 and the prerequisite Direct3D and DirectX Graphics Infrastructure (DXGI) code to create a device and manage a swap chain. The code for creating a Direct3D device with the D3D11CreateDevice function suitable for GPU or CPU rendering comes to roughly 35 lines of code. However, with the help of my little header file, it amounts to this:

auto device = CreateDevice();

The CreateDevice function returns a Device1 object. All of the Direct3D definitions are in the Direct3D namespace, so I could be more explicit and write it as follows:

Direct3D::Device1 device = Direct3D::CreateDevice();

The Device1 object is simply a wrapper around an ID3D11­Device1 COM interface pointer, the Direct3D device interface introduced with the DirectX 11.1 release. The Device1 class derives from the Device class, which in turn is a wrapper around the original ID3D11Device interface. It represents one reference and adds no additional overhead compared to just holding on to the interface pointer itself. Note that Device1 and its parent class, Device, are regular C++ classes rather than interfaces. You could think of them as smart pointers, but that would be overly simplistic. Sure, they handle reference counting and provide the “->” operator to directly call the method of your choosing, but they really begin to shine when you start using the many nonvirtual methods provided by the dx.h library.

Here’s an example. Often you might need the Direct3D device’s DXGI interface to pass to some other method or function. You could do it the hard way:

auto device = Direct3D::CreateDevice();
wrl::ComPtr<IDXGIDevice2> dxdevice;
HR(device->QueryInterface(dxdevice.GetAddressOf()));

That certainly works, but now you also have to deal with the DXGI device interface directly. You also need to remember that the IDXGIDevice2 interface is the DirectX 11.1 version of the DXGI device interface. Instead, you can simply call the AsDxgi method:

auto device = Direct3D::CreateDevice();
auto dxdevice = device.AsDxgi();

The resulting Device2 object, defined in the Dxgi namespace this time, wraps the IDXGIDevice2 COM interface pointer, providing its own set of nonvirtual methods. As a further example, you might want to use the DirectX “object model” to make your way to the DXGI factory:

auto device   = Direct3D::CreateDevice();
auto dxdevice = device.AsDxgi();
auto adapter  = dxdevice.GetAdapter();
auto factory  = adapter.GetParent();

This is, of course, such a common pattern that the Direct3D Device class provides the GetDxgiFactory method as a convenient shortcut:

auto d3device = Direct3D::CreateDevice();
auto dxfactory = d3device.GetDxgiFactory();

So, apart from a few convenience methods and functions such as GetDxgiFactory, the nonvirtual methods map one-to-one to the underlying DirectX interface methods and functions. That might not seem like much, but a number of techniques are combined to produce a far more convenient and productive programming model for DirectX. The first is the use of scoped enumerations. The DirectX family of APIs defines a dizzying array of constants, many of which are traditional enums, flags or constants. They aren’t strongly typed, they’re hard to find, and they don’t play well with Visual Studio IntelliSense. Ignoring the factory options for a minute, here’s what you need to create a Direct2D factory:

wrl::ComPtr<ID2D1Factory1> factory;
HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
                     factory.GetAddressOf()));

The D2D1CreateFactory function’s first parameter is an enum, but because it’s not a scoped enum, it’s hard to discover with Visual Studio IntelliSense. These old-school enums provide a bit of type safety, but not a whole lot. At best, you’ll be treated to an E_INVALIDARG result code at run time. I don’t know about you, but I’d rather catch such errors at compile time, or better yet, avoid them altogether:

auto factory = CreateFactory(FactoryType::MultiThreaded);

Again, I don’t need to spend time digging around to find out what the latest version of the Direct2D factory interface is called. Certainly, the biggest benefit here is productivity. Of course, the DirectX API is about more than creating and calling COM interface methods. Many plain old data structures are used to bundle up various properties and parameters. The description of a swap chain is a good example. With all of its confusing members, I can never quite remember how this structure should be prepared, not to mention the platform specifics. Here, again, the library comes in handy by providing a replacement for the intimidating DXGI_SWAP_CHAIN_DESC1 structure:

SwapChainDescription1 description;

In this case, binary-compatible replacements are provided to ensure that DirectX sees the same type, but you get to use something a little more practical. This isn’t unlike what the Microsoft .NET Framework provides with its P/Invoke wrappers. The default constructor provides suitable defaults for most desktop and Windows Store apps. You might, for example, want to override this for desktop apps to produce smoother rendering while resizing:

SwapChainDescription1 description;
description.SwapEffect = SwapEffect::Discard;

This swap effect, by the way, is also required when targeting Windows Phone 8, but it’s not permitted in Windows Store apps. Go figure.

Many of the best libraries lead you to a working solution quickly and easily. Let’s consider a concrete example. Direct2D provides a linear gradient brush. Creating such a brush involves three logical steps: defining the gradient stops, creating the gradient stop collection and then creating the linear gradient brush given that collection. Figure 1 shows what this might look like using the Direct2D API directly.

Figure 1 Creating a Linear Gradient Brush the Hard Way

D2D1_GRADIENT_STOP stops[] =
{
  { 0.0f, COLOR_WHITE },
  { 1.0f, COLOR_BLUE },
};
wrl::ComPtr<ID2D1GradientStopCollection> collection;
HR(target->CreateGradientStopCollection(stops,
   _countof(stops),
   collection.GetAddressOf()));
wrl::ComPtr<ID2D1LinearGradientBrush> brush;
HR(target->CreateLinearGradientBrush(D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES(),
   collection.Get(),
   brush.GetAddressOf()));

With the help of dx.h it becomes a whole lot more intuitive:

GradientStop stops[] =
{
  GradientStop(0.0f, COLOR_WHITE),
  GradientStop(1.0f, COLOR_BLUE),
};
auto collection = target.CreateGradientStopCollection(stops);
auto brush = target.CreateLinearGradientBrush(collection);

Although this isn’t dramatically shorter than the code in Figure 1, it’s certainly easier to write, and you’re far less likely to get it wrong the first time, especially with the help of IntelliSense. The library uses various techniques to produce a more enjoyable programming model. In this case, the CreateGradientStopCollection method is overloaded with a function template to deduce the size of the GradientStop array at compile time so there’s no need to use the _countof macro.

What about error handling? Well, one of the prerequisites of producing such a concise programming model is the adoption of exceptions for error propagation. Consider the definition of the Direct3D Device AsDxgi method I mentioned before:

inline auto Device::AsDxgi() const -> Dxgi::Device2
{
  Dxgi::Device2 result;
  HR(m_ptr.CopyTo(result.GetAddressOf()));
  return result;
}

This is a very common pattern in the library. First, notice that the method is const. Virtually all of the methods in the library are const, as the only data member is the underlying ComPtr and there’s no need to modify it. In the method body, you can see how the resulting Device object comes to life. All the library classes provide move semantics, so even though this might appear to perform a number of copies—and, by inference, a number of AddRef/Release pairs—there is in fact none of that happening at run time. The HR wrapping the expression in the middle is an inline function that throws an exception if the result is not S_OK. Finally, the library will always try to return the most specific class to avoid the caller having to perform additional calls to QueryInterface to expose more functionality.

The previous example used the ComPtr CopyTo method, which effectively just calls QueryInterface. Here’s another example from Direct2D:

inline auto BitmapBrush::GetBitmap() const -> Bitmap
{
  Bitmap result;
  (*this)->GetBitmap(result.GetAddressOf());
  return result;
}

This one is a little different in that it directly calls a method on the underlying COM interface. This pattern is in fact what constitutes the bulk of the library’s code. Here I’m returning the bitmap with which the brush paints. Many Direct2D methods return void, as is the case here, so there’s no need for the HR function to check the result. The indirection leading to the GetBitmap method might not be so obvious, however.

As I was prototyping early versions of the library, I had to make a choice between playing tricks with the compiler and playing tricks with COM. My early attempts involved playing tricks with C++ using templates, specifically type traits, but also compiler type traits (also known as intrinsic type traits). This was fun at first, but it quickly became apparent that I was making more work for myself.

You see, the library models the “is-a” relationship between COM interfaces as concrete classes. COM interfaces may only inherit directly from one other interface. With the exception of IUnknown itself, each COM interface must directly inherit from one other interface. Ultimately, this leads all the way back up the type hierarchy to IUnknown. I started by defining a class for each COM interface. The RenderTarget class contained an ID2D1RenderTarget interface pointer. The DeviceContext class contained an ID2D1DeviceContext interface pointer. This seems reasonable enough until you want to treat a DeviceContext as a RenderTarget. After all, the ID2D1DeviceContext interface is derived from the ID2D1RenderTarget interface. It would be quite reasonable to have a DeviceContext and want to pass it to a method that expects a RenderTarget as a reference parameter.

Unfortunately, the C++ type system doesn’t see it that way. Using this approach, DeviceContext can’t actually derive from RenderTarget or else it would hold two references. I tried a combination of move semantics and intrinsic type traits to correctly move the references around as needed. It almost worked, but there were cases where an extra AddRef/Release pair was being introduced. Ultimately, it proved too complex, and a simpler solution was needed.

Unlike C++, COM has a very well-defined binary contract. That is, after all, what COM is all about. As long as you stick to the agreed-upon rules, COM won’t let you down. You can play tricks with COM, so to speak, and use C++ to your advantage rather than fighting against it. This means that each C++ class isn’t holding a strongly typed COM interface pointer, but simply a generic reference to IUnknown. C++ adds the type safety back, and its rules for class inheritance—and, more recently, move semantics—let me once again treat these COM “objects” as C++ classes. Conceptually, I started with this:

class RenderTarget { ComPtr<ID2D1RenderTarget> ptr; };
class DeviceContext { ComPtr<ID2D1DeviceContext> ptr; };

And I ended up with this:

class Object { ComPtr<IUnknown> ptr; };
class RenderTarget : public Object {};
class DeviceContext : public RenderTarget {};

As the logical hierarchy implied by the COM interfaces and their relationships is now embodied by a C++ object model, the programming model as a whole is far more natural and usable. There’s a lot more to it, and I encourage you to take a closer look at the source code. As of this writing, it covers virtually all of Direct2D and the Windows Animation Manager, as well as useful chunks of DirectWrite, the Windows Imaging Component (WIC), Direct3D and DXGI. In addition, I’m regularly adding more functionality, so check back regularly. Enjoy!


Kenny Kerr is a computer programmer based in Canada, an author for Pluralsight and a Microsoft MVP. He blogs at kennykerr.ca and you can follow him on Twitter at twitter.com/kennykerr.

Thanks to the following technical expert for reviewing this article: Worachai Chaoweeraprasit (Microsoft)
Worachai Chaoweeraprasit (Microsoft), wchao@microsoft.com

Worachai Chaoweeraprasitis the development lead for Direct2D and DirectWrite. He's obsessed with speed and quality of 2D vector graphics as well as onscreen readability of text. In his free time he enjoys being cornered by the two little people at home.

 

Rate: