[D3D 11] Wie viele Constant Buffers?

Für Fragen zu Grafik APIs wie DirectX und OpenGL sowie Shaderprogrammierung.
Antworten
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

[D3D 11] Wie viele Constant Buffers?

Beitrag von Krishty »

Man soll Constant Buffers nach Änderungshäufigkeit sortieren. Okay.

Ich habe hier eine Szene mit 100.000 Objekten und 10.000 verschiedenen Materialien (Ambient, Diffuse, …). Die Transformationsmatrizen ändern sich fast nie. Die Materialien ändern sich noch viel seltener. Wir können also erstmal übereinkommen, dass Transformationsmatrizen und Materialdaten in getrennte Constant Buffers gehen.

Soll ich jetzt 100.000 Constant Buffers mit Transformationsmatrizen anlegen? Oder nur einen, und den in jedem Frame 100.000 Mal überschreiben? Soll ich 10.000 Constant Buffers mit Materialdaten anlegen? Oder nur einen, den ich in jedem Frame 10.000 Mal überschreibe?

Dauerndes Überschreiben geht irgendwie dem Sinn von Buffern zuwider. (Es soll ja auf der GPU gepuffert bleiben, so lange es sich nicht ändert. Der große Unterschied zu D3D 9, wo man jeden Frame alle Konstanten neu zur GPU geschickt hat.) Andererseits erscheint mir das exzessive Erzeugen von Constant Buffers bedenklich. Dann wiederum finde ich aber keinen Hinweis in den Resource Limits, dass das irgendwie nach oben begrenzt wäre.

Ich habe jetzt testweise 10.000 Constant Buffers für die Materialien erzeugt, und das läuft richtig gut. (Zig Megabyte weniger Working Set pro Frame!) Mache ich alles richtig, oder wirft gerade jemand die Hände über dem Kopf zusammen?
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
kimmi
Moderator
Beiträge: 1405
Registriert: 26.02.2009, 09:42
Echter Name: Kim Kulling
Wohnort: Luebeck
Kontaktdaten:

Re: [D3D 11] Wie viele Constant Buffers?

Beitrag von kimmi »

In einem Talk eines Unreal-Engine-Engineers habe ich zu dem Thema mal die folgende Antwort bekommen: "Strongly depends on you current situation and environment."
Wenn du trotz der Switches zwischen den Constant-Buffern keinen signifikanten Performance-Bottlenecks findest, hat es für dein Szenario hin. Ansonsten könntest du die jeweiligen Buffer vermutlich noch etwas in Cluster unterteilen, um zusammen gehörende Gruppen besser andressieren zu können.
Aber wenn es läuft: warum nicht :).

Gruß Kimmi
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [D3D 11] Wie viele Constant Buffers?

Beitrag von Krishty »

Hmmm; okay. Nvidia sagt (https://developer.nvidia.com/content/co ... ant-pain-0 ), dass man mit XXSetConstantBuffers() + DrawIndexed() doppelt so viel gerendert kriegt wie mit Map(DISCARD) + Unmap() + DrawIndexed() + cache miss (Kopieraufwand noch nicht einberechnet), und bisher deckt sich das mit meiner Leistung.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [D3D 11] Wie viele Constant Buffers?

Beitrag von Schrompf »

Ich hatte Constant Buffers so verstanden, dass es nur kleine kontinuierliche Blöcke GPU-Speicher sind, und in einem Descriptor Block (oder wie der hieß) liegt ein Zeiger darauf. Und da in Vulkan und DX12 ja jeder DrawCall nur noch ein Gesamt-Satz an solchen Zeigern ist, *müsste* es völlig ok sein, hundertausende winzige Constant Buffers rumliegen zu haben.

Der alte C++-Bastler in mir will einen Custom Allocator dafür schreiben, aber ich glaube, wir haben immer noch keinen Direktzugriff auf die Speicher und Zeiger der GPU.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
kimmi
Moderator
Beiträge: 1405
Registriert: 26.02.2009, 09:42
Echter Name: Kim Kulling
Wohnort: Luebeck
Kontaktdaten:

Re: [D3D 11] Wie viele Constant Buffers?

Beitrag von kimmi »

Mit Vulkan sollte das gehen :). DX11? Ich weiß ja nicht ...
David_pb
Beiträge: 18
Registriert: 23.07.2014, 20:50

Re: [D3D 11] Wie viele Constant Buffers?

Beitrag von David_pb »

Hi,

du musst bedenken was im Hintergrund passiert wenn du einem Puffer überschreibst: Der Speicher kann in diesem Fall ja nicht wirklich überschrieben werden, weil die GPU den Inhalt (i.A.) noch gar nicht verarbeiten konnte. Würde der Speicher überschrieben werden dann hätte man ein Szenario, wo unterschiedliche Objekte mit den selben Konstanten gerendert werden würden. Aus diesem Grund wird der Puffer "umbenannt", der Treiber alloziert also einen neuen Speicherbereich und packt diesen hinter den Handle des Puffers. Das Update geht dann also in den neuen Speicherbereich, so das vorher abgesetzte Drawcalls/Dispatches ihre eigene Version des CBuffers behalten. Im Endeffekt ist der Speicherfootprint also ähnlich, hat aber einen gewissen Managementoverhead auf Treiberseite. Dort wird nämlich häufig versucht den Prozess zu optimieren, z.B. indem der Treiber bereits im Vorfeld einen größeren Speicherblock reserviert und dann solang einen Offset verschiebt bis der Block aufgebraucht ist. Im Idealfall sind beim nächsten Discard die ersten Bereiche bereits von der GPU konsumiert, so das eine Anfrage beim Speichermgmnt effektiv verhindert wird (unwahrscheinlich bei deinem Szenario). Das Pattern kennt man z.B. auch bei Streamingbuffern, wenn beispielsweise Instanzdaten gesammelt, oder Partikeldaten von der CPU geschrieben werden. In diesem Fall würde man einen Puffer reservieren der WorstCaseGröße * MaximaleLatenz groß ist und jeweils mit NO_OVERWRITE mappen + offsetten bis ein Überlauf entsteht, ansonsten mit DISCARD um den Puffer zu resetten. Im Idealfall würde der Puffer also nie wirklich 'renamed' werden.

Ein weiteres potentielles Problem sind Cacheflushes/Pipelinesyncs die ggf notwendig sind, bevor die Daten von der GPU gelesen werden können.

Im Falle von sehr häufigen Updates kann der Overhead groß werden, so das du Puffer nicht sharen solltest (manche APIs bieten die Möglichkeit solche Konstanten direkt in den Commandbuffer zu schreiben, z.B. über Rootkonstanten in DX12). Für Puffer mit weniger hohen Update-Frequenz ist der Overhead häufig vertretbar. In vielen Fällen sind die Konstanten aber tatsächlich unveränderlich (z.B. häufig bei Materialparametern o.Ä.). In diesen Fällen kann es Sinn machen die Daten offline in einzelne Puffer zu backen und einfach zu binden. Das gibt den Treiber auch die Möglichkeit die Daten in GPU 'nahmen' Speicher (z.B. VRAM) zu lagern, weil kein CPU Zugriff notwendig ist.

Ein Allokationsschema für CBuffer mit DX11 ist schwierig, weil 'partial' Updates nicht möglich sind (DX11.1 bietet das an). Ein CBuffer-Pool ist allerdings eine Möglichkeit die ggf am nächsten an die Idee eines Sperichermanagement ran kommt. DX12/Vulkan sind da etwas flexibler, Zugriff auf Speicheradressen hat man zwar auch dort nicht, ist aber für einen Allokator auch nicht notwendig. Ich hatte in einem Spiel mal gesehen, das statt CBuffern sämtliche Konstanten über TBuffer an die GPU gefüttert wurden um eine art Speicherverwaltung zu ermöglichen. Ist sicher auch eine Möglichkeit. :-)
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [D3D 11] Wie viele Constant Buffers?

Beitrag von Krishty »

Danke!
(manche APIs bieten die Möglichkeit solche Konstanten direkt in den Commandbuffer zu schreiben, z.B. über Rootkonstanten in DX12)
Das kann ich klären: Der oben verlinkte Nvidia-Artikel sagt, dass auch unter D3D 11 alle Constant Buffer Updates direkt in den Command Buffer kopiert werden, und der ist auf 128 MiB limitiert (danach Flush & Stall). Eine API-Garantie ist das aber natürlich nicht.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [D3D 11] Wie viele Constant Buffers?

Beitrag von Krishty »

Entschuldigt die späte Rückmeldung, aber ich bin erst jetzt zum Testen gekommen. (Mit Materialien haben die vielen Buffer ja gut funktioniert; nun wollte ich die Transformationen ebenfalls umstellen.)

Vorher: Ein globaler Constant Buffer, der pro Objekt gemappt wird und die Transformationsmatrizen transportiert.

Nachher: Für jedes Objekt ein eigener Constant Buffer im Layout

  float4x4 objectToWorld;
  float3x3 normalsToWorld;


der zur Laufzeit nur noch via VSSetConstantBuffers() gebunden wird.

Bild

Das Ergebnis mit 30.000 Objekten war katastrophal:
  • zehn Sekunden zusätzliche Ladezeit nur zum Initialisieren der Constant Buffers (Rest lädt in unter zwei Sekunden!)
  • 1500 MiB zusätzlicher Speicherverbrauch – bei nur 112 * 30.000 = 3,4 MiB Nutzdaten! Das entspricht 50 KiB Overhead pro Constant Buffer.
  • Framerate ungefähr geviertelt.
Ich hab’s nur auf einem Nvidia-System getestet, aber das reicht wohl schon, um es als Schuss in den Ofen zu deklarieren. D3DUSAGE_DYNAMIC, D3DUSAGE_DEFAULT, und D3DUSAGE_IMMUTABLE haben dabei nichts ausgemacht.

Ich möchte weiterhin vermeiden, alle Konstanten in jedem Frame neu auf die GPU zu laden. Soll ich einen Structured Buffer ausprobieren, auf den jedes Objekt via Index zugreift? Ich wollte das eigentlich nicht machen, weil das uniforme Lesen aus Constant Buffers viel schneller sein soll. Ich würde gewissermaßen die Upload-Zeit sparen, und dafür in den Shadern wesentlich höhere Latenz haben.

Alternativ würde ich direkt zu Deferred Contexts gehen um zu prüfen, ob eine aufgezeichnete Sequenz von Map() und Draw() schneller ist.

Nachtrag: Heiliger Bimbam, dieses Diagramm ist ja nicht gerade vielversprechend:
Bild
Achtfach parallel in Deferred Contexts schreiben ist bloß 20 % schneller als alles in einem Thread im Immediate Context zu machen?!

Da geht es um den Fall, dass sich alle Objekte in jedem Frame bewegen. Bei mir geht es eher darum, dass alles statisch ist und ich deshalb einfach einmal alle Draw Calls aufzeichnen könnte, statt sie in jedem Frame zu wiederholen. Mein Gedanke war: „Hey, dann spult der Treiber die aufgezeichnete Liste direkt auf der GPU ab! Superschnell!“ Das Diagramm suggeriert aber eher, dass die Befehle auf API-Ebene aufgezeichnet und abgespult werden, statt auf Treiber-Ebene. Das einzige, was ich gegenüber meiner jetzigen Render Loop sparen würde, wären die paar ifs, mit denen ich auf redundante Texturen prüfe. Da vergeht mir direkt die Lust.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
unbird
Beiträge: 9
Registriert: 17.12.2015, 08:35

Re: [D3D 11] Wie viele Constant Buffers?

Beitrag von unbird »

Hab beim experimentieren festgestellt, dass StructuredBuffers und VTF ok sind. Vermutlich gar nicht mal so schlecht für World-Transformationen. Bei constant buffers ist die Idee, dass der Shader auf ALLE Daten möglichst schnellen zugriff hat (weiss grad nicht mehr den genauen GPU-Term, aber sowas wie cascade, d.h. alle Kernels bekommen die Daten zugeschoben). Für Instanz-Daten "bleibt" der VS ja ne Zeitlang bei derselben World-Transformation, d.h. die kommen dann aus dem Texturecache.

Siehe unten Screenshots einer Animation von ca. 4000 Objekten (Ich hab grad die Version nicht, wo ich gesamthaft noch viel mehr Objekte hatte, allerdings nich alle animierte). Animation ist ohne GPU Hexerei, auf CPU die 4x4 Matrizen berechnen. Danach ein Upload zu einem Stage.

Anschliessend kann man den dann auf den eigentlichen Buffer mittels CopySubResourceRegion kopieren. Evtl. punktuell nötig, also mehrere Aufrufe. Ich ging allerdings einen noch indirekteren Weg: Scattering der Daten per PS (oder per CS). Damit kopiert man ergo nur Zeugs rum, das wirklich geändert hat.

Hier gab's ein kleines Problem bez. API (weil das drei instanced draw calls mit unterschiedlichen Meshes sind): SV_InstanceID fängt immer bei 0 an, auch wenn man offsets benutzt. Dazu legt man halt künstlich einen Instance-Stream mit integers an (0,1,2,3,4,5). Auch fürs Scattering hat man dann einen dynamischen integer buffer (zum festlegen wo die einzelnen updates hingehen sollen, scattering eben).

Falls Du nur statisches Zeugs hast, ist das natürlich alles viel einfacher. Vielleicht hinkt der Vergleich aber auch, weil ich sehr polygonarme Meshes habe, und wenig draw calls. Aber ein Versuch ists schon nur Wert, weil Du statt 30000 nur zwei bis vier Buffers brauchst. Meine App startet nach ca. 1 sekunde (C# / SlimDX, erst noch im DX debug mode, AMD Phenom, GTX960, 60 FPS).
Dateianhänge
ScreenShot4.jpg
ScreenShot3.jpg
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [D3D 11] Wie viele Constant Buffers?

Beitrag von Krishty »

Erste Sahne; danke für die Ausführung! Dann weiß ich jetzt, was zu tun ist. Der Umbau ist nur leider etwas zu groß für heute abend :(
unbird hat geschrieben:Bei constant buffers ist die Idee, dass der Shader auf ALLE Daten möglichst schnellen zugriff hat (weiss grad nicht mehr den genauen GPU-Term, aber sowas wie cascade, d.h. alle Kernels bekommen die Daten zugeschoben). Für Instanz-Daten "bleibt" der VS ja ne Zeitlang bei derselben World-Transformation, d.h. die kommen dann aus dem Texturecache.
Naja; Constant Buffers haben halt geringe Latenz, so lange alle Threads genau gleichzeitig auf die selben Daten zugreifen. Cascaded ist AFAIK, wenn alle Threads auf *unterschiedliche* Daten zugreifen, und da sind Structured Buffers perfekt. Nvidia sagt sogar explizit, dass man Bandbreite und Latenz verschwendet, wenn alle Threads von der selben Stelle eines Structured Buffers lesen. Der Knackpunkt ist wohl, dass Latenz != Stall, und dass die Latenz nichts ausmacht, so lange die GPU in der Zwischenzeit andere Dinge zum Ausführen findet.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
unbird
Beiträge: 9
Registriert: 17.12.2015, 08:35

Re: [D3D 11] Wie viele Constant Buffers?

Beitrag von unbird »

Keine Ursache. Und Danke für die Klarstellung ich hatte das tatsächlich falsch rum in Erinnerung.

Den Ausdruck den ich suchte heisst übrigens "constant waterfalling" (Structured Buffers vs. Constant Buffers). Man muss die Ausdrücke kennen um sinnvoll gurgeln zu können ;)

Edit : Das mit dem Speicherverbrauch ist wirklich komisch. Klingt, als würde pro CB fast das Resourcenlimit von 64k reserviert ( 1500 MB / 30000 = ca. 50k ). Weiss jemand etwas darüber ? Ist das Treiber/Hardwarespezifisch ?
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [D3D 11] Wie viele Constant Buffers?

Beitrag von Krishty »

Sooo; ich habe mich mal durchgebissen …
unbird hat geschrieben:Hier gab's ein kleines Problem bez. API (weil das drei instanced draw calls mit unterschiedlichen Meshes sind): SV_InstanceID fängt immer bei 0 an, auch wenn man offsets benutzt.
… damit sind auch schon viele andere auf die Schnauze geflogen.
Dazu legt man halt künstlich einen Instance-Stream mit integers an (0,1,2,3,4,5).
… ganz genau so macht das Ogre seit 2.1 für die gesamte Szene und das ist tatsächlich der optimale Weg.

Bei mir musste ich es zum Glück nicht so weit treiben: Die zwei Matrizen, die sich pro Modell ändern, kann ich auch via Vertex Buffer übergeben statt einen Structured Buffer anlegen zu müssen. Also:
  • Einen zusätzlichen Vertex Buffer für die pro-Mesh-Matrizen angelegt.
  • Input Layout um zusätzliche Komponenten erweitert:
    • InputSlot auf 1 gesetzt (also aus zweitem Vertex Buffer in Slot 1 lesen)
    • InputSlotClass auf D3D11_INPUT_PER_INSTANCE_DATA gesetzt
    • InstanceDataStepRate auf 1 gesetzt
    • einige Online-Tutorials empfehlen, AlignedByteOffset auf 0 zurückzusetzen – ist nicht nötig; D3D berechnet das Alignment pro Slot, und wir haben einen neuen Slot begonnen
  • Protip am Rande: Wenn man seinen Positionsdaten den Namen POSITION gibt, kann der Graphics Debugger von Visual Studio den Vertex Buffer in 3D anzeigen!
  • den zusätzlichen Vertex Buffer in Slot 1 gebunden
  • DrawIndexedInstanced() aufgerufen – StartInstanceLocation ist der Index der Matrix, die benutzt werden soll
… und Bingo, Rendering der gesamten Szene ohne CPU-Uploads. Die Performance hat sich gegenüber Map() eines einzelnen Constant Buffers ein Bisschen verbessert (ich habe gerade noch den Flaschenhals an anderer Stelle), aber die Veränderung ist durchaus spürbar :) Vor allem ist es viel schneller und speicherschonender als hunderttausend Constant Buffers. Je nach Quelle und Architektur sollen Structured Buffers oder Constant Buffers als Speicher für die Daten schneller sein als der Input Assembler, aber ich habe gerade keine Zeit, herumzuexperimentieren.


Nun der nächste Schritt – Objektpositionen mit der CPU verändern:
  • Den Vertex Buffer mit den Matrizen nicht D3D11_USAGE_IMMUTABLE deklarieren sondern D3D11_USAGE_DEFAULT
  • Wenn ein Objekt verschoben wird, via UpdateSubresource() die neuen Matrizen in den Vertex Buffer schreiben
  • ???
  • PROFIT!!!
Auch hier munkelt man wieder, dass Map() schneller sein könnte, aber damit kann man nur komplette Puffer überschreiben. Staging wäre auch eine Möglichkeit. Wie auch immer – ich habe gerade keine Zeit zu experimentieren.


Als nächstes fasse ich die Object-to-World und World-to-Screen-Matrizen im Compute Shader zusammen statt im Vertex Shader der Objekte.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten