D3DXSaveSurfaceToFileを使用せずに、DirectX 9の画面をメモリの未加工ビットマップにキャプチャする方法


8

OpenGLでこのようなことができることを知っています

glReadBuffer( GL_FRONT );
glReadPixels( 0, 0, _width, _height, GL_RGB, GL_UNSIGNED_BYTE, _buffer ); 

そして、かなり高速です。_bufferに生のビットマップを取得します。DirectXでこれを実行しようとすると、D3DDeviceオブジェクトがあると仮定すると、次のようなことができます

if (SUCCEEDED(D3DDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pBackbuffer))) {
   HResult hr = D3DXSaveSurfaceToFileA(filename, D3DXIFF_BMP, pBackbuffer, NULL, NULL); 

しかし、D3DXSaveSurfaceToFileはかなり遅く、キャプチャをディスクに書き込む必要がないので、これを行うためのより高速な方法があるかどうか疑問に思っていました。

編集:私はDirectXアプリケーションのみを対象としています。実際にはDX9アプリ。私はプレゼントへのフックを介してこれを行っています。(必要に応じて迂回を使用しています)、すべてのフレームでこれを実行しています。BMP PNG JPEGなどの特定のビットマップ形式は必要ありません(または必要ありません)。RGB24の色空間は問題ありません。YUV480pとして取得する方が良いでしょうが、絶対に必要というわけではありません。非常に役立つ回答をありがとうございました


2
「ビットマップ」とはどういう意味ですか?特定の形式のファイルまたはピクセルカラーの配列?ファイルの場合は、これを試してくださいmsdn.microsoft.com/en-us/library/windows/desktop/…配列の場合、答えも知りたいです。
snake5

ええ、RGB情報のピクセルあたり3バイトのバイトの配列を意味しました。glReadPixelsがopenGLで行うのとまったく同じです。私はRAWビットマップを意味しました。質問をより明確にするために更新します
cloudraven

実際、あなたが言及したその関数はそれをメモリに保存します。取得したd3xbufferを読み取るだけです。私はそれを試すべきです、もしそれが十分に速いならば、私はそれを解決策として受け入れるかもしれません。(それでも誰かが画面をキャプチャするより速い方法を知っている場合は、それは大歓迎です)
cloudraven

回答:


6

これを行う方法は次のとおりです。

IDirect3DDevice9 :: GetBackBuffer:現在取得しているのと同じように、バックバッファーを表すIDirect3DSurface9へのアクセスを取得します。この呼び出しが参照カウントをインクリメントするので、完了したらこのサーフェスを解放することを忘れないでください!

IDirect3DSurface :: GetDesc:バックバッファサーフェスの説明を取得します。これにより、幅、高さ、およびフォーマットがわかります。

IDirect3DDevice9 :: CreateOffscreenPlainSurfaceD3DPOOL_SCRATCHに新しいサーフェスオブジェクトを作成します。通常、同じ幅、高さ、および形式を使用する必要があります(ただし、実際にはこのメソッドを使用する必要はありません)。繰り返しますが、完了したらリリースします。この操作をフレームごとに実行する場合(この場合は、実行しようとしていることに対するシェーダーベースのアプローチなどの代替手段を検討する方が良いでしょう)、起動時に1回だけオフスクリーンプレーンサーフェスを作成して再利用できます。フレームごとに作成するのではなく、

D3DXLoadSurfaceFromSurface:バックバッファーサーフェスからオフシーンのプレーンサーフェスにコピーします。これにより、サイズ変更とフォーマット変換が自動的に行われます。または、フォーマットを変更したくない、または変更する必要がある場合は、IDirect3DDevice9 :: GetRenderTargetDataを使用できますが、その場合は、代わりにD3DPOOL_SYSTEMMEMにオフスクリーンプレーンサーフェスを作成します。

IDirect3DSurface9 :: LockRect:オフスクリーンプレーンサーフェスのデータにアクセスし、それを使って独自の邪悪な方法を持つ。完了したら、UnlockRect。

これははるかに多くのコードのように見えますが、glReadPixelsと同じくらい高速であり、フォーマット変換を実行する必要がない場合はさらに高速になる可能性があります(GL_RGBを使用するglReadPixelsはほぼ確実に実行します)。

追加して編集:スクリーンショットにこのメソッドを使用するのに役立つ可能性があるいくつかの(rought 'n' ready)ヘルパー関数も持っています。

// assumes pitch is measured in 32-bit texels, not bytes; use locked_rect.Pitch >> 2
void CollapseRowPitch (unsigned *data, int width, int height, int pitch)
{
    if (width != pitch)
    {
        unsigned *out = data;

        // as a minor optimization we can skip the first row
        // since out and data point to the same this is OK
        out += width;
        data += pitch;

        for (int h = 1; h < height; h++)
        {
            for (int w = 0; w < width; w++)
                out[w] = data[w];

            out += width;
            data += pitch;
        }
    }
}


void Compress32To24 (byte *data, int width, int height)
{
    byte *out = data;

    for (int h = 0; h < height; h++)
    {
        for (int w = 0; w < width; w++, data += 4, out += 3)
        {
            out[0] = data[0];
            out[1] = data[1];
            out[2] = data[2];
        }
    }
}

// bpp is bits, not bytes
void WriteDataToTGA (char *name, void *data, int width, int height, int bpp)
{
    if ((bpp == 24 || bpp == 8) && name && data && width > 0 && height > 0)
    {
        FILE *f = fopen (name, "wb");

        if (f)
        {
            byte header[18];

            memset (header, 0, 18);

            header[2] = 2;
            header[12] = width & 255;
            header[13] = width >> 8;
            header[14] = height & 255;
            header[15] = height >> 8;
            header[16] = bpp;
            header[17] = 0x20;

            fwrite (header, 18, 1, f);
            fwrite (data, (width * height * bpp) >> 3, 1, f);

            fclose (f);
        }
    }
}

私は実際に毎フレームやっています。現在の機能をフックして、フック内にフレームをキャプチャしています。これを行うためにシェーダーを使用することについて詳しく説明してください。それが私が探しているものです。完全な回答をありがとうございました。
cloudraven

1
シェーダーベースのアプローチは、キャ​​プチャされた画像を使用してその後のレンダリングを行う場合にのみ適切です。あなたの説明から、ビデオキャプチャのようなことをしているように聞こえますか?これを確認できますか?
Maximus Minimus

おっしゃるとおりですが、ビデオキャプチャは正確には行っていません。非常に近いです。すべてのフレームをエンコードするわけではありませんが(十分なパフォーマンスを必要とするのに十分です)、キャプチャした画像にいくつかの2Dフィルターを適用してからエンコードします。
cloudraven

BGRAを使用するようにコードを変更しました。それは良くなった。洞察をありがとう
cloudraven

ちなみに、シェーダーアプローチに関するフォローアップの質問を投稿しました。よろしくお願いします。gamedev.stackexchange.com/questions/44587/...
cloudraven

1

LockRectangleが必要だと思いますpBackBuffer、その後からのピクセルを読んD3DLOCKED_RECT.pBits。これはかなり速いはずです。数ピクセルのみを読み取る必要がある場合は、バックバッファー全体をDX11 CopySubresourceRegion(DX9にも代替案があると確信しています)などを介して小さなバッファーにコピーすることを検討してください。これはGPUで行われ、この小さなバッファーをGPUからLockRectangleを介したCPU-バス上の大きなバックバッファーの転送を保存します。


1
答えには、最も重要な部分である変換がありません。pBitsは、サーフェス形式のデータを指します。ほとんどの場合、バイトトリプレットの配列(RGB24)とは異なります。
snake5

1
フォーマット変換とは別に、これにはCreateDeviceにロック可能なバックバッファーフラグ(ドキュメントではパフォーマンスヒットであると警告されています)が必要であり、バックバッファーをロックすると常にパイプラインストールが発生します(データ転送よりもはるかに大きな問題になります)。
Maximus Minimus

1
そのため、バックバッファの一部を別のバッファにコピーするようにアドバイスしました。サーフェスから生データを読み取ることができる場合、「ビットマップ」形式への変換はあなた次第です...
GPUquant

プロセスの途中で実際にYuv420pに変換する必要があります。DirectXはRGBではない内部形式を使用していますか?
cloudraven

2
RGB内部フォーマットなどはありません-内部フォーマットは32bppです-OpenGL GL_RGBであっても、予備の8ビットが未使用であることを意味します。opengl.org/wiki/Common_Mistakes#Texture_upload_and_pixel_readsを参照してください。したがって、OpenGLとD3Dの両方が実際には内部でBGRAになるため、GL_RGBの使用は低速です。データの圧縮とスウィズルの両方を行う必要があるため、ソフトウェアプロセスが低速になります。
Maximus Minimus

1

GetBackbufferを使用して画面をキャプチャすることはできません。この関数は、データではなく、バックバッファのポインタのみを取得します。

私が考えることができる正しい方法は以下の通りです

  1. CreateOffscreenPlainSurfaceを呼び出して、オフスクリーンサーフェスを作成します。
  2. コールGetFrontBufferDataを、画面イメージをオフスクリーンに取得します。
  3. サーフェスのLockRectを呼び出して、サーフェスのデータを取得します。オフスクリーンサーフェスは、プールタイプに関係なく常にロック可能でした。

1
IDirect3DDevice9 :: GetFrontBufferDataは- 「この機能は、設計により、非常に遅く、任意のパフォーマンス・クリティカル・パスに使用すべきではない」 - msdn.microsoft.com/en-us/library/windows/desktop/...
マクシムスMINIMUS

1
はい、それは遅いですが、私の知る限り、DirectXで画面イメージを取得する唯一の方法です。または、EndScene関数を呼び出す前にフックすることもできます。
zdd

ありがとう!私は実際にはフックとしてそれを行っていますが、EndSceneではなくPresentに対して行っています。具体的には、directXアプリケーションをフックしています。それを考えると、私がそれについてできるもっと賢い何かがあります。
cloudraven

@cloudraven、私はフッキングに慣れていませんが、参考のためにこのページをご覧ください。stackoverflow.com/questions/1994676/...
ZDD

1

少し遅れた。しかし、これが誰かに役立つことを願っています。

作成

        if (SUCCEEDED(hr = _device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuf)))
        {
            D3DSURFACE_DESC sd;
            backBuf->GetDesc(&sd);

            if (SUCCEEDED(hr = _device->CreateOffscreenPlainSurface(sd.Width, sd.Height, sd.Format, D3DPOOL_SYSTEMMEM, &_surface, NULL)))

捕獲

    if (SUCCEEDED(hr = _device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuf)))
    {
        if (SUCCEEDED(hr = _device->GetRenderTargetData(backBuf, _surface)))
        {
            if (SUCCEEDED(hr = D3DXSaveSurfaceToFileInMemory(&buf, D3DXIFF_DIB, _surface, NULL, NULL)))
            {
                LPVOID imgBuf = buf->GetBufferPointer();
                DWORD length = buf->GetBufferSize();

bufには生の画像データがあります。すべてを解放することを忘れないでください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.