July 2014

Volume 29 Number 7

Windows with C++ : Embracing the Windows Composition Engine

Kenny Kerr | July 2014

Kenny KerrThe Windows composition engine represents a departure from a world in which each DirectX app requires its own swap chain to one where even so fundamental a construct is unnecessary. Sure, you can continue to write Direct3D and Direct2D apps using a swap chain for presentation, but you no longer have to. The composition engine brings us that much closer to the metal—the GPU—by allowing apps to create composition surfaces directly.

The composition engine’s only objective is to compose different bitmaps together. You might request various effects, transforms and even animations to be produced, but at the end of the day it’s about composing bitmaps. The engine itself has no graphics-rendering capabilities such as those provided by Direct2D or Direct3D, and it doesn’t define vectors or text. What it cares about is composition. Give it a collection of bitmaps and it will do amazing things to combine and compose them together.

Those bitmaps can take any number of forms. A bitmap could actually be video memory. It could even be a swap chain, as I illustrated in my June column (msdn.microsoft.com/magazine/dn745861). But if you really want to begin embracing the composition engine, you need to take a look at composition surfaces. A composition surface is a bitmap provided directly by the composition engine and, as such, allows for certain optimizations that would be difficult to achieve with other forms of bitmaps.

This month, I’m going to take the alpha-blended window from my previous column and show you how it can be reproduced using a composition surface rather than a swap chain. There are some interesting benefits—and also some unique challenges, particularly around handling device loss and per-monitor DPI scaling. But first I need to revisit the issue of window management.

In my previous columns I’ve either used ATL for window management or illustrated how to register, create and pump window messages directly with the Windows API. There are pros and cons to both approaches. ATL continues to work just fine for window management, but it’s slowly losing developer mindshare and Microsoft certainly stopped investing in it long ago. On the other hand, creating a window directly with RegisterClass and CreateWindow tends to be problematic because you can’t easily associate a C++ object with a window handle. If you’ve ever thought of arranging such a union, you may have been tempted to take a peek at the ATL source code to see how this could be achieved, only to realize that there’s some dark magic to it with things called “thunks” and even assembly language.

The good news is that it needn’t be that hard. Though ATL certainly produces very efficient message dispatching, a simple solution involving only standard C++ will do the trick just fine. I don’t want to get too sidetracked with the mechanics of window procedures, so I’ll simply direct you to Figure 1, which provides a simple class template that makes the necessary arrangements to associate a “this” pointer with a window. The template uses the WM_NCCREATE message to extract the pointer and stores it with the window handle. It subsequently retrieves the pointer and sends messages to the most derived message handler.

Figure 1 A Simple Window Class Template

template <typename T>
struct Window
{
  HWND m_window = nullptr;
  static T * GetThisFromHandle(HWND window)
  {
    return reinterpret_cast<T *>(GetWindowLongPtr(window,
                                                  GWLP_USERDATA));
  }
  static LRESULT __stdcall WndProc(HWND   const window,
                                   UINT   const message,
                                   WPARAM const wparam,
                                   LPARAM const lparam)
  {
    ASSERT(window);
    if (WM_NCCREATE == message)
    {
        CREATESTRUCT * cs = reinterpret_cast<CREATESTRUCT *>(lparam);
        T * that = static_cast<T *>(cs->lpCreateParams);
        ASSERT(that);
        ASSERT(!that->m_window);
        that->m_window = window;
        SetWindowLongPtr(window,
                         GWLP_USERDATA,
                         reinterpret_cast<LONG_PTR>(that));
    }
    else if (T * that = GetThisFromHandle(window))
    {
      return that->MessageHandler(message,
                                  wparam,
                                  lparam);
    }
    return DefWindowProc(window,
                         message,
                         wparam,
                         lparam);
  }
  LRESULT MessageHandler(UINT   const message,
                         WPARAM const wparam,
                         LPARAM const lparam)
  {
    if (WM_DESTROY == message)
    {
      PostQuitMessage(0);
      return 0;
    }
    return DefWindowProc(m_window,
                         message,
                         wparam,
                         lparam);
  }
};

The assumption is that a derived class will create a window and pass this pointer as the last parameter when calling the Create­Window or CreateWindowEx functions. The derived class can simply register and create the window, and then respond to window messages with a MessageHandler override. This override relies on compile-time polymorphism so there’s no need for virtual functions. However, the effect is the same, so you still need to worry about reentrancy. Figure 2 shows a concrete window class that relies on the Window class template. This class registers and creates the window in its constructor, but it relies on the window procedure provided by its base class.

Figure 2 A Concrete Window Class

struct SampleWindow : Window<SampleWindow>
{
  SampleWindow()
  {
    WNDCLASS wc = {};
    wc.hCursor       = LoadCursor(nullptr, IDC_ARROW);
    wc.hInstance     = reinterpret_cast<HINSTANCE>(&__ImageBase);
    wc.lpszClassName = L"SampleWindow";
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    RegisterClass(&wc);
    ASSERT(!m_window);
    VERIFY(CreateWindowEx(WS_EX_NOREDIRECTIONBITMAP,
                          wc.lpszClassName,
                          L"Window Title",
                          WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          nullptr,
                          nullptr,
                          wc.hInstance,
                          this));
    ASSERT(m_window);
  }
  LRESULT MessageHandler(UINT message,
                         WPARAM const wparam,
                         LPARAM const lparam)
  {
    if (WM_PAINT == message)
    {
      PaintHandler();
      return 0;
    }
    return __super::MessageHandler(message,
                                   wparam,
                                   lparam);
  }
  void PaintHandler()
  {
    // Render ...
  }
};

Notice that in the constructor in Figure 2, the m_window inherited member is uninitialized (a nullptr) prior to calling CreateWindow, but initialized when this function returns. This may seem like magic but it’s the window procedure that hooks this up as messages begin arriving, long before CreateWindow returns. The reason it’s important to keep this in mind is that by using code such as this you can reproduce the same dangerous effect as calling virtual functions from a constructor. If you end up deriving further, just be sure to pull the window creation out of the constructor so that this form of reentrancy doesn’t trip you up. Here’s a simple WinMain function that can create the window and pump the window messages:

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
  SampleWindow window;
  MSG message;
  while (GetMessage(&message, nullptr, 0, 0))
  {
    DispatchMessage(&message);
  }
}

OK, back to the topic at hand. Now that I have a simple window class abstraction, I can use it to more easily manage the collection of resources needed to build a DirectX application. I’m also going to show you how to properly handle DPI scaling. While I covered DPI scaling in detail in my February 2014 column (msdn.microsoft.com/magazine/dn574798), there are some unique challenges when you combine it with the DirectComposition API. I’ll start at the top. I need to include the shell scaling API:

#include <ShellScalingAPI.h>
#pragma comment(lib, "shcore")

I can now begin to assemble the resources I need to bring my window to life. Given that I have a window class, I can simply make these members of the class. First, the Direct3D device:

ComPtr<ID3D11Device> m_device3d;

Next, the DirectComposition device:

ComPtr<IDCompositionDesktopDevice> m_device;

In my previous column I used the IDCompositionDevice inter­face to represent the composition device. That’s the interface that originated in Windows 8, but it has since been replaced in Windows 8.1 with the IDCompositionDesktopDevice interface, which derives from another new interface called IDComposition­Device2. These are not related to the original. The IDComposition­Device2 interface serves to create most of the composition resources and also controls transactional composition. The IDCompositionDesktopDevice interface adds the ability to create certain window-specific composition resources.

I’ll also need a composition target, visual and surface:

ComPtr<IDCompositionTarget>  m_target;
ComPtr<IDCompositionVisual2> m_visual;
ComPtr<IDCompositionSurface> m_surface;

The composition target represents the binding between the desktop window and a visual tree. I can actually associate two visual trees with a given window, but more on that in a future column. The visual represents a node in the visual tree. I’m going to explore visuals in a subsequent column, so for now there’ll be just a single root visual. Here, I’m just using the IDCompositionVisual2 interface, which derives from the IDCompositionVisual interface I used in my previous column. Finally, there’s the surface representing the content or bitmap associated with the visual. In my previous column I simply used a swap chain as the content of a visual, but in a moment I’m going to show you how to create a composition surface instead.

To illustrate how to actually render something and manage the rendering resources, I need a few more member variables:

ComPtr<ID2D1SolidColorBrush> m_brush;
D2D_SIZE_F                   m_size;
D2D_POINT_2F                 m_dpi;
SampleWindow() :
  m_size(),
  m_dpi()
{
  // RegisterClass / CreateWindowEx as before
}

The Direct2D solid color brush is quite inexpensive to create, but many other rendering resources are not so lightweight. I’ll use this brush to illustrate how to create rendering resources outside of the rendering loop. The DirectComposition API also optionally takes over the creation of the Direct2D render target. This allows you to target a composition surface with Direct2D, but it also means you lose a bit of contextual information. Specifically, you can no longer cache the applicable DPI scaling factor in the render target because DirectComposition creates it for you on demand. Also, you can no longer rely on the render target’s GetSize method to report the size of the window. But don’t worry, I’ll show you how to make up for these drawbacks in a moment.

As with any application that relies on a Direct3D device, I need to be careful to manage the resources that reside on the physical device, on the assumption the device may be lost at any moment. The GPU might hang, reset, be removed or simply crash. In addition, I need to be careful not to respond inappropriately to window messages that might arrive before the device stack is created. I’ll use the Direct3D device pointer to indicate whether the device has been created:

bool IsDeviceCreated() const
{
  return m_device3d;
}

This just helps to make the query explicit. I’ll also use this pointer to initiate a reset of the device stack to force all the device-dependent resources to be recreated:

void ReleaseDeviceResources()
{
  m_device3d.Reset();
}

Again, this just helps to make this operation explicit. I could release all of the device-dependent resources here, but that’s not strictly necessary and it can quickly become a maintenance headache as different resources are added or removed. The meat of the device creation process lives in another helper method:

void CreateDeviceResources()
{
  if (IsDeviceCreated()) return;
  // Create devices and resources ...
}

It’s here in the CreateDeviceResources method that I can create or recreate the device stack, the hardware device, and the various resources the window requires. First, I create the Direct3D device upon which everything else rests:

HR(D3D11CreateDevice(nullptr,    // Adapter
                     D3D_DRIVER_TYPE_HARDWARE,
                     nullptr,    // Module
                     D3D11_CREATE_DEVICE_BGRA_SUPPORT,
                     nullptr, 0, // Highest available feature level
                     D3D11_SDK_VERSION,
                     m_device3d.GetAddressOf(),
                     nullptr,    // Actual feature level
                     nullptr));  // Device context

Notice how the resulting interface pointer is captured by the m_device3d member. Now, I need to query for the device’s DXGI interface:

ComPtr<IDXGIDevice> devicex;
HR(m_device3d.As(&devicex));

In my previous column, it was at this point that I created the DXGI factory and swap chain to be used for composition. I created a swap chain, wrapped it in a Direct2D bitmap, targeted the bitmap with a device context and so on. Here, I’m going to do things quite differently. Having created the Direct3D device, I’m now going to create a Direct2D device pointing to it, and then create DirectComposition device pointing to the Direct2D device. Here’s the Direct2D device:

ComPtr<ID2D1Device> device2d;
HR(D2D1CreateDevice(devicex.Get(),
                    nullptr, // Default properties
                    device2d.GetAddressOf()));

I use a helper function provided by the Direct2D API instead of the more familiar Direct2D factory object. The resulting Direct2D device simply inherits the threading model from the DXGI device, but you can override that and turn on debug tracing as well. Here’s the DirectComposition device:

HR(DCompositionCreateDevice2(
   device2d.Get(),
   __uuidof(m_device),
   reinterpret_cast<void **>(m_device.ReleaseAndGetAddressOf())));

I’m careful to use the m_device member’s ReleaseAndGet­AddressOf method to support the recreation of the device stack after device loss. And given the composition device, I can now create the composition target as I did in my previous column:

HR(m_device->CreateTargetForHwnd(m_window,
                                 true, // Top most
                                 m_target.ReleaseAndGetAddressOf()));

And the root visual:

HR(m_device->CreateVisual(m_visual.ReleaseAndGetAddressOf()));

Now it’s time to focus on the composition surface that replaces the swap chain. In the same way the DXGI factory has no idea how big a swap chain’s buffers ought to be when I call the CreateSwapChainForComposition method, the DirectComposition device has no idea how big the underlying surface ought to be. I need to query the size of the window’s client area and use that information to inform the creation of the surface:

RECT rect = {};
VERIFY(GetClientRect(m_window,
                     &rect));

The RECT struct has left, top, right, and bottom members and I can use those to determine the desired size of the surface to be created using physical pixels:

HR(m_device->CreateSurface(rect.right - rect.left,
                           rect.bottom - rect.top,
                           DXGI_FORMAT_B8G8R8A8_UNORM,
                           DXGI_ALPHA_MODE_PREMULTIPLIED,
                           m_surface.ReleaseAndGetAddressOf()));

Keep in mind the actual surface may well be larger than the size requested. This is because the composition engine might pool or round allocations for greater efficiency. This isn’t a problem, but it does affect the resulting device context because you won’t be able to rely on its GetSize method, but more on that in a moment.

The parameters to the CreateSurface method are, thankfully, a simplification of the DXGI_SWAP_CHAIN_DESC1 structure’s many knobs and dials. Following the size, I specify the pixel format and alpha mode, and the composition device returns a pointer to the newly created composition surface. I can then simply set this surface as the content of my visual object and set the visual as the root of my composition target:

HR(m_visual->SetContent(m_surface.Get()));
HR(m_target->SetRoot(m_visual.Get()));

I do not, however, need to call the composition device’s Commit method at this stage. I’m going to be updating the composition surface in my rendering loop, but those changes will take effect only when the Commit method is called. At this point, the composition engine is ready for me to begin rendering, but I still have a few loose ends to tie up. They have nothing to do with composition, but everything to do with correctly and efficiently using Direct2D for rendering. First, any render target-specific resources such as bitmaps and brushes ought to be created outside of the rendering loop. This can be a little awkward because DirectComposition creates the render target. Fortunately, the only requirement is that these resources be created in the same address space as the eventual render target, so I can simply create a throw-away device context here in order to create such resources:

ComPtr<ID2D1DeviceContext> dc;
HR(device2d->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                                 dc.GetAddressOf()));

I can then use this render target to create the app’s single brush:

D2D_COLOR_F const color = ColorF(0.26f,
                                 0.56f,
                                 0.87f,
                                 0.5f);
HR(dc->CreateSolidColorBrush(color,
                             m_brush.ReleaseAndGetAddressOf()));

The device context is then discarded but the brush remains to be reused in my rendering loop. This is somewhat counterintuitive, but it will make sense in a moment. The last thing I need to do before rendering is to fill in those two member variables m_size and m_dpi. Traditionally, a Direct2D render target’s GetSize method provides the size of the render target in logical pixels, otherwise known as device-independent pixels. This logical size already accounts for the effective DPI, so I’ll deal with that first. As I illustrated in my February 2014 column about high-DPI apps, I can query the effective DPI for a particular window by first determining the monitor on which a given window predominantly resides and then getting the effective DPI for that monitor. Here’s what that looks like:

HMONITOR const monitor = MonitorFromWindow(m_window,
                     MONITOR_DEFAULTTONEAREST);
unsigned x = 0;
unsigned y = 0;
HR(GetDpiForMonitor(monitor,
                    MDT_EFFECTIVE_DPI,
                    &x,
                    &y));

I can then cache these values in my m_dpi member so I can easily update the device context provided by the DirectComposition API inside my rendering loop:

m_dpi.x = static_cast<float>(x);
m_dpi.y = static_cast<float>(y);

Now, calculating the logical size of the client area in logical pixels is a simple matter of taking the RECT structure that already holds the size in physical pixels and factoring in the effective DPI values I now have handy:

m_size.width  = (rect.right - rect.left) * 96 / m_dpi.x;
m_size.height = (rect.bottom - rect.top) * 96 / m_dpi.y;

And that concludes the CreateDeviceResources method and all it’s responsible for. You can see how it all comes together in Figure 3, which shows the CreateDeviceResources method in its entirety.

Figure 3 Creating the Device Stack

void CreateDeviceResources()
{
  if (IsDeviceCreated()) return;
  HR(D3D11CreateDevice(nullptr,    // Adapter
                       D3D_DRIVER_TYPE_HARDWARE,
                       nullptr,    // Module
                       D3D11_CREATE_DEVICE_BGRA_SUPPORT,
                       nullptr, 0, // Highest available feature level
                       D3D11_SDK_VERSION,
                       m_device3d.GetAddressOf(),
                       nullptr,    // Actual feature level
                       nullptr));  // Device context
  ComPtr<IDXGIDevice> devicex;
  HR(m_device3d.As(&devicex));
  ComPtr<ID2D1Device> device2d;
  HR(D2D1CreateDevice(devicex.Get(),
                      nullptr, // Default properties
                      device2d.GetAddressOf()));
  HR(DCompositionCreateDevice2(
     device2d.Get(),
     __uuidof(m_device),
     reinterpret_cast<void **>(m_device.ReleaseAndGetAddressOf())));
  HR(m_device->CreateTargetForHwnd(m_window,
                                   true, // Top most
                                   m_target.ReleaseAndGetAddressOf()));
  HR(m_device->CreateVisual(m_visual.ReleaseAndGetAddressOf()));
  RECT rect = {};
  VERIFY(GetClientRect(m_window,
                       &rect));
  HR(m_device->CreateSurface(rect.right - rect.left,
                             rect.bottom - rect.top,
                             DXGI_FORMAT_B8G8R8A8_UNORM,
                             DXGI_ALPHA_MODE_PREMULTIPLIED,
                             m_surface.ReleaseAndGetAddressOf()));
  HR(m_visual->SetContent(m_surface.Get()));
  HR(m_target->SetRoot(m_visual.Get()));
  ComPtr<ID2D1DeviceContext> dc;
  HR(device2d->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                                   dc.GetAddressOf()));
  D2D_COLOR_F const color = ColorF(0.26f,
                                   0.56f,
                                   0.87f,
                                   0.5f);
  HR(dc->CreateSolidColorBrush(color,
                               m_brush.ReleaseAndGetAddressOf()));
  HMONITOR const monitor = MonitorFromWindow(m_window,
                                             MONITOR_DEFAULTTONEAREST);
  unsigned x = 0;
  unsigned y = 0;
  HR(GetDpiForMonitor(monitor,
                      MDT_EFFECTIVE_DPI,
                      &x,
                      &y));
  m_dpi.x = static_cast<float>(x);
  m_dpi.y = static_cast<float>(y);
  m_size.width  = (rect.right - rect.left) * 96 / m_dpi.x;
  m_size.height = (rect.bottom - rect.top) * 96 / m_dpi.y;
}

Before implementing the message handlers, I need to override the Window class template’s MessageHandler to indicate which messages I’d like to handle. At a minimum, I need to handle the WM_PAINT message where I’ll provide the drawing commands; the WM_SIZE message where I’ll adjust the surface size; and the WM_DPICHANGED message where I’ll update the effective DPI and size of the window. Figure 4 shows the MessageHandler and, as you’d expect, it simply forwards the messages on to the appropriate handlers.

Figure 4 Dispatching Messages

LRESULT MessageHandler(UINT message,
                       WPARAM const wparam,
                       LPARAM const lparam)
{
  if (WM_PAINT == message)
  {
    PaintHandler();
    return 0;
  }
  if (WM_SIZE == message)
  {
    SizeHandler(wparam, lparam);
    return 0;
  }
  if (WM_DPICHANGED == message)
  {
    DpiHandler(wparam, lparam);
    return 0;
  }
  return __super::MessageHandler(message,
                                 wparam,
                                 lparam);
}

The WM_PAINT handler is where I create the device resources on-demand before entering the drawing sequence. Remember that CreateDeviceResources does nothing if the device already exists:

void PaintHandler()
{
  try
  {
    CreateDeviceResources();
    // Drawing commands ...
}

In this way, I can simply react to device loss by releasing the Direct3D device pointer through the ReleaseDeviceResources method, and the next time around the WM_PAINT handler will recreate it all. The entire process is enclosed in a try block so that any device failure can be handled reliably. To begin drawing to the composition surface, I need to call its BeginDraw method:

ComPtr<ID2D1DeviceContext> dc;
POINT offset = {};
HR(m_surface->BeginDraw(nullptr, // Entire surface
                        __uuidof(dc),
                        reinterpret_cast<void **>(dc.GetAddressOf()),
                        &offset));

BeginDraw returns a device context—the Direct2D render target—that I’ll use to batch up the actual drawing commands. The DirectComposition API uses the Direct2D device I originally provided when creating the composition device to create and return the device context here. I can optionally provide a RECT structure in physical pixels to clip the surface or specify a nullptr to allow unrestricted access to the drawing surface. The BeginDraw method also returns an offset, again in physical pixels, to indicate the origin of the intended drawing surface. This will not necessarily be the top-left corner of the surface and care must be taken to adjust or transform any drawing commands so they’re properly offset.

The composition surface also sports an EndDraw method and these two take the place of the Direct2D BeginDraw and EndDraw methods. You must not call the corresponding methods on the device context as the DirectComposition API takes care of this for you. Obviously, the DirectComposition API also ensures that the device context has the composition surface selected as its target. Moreover, it’s important you don’t hold on to the device context but release it promptly after drawing concludes. Also, the surface isn’t guaranteed to retain the contents of any previous frame that may have been drawn, so care needs to be taken to either clear the target or redraw every pixel before concluding.

The resulting device context is ready to go but doesn’t have the window’s effective DPI scaling factor applied. I can use the DPI values I previously calculated inside my CreateDeviceResources method to update the device context now:

dc->SetDpi(m_dpi.x,
           m_dpi.y);

I’ll also simply use a translation transformation matrix to adjust the drawing commands given the offset required by the DirectComposition API. I just need to be careful to translate the offset to logical pixels because that’s what Direct2D assumes:

dc->SetTransform(Matrix3x2F::Translation(offset.x * 96 / m_dpi.x,
                                         offset.y * 96 / m_dpi.y));

I can now clear the target and draw something app-specific. Here, I draw a simple rectangle with the device-dependent brush I created earlier in my CreateDeviceResources method:

dc->Clear();
D2D_RECT_F const rect = RectF(100.0f,
                              100.0f,
                              m_size.width - 100.0f,
                              m_size.height - 100.0f);
dc->DrawRectangle(rect,
                  m_brush.Get(),
                  50.0f);

I’m relying on the cached m_size member rather than any size reported by the GetSize method, as the latter reports the size of the underlying surface rather than the client area.

Concluding the drawing sequence involves a number of steps. First, I need to call the EndDraw method on the surface. This tells Direct2D to complete any batched drawing commands and write them to the composition surface. The surface is then ready to be composed—but not before the Commit method is called on the composition device. At that point, any changes to the visual tree, including any updated surfaces, are batched up and made available to the composition engine in a single transactional unit. This concludes the rendering process. The only remaining question is whether the Direct3D device has been lost. The Commit method will report any failure and the catch block will release the device. If all goes well, I can tell Windows that I’ve successfully painted the window by validating the window’s entire client area with the ValidateRect function. Otherwise, I need to release the device. Here’s what this might look like:

// Drawing commands ...
  HR(m_surface->EndDraw());
  HR(m_device->Commit());
  VERIFY(ValidateRect(m_window, nullptr));
}
catch (ComException const & e)
{
  ReleaseDeviceResources();
}

I don’t need to repaint explicitly, because Windows will simply continue to send WM_PAINT messages if I don’t respond by validating the client area. The WM_SIZE handler is responsible for adjusting the size of the composition surface and also for updating the cached size of the render target. I won’t react if the device isn’t created or the window is minimized:

void SizeHandler(WPARAM const wparam,
                 LPARAM const lparam)
{
  try
  {
    if (!IsDeviceCreated()) return;
    if (SIZE_MINIMIZED == wparam) return;
    // ...
}

A window typically receives a WM_SIZE message before it’s had an opportunity to create the device stack. When that happens, I simply ignore the message. I also ignore the message if the WM_SIZE message is a result of a minimized window. I don’t want to unnecessarily adjust the size of the surface in that case. As with the WM_PAINT handler, the WM_SIZE handle encloses its operations in a try block. Resizing, or in this case recreating, the surface may well fail due to device loss and this ought to result in the device stack being recreated. But, first, I can extract the new size of the client area:

unsigned const width  = LOWORD(lparam);
unsigned const height = HIWORD(lparam);

And update the cached size in logical pixels:

m_size.width  = width  * 96 / m_dpi.x;
m_size.height = height * 96 / m_dpi.y;

The composition surface isn’t resizable. I’m using what might be called a non-virtual surface. The composition engine also offers virtual surfaces that are resizable but I’ll talk more about that in an upcoming column. Here, I can simply release the current surface and recreate it. Because changes to the visual tree aren’t reflected until changes are committed, the user won’t experience any flickering while the surface is discarded and recreated. Here’s what this might look like:

HR(m_device->CreateSurface(width,
                           height,
                           DXGI_FORMAT_B8G8R8A8_UNORM,
                           DXGI_ALPHA_MODE_PREMULTIPLIED,
                           m_surface.ReleaseAndGetAddressOf()));
HR(m_visual->SetContent(m_surface.Get()));

I can then respond to any failure by releasing the device resources so that the next WM_PAINT message will cause them to be recreated:

// ...
}
catch (ComException const & e)
{
  ReleaseDeviceResources();
}

That’s it for the WM_SIZE handler. The final necessary step is to implement the WM_DPICHANGED handler to update the effective DPI and size of the window. The message’s WPARAM provides the new DPI values and the LPARAM provides the new size. I simply update the window’s m_dpi member variable and then call the SetWindowPos method to update the window’s size. The window will then receive another WM_SIZE message that my WM_SIZE handler will use to adjust the m_size member and recreate the surface. Figure 5 provides an example of how to handle these WM_DPICHANGED messages.

Figure 5 Handling DPI Updates

void DpiHandler(WPARAM const wparam,
                LPARAM const lparam)
{
  m_dpi.x = LOWORD(wparam);
  m_dpi.y = HIWORD(wparam);
  RECT const & rect = *reinterpret_cast<RECT const *>(lparam);
  VERIFY(SetWindowPos(m_window,
                      0, // No relative window
                      rect.left,
                      rect.top,
                      rect.right - rect.left,
                      rect.bottom - rect.top,
                      SWP_NOACTIVATE | SWP_NOZORDER));
}

I’m happy to see the members of the DirectX family drawing closer together with improved interoperability and performance, thanks to deep integration between Direct2D and DirectComposition. I hope you’re as excited as I am about the possibilities for building rich native apps with DirectX.


Kenny Kerr is a computer programmer based in Canada, as well as 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 Microsoft technical experts for reviewing this article: Leonardo Blanco and James Clarke