[Projekt] breeze 2.0

Hier könnt ihr euch selbst, eure Homepage, euren Entwicklerstammtisch, Termine oder eure Projekte vorstellen.
Forumsregeln
Bitte Präfixe benutzen. Das Präfix "[Projekt]" bewirkt die Aufnahme von Bildern aus den Beiträgen des Themenerstellers in den Showroom. Alle Bilder aus dem Thema Showroom erscheinen ebenfalls im Showroom auf der Frontpage. Es werden nur Bilder berücksichtigt, die entweder mit dem attachement- oder dem img-BBCode im Beitrag angezeigt werden.

Die Bildersammelfunktion muss manuell ausgeführt werden, die URL dazu und weitere Details zum Showroom sind hier zu finden.

This forum is primarily intended for German-language video game developers. Please don't post promotional information targeted at end users.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

[Projekt] breeze 2.0

Beitrag von CodingCat »

Zudomons Aufruf folgend, die Einweihung der Shader-getriebenen DirectX 11 Rendering Pipeline der von Grund auf neu geschriebenen Engine breeze 2.0.
jellycube.png
Die neue Iteration setzt eine ganze Reihe von Dingen um, die mir schon seit Längerem ein Anliegen sind. Dennoch kostet so ein Rewrite immer viel Überwindung, mich persönlich diesmal besonders, weil kein allzu großer Leidensdruck besteht. Die letzte Iteration hätte sich weiterentwickeln lassen können, entsprach sie doch nach etwa 3 Jahren Lebenszeit lediglich nicht mehr meinen eigenen, derweil bedeutend weiterentwickelten Programmierstandards. So gehe ich ein letztes Mal diesen Schritt (ich mag sie gar nicht zählen) und lasse ca. 100 kloc zurück, mit der Gewissheit, dass dies, in welcher Form auch immer, schlussendlich eine Endfassung sein wird, sein muss.

Wenngleich es sich um ein Rewrite handelt, nehme ich sämtliche Konzeptarbeit aus der letzten Iteration mit. Insbesondere im Rahmen des Rendering-Ablaufs konnte ich im Lauf der Jahre eine ganze Reihe gut funktionierender und teilweise nicht ganz trivialer Konzepte herausarbeiten, die ich zum Teil bereits auf der Devmania 2009 in einer etwas in technischen Details verlorenen Präsentation vorgestellt hatte. Das natürliche Wachstum dieser Konzepte gepaart mit meinen damaligen Designfertigkeiten resultierten zwangsläufig in einer etwas undurchsichtigen Aufgabenverteilung und einigen Verstrickungen, und es erfüllt mich mit Glück, dass ich diese nun endlich in destillierter und entwirrter Form klar implementieren konnte.

Die neue Iteration basiert auf der ebenfalls innerhalb der letzten 6 Monaten entwickelten lean C++ Bibliothek, und macht damit erstmalig massiven Gebrauch von Speicher- und Ressourcenverwaltung mit RAII. Damit verbunden ist weitreichende Ausnahmesicherheit. Es war eine unglaubliche Erfahrung, minimalen Wrapping-Prinzipien folgend erstmals praktisch alle Fehler mit einer verschwindenden Menge von Zusatzcode sinnvoll, wohldefiniert und strukturiert behandeln zu können. Zuvor war ich 6 Monate an einem Java-Projekt beteiligt gewesen, bei dem ich erstmals versucht hatte, Ausnahmesicherheit konsequent umzusetzen, was sich dort mangels RAII und dank gefühlter 2000 verschiedener Ausnahmetypen als gigantische Qual entpuppte. Mit einer geringen Anzahl von Ausnahmetypen und RAII wird einem in C++ die Fehlerbehandlung praktisch geschenkt. Zudem erspart man sich sämtliche manuelle Entsprerrungs- und Freigabelogik, wodurch ich in meinem Rewrite nun bereits mit einem gefühlten Zehntel des Quellcodes der alten Iteration wesentlich mehr Funktionalität implementieren kann.

(Anmerkung hierbei: Schon mit einer geringen Anzahl von Fehlerbehandlungsmakros (z.B. automatisierter Log-Eintrag und/oder Ausnahme bei Fehlschlag eines WinAPI- oder DirectX-Aufrufs) schrumpft der Fehlerbehandlungscode auf eine bis keine Zeile pro potentiell fehlschlagendem API-/System-Aufruf. Ist man erstmal weg von der unmittelbaren Dritt-API-Ebene im eigenen Code angekommen, regeln Ausnahmen den Rest praktisch von selbst.)

Ganz wichtig ist natürlich der Schritt von DirectX 9 auf DirectX 11, womit mir endlich neue Türen wie Tesselation, Compute Shaders, Geometry Shaders und korrektes Multisampling offen stehen. Bezüglich API-Unabhängigkeit habe ich endlich auf jegliche Laufzeitpolymorphie verzichtet, ich implementiere momentan (und wohl auch zukünftig) ausschließlich einen DirectX-11-Zweig. Einige Entkopplungsvorkehrungen erlauben prinzipiell die Implementierung und Auswahl weiterer Alternativen zur Compilezeit.
Ich kann nur allen davon abraten, APIs mehr oder weniger unverändert um der Unabhängigkeit Willen zu wrappen. Das ist nicht nur reine Zeitverschwendung, sondern verhindert ausschließlich extrem effektiv die Nutzung neuer wie ungewrappter API-Features. Die erste Ebene, auf der sich Entkopplung überhaupt lohnt, ist die, die überhaupt nichts mehr mit den Aufgaben der zugrundeligenden API zu tun hat. Für die letzte Iteration hatte ich DirectX 9 tatsächlich ziemlich komplett gewrappt, und es hat mir, abgesehen von einem etwas breiteren Überblick über die API, absolut nichts gebracht (von der Zeit, die ich damals vertan habe, will ich gar nicht reden).

Weiterhin verfolge ich auf Twitter seit einiger Zeit mit Spannung die Bewegung des Data-oriented Design. Die Idee, alleine durch Datenaufteilung und -sortierung sowie Vereinfachung des Codes auf genau das, was er schlussendlich auch tun soll (d.h. keine Laufzeitpolymorphie, wenig Indirektionen), bedeutende Effizienzsteigerungen zu erreichen, ist in ihrer Logik und Einfachheit bestechend. Da baut man über Jahre Schlösser von hochgradig objektorientierten, gekapselten Systemen, nur um am Ende gesagt zu kriegen, dass der Code, den man intuitiv als Anfänger geschrieben hätte, der Code, der den Job ohne Erweiterbarkeit und ohne große Flexibilität einfach nur erfüllt, schlussendlich auch der beste gewesen wäre. Ich bin von dieser Bewegung alles andere als bedingungslos überzeugt, gerade weil es mit dieser Iteration mein Ziel ist, am Ende auf ein gut designtes Stück offener Software blicken zu können, wohingegen Data-oriented Design Software für eine ganz bestimmte Aufgabe / ein bestimmtes Produkt liefert.
Dennoch hat die Bewegung mich maßgeblich beeinflusst, gerade mit ihrem Credo der Datentransformation anstelle von Datenmanipulation habe ich mich bereits angefreundet. So versuche ich in dieser Iteration innerhalb von inneren Schleifen, beispielsweise dort, wo die gesamte Szene gecullt wird, auf jegliche Indirektion und Polymorphie zu verzichten. Stattdessen habe ich renderbare Objekte auf Datenebene soweit vereinheitlicht, dass ich sie alle in einer einzigen Schleife ohne Aufruf weiterer nicht-inline Funktionen abarbeiten, und sichtbare Objekte direkt in weitere vorkategorisierte Listen eintragen kann.

Schlussendlich bleibt mein Ziel trotz Ansätzen von Data-oriented Design ein klares objektorientiertes Design. Ich vermeide Indirektionen und Laufzeitpolymoprhie, wo ich die Möglichkeiten sehe, vermeide überflüssige Funktionalität und die Vermischung von Aufgaben. Um den Overhead vieler kleiner Objekte aufgrund von rigoroser Aufgabenteilung zu verringern, aggregiere ich viele Klassen by-value, das verringert die Speicherfragmentierung und erhöht die Lokalität für Cache-Ausnutzung.

Eine letzte bedeutende Sache, die mir in der letzten Iteration im Nachhinein sehr negativ aufgefallen ist, war der viel zu hohe Grad an Kapselung. Statt sich selbst aus vielen weiteren Objekten aufzubauen, werden Objekte in der neuen Iteration weitestgehend von außen zusammengebaut. Das macht die API zunächst unglaublich unübersichtlich, weil unter 20 manuell erstellten Framework-Objekten nicht mal ein Würfel gerendert werden kann, lässt sich jedoch sehr sehr leicht durch einige Fabrik-Funktionen beheben (ja, freie Funktionen, benutze ich nun auch für alles, was nicht mit Implementierungsdetails von Klassen zu tun hat). Diese Fabrikfunktionen ergänzen dann fehlende Objektkonstruktionsparameter einfach durch neu erzeugte Instanzen der notwendigen Klassen, ohne dass der Anwender je mehr als sein Renderer-Objekt rumreichen muss. Am Ende hat man dank geringer Kapselungstiefe einen gigantischen Flexibilitätsgewinn und zugleich sehr gute Usability, obendrein beides mit einem Minimum an Laufzeitpolymorphie.

Wow, das war viel Metatalk, ich hoffe nicht gänzlich uninteressant. Ich möchte mich an dieser Stelle noch bei Krishty, dot und jokester bedanken, die meine Programmierung in vielerlei Hinsicht maßgeblich beeinflusst haben, und ohne die ich nie dorthin gefunden hätte, wo ich heute bin.

Zur Diskussion sind alle herzlich eingeladen. Ich meinerseits hoffe, dass ich bald die Zeit finde, konkreteres zu berichten. Möglicherweise biete ich das ganze sogar bald Open Source an. ;-)
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Schrompf
Moderator
Beiträge: 5045
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von Schrompf »

Danke für die umfangreiche Beschreibung. Ich lese da bei vielen Dingen heraus, wieviel Du dabei gelernt hast. Mich würden dann viele Details zu all den Komponenten und ihrer Zusammenarbeit interessieren, aber das kann ich mir ja eigentlich auch auf der Devmania anschauen. Ich bin speziell an der eleganten Fehlerprüfung und -behandlung interessiert - ich empfinde es immer als Krampf, Rückgabewerte zu prüfen und darauf auch so zu reagieren, dass der Fehler nicht nur sauber behandelt werden kann, wenn es nötig ist, sondern auch keinen Ärger für den Aufrufer macht, wenn der sich nicht dafür interessiert.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von dot »

Sehr schön :mrgreen: Wie weit bist du denn damit?
CodingCat hat geschrieben:Ich kann nur allen davon abraten, APIs mehr oder weniger unverändert um der Unabhängigkeit Willen zu wrappen. Das ist nicht nur reine Zeitverschwendung, sondern verhindert ausschließlich extrem effektiv die Nutzung neuer wie ungewrappter API-Features.
Das kann man gar nicht oft genug unterschreiben :) Es ist nicht nur Programmierzeitverschwendung, sondern auch Laufzeitverschwendung...
CodingCat hat geschrieben:Weiterhin verfolge ich auf Twitter seit einiger Zeit mit Spannung die Bewegung des Data-oriented Design.
Ich assoziere "DoD" stark mit dem Glauben, dass das, was DoD sein will, und OOP in irgendeiner Konkurrenz zueinander stehen. Zumindest hab ich den Eindruck, dass die meisten Artikel zum Thema DoD wohl so ein Weltbild propagieren. Imo ist das völliger Schwachsinn. Ich seh in DoD und OOP zwei Paradigmen, die auf völlig unterschiedlichen Ebenen wirken, zwischen denen man sich nicht entscheiden muss, sondern die Hand in Hand gehen. DoD, so wie ich es versteh', fängt im Prinzip dort an, wo OOP aufhört. D.h. ich verwend DoD z.B. in der Implementierung einer Klasse, die Teil eines Systems ist, das auf höherer Ebene OO ist. Ich denk, du siehst das wohl genauso. Und ich denk ehrlich gesagt, dass das eigentlich nicht wirklich was Neues ist, sondern dass jeder erfahrene Programmierer, der Wert auf Performance legt, das sowieso so macht. Es hat sich nun nur offenbar jemand einen fancy Name dafür ausgedacht und irgendwie einen Hype initiiert, dessen Anhänger aber einem imo fehlgeleiteten Glauben folgen. Aber gut wenn es als Inspiration dient, hat es mir auch, denk ich :)
CodingCat hat geschrieben:Ich möchte mich an dieser Stelle noch bei Krishty, dot und jokester bedanken, die meine Programmierung in vielerlei Hinsicht maßgeblich beeinflusst haben, und ohne die ich nie dorthin gefunden hätte, wo ich heute bin.
I'm flattered :oops: . Ich denk, das Kompliment kann ich zurückgeben ;)
Schrompf hat geschrieben:Ich bin speziell an der eleganten Fehlerprüfung und -behandlung interessiert - ich empfinde es immer als Krampf, Rückgabewerte zu prüfen und darauf auch so zu reagieren, dass der Fehler nicht nur sauber behandelt werden kann, wenn es nötig ist, sondern auch keinen Ärger für den Aufrufer macht, wenn der sich nicht dafür interessiert.
Also bei mir schaut das atm meist so in der Art aus:

Code: Alles auswählen

  if (FAILED(device->CreateVertexShader(data(), size(), 0, &shader)))
    throw std::runtime_error("unable to create vertex shader");
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von CodingCat »

Eine sehr schöne Sache, die ich von Krishty gelernt habe, ist die Auslagerung des eigentlichen Werfens in eine nicht-inline-Methode:

Code: Alles auswählen

#define LEAN_NOLTINLINE _declspec(noinline)

LEAN_NOLTINLINE bool apiCall()
{
	return (rand() % 2 != 0);
}

LEAN_NOLTINLINE void throwMsg(const char *msg)
{
	throw std::runtime_error(msg);
}

void doSth1()
{
	if (!apiCall())
		throw std::runtime_error("apiCall() failed");
}

void doSth2()
{
	if (!apiCall())
		throwMsg("apiCall() failed");
}
Warum das sinnvoll ist, zeigt die Disassembly:

Code: Alles auswählen

// void doSth1()
// {
00A91640  push        ebp  
00A91641  mov         ebp,esp  
00A91643  sub         esp,10h  
//	if (!apiCall())
00A91646  call        apiCall (0A915F0h)  
00A9164B  test        al,al  
00A9164D  jne         $LN5 (0A91671h)  
//		throw std::runtime_error("apiCall() failed");
00A9164F  lea         eax,[ebp-4]  
00A91652  push        eax  
00A91653  lea         ecx,[ebp-10h]  
00A91656  mov         dword ptr [ebp-4],offset string "apiCall() failed" (0A941B0h)  
00A9165D  call        dword ptr [__imp_std::exception::exception (0A9410Ch)]  
00A91663  push        offset __TI1?AVexception@std@@ (0A94490h)  
00A91668  lea         ecx,[ebp-10h]  
00A9166B  push        ecx  
00A9166C  call        _CxxThrowException (0A92F0Ah)  
// }
00A91671  mov         esp,ebp  
00A91673  pop         ebp  
00A91674  ret  

// void doSth2()
// {
//	if (!apiCall())
00A91680  call        apiCall (0A915F0h)  
00A91685  test        al,al  
00A91687  jne         doSth2+0Eh (0A9168Eh)  
//		throwMsg("apiCall() failed");
00A91689  jmp         throwMsg (0A91610h)  
// }
00A9168E  ret
Wie zu sehen ist, spuckt ein einfaches throw pro geprüftem Aufruf eine gigantische Menge überflüssigen Codes aus, die sich auf diese Weise elegant komplett eliminieren lässt.
Zuletzt geändert von CodingCat am 08.09.2011, 14:18, insgesamt 1-mal geändert.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Schrompf
Moderator
Beiträge: 5045
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von Schrompf »

Interessant. Danke.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von CodingCat »

dot hat geschrieben:Ich assoziere "DoD" stark mit dem Glauben, dass das, was DoD sein will, und OOP in irgendeiner Konkurrenz zueinander stehen. Zumindest hab ich den Eindruck, dass die meisten Artikel zum Thema DoD wohl so ein Weltbild propagieren. Imo ist das völliger Schwachsinn. Ich seh in DoD und OOP zwei Paradigmen, die auf völlig unterschiedlichen Ebenen wirken, zwischen denen man sich nicht entscheiden muss, sondern die Hand in Hand gehen. DoD, so wie ich es versteh', fängt im Prinzip dort an, wo OOP aufhört. D.h. ich verwend DoD z.B. in der Implementierung einer Klasse, die Teil eines Systems ist, das auf höherer Ebene OO ist. Ich denk, du siehst das wohl genauso.
Ja, das sehe ich prinzipiell genauso. Der Widerspruch zwischen DoD und OOP liegt darin, dass sich diese Trennebene verschieben läst. DoD-Anhänger schieben diese Trennebene wohl gerne sehr hoch, d.h. nicht nur auf Implementierungsebene einzelner Klassen, sondern gerne auch weit bis in die Architektur hinein. Und genau hier liegt wohl auch die Schwierigkeit flexiblen wie effizienten Designs.
So kann es schon einen großen Unterschied machen, ob verschiedene renderbare Objekttypen in getrennten Datenvektoren vorliegen, oder polymorph in einer gemischten Referenzliste verarbeitet werden. Da ich in der letzten Iteration praktisch nur auf Polymorphie gesetzt habe, und damit keine allzu großen Performanceprobleme hatte, bin ich hier einen Hybridweg gegangen, während sich das Culling wunderbar auf einen Bounding-Volumentyp vereinheitlichen und so extrem kompakt und effizient implementieren lässt, arbeite ich beim Rendering weiterhin mit gemischten polymoprhen Referenzlisten, um nicht allzu viel Flexibilität zu verlieren.

Hier ließe sich natürlich wesentlich mehr Arbeit hineinstecken, so könnte man Polymorphie beispielsweise auch allgemeingültig im Kollektiv umsetzen, mit etwas RTTI (ob built-in oder eigens angepasst) ließen sich gemischte polymorphe Objekte wunderbar automatisiert in getrennte Listen vorsortieren, so bekäme man das beste aus beiden Welten. Meine Zeit ist aber im Moment zu begrenzt, um solche Spielereien mal eben zum Spaß zu implementieren. ;-)
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von dot »

CodingCat hat geschrieben:Ja, das sehe ich prinzipiell genauso. Der Widerspruch zwischen DoD und OOP liegt darin, dass sich diese Trennebene verschieben läst. DoD-Anhänger schieben diese Trennebene wohl gerne sehr hoch, d.h. nicht nur auf Implementierungsebene einzelner Klassen, sondern gerne auch weit bis in die Architektur hinein. Und genau hier liegt wohl auch die Schwierigkeit flexiblen wie effizienten Designs.
So kann es schon einen großen Unterschied machen, ob verschiedene renderbare Objekttypen in getrennten Datenvektoren vorliegen, oder polymorph in einer gemischten Referenzliste verarbeitet werden. Da ich in der letzten Iteration praktisch nur auf Polymorphie gesetzt habe, und damit keine allzu großen Performanceprobleme hatte, bin ich hier einen Hybridweg gegangen, während sich das Culling wunderbar auf einen Bounding-Volumentyp vereinheitlichen und so extrem kompakt und effizient implementieren lässt, arbeite ich beim Rendering weiterhin mit gemischten polymoprhen Referenzlisten, um nicht allzu viel Flexibilität zu verlieren.
Natürlich. Den richtigen Tradeoff zu finden, ist dann die Kunst des Softwaredesign ;)
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von CodingCat »

Nachtreport: Ich habe noch etwas Zeit für die Programmierung gefunden und einen State Manager implementieren können. Dieser Name wurde bereits derart oft verwendet, dass die erste Assoziation vermutlich der klassische Redundanzfiter ist, der einfach mit massig Verzweigungen vor jedem Set*State-Aufruf die zu setzenden Werte mit den alten vergleicht, und bei Übereinstimmung ignoriert. Davon abgesehen, dass der Treiber auf derartige Redundanz womöglich selbst noch einmal filtert, möchte ich hier einen Schritt weiter gehen.

Die meisten Objekte werden mit exakt denselben Rasterizer-, Depth-Stencil- und Blend-States gerendert, die beste Lösung wäre also, mit dem Setzen redundanter States gar nicht erst zu beginnen. Natürlich sollten zwischenzeitliche State Changes für spezielle Objekte im Renderablauf grundsätzlich möglich sein, ein erster Ansatz wäre also in diesem Fall nach jedem speziellen Objekt das klassische Zurücksetzen veränderter States auf den allgemeinen Grundzustand. Auch damit bin ich allerdings nicht zufrieden, weil bei einer ganzen Reihe konsekutiv gerenderter spezieller Objekte (ich sortiere Objekte nach Shaders inkl. States) ein zusätzliches State-Flattern aufträte, ohne Zurücksetzen hätte man dagegen im schlimmsten Fall nur einige redundante Setzaufrufe gehabt.

Damit kommen wir zur eigentlichen Idee des gerade implementierten State Managers: States werden erst dann zurückgesetzt, wenn sie von einem zuvor gerenderten Objekt verändert und vom aktuell gerenderten Objekt nicht mit weiteren eigenen speziellen States überschrieben wurden. Das klingt zunächst nach noch mehr Prüfaufwand als im ersten Fall des simplen Redundanzfilters, lässt sich tatsächlich aber mit sehr wenigen Operationen sehr viel effizienter umsetzen. Dabei wird der aktuelle Grundzustand im State Manager gespeichert, zusammen mit einer Bitmaske, in der für jeden durch den Grundzustand wohldefinierten Zustand ein Bit reserviert ist (im Moment vier: Rasterizer-, Depth-Stencil- und Blend-State sowie eine Render-Target-Liste). Zwei weitere Bitmasken changed und overwritten folgen demselben Muster, und werden nun von den gerenderten Objekten im Renderablauf aktualisiert. Überschreibt ein Objekt beim Rendern einen der Zustände, setzt es das entsprechende overwritten-Flag. Direkt vor dem Draw-Call ruft es StateManager::Reset() auf, und setzt damit alle nicht als overwritten markierten States auf den Grundzustand zurück. Im Anschluss wird die overwritten-Maske zur changed-Maske für das nächste Objekt. Dieses wird auf diese Weise die changed markierten States vor seinem eigenen Draw-Call dann wiederum nur zurücksetzen, wenn es diese zuvor nicht selbst überschrieben hat, etc.

Zur Effizienz: Die Bitmasken lassen sich über einfache Bitverknüpfungen aktualisieren, die zurückzusetzenden States berechnen sich als reset = defined & changed & ~overwritten. Was bleibt, ist das Zurücksetzen der States anhand der reset-Maske. Dieses lässt sich sogar, im Gegensatz zu den eingangs erwähnten if-Kaskaden bei einfacher Filterung redundanter States, mit etwas Kombinatorik in einer einzigen switch-Sprungtabelle umsetzen. ;-)
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Schrompf
Moderator
Beiträge: 5045
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von Schrompf »

Und wieviel ms hat es am Ende gebracht? Den Teil fände ich spannend. Ich kann mir nämlich vorstellen, dass die Antwort 0 lautet, egal wieviel Intelligenz Du in das State Management steckst. [edít] Außerdem: mir behagt der Gedanke dieser Wirkungsumkehr nicht so recht: wenn ich einen abweichenden State Block setze, bewirkt das evtl. gar nichts, lasse ich ihn weg, bekomme ich ohne einen expliziten Funktionsaufruf plötzlich abgeänderte States.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von CodingCat »

Dass es keinen Zeitgewinn bringt, kann ich mir auch vorstellen, Profilen werde ich sehr bald, wenn der Render-Ablauf abgeschlossen ist und ich geeignete Testdaten generiert habe.

Den zweiten Punkt verstehe ich nicht ganz: Dieses Verhalten ist genau so gewünscht, denn in jeder Render Queue gibt es einen wohldefinierten Grundzustand, von dem jedes gerenderte Objekt bei korrekter Nutzung des State Managers ausgehen kann. Da die Rendering Pipeline Shader-/Effekt-getrieben ist, ist dieses Verhalten essentiell, andernfalls müsste jeder Effekt IMMER alle States selbst definieren und setzen, was bei späterer Änderung des Grundzustandes suboptimal wäre. So muss jeder Effekt nur jene States selbst definieren und setzen, die ausschließlich für diesen von Bedeutung sind. D.h. das, was ich am Ende will, ist dasselbe Verhalten wie bei konsequentem sofortigen Zurücksetzen eigener spezieller States, nur ohne Flatterverhalten. Und wenn du einen geänderten State Block setzt, bewirkt das immer etwas, wieso meinst du, dass es nichts bewirkt?

Was zu prüfen bleibt, ist, ob das konsequente erneute Setzen des kompletten Grundzustands oder wahlweise teilweise spezialisierten Zustands vor dem Rendern gegenüber dem oben beschriebenen Verfahren messbare Performancenachteile mit sich bringt.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Schrompf
Moderator
Beiträge: 5045
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von Schrompf »

Das ist wohl Geschmackssache. Mir als Programmierer gefällt nicht, wenn ich ein SetState( irgendwas) mache und den State kriege, aber wenn ich den Funktionsaufruf weglasse, passiert eben nicht "nichts", sondern irgendwas anderes. Das sind Automatismen, die ich irgendwann vergessen würde und dann eine lange fröhliche Nacht des Debuggens vor mir habe.

Und ich kann mir wie gesagt nicht vorstellen, dass das Zurücksetzen auf einen Basisstate irgendeine Ersparnis gegenüber "Wechsle zu Wunschstate, falls nicht schon gesetzt" ist. Die Anzahl der Wechsel scheint mir dieselbe zu sein. Aber ich bin gespannt, was Du rausbekommen wirst.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von CodingCat »

Schrompf hat geschrieben:Das ist wohl Geschmackssache. Mir als Programmierer gefällt nicht, wenn ich ein SetState( irgendwas) mache und den State kriege, aber wenn ich den Funktionsaufruf weglasse, passiert eben nicht "nichts", sondern irgendwas anderes. Das sind Automatismen, die ich irgendwann vergessen würde und dann eine lange fröhliche Nacht des Debuggens vor mir habe.
Die Sache ist die, dass so ein Effekt, sogar jeder nicht konsekutiv gerenderte Pass darin, eben nur einen lokalen Geltungsbereich hat. Am Ende werden die gerenderten Objekte nach Material oder Tiefe sortiert, d.h. jeder State, der von einem Effekt nicht explizit gesetzt wurde, ist ohne zusätzliches State Management zwangsläufig undefiniert. Da ist mir persönlich ein wohldefinierter lokaler Geltungsbereich pro Effekt/Passintervall wesentlich lieber.

Wie das nun am besten umzusetzen ist, wird das Profiling zeigen. Ich denke, ich muss hier demnächst mal die Rendering Pipeline skizzieren, damit überhaupt grob der Kontext klar wird. ;)
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von dot »

Würd mich auch sehr interessieren, ob und wieviel Performance du dadurch gewinnen kannst. Ich vermute mal, dass es sich wenn, dann nur um einen verschwindend geringen Geschwindigkeitsvorteil handelt. Denn für den selben Satz an States gibt dir Create*State() ja definitionsgemäß immer das selbe Stateobjekt zurück, was bedeutet, dass die Runtime redundante States mit simplen Pointervergleichen filtern kann. Du sparst also effektiv wohl nur den Functioncall-Overhead der 4 *SetState() Methoden. Ich halte es normalerweise so, dass jeder Shader einfach alle States setzt, die ihn irgendwie betreffen.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von CodingCat »

Ja, diese Vermutung habe ich auch (wobei zwei der genannten drei State-Blöcke noch wenige weitere Argumente mit in die Set-Calls nehmen, aber das ist natürlich nur unwesentlich mehr). Ich habe das im ersten Post zu sehr auf die Performance-Ebene reduziert, aber letztlich ist mir der wohldefinierte Grundzustand mindestens genauso wichtig. Ich möchte einfach nicht, dass jeder Effekt willkürlich irgendwelche Standard-State-Blöcke definieren und setzen muss.
Zwar ließe sich die Definition über Includes sehr leicht zentralisieren, doch auch das redundante Setzen des Grundzustandes in jedem ersten Pass passt mir aus Prinzip nicht: Verschiedene Effekte wird es wesentlich mehr geben als verschiedene renderbare Objekttypen. Funktioniert die State-Logik eines renderbaren Objekttyps einmal, stimmt der Grundzustand für alle Effekte, die diesem Objekttyp je zugewiesen werden. Im Effekt dagegen wird es sehr schnell sehr mühsam, in jedem Pass jedes Mal alle States anzugeben.

Was bleibt, ist also nur die Alternative, den Grundzustand vor jedem gerenderten Objekt vollständig wiederherzustellen. Hier wird sich zeigen, wie gut die Redundanzfilterung sich schlägt. Im übrigen umfasst der Grundzustand bei mir auch sämtliche Render Targets, spätestens dort bin ich bei der Effizienz der Redundanzfilterung etwas skeptisch, aber wir werden es sehen. ;-)
Das Wechseln von Render Targets ließe sich natürlich notfalls auch auf verschiedene Render Queues und Post-Processing-Passes begrenzen.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von CodingCat »

Kleine Spielerei nach Bugfixing und erstem Szenentest. Technische Details liefere ich nach, wenn der Klausurzeitdruck nachlässt. ;-)
random3.png
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von CodingCat »

Tiefenbasierte Rekonstruktion der Pixelweltposition und Tiefenmodifikation. Bildspam.
random11.png
random10.png
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
klickverbot
Establishment
Beiträge: 191
Registriert: 01.03.2009, 19:22
Echter Name: David N.

Re: [Projekt] breeze 2.0

Beitrag von klickverbot »

Und nachdem du mit allen Effekten fertig bist, bleibt nur noch eine verschmierte graue Fläche zurück?

SCNR :P
condor
Beiträge: 14
Registriert: 31.07.2011, 14:52

Re: [Projekt] breeze 2.0

Beitrag von condor »

Sieht gut aus.
Hab nicht alles durchgelesen, vielleicht hast du das schon beantwortet. Kann man irgendwo den Quelltext ansehen? oder hast du es vor zu veröffentlichen? Wäre bestimmt für viele interessant.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von CodingCat »

Ich werden den Quelltext wohl im Laufe der nächsten Wochen mal rausgeben, wie öffentlich, kommt darauf an, wie weit ich jetzt komme. ;-)

Weiter gehts, gestern ist der Rendering-Ablauf fertig geworden. Renderbare Objekte (im Folgenden Renderables) werden nun von Perspektiven automatisch in Pipeline-Abschnitte (i.F. Stages) und Render-Warteschlagen (i.F. Queues) einsortiert, welche entsprechend ihren jeweiligen Ebenen-Spezifikationenen (i.F. Layers) in der richtigen Reihenfolge abgearbeitet werden.

Jedes Renderable trägt sich dabei zunächst mit beliebig vielen Render Jobs in eine Szenerie ein (zur "Ladezeit"). Die Render Jobs geben jeweils eine Stage und eine Queue an, in der sie dann beim Einsortieren durch die Perspektive (nach Culling) landen wollen (zur "Frame-Zeit").
Im Falle eines Depth-Pre-Pass oder gar G-Buffer-Pre-Pass würde sich ein Renderable beispielsweise in die Pre Pass Stage/Default Render Queue mit einem Pre-Pass-Shader(-Pass) und in die Default Stage/Default Render Queue mit einem Standard-Shader(-Pass) eintragen. Weder Stages noch Queues werden durch die Engine vorgegeben, sondern müssen zur Laufzeit durch die Anwendung oder duch Definition in einem geladenen Effekt definiert werden.

Intern wird der Rendering-Ablauf von zwei Komponenten gesteuert, der Rendering Pipeline und der Pipeline-Perpsektive. Die Pipeline verfügt über eine geordnete Liste aller zu rendernden Perspektiven, die zu Beginn eines jeden Frames aufgebaut wird. Die Abarbeitung erfolgt rückwärts, da spätere Perspektiven i.d.R. von früheren Perspektiven hinzugefügt wurden, und mit großer Wahrscheinlichkeit entsprechende Abhängigkeiten unter den Perspektiven bestehen. Pro Stage ruft die Pipeline einmal alle Perspektiven auf, welche sich in der gegebenen Stage einmal durch alle Queues arbeiten.

Die Perspektiven sortieren alle ihre Queues entsprechend eines im jeweiligen Render Job enthaltenen Sortier-Index, der standardmäßig Hashes von Shader(-Passes) und Materialien enthält, um auf eine minimale Zahl von Shader-Wechseln zu optimieren. Optional kann dieser Sortier-Index durch ein Flag in der Queue Description mit Tiefenwerten überschrieben werden, um z.B. alphatransparente Geometrie back-to-front zu rendern.

Heute gehe ich über zum bereits angesprochenen State Management. Ich habe mich in der Zwischenzeit entschieden, die Zustandsautomatennatur der Grafik-APIs vollends auszunutzen, und nach Möglichkeit alle States nur ein einziges Mal vor Beginn einer neuen Render Queue zu setzen. Sollten Renderables aus der Reihe fallen, können diese den aktuellen Zustand, wie in vorangegangenen Posts diskutiert, überschreiben und invalidieren, invalidierte States werden bei Bedarf durch das nächste Renderable zurückgesetzt. Der Hintergrund ist dabei nicht nur, API-Calls zu sparen, sondern viel wichtiger, die Engine-seitige Suche nach zu setzenden Informationen, insbesondere Konstanten und Texturen, auf ein Minimum zu reduzieren. Dazu bald mehr im Rahmen der Shader-getriebenen Architektur.

Die State-Block-Architektur in DirectX 11 bringt viele Vorteile, aber auch einige Nachteile, so werde ich jetzt eine eigene Logik dafür implementieren müssen, in Effekten vorkommende Rasterizer State Blocks mit umgekehrtem Cull Mode zu duplizieren, um je nach Ausrichtung der Perspektive (Reflexionen spiegeln das Koordinatensystem!) automatisch die Drehrichtung der Dreiecke anpassen zu können. :|
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von dot »

Hast du dir eigentlich schonmal überlegt, dem Effekt-Framework den Rücken zu kehren und stattdessen was eigenes zu basteln?
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von CodingCat »

Nein, weswegen?
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von dot »

Naja, genau wegen solchen Dingen, Flexibilität etc. Wär auch mal interessant, wie es mit dem Overhead des Effect Framework aussieht.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von CodingCat »

Das D3DX11 Effects Framework ist, soweit ich das beurteilen kann, extrem effizient. Es tut alles, um den Speicher so kompakt wie möglich zu halten (inkl. Kompaktierung mehrerer dynamisch allokierter Speicherbereiche in wenige zusammenhängende Speicherbereiche nach dem Laden), verringert Branching mittels Sprungtabellen, fasst einzelne API-Aufrufe zu möglichst großen Array-Aufrufen zusammen, kurzum, ich glaube kaum, dass das bei diesem Funktionsumfang noch viel besser geht. Auch in Bezug auf Flexibilität ist das neue D3DX11 Effects Framework sehr gelungen, der Anwender erhält nahezu vollen Zugriff auf alle Daten, die dem Framework selbst zur Verfügung stehen, gewünschte Zusatzfunktionalität lässt sich so exzellent von außen hinzufügen.

Sollte Speicher zur Laufzeit ein Problem sein, haben die Entwickler sogar so weit gedacht, dass sich sämtliche nach dem Laden und Analysieren überflüssigen Informationen mittels Optimize()-Aufruf freigeben lassen. Selbst wenn die gesamte Datenreflexion durch ein eigenes System übernommen wird, lohnt es sich also, das Framework wenigstens zum Laden einzusetzen.

Ein in Bezug auf Funktionalität auch nur halbwegs äquivalentes System selbst zu implementieren, wäre ein gigantischer Aufwand, und der Performance-Gewinn vermutlich minimal.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von CodingCat »

Da lobt man das FX Framework über alle Maßen, und schon rennt man voll rein. Standardmäßig unterstützt der HLSL-Compiler keine registergebundenen static uniforms/cbuffers, d.h. es gibt absolut keine Möglichkeit, dem Framework zu verbieten, bereits durch andere Effekte gesetzte Constant Buffers mit irgendwas, und sei es nullptr, zu überschreiben. Glücklicherweise liegen dem Framework die Sources bei, womit ich mir dieses Feature nun selbst implementieren konnte (Hack: Variablen/Objekte mit dem Präfix unmanaged_ im Namen fliegen zur Laufzeit raus).

Der Code des FX Frameworks ist jedes Mal aufs Neue eine Provokation, sämtliche Fehlerbehandlung geschieht ausschließlich über gotos, die exzessive Nutzung von Seiteneffekte macht die Auswirkungen fast jeder Änderung absolut unabsehbar. Was bleibt, ist das ungute Gefühl, dass es im restlichen DirectX-SDK nicht anders aussieht.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Zudomon
Establishment
Beiträge: 2259
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von Zudomon »

Tja, will mans richtig haben, muss mans selber machen... dann ist es vielleicht nicht besser, aber zumindest weiß man, was man verbrochen hat! :lol:
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von CodingCat »

Auf der hochfrequentesten Ebene sind Constant Buffers ein Fluch. Wegen den Layout-Garantien weigert sich der HLSL-Compiler ab Shader Model 4, unbenutzte Konstanten automatisch zu eliminieren. Das wiederum heißt, dass pro gerendertem Objekt der gesamte übergroße Constant Buffer zur GPU geschickt werden muss, auch wenn nur ein Teil tatsächlich im Shader Verwendung findet. Fazit: Alle pro Objekt variierenden ungenutzten Konstanten müssen von Hand eliminiert werden. :twisted:
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von CodingCat »

Venture into city generation ...
city2[1].png
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
joggel

Re: [Projekt] breeze 2.0

Beitrag von joggel »

Stadt generisch erstellt?
Darf man fragen, wie das angestellt wurde?
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von CodingCat »

Die Grundidee für das Straßennetz ist von hier geklaut: http://www.complexification.net/gallery ... substrate/

An zufälligen Punkten werden mit zufälliger Ausrichtung einige Linien gespawnt und schrittweise verlängert, tritt eine Kollision auf, wird die kollidierende Linie angepasst und fixiert, und auf einem zufällig ausgewählten Punkt einer zufällig ausgewählten Linie wird orthogonal eine neue Linie gespawnt. Damit sich Häuser einigermaßen sinnvoll platzieren lassen, wurde der Algorithmus noch um eine Abstandskontrolle erweitert, die dafür sorgt, dass neue Linien nicht zu nah an anderen parallel laufenden Linien spawnen.

Im Anschluss werden an den Straßenrändern Würfel platziert, die Segmente der Grundrisse werden zu den Straßen in einen Quadtree geschrieben, um Überlappungen mit nachfolgend generierten Häusern vermeiden zu können.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] breeze 2.0

Beitrag von Krishty »

Mir persönlich sehen die Grundrisse zu viel nach zersplittertem Glas aus. Dabei ließen sich ja gerade Städte des amerikanischen Imperiums sehr einfach erzeugen, indem man in diesem Algorithmus mit einer einzigen Grundlinie ohne Kollisionen beginnen würde …

für europäische, geomorph gewachsene Städte würde ich gern ein überzeugendes Konzept sehen :)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten