idee von einem dynamischen vertex/indexbuffer



  • (ziemlich lang)
    die idee kam mir grad beim abend essen, als ich ein bischen über das problem des statischen indexbuffers nachgedacht habe(da ich nicht weis, ob sich vertex/index buffer in ogl genauso verhalten wie in directx,weil ich ogl nicht kenne, bezieht sich meine idee erstmal nur auf directX):

    also, jeder kennt das problem: einerseits müssen programmierer drauf achten, dass ein buffer nicht zulange gelockt bleibt, andererseits kann der programmierer manchmal im vorfeld garnicht wissen,wieviele vertices bzw indices er zu erwarten hat.

    bei einem vertice ist das problem nicht ganz so schlimm, im zweifelsfall kann der programmierer einen großen vector anlegen, und am ende einfach die größe abfragen,einen buffer der entsprechenden größe erstellen und füllen,fertig.

    bei indizes sieht das ja schon anders aus.
    zwar kann man auch sie in einen großen vector packen, doch bekommt man dann probleme wenns darum geht,die einzelnen indizes zu rendern-immerhin brauchen ja viele abschnitte andere render states als andere.
    Das im Vorfeld zu ordnen, und dann während des renderns immer den entsprechenden bereich aufzurufen kann sehr komplex werden.

    und hier setzt meine idee an:
    (hoffe ich erklärs jetzt anschaulich genug)
    gehen wir mal von dem ansatz aus, dass jedes objekt aus x verschiedenen index buffern besteht-für jede render-state kombination die vorkommt einen.

    verschiedene objekte haben verschiedene-aber auch gleiche renderstates, und objekte gleichen typs sind in dem punkt ja auch absolut gleich.

    nun wärs ja absolut unübersichtlich, für jedes objekt x indexbuffer anzulegen,stellt euch nur mal vor, ihr müsstet sie alle locken, füllen, und dann rendern 🙄.

    aber man könnte ja auch eine Klasse erstellen, die folgende eigenschaften hat:
    jede Klasse hat platz für die eigenen indizes, inklusive renderstates,materialien usw(oder auch nur techniken),und eine rendermethode, die die indizes,mithilfe des passenden vertexbuffers rendert.

    bisher nichts ungewöhnliches, wenn jemand mit der klasse so wie ich sie bisher vorgestellt habe weiterarbeiten würde, wäre er morgen noch nicht mit der berechnung des ersten komplexeren bildes fertig 😉

    der Clou ist, dass jede Klasse mitglied einer liste sein kann, aber auch startpunkt einer eigenen liste ist(damit mein ich erstmal nicht std::list, über den typ bin ich mir noch nicht so im klaren)

    was mein ich nun damit?
    nehmen wir mal an, ich habe ein objekt mit 7 verschiedenen teilen(also mit unterschiedlichen renderstates):
    ich erstelle zuerst die erste indexbuffer klasse, ich gebe die renderstates an, und übergeb ihm die indizes.
    nun erstell ich eine 2. Klasse dieses Typs, mit anderen states, und nun kommts: ich übergebe diesen neuen buffer dem zuerst erstellten:

    B1=Buffer1 B2=Buffer2 D=Data
    Die buffer einzeln:                        und nun zusammen:
      D    D                                     D---->D 
     /    /                                     /     [e]uarr[/e]
    B1   B2                                    B1    B2
    

    wie man sieht hab ich in der zeichnung beim buffer 2 nach dem zusammenlegen den Pfeil verändert. Dies tat ich, um zu zu zeigen, dass die Klasse nun nicht mehr das recht hat, die Daten zu löschen-nurnoch zu verändern.
    Dies ist wichtig, was man im folgenden sehen kann:
    Ich habe ja davon gesprochen,dass jeder punkt mitglied einer liste, und startpunkt einer liste sein kann, in der obigen zeichnung sind zuerst b1 und b2 jeweils startpunkte einer eigenen liste(mit jeweils einem datenfeld), im 2. teil der zeichnung ist dann b1 startpunkt einer liste, und b2 mitglied einer liste.
    Aber ich sprach ja davon, dass jede Klasse beides sein kann, also noch eine zeichnung um dies zu verdeutlichen.
    als ausgangspunkt nehmen wir 2 Buffer der form, wie sie in teil2 von zeichnung 1 aussahen:

    einzeln:
      D---->D    D---->D
     /     [e]uarr[/e]    /     [e]uarr[/e]
    B1    B2   B3    B4
    addiert:
        B3     B4
       [e]darr[/e]      [e]darr[/e]
       D      D
      /      /
      D---->D
     /     [e]uarr[/e]
    B1    B2
    

    dabei sollte klar werden, dass der 2. operator immer die "anrechte" auf den gegenstand verliert, was "clonen" verhindern soll-der buffer kann halt nicht 2 mal addiert werden, desweiteren kann dieser buffer nun nichtmehr ziel eines anhängversuchs werden-es wär ja nun auch weitestgehend sinnlos.
    Was man noch sieht ist, dass die neuankömmlinge sich an die alteingesessenen Buffer angehängt haben-in dem beispiel geh ich ganz einfach davon aus, dass b1 die gleichen stats wie b3 hat und b2 die gleichen wie b4.
    sinn dieser verkettung nach oben ist,dass gleiches zu gleichem kommt,hinterher beim rendern kann die renderfunktion alle anhängsel inklusive sich selber als EINEN Buffer betrachten-ein ungemeiner arbeits- und zeitvorteil.
    im oberen beispiel waren die beiden Buffer ja logisch gleich, was ist aber wenn sie das nicht sind?
    dann passiert das:

    einzeln:
      D---->D    D---->D
     /     [e]uarr[/e]    /     [e]uarr[/e]
    B1    B2   B3    B4
    zusammen:
      D---->D---->D---->D
     /     [e]uarr[/e]     [e]uarr[/e]     [e]uarr[/e] 
    B1    B2    B3    B4
    

    wenn ihr nun hier angekommens eid hab ihr diesen doch recht langen text geschafft 😉
    nun dürft ihr eine bewertung angeben,skala ist egal,ob punkte, schulnoten, skalen von -unendlich bis +unendlich, ihr habt die absolute freiheit(aber bitte immer angeben, wie die skala misst :D)

    ne jetzt mal ernsthaft: wie findet ihr die idee, bis zum rendern bleibt ja jeder buffer faktisch autonom, er kann verändert werden, vergrößert verkleinert(vielleicht sogar gelöscht, wer weis,was sich im laufe der code arbeiten noch ergibt).
    vielleicht bietet das system auch vorteile beim culling,jeder buffer stellt einen teil dar, und jeder teil ist so, wie der programmierer es sich vorstellt, einen buffer zu cullen würde theoretisch nur bedeuten, ihn nicht anzuhängen-wenn er im nächsten frame sichtbar ist, kann er ja einfach wieder angehängt werden.



  • Ich glaube, ich habe nicht so ganz verstanden, was du machen willst. Oder ich erkenne einfach nicht den Vorteil deiner Methode anstatt das einfach jedes Objekt seinen Indexbuffer hat.

    Bye, TGGC \-/



  • Ich habe es so verstanden, dass er seine ganzen Modelle (also die Indexbuffer) nach ihrem Material in einer Art Baum sortiert. Und das alles bei recht dynamischer Geometrie.

    @otze: Sag doch mal, was Du da programmierst. Was ändert sich an Deiner Geometrie denn, wenn Du dauernd locken musst? So ist das alles etwas abstrakt, ich bin jemand der Beispiele braucht 😉



  • ích drück mal das prinzip aus meinem ersten post anders aus:
    wenn man 10 speicherbereiche hat, und man jeden speicherbereich der gleichen prozedur unterziehen muss, wobei jeder speicherbereich logisch gleich ist, wieso legt man sie nicht alle zusammen,und ruft die funktion nur ein einziges mal auf?

    bsp: gerendert werden müssen: 10 objekte des selben typs.

    //pseudocode, der nur das prinzip verdeutlichen soll
    PDIRECT3DINDEXBUFFER9 Buffer;
    short* data,
    Objekts obj;//10 objekte selben typs
    D3DDevice9->Create(obj.size()*2,D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC,D3DFMT_INDEX16,D3DPOOL_DEFAULT,&Buffer);
    Buffer->Lock(0,0,static_cast<void**>(&data),D3DLOCK_NOSYSLOCK);
    obj.copyindizes(data);
    Buffer->Unlock;
    //Ergebnis: 10 objekte des selben typs sind nun im selben buffer, und können
    //auch gemeinsam gezeichnet werden.
    


  • so, hab mal angefangen, das ganze aufzuschreiben, und die indexbuffer gehen (wenigstens auf dem papier) ganz gut...wieder erwarten.
    dafür sind die vertexbuffer ein problem...auch wieder erwarten.

    das problem besteht einfach darin, dass die vertexbuffer ihre daten im gegensatz zu indexbuffern plan speichern, bzw plan speichern müssen,und ich somit ein problem hab, den vertexbuffer zu setzen- setz ich einfach alle vertices, egal ob sie überhaupt gebraucht werden oder nicht, dann weis ich nicht was passiert, ich hab auch irgendwie nich lust herauszufinden, was passieren wird, die punkte werden aber höchstwahrscheinlich nicht einfach übersprungen-bildfehler und bremsklotz.
    wenn ich aber nur die wirklich gebrauchten punkte raussuche-dann kann ich mit tausenden pointern arbeiten, da ja einige punkte weit ab vom schuss sein können, wenn zb 3 indexbuffer einen punkt brauchen 🙄.

    ich glaub, ich werd wohl doch besser aufn bissl speicherplatz verzichten müssen, und bestimmte punkte einfach doppelt in den speicher laden, und jedem indexbuffer seinen eigenen verticebuffer spendiren...oder hat jemand ne bessere idee?



  • Das Problem dürfte sein nach was man sortiert?

    Schau dir doch mal das hier an:

    http://www.mvps.org/directx/articles/vcache.htm



  • @1:Ja, so versteh ich das auch. Aber warum?
    @otze: Was hast du denn für eine Prozedur, die du auf alle deine Indexbuffer anwenden willst? Und warum willst du 10 Objekte gleichen Typs, eines reicht doch?

    Bye, TGGC \-/



  • TGGC schrieb:

    @1:Ja, so versteh ich das auch. Aber warum?
    @otze: Was hast du denn für eine Prozedur, die du auf alle deine Indexbuffer anwenden willst? Und warum willst du 10 Objekte gleichen Typs, eines reicht doch?

    wenn du 10 schrauben hast, sind sie auf verschiedenen positionen,also haben sie alle verschiedene punkte.deshalb brauch ich sie 10 mal,wenn ich da nicht in jedem frame alle punkte neu berechnen will, egal ob ne bewegung stattfand oder nicht.

    eine prozedur die auf alle indexbuffer gleichen typs zutrifft ist zb render().



  • wenn du 10 schrauben hast, sind sie auf verschiedenen positionen,also haben sie alle verschiedene punkte.deshalb brauch ich sie 10 mal,wenn ich da nicht in jedem frame alle punkte neu berechnen will, egal ob ne bewegung stattfand oder nicht. 
    
    eine prozedur die auf alle indexbuffer gleichen typs zutrifft ist zb render().
    

    Moment mal: Wenn du zehn Schrauben Objekte gleicher Geometrie nur mit verschiedenen Positionen/Rotationen/Skalierungen hast, dann brauchst du Vertex und Indexbuffer nur einmal. Beim rendern setzt du die Position etc. mit der World-Matrix und die Sache hat sich.

    Oder verpeil ich das gerade? 😕



  • du verpeilst das derbe 😃 , weil du dann immernoch 10mal rendern musst, der sinn ist es aber nur einmal alle schrauben gleichzeitig rendern zu müssen



  • otze schrieb:

    wenn du 10 schrauben hast, sind sie auf verschiedenen positionen,also haben sie alle verschiedene punkte.deshalb brauch ich sie 10 mal,wenn ich da nicht in jedem frame alle punkte neu berechnen will, egal ob ne bewegung stattfand oder nicht.

    eine prozedur die auf alle indexbuffer gleichen typs zutrifft ist zb render().

    Rate mal, warum Grafikkarten _T_nL in Hardware machen. Wenn du renderst brauchst du auch nicht alle Index-Daten gleichzeitig. IMHO weisst du noch nicht genug, um dir so'n System auszudenken. Lass es besser und sammle noch einige Erfahrung.

    Bye, TGGC \-/



  • TGGC schrieb:

    Rate mal, warum Grafikkarten _T_nL in Hardware machen. Wenn du renderst brauchst du auch nicht alle Index-Daten gleichzeitig. IMHO weisst du noch nicht genug, um dir so'n System auszudenken. Lass es besser und sammle noch einige Erfahrung.

    Bye, TGGC \-/

    1._T_ funktioniert meines wissens aber nur dann, wenn man keine shader benutzt, benutz ich die aber, dann fällt der Punkt schonmal flach.
    2. wieso sollte die hardware nicht alle indexdaten brauchen, die zu den vertices und damit zu den dreiecken die gerendert werden sollen gehören?
    Steh ich da irgendwo auf der Leitung?
    3. man kann nur lernen, wenn man sich neuen herausforderungen stellt,und wenn man dabei auf die schnauze fliegt, dann ist das nur ein weiterer Lerneffekt 🤡



  • 1._T_ funktioniert meines wissens aber nur dann, wenn man keine shader benutzt, benutz ich die aber, dann fällt der Punkt schonmal flach.

    Der Shader macht doch grade das _T_. Ist jetzt im Gegensatz zu Dx7 Grakas einigermaßen frei programmierbar.

    Du willst 10 x das gleiche Objekt mit einem DrawPrimitive() Aufruf rendern? Ich kann mir nicht vorstellen, wie das gehen soll,
    außer Du kopierst den Indexbuffer 10x hintereinander und renderst den dann.
    Aber vielleicht irre ich mich, Belehrung erwünscht.



  • Die shader ERSETZEN das _T_. Wenn man keine shader benutzt, wird das TnL aufgerufen, wenn man shader benutzt, ist man dafür verantwortlich.

    Du willst 10 x das gleiche Objekt mit einem DrawPrimitive() Aufruf rendern?

    erstmal benutze ich DrawIndexedPrimitive(),dann kann ich alle 10 objekte direkt hintereinander in den index/vertexbuffer der graka schreiben,die indizes kümmern sich um den rest.

    ergo hab ich im endeffekt nur einen aufruf,natürlich unter der vorraussetzung, dass alle objekte die gleichen render/textur states etc haben,und das kommt einen wirklich billig, da ein dafür richtig eingestellter buffer noch bei sehr hohen vertexzahlen eine konstante geschwindigkeit aufweist zb bei einem schon etwas älteren system blieb der entsprechende vertexbuffer noch bei 32000 vertices bei konstant 10ms(das war unter testbedingungen), heutzutage sollten es noch viel mehr sein; die daten beziehen sich auf das Buch"3D Spiele programmierung" von David Scherfgen.
    Nun kann man ja etwas berechnen, wieweit man normalerweise einen vertex/index buffer füllt,wahrscheinlich nicht mehr als 1000-2000 Einträge pro renderdurchgang,dafür aber vielmehr durchgänge, und das kostet...

    Die ganze sache ist zwar auch nicht sehr resourcenschonend, und ich weis nicht, wieviel es kostet die einzelnen vertices mit ihren matritzen über die cpu zu jagen, anstatt über die gpu,aber da das culling um einiges leichter sein dürfte,da ich die buffer theoretisch nur "aushängen" muss,kann das system doch wieder ne ganze menge rausholen.

    genaueres kann ich natürlich im vorfeld nicht sagen,da mir die theoretischen grundlagen fehlen, um die komplexität eines algorithmus zu berechnen...

    ps: für das resourcenproblem kann man aber auch ne lösung finden:
    gleiche modelle brauchen genau die gleichen indizes, und für die vertizes würden die eines "grundmodells" reichen, die man einfach nur bei anforderung ausliest, mithilfe einer matrix richtig ausrichtet und an den vertexbuffer schickt-aber das kostet schonwieder^^



  • Hi,

    so ein System lohnt sich nur dann (und da auch nur eventuell) wenn das zu Rendernde Objekt (z.B. die Schraube) sehr wenige Vertices hat. Sobald das über 100 Tris hinausgeht dürfte es schneller sein, das ganze über dynamisch Indexbuffer mit mehreren Draw***Primitive() Aufrufen zu rendern. Dafür ist die Grafikkarte ja da. Culling macht man ja auch i.d.R. nur auf der Ebene von Bounding Objekten die mit den zu rendernden Triangles vom geometrischen Standpunkt her wenig zu tun haben 🙂

    Ciao,
    Stefan



  • klar haben dreiecke nur begrenzt mit den bounding boxes zu tun, andererseits müssen die dreiecke aber auch verworfen werden,wenn sie nicht sichtbar sind, und da ist das false setzen eines bool wertes im entsprechenden buffer um einiges einfacher.

    jetzt nochmal zum rendern:
    rufst du für jeden teilbuffer DrawIndexedPrimitive auf, sind das ja n*10ms die nur fürs laden des vertexbuffers nach den oben genannten daten draufgehen,obwohl rein rechnerisch heutzutage wahrscheinlich weniger zeit benötigt wird, aber dazu auch die anzahl der angezeigten objekte und teilbuffer sich drastisch erhöht hat.
    Aufjedenfall muss diese zeit auch erstmal wieder reingeholt werden.

    aber wenn ihr sagt, dass die punktrechnerei soviel zeit verschlingt, dann muss ich mir vielleicht ein anderes system überlegen.und im zweifesfall kann man auch irgendwann später mal beide systeme gegeneinander austesten...



  • Hi,

    andererseits müssen die dreiecke aber auch verworfen werden,wenn sie nicht sichtbar sind

    Nein. Je nach Cullingebene die Du auf der CPU durchnudelst kann die Grafikkarte schneller rasterisieren und Pixel verwerfen als Du einzelne Dreiecke auf der CPU cullen kannst. Heutzutage lohnen sich eher Top-Level Scene-Management Ansätze wo man nur Gruppen von Objekten (nicht Triangles) auf Sichtbarkeit prüft.

    sind das ja n*10ms die nur fürs laden des vertexbuffers

    Das kann man pauschal nicht sagen. Es hängt vom Memory ab in dem die Buffer lagern, wie voll der VRAM in just diesem Moment ist usw.

    Die Grundidee viel Geometrie in einem Call rendern zu können ist sicherlich schon richtig. Aber dieses System hat auch Grenzen. Beispielsweise gibt es für jede Grafikkarte eine bestimmte Menge an Triangles die optimal für einen Call ist. Und das variiert von ATI zu NVIDIA und natürlich auch von Karte zu Karte. Tausende von Triangle in einem Call zu rendern ist nicht zwangsläufig schneller als Hunderte von Polygonen in Dutzenden von Calls zu rendern.

    Ciao,
    Stefan



  • Hi,

    andererseits müssen die dreiecke aber auch verworfen werden,wenn sie nicht sichtbar sind

    Nein. Je nach Cullingebene die Du auf der CPU durchnudelst kann die Grafikkarte schneller rasterisieren und Pixel verwerfen als Du einzelne Dreiecke auf der CPU cullen kannst. Heutzutage lohnen sich eher Top-Level Scene-Management Ansätze wo man nur Gruppen von Objekten (nicht Triangles) auf Sichtbarkeit prüft.

    ich hab mich missverständlich ausgedrückt, ich meinte einfach, dass man den ganzen teilbuffer auf false setzt wenn er nichmehr sichtbar ist, ich wollte das nich so lowlvl machen.

    sind das ja n*10ms die nur fürs laden des vertexbuffers

    Das kann man pauschal nicht sagen. Es hängt vom Memory ab in dem die Buffer lagern, wie voll der VRAM in just diesem Moment ist usw.

    das hab ich ja auch direkt im nächsten satz gesagt, dass es wahrscheinlich nichmehr soviel zeit brauchen wird

    Die Grundidee viel Geometrie in einem Call rendern zu können ist sicherlich schon richtig. Aber dieses System hat auch Grenzen. Beispielsweise gibt es für jede Grafikkarte eine bestimmte Menge an Triangles die optimal für einen Call ist. Und das variiert von ATI zu NVIDIA und natürlich auch von Karte zu Karte. Tausende von Triangle in einem Call zu rendern ist nicht zwangsläufig schneller als Hunderte von Polygonen in Dutzenden von Calls zu rendern.

    Dann werd ich meine idee wohl erstmal noch ruhen lassen müssen..bis ich mehr erfahrung hab 😉
    leider steht aber schond ie grundstruktur der Buffer, glücklicherweise hab ich immer auf die interfaces geachtet, muss nur ein paar implementationen neu machen, dann kann ich wenigstens effektiv verhindern, dass zuviel zwischen 2 zeichenvorgängen geändert werden muss, is ja auch was^^

    und wehe jetzt kommt jemand mit dem argument, dass die zeit die man zum sortieren benötigt, die zeitersparnis auffrisst, dann schrei ich 😃



  • Mir scheint die Gelegenheit hier ganz gut, um mal eine Frage anzubringen:

    Die Geometrie meines Levels liegt komplett in einem einzigen Vertex und einem einzigen Indexbuffer. Das alles wird "virtuell" in einem Baum aufgeteilt,
    und in den Blättern dieses Baumes wird dann nur noch gespeichert, wo sich die zum Blatt gehörenden Indexe im großen Indexbuffer befinden. Das ganze ist innerhalb des Blattes
    natürlich noch nach Material sortiert.
    Beim Cullen erhalte ich dann für jedes Material eine Liste von solchen Subsets, die zu rendern sind. Ich rufe dann für jedes davon einmal DrawIndexedPrimitive() auf.

    Kann man das geschickter machen? Wenn ich immer erst alle Indexe mit dem gleichen Material in einen Indexbuffer kopieren und den zur Grafikkarte
    hochladen muss ist das ja auch nicht schneller als viele Draw Aufrufe !?



  • 0x00000001 schrieb:

    Die Geometrie meines Levels liegt komplett in einem einzigen Vertex

    😃 😃 Geil! Was für 'ne Matrix nutzt Du dafür!?!? 🤡 😉


Anmelden zum Antworten