15

D3D テクスチャとサーフェスからシステム メモリにデータを戻す方法を理解する必要があります。そのようなことを行うための最速の方法と方法は何ですか?

また、サブレクトが 1 つだけ必要な場合、全体をシステム メモリに読み戻すことなく、その部分だけを読み戻すにはどうすればよいでしょうか。

要するに、以下をシステムメモリにコピーする方法の簡潔な説明を探しています:

  1. テクスチャ_
  2. テクスチャサブセット_
  3. 表面_
  4. サーフェスサブセット_
  5. D3DUSAGE_RENDERTARGETテクスチャ
  6. D3DUSAGE_RENDERTARGETテクスチャサブセット

これは Direct3D 9 ですが、D3D の新しいバージョンに関する回答もいただければ幸いです。

4

1 に答える 1

30

最も複雑な部分は、ビデオ メモリ (「既定のプール」) にあるサーフェスからの読み取りです。これはほとんどの場合、レンダー ターゲットです。

最初に簡単な部分を取得しましょう。

  1. テクスチャからの読み取りは、そのテクスチャの 0 レベル サーフェスからの読み取りと同じです。下記参照。
  2. テクスチャのサブセットについても同じです。
  3. デフォルト以外のメモリ プール (「システム」または「マネージド」) にあるサーフェスからの読み取りは、それをロックしてバイトを読み取るだけです。
  4. 表面のサブセットについても同じです。関連する部分をロックして読むだけです。

これで、ビデオ メモリ (「デフォルト プール」) にあるサーフェスが残りました。これは、レンダー ターゲットとしてマークされたサーフェス/テクスチャ、デフォルト プールで作成した通常のサーフェス/テクスチャ、またはバックバッファ自体になります。ここで複雑なのは、ロックできないことです。

簡単な答えは、D3D デバイスのGetRenderTargetDataメソッドです。

より長い答え(以下にあるコードの大まかな概要):

  1. rt = レンダー ターゲット サーフェスを取得します (これは、テクスチャのサーフェスまたはバックバッファなどにすることができます)。
  2. rtがマルチサンプリングされている場合( GetDesc、D3DSURFACE_DESC.MultiSampleType をチェック)、次のようになります。b) rtからこの新しいサーフェスへの StretchRect。c) rt = この新しい面 (つまり、この新しい面に進みます)。
  3. off = オフスクリーン プレーン サーフェスを作成 (CreateOffscreenPlainSurface、D3DPOOL_SYSTEMMEM プール)
  4. device->GetRenderTargetData( rt , off )
  5. 現在オフには、レンダー ターゲット データが含まれています。LockRect()、データを読み取り、その上で UnlockRect() を実行します。
  6. 掃除

さらに長い答え(私が取り組んでいるコードベースからの貼り付け)が続きます。これは、コードベースの残りのクラス、関数、マクロ、およびユーティリティを使用するため、そのままではコンパイルされません。しかし、それはあなたが始める必要があります。また、ほとんどのエラー チェック (指定された幅/高さが範囲外かどうかなど) も省略しました。また、実際のピクセルを読み取り、適切な宛先形式に変換する部分も省略しました (これは非常に簡単ですが、サポートする形式変換の数によっては長くなる可能性があります)。

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++ の場合と非常によく似ています。

上記のコードは、表面全体を読み取ります。その一部だけを効率的に読みたい場合は、おおよそ次の方法が最速だと思います。

  1. 必要なサイズのデフォルトのプール サーフェスを作成します。
  2. 元のサーフェスの一部からその小さいサーフェスに StretchRect を実行します。
  3. 小さい方で通常どおりに進みます。

実際、これは上記のコードがマルチサンプリング サーフェスを処理するために行っていることと非常によく似ています。マルチサンプル サーフェスの一部だけを取得したい場合は、マルチサンプル リゾルブを実行して、その一部を 1 つの StretchRect で取得できると思います。

編集:ピクセルの実際の読み取りとフォーマット変換を行うコードを削除しました。質問とは直接関係なく、コードが長かったです。

編集: 編集された質問に合わせて更新されました。

于 2008-09-23T10:01:40.863 に答える