D2Dデバイスコンテキストを使う最小サンプル
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対応まではやっていません。
デバイスロストの処理は試せていません。デバイスロストの発生方法が分かりませんでした。ビデオカードのデバイスドライバのインストールや変更は試したのですが……。
以下、主に参考にしたウェブページです。
- How to render by using a Direct2D device context (MSDN Library)
- Windows with C++ – Introducing Direct2D 1.1 (MSDN Magazine)
- デスクトップアプリでDirect3D11+Direct2Dの初期化まとめ – ゆとりーなの日記
- ID2D1HwndRenderTargetとID2D1DeviceContext – sugarontopの日記
- ホイール欲しい ハンドル欲しい ≫ Direct3D 10 ウィンドウのリサイズ (ResizeBuffers)
あと、主なAPIのMSDNライブラリに存在するリファレンスのページです。
- D3D11CreateDevice(日本語)
- D3D11CreateDevice function
- ID2D1Factory1::CreateDevice method
- ID2D1Device::CreateDeviceContext method
- DXGI_SWAP_CHAIN_DESC1 structure
- IDXGIFactory2::CreateSwapChainForHwnd method
- ID2D1DeviceContext::CreateBitmapFromDxgiSurface method
より短いコードを目指すならD2D1CreateDeviceContext functionもあったのですが、今回は使いませんでした。ファクトリ(ID2D1Factory1)を毎回生成する点を気にしたためです。
2015年12月25日修正:デバイスロストの動作確認ができました。それにより、OnRenderメンバー関数を修正しました。Present関数の呼び出しをEndDraw関数が成功した場合のみに行うよう変更しました。
スポンサード リンク |