マルチスレッド Direct2D アプリ
Direct2D アプリ 開発する場合は、複数のスレッドから Direct2D リソースにアクセスすることが必要になる場合があります。 また、マルチスレッドを使用してパフォーマンスを向上させたり、応答性を向上させたりすることもできます (画面表示に 1 つのスレッドを使用し、オフライン レンダリングに別のスレッドを使用する場合など)。
このトピックでは、Direct3D レンダリングをほとんどまたはまったく しないマルチスレッド Direct2D アプリを開発するためのベスト プラクティスについて説明します。 コンカレンシーの問題に起因するソフトウェアの欠陥は追跡が難しい場合があり、マルチスレッド ポリシーを計画し、ここで説明するベスト プラクティスに従うと便利です。
手記
2 つの異なるシングル スレッド Direct2D ファクトリから作成された 2 つの Direct2D リソースにアクセスする場合、基になる Direct3D デバイスとデバイス コンテキストも異なる限り、アクセスの競合は発生しません。 この記事の「Direct2D リソースへのアクセス」とは、特に明記されていない限り、"同じ Direct2D デバイスから作成された Direct2D リソースへのアクセス" を意味します。
Direct2D API のみを呼び出す Thread-Safe アプリの開発
Direct2D ファクトリ インスタンス マルチスレッドを作成できます。 マルチスレッド ファクトリとそのすべてのリソースを複数のスレッドから使用して共有できますが、(Direct2D 呼び出しを介して) それらのリソースへのアクセスは Direct2D によってシリアル化されるため、アクセスの競合は発生しません。 アプリが Direct2D API のみを呼び出す場合、このような保護は、最小限のオーバーヘッドで細かいレベルで Direct2D によって自動的に行われます。 ここでマルチスレッド ファクトリを作成するコード。
ID2D1Factory* m_D2DFactory;
// Create a Direct2D factory.
HRESULT hr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_MULTI_THREADED,
&m_D2DFactory
);
次の図は、Direct2D が、Direct2D API のみを使用して呼び出しを行う 2 つのスレッドをシリアル化する方法を示しています。
最小限の Direct3D または DXGI 呼び出しで Direct2D アプリを Thread-Safe 開発する
多くの場合、Direct2D アプリでは、Direct3D または DXGI 呼び出し 行われます。 たとえば、表示スレッドは Direct2D で描画され、DXGI スワップ チェーンを使用して表示されます。
この場合、スレッド セーフの保証はより複雑になります。一部の Direct2D 呼び出しは、Direct3D または DXGI を呼び出す別のスレッドによって同時にアクセスされる可能性がある、基になる Direct3D リソースに間接的にアクセスします。 これらの Direct3D または DXGI の呼び出しは Direct2D の認識と制御から外れているため、マルチスレッドの Direct2D ファクトリを作成する必要がありますが、アクセスの競合を回避するには操作を行う必要があります。
次の図は、スレッド T0 が Direct2D 呼び出しを介して間接的にリソースにアクセスし、T2 が Direct3D または DXGI 呼び出しを介して同じリソースに直接アクセスするため、Direct3D リソース アクセスの競合を しています。
スレッド保護図
ここでリソース アクセスの競合を回避するには、Direct2D が内部アクセス同期に使用 ロックを明示的に取得し、次に示すように、スレッドが Direct3D または DXGI 呼び出し 行う必要があるときにそのロックを適用することをお勧めします。 特に、HRESULT リターン コードに基づく例外または早期アウト システムを使用するコードには特別な注意を払う必要があります。 このため、RAII (リソース取得は初期化) パターンを使用して、Enter メソッドを呼び出し、Leave メソッドを呼び出すことをお勧めします。
このコードは、Direct3D または DXGI 呼び出し ロックしてからロック解除するタイミングの例を示しています。
void MyApp::DrawFromThread2()
{
// We are accessing Direct3D resources directly without Direct2D's knowledge, so we
// must manually acquire and apply the Direct2D factory lock.
ID2D1Multithread* m_D2DMultithread;
m_D2DFactory->QueryInterface(IID_PPV_ARGS(&m_D2DMultithread));
m_D2DMultithread->Enter();
// Now it is safe to make Direct3D/DXGI calls, such as IDXGISwapChain::Present
MakeDirect3DCalls();
// It is absolutely critical that the factory lock be released upon
// exiting this function, or else any consequent Direct2D calls will be blocked.
m_D2DMultithread->Leave();
}
手記
Direct3D または DXGI 呼び出し (特に IDXGISwapChain::P 存在) によっては、呼び出し元の関数またはメソッドのコードに対してロックを取得したり、コールバックをトリガーしたりする場合があります。 これを認識し、このような動作がデッドロックを引き起こしていないことを確認する必要があります。 詳細については、「DXGI の概要」 トピックを参照してください。
direct2d と direct3d のスレッド ロック図を
Enter を使用し、Leave メソッドを使用すると、呼び出しは自動 Direct2D と明示的なロックによって保護されるため、アプリはアクセスの競合にヒットしません。
この問題を回避するには、他にも方法があります。 ただし、Direct3D または DXGI 呼び出し、Direct2D ロックを使用して明示的に保護することをお勧めします。これは、通常、Direct2D のカバーの下でより細かいレベルでコンカレンシーを保護し、オーバーヘッドが低くなるため、パフォーマンスが向上するためです。
ステートフル操作の原子性の確保
DirectX のスレッド セーフ機能は、2 つの個別の API 呼び出しが同時に行われないようにするのに役立ちますが、ステートフル API 呼び出しを行うスレッドが相互に干渉しないようにする必要もあります。 例を次に示します。
- 画面上 (スレッド 0) とオフスクリーン (スレッド 1) の両方にレンダリングする 2 行のテキストがあります。行 #1 は "A が大きい" で、行 #2 は "B より" であり、どちらも黒の実線ブラシを使用して描画されます。
- スレッド 1 は、テキストの最初の行を描画します。
- スレッド 0 はユーザー入力に反応し、テキスト行をそれぞれ "B が小さい" と "A より小さい" に更新し、ブラシの色を独自の描画用に赤色に変更しました。
- スレッド 1 は、2 行目のテキストを引き続き描画します。これは、現在は "A より" で、赤いカラー ブラシを使用します。
- 最後に、画面外の描画ターゲットに 2 行のテキストが表示されます。"A is greater" (黒)、"than A" (赤) です。
スクリーン スレッドのオンとオフの図を
一番上の行では、スレッド 0 は現在のテキスト文字列と現在の黒いブラシで描画します。 スレッド 1 は、上半分の画面外描画のみを終了します。
中央の行では、スレッド 0 はユーザーの操作に応答し、テキスト文字列とブラシを更新してから、画面を更新します。 この時点で、スレッド 1 はブロックされます。 下の行では、スレッド 1 の後の最終的な画面外レンダリングは、変更されたブラシと変更されたテキスト文字列を使用して下半分の描画を再開します。
この問題に対処するには、スレッドごとに個別のコンテキストを用意することをお勧めします。
- レンダリング時に変更可能なリソース (例のテキスト コンテンツや純色ブラシなど、表示または印刷中に変化する可能性があるリソース) が変更されないように、デバイス コンテキストのコピーを作成する必要があります。 このサンプルでは、描画する前に、これら 2 行のテキストとカラー ブラシのコピーを保持する必要があります。 これにより、各スレッドが描画および提示する完全で一貫性のあるコンテンツを持っていることを保証できます。
- 重み付けリソース (ビットマップや複雑なエフェクト グラフなど) を共有する必要があります。このリソースは 1 回初期化された後、スレッド間で変更されることはないため、パフォーマンスを向上させる必要があります。
- 1 回初期化され、スレッド間で変更されない軽量リソース (単色ブラシやテキスト形式など) を共有できます。
概要
マルチスレッド Direct2D アプリを開発する場合は、マルチスレッド Direct2D ファクトリを作成し、そのファクトリからすべての Direct2D リソースを派生させる必要があります。 スレッドが Direct3D または DXGI 呼び出し 行う場合は、Direct3D または DXGI 呼び出しを保護するために Direct2D ロックを明示的に適用する必要もあります。 さらに、スレッドごとに変更可能なリソースのコピーを用意することで、コンテキストの整合性を確保する必要があります。