Eigene Direct3D-State Verwaltung Sinnvoll?



  • Ich habe vor einigen Monaten ein Buch über DX gelesen in dem empfohlen wurde die States (also Textur/Render/Sampler) in einer eigenen Klasse zu verwalten. Das ganze soltle so aussehen das man intern in der klasse ein array hat das nach der Initialisierung die aktuellen DX Werte annimmt und das man danach die State änderungen an die Klasse schickt die dann eine abfrage mit dem array macht und falls der Wert nicht schon gesetzt ist den Befehl an DX weiterleitet. Als Begründung für dieses vorgehen wurde angeführt, das man damit mehrfaches setzen des selben Wertes vermeiden kann, und man sich nicht darauf verlassen kann das der treiber das zB übernimmt. Die Frage meinerseits wäre wie sinnvoll ist so eine Art der Verwaltung wirklich, bzw macht sie überhaupt Sinn?



  • Ziel sollte sein Render-Calls mit identischen States zu gruppieren.
    Das leistet die von Dir beschriebene Klasse jedoch nicht und ist sogar ueberfluessig wenn eine sinnvolle Gruppierung bereits vorliegt.



  • Naja was sie leisten soll ist das hier:

    SetRenderState(/* */);
    //Rendern
    SetRenderState(/* */);
    //Rendern
    

    Nehmen wir an beide Aufrufe setzen den selben State. Beide gehen an die Klasse, der erste würde an DX weitergeleitet werden, beim 2. würde die if abfrage der internen Tabelle 100%ig ergeben das er schon gesetzt ist und würde den Befehl verschlucken.


  • Mod

    grundsaetzlich sollte man immer die API kapseln (ist meine persoenliche meinung). es gibt neben soeinem state cache auch andere dinge die dann immer nuetzlich sind, z.b. beim debuggen koenntest du jeden aufruf in ein logfile ausgeben und so sehen, ob alles so lief wie erwartet, oder wenn du mal nen bug fuer ein frame nur siehst, koenntest du alle frames miteinander vergleichen und dann den ausreisser finden um zu sehen was anders war.
    weiter kannst du sehr einfach profillings einbauen um in jeder scene zu sehen was das bottleneck ist, z.b. alle textur-sets aufrufe ignorieren und immer die selbe textur gesetzt haben.

    weiter kannst du deine eigene api sinnvoller machen statt die generische d3d api zu nutzen, z.b. hab ich ne 3d engine die auf psp laeuft, aber zum debuggen benutze ich dann doch noch gerne den PC und visual studio, deswegen kann ich nicht direkt den commandbuffer beschreiben und auch die states sind nicht compatible. deswegen setz ich auch nicht 10 states individuell, sondern z.b. Render(AlphaBlend,VBuffer,IBuffer) und dahinter, je nachdem ob psp oder opengl, setz ich 10 oder 20 ... states.
    so brauch ich auch nicht wie du es vor hast bei jedem state ein if einzubauen, ich kann abfragen ob der vorherige aufruf schon alphablend war so mit einem if alles ueberspringen.

    deswegen: ja, state-cache, oder state-guard oder wie auch immer das manche nennen, ist nuetzlich (es gab mal im d3d sdk ne demo die 10% performance boost gab wenn man den einschaltete), aber du solltest nicht stumpf d3d nachcoden, sondern einen wrapper schreiben der fuer dich nuetzlich ist.



  • würde die if abfrage ergeben das er schon gesetzt ist und würde den Befehl verschlucken.

    Betrachte es mal allgemeiner.
    Du haste eine Menge von Vertexbuffern, jeder mit einem Set von Renderstates.
    Zu finden ist nun eine Reihenfolge sodass die die Anzahl der noetigen Renderstate-Changes minimal ist.
    Das von Dir beschriebene Verfahren veraendert aber die Reihenfolge nicht, fuehrt also nur zum idealen Ergebis wenn die entsprechende Reihenfolge zufaellig schon gegeben ist.


  • Mod

    hellihjb schrieb:

    Das von Dir beschriebene Verfahren veraendert aber die Reihenfolge nicht, fuehrt also nur zum idealen Ergebis wenn die entsprechende Reihenfolge zufaellig schon gegeben ist.

    die komplexitaet ist meistens so hoch dass mein nicht _die_ ideale reihenfolge in annehmbarer zeit finden kann.
    sortieren bringt natuerlich was, aber selbst ohne sortieren kann man schon sehr viele redundante arbeit sparen.



  • rapso schrieb:

    so brauch ich auch nicht wie du es vor hast bei jedem state ein if einzubauen, ich kann abfragen ob der vorherige aufruf schon alphablend war so mit einem if alles ueberspringen.

    Dem kann ich ehrlich gesagt nicht ganz folgen. Wie willst du es dann amchen? Die States sind ja in einem enum geordnet, die Variante die dort vorgelegt wurde war ein array mit letzter Stelle+1 zu machen und dann einfach den Enumwert als Zugriff auf die Stelle im Array zu nutzen und dann eine if Abfrage zu machen, ob der Wert im Array mit dem übergebenen übereinstimmt. Mich würde mal inetressieren wie du das da genau meisnt, ich kann mir darunter nichts konkretes vorstellen.

    hellihjb schrieb:

    Betrachte es mal allgemeiner.
    Du haste eine Menge von Vertexbuffern, jeder mit einem Set von Renderstates.
    Zu finden ist nun eine Reihenfolge sodass die die Anzahl der noetigen Renderstate-Changes minimal ist.
    Das von Dir beschriebene Verfahren veraendert aber die Reihenfolge nicht, fuehrt also nur zum idealen Ergebis wenn die entsprechende Reihenfolge zufaellig schon gegeben ist.

    Es geht mir ja weniegr um Changes sondern viel mehr darum das doppelte Befehle ja durchaus vorkommen können, also das für einen Bufefr eben Statts gesetzt werden die shcon auf dem Wert sind, doer Ordnest du deine Buffer so genau das du imemr weißt welcher Wert gerade exakt gesetzt ist und dann nur das setzt was nichts chon passend vorher gesetzt wurde?



  • Xebov schrieb:

    rapso schrieb:

    so brauch ich auch nicht wie du es vor hast bei jedem state ein if einzubauen, ich kann abfragen ob der vorherige aufruf schon alphablend war so mit einem if alles ueberspringen.

    Dem kann ich ehrlich gesagt nicht ganz folgen. Wie willst du es dann machen?

    Man abstrahiert die Api derart, dass jeweils eine Menge von Renderstate gleichzeitig gesetzt werden.

    Xebov schrieb:

    Ordnest du deine Buffer so genau das du imemr weißt welcher Wert gerade exakt gesetzt ist und dann nur das setzt was nichts chon passend vorher gesetzt wurde?

    Ja, das ergibt sich aber schon einige Abstraktionsebenen hoeher indem Objekte Materialien zugewiesen werden.


  • Mod

    hellihjb schrieb:

    Xebov schrieb:

    rapso schrieb:

    so brauch ich auch nicht wie du es vor hast bei jedem state ein if einzubauen, ich kann abfragen ob der vorherige aufruf schon alphablend war so mit einem if alles ueberspringen.

    Dem kann ich ehrlich gesagt nicht ganz folgen. Wie willst du es dann machen?

    Man abstrahiert die Api derart, dass jeweils eine Menge von Renderstate gleichzeitig gesetzt werden.

    genau, wie ich oben als beispiel gab z.b.

    Render(Alphablend,VBuffer,IBuffer);
    

    Alphablend ist bei mir entsprechend ein enum von moeglichen zeichenarten.
    so ist das sortieren spaeter auch einfacher, weil du nicht 100 verschiedene states anordnen musst, sondern nur die paar flags.
    aber selbst ohne sortieren hast du schon recht viel gewinn, normalerweise.



  • rapso schrieb:

    genau, wie ich oben als beispiel gab z.b.

    Render(Alphablend,VBuffer,IBuffer);
    

    Alphablend ist bei mir entsprechend ein enum von moeglichen zeichenarten.
    so ist das sortieren spaeter auch einfacher, weil du nicht 100 verschiedene states anordnen musst, sondern nur die paar flags.
    aber selbst ohne sortieren hast du schon recht viel gewinn, normalerweise.

    Kannst du das evtl noch etwas genauer ausführen mit nem Beispiel? Bei den vielen States die es in DX gibt kann ich mir so eine Anordnung irgendwie gerade garnicht bildlich vorstellen.


  • Mod

    Xebov schrieb:

    rapso schrieb:

    genau, wie ich oben als beispiel gab z.b.

    Render(Alphablend,VBuffer,IBuffer);
    

    Alphablend ist bei mir entsprechend ein enum von moeglichen zeichenarten.
    so ist das sortieren spaeter auch einfacher, weil du nicht 100 verschiedene states anordnen musst, sondern nur die paar flags.
    aber selbst ohne sortieren hast du schon recht viel gewinn, normalerweise.

    Kannst du das evtl noch etwas genauer ausführen mit nem Beispiel? Bei den vielen States die es in DX gibt kann ich mir so eine Anordnung irgendwie gerade garnicht bildlich vorstellen.

    was genau kannst du dir nicht vorstellen? ich setze einfach alle noetigen state fuer diese art von drawcall, nichts besonderes.



  • Wie das bei dir im inneren Funktioniert. Du hast ein Flag das du setzt und in der Funktion wird das Flag dann zerlegt und die States gesetzt? Deinen Prüfmechanismuß kann ich nicht nachvollziehen. Wenn ich ein Array habe ist das ja klar, das Array ist ein Spiegelbild der states, ich setze ihn und wenne r schon gesetzt ist geht es unter, das wäre ein State, ein Call, aber wie genau Funktioniert das bei dir? Ich steig da nicht durch.


  • Mod

    Xebov schrieb:

    Wie das bei dir im inneren Funktioniert. Du hast ein Flag das du setzt und in der Funktion wird das Flag dann zerlegt und die States gesetzt? Deinen Prüfmechanismuß kann ich nicht nachvollziehen.

    mein pruefmechanismus:

    if(DrawCallType==Alphablend && ...
    

    Wenn ich ein Array habe ist das ja klar, das Array ist ein Spiegelbild der states, ich setze ihn und wenne r schon gesetzt ist geht es unter, das wäre ein State, ein Call, aber wie genau Funktioniert das bei dir? Ich steig da nicht durch.

    ... DrawCallType!=LastDrawCallType)
     //states setzen
    

    ich weiss nicht was ich dazu noch schreiben sollte 😕 .



  • OK, ich versuchs mal etwas ausführlicher auszudrücken. DX hat ja insgesammt über 100 States. Mir fehlt hier gerade ein Ansatz wie du die geordnet hast um sie als Flag Nutzbar zu machen (was ja auf einem unsigned int gerademal 32 wären). Deine Prüfengine verstehe ich auch nur zum Teil, ich schätze mal du speicherst die beim letzten Aufruf übergebenen Flags und schaust ob dieses Packet vorher schon durch ist und man sich das setzen sparen kann (dh du gehst nicht nach einzellnen Statts vor?).


  • Mod

    Xebov schrieb:

    Mir fehlt hier gerade ein Ansatz wie du die geordnet hast um sie als Flag Nutzbar zu machen (was ja auf einem unsigned int gerademal 32 wären).

    ich verstehe dein problem nicht, mein mit alphablend gerendertes objekt setz alle alphablend states. was soll ich da noch fuer flags nutzbar machen?

    Deine Prüfengine verstehe ich auch nur zum Teil,

    jetzt ist der pruefmechanismus zu einer pruefengine aufgestiegen? bin schon gespannt, ob du mich gleich weiter ueber mein geo-orbitales-kaskadiertes-state-caching mit pay-back functionality fragst 😉
    es ist nur eine if-abfrage wie du siehst.

    ich schätze mal du speicherst die beim letzten Aufruf übergebenen Flags und schaust ob dieses Packet vorher schon durch ist und man sich das setzen sparen kann (dh du gehst nicht nach einzellnen Statts vor?).

    nochmals:

    if(DrawCallType==Alphablend && DrawCallType!=LastDrawCallType)
    {
      LastDrawCallType=DrawCallType;
      //schalte blend ein
      //setze blend mode
      //schalte zwrite aus
      //sag meiner oma sie soll das lciht ausmachen
    }
    //zeichne objekt
    

    das ist so simpel, ich sehe nicht was man daran missverstehen kann.



  • rapso schrieb:

    ich verstehe dein problem nicht, mein mit alphablend gerendertes objekt setz alle alphablend states. was soll ich da noch fuer flags nutzbar machen?

    OK jetzt verstehe ich was du meinst, ich hatte mir das ganze etwas komplexer vorgestellt.

    rapso schrieb:

    mit pay-back functionality fragst 😉

    Das käme jetzt ganz drauf an was es ja zurückgezahlt gibt 😃

    rapso schrieb:

    das ist so simpel, ich sehe nicht was man daran missverstehen kann.

    Ich bin davon ausgegangen das du da Flags zusammenfasst, deswegen hatte ich mir das ganze etwas Komplexer vorgestellt, dachte nicht das du das so einfach gelöst hast.


  • Mod

    Xebov schrieb:

    Ich bin davon ausgegangen das du da Flags zusammenfasst, deswegen hatte ich mir das ganze etwas Komplexer vorgestellt, dachte nicht das du das so einfach gelöst hast.

    die sind zusammengefasst, das ist natuerlich applikationen spezifisch und keine generische loesung, aber oft ist das sehr viel besser als eine generische loesung, eben weil du sehr einfach die reihenfolge optimieren kannst, um wenig stats zu setzen und als entwickler siehst du dann auch die probleme direkt

    stell dir 3 drawcalls vor

    dc1: shaderA, ObjectA
    dc1: shaderB, ObjectA
    dc1: shaderA, ObjectB

    welche reihenfolge ist jetzt die beste? und jetzt stell dir das noch mit 6drawcalls und textureA textureB vor, jetzt mit 10000drawcalls und allen moeglichen states...

    alternativ
    DC1: diffuse, objectA
    DC1: alpha, objectB
    dc1: diffuse, objectB

    nein, das ist nicht das selbe, diffuse und alpha haben 10 dinge die du setzt.
    du kommst im ganzen spiel zu 99% mit vielleicht 2 oder 3 grossen states aus, texturen und per object shader parameter muessen vermutlich extra, aber das sortierst du einmal und hast mit wenig aufwand eine recht gute reihenfolge von states die du nur aenderst, wenn es wirklich zu eine neuen gruppe von objekten kommt z.b. alpha-objekte.

    das ist nicht analytisch perfekt. aber von aufwand/nutzen bist du bei sehr wenig aufwand (ID+sortieren) und 98% perfektion.
    desweiteren hast du volle kontrolle ueber sowas kritisches und musst dir nicht irgendwann eine level ansehen bei dem artist so ziemlich jedem objekt um 0.1% andere farben und blend-werte gaben und deswegen dein ganze caching fuer'n A.. sind 😉



  • Das hört sichs chonmal nicht schlecht an, was mich da aber mal interessieren würde, wie evrwaletst dus wenn du fx Files vorgesetzt bekommst? Übersetzt du die dann auch in das System?


  • Mod

    Xebov schrieb:

    Das hört sichs chonmal nicht schlecht an, was mich da aber mal interessieren würde, wie evrwaletst dus wenn du fx Files vorgesetzt bekommst? Übersetzt du die dann auch in das System?

    ich benutze keine fx files. die sind eigentlich der kontrast zu performance, wenig arbeit, generallisiert, aber halt an vielen stellen suboptimal, ist sowas wie c# fuer shadern.

    ich sag nichts gegen c# oder fx files, den meisten sollte das reichen, weil es vieles mit ausreichend leistung ohne nachdenken moeglich macht.


Log in to reply