Frage

Ich brauche, um herauszufinden, wie die Daten von D3D Texturen zu erhalten und Oberflächen zurück in den Systemspeicher. Was ist der schnellste Weg, um solche Dinge und wie zu tun?

Auch wenn ich nur eine subrect benötigen, wie kann man nur den Teil zurückgelesen, ohne dass die ganze Sache in den Systemspeicher zu lesen, wieder mit?

Kurz gesagt: Ich suche kurze Beschreibungen, wie die folgenden Systemspeicher kopieren:

  1. Textur
  2. Untergruppe Textur
  3. Oberfläche
  4. Untergruppe eine Oberfläche
  5. D3DUSAGE_RENDERTARGET Textur
  6. Untergruppe D3DUSAGE_RENDERTARGET Textur

Dies ist Direct3D 9, aber Antworten zu neueren Versionen von D3D zu schätzen würden.

War es hilfreich?

Lösung

Der beteiligte Teil von einer Oberfläche liest, die in dem Videospeicher ist ( „Standard-Pool“). Dies wird am häufigsten Ziele machen.

Lassen Sie uns die einfachen Teile erhalten zuerst:

  1. von einer Textur zu lesen ist die gleiche wie von 0-Ebene Oberfläche dieser Textur zu lesen. Siehe unten.
  2. das gleiche für Teilmenge einer Textur.
  3. Lesen von einer Oberfläche, die in Nicht-Standard-Speicherpool ( „System“ oder „managed“) blockiert gerade ist und das Lesen Bytes.
  4. das gleiche für Teilmenge der Oberfläche. Nur relevanten Teil sperrt und sie lesen.

So, jetzt wir Oberflächen hinterlassen haben, die in dem Videospeicher sind ( „Standard-Pool“). Dies würde als Renderziel jede Oberfläche / Textur markiert sein, oder jede regelmäßige Oberfläche / Textur, die Sie in Standard-Pool erstellt haben, oder die Backbuffer selbst. Der komplexeste Teil hier ist, dass Sie es nicht sperren können.

Kurze Antwort ist: GetRenderTargetData Methode D3D-Gerät.

Lange Antwort (eine grobe Übersicht über den Code, der unten sein wird):

  1. rt = get Zielfläche machen (dies kann Oberfläche der Textur sein oder Backbuffer, etc.)
  2. wenn rt wird gesampelt (GetDesc, überprüft D3DSURFACE_DESC.MultiSampleType), dann: a) erstellen eine andere Renderziel Oberfläche gleicher Größe, gleichen Format, aber ohne Multi-Sampling; b) StretchRect von rt in diese neue Oberfläche; c) rt = diese neue Oberfläche (das heißt auf diese neue Oberfläche gehen).
  3. Aus = create Offscreen ebener Fläche (CreateOffscreenPlainSurface, D3DPOOL_SYSTEMMEM Pool)
  4. Gerät-> GetRenderTargetData ( rt , Aus )
  5. jetzt Aus enthält Zieldaten machen. LockRect () gelesenen Daten, UnlockRect () auf sie.
  6. Bereinigung

Auch längere Antwort (Paste aus der Code-Basis arbeite ich an) folgt. Dieser wird nicht kompiliert aus der Box, weil es verwendet einige Klassen, Funktionen, Makros und Utilities vom Rest der Code-Basis; aber es sollte Sie loslegen. I auch die meisten Fehler weggelassen Überprüfung (z.B. ob gegebene Breite / Höhe außerhalb der Grenzen ist). Ich weggelassen auch den Teil, dass die tatsächlichen Pixel und möglicherweise wandelt sie in geeignete Zielformat liest (das ist ganz einfach, kann aber lange erhalten, je nach Anzahl der Formatkonvertierungen Sie unterstützen möchten).

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 in dem obigen Code ist ein Smartpointer zu einem COM-Objekt (es freigibt Objekt auf Zuordnung bzw. destructor). Vereinfacht Fehler viel Handhabung. Dies ist sehr ähnliche Dinge in Visual C ++ _comptr_t.

Der obige Code liest gesamte Oberfläche zurück. Wenn Sie nur einen Teil davon effizient lesen möchten, dann glaube ich, schnellste Weg ist in etwa:

  1. Erstellen Sie eine Standardpooloberfläche, die der benötigten Größe ist.
  2. StretchRect von einem Teil der ursprünglichen Oberfläche zu diesem kleiner.
  3. gehen Sie wie gewohnt mit dem kleineren.

In der Tat ist dies ganz ähnlich dem, was oben stehenden Code funktioniert Multisample-Oberflächen zu behandeln. Wenn Sie nur einen Teil einer Multisample-Oberfläche erhalten möchten, können Sie eine Multisample Entschlossenheit tun und einen Teil davon in einem StretchRect bekommen, denke ich.

Bearbeiten : entfernten Stück Code, das tatsächliche Lesen von Pixeln und Formatkonvertierungen der Fall ist. War das nicht direkt in Frage bezogen, und der Code war lang.

Bearbeiten . Aktualisiert bearbeitet Frage übereinstimmen

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top