4 Stimmen

Kann ein Konstantenpuffer für viele Objekte verwendet werden?

Ich bin neu in Direct3D 11 und habe Schwierigkeiten zu verstehen, wie ich konstante (und andere) Puffer objektweise aktualisieren kann. Ich habe einen einfachen Code, in dem ich versuche, zwei Quadrate auf den Bildschirm zu zeichnen, aber an verschiedenen Positionen. Hier ist der Code, den ich benutze, um sie zu zeichnen.

// ------------------------------------------------------------------------------------------------------------------------------
void QuadShape::UpdateBuffers(ID3D11DeviceContext* pContext)
{
  // Wir müssen über unsere Vertices Bescheid wissen + die konstanten Puffer setzen...
  // HINWEIS: Wir müssen das nur tun, wenn sich die Pufferdaten tatsächlich ändern...
  XMMATRIX translate = XMMatrixTranspose(XMMatrixTranslation(X, Y, 0));
  XMStoreFloat4x4(&ConstData.World, translate);

  D3D11_MAPPED_SUBRESOURCE mappedResource;
    ZeroMemory(&mappedResource, sizeof(D3D11_MAPPED_SUBRESOURCE));

  pContext->Map(ConstBuf, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
  memcpy(mappedResource.pData, &ConstData, sizeof(ObjectConstBuffer));
  pContext->Unmap(ConstBuf, 0);

}

// ------------------------------------------------------------------------------------------------------------------------------
void QuadShape::Draw(ID3D11DeviceContext* pContext)
{
  UpdateBuffers(pContext);
  pContext->DrawIndexed(_VertCount, _StartVert, 0);
}

Sie sehen, dass ich eine Translationsmatrix basierend auf der aktuellen X/Y-Position des Objekts berechne und diese in den konstanten Puffer des Objekts, bezeichnet als 'ConstBuf', mappe. Das Problem, das ich habe, ergibt sich daraus, dass alle Quadrate letztendlich an derselben Position gezeichnet werden, obwohl ich überprüft habe, dass die für jedes berechneten Matrizen tatsächlich unterschiedlich sind und dass die Puffer tatsächlich dynamisch sind.

Ich vermute, dass das Problem darin besteht, dass die gemappten Ressourcen einfach mit der letzten Matrix überschrieben werden, aber ich dachte, dass MAP_WRITE_DISCARD dies vermeiden sollte. Ich bin verwirrt, wie kann ich einen anderen konstanten Puffer pro Objekt verwenden und sie an einer anderen Position anzeigen lassen?

12voto

Roger Rowland Punkte 25785

Sie sollten Ihre Konstantenpuffer nach Aktualisierungshäufigkeit gruppieren, sodass Sie Daten, die sich pro Objekt ändern, in einem Konstantenpuffer platzieren. Wenn Sie andere Daten haben - wie eine Projektionsmatrix -, die sich nur ändern, wenn das Fenster neu skaliert wird, platzieren Sie diese in einem anderen Konstantenpuffer.

Beispielweise definieren Sie zwei solche Puffer wie folgt:

struct CBPerObject
{
    XMMATRIX mWorld;    // Weltmatrix
};

struct CBChangeOnResize
{
    XMMATRIX mProjection;     // Projektionsmatrix
};

Erstellen Sie dann die Konstantenpuffer und behalten Sie eine Referenz auf sie in Member-Variablen:

CComPtr m_pCBPerObject;        // dx11 Konstantenpuffer (pro Objekt)
CComPtr m_pCBChangeOnResize;   // dx11 Konstantenpuffer (ändern bei Skalierung)

Erstellungscode (Fehlerbehandlung ausgelassen für Klarheit):

// Konstantenpuffer erstellen
D3D11_BUFFER_DESC pBuffDesc;
ZeroMemory(&pBuffDesc, sizeof(pBuffDesc));
pBuffDesc.Usage = D3D11_USAGE_DEFAULT;
pBuffDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
pBuffDesc.CPUAccessFlags = 0;

// Änderungen pro Objekt
pBuffDesc.ByteWidth = sizeof(CBPerObject);
m_pDevice->CreateBuffer(&pBuffDesc, nullptr, &m_pCBPerObject);

// Änderungen bei Skalierung
pBuffDesc.ByteWidth = sizeof(CBChangeOnResize);
m_pDevice->CreateBuffer(&pBuffDesc, nullptr, &m_pCBChangeOnResize);

Jetzt können Sie sie nur einmal während der Initialisierung binden (vorausgesetzt, das Layout ändert sich nicht):

// Konstantenpuffer ändern sich nie in Shadern
pContext->VSSetConstantBuffers(0, 1, &m_pCBPerObject.p);
pContext->VSSetConstantBuffers(1, 1, &m_pCBChangeOnResize.p);

Sie müssen möglicherweise auch an den Pixelpshader binden, aber verwenden Sie stattdessen PSSetConstantBuffers.

Aktualisieren Sie jetzt nur die Konstantenpuffer bei Bedarf. Zum Beispiel, wenn das Fenster neu skaliert wird:

void CMyClass::OnSize()
{
    // Projektionsmatrix aktualisieren
    CBChangeOnResize cbBuffer;

    // Berechnen Sie die Projektionsmatrix - zum Beispiel:
    XMMATRIX mProjection = XMMatrixOrthographicOffCenterLH(fLeft, fRight, fBottom, 
                                                           fTop, 0.1f, 1000.0f);
    cbBuffer.mProjection = XMMatrixTranspose(mProjection);

    // Konstantenpuffer aktualisieren
    pContext->UpdateSubresource(m_pCBChangeOnResize, 0, nullptr, &cbBuffer, 0, 0);
}

Ebenso, beim Zeichnen, aktualisieren Sie die Weltmatrix für jedes Objekt:

void CMyClass::DrawScene()
{
    // Szene komplett zeichnen
    CBPerObject cbBuffer;

    // ... Render-Target löschen usw.

    // für jedes Objekt ...
    {
        cbBuffer.mWorld = XMLoadFloat4x4(pObject->GetWorld());

        // CB aktualisieren und Objekt zeichnen
        pContext->UpdateSubresource(m_pCBPerObject, 0, nullptr, &cbBuffer, 0, 0);
        pContext->DrawIndexed(6, 0, 0);
    }

    // ... usw.
}

Es ist also nicht erforderlich, die Konstantenpuffer erneut zu binden, es sei denn, das Layout ändert sich, und Sie können UpdateSubresource anstelle von Map / Unmap verwenden, wie oben gezeigt.

In Ihrem Vertex- (Pixel-)Shader definieren Sie die Konstantenpuffer gemäß dem Slot, an dem sie gebunden wurden:

cbuffer cbPerObject : register (b0)
{
    matrix mWorld;
};

cbuffer cbChangeOnResize : register (b1)
{
    matrix mProjection;
};

In der Syntax: register (b0) und register (b1) beziehen sich b0 und b1 auf das erste Argument, das an VSSetConstantBuffers übergeben wurde.

0voto

jaho Punkte 4706

Sie müssen den Konstantenpuffer innerhalb Ihres Shaders aktualisieren. Fügen Sie am Ende Ihrer UpdateBuffers-Methode Folgendes hinzu:

pContext->VSSetConstantBuffers(0, 1, ConstBuf);

Stellen Sie außerdem sicher, dass Ihre Puffer auf eine 16-Byte-Grenze ausgerichtet sind: http://msdn.microsoft.com/en-us/library/bb509632%28v=vs.85%29.aspx

Die Leistungseinbuße ist wahrscheinlich nicht so groß, wie Sie sich vielleicht vorstellen, insbesondere im Vergleich zu anderen teuren Operationen, die Sie pro Frame durchführen (Ich werde es tatsächlich aus Neugier testen und Ihnen die Ergebnisse mitteilen), und dies ist ein ziemlich standardmäßiger Weg, es zu tun. Wenn Sie mir nicht glauben wollen, werfen Sie einen Blick auf diesen Artikel von Frank Luna, der unter anderem Konstantenpuffer ziemlich gut erklärt und auch gängige Strategien für den Umgang mit ihnen bereitstellt:

http://d3dcoder.net/Data/Resources/d3d11Metro.pdf

Wenn Sie sich wirklich Sorgen machen, dass Sie *SetConstantBuffers pro Objektbasis aufrufen (z. B. haben Sie viele unabhängig bewegliche Objekte), können Sie es einmal pro Frame binden und dann einen Zeiger auf den gebundenen Puffer für jedes Objekt verwenden.

Ein Weg ist die Verwendung einer Shader-Klasse für diesen Zweck, die alle shaderbezogenen Ressourcen umschließt sowie das Zeichnen der Objekte, also würde ich in meiner Hauptaktualisierungsmethode Shader::bindBuffers() einmal aufrufen und dann Shader::Render(Object*) für jedes Objekt zusammen mit seinen Parametern aufrufen, einschließlich Matrizen. Innerhalb von Shader::Render() verwende ich die zuvor gebundenen Puffer und aktualisiere sie mit map/unmap usw.

Ein anderer Ansatz ist es, Objekte sich selbst zeichnen zu lassen, wie Sie es tun, aber einen Zeiger auf den Konstantenpuffer, der als Mitglied der aufrufenden Klasse gespeichert ist, an die Rendermethode (also UpdateBuffers in Ihrem Fall) weiterzugeben. Ein ähnlicher Ansatz ist in diesem Beispiel von Microsoft zu sehen:

http://code.msdn.microsoft.com/windowsapps/Metro-style-DirectX-7c64aa8d/sourcecode?fileId=50992&pathId=642179348

Diese sind jedoch Optimierungen, und Sie sollten sich im Allgemeinen nicht vorzeitig darum kümmern.

0voto

Sie müssen "update-bind-draw" für jedes Objekt in diesem Fall ausführen. Pseudocode:

foreach(object in objects)
{
   constantBuffer.update(object.position);
   device.bind(constantBuffer);
   device.draw();
}

Ihr Code fehlt Bind-Aufrufe.

Auf diese Weise erhalten Sie für N Objekte N Pufferaktualisierungen, N Bindungen und N Draw-Aufrufe, was furchtbar ineffizient ist, wenn Sie viele Objekte haben (Sie verbringen viel Zeit im Treiber, haben CPU-GPU-Synchronisationspunkte, der Speicherbandbreite wird durch redundante Datenübertragungen belegt). Unnötige Bindungsaufrufe können jedoch durch eine anständige Treiberimplementierung eliminiert werden.

<b>Update</b><br>Es gibt verschiedene Ansätze, die Speicher- und Codekomplexität gegen die Laufzeitleistung abwägen. Insbesondere gibt es <em>"Batching"</em> und eine Familie von Techniken, genannt <em>"Geometry Instancing"</em>:<br><br>1. <strong>Batching</strong> kann beschrieben werden als Zusammenführen von vorab transformierten Vertices aller Objekte in einem Vertex-Puffer, anstelle sie im Shader zu transformieren. Sie sparen beträchtlich Zeit für den Vertex-Shader ein, aber wenn irgendwelche Attribute des Objekts (z.B. Position) sich ändern, müssen Sie alle Vertices des Objekts <em>auf der CPU</em> (!) neu berechnen und den Vertex-Puffer teilweise aktualisieren. Es eignet sich am besten für statische Objekte (z.B. die sich selten oder gar nicht bewegen) und kleine Objekte (nicht viele zu aktualisierende Vertices). Wird oft verwendet, um 2D-Inhalte zu zeichnen: Sprites, GUI und Text.<br><br>2. <strong>Hardware-Geometry-Instanzierung</strong>. Sie benötigen einen zusätzlichen Vertex-Puffer, der Instanzattribute anstelle von Vertexattributen speichert (wir nennen ihn Instanzpuffer). Sie müssen das Eingabelayou ändern und per-Instanzattribute hinzufügen. Sie aktualisieren den Instanzpuffer, wenn sich Objekte ändern, genauso wie Sie es mit jedem Vertex-Puffer tun. Sie zeichnen mit <code>DrawInstanced*()</code> Aufrufen. Es ist eine beliebte Technik, um das Zeichnen vieler dynamischer Objekte zu optimieren, die sehr ähnlich sind (Gras, Blätter, Meteoriten, Straßenverkehr, Fans in einem Fußballstadion usw.). Mehrere fortgeschrittene Tricks ermöglichen es, es sogar auf sehr unterschiedlich aussehende Objekte anzuwenden.<br><br>3. <strong>Software-Geometry-Instanzierung</strong>. Implementiert von Hand <em>Hardware-Geometry-Instanzierung</em>. Kann verwendet werden, wenn Ihr Zielhardware keine Hardware-Instanzierung unterstützt (sehr alte Desktop- und einige mobile Geräte) oder wenn Sie eine fortgeschrittene Kontrolle wünschen. Sie können einfach ein Indexattribut zu jedem Vertex hinzufügen und als Instanzinformationsschutz entweder einen großen Konstantenpuffer (für alte Geräte) oder Textur oder Strukturbuffer (auf neueren Geräten) verwenden, etc.<br><br>Referenzen:<br><a href="http://en.wikipedia.org/wiki/Geometry_instancing" rel="nofollow">Geometry-Instanzierung bei Wikipedia</a><br><a href="http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter03.html" rel="nofollow">Kapitel 3. Inside Geometry Instancing</a> (Achtung: enthält einige alte Informationen)<br><a href="http://www.rastertek.com/dx11tut37.html" rel="nofollow">rastertek.com - Tutorial 37: Instancing</a><br><a href="http://www.rastertek.com/dx11tut37.html" rel="nofollow">braynzarsoft.net - Lektion 32: Direct3D 11 Instancing</a><br><a href="http://developer.download.nvidia.com/SDK/10.5/direct3d/samples.html" rel="nofollow">NVIDIA Direct3D SDK 10 Code Samples</a>: Instancing-Tests, Skinned Instancing (Download <a href="https://developer.nvidia.com/directx" rel="nofollow">SDKs</a>)<br><a href="http://www.microsoft.com/en-us/download/details.aspx?id=6812" rel="nofollow">DirectX SDK-Beispiel</a> "Instancing10"<br><br><strong>Update</strong><br>Beachten Sie, dass während Instanzierung und Batching großartig sind, bedeutet dies nicht, dass Sie Konstantenpuffer auslassen müssen. Wie in der Antwort von <em>Roger Rowland</em> erklärt, gibt Ihnen eine intelligente Gruppierung von Konstanten in mehreren Fällen gute Ergebnisse, wie unveränderliche Konstanten, Konstanten, die von vielen Objekten gemeinsam genutzt werden, wie Kamerakonstanten usw. Siehe "<a href="https://developer.nvidia.com/gdc-2012" rel="nofollow">Don't Throw it All Away – Managing Buffers Efficiently</a>" NVIDIA-Präsentation über den genauen Umgang mit Puffern

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X