最も複雑な部分は、ビデオ メモリ (「既定のプール」) にあるサーフェスからの読み取りです。これはほとんどの場合、レンダー ターゲットです。
最初に簡単な部分を取得しましょう。
- テクスチャからの読み取りは、そのテクスチャの 0 レベル サーフェスからの読み取りと同じです。下記参照。
- テクスチャのサブセットについても同じです。
- デフォルト以外のメモリ プール (「システム」または「マネージド」) にあるサーフェスからの読み取りは、それをロックしてバイトを読み取るだけです。
- 表面のサブセットについても同じです。関連する部分をロックして読むだけです。
これで、ビデオ メモリ (「デフォルト プール」) にあるサーフェスが残りました。これは、レンダー ターゲットとしてマークされたサーフェス/テクスチャ、デフォルト プールで作成した通常のサーフェス/テクスチャ、またはバックバッファ自体になります。ここで複雑なのは、ロックできないことです。
簡単な答えは、D3D デバイスのGetRenderTargetDataメソッドです。
より長い答え(以下にあるコードの大まかな概要):
- rt = レンダー ターゲット サーフェスを取得します (これは、テクスチャのサーフェスまたはバックバッファなどにすることができます)。
- rtがマルチサンプリングされている場合( GetDesc、D3DSURFACE_DESC.MultiSampleType をチェック)、次のようになります。b) rtからこの新しいサーフェスへの StretchRect。c) rt = この新しい面 (つまり、この新しい面に進みます)。
- off = オフスクリーン プレーン サーフェスを作成 (CreateOffscreenPlainSurface、D3DPOOL_SYSTEMMEM プール)
- device->GetRenderTargetData( rt , off )
- 現在オフには、レンダー ターゲット データが含まれています。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++ の場合と非常によく似ています。
上記のコードは、表面全体を読み取ります。その一部だけを効率的に読みたい場合は、おおよそ次の方法が最速だと思います。
- 必要なサイズのデフォルトのプール サーフェスを作成します。
- 元のサーフェスの一部からその小さいサーフェスに StretchRect を実行します。
- 小さい方で通常どおりに進みます。
実際、これは上記のコードがマルチサンプリング サーフェスを処理するために行っていることと非常によく似ています。マルチサンプル サーフェスの一部だけを取得したい場合は、マルチサンプル リゾルブを実行して、その一部を 1 つの StretchRect で取得できると思います。
編集:ピクセルの実際の読み取りとフォーマット変換を行うコードを削除しました。質問とは直接関係なく、コードが長かったです。
編集: 編集された質問に合わせて更新されました。