Direct3D テクスチャおよびサーフェスからのリードバックの実行
-
02-07-2019 - |
質問
D3D テクスチャとサーフェスからデータをシステム メモリに戻す方法を理解する必要があります。そのようなことを行う最も速い方法とその方法は何ですか?
また、サブレクトリが 1 つだけ必要な場合、全体をシステム メモリに読み戻さずに、その部分だけを読み戻すにはどうすればよいでしょうか?
要するに、次のものをコピーする方法についての簡潔な説明を探しています。 システムメモリ:
- ある テクスチャ
- ある サブセット の テクスチャ
- ある 表面
- ある サブセット の 表面
- ある D3DUSAGE_RENDERTARGET テクスチャ
- ある サブセット の D3DUSAGE_RENDERTARGET テクスチャ
これは Direct3D 9 ですが、D3D の新しいバージョンについての回答も歓迎します。
解決
最も複雑な部分は、ビデオ メモリ (「デフォルト プール」) にあるサーフェスからの読み取りです。ほとんどの場合、これはレンダー ターゲットです。
まず簡単な部分を取得しましょう。
- テクスチャからの読み取りは、そのテクスチャの 0 レベル サーフェスからの読み取りと同じです。以下を参照してください。
- テクスチャのサブセットについても同様です。
- デフォルト以外のメモリ プール (「システム」または「管理対象」) にあるサーフェスからの読み取りは、単にロックしてバイトを読み取るだけです。
- サーフェスのサブセットについても同様です。該当部分をロックして読んでください。
これで、ビデオ メモリ (「デフォルト プール」) にサーフェスが残りました。これは、レンダー ターゲットとしてマークされた任意のサーフェス/テクスチャ、デフォルト プールで作成した通常のサーフェス/テクスチャ、またはバックバッファ自体になります。ここで複雑なのは、ロックできないことです。
短い答えは次のとおりです。 GetRenderTargetData D3D デバイス上のメソッド。
より長い答え (以下にコードの大まかな概要):
- RT = レンダー ターゲット サーフェスを取得します (これはテクスチャのサーフェスやバックバッファなどにすることができます)
- もし RT マルチサンプリングされている場合 (GetDesc、D3DSURFACE_DESC.MultiSampleType を確認)、次のようになります。a) 同じサイズ、同じフォーマットの別のレンダー ターゲット サーフェスを作成しますが、 それなし マルチサンプリング;b) からのStretchRect RT この新しい表面に。c) RT = この新しい表面 (すなわち、この新しい表面を進んでください)。
- オフ = オフスクリーンプレーンサーフェスを作成 (CreateOffscreenPlainSurface、D3DPOOL_SYSTEMMEM プール)
- デバイス->GetRenderTargetData( RT, オフ )
- 今 オフ レンダー ターゲット データが含まれています。LockRect()、データの読み取り、それに対する UnlockRect()。
- 掃除
さらに長い答え(私が取り組んでいるコードベースから貼り付け)が続きます。これ しない コードベースの残りの一部のクラス、関数、マクロ、ユーティリティを使用するため、すぐにコンパイルできます。しかし、それがあなたを始めるはずです。また、エラーチェックのほとんどを省略しました (例:指定された幅/高さが範囲外かどうか)。また、実際のピクセルを読み取り、それらを適切な宛先形式に変換する部分も省略しました (これは非常に簡単ですが、サポートする形式変換の数によっては長くなる可能性があります)。
bool GfxDeviceD3D9::ReadbackImage( /* params */ )
{
HRESULT hr;
IDirect3DDevice9* dev = GetD3DDevice();
SurfacePointer renderTarget;
hr = dev->GetRenderTarget( 0, &renderTarget );
if( !renderTarget || FAILED(hr) )
return false;
D3DSURFACE_DESC rtDesc;
renderTarget->GetDesc( &rtDesc );
SurfacePointer resolvedSurface;
if( rtDesc.MultiSampleType != D3DMULTISAMPLE_NONE )
{
hr = dev->CreateRenderTarget( rtDesc.Width, rtDesc.Height, rtDesc.Format, D3DMULTISAMPLE_NONE, 0, FALSE, &resolvedSurface, NULL );
if( FAILED(hr) )
return false;
hr = dev->StretchRect( renderTarget, NULL, resolvedSurface, NULL, D3DTEXF_NONE );
if( FAILED(hr) )
return false;
renderTarget = resolvedSurface;
}
SurfacePointer offscreenSurface;
hr = dev->CreateOffscreenPlainSurface( rtDesc.Width, rtDesc.Height, rtDesc.Format, D3DPOOL_SYSTEMMEM, &offscreenSurface, NULL );
if( FAILED(hr) )
return false;
hr = dev->GetRenderTargetData( renderTarget, offscreenSurface );
bool ok = SUCCEEDED(hr);
if( ok )
{
// Here we have data in offscreenSurface.
D3DLOCKED_RECT lr;
RECT rect;
rect.left = 0;
rect.right = rtDesc.Width;
rect.top = 0;
rect.bottom = rtDesc.Height;
// Lock the surface to read pixels
hr = offscreenSurface->LockRect( &lr, &rect, D3DLOCK_READONLY );
if( SUCCEEDED(hr) )
{
// Pointer to data is lt.pBits, each row is
// lr.Pitch bytes apart (often it is the same as width*bpp, but
// can be larger if driver uses padding)
// Read the data here!
offscreenSurface->UnlockRect();
}
else
{
ok = false;
}
}
return ok;
}
SurfacePointer
上記のコードには、COM オブジェクトへのスマート ポインターが含まれています (代入またはデストラクターでオブジェクトを解放します)。エラー処理が大幅に簡素化されます。これは非常によく似ています _comptr_t
Visual C++ のこと。
上記のコードは表面全体を読み取ります。一部だけを効率的に読みたい場合は、おおよそ次の方法が最も早いと思います。
- 必要なサイズのデフォルトのプール サーフェスを作成します。
- 元のサーフェスの一部からその小さいサーフェスまで StringRect します。
- 小さい方を通常どおりに進めます。
実際、これは上記のコードがマルチサンプリングされたサーフェスを処理するために行うことと非常に似ています。マルチサンプル サーフェスの一部だけを取得したい場合は、マルチサンプル リゾルブを実行して、その一部を 1 つの StretchRect で取得できると思います。
編集:実際のピクセルの読み取りとフォーマット変換を行うコード部分を削除しました。質問とは直接関係がなく、コードも長かったです。
編集:編集された質問と一致するように更新されました。