Direct3D 11.0とDirect2D/DirectWriteの連携を試してみました。
1. Direct3DとDirect2Dにまつわる面倒な話
Direct3Dは3DCGを高速に描画します。Direct3Dには2D専用の機能がなく、文字列表示もありません。Direct3D 9にはDirect3D Extensionとして文字列を描画するID3DXFontインターフェイスが用意されましたが、Direct3D 10で廃止となりました。
Direct3D 10からはDirect3Dの構造が大幅に変更されました。3D処理に直接関係のない各種バッファやディスプレイ・GPUの管理などをDXGIが管理することになりました。GDIやDirect3Dなどの描画コマンドは、全てDXGIを経由し、カーネルモードグラフィックスドライバに伝えられます。
Direct3D 10.1と同時にDirect2DとDirectWriteが登場しました。Direct2DはGDI+などを置き換えるもので、GPUを使った高速なレンダリングに対応しています。DirectWriteはDirect2Dの一機能で、GPUを使って高速かつ高品質に文字列をレンダリングします。
Direct2Dは内部的にはDirect3D 10.1を使っています。よって、Direct3D 10.1とDirect2D/DirectWriteの相互運用は簡単です。しかし、Direct3D 11.0と連携させようとすると、DXGIを何度も経由しなければなりません。つまり、Direct3D 10.1リソース→DXGIリソース→Direct3D 11.0サーフェイスという面倒な変換を噛まさなければなりません。しかもDirect3D 10.1とDirect3D 11.0は完全に独立して動くため、DXGIの同期機構を使って同期を取らなければなりません。
WPF + Direct2D のサンプル(サーフェイスの共有)に書いてあるように、Direct2DとDirect3D 10.1あるいはGDIとの相互運用パスは用意されていますが、Direct3D 11ではDXGIを経由しなければなりません。
めまいのする話はまだ続きます。DXGIを経由したリソースのやり取りでは、同期機構を使うことを知らせるフラグ(D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX)を立てる必要があります。しかし、スワップチェイン生成時にこのフラグを立てることができないため、レンダーターゲットをDirect3D 11.0とDirect3D 10.1(Direct2D)の間で受け渡しすることができません。つまり、Direct2Dの描画先となるテクスチャをDirect3Dで別に用意し、最終的なレンダリングを行う前にDirect3DとDirect2Dのレンダリング結果を自分でブレンドさせなければなりません。
Microsoftが提供するDirect2D/DirectWriteを使うソフト(Internet Explorer 9など)もDirect3D 10.1を使っているようで、Direct3D 11は蚊帳の外でした。
2. DXGI 1.2の登場と相互運用性の改善
この残念すぎる仕様は、Windows 8の登場とともに大きく改善されました。Windows 8ではDirect3D 11.1、DXGI 1.2、Direct2D 1.1とDirectWrite 1.1(新API名を参考に筆者独自の命名でありMicrosoft公式の記述ではありません)が新たに提供されましたが、Direct2DとDirect3D 10.1以外のDirect3Dともう少し簡単に連携できるようになりました。Windows Store Appを意識した新設計のため(特にDXGI 1.2の)APIが少し複雑になり、絶対的なソースコードの行数が大幅に減るわけではありませんが、同期機構が不要になるなど、使い勝手が良くなりました。
が、Direct3D 11.1など(厳密に言えばWDDM 1.2)はWindows 7に提供されないという噂が流れました。ところが先日、Microsoft公式ブログのDirectX 11.1 and Windows 7という記事で、WDDM 1.2に依存するAPIの提供が公言されました(WDDM 1.2自体には今の所対応しません)。Internet Explorer 10などがWDDM 1.2のAPIに依存しているため、フレームワークだけでも提供しないとIE10がWindows 7で動かないという問題が背景にあるようです。Windows 7でDirect3D 11.1の全ての新機能が使えるわけではありませんが、DXGIはWDDMの下位互換性があるため、WDDM 1.1までの範囲内である「制限されたモード」の下でWindows 8と同じAPIを使うことができます。
3. Windows 7で新APIに対応するための手順
Platform Update(KB 2670838)を当てることで各種コンポーネントがアップデートされます。しかし、現在はβ版のようです。筆者環境では正常に動作しました(Intel G45、GeForce 9800GTX+、GeForce 560Ti、AMD Switchable Graphics)。コントロールパネルからのアンインストールも可能です。
4. 副作用
DXGI 1.2になることで、いままでDirectX SDKに付属していたPIX(Direct3Dのためのデバッグツール)は使えなくなります。代わりに、VisualStudio 2012に付属するグラフィックスデバッグを使います。
しかし、グラフィックスデバッグはExpress版にはありません。前例から考えて、Platrform UpdateはIE10の自動配布と同時に行われると思われるので、アップデートせずPIXを使い続けるのは無理でしょう。実質有料化です。失望しました。Professional版は約6万円です(アカデミック版もなし。学生はDreamSparkがおすすめです)。
なお、Express版でもビルド・実行はできます。また、Direct3D 9のデバッグはVisualStudioでは提供されておらず、PIXが引き続き利用できます。
5. サンプルプログラム
長いのでpastebinに貼りました。一部力尽きているのはご愛嬌。
なお、DXGI 1.1で同期を使う方法は、Direct3Dのレンダリング結果と合成するにはシェーダを使わなければなりませんが、面倒だったのでDirect2Dのレンダリング結果をコピーして誤魔化しています。
DXGI 1.1で同期を使う方法(D3DD2DInteropMutex)
必要最小限の手順をざっと書くと次のような感じです。
- HWNDを指定してDirect3D 11デバイスとスワップチェインの作成
- DXGIアダプタの取得
- Direct3D 10.1デバイスの作成
- 共有用のDirect3D 11 2Dテクスチャの作成
- そのテクスチャからIDXGIKeyedMutexとIDXGIResourceの取得
- IDXGIResourceから共有ハンドルのオープン
- 共有ハンドルからDirect3D 10.1デバイスに紐づけられたDXGIサーフェイスの作成
- DXGIサーフェイスからIDXGIKeyedMutexの取得
- Direct2DのレンダーターゲットにそのDXGIサーフェイスを指定
- IDXGIKeyedMutex->AquireSync()とReleaseSync()を使って同期を取りながら描画
- Direct2Dのレンダリング結果とDirect3Dのレンダリング結果を合成(サンプルではケチりました)
DXGI 1.2で同期を使わない方法(D3DD2DInterop)
こちらの手順も書いておきます
- Direct3D 11デバイスの作成
- DXGIデバイスの取得
- そのDXGIデバイスを指定してDirect2DデバイスとDirect2Dデバイスコンテクストの作成
- DXGIアダプタの取得
- HWNDを指定してスワップチェインの作成
- そのスワップチェインからDXGIサーフェイスの作成
- そのDXGIサーフェイスからDirect2Dビットマップを作成
- Direct2Dデバイスコンテクストのレンダリング先にそのDirect2Dビットマップを指定
- 普通に描画
後者の方法では、前者の方法より取得すべきもの(IDXGIKeyedMutexとか)が大きく減っており、面倒くさい感じが幾分か抑えられていると思います。
なお、デバイスロストは考慮していません(忘れていました(:P)。
6. 既知の問題
同期を使う方法(面倒くさい方法)では、グラフィックスデバッグもPIXもバグってしまうようです。共有サーフェイス周辺でコケますが、原因は分かりません。Microsoft公式の共有サーフェイスのサンプルでもデバッグできなかったので、仕様なのかもしれません。
同期を使わない方法(あまり面倒くさくない方法)も、Windows7上でのグラフィックスデバッグではコケます。原因不明です。当たり前ですがPIXはDXGI 1.2に非対応なのでダメです。しかし、WDDM 1.2ドライバがインストールされたWindows 8(GeForce 9800GTX+)ではデバッグできました。
まとめると、Direct3DとDirect2D/DirectWriteとの連携を使うグラフィックスデバッグでは、同期を使わない方法で、Windows 8とVisualStudio 2012 Professinal以上とWDDM 1.2ドライバが必須という状況です。通常の実行はいずれも問題ありません。
対処方法をご存じの方は、コメントを入れてもらえると助かります。
そういえば、NVIDIA Parallel Nsightのグラフィックスデバッガだとどうなるんでしょうね。気が向いたら試します。
7. 余談
もう文字列のテクスチャ貼り付けでいいような気がしてきた
■参考文献
- Surface Sharing Between Windows Graphics APIs (Windows)
- Devices and device contexts (Windows)
- Direct2D と Direct3D 11 の共有方法
おしり
