May 2013

Volume 28 Number 05

Windows with C++ - Introducing Direct2D 1.1

By Kenny Kerr | May 2013

Kenny KerrWindows 8 launched with a major new version of Direct2D. Since then, Direct2D has made it into Windows RT (for ARM devices) and Windows Phone 8, both of which are based on this latest version of Windows. Support for Direct2D on the phone OS isn’t yet official, but it’s just a matter of time. What about Windows 7? A platform update is being prepared to bring the latest version of the DirectX family of APIs to Windows 7. It includes the latest versions of Direct2D, Direct3D, DirectWrite, the Windows Imaging Component, the Windows Animation Manager and so on. A major driver for this is, of course, Internet Explorer. Anywhere you find Internet Explorer 9 or higher, you’ll find Direct2D. By the time you read this, it’s likely that Internet Explorer 10 will be available on Windows 7 and that, too, will require Direct2D 1.1 to be installed as a matter of course. Given its ubiquity, there’s really no reason not to use Direct2D 1.1. But what is Direct2D 1.1 and how can you start using it? That’s the topic of this month’s column.

Direct2D 1.1 might sound like a minor version update, and in some ways, it is. It doesn’t fundamentally change the API. Everything you know about Direct2D continues to be every bit as relevant today. It’s still modeled around device-specific and device-independent resources, render targets, geometries, brushes and so on. But in version 1.1, Direct2D grows up. The original version of Direct2D that launched with Windows 7 was in some ways an outsider to DirectX. It lagged behind, being tied to DirectX 10 rather than 11, the version of DirectX that it launched with. Even though it provided an excellent interop story for GDI and the Windows Imaging Component, it didn’t provide the best possible experience for working with DirectX itself. It wasn’t bad, but in Direct2D 1.1 things get a whole lot better. Direct3D and Direct2D are now in many ways siblings in the DirectX family. Thanks to this greater parity, even more of the graphics processing unit (GPU) is now available to the Direct2D developer without the need to jump out of the 2D abstraction. Moreover, when you do need to make the leap, it’s both simple and efficient.

In my last column (msdn.microsoft.com/magazine/jj991972), I showed how you could use ID2D1HwndRenderTarget, the Direct2D HWND render target, in a desktop window. This mechanism continues to be supported by Direct2D 1.1 and is still the simplest way to get started with Direct2D, as it hides all of the underlying Direct3D and DirectX Graphics Infrastructure (DXGI) plumbing. To take advantage of the improvements in Direct2D 1.1 you need to eschew this render target, however, and instead opt for ID2D1DeviceContext, the new device context render target. At first, this might sound like something from Direct3D, and in some ways, it is. Like the HWND render target, the Direct2D device context inherits from the ID2D1RenderTarget interface and is thus very much a render target in the traditional sense, but it’s a whole lot more powerful. Creating it, however, is a bit more involved but well worth the effort, as it provides many new features and is the only way to use Direct2D with the Windows Runtime (WinRT). Therefore, if you want to use Direct2D in your Windows Store or Windows Phone apps, you’ll need to embrace the Direct2D device context. In this month’s column I’ll show you how to use the new render target in a desktop app. Next month, I’ll show you how the render target works with the Windows Runtime—this has more to do with the Windows Runtime than it does with Direct2D. The bulk of what you need to learn has to do with managing the Direct3D device, the swap chain, buffers and resources.

The nice thing about the original Direct2D HWND render target was that you really didn’t need to know anything about Direct3D or DirectX to get stuff done. That’s no longer the case. Fortunately, there isn’t a whole lot you need to know, as DirectX can certainly be daunting. DirectX is really a family of closely related APIs, of which Direct3D is the most well-known, while Direct2D is starting to steal some attention thanks to its relative ease of use and incredible power. Along the way, different parts of DirectX have come and gone. One relatively new member of the family is DXGI, which debuted with Direct3D 10. DXGI provides common GPU resource management facilities across the various DirectX APIs. Bridging the gap between Direct2D and Direct3D involves DXGI. The same goes for bridging the gap between Direct3D and the desktop’s HWND or the WinRT CoreWindow. DXGI provides the glue that binds them all together.

Headers and Other Glue

As I’ve done in the past, I’ll continue to use the Active Template Library (ATL) on the desktop just to keep the examples concise. You can use your own library or no library at all. It really doesn’t matter. I covered these options in my February 2013 column (msdn.microsoft.com/magazine/jj891018). To begin, you need to include the necessary Visual C++ libraries:

#include <wrl.h>
#include <atlbase.h>
#include <atlwin.h>

The Windows Runtime C++ Template Library (WRL) provides the handy ComPtr smart pointer, and the ATL headers are there for managing the desktop window. Next, you need the latest DirectX headers:

#include <d2d1_1.h>
#include <d3d11_1.h>

The first one is the new Direct2D 1.1 header file. The Direct3D 11.1 header is needed for device management. To keep things simple I’ll assume the WRL and Direct2D namespaces are as follows:

using namespace Microsoft::WRL;
using namespace D2D1;

Next, you need to tell the linker how to resolve the factory functions you’ll be using:

#pragma comment(lib, "d2d1")
#pragma comment(lib, "d3d11")

I tend to avoid talking about error handling. As with so much of C++, the developer has many choices for how errors can be handled. This flexibility is in many ways what draws me, and many others, to C++, but it can be divisive. Still, I get many questions about error handling, so to avoid any confusion, Figure 1 shows what I rely on for error handling in desktop apps.

Figure 1 Error Handling

#define ASSERT ATLASSERT
#define VERIFY ATLVERIFY
#ifdef _DEBUG
#define HR(expression) ASSERT(S_OK == (expression))
#else
struct ComException
{
  HRESULT const hr;
  ComException(HRESULT const value) : hr(value) {}
};
inline void HR(HRESULT const hr)
{
  if (S_OK != hr) throw ComException(hr);
}
#endif

The ASSERT and VERIFY macros are just defined in terms of the corresponding ATL macros. If you’re not using ATL, then you could just use the C Run-Time Library (CRT) _ASSERTE macro instead. Either way, assertions are vital as a debugging aid. VERIFY checks the result of an expression but only asserts in debug builds. In release builds the ASSERT expression is stripped out entirely while the VERIFY expression remains. The latter is useful as a sanity check for functions that must execute but shouldn’t fail short of some apocalyptic event. Finally, HR is a macro that ensures the expression—typically a COM-style function or interface method—is successful. In debug builds, it asserts so as to quickly pinpoint the line of code in a debugger. In release builds, it throws an exception to quickly force the application to crash. This is particularly handy if your application uses Windows Error Reporting (WER), as the offline crash dump will pinpoint the failure for you.

The Desktop Window

Now it’s time to start framing up the DesktopWindow class. First, I’ll define a base class to wrap up much of the boilerplate windowing plumbing. Figure 2 provides the initial structure.

Figure 2 Desktop Window Skeleton

template <typename T>
struct DesktopWindow :
  CWindowImpl<DesktopWindow<T>,
              CWindow,
              CWinTraits<WS_OVERLAPPEDWINDOW | WS_VISIBLE>>
{
  ComPtr<ID2D1DeviceContext> m_target;
  ComPtr<IDXGISwapChain1> m_swapChain;
  ComPtr<ID2D1Factory1> m_factory;
  DECLARE_WND_CLASS_EX(nullptr, 0, -1);
  BEGIN_MSG_MAP(c)
    MESSAGE_HANDLER(WM_PAINT, PaintHandler)
    MESSAGE_HANDLER(WM_SIZE, SizeHandler)
    MESSAGE_HANDLER(WM_DISPLAYCHANGE, DisplayChangeHandler)
    MESSAGE_HANDLER(WM_DESTROY, DestroyHandler)
  END_MSG_MAP()
  LRESULT PaintHandler(UINT, WPARAM, LPARAM, BOOL &)
  {
    PAINTSTRUCT ps;
    VERIFY(BeginPaint(&ps));
    Render();
    EndPaint(&ps);
    return 0;
  }
  LRESULT DisplayChangeHandler(UINT, WPARAM, LPARAM, BOOL &)
  {
    Render();
    return 0;
  }
  LRESULT SizeHandler(UINT, WPARAM wparam, LPARAM, BOOL &)
  {
    if (m_target && SIZE_MINIMIZED != wparam)
    {
      ResizeSwapChainBitmap();
      Render();
    }
    return 0;
  }
  LRESULT DestroyHandler(UINT, WPARAM, LPARAM, BOOL &)
  {
    PostQuitMessage(0);
    return 0;
  }
  void Run()
  {
    D2D1_FACTORY_OPTIONS fo = {};
    #ifdef _DEBUG
    fo.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
    #endif
    HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
                         fo,
                         m_factory.GetAddressOf()));
    static_cast<T *>(this)->CreateDeviceIndependentResources();
    VERIFY(__super::Create(nullptr, nullptr, L"Introducing Direct2D 1.1"));
    MSG message;
    BOOL result;
    while (result = GetMessage(&message, 0, 0, 0))
    {
      if (-1 != result)
      {
        DispatchMessage(&message);
      }
    }
  }
};

DesktopWindow is a class template, as I rely on static or compile-­time polymorphism to call the app’s window class for drawing and resource management at appropriate points. Again, I’ve already described the mechanics of ATL and desktop windows in my February 2013 column, so I won’t repeat that here. The main thing to note is that the WM_PAINT, WM_SIZE and WM_DISPLAYCHANGE messages are all handled with a call to a Render method. The WM_SIZE message also calls out to a ResizeSwapChainBitmap method. These hooks are needed to let DirectX know what’s happening with your window. I’ll describe what these do in a moment. Finally, the Run method creates the standard Direct2D factory object, retrieving the new ID2D1Factory1 interface in this case, and optionally lets the app’s window class create device-independent resources. It then creates the HWND itself and enters the message loop. The app’s wWinMain function is then a simple two-liner:

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
  SampleWindow window;
  window.Run();
}

Creating the Device

So far, most of what I’ve described has been a recap of what I’ve shown in previous columns for window management. Now I’ve come to the point where things get very different. The HWND render target did a lot of work for you to hide the underlying DirectX plumbing. Being a render target, the Direct2D device context still delivers the results of drawing commands to a target, but the target is no longer the HWND—rather, it’s a Direct2D image. This image is an abstraction, which can literally be a Direct2D bitmap, a DXGI surface or even a command list to be replayed in some other context.

The first thing to do is to create a Direct3D device object. Direct3D defines a device as something that allocates resources, renders primitives and communicates with the underlying graphics hardware. The device consists of a device object for managing resources and a device-context object for rendering with those resources. The D3D11CreateDevice function creates a device, optionally returning pointers to the device object and device-context object. Figure 3 shows what this might look like. I don’t want to bog you down with Direct3D minutiae, so I won’t describe every option in detail but instead will focus on what’s relevant to Direct2D.

Figure 3 Creating a Direct3D Device

HRESULT CreateDevice(D3D_DRIVER_TYPE const type, 
  ComPtr<ID3D11Device> & device)
{
  UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
  #ifdef _DEBUG
  flags |= D3D11_CREATE_DEVICE_DEBUG;
  #endif
  return D3D11CreateDevice(nullptr,
                           type,
                           nullptr,
                           flags,
                           nullptr, 0,
                           D3D11_SDK_VERSION,
                           device.GetAddressOf(),
                           nullptr,
                           nullptr);
}

This function’s second parameter indicates the type of device that you’d like to create. The D3D_DRIVER_TYPE_HARDWARE constant indicates that the device should be backed by the GPU for hardware-accelerated rendering. If a GPU is unavailable, then the D3D_DRIVER_TYPE_WARP constant may be used. WARP is a high-performance software rasterizer and is a great fallback. You shouldn’t assume that a GPU is available, especially if you’d like to run or test your software in constrained environments such as Hyper-V virtual machines (VMs). Here’s how you might use the function from Figure 3 to create the Direct3D device:

 

ComPtr<ID3D11Device> d3device;
auto hr = CreateDevice(D3D_DRIVER_TYPE_HARDWARE, d3device);
if (DXGI_ERROR_UNSUPPORTED == hr)
{
  hr = CreateDevice(D3D_DRIVER_TYPE_WARP, d3device);
}
HR(hr);

Figure 3 also illustrates how you can enable the Direct3D debug layer for debug builds. One thing to watch out for is that the D3D11CreateDevice function will mysteriously fail if the debug layer isn’t actually installed. This won’t be a problem on your development machine, because Visual Studio would’ve installed it along with the Windows SDK. If you happen to copy a debug build onto a test machine, you might bump into this problem. This is in contrast to the D2D1CreateFactory function, which will still succeed even if the Direct2D debug layer isn’t present.

Creating the Swap Chain

The next step is to create a DXGI swap chain for the application’s HWND. A swap chain is a collection of buffers used for displaying frames to the user. Typically, there are two buffers in the chain, often called the front buffer and the back buffer, respectively. The GPU presents the image stored in the front buffer while the application renders into the back buffer. When the application is done rendering it asks DXGI to present the back buffer, which basically swaps the pointers to the front and back buffers, allowing the GPU to present the new frame and the application to render the next frame. This is a gross simplification, but it’s all you need to know for now.

To create the swap chain you first need to get hold of the DXGI factory and retrieve its IDXGIFactory2 interface. You can do so by calling the CreateDXGIFactory1 function, but given that you’ve just created a Direct3D device object, you can also use the DirectX object model to make your way there. First, you need to query for the device object’s IDXGIDevice interface:

ComPtr<IDXGIDevice> dxdevice;
HR(d3device.As(&dxdevice));

Next, you need to retrieve the display adapter, virtual or otherwise, for the device:

ComPtr<IDXGIAdapter> adapter;
HR(dxdevice->GetAdapter(adapter.GetAddressOf()));

The adapter’s parent object is the DXGI factory:

ComPtr<IDXGIFactory2> factory;
HR(adapter->GetParent(__uuidof(factory),
                      reinterpret_cast<void **>(factory.GetAddressOf())));

This might seem like a lot more work than simply calling the CreateDXGIFactory1 function, but in a moment you’re going to need the IDXGIDevice interface, so it’s really just one extra method call.

The IDXGIFactory2 interface provides the CreateSwapChainForHwnd method for creating a swap chain for a given HWND. Before calling it, you need to prepare a DXGI_SWAP_CHAIN_DESC1 structure describing the particular swap chain structure and behavior that you’d like. A bit of care is needed when initializing this structure, as it’s what most distinguishes the various platforms on which you’ll find Direct2D. Here’s what it might look like:

DXGI_SWAP_CHAIN_DESC1 props = {};
props.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
props.SampleDesc.Count = 1;
props.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
props.BufferCount = 2;

The Format member describes the desired format for the swap chain buffers. I’ve chosen a 32-bit format with 8 bits for each color channel and alpha component. Overall, this provides the best performance and compatibility across devices and APIs. The SampleDesc member affects Direct3D image quality, as it relates to antialiasing. Generally, you’ll want Direct2D to handle antialiasing, so this configuration merely tells Direct3D not to do anything. The BufferUsage member describes how the swap chain buffers will be used and allows DirectX to optimize memory management. In this case I’m indicating that the buffer will be used only for rendering output to the screen. This means that the buffer won’t be accessible from the CPU, but the performance will be greatly improved as a result. The BufferCount member indicates how many buffers the swap chain will contain. This is typically no more than two to conserve memory, but it may be more for exceedingly high-speed rendering (although that’s rare). In fact, to conserve memory, Windows Phone 8 allows only a single buffer to be used. There are a number of other swap chain members, but these are the only ones required for a desktop window. Now create the swap chain:

HR(factory->CreateSwapChainForHwnd(d3device.Get(),
                                   m_hWnd,
                                   &props,
                                   nullptr,
                                   nullptr,
                                   m_swapChain.GetAddressOf()));

The first parameter indicates the Direct3D device where the swap chain resources will be allocated, and the second is the target HWND where the front buffer will be presented. If all goes well, the swap chain is created. One thing to keep in mind is that only a single swap chain can be associated with a given HWND. This might seem obvious, or not, but it’s something you need to watch out for. If this method fails, it’s likely that you failed to release device-specific resources before re-creating the device after a device-loss event.

Creating the Direct2D Device

The next step is to create the Direct2D device. Direct2D 1.1 is modeled more closely on Direct3D in that instead of simply having a render target, it now has both a device and device context. The device is a Direct2D resource object that’s linked to a particular Direct3D device. Like the Direct3D device, it serves as a resource manager. Unlike the Direct3D device context, which you can safely ignore, the Direct2D device context is the render target that exposes the drawing commands, which you’ll need. The first step is to use the Direct2D factory to create the device that’s bound to the underlying Direct3D device via its DXGI interface:

ComPtr<ID2D1Device> device;
HR(m_factory->CreateDevice(dxdevice.Get(),
                           device.GetAddressOf()));

This device represents the display adapter in Direct2D. I typically don’t hold on to the ID2D1Device pointer, because that makes it simpler to handle device loss and swap chain buffer resizing. What you really need it for is to create the Direct2D device context:

HR(device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                               m_target.GetAddressOf()));

What you now have is a Direct2D render target that you can use to batch up drawing commands as usual, but there’s still one more step before you can do so. Unlike the HWND render target, which could only ever target the window for which it was created, the device context can switch targets at run time and initially has no target set at all.

Connecting the Device Context and Swap Chain

The next step is to set the swap chain’s back buffer as the target of the Direct2D device context. The swap chain’s GetBuffer method will return the back buffer as a DXGI surface:

ComPtr<IDXGISurface> surface;
HR(m_swapChain->GetBuffer(0, // buffer index
             __uuidof(surface),
             reinterpret_cast<void **>(surface.GetAddressOf())));

You can now use the device context’s CreateBitmapFromDxgi­Surface method to create a Direct2D bitmap to represent the DXGI surface, but first you need to describe the bitmap’s format and intended use. You can define the bitmap properties as follows:

auto props = BitmapProperties1(
  D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
  PixelFormat(
    DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE));

The D2D1_BITMAP_OPTIONS_TARGET constant indicates that the bitmap will be used as the target of a device context. The D2D1_BITMAP_OPTIONS_CANNOT_DRAW constant relates to the swap chain’s DXGI_USAGE_RENDER_TARGET_OUTPUT attribute, indicating that it can be used only as an output and not as an input to other drawing operations. PixelFormat just describes to Direct2D what the underlying swap chain buffer looks like. You can now create a Direct2D bitmap to point to the swap chain back buffer and point the device context to this bitmap:

ComPtr<ID2D1Bitmap1> bitmap;
HR(m_target->CreateBitmapFromDxgiSurface(surface.Get(),
                                         props,
                                         bitmap.GetAddressOf()));
m_target->SetTarget(bitmap.Get());

Finally, you need to tell Direct2D how to scale its logical coordinate system to the physical display embodied by the DXGI surface:

float dpiX, dpiY;
m_factory->GetDesktopDpi(&dpiX, &dpiY);
m_target->SetDpi(dpiX, dpiY);

Rendering

The DesktopWindow Render method acts as the catalyst for much of the Direct2D device management. Figure 4 provides the basic outline of the Render method.

Figure 4 The DesktopWindow Render Method

void Render()
{
  if (!m_target)
  {
    CreateDevice();
    CreateDeviceSwapChainBitmap();
  }
  m_target->BeginDraw();
  static_cast<T *>(this)->Draw();
  m_target->EndDraw();
  auto const hr = m_swapChain->Present(1, 0);
  if (S_OK != hr && DXGI_STATUS_OCCLUDED != hr)
  {
    ReleaseDevice();
  }
}

As with the HWND render target, the Render method first checks whether the render target needs to be created. The CreateDevice method contains the Direct3D device, DXGI swap chain and Direct2D device context creation. The CreateDeviceSwapChainBitmap method contains the code to connect the swap chain to the device context by means of a DXGI surface-backed Direct2D bitmap. The latter is kept separate because it’s needed during window resizing.

The Render method then follows the usual pattern of bracketing draw commands with the BeginDraw and EndDraw methods. Notice that I don’t bother to check the result of the EndDraw method. Unlike the HWND render target, the device context’s EndDraw method doesn’t actually present the newly drawn frame to the screen. Instead, it merely concludes rendering to the target bitmap. It’s the job of the swap chain’s Present method to present this to the screen, and it’s at this point that any rendering and presentation issues can be handled.

Because I’m only using a simple event-driven rendering model for this window, the presentation is straightforward. If I were using an animation loop for synchronized rendering at the display’s refresh frequency, things would get a lot more complicated, but I’ll cover that in a future column. In this case, there are three scenarios to deal with. Ideally, Present returns S_OK and all is well. Alternatively, Present returns DXGI_STATUS_OCCLUDED indicating the window is occluded, meaning it’s invisible. This is increasingly rare, as desktop composition relies on the window’s presentation to remain active. One source of occlusion, however, is when the active desktop is switched. This happens most often if a User Account Control (UAC) prompt appears or the user presses Ctlr+Alt+Del to switch users or lock the computer. At any rate, occlusion doesn’t mean failure, so there’s nothing you need to do except perhaps avoid extra rendering calls. If Present fails for any other reason, then it’s safe to assume that the underlying Direct3D device has been lost and must be re-created. The DesktopWindow’s ReleaseDevice method might look like this:

void ReleaseDevice()
{
  m_target.Reset();
  m_swapChain.Reset();
  static_cast<T *>(this)->ReleaseDeviceResources();
}

Here’s where you can start to understand why I avoid holding on to any unnecessary interface pointers. Every resource pointer represents a reference directly or indirectly to the underlying device. Each one must be released in order for the device to be properly re-created. At a minimum, you need to hold on to the render target (so that you can actually issue drawing commands) and the swap chain (so that you can present). Related to this—and the final piece of the puzzle—is the ResizeSwapChainBitmap method I alluded to inside the WM_SIZE message handler.

The HWND render target made this simple with its Resize method. Because you’re now in charge of the swap chain, it’s your responsibility to resize its buffers. Of course, this will fail unless all references to these buffers have been released. Assuming you aren’t directly holding on to any, this is simple enough. Figure 5 shows you how.

Figure 5 Resizing the Swap Chain

void ResizeSwapChainBitmap()
{
  m_target->SetTarget(nullptr);
  if (S_OK == m_swapChain->ResizeBuffers(0,
                                         0, 0,
                                         DXGI_FORMAT_UNKNOWN,
                                         0))
  {
    CreateDeviceSwapChainBitmap();
  }
  else
  {
    ReleaseDevice();
  }
}

In this case, the only reference to the swap chain’s back buffer that the DesktopWindow holds is the one held indirectly by the device context render target. Setting this to a nullptr value releases that final reference so that the ResizeBuffers method will succeed. The various parameters just tell the swap chain to resize the buffers based on the window’s new size and keep everything else as it was. Assuming ResizeBuffers succeeds, I simply call the CreateDeviceSwapChainBitmap method to create a new Direct2D bitmap for the swap chain and hook it up to the Direct2D device context. If anything goes wrong, I simply release the device and all of its resources; the Render method will take care of re-creating it all when the time comes.

You now have everything you need to render in a desktop window with Direct2D 1.1! And that’s all I have room for this month. Join me next time as I continue to explore Direct2D.


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)