デバイスロスト問題

DirectXにはデバイスロストというとても厄介な問題がある。
現状のDXRubyではD3DXSpriteをロストさせてリセット、復元の処理を入れてある。
これをしないとデバイスロスト時に描画されなくなってしまうのだ。
テクスチャはD3DPOOL_MANAGEDを指定しているから、自動的にビデオメモリとシステムメモリ両方にコピーされていて、復元もやってくれる。
ちなみにデバイスロスト時の動作を確認するには、Windows2000ならCTRL+ALT+DELでいけたと思うが、XPだと押してもタスクマネージャが出るたけなので、ユーザーの切り替えを選んで戻すとロストできる。


Imageクラスの拡張としてSurfaceを導入しようと思うと、テクスチャをD3DPOOL_DEFAULTにしないといけなくて、これはビデオメモリにしかデータを置かないから、デバイスロストのダメージをモロに受ける。
イメージのロードはRubyスクリプトがやるから、ライブラリ側でロードしなおしをしようと思うと、どのようにロードしたのかを全て記憶しておいて辿る必要がある。
Imageオブジェクトをハードウェアで編集するのだから、ロードしなおしなんかしたらデータが戻ってしまって、そもそも問題だ。
D3DPOOL_MANAGEDの場合にDirectXがやっているように、システムメモリにコピーを持っておいて、そこから戻すようにするのがとりあえずの正解だ。


D3DPOOL_MANAGEDにするとレンダーターゲットにできないから、更新は常にソフトウェアでシステムメモリに行われて、ビデオメモリにコピーされると想像できる。
一部ハードウェアで描かれる命令もあるかもしれないが、それは実行するたびに更新分だけシステムメモリにコピーされているはずだ。
じゃなければデバイスロスト時に正常に復元できない。
Imageオブジェクトのハードウェア描画をサポートする場合、これと同じことが言える。
lineやcircleはハードウェア描画はできないから、システムメモリに描画して、ビデオメモリにコピー。
ハードウェア描画した場合は、その直後にシステムメモリにコピー。
このように同期を取っていけば、なんとかなるような気はするのだが、たぶんそれではまだ甘い。


DirectXではデバイスロストした場合に、描画系の命令は何もしないで正常終了する。
だから画面に何も出なくなるのだ。
で、プログラムのどこかで復元処理をすれば、また動き始める。
Imageオブジェクトのハードウェア描画をする場合に、この動作はとんでもなく困る。
Ruby側は更新したつもりのImageオブジェクトが更新されていないのだから。
ビデオメモリのデータをシステムメモリにコピーすると壊れている可能性があるから、ロスト時はブロックしたいが、ロストを検出できるのはPresentなど一部のメソッドだけだから、おそらくシステムメモリのデータも破壊される。
Imageオブジェクトを毎フレームクリアして編集する使い方なら大丈夫だろうが、毎フレーム追加更新するような使い方をすると、完全にアウトだ。


対応策はほぼ無い。
あるとすれば、Imageオブジェクトの編集のたびにロストチェックをして、ブロックするか・・・でもRuby側は編集したつもりになっているなら、追加更新が途中だけ抜けることになる。
完璧に対応するなら、Ruby側にロストを通知して、スクリプトのほうで対策してもらうしかない。
しかしそれではデバイスロスト問題を丸投げしているだけで、厄介をユーザーに押し付けることになってしまう。
ハードウェア描画ができるクラスはImageとは別にSurfaceクラスとして新たに作って、そっちは毎フレームクリアされるようにしてしまうとか。
なんだか微妙に使いづらいな。


結局のところ、Imageオブジェクト間転送のハードウェア処理は諦めることになりそうだな。
ソフトで矩形転送をサポートする程度か。
派手なエフェクトつけてバッファ編集したいという要望があれば、制限付きでSurfaceクラスを追加するかも?みたいな。