|
JLI Spieleprogrammierung
|
Vorheriges Thema anzeigen :: Nächstes Thema anzeigen |
Autor |
Nachricht |
Fallen JLI MVP
Alter: 40 Anmeldedatum: 08.03.2003 Beiträge: 2860 Wohnort: Münster Medaillen: 1 (mehr...)
|
Verfasst am: 23.09.2009, 10:47 Titel: LowLevel DeviceCommands oder lieber nicht? |
|
|
Hallo Freunde der hohen Künste,
ich bastel derzeit an einem DX10 basierten Demoframework, oder anders gesagt ich bastel mir eine 3D Engine zusammen (erspart euch die: "mach Spiele keine Engines" floskel ).
Nun stellt sich eine kleine aber feine Designentscheidung und zwar geht es um die allseits beliebte Statesortierung.
Vor einiger Zeit habe ich mal den Ansatz verfolgt jede grobe Stateänderung am DirectX Device zu Kapseln in ein sogenanntes DeviceCommand. So ein DeviceCommand sah so aus das es ein Hash hatte zum eindeutigen identifizieren, eine Typbeschreibung und einige andere Werte, zB die Textur die gesetzt werden soll bei einem SetTextureDeviceCommand.
Das System was ich dazu entworfen hatte sah vor das meine Resourcen, wie zB ein Material oder ein MeshBuffer eine Reihe von DeviceCommands generieren und in einem DeviceCommandBuffer speichern. So sah zB der Buffer eines Materials aus welches ein Shader setzt sowie 2 Texturen (die Texturen unterscheiden sich hier zB durch den Hash im DeviceCommand):
MaterialDeviceCommandBuffer:
Ein MeshbufferDeviceCommandBuffer würde nun so aussehen:
Als Legende (zahlen hinter den Kürzeln signalisieren eine andere Variante):
Zitat: | IB = IndexBuffer
VB = VertexBuffer
T = Textur
S = Shader
R = Rendern |
Diese Buffer wurden vor dem Rendern wenn sie sich verändert haben optimiert und in einen grossen RenderDeviceCommandBuffer gelegt. Das Optimieren sah vor das Objekte (zB Material+MeshBuffer) welche die gleichen CommandBuffer benutzen so sortiert wurden das nur wenige Devicechanges auftreten, zB würde folgendes:
Modell1:
Zitat: | [T1 - S1] + [IB1 - VB1] + [R1] |
Modell2:
Zitat: | [T2 - S1] + [IB1 - VB2] + [R2] |
Modell3:
Zitat: | [T1 - S1] + [IB1 - VB2] + [R3] |
So zusammen gefasst werden:
Zitat: | [IB1 - S1Start - T1 - VB1 - R1 - VB2 - R3 - T2 - R2 - S1Ende] |
Und auch in dieser Reihenfolge gerendert werden. (Hier sind mal die Commands für das setzen des Rendertargets, setzen der Transformation, etc weggelassen worden, das ist auch ohne schon komplex genug).
Das System hat auch wunderbar funktioniert, die Befehle waren gut sortiert und die Anzahl der nötigen DeviceChanges wurde drastisch reduziert. Leider konnte ich diesen Ansatz nicht weiter verfolgen. Vorteile waren aber das wie gesagt die Statechanges gut optimiert wurden je nach Bedarf und das man so ganz gut nette Effekte in den höheren Ebenen der Engine zusammen stellen konnte, Ein PingPong Buffer für Blur Effekte zB war um einiges einfacher darzustellen als auf herkömmliche Arten.
Nun stellt sich mir aber die Frage nach der Performance, die Commands müssen sortiert, optimiert und auch ausgeführt werden. Jede der 3 Aktionen erfordert einen Zugriff auf die vTable und einen Methodenaufruf. Dies ist bei den ersten beiden Fällen nicht so gravierend da nur wenig neu optimiert und sortiert werden muss solange sich keine Parameter in den Commands geändert haben. Allerdings muss der ganze DeviceCommandBuffer Baum den es gibt flach gemacht werden und ausgeführt werden.
Simpler erscheint mir da die Variante, dass ich eine Reihe von Renderables habe die einmal pro Rendervorgang sortiert werden (sortiert nur nach dem Material und zB der Distanz zur Kamera) und gerendert werden. Hier mal ein Vergleich:
(Ich habe das was hier gemacht werden würde mal in DCBuffer-Form niedergeschrieben)
Zitat: | Modell1 - Rendern [T1 - S1] + [IB1 - VB1] + [R1]
Modell2 - Rendern [T2 - S1] + [IB1 - VB2] + [R2]
Modell3 - Rendern [T1 - S1] + [IB1 - VB2] + [R3] |
vs:
Zitat: | IB1
S1Start
T1
VB1
R1
VB2
R3
T2
R2
S1Ende |
Die einzelnen Aufrufe am Device habe ich hier mal weggelassen und kann sich jeder dazu denken.
Die Frage die sich nun stellt ist was nun besser wäre?
Meinungen, Anregungen und Fragen sind gerne erwünscht _________________ "I have a Core2Quad at 3.2GHz, 4GB of RAM at 1066 and an Nvidia 8800 GTS 512 on Vista64 and this game runs like ass whereas everything else I own runs like melted butter over a smokin' hot 18 year old catholic schoolgirl's arse." |
|
Nach oben |
|
|
PeaceKiller JLI Master
Alter: 35 Anmeldedatum: 28.11.2002 Beiträge: 970
Medaillen: Keine
|
Verfasst am: 24.09.2009, 19:35 Titel: |
|
|
Erstmal muss ich sagen, dass ich es toll finde, dass jemand hier ausm Forum mal wieder was bastelt. Ich habe selber seit langem auch wieder was vor, seit ich Direct 2D gesehen habe. Vielleicht hast du irgendwann Zugriff auf DX11 Karten. Catmull-Clark in Hardware ist schon sehr verlockend und kommt denke ich auch in Demos gut.
Zum Thema:
Ich dachte, dass Engines, die jedes Polygon einzeln sortieren seit Q3A aus der Mode sind. Allerdings sind unnötige State-Changes es schon wert sie in Angriff zu nehmen. Außerdem gibt es heutzutage ja fast nur noch Dualcores, es wäre zu schade, wenn die ganze Rechenpower brachliegt. Vielleicht kann man das ganze ja auch geschickt cachen.
Leider kann ich sonst keinen qualifizierteren Kommentar abgeben.
Und bist du wirklich im klaren darüber, was für ein Aufwand eine eigene Engine ist? _________________ »If the automobile had followed the same development cycle as the computer, a Rolls-Royce would today cost $100, get a million miles per gallon, and explode once a year, killing everyone inside.«
– Robert X. Cringely, InfoWorld magazine |
|
Nach oben |
|
|
Dr. Best Senior JLI'ler
Alter: 34 Anmeldedatum: 17.06.2004 Beiträge: 269 Wohnort: Köln Medaillen: Keine
|
Verfasst am: 24.09.2009, 23:16 Titel: |
|
|
Ich hab ja auch eine eigene Engine. Das was man momentan auf Ultimate3D.org sieht ist schon lange nicht mehr aktuell, wird aber von vielen genutzt. Inzwischen arbeite ich mit Direct3D 9.0 und mache die Engine vorerst für die eigene Verwendung.
Zum Thema: Ich finde das ganze recht schwierig. Es gibt ja nicht nur ein Kriterium nach dem man sortieren kann:
- Man kann nach den Shadern und den damit verbundenen Einstellungen sortieren um das Wechseln von Shadern, Konstanten und States zu minimieren.
- Man kann nach den Vertex- und Indexbuffern sortieren.
- Man kann nach den Texturen sortieren.
- Man kann Front to Back sortieren um das Rendern unsichtbarer Fragmente zu minimieren.
Alles hat seine Daseinsberechtigung, aber man kann es nicht alles gemeinsam haben. Ich habe mal ein nVidia-Paper gelesen in dem Texturatlanten empfohlen wurden um wenigstens das Texturen wechseln loszuwerden. Bloß leider bleiben da dann trotzdem noch drei Kriterien. In meiner Engine setze ich momentan auf Front to Back (wobei ich allerdings Octree-Zweige und nicht einzelne Objekte sortiere). Desto voller die Pixel Shader werden, desto mehr lohnt sich das. Es ist immer wieder erfreulich in PIX zu lesen "This pixel was eliminated because: It failed the depth test." .
Ich habe keine konkreten Benchmarks gemacht. Man hört immer überall wie schlimm viele Batches den CPU und den BUS belasten und sie zu minimieren ist sicherlich sinnvoll, aber was man dafür in Kauf nehmen sollte bleibt fragwürdig. Was ich im Moment mache ist im Wesentlichen dein zweiter Ansatz. Der wesentliche Vorteil davon ist offensichtlich, dass man die Objekte in beliebiger Reihenfolge rendern kann und dadurch Back-to-Front-Sortierung möglich wird. Dein erster Ansatz reduziert die Anzahl der Batches bestimmt sehr wesentlich da es die optimale Mischung der oben genannten ersten drei Wege darstellt und ich kann mir vorstellen, dass es algorithmisch ziemlich interessant ist, aber wenn die Grafikkarte dann für jeden Pixel zehn Fragmente rendern darf hast du das ganze bis zum CPU Leerlauf optimiert.
Wenn du viele komplexe Pixel-Shader verwendest ist es denke ich sinnvoller den zweiten Ansatz zu wählen. Zusätzlich ist es natürlich unerlässlich ein gutes System für Instancing zu haben um damit die Batches zu reduzieren. Wenn du es genau wissen willst gibt es eigentlich nur einen Weg. Implementier beides, baue eine Testszene die den späteren Anforderungen entspricht und mache ein gründliches Benchmark. _________________
Ich bin da, wer noch? |
|
Nach oben |
|
|
PeaceKiller JLI Master
Alter: 35 Anmeldedatum: 28.11.2002 Beiträge: 970
Medaillen: Keine
|
Verfasst am: 25.09.2009, 15:45 Titel: |
|
|
Dr. Best hat Folgendes geschrieben: | Ich habe keine konkreten Benchmarks gemacht. Man hört immer überall wie schlimm viele Batches den CPU und den BUS belasten und sie zu minimieren ist sicherlich sinnvoll, aber was man dafür in Kauf nehmen sollte bleibt fragwürdig. |
Viele State-Changes belasten v.a. die Graka! Da sie bei jedem praktisch kurz stalled. Sollte deferred shading nicht das Problem reduzieren, das man bekommt, wenn man nicht front to back sortiert?
Vielleicht kannst du ja beides implementieren und dann mal benchmarken _________________ »If the automobile had followed the same development cycle as the computer, a Rolls-Royce would today cost $100, get a million miles per gallon, and explode once a year, killing everyone inside.«
– Robert X. Cringely, InfoWorld magazine |
|
Nach oben |
|
|
Fallen JLI MVP
Alter: 40 Anmeldedatum: 08.03.2003 Beiträge: 2860 Wohnort: Münster Medaillen: 1 (mehr...)
|
Verfasst am: 25.09.2009, 16:21 Titel: |
|
|
Ah es sind ja doch noch ein par Aktiv
Den Ansatz oben habe ich schonmal implementiert, der Algo zum sortieren und optimieren ist wirklich sehr interessant und aktuell einer der Codefragmente auf die ich verdammt Stolz bin (sowas hatte ich bisher noch nie). Je Polygon wird auch hierbei nicht sortiert sondern nur nach den einzelnen Statechanges/Drawcalls, per PolySortierung, nein Danke das muss nicht sein
Ich bastel mir übrigens die Engine da es auf Scenepartys sehr doof aussieht wenn man etwas mit einer fertigen Engine bastelt, ein grosser Part ist da ja das technische KnowHow
Beides zu implementieren wird eine Fleißarbeit und ich bin nicht fleißig daher lasse ich das lieber
Vermutlich werde ich aber einfach mal Variante1 benutzen, auch wenn es vom Design her etwas umgewandelt wird, so das ich nicht pro DeviceCommand eine Klasse habe sondern weniger Informationen und dafür bestehende Strukturen ausnutze (zB DrawPrimitive direkt in der MeshBuffer Klasse.).
Dazu habe ich mir nur mal kurz nebenbei überlegt das ich ein Interface habe von dem alle CommandoNutzenden Klassen ableiten können:
CPP: | interface IRenderable
{
Command* GetCommands();
void AddSubRenderable(IRenderable& SubRenderable);
void RemoveSubRenderable(IRenderable& SubRenderable);
void PerformCommand(Command CommandType);
bool CompareCommand(Command CommandType, IRenderable Other);
} |
Command ist dabei eine möglichst leichtgewichtige Kontrollstruktur ähnlich dem hier:
CPP: | enum Command
{
ClearColor,
ClearDepth,
SetTexture,
SetRenderTarget
Render
}
|
Irgendwo müsste man noch das genaue Sortierverhalten definieren so das zB nichts vor ein SetRenderTarget gelegt werden darf um RenderKetten aufrecht zu erhalten, aber auch das ist irgendwie möglich.
Evtl könnte man das in einigen Bitwerten im Enum ablegen:
CPP: | enum CommandFlag
{
NoSort = 0x100,
RemoveDuplicates = 0x200,
EndsSequence = 0x400
}
enum Command
{
ClearColor = 0x001 | NoSort,
ClearDepth = 0x002 | NoSort,
SetTexture = 0x004 | RemoveDuplicates,
SetRenderTarget = 0x008 | NoSort | EndsSequence,
Render = 0x010,
}
|
Nun fehlen noch ein par Dinge um bestimmte Sequencen wiederholbar zu machen für den Fall das Shader mehrere Passes haben, dazu könnte man wiederum void PerformCommand(Command CommandType); so erweitern das es ein Rückgabewert liefert wie zB:
CPP: | enum ProcessResult
{
Done = 0x0,
RepeatFrom = 0x1,
JumpTo = 0x2
}
|
Und ein optionales Command für jumpTo sowie RepeatFrom, dadurch kann kan zB immer so lange zum vorherigen NextPass Command springen bis alle Passes abgeschlossen wurden.
Was meint ihr? _________________ "I have a Core2Quad at 3.2GHz, 4GB of RAM at 1066 and an Nvidia 8800 GTS 512 on Vista64 and this game runs like ass whereas everything else I own runs like melted butter over a smokin' hot 18 year old catholic schoolgirl's arse." |
|
Nach oben |
|
|
Fallen JLI MVP
Alter: 40 Anmeldedatum: 08.03.2003 Beiträge: 2860 Wohnort: Münster Medaillen: 1 (mehr...)
|
Verfasst am: 12.11.2009, 14:01 Titel: |
|
|
Ich habe die Sache nun bei mir mal implementiert läuft aktuell sogar sehr sauber, wie es mit komplexen Szenen abseits eines Würfels aussieht muss sich noch zeigen.
Aktuell sieht zB fürs darstellen eines Würfels der Code so aus:
(erstellen der Resourcen habe ich mal weg gelassen)
CPP: | AddToNode(rootNode, window->get_SwapChain());
AddToNode(rootNode, currentCamera);
AddToNode(rootNode, lightParameters);
AddToNode(rootNode, normalShader);
AddToNode(rootNode, wallTexture);
AddToNode(rootNode, cubeTransformation);
AddToNode(rootNode, cubeBuffer);
|
_________________ "I have a Core2Quad at 3.2GHz, 4GB of RAM at 1066 and an Nvidia 8800 GTS 512 on Vista64 and this game runs like ass whereas everything else I own runs like melted butter over a smokin' hot 18 year old catholic schoolgirl's arse." |
|
Nach oben |
|
|
|
|
Du kannst keine Beiträge in dieses Forum schreiben. Du kannst auf Beiträge in diesem Forum nicht antworten. Du kannst deine Beiträge in diesem Forum nicht bearbeiten. Du kannst deine Beiträge in diesem Forum nicht löschen. Du kannst an Umfragen in diesem Forum nicht mitmachen.
|
Powered by phpBB © 2001, 2005 phpBB Group Deutsche Übersetzung von phpBB.de
|