Windows 8で追加されたID2D1DeviceContextによるDirect2D描画を試してみたくて、最小のコードを作ってみました。

ウィンドウ作成にATLを使っています。初めはWTLも使っていましたが、ほんの少しだったので、使わないように修正しました。

#define UNICODE
#define _UNICODE
 
#include <cstdlib>
 
#include <windows.h>
#include <d3d11_1.h>
#include <dxgi1_2.h>
#include <d2d1_2.h>
#include <wrl/client.h>
 
#include <atlbase.h>
#include <atlwin.h>
#include <atltypes.h>
 
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d2d1.lib")
 
using Microsoft::WRL::ComPtr;
 
class TestWindow : public ATL::CWindowImpl<TestWindow>
{
public:
  // WM_ERASEBKGNDでのちらつきを抑えるため、-1としている。
  DECLARE_WND_CLASS_EX(L"Test Window Class", 0, -1);
 
  // WTLでは、コメントアウトしたほうのメッセージクラッカーが使える。
  BEGIN_MSG_MAP(TestWindow)
    MESSAGE_HANDLER(WM_SIZE, OnSize) //MSG_WM_SIZE(OnSize)
    MESSAGE_HANDLER(WM_PAINT, OnPaint) //MSG_WM_PAINT(OnPaint)
    MESSAGE_HANDLER(WM_CREATE, OnCreate) //MSG_WM_CREATE(OnCreate)
    MESSAGE_HANDLER(WM_DESTROY, OnDestroy) //MSG_WM_DESTROY(OnDestroy)
  END_MSG_MAP()
 
private:
  //void OnSize(UINT type, SIZE size)
  LRESULT OnSize(UINT, WPARAM type, LPARAM lp, BOOL&)
  {
    SIZE size{ GET_X_LPARAM(lp), GET_Y_LPARAM(lp) };
    if (type == SIZE_MINIMIZED || type == SIZE_MAXHIDE)
      return 0;
 
    DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
    auto hr = resource.m_dxgiSwapChain->GetDesc1(&swapChainDesc);
    if (SUCCEEDED(hr)
      && swapChainDesc.Width == size.cx && swapChainDesc.Height == size.cy)
    {
      return 0;
    }
    resource.m_d2dDeviceContext->SetTarget(nullptr);
 
    hr = resource.m_dxgiSwapChain->ResizeBuffers(
      swapChainDesc.BufferCount, 0, 0, DXGI_FORMAT_UNKNOWN, 0);
    if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
    {
      HandleDeviceLost();
    }
    InitializeRenderTarget();
 
    BOOL b{};
    OnPaint(0, 0, 0, b); // WTL使用時はOnPaint(nullptr);
    return 0;
  }
 
  //void OnPaint(HDC)
  LRESULT OnPaint(UINT, WPARAM, LPARAM, BOOL&)
  {
    OnRender();
    ValidateRect(nullptr);
    return 0;
  }
 
  void OnRender()
  {
    resource.m_d2dDeviceContext->BeginDraw();
    resource.m_d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Black));
 
    // 実際には、このブラシも、毎回作成せずメンバ変数にしたほうが良い。
    // 必須の処理ではないことをはっきりと示すため、わざとメンバ変数にしなかった。
    ComPtr<ID2D1SolidColorBrush> brush;
    resource.m_d2dDeviceContext->CreateSolidColorBrush(
      D2D1::ColorF(D2D1::ColorF::White), &brush);
 
    // 試しに円を描いてみる。
    RECT rc;
    GetClientRect(&rc);
    D2D1_ELLIPSE ellipse{
      {rc.right * 0.5f, rc.bottom * 0.5f},
      rc.right * 0.5f,
      rc.bottom * 0.5f
    };
    resource.m_d2dDeviceContext->DrawEllipse(ellipse, brush.Get(), 10.f);
 
    auto hr = resource.m_d2dDeviceContext->EndDraw();
    if (hr == D2DERR_RECREATE_TARGET)
    {
      HandleDeviceLost();
    }
    else if (SUCCEEDED(hr))
    {
      hr = resource.m_dxgiSwapChain->Present(1, 0);
      if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
      {
        HandleDeviceLost();
      }
    }
  }
 
  //LRESULT OnCreate(const CREATESTRUCT*)
  LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&)
  {
    if (FAILED(CreateDeviceIndependentResources()))
      return -1;
    if (FAILED(CreateDeviceResources()))
      return -1;
    return 0;
  }
 
  void HandleDeviceLost()
  {
    MessageBox(L"デバイスロストが発生しました。");
 
    ReleaseDeviceResources();
    CreateDeviceResources();
    // いったんメッセージループに戻したくて、OnRenderを直接呼ばず、こうしている。
    Invalidate();
  }
 
  //void OnDestroy()
  LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL&)
  {
    PostQuitMessage(0);
    ReleaseDeviceResources();
    return 0;
  }
 
  void ReleaseDeviceResources()
  {
    resource = {};
  }
 
  HRESULT Direct3DCreateDevice(
    D3D_DRIVER_TYPE driverType, _COM_Outptr_ ID3D11Device** ppDevice)
  {
    UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT
      | D3D11_CREATE_DEVICE_SINGLETHREADED;
#ifdef _DEBUG
    //flags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
 
    const D3D_FEATURE_LEVEL featureLevels[] =
    {
      D3D_FEATURE_LEVEL_11_1,
      D3D_FEATURE_LEVEL_11_0,
      D3D_FEATURE_LEVEL_10_1,
      D3D_FEATURE_LEVEL_10_0,
      D3D_FEATURE_LEVEL_9_3,
      D3D_FEATURE_LEVEL_9_2,
      D3D_FEATURE_LEVEL_9_1,
    };
    return D3D11CreateDevice(nullptr,
      driverType,
      nullptr,
      flags,
      featureLevels,
      ARRAYSIZE(featureLevels),
      D3D11_SDK_VERSION,
      ppDevice,
      nullptr,
      nullptr);
  }
 
  HRESULT Direct3DCreateDevice(_COM_Outptr_ ID3D11Device** ppDevice)
  {
    auto hr = Direct3DCreateDevice(D3D_DRIVER_TYPE_HARDWARE, ppDevice);
    if (SUCCEEDED(hr)) return hr;
    return Direct3DCreateDevice(D3D_DRIVER_TYPE_WARP, ppDevice);
  }
 
  HRESULT CreateDeviceResources()
  {
    ComPtr<ID3D11Device> device;
 
    auto hr = Direct3DCreateDevice(&device);
    if (FAILED(hr)) return hr;
    ComPtr<IDXGIDevice2> dxgiDevice;
    hr = device.As(&dxgiDevice);
    if (FAILED(hr)) return hr;
 
    ComPtr<ID2D1Device> d2d1Device;
    hr = m_d2dFactory->CreateDevice(dxgiDevice.Get(), &d2d1Device);
    if (FAILED(hr)) return hr;
 
    hr = d2d1Device->CreateDeviceContext(
      D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
      &resource.m_d2dDeviceContext);
    if (FAILED(hr)) return hr;
    ComPtr<IDXGIAdapter> dxgiAdapter;
    hr = dxgiDevice->GetAdapter(&dxgiAdapter);
    if (FAILED(hr)) return hr;
    ComPtr<IDXGIFactory2> dxgiFactory;
    hr = dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory));
    if (FAILED(hr)) return hr;
 
    DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
    swapChainDesc.Width = 0;
    swapChainDesc.Height = 0;
    swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    swapChainDesc.Stereo = FALSE;
    swapChainDesc.SampleDesc.Count = 1;
    swapChainDesc.SampleDesc.Quality = 0;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapChainDesc.BufferCount = 2;
    //swapChainDesc.Scaling = DXGI_SCALING_NONE;
    swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
    //swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
    swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
    hr = dxgiFactory->CreateSwapChainForHwnd(
      device.Get(),
      m_hWnd,
      &swapChainDesc,
      nullptr,
      nullptr,
      &resource.m_dxgiSwapChain);
    if (FAILED(hr)) return hr;
    (void)dxgiDevice->SetMaximumFrameLatency(1);
    hr = InitializeRenderTarget();
    if (FAILED(hr)) return hr;
    return S_OK;
  }
 
  HRESULT InitializeRenderTarget()
  {
    ComPtr<ID2D1Bitmap1> d2d1RenderTarget;
    ComPtr<IDXGISurface2> dxgiSurface;
    auto hr = resource.m_dxgiSwapChain->GetBuffer(
      0, IID_PPV_ARGS(&dxgiSurface));
    if (FAILED(hr)) return hr;
    hr = resource.m_d2dDeviceContext->CreateBitmapFromDxgiSurface(
      dxgiSurface.Get(),
      D2D1::BitmapProperties1(
        D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
        D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)),
      &d2d1RenderTarget);
    if (FAILED(hr)) return hr;
    resource.m_d2dDeviceContext->SetTarget(d2d1RenderTarget.Get());
    return S_OK;
  }
 
  HRESULT CreateDeviceIndependentResources()
  {
    D2D1_FACTORY_OPTIONS options = {};
#ifdef _DEBUG
    options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
    auto hr = D2D1CreateFactory(
      D2D1_FACTORY_TYPE_SINGLE_THREADED,
      options,
      m_d2dFactory.GetAddressOf());
    if (FAILED(hr)) return hr;
    return S_OK;
  }
 
  // デバイス非依存リソース
  ComPtr<ID2D1Factory1> m_d2dFactory;
  // デバイス依存リソース
  struct DeviceResources
  {
    ComPtr<IDXGISwapChain1> m_dxgiSwapChain;
    ComPtr<ID2D1DeviceContext> m_d2dDeviceContext;
  } resource;
};
 
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int cmdShow)
{
  CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
 
  TestWindow wnd;
  if (!wnd.Create(nullptr, ATL::CWindow::rcDefault,
    TEXT("Hello, world"), WS_OVERLAPPEDWINDOW))
  {
    std::quick_exit(-1);
  }
 
  wnd.ShowWindow(cmdShow);
  wnd.UpdateWindow();
 
  MSG msg;
  while (GetMessage(&msg, nullptr, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  std::quick_exit(static_cast<int>(msg.wParam));
}

例として、円を描く処理を入れています。

コメントアウトしているDXGI_SCALING_NONEとDXGI_SWAP_EFFECT_FLIP_SEQUENTIALは、Windows 8で追加されたものです。最初はそちらを使っていましたが、Windows 7で動かしてみた際に変更しました。

システムDPIを想定しています。manifestファイルを追加した上で、文字の大きさ150% (144 DPI)でも動かしてみてあります。Per-monitor DPI対応まではやっていません。

デバイスロストの処理は試せていません。デバイスロストの発生方法が分かりませんでした。ビデオカードのデバイスドライバのインストールや変更は試したのですが……。

以下、主に参考にしたウェブページです。

あと、主なAPIのMSDNライブラリに存在するリファレンスのページです。

より短いコードを目指すならD2D1CreateDeviceContext functionもあったのですが、今回は使いませんでした。ファクトリ(ID2D1Factory1)を毎回生成する点を気にしたためです。

2015年12月25日修正:デバイスロストの動作確認ができました。それにより、OnRenderメンバー関数を修正しました。Present関数の呼び出しをEndDraw関数が成功した場合のみに行うよう変更しました。


スポンサード リンク

この記事のカテゴリ

  • ⇒ D2Dデバイスコンテキストを使う最小サンプル
  • ⇒ D2Dデバイスコンテキストを使う最小サンプル
  • ⇒ D2Dデバイスコンテキストを使う最小サンプル