D3DImageとDirect2Dデバイスコンテキストを組み合わせるコードが全然見当たらなかったので、試しに書いてみました。

WPFのD3DImageは、そのままではDirect2Dでの描画を表示できるようにはできていません。

  • D3DImageはDirect3D 9サーフェースしか受け付けない。
  • Direct2DはDXGIサーフェース(つまりDirect3D 10以上)を使う。

対策として、Direct3D 9Exから追加された共有ハンドル (shared handle)を使います。これを使えば、Direct3D/DXGIのバージョンの壁を越えて、共有のリソースが作れます。今回はサーフェースを共有します。

ATL::CComPtr<IDirect3DDevice9Ex> d3d9Device;
// IDirect3D9Ex::CreateDeviceExでDirect3D 9Exデバイスを作っておく。
 
ATL::CComPtr<IDirect3DSurface9> renderTarget;
HANDLE hSharedResource = nullptr;
Marshal::ThrowExceptionForHR(d3d9Device->CreateRenderTarget(
  WIDTH, HEIGHT,
  D3DFMT_A8R8G8B8,
  D3DMULTISAMPLE_NONE,
  0,
  FALSE,
  &renderTarget,
  &hSharedResource));
 
ATL::CComPtr<ID3D11Device> d3d11Device;
// D3D11CreateDeviceでDirect3D 11デバイスを作っておく。
 
ATL::CComPtr<IDXGISurface2> dxgiSurface;
Marshal::ThrowExceptionForHR(d3d11Device->OpenSharedResource(
  hSharedResource,
  IID_PPV_ARGS(&dxgiSurface)));
 
// dxgiSurfaceに対して、Direct2Dで描画する。

書いていて気付いたことなどです。

  • 今回は、OpenSharedResourceの結果をIDXGISurface2で受け取っていますが、ID3D11Texture2Dとしても受け取れると思います。
  • 描画後(ID2D1DeviceContext::EndDraw呼び出し後)にID3D11DeviceContext::Flushを呼び出す必要があります。MSDNライブラリのID3D11Device::OpenSharedResourceにも書いてありますね。
  • D3D11CreateDeviceにおいてD3D_DRIVER_TYPE_WARPではダメでした。そのため、D3D_DRIVER_TYPE_HARDWAREのみとしています。

以下、コード全体です。C++/CLIです。お試しのつもりだったので、wWinMain関数に直接いろいろ書いています。

// cl /clr /EHa /MD wpfd2d.cpp /AI ^
// "%ProgramFiles(x86)%\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6"
 
#include <cstdlib>
 
#include <windows.h>
#include <d3d9.h>
#include <d3d11_1.h>
#include <dxgi1_2.h>
#include <d2d1_2.h>
 
#include <atlbase.h>
 
#using <System.Xaml.dll>
#using <WindowsBase.dll>
#using <PresentationCore.dll>
#using <PresentationFramework.dll>
 
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d2d1.lib")
 
using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Windows;
using namespace System::Windows::Controls;
using namespace System::Windows::Interop;
 
constexpr int WIDTH = 800;
constexpr int HEIGHT = 600;
 
void OnRenderImageCore(_Inout_ ID2D1DeviceContext* d2dDeviceContext)
{
  d2dDeviceContext->BeginDraw();
  d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::DarkGreen));
 
  ATL::CComPtr<ID2D1SolidColorBrush> brush;
  d2dDeviceContext->CreateSolidColorBrush(
    D2D1::ColorF(D2D1::ColorF::White), &brush);
 
  // 試しに円を描いてみる。
  D2D1_ELLIPSE ellipse{
    { WIDTH * 0.5f, HEIGHT * 0.5f },
    WIDTH * 0.5f,
    HEIGHT * 0.5f,
  };
  d2dDeviceContext->DrawEllipse(ellipse, brush, 10.f);
  D2D1_ELLIPSE ellipseS{
    { 100.f, 100.f },
    100.f,
    100.f,
  };
  d2dDeviceContext->DrawEllipse(ellipseS, brush, 10.f);
 
  Marshal::ThrowExceptionForHR(d2dDeviceContext->EndDraw());
}
 
[STAThread]
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
  auto w = gcnew Window;
  auto image = gcnew Image;
  auto d3dImage = gcnew D3DImage;
 
  image->Source = d3dImage;
  image->Width = WIDTH;
  image->Height = HEIGHT;
  w->SizeToContent = SizeToContent::WidthAndHeight;
  w->ResizeMode = ResizeMode::CanMinimize;
  w->Content = image;
 
  // 強制的にウィンドウを作る効果がある。
  // この次にウィンドウハンドルを取得するため、ウィンドウの作成が必要。
  w->Show();
 
  auto hwnd = static_cast<HWND>(safe_cast<HwndSource^>(
    PresentationSource::FromVisual(w))->Handle.ToPointer());
 
  ATL::CComPtr<IDirect3D9Ex> d3d9;
  Marshal::ThrowExceptionForHR(Direct3DCreate9Ex(DIRECT3D_VERSION, &d3d9));
 
  ATL::CComPtr<IDirect3DDevice9Ex> d3d9Device;
  D3DPRESENT_PARAMETERS d3dpp{};
  d3dpp.BackBufferWidth = 800;
  d3dpp.BackBufferHeight = 600;
  d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
  d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
  d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
  d3dpp.hDeviceWindow = hwnd;
  d3dpp.Windowed = TRUE;
  Marshal::ThrowExceptionForHR(d3d9->CreateDeviceEx(
    D3DADAPTER_DEFAULT,
    D3DDEVTYPE_HAL,
    hwnd,
    D3DCREATE_FPU_PRESERVE
    | D3DCREATE_MULTITHREADED
    | D3DCREATE_SOFTWARE_VERTEXPROCESSING
    | D3DCREATE_DISABLE_PRINTSCREEN,
    &d3dpp,
    nullptr,
    &d3d9Device));
 
  ATL::CComPtr<IDirect3DSurface9> renderTarget;
  HANDLE hSharedResource = nullptr;
  // 最初D3DFMT_X8R8G8B8にしたら、Direct2Dのところでエラーになった
  Marshal::ThrowExceptionForHR(d3d9Device->CreateRenderTarget(
    WIDTH, HEIGHT,
    D3DFMT_A8R8G8B8,
    D3DMULTISAMPLE_NONE,
    0,
    FALSE,
    &renderTarget,
    &hSharedResource));
 
  ATL::CComPtr<ID3D11Device> d3d11Device;
  ATL::CComPtr<ID3D11DeviceContext> d3d11ImmediateContext;
  UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
  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,
  };
  Marshal::ThrowExceptionForHR(D3D11CreateDevice(nullptr,
    D3D_DRIVER_TYPE_HARDWARE,
    nullptr,
    flags,
    featureLevels,
    ARRAYSIZE(featureLevels),
    D3D11_SDK_VERSION,
    &d3d11Device,
    nullptr,
    &d3d11ImmediateContext));
  ATL::CComQIPtr<IDXGIDevice2> dxgiDevice = d3d11Device;
  ATL::CComPtr<IDXGISurface2> dxgiSurface;
  Marshal::ThrowExceptionForHR(d3d11Device->OpenSharedResource(
    hSharedResource,
    IID_PPV_ARGS(&dxgiSurface)));
 
  ATL::CComPtr<ID2D1Factory1> d2dFactory;
  D2D1_FACTORY_OPTIONS options = {};
#ifdef _DEBUG
  options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
  Marshal::ThrowExceptionForHR(D2D1CreateFactory(
    D2D1_FACTORY_TYPE_MULTI_THREADED,
    options,
    &d2dFactory));
  ATL::CComPtr<ID2D1Device> d2dDevice;
  Marshal::ThrowExceptionForHR(d2dFactory->CreateDevice(
    dxgiDevice,
    &d2dDevice));
  ATL::CComPtr<ID2D1DeviceContext> d2dDeviceContext;
  Marshal::ThrowExceptionForHR(d2dDevice->CreateDeviceContext(
    D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
    &d2dDeviceContext));
  ATL::CComPtr<ID2D1Bitmap1> d2d1RenderTarget;
  Marshal::ThrowExceptionForHR(d2dDeviceContext->CreateBitmapFromDxgiSurface(
    dxgiSurface,
    D2D1::BitmapProperties1(
      D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
      D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)),
    &d2d1RenderTarget));
  d2dDeviceContext->SetTarget(d2d1RenderTarget);
 
  OnRenderImageCore(d2dDeviceContext);
 
  d3d11ImmediateContext->Flush();
 
  if (d3dImage->IsFrontBufferAvailable)
  {
    d3dImage->Lock();
    d3dImage->SetBackBuffer(
      D3DResourceType::IDirect3DSurface9, IntPtr(renderTarget));
    d3dImage->AddDirtyRect(
      Int32Rect(0, 0, d3dImage->PixelWidth, d3dImage->PixelHeight));
    d3dImage->Unlock();
  }
 
  auto app = gcnew Application;
  std::quick_exit(app->Run(w));
}

C++/CLIだとWRLが使えないんですね。Microsoft::WRL::ComPtrを使おうとしたら、インクルードしただけで#errorで引っかかるようになっていました。そのため、ATL::CComPtrを使っています。

以上、Direct2D 1.1→DXGI 1.2 (Direct3D 11.1)→Direct3D 9Ex→D3DImageという組み合わせのサンプルコードでした。


スポンサード リンク

この記事のカテゴリ