マルチスレッド 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 API のみを使用して呼び出しを行う 2 つのスレッドを Direct2D がシリアル化する方法を示しています。

2 つのシリアル化されたスレッドの図。

Direct3D または DXGI 呼び出しを最小限に抑えたThread-Safe Direct2D アプリの開発

Direct2D アプリが Direct3D または DXGI の呼び出しを行うことがよくあります。 たとえば、ディスプレイ スレッドは Direct2D で描画され、 DXGI スワップ チェーンを使用して表示されます。

この場合、スレッド セーフの保証はより複雑になります。一部の Direct2D 呼び出しは、基になる Direct3D リソースに間接的にアクセスします。これは、 Direct3D または DXGI を呼び出す別のスレッドによって同時にアクセスされる可能性があります。 これらの Direct3D または DXGI 呼び出しは Direct2D の認識と制御を外れているため、マルチスレッドの Direct2D ファクトリを作成する必要がありますが、アクセスの競合を回避するには mor を実行する必要があります。

次の図は、スレッド T0 が Direct2D 呼び出しを介して間接的にリソースにアクセスし、 T2 が Direct3D または DXGI 呼び出しを介して同じリソースに直接アクセスするため、Direct3D リソース アクセスの競合を示しています。

注意

この場合、 Direct2D によって提供されるスレッド保護 (このイメージの青いロック) は役に立ちません。

 

スレッド保護図。

ここでリソース アクセスの競合を回避するには、 Direct2D が内部アクセス同期に使用するロックを明示的に取得し、次に示すように、スレッドが Direct3D または DXGI 呼び出しを行う必要がある場合に、そのロックを適用することをお勧めします。 特に、HRESULT リターン コードに基づいて例外または早期システムを使用するコードには特別な注意を払う必要があります。 このため、RAII (リソース取得は初期化) パターンを使用して 、Enter メソッドと Leave メソッドを呼び出すことをお勧めします。

注意

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 resent) は、呼び出し元の関数またはメソッドのコードにロックやトリガー コールバックを取得する場合があります。 この点に注意し、このような動作によってデッドロックが発生しないようにする必要があります。 詳細については、 DXGI の概要 に関するトピックを参照してください。

 

direct2d および direct3d スレッド ロック図。

Enter メソッドと Leave メソッドを使用すると、呼び出しは自動 Direct2D と明示的なロックによって保護されるため、アプリはアクセスの競合にヒットしません。

この問題を回避するには、他にも方法があります。 ただし、 Direct2D ロックを使用して Direct3D または DXGI 呼び出しを明示的に保護することをお勧めします。 Direct2D ロックは、Direct2D のカバーの下ではるかに細かいレベルと低いオーバーヘッドでコンカレンシーを保護するため、通常はパフォーマンスが向上します。

ステートフル操作の原子性の確保

DirectX のスレッドセーフ機能は、2 つの個別の API 呼び出しが同時に行われないようにするのに役立ちますが、ステートフル API 呼び出しを行うスレッドが相互に干渉しないようにする必要もあります。 次に例を示します。

  1. 画面上 (スレッド 0) とオフスクリーン (スレッド 1) の両方にレンダリングする 2 行のテキストがあります。行 #1 は "A が大きい" で、行 #2 は "B より" で、どちらも黒の実線ブラシを使用して描画されます。
  2. スレッド 1 は、テキストの最初の行を描画します。
  3. スレッド 0 はユーザー入力に反応し、テキスト行の両方を "B は小さい" と "A より" に更新し、ブラシの色を独自の描画用に赤で塗りつぶすように変更します。
  4. スレッド 1 は、現在は "A より" である 2 行目のテキストを赤いカラー ブラシで引き続き描画します。
  5. 最後に、画面外の描画ターゲットに 2 行のテキストが表示されます。"A は大きい" (黒)、"A より大きい" が赤で表示されます。

オンとオフの画面スレッドの図。

一番上の行では、スレッド 0 は現在のテキスト文字列と現在の黒いブラシで描画されます。 スレッド 1 は、上半分の画面外描画のみを終了します。

中央の行では、スレッド 0 はユーザーの操作に応答し、テキスト文字列とブラシを更新してから画面を更新します。 この時点で、スレッド 1 はブロックされます。 下の行では、スレッド 1 の後の最終的な画面外レンダリングは、変更されたブラシと変更されたテキスト文字列を使用して下半分の描画を再開します。

この問題に対処するには、スレッドごとに個別のコンテキストを用意することをお勧めします。そのため、次のことを行います。

  • デバイス コンテキストのコピーを作成して、変更可能なリソース (例のテキスト コンテンツや純色ブラシなど、表示や印刷中に異なる可能性があるリソース) がレンダリング時に変更されないようにする必要があります。 このサンプルでは、描画する前に、これらの 2 行のテキストとカラー ブラシのコピーを保持する必要があります。 そうすることで、各スレッドが描画および提示するための完全で一貫性のあるコンテンツを持っていることを保証できます。
  • 1 回初期化され、スレッド間で変更されない重み付けリソース (ビットマップや複雑な効果グラフなど) を共有して、パフォーマンスを向上させる必要があります。
  • 1 回初期化され、スレッド間で変更されない軽量リソース (単色ブラシやテキスト形式など) を共有できます

まとめ

マルチスレッド Direct2D アプリを開発する場合は、マルチスレッド Direct2D ファクトリを作成し、そのファクトリからすべての Direct2D リソースを派生させる必要があります。 スレッドが Direct3D または DXGI 呼び出しを行う場合は、Direct3D または DXGI 呼び出しを保護するために Direct2D ロックを明示的に取得してから適用する必要もあります。 さらに、スレッドごとに変更可能なリソースのコピーを用意することで、コンテキストの整合性を確保する必要があります。