ConstantBuffer Updates sind nicht trivial

LogoEin Thema, was mich zurzeit sehr beschäftigt, ist der korrekte Umgang mit dem ConstantBuffer in Direct3D 11. Der Constant Buffer wird dafür verwendet, um einem Shader fixe Parameter zu übergeben. Diese Parameter sind bei Mosaic Snake 3D etwa die aktuelle Transformation, die Lichtfarbe, die Lichtstärke und noch ein paar andere. Der Ansatz, neue Werte in den ConstantBuffer zu schreiben, ist bei Mosaic Snake 3D dabei relativ primitiv: Es gibt einen CosntantBuffer über das gesamte Programm. Jedes Mal, wenn ein Objekt (z. B. ein Teil der Schlange) gezeichnet wird, wird der ConstantBuffer vorher mit den aktuellen Informationen überschrieben und danach wird gerendert. Bei diesem Spiel hat der Ansatz auch sehr gut funktioniert, allerdings ergeben Tests in einem anderen Szenario ein ganz anderes Bild…

Der Screenshot oben links zeigt, von welchem Szenario ich schreibe. Ich möchte jetzt nicht nur max. ein paar Hundert Objekte, sondern mehrere Tausend zeichnen. Mit dem Weg aus Mosaic Snake 3D würde das bedeuten, dass pro Frame eben diese mehrere Tausend Mal der ConstantBuffer aktualisiert werden muss und danach jeweils gerendert wird. Sieht auf den ersten Blick nicht sehr effizient aus, Techniken wie Instancing würden das auch klar beschleunigen. Auf der anderen Seite war zumindest meine Meinung, dass das auf aktueller Hardware aber trotzdem kein Problem sein sollte. Dem ist aber nicht so, denn wie es sich raus stellt, fängt obige Szene (1.000 Objekte) bei meinem PC (Intel I-7 Prozessor, Radeon 57xx) zu ruckeln an. Nach einer Performance-Analyse mit Visual Studio sieht man auch schnell, wo das Problem liegt: Die ConstantBuffer Updates schlucken sehr viel Performance. Zu sehen ist das auf nachfolgenden Screenshot.

screen_02

Wie schon geschrieben, per Instancing würde sich das klar verbessern lassen. Nach meiner Meinung dürfte aber die Grenze nicht schon bei 1.000 Objekte liegen, wenn man kein Instancing verwendet. Daher habe ich mich mit dem Thema etwas mehr beschäftigt und bis jetzt ein paar interessante bessere Ansätze gefunden. So schlägt etwa dieser Artikel von NVidia vor, die Parameter für die Shader auf mehrere ConstantBuffer aufzuteilen. Demnach wäre eine Trennung zwischen wirklich globalen Daten (z. B. ViewProj-Matrix, Licht, …), Objekt-Daten (z. B. World-Matrix) und evtl. sogar Material-Daten (z. B. Reflektion, …) sinnvoll. Ziel davon ist es, die Anzahl von Updates auf den ConstantBuffer stark zu minimieren. So müsste danach nur dann der ConstantBuffer vor dem Rendern eines Objekts aktualisiert werden, wenn sich auch wirklich dessen World-Matrix verändert hat. In meinem Ansatz oben muss man es demgegenüber zwangsweise immer machen, da sich durch die Kamera die ViewProj-Matrix regelmäßig ändert. Im von NVidia beschriebenen Ansatz wäre dieser Update nur einmal auf dem globalen ConstantBuffer zu Beginn jedes Frames nötig und unabhängig von den Objekt-Parametern

Ein weiterer wichtiger Schritt zur Optimierung habe ich in diesem Forum-Beitrag auf www.indiedev.de diskutiert. Benutzt man einen gemeinsamen ConstantBuffer für alle zu rendernden Objekte, so muss man diesen zwangsweise vor so gut wie jedem Objekt aktualisieren, da z. B. die Transform Eigenschaft normalerweise immer anders ist (das im letzten Absatz beschriebene Vorgehen würde daher nicht die Anzahl der Updates reduzieren). Diesem Thema kann man aus dem Weg gehen, wenn man für jedes Objekt einen eigenen ConstantBuffer mit den reinen Objekt-Daten erzeugt. So wird vor jedem Rendern nur das Setzen eines anderen ConstantBuffers nötig, was die Anzahl an nötigen Updates definitiv reduziert.

Der nächste Schritt von mir wird sein, dieses Vorgehen zu implementieren und entsprechend zu testen. Ob das wirklich einen deutlichen Performance-Gewinn bringt, wird sich zeigen…

Quellen:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.