So erfassen Sie den Bildschirm in DirectX 9 mit einer unformatierten Bitmap im Speicher, ohne D3DXSaveSurfaceToFile zu verwenden

8

Ich weiß, dass ich in OpenGL so etwas machen kann

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

Und es ist ziemlich schnell, ich bekomme die rohe Bitmap in _buffer. Wenn ich das in DirectX versuche. Angenommen, ich habe ein D3DDevice-Objekt, kann ich so etwas tun

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

Aber D3DXSaveSurfaceToFile ist ziemlich langsam, und ich muss das Capture sowieso nicht auf die Festplatte schreiben, also habe ich mich gefragt, ob es einen schnelleren Weg gibt, dies zu tun.

BEARBEITEN: Ich ziele nur auf DirectX-Anwendungen ab. Eigentlich DX9 Apps. Ich mache das durch einen Haken an Present. (Ich verwende Umwege, wenn dies relevant ist), und ich mache dies für jeden Frame. Ich brauche (oder möchte) kein bestimmtes Bitmap-Format wie BMP PNG JPEG usw., und ein Farbraum von RGB24 ist in Ordnung. Es als YUV480p zu bekommen wäre besser, aber definitiv nicht notwendig. Vielen Dank an alle für die sehr hilfreichen Antworten

Wolkenraben
quelle
2
Was meinst du mit "Bitmap"? Eine Datei in einem bestimmten Format oder eine Reihe von Pixelfarben? Wenn es sich um eine Datei handelt, versuchen Sie Folgendes : msdn.microsoft.com/en-us/library/windows/desktop/… Wenn Array, möchte ich auch die Antwort wissen.
Schlange5
Ja, ich meinte ein Array von Bytes, 3 Bytes pro Pixel RGB-Informationen. Genau das gleiche wie glReadPixels in openGL. Ich meinte eine RAW-Bitmap. Ich werde die Frage aktualisieren, um es klarer zu machen
Cloudraven
Diese Funktion, die Sie erwähnen, speichert sie im Speicher. Ich kann nur den d3x-Puffer lesen, den sie erhält. Ich sollte es versuchen, wenn es schnell genug ist, kann ich es einfach als Lösung akzeptieren. (Dennoch, wenn jemand einen schnelleren Weg kennt, um den Bildschirm zu erfassen, ist es sehr willkommen)
Cloudraven

Antworten:

6

Ich mache das wie folgt.

IDirect3DDevice9 :: GetBackBuffer : Erhalten Sie Zugriff auf das IDirect3DSurface9, das den Backpuffer darstellt, wie Sie es derzeit haben. Vergessen Sie nicht, diese Oberfläche freizugeben, wenn Sie fertig sind, da dieser Aufruf den Referenzzähler erhöht!

IDirect3DSurface :: GetDesc : Ruft die Beschreibung der hinteren Pufferoberfläche ab , die Ihnen die Breite, Höhe und das Format angibt .

IDirect3DDevice9 :: CreateOffscreenPlainSurface : Erstellen Sie ein neues Oberflächenobjekt in D3DPOOL_SCRATCH. Normalerweise möchten Sie dieselbe Breite, Höhe und dasselbe Format verwenden (dies ist jedoch bei dieser Methode nicht erforderlich). Wieder freigeben, wenn Sie fertig sind. Wenn Sie diesen Vorgang in jedem Frame ausführen (in diesem Fall sollten Sie besser nach Alternativen suchen, z. B. nach einem Shader-basierten Ansatz für das, was Sie tun möchten), können Sie die Offscreen-Oberfläche einfach einmal beim Start erstellen und wiederverwenden es, anstatt es jedes Bild zu erstellen.

D3DXLoadSurfaceFromSurface : Kopieren von der hinteren Pufferoberfläche auf die Offsceen-Ebene. Dadurch wird automatisch eine Größen- und Formatkonvertierung für Sie durchgeführt. Wenn Sie das Format nicht ändern möchten oder müssen, können Sie alternativ IDirect3DDevice9 :: GetRenderTargetData verwenden . Wenn dies jedoch der Fall ist , erstellen Sie stattdessen die Offscreen-Oberfläche in D3DPOOL_SYSTEMMEM.

IDirect3DSurface9 :: LockRect : Erhalten Sie Zugriff auf die Daten in der Offscreen-Ebene und haben Sie Ihren eigenen bösen Weg damit; UnlockRect wenn fertig.

Das sieht nach viel mehr Code aus, aber Sie werden feststellen, dass es genauso schnell ist wie glReadPixels und sogar schneller sein kann, wenn Sie keine Formatkonvertierung durchführen müssen (was glReadPixels mit GL_RGB mit ziemlicher Sicherheit tun).

Bearbeiten, um hinzuzufügen: einige (überlegte und fertige) Hilfsfunktionen, die ich auch habe, die für die Verwendung dieser Methode für Screenshots nützlich sein können:

// 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);
        }
    }
}
Maximus Minimus
quelle
Ich mache es eigentlich jeden Frame. Ich binde die Present-Funktion ein und lasse dann den Frame in meinem Hook erfassen. Könnten Sie bitte näher darauf eingehen, einen Shader dafür zu verwenden, das könnte das sein, wonach ich suche. Vielen Dank für Ihre gründliche Antwort.
Cloudraven
1
Ein Shader-basierter Ansatz ist nur geeignet, wenn Sie das aufgenommene Bild anschließend rendern. Aus Ihrer Beschreibung geht hervor, dass Sie eher Videoaufnahmen machen, ja? Können Sie das bestätigen?
Maximus Minimus
Sie haben richtig geraten, aber ich mache nicht gerade Videoaufnahmen, es ist sehr nah. Ich codiere nicht alle Frames (aber genug, um eine gute Leistung zu erzielen), und ich wende vor dem Codieren einige 2D-Filter auf das aufgenommene Bild an.
Cloudraven
Ich habe meinen Code geändert, um BGRA zu verwenden. Es wurde besser. Vielen Dank für den Einblick
Cloudraven
Übrigens habe ich eine Folgefrage zum Shader-Ansatz gestellt. Ich wäre Ihnen dankbar, wenn Sie sich das ansehen würden. gamedev.stackexchange.com/questions/44587/...
cloudraven
1

Ich glaube, Sie müssen LockRectangle auf dem pBackBuffer und dann die Pixel aus D3DLOCKED_RECT.pBits lesen . Das sollte ziemlich schnell gehen. Wenn Sie nur wenige Pixel lesen müssen, sollten Sie den gesamten Backbuffer über DX11 CopySubresourceRegion (ich bin sicher, dass es auch in DX9 eine Alternative gibt) in einen kleineren Puffer kopieren. Dies geschieht auf der GPU und streamen Sie dann diesen kleinen Puffer von der GPU auf CPU über LockRectangle - Sie speichern die Übertragung des großen Backbuffers auf dem Bus.

GPUquant
quelle
1
Der Antwort fehlt der wichtigste Teil: Konvertierung. pBits zeigen auf Daten im Oberflächenformat. Es wird höchstwahrscheinlich nicht gleich einem Array von Byte-Triplets (RGB24) sein.
Schlange5
1
Abgesehen von der Formatkonvertierung erfordert dies CreateDevice mit dem abschließbaren Backbuffer-Flag (das in der Dokumentation als Leistungseinbruch gewarnt wird), und das Sperren des Backbuffers führt immer zu einem Pipeline-Stillstand (was ein viel größeres Problem als die Datenübertragung darstellt).
Maximus Minimus
1
Aus diesem Grund habe ich empfohlen, einen Teil des Backbuffers in einen anderen Puffer zu kopieren. Wenn Sie Rohdaten von der Oberfläche lesen können, liegt die Konvertierung in das "Bitmap" -Format bei Ihnen ...
GPUquant
Ich werde es tatsächlich irgendwann während des Prozesses in Yuv420p konvertieren müssen. Verwendet DirectX also ein internes Format, das nicht RGB ist?
Cloudraven
2
Es gibt kein internes RGB-Format - interne Formate sind 32 bpp - selbst in OpenGL bedeutet GL_RGB nur, dass die freien 8 Bit nicht verwendet werden. Siehe opengl.org/wiki/Common_Mistakes#Texture_upload_and_pixel_reads - also werden sowohl OpenGL als auch D3D intern BGRA sein, weshalb die Verwendung von GL_RGB langsam ist - es muss sowohl komprimiert als auch in RGB umgewandelt werden, was ein langsamer Softwareprozess ist.
Maximus Minimus
1

Sie können den Bildschirm nicht mit GetBackbuffer erfassen. Diese Funktion ruft nur einen Zeiger auf den Rückpuffer ab, nicht auf die Daten.

Die richtige Art, wie ich denken kann, ist wie folgt

  1. Erstellen Sie eine Off-Screen-Oberfläche, indem Sie CreateOffscreenPlainSurface aufrufen .
  2. Rufen Sie GetFrontBufferData auf, um das Bildschirmbild auf die Oberfläche außerhalb des Bildschirms zu übertragen
  3. Rufen Sie LockRect der Oberfläche auf, um die Daten in der Oberfläche abzurufen. Eine Oberfläche außerhalb des Bildschirms war unabhängig von ihren Pooltypen immer sperrbar.
zdd
quelle
1
IDirect3DDevice9 :: GetFrontBufferData - " Diese Funktion ist von Natur aus sehr langsam und sollte in keinem leistungskritischen Pfad verwendet werden " - msdn.microsoft.com/en-us/library/windows/desktop/…
Maximus Minimus
1
Ja, es ist langsam, aber AFAIK, es ist die einzige Möglichkeit, das Bildschirmbild von DirectX abzurufen, oder Sie können eine Verbindung herstellen, bevor Sie die EndScene-Funktion aufrufen.
zdd
Vielen Dank! Ich mache es tatsächlich als Hook, aber nicht für EndScene, sondern für Present, und ich binde speziell an directX-Anwendungen. Angesichts dessen gibt es etwas Klügeres, das ich dagegen tun kann.
Cloudraven
@cloudraven, ich bin nicht mit Hooking vertraut, aber Sie können sich diese Seite als Referenz ansehen. stackoverflow.com/questions/1994676/…
zdd
1

Ein bisschen spät. Aber hoffe das hilft jemandem.

Erstellen

        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)))

Erfassen

    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();

In buf haben Sie Rohbilddaten. Vergiss nicht, alles freizugeben.

vik_78
quelle