March 2013

Volume 28 Number 03

Windows with C++ - Rendering in a Desktop Application with Direct2D

By Kenny Kerr | March 2013

Kenny KerrIn my last column, I showed you how easy it actually is to create a desktop application with C++ without any library or framework. In fact, if you were feeling particularly masochistic, you could write an entire desktop application from within your WinMain function as I’ve done in Figure 1. Of course, that approach simply doesn’t scale.

 

 

Figure 1 The Masochist’s Window

int __stdcall wWinMain(HINSTANCE module, HINSTANCE, PWSTR, int)
{
  WNDCLASS wc = {};
  wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
  wc.hInstance = module;
  wc.lpszClassName = L"window";
  wc.lpfnWndProc = [] (HWND window, UINT message, WPARAM
    wparam, LPARAM lparam) -> LRESULT
  {
    if (WM_DESTROY == message)
    {
      PostQuitMessage(0);
      return 0;
    }
    return DefWindowProc(window, message, wparam, lparam);
  };
  RegisterClass(&wc);
  CreateWindow(wc.lpszClassName, L"Awesome?!",
    WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
    CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, module, nullptr);
  MSG message;
  BOOL result;
  while (result = GetMessage(&message, 0, 0, 0))
  {
    if (-1 != result) DispatchMessage(&message);
  }
}

I also showed how the Active Template Library (ATL) provides a nice C++ abstraction for hiding much of this machinery and how the Windows Template Library (WTL) takes this even further, primarily for applications heavily invested in the USER and GDI approaches to application development (see my February column at msdn.microsoft.com/magazine/jj891018).

The future of application rendering in Windows is hardware-­accelerated Direct3D, but that really is impractical to work with directly if all you want to do is render a two-dimensional application or game. That’s where Direct2D comes in. I introduced Direct2D briefly when it was first announced a few years back, but I’m going to spend the next few months taking a much closer look at Direct2D development. Check out my June 2009 column, “Introducing Direct2D” (msdn.microsoft.com/magazine/dd861344), for an introduction to the architecture and fundamentals of Direct2D.

One of the key design underpinnings of Direct2D is that it focuses on rendering and leaves the other aspects of Windows application development to you or other libraries that you might employ. Although Direct2D was designed to render in a desktop window, it’s up to you to actually provide this window and optimize it for Direct2D rendering. So this month, I’m going to focus on the unique relationship between Direct2D and the desktop application window. You can do many things to optimize the window handling and rendering process. You want to reduce unnecessary painting and avoid flicker, and just provide the best possible experience for the user. Of course, you’ll also want to provide a manageable framework within which to develop your application. I’ll be tackling these issues here.

The Desktop Window

In the ATL example last month, I gave the example of a window class deriving from the ATL CWindowImpl class template. Everything is nicely contained within the application’s window class. However, what ends up happening is that a lot of window and rendering plumbing ends up interspersed with the window’s application-specific rendering and event handling. To solve this problem, I tend to push as much of this boilerplate code as is practical into a base class, using compile-time polymorphism to reach up to the application’s window class when this base class needs its attention. This approach is used quite a lot by ATL and WTL, so why not extend that for your own classes?

Figure 2 illustrates this separation. The base class is the Desktop­Window class template. The template parameter gives the base class the ability to call up to the concrete class without the use of virtual functions. In this case, it’s using this technique to hide a bunch of render-specific pre- and post-processing while calling up to the application’s window to perform the actual drawing operations. I’ll expand on the DesktopWindow class template in a moment, but first its window class registration needs a bit of work.

Figure 2 The Desktop Window

template <typename T>
class DesktopWindow :
  public CWindowImpl<DesktopWindow<T>, CWindow,
    CWinTraits<WS_OVERLAPPEDWINDOW | WS_VISIBLE>>
{
  BEGIN_MSG_MAP(DesktopWindow)
    MESSAGE_HANDLER(WM_PAINT, PaintHandler)
    MESSAGE_HANDLER(WM_DESTROY, DestroyHandler)
  END_MSG_MAP()
  LRESULT DestroyHandler(UINT, WPARAM, LPARAM, BOOL &)
  {
    PostQuitMessage(0);
    return 0;
  }
  LRESULT PaintHandler(UINT, WPARAM, LPARAM, BOOL &)
  {
    PAINTSTRUCT ps;
    VERIFY(BeginPaint(&ps));
    Render();
    EndPaint(&ps);
    return 0;
  }
  void Render()
  {
    ...
    static_cast<T *>(this)->Draw();
    ...
  }
    ...
};
struct SampleWindow : DesktopWindow<SampleWindow>
{
  void Draw()
  {
    ...
  }
};

Optimizing the Window Class

One of the realities of the Windows API for desktop applications is that it was designed to simplify rendering with traditional USER and GDI resources. Some of these “conveniences” need to be disabled to allow Direct2D to take over, to avoid unnecessary painting leading to unsightly flicker. Other of these defaults must also be tweaked to work in a way that better suits Direct2D rendering. Much of this can be achieved by changing the window class information before it’s registered, but you may have noticed that ATL hides this from the programmer. Fortunately, there’s still a way to achieve this.

Last month, I showed how the Windows API expects a window class structure to be registered before a window is created based on its specification. One of the attributes of a window class is its background brush. Windows uses this GDI brush to clear the window’s client area before the window begins painting. This was convenient in the days of USER and GDI but is unnecessary and the cause of some flicker for Direct2D applications. A simple way to avoid this is by setting the background brush handle in the window class structure to a nullptr. If you develop on a relatively fast Windows 7 or Windows 8 computer, you might think this is unnecessary because you don’t notice any flicker. That’s just because the modern Windows desktop is composed so efficiently on the Graphics Processing Unit (GPU) that it’s hard to pick it up. However, it’s easy enough to strain the rendering pipeline to exaggerate the effect that you might experience on slower machines. If the window’s background brush is white and you then paint the window’s client area with a contrasting black brush, by adding a little sleep delay—anywhere between 10 ms and 100 ms—you’ll have no trouble picking up the striking flicker. So how to avoid it?

As I mentioned, if your window class registration lacks a background brush, then Windows won’t have any brush with which to clear your window. However, you may have noticed in the ATL examples that the window class registration is completely hidden. A common solution is to handle the WM_ERASEBKGND message that default window processing handles—courtesy of the DefWindowProc function—by painting the window’s client area with the window class background brush. If you handle this message, returning true, then no painting occurs. This is a reason­able solution, as this message is sent to a window regardless of whether the window class has a valid background brush or not. Another solution is to just avoid this no-op handler and remove the background brush from the window class in the first place. Fortunately, ATL makes it relatively simple to override this part of window creation. During creation, ATL calls the GetWndClassInfo method on the window to get this window class information. You can provide your own implementation of this method, but ATL provides a handy macro that implements it for you:

DECLARE_WND_CLASS_EX(nullptr, CS_HREDRAW | CS_VREDRAW, -1);

The last argument for this macro is meant to be a brush constant, but the -1 value tricks it into clearing this attribute of the window class structure. A surefire way to determine whether the window’s background has been erased is to check the PAINTSTRUCT filled in by the BeginPaint function inside your WM_PAINT handler. If its fErase member is false, then you know that Windows has cleared your window’s background, or at least that some code responded to the WM_ERASEBKGND message and purported to clear it. If the WM_ERASEBKGND message handler doesn’t or isn’t able to clear the background, then it’s up to the WM_PAINT message handler to do so. However, here we can employ Direct2D to completely take over the rendering of the window’s client area and avoid this double painting. Just be sure to call the EndPaint function, assuring Windows that you did indeed paint your window, otherwise Windows will continue to pester you with an unnecessary stream of WM_PAINT messages. This, of course, would hurt your application’s performance and increase overall power consumption.

The other aspect of the window class information that deserves our attention is the window class styles. This is, in fact, what the second argument to the preceding macro is for. The CS_HREDRAW and CS_VREDRAW styles cause the window to be invalidated every time the window is resized both vertically and horizontally. This certainly isn’t necessary. You could, for example, handle the WM_SIZE message and invalidate the window there, but I’m always glad when Windows will save me from writing a few extra lines of code. Either way, if you neglect to invalidate the window, then Windows won’t send your window any WM_PAINT messages when the window’s size is reduced. This might be fine if you’re happy for the window’s contents to be clipped, but it’s common these days to paint various window assets relative to the window’s size. Whatever you prefer, this is an explicit decision you need to make for your application window.

While I’m on the topic of the window background, it’s often desirable to invalidate a window explicitly. This allows you to keep your window’s rendering rooted in the WM_PAINT message rather than have to handle painting at different places and in different code paths through your application. You might want to paint something in response to a mouse click. You could, of course, do the rendering right there in the message handler. Alternatively, you could simply invalidate the window and let the WM_PAINT handler render the current state of the application. This is the role of the InvalidateRect function. ATL provides the Invalidate method that just wraps up this function. What often confuses developers about this function is how to deal with the “erase” parameter. Conventional wisdom seems to be that saying “yes” to erase will cause the window to be repainted immediately and saying “no” will defer this somehow. This isn’t true and the documentation says as much. Invalidating a window will cause it to be repainted promptly. The erase option is in lieu of the DefWindowProc function, which would normally clear the window background. If erase is true, then the subsequent call to BeginPaint will clear the window background. Here, then, is another reason for avoiding the window class background brush entirely rather than relying on a WM_ERASEBKGND message handler. Without a background brush, BeginPaint again has nothing to paint with, so the erase option has no effect. If you let ATL set a background brush for your window class, then you need to be careful when invalidating your window because this will again introduce flicker. I added this protected member to the DesktopWindow class template for this purpose:

void Invalidate()
{
  VERIFY(InvalidateRect(nullptr, false));
}

It’s also a good idea to handle the WM_DISPLAYCHANGE message to invalidate the window. This ensures that the window is properly repainted should something about the display change affect the window’s appearance.

Running the App

I like to keep my application’s WinMain function relatively simple. To achieve this goal, I added a public Run method to the DesktopWindow class template to hide the entire window and Direct2D factory creation, as well as the message loop. The DesktopWindow’s Run method is shown in Figure 3. This lets me write my application’s WinMain function quite simply:

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

Figure 3 The DesktopWindow Run Method

int 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"Direct2D"));
  MSG message;
  BOOL result;
  while (result = GetMessage(&message, 0, 0, 0))
  {
    if (-1 != result)
    {
      DispatchMessage(&message);
    }
  }
  return static_cast<int>(message.wParam);
}

Before creating the window, I prepare the Direct2D factory options by enabling the debug layer for debug builds. I highly recommend that you do the same, as it allows Direct2D to trace out all kinds of useful diagnostics as you develop your application. The D2D1CreateFactory function returns the factory interface pointer that I hand over to the Windows Runtime Library’s excellent ComPtr smart pointer, a protected member of the DesktopWindow class. I then call the CreateDeviceIndependentResources method to create any device-independent resources—things such as geometries and stroke styles that can be reused throughout the life of the application. Although I allow derived classes to override this method, I provide an empty stub in the DesktopWindow class template if this isn’t needed. Finally, the Run method concludes by blocking with a simple message loop. Check out last month’s column for an explanation of the message loop.

The Render Target

The Direct2D render target should be created on demand inside the Render method called as part of the WM_PAINT message handler. Unlike some of the other Direct2D render targets, it’s entirely possible that the device—a GPU in most cases—that provides the hardware-accelerated rendering for a desktop window can disappear or change in some way as to make any resources allocated by the render target invalid. Because of the nature of immediate mode rendering in Direct2D, the application is responsible for tracking what resources are device-specific and might need to be re-created from time to time. Fortunately, this is easy enough to manage. Figure 4 provides the complete DesktopWindow Render method.

Figure 4 The DesktopWindow Render Method

void Render()
{
  if (!m_target)
  {
    RECT rect;
    VERIFY(GetClientRect(&rect));
    auto size = SizeU(rect.right, rect.bottom);
    HR(m_factory->CreateHwndRenderTarget(RenderTargetProperties(),
      HwndRenderTargetProperties(m_hWnd, size),
      m_target.GetAddressOf()));
    static_cast<T *>(this)->CreateDeviceResources();
  }
  if (!(D2D1_WINDOW_STATE_OCCLUDED & m_target->CheckWindowState()))
  {
    m_target->BeginDraw();
    static_cast<T *>(this)->Draw();
    if (D2DERR_RECREATE_TARGET == m_target->EndDraw())
    {
      m_target.Reset();
    }
  }
}

The Render method begins by checking whether the ComPtr managing the render target’s COM interface is valid. In this way, it only re-creates the render target when necessary. This will happen at least once the first time the window is rendered. If something happens to the underlying device, or for whatever reason the render target needs to be re-created, then the EndDraw method toward the end of the Render method in Figure 4 will return the D2DERR_RECREATE_TARGET constant. The ComPtr Reset method is then used to simply release the render target. The next time the window is asked to paint itself, the Render method will go through the motions of creating a new Direct2D render target.

It starts by getting the client area of the window in physical pixels. Direct2D for the most part uses logical pixels exclusively to allow it to support high-DPI displays naturally. This is the point at which it initiates the relationship between the physical display and its logical coordinate system. It then calls the Direct2D factory to create the render target object. It’s at this point that it calls out to the derived application window class to create any device-specific resources—things such as brushes and bitmaps that are dependent on the device underlying the render target. Again, an empty stub is provided by the DesktopWindow class if this isn’t needed.

Before drawing, the Render method checks that the window is actually visible and not completely obstructed. This avoids any unnecessary rendering. Typically, this only happens when the underlying DirectX swap chain is invisible, such as when the user locks or switches desktops. The BeginDraw and EndDraw methods then straddle the call to the application window’s Draw method. Direct2D takes the opportunity to batch geometries in a vertex buffer, coalesce drawing commands, and so on to provide the greatest throughput and performance on the GPU.

The final critical step to integrate Direct2D properly with a desktop window is to resize the render target when the window is resized. I’ve already talked about how the window is automatically invalidated to ensure that it’s promptly repainted, but the render target itself has no idea that the window’s dimensions have changed. Fortunately, this is easy enough to do, as Figure 5 illustrates.

Figure 5 Resizing the Render Target

MESSAGE_HANDLER(WM_SIZE, SizeHandler)
LRESULT SizeHandler(UINT, WPARAM, LPARAM lparam, BOOL &)
{
  if (m_target)
  {
    if (S_OK != m_target->Resize(SizeU(LOWORD(lparam),
      HIWORD(lparam))))
    {
      m_target.Reset();
    }
  }
  return 0;
}

Assuming the ComPtr currently holds a valid render target COM interface pointer, the render target’s Resize method is called with the new size, as provided by the window message’s LPARAM. If for some reason the render target can’t resize all of its internal resources, then the ComPtr is simply reset, forcing the render target to be re-created the next time rendering is requested.

And that’s all I have room for in this month’s column. You now have everything you need to both create and manage a desktop window as well as to use the GPU to render into your application’s window. Join me next month 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
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.