プロが教える店舗&オフィスのセキュリティ対策術

DirectX 11 ConsntanBufferの更新について

マルチスレッドでConsntanBufferの更新を行うときにエラーが出てしまいます。

基本的な設計ですが、

1. メインのシーケンスを行うスレッド

2.DeviceContextに描画コマンドを詰むスレッド

のスレッド2本構成です。
1から2へのスレッドへは仮想的な描画コマンドを発行し、
ダブルバッファリングを行い並列性を高めています。
# よくある方式だと思います。


HLSLで簡単なシェーダを作りポリゴンを表示することはできたのですが、
DirectXをDebugモードで実行すると以下のようなエラーが出てしまいました。

D3D11: CORRUPTION: ID3D11DeviceContext::Map: Two threads were found to be executing functions associated with the same Device at the same time. This will cause corruption of memory. Appropriate thread synchronization needs to occur external to the Direct3D API. 3516 and 5376 are the implicated thread ids. [ MISCELLANEOUS CORRUPTION #28: CORRUPTED_MULTITHREADING ]

内容はスレッド間で同じバッファに対してID3D11DeviceContext::Mapを実行しているため、
中身が破壊される可能性があるというものです。


先述の通り、基本的にDeviceContextを取り扱うのは2のスレッドです。
しかし、内容が動的に変更されるバッファについては
1のスレッドでID3D11DeviceContext::Mapを実行し、
取得したアドレスを直接書き換えるようにしています。

エラーではID3D11DeviceContext::Mapが2つのスレッドで実行されているというのですが、
初期化時は別として、通常のアップデート処理を行っている間は1のスレッドでしか使っていません。

書き込み先のバッファもダブルバッファ化しており、
またCPUAccess / USAGE / BIND / MAPのフラグも
D3D11_CPU_ACCESS_WRITE / D3D11_USAGE_DYNAMIC / D3D11_BIND_CONSTANT_BUFFER / D3D11_MAP_WRITE_DISCARD
と、動的バッファを使うときの標準的な組み合わせで使用しています。

いろいろとサンプルプログラムを見ているのですが、
出回っているのは大抵がシングルスレッドで作られており、
今回の件に関してはあまり参考になりませんでした。


プログラム中ID3D11DeviceContext::Mapを呼び出しているところは
かなり限定できるため、おそらく間違いないありません。

いろいろ調べては見たのですがエラーが出てしまう原因が特定できずに困っています。
エラーの解決方法をご存知の方はいらっしゃいますでしょうか?

■補則
D3D11_MAP_WRITE_DISCARDはCPUとGPUの同期を制御するフラグですので、
CPUとCPUの競合がおきている今回の件とは別のはずです。

他にはID3D11DeviceContextのメソッド中の何かが
内部でID3D11DeviceContext::Mapを呼び出している可能性がありそうです。
2のスレッドで問題のバッファにアクセスしているメソッドは
ID3D11DeviceContext::VSSetConstantBuffersぐらいです。
(これが内部でMapを呼び出しているとは思えませんが…)

A 回答 (1件)

デバイス コンテキストID3D11DeviceContextのメソッドはいずれもスレッド セーフではありません。


呼び出しがスレッド セーフになったのはID3D11Deviceだけです。そのため、デバイス コンテキストはスレッドごとに作成する必要があります。
具体的には、メインスレッドではイミディエイト コンテキスト(Immediate Context)、サブスレッドではディファード コンテキスト(Deferred Context)として、それぞれスレッドごとに作成する必要があります。
Direct3D 11のマルチスレッド レンダリングは、サブスレッド側で遅延された描画コマンドのリストを記録(作成)し、メインスレッド側で再生(実行)する、という方式になっています。

DirectX SDKにMultithreadedRendering11サンプルが付いているので、(例のごとくDXUTやら大量のグローバル変数やらで少し読みづらいですが)それが参考になるかと思います。Map/Unmapは各スレッドごとに独立して実行しています。

http://msdn.microsoft.com/ja-jp/library/ee422102 …
http://wlog.flatlib.jp/archive/1/2008-11-7

この回答への補足

>デバイス コンテキストID3D11DeviceContextのメソッドはいずれもスレッド セーフではありません。
>呼び出しがスレッド セーフになったのはID3D11Deviceだけです。そのため、デバイス コンテキストはスレッドごとに作成する必要があります。

なるほど。Direct3D 9ではMULTITHREADオプションでスレッドセーフに出来ましたが、
おっしゃるとおりスレッド単位でコンテキストを分けた方が
クリティカルセクションのロスが減らせそうです。

DeferredContextはDirect 3D 10では使えないようですね。
今作っているプログラムはマルチプラットホーム対応を検討しているため、
できるだけ標準化されたAPIで実装しようとしています。
となればリソースの更新も仮想コマンドを投げて
2のスレッドで行うことにします。

ただ、この方法ですと書き込み先のアドレスが、
書き込み元のスレッドで取得できなくなります。
1のスレッドではテンポラリバッファを用意し、
そこに書き込んだ後、2のスレッドでMapをしmemcpyをします。
メモリが余分に必要なのに加えてmemcpy分の処理が上積みされてしまいます。

残るは毎フレームID3D11Device::CreateBuffer/ID3D11Buffer::Releaseを呼び出す方法です。
D3D11_SUBRESOURCE_DATAに1のスレッドで操作しているバッファをセットするので、
コピー処理は不要です。
# Direct3D 9ではコピーが必須ですし、
# Direct3D 10以降であっても内部的にコピー処理が走るかも知れないので
# 気休めではありますが…
バッファの生成と破棄が大量に呼び出されることになりますが
メモリ容量と処理速度と汎用化のバランスが一番とれている気がします。
初期化時にSINGLETHREADフラグがセットできるので、
実質Direct3D 9と同じ扱いになりそうです。

補足日時:2011/02/23 13:04
    • good
    • 0
この回答へのお礼

話は脱線しましたが、
ID3D11DeviceContextがスレッドセーフでないことは理解しました。
# もっとわかりやすい警告を出してくれれば速く気づけたのですが…

大変助かりました。
どうもありがとうございました。

お礼日時:2011/02/23 13:04

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!