Performance-Frage [weiteres Problem!]
-
Hi!
Ich hab schon ne Weile nicht mehr in C++ programmiert und hatte eine Idee für ein primitives 3D-Spiel.Das Grundgerüst steht zum Teil schon. Ich kann einfach Objekte (Flächen, Würfel,...) erstellen und rendern.
Das Problem dass dabei nun aufgetaucht ist ist folgendes - von dem ich gar nicht weiß ob es ein Problem ist.Vorweg: Mein Rechner ist nicht mehr der Neueste, daher könnte es auch am Rechner liegen.
"Problem":
Ich zuerst lauter einzelne Klassen für die 3D Objekte erstellt (Würfel, Flächen,..) und dann eine die diese verwaltet (in einem vector speichert)
Die Objekte speichern Rotation, Position usw, aber nicht die Vertices der Objekte.
Das Darstellen findet in der Objectmanager-Klasse statt:for(int q=0;q<numOfObjects;q++){ [...] CUSTOMVERTEX cbuf[]={....}; BYTE* VertexBufferStart; Device->CreateVertexBuffer(sizeof(cbuf), D3DUSAGE_WRITEONLY, D3D_CUSTOMVERTEX, D3DPOOL_MANAGED, &VB, NULL); VB->Lock(0,0,(void**)&VertexBufferStart,0); memcpy(VertexBufferStart,cbuf,sizeof(cbuf)); //VertSize=sizeof(cbuf); VB->Unlock(); Device->SetFVF(D3D_CUSTOMVERTEX); // Objects[q]->Draw(MyResMan); Device->SetTransform( D3DTS_WORLD, &Objects[q]->WorldMatrix); Device->SetStreamSource(0,VB,0,sizeof(CUSTOMVERTEX)); if(!Objects[q]->activateTextures){ Device->SetTexture(0,NULL); Device->DrawPrimitive(D3DPT_TRIANGLELIST,0,12); } else{ Device->SetTexture(0,MyResMan.getTexture(Objects[q]->TextureID[0]).getTexture()); Device->DrawPrimitive(D3DPT_TRIANGLELIST,0,2); Device->SetTexture(0,MyResMan.getTexture(Objects[q]->TextureID[1]).getTexture()); Device->DrawPrimitive(D3DPT_TRIANGLELIST,6,2); Device->SetTexture(0,MyResMan.getTexture(Objects[q]->TextureID[2]).getTexture()); Device->DrawPrimitive(D3DPT_TRIANGLELIST,12,2); Device->SetTexture(0,MyResMan.getTexture(Objects[q]->TextureID[3]).getTexture()); Device->DrawPrimitive(D3DPT_TRIANGLELIST,18,2); Device->SetTexture(0,MyResMan.getTexture(Objects[q]->TextureID[4]).getTexture()); Device->DrawPrimitive(D3DPT_TRIANGLELIST,24,2); Device->SetTexture(0,MyResMan.getTexture(Objects[q]->TextureID[5]).getTexture()); Device->DrawPrimitive(D3DPT_TRIANGLELIST,30,2); }Also erstelle ich jedes mal einen temporären Vertexbuffer und frage nur die Größe, und Form der einzelnen objekte ab. Das funktionierta uch alles.
und wenn ich keine Texturen verwende - sondern nur die Farben Vertices interpoliere - also einfach einen Würfel mit verschiedenen Seitenfarben darstelle - dann läuft alles bei sauberen 60 FPS
Wenn ich aber Texturen verwende, sinkt das ganze auf <=30 FPSIn dem Beispielprogramm hab ich einen Großen Würfel der aus lauter kleineren Würfeln besteht eingebaut. (Also 7*7*7 Würfel - wobei jeder Würfel aus 12 Dreiecken besteht - somit also 36 Vertices pro Würfel )
Nun ist meine Frage:
Ist das normal? Ich meine zwar sind es 7*7*7*36 Vertices = 12348 Vertices Aber davon werden ja nie alle gleichzeitig dargestellt (Kehrseite der Würfel) Also sind höchstens 7*7*7*18 =6174 zu selben Zeit sichtbar.Bzw. 2058 Texturen. (343 Würfel mit 3 Seiten)
Bei Spielen die ich bisher gespielt habe wurden sicher viel mehr Objekte und Texturen dargestellt aber die FPS sind nicht so rapide abgefallen.
Da kann doch etwas nicht stimmen oder?
Wie gesagt...wenn ich keine Texturen festlege, dann läuft alles bei 60 fps ... mit texturen sind es dann nur mehr 30...wenn ich die Würfelmenge erhöhe sinken auch die FPS.Soweit ich festgestellt habe liegt es aber nur an diesem Teil:
Device->SetTexture(0,MyResMan.getTexture(Objects[q]->TextureID[0]).getTexture()); Device->DrawPrimitive(D3DPT_TRIANGLELIST,0,2); Device->SetTexture(0,MyResMan.getTexture(Objects[q]->TextureID[1]).getTexture()); Device->DrawPrimitive(D3DPT_TRIANGLELIST,6,2); Device->SetTexture(0,MyResMan.getTexture(Objects[q]->TextureID[2]).getTexture()); Device->DrawPrimitive(D3DPT_TRIANGLELIST,12,2); Device->SetTexture(0,MyResMan.getTexture(Objects[q]->TextureID[3]).getTexture()); Device->DrawPrimitive(D3DPT_TRIANGLELIST,18,2); Device->SetTexture(0,MyResMan.getTexture(Objects[q]->TextureID[4]).getTexture()); Device->DrawPrimitive(D3DPT_TRIANGLELIST,24,2); Device->SetTexture(0,MyResMan.getTexture(Objects[q]->TextureID[5]).getTexture()); Device->DrawPrimitive(D3DPT_TRIANGLELIST,30,2);Egal ob ich vorher etwas rotieren lasse oder verschiebe.
Ich hoffe ihr versteht was ich meine
Dazu habe ich noch das Problem dass die Kanten der Würfel (wenn sie weiter weg sind) flimmern.
Wie gesagt...habe schon lange nicht mehr mit c++ programmiert und mit Direct3D sowieso nur kaum.
Hier noch ein Bild damit ihr versteht wie das Gebilde aussieht:
http://i191.photobucket.com/albums/z231/kuldren/WrfelWrfel.jpg
KURZFASSUNG:
Hab ich einen groben Denkfehler in meinem Programm oder liegts einfach an meinem Rechner?
(Später soll jedes Objekt (Fahrzeuge, usw) einfach einen Objectmanager für die Grafik bekommen und der verwaltet dann eben die darstellung.)
-
Hat nicht jeder Würfel die gleiche Textur?
Ich kenne mich mit D3D nicht gut aus, aber
Device->SetTexture(0,MyResMan.getTexture(Objects[q]->TextureID[0]).getTexture());erscheint mir im Grunde das gleiche zu sein wie glBindTexture() in OpenGL.
Und Texturwechsel sind recht kostspielig. Du bindest ja nun für jeden Würfel die Textur neu, obwohl es immer die gleiche ist. Also würde es reichen, die Textur einmal zu binden. Auch wenn es nur rund 250 Wechsel sind, kann das schon was ausmachen.
Was für eine Grafikkarte hast du denn?
-
Powerpaule schrieb:
Hat nicht jeder Würfel die gleiche Textur?
Ich kenne mich mit D3D nicht gut aus, aber
Device->SetTexture(0,MyResMan.getTexture(Objects[q]->TextureID[0]).getTexture());erscheint mir im Grunde das gleiche zu sein wie glBindTexture() in OpenGL.
Und Texturwechsel sind recht kostspielig. Du bindest ja nun für jeden Würfel die Textur neu, obwohl es immer die gleiche ist. Also würde es reichen, die Textur einmal zu binden. Auch wenn es nur rund 250 Wechsel sind, kann das schon was ausmachen.
Was für eine Grafikkarte hast du denn?Guter Ansatz danke!
Aber es sind unterschiedliche Texturen...
die 6 Seiten werden nacheinander dargestellt. und jede hat ihre eigene textur.
Der unterschied steht in der "Objects[q]->TextureID[0]" - ID
Jede Textur wird vorher geladen und nachher nur per ID abgerufen.Aber die Wechsel sind sicher ein Problem.
-
Also wenn du jede Textur jedesmal neu laden würdest wäre es sicher kein Wunder

Also sinds 6 Texturen pro Würfel, macht demnach ca. 1500 Wechsel? Das solltest du auf jeden Fall ausmerzen.
Es dürfte schneller sein, Erst eine Textur zu binden, dann durch alle Würfel zu gehen und die entsprechende Seite zu zeichnen, und dann die nächste Textur zu binden. Sind zwar mehr Schleifendurchläufe, aber am Ende müsste es trotzdem um einiges schneller gehen.
-
Powerpaule schrieb:
Also wenn du jede Textur jedesmal neu laden würdest wäre es sicher kein Wunder

Also sinds 6 Texturen pro Würfel, macht demnach ca. 1500 Wechsel? Das solltest du auf jeden Fall ausmerzen.
Es dürfte schneller sein, Erst eine Textur zu binden, dann durch alle Würfel zu gehen und die entsprechende Seite zu zeichnen, und dann die nächste Textur zu binden. Sind zwar mehr Schleifendurchläufe, aber am Ende müsste es trotzdem um einiges schneller gehen.Das ist eine gute Idee, danke!
Nein nein..die Textur wird vorher geladen und dann wird nur der Zeiger auf die Textur übergeben
-
Sorry wg Doppelpost aber:
Mir sind nun einige Dinge eingefallen:
Wie speichere ich am Besten die Vertices ab?
Soll ich erst alle Objekte beim Zeichnen initialisieren (also vertices festlegen) und in einem vector speichern?
Damit ich die nicht bei jedem der 6 Zeichenvorgänge neu initialisieren muss...UND das Wichtigste:
Das mit den Würfeln ist ja nur ein Beispiel...im Spiel wird es viele verschiedene Würfeltexturen geben.
Da müsste ich ja dann die Fahrzeuge alle Objekt für Objekt zeichnen. Also...z.b. erst alle Räder dann das Chassis,...usw.Das werden dann ja verdammt viele Durchläufe und ein großer Aufwand. mh
-
Das Stichwort lautet Renderqueues, das sind Puffer welche einen bestimmten Renderkontext enthalten und alle Vertices die diesen Kontext besitzen werden in diese Queue gestellt. Anschließend muss man nur die einzelnen Queues rendern und spart sich eine Menge unnötiger Texturwechsel&Co.
Das ganz ist allerdings etwas komplexer um es hier genauer zu beschreiben. In Stefan Zerbst's Buch 3D Game Engine Programming entwirft er eine Engine die das implementiert (mit DirectX).
-
Im Prinzip geht es, wenn ich das richtig verstehe, nur darum Batches nach gewissen Eigenschaften zu sortieren, und sie dann in dieser Reihenfolge zu rendern. Die Render-Queues wären dann Buckets in einem Bucket-Sort wenn man es von der Seite aus betrachtet. Bei ausreichend kleiner Anzahl von Buckets (also hier Render-Queues) ist das ziemlich sicher die beste (schnellste) Lösung.
Falls das Blödsinn sein sollte bitte mich entsprechend zu korrigieren.
-
kein bloedsinn, du hast es schon richtig gesagt. man sammelt alle 'renderjobs', sortiert die dann (quicksort reicht meist) und rendert das dann mit moeglichst wenig statechanges.
das waere bei den boxen aber irgendwie nicht die loesung. besser waere es soviele texturen wie moeglich in eine zu stecken, genauso die vertices der boxen und dann mit moeglichst wenig drawcalls alles abrendern.
-
Danke für die vielen Antworten!
Ich hab schon 2 große Fehler gefunden ..... verdammt blöde Fehler obendrein.....
und jetzt läuft es flüssig bei 60 FPS ohne irgendwelche Probleme.Lag wohl echt daran dass ich schon so lang nichts mehr in C++ und DX programmiert hab.
-
Ich bin nun auf ein anderes seltsamens Problem gestoßen ... wollte aber nicht extra nen Thread aufmachen.
Das Spiel ist jetzt schon etwas fortgeschritten und sieht so aus:
http://i191.photobucket.com/albums/z231/kuldren/Screenshot28August200802.jpgDas Problem ist...Gestern lief alles noch sauber bei 60 FPS ...
Nun habe ich nur die Datenstruktur für das Spielfeld (also statt einem Viereck dass aus 2 Dreiecken besteht direkt nur 2 Dreiecke verwendet) umstrukturiert.
Nach dem dabei aber aus der Perspektive die man auf dem Screenshot sieht die FPS auf ca 50 gesunken sind hab ich alles rückgängig gemacht und dachte es liegt einfach daran dass ich ja bei 2 dreiecken drawprimitve 2 mal aufrufe anstatt einmal bei einem viereck( wg. der matrizen).
Nach dem ganzen rückgängig machen hat sich aber nichts an den FPS verändert ...
Wenn ich allerdings die Kamera noch weiter nach oben bewege, bzw. ganz nah an der Spielfeld heran steigen die FPS wieder auf 59/60 an....
Im Vollbildmodus läuft sowieso alles auf 60 fps....
Nun meine Frage: Habt ihr eine Ahnung wieso das so sein könnte?Die ZBuffer Einstellungen sehen so aus:
D3DXMATRIX ProjMatrix; D3DXMatrixPerspectiveFovLH(&ProjMatrix, D3DX_PI/4, (float)ResolutionX / (float)ResolutionY, 1000.0f, 10000.0f );BTW: Ist egal ob 1024x768 oder 1280x800 (Das war bisher standard) - der effekt ist der gleiche.
-
Habt ihr eine Ahnung wieso das so sein könnte?
Ich vermute mal, dass es *nicht* an den ZBuffer-Einstellungen liegt.
Du koenntest mal den VSync abschalten um zu sehen, wieviel FPS Du wirklich hast.
-
hellihjb schrieb:
Habt ihr eine Ahnung wieso das so sein könnte?
Ich vermute mal, dass es *nicht* an den ZBuffer-Einstellungen liegt.
Du koenntest mal den VSync abschalten um zu sehen, wieviel FPS Du wirklich hast.Ohne Irgendwas in Kamerareichweite hab ich ohne VSync 100 FPS ... wenn ich 25 Turrets und das Spielfeld darstelle (->siehe screenshot)
dann sinds etwa 80-85 aus der höheWenn ich so nahe heranscrolle dass ich die Turrets alle im Bild habe sinken die fps auf 50 herab
-
So...ich wollte keinen extra Thread aufmachen - auch weil die Frage wieder zum gleichen Thema gehört.
Mein Spiel ist jetzt so weit dass es ein Spielfeld gibt (10x10 Felder zu je 2 Dreiecken.
Dazu eine große Fläche dahinter (wieder 2 Dreiecke) und zum Austesten auf jedem Feld ein Geschützturm (bestehend aus 13 Würfeln zu je 12 Dreiecken)Also ca 13*12*100=15600 Dreiecke.
Wobei das Spielfeld zu vernachlässigen ist. Ich habe mich bemüht grobe Fehler auszumerzen und es nun hinbekommen dass das ganze bei ca 40 FPS läuft.
Aber in einem anderen Thread hab ich was von 1.4 Mio! Dreiecken gelesen und 20 FPS ....

Natürlich wäre es toll wenn mein Programm auch mit 100 Geschützturmen auf dem Spielfeld flüssig bei 60 fps laufen würde aber das tut es leider nicht.
Vom logischen Teil her:
Ich stelle die Geschütztürme Objekt für Objekt dar, wobei ich immer das gleiche Objekt der Türme darstelle (dabei alle türme durchlaufe) und dann zum nächsten teil des turms wechsle (wieder alle durchlaufe und dabei darstelle usw.)
Also erst alle Bodenplatten, dann alle Mittelteile usw.
Weil diese Teile alle unterschiedliche Texturen haben.das sieht so aus:
for(int id=2;id<7;id++){ Device->SetStreamSource(0,my3DObjectManager.Objects[id]->VB,0,sizeof(CUSTOMVERTEX)); for(int allTurrets=0;allTurrets<numOfMiniGunTurrets;allTurrets++){ my3DObjectManager.moveTo(MiniGunTurrets[allTurrets]->getPosition()); my3DObjectManager.DrawAll(id,MiniGunTurrets[allTurrets]->outerRotation, MiniGunTurrets[allTurrets]->innerRotation, MiniGunTurrets[allTurrets]->Position); } }"id" sind die einzelteile eben von 1-13 (bzw. 0-12)
die DrawAll Methode sieht so aus (q steht hier für id) :
if(0<q && q<7){ float buf=oR.x; oR.x=0; Objects[q]->FitRotMovToMatrix(iR,oR,pos); oR.x=buf; } else if(q>6){ Objects[q]->FitRotMovToMatrix(iR,oR,pos); } else Objects[q]->FitRotMovToMatrix(iR,D3DXVECTOR3(0,0,0),pos); Device->SetTransform( D3DTS_WORLD, &Objects[q]->WorldMatrix); if(!Objects[q]->activateTextures){ Device->SetTexture(0,NULL); Device->DrawPrimitive(D3DPT_TRIANGLELIST,0,12); } else{ Device->DrawPrimitive(D3DPT_TRIANGLELIST,0,12); }Ich habe die Texturwechsel minimiert, die Abfolge der Darstellung verändert usw. aber ich schaffs einfach nicht dass das ganze flüssiger läuft.
Ich kann mir auch nicht vorstellen dass es an meiner Hardware liegt.
(P4 DualCore (6600 jeweils 1.8 GHZ) und NVIDIA GeForce 7500 LE)
Mag sein dass das eher unteres HW Ende ist aber z.b. Oblivion und WoW lassen sich bei mindestens 1024,768 und mittleren Details gut spielen (flüssig) und diese Spiele müssen doch viel mehr als 16.000 Dreiecke darstellen, oder?Dazu muss ich sagen dass ich noch keine Indexbuffer verwende (noch nicht damit beschäftigt) -soll ja ein Lernprojekt sein-und kommt noch - aber das Problem mit den FPS liegt doch wohl an meinem Code oder?
Wenn ich übrigens einfach den DrawPrimitive Aufruf auskommentiere läuft natürlich alles bei 60 fps (bedeutet aber eigentlich auch nichts)Kann es daran liegen dass ich jedes objekt einzeln darstelle? sollte ich alle in einen vertexbufferpacken? (Kann doch aber auch nicht funktionieren....wg der WorldMatrix...
Habe da leider noch nicht viel Ahnung....
-
Ein DrawPrimitive-Call lohnt sich fuer 12 Triangles nicht.
Steck alle Objekte die zusammen transformiert werden sollen in einen Vertexbuffer.
-
hellihjb schrieb:
Ein DrawPrimitive-Call lohnt sich fuer 12 Triangles nicht.
Steck alle Objekte die zusammen transformiert werden sollen in einen Vertexbuffer.Jeder einzelne Geschützturm hat eine andere ausrichtung...also eine andere Worldmatrix....
und die haben alle den gleichen vertexbuffer nur verschiebe ich dann eben an einen bestimmten punkt, rotiere das ganze und stelle es dar...dann zum nächsten punkt wo eingeschütz stehen soll usw.Ich muss doch für jeden Würfel vor dem darstellen
die rotation und position an eine WorldMatrix angeben oder nicht?
und dann rufe ich
[QUOTE] Device->SetTransform( D3DTS_WORLD, &Objects[q]->WorldMatrix);[/cpp]
auf um mitzuteilen dass das objekt so positioniert und rotiert werden soll
da kann ich doch gar nicht alle gleichen objekte in den gleichen drawprimitiv aufruf packen oder? weil ich ja dazwischen andere matrizen (weil andere rotationen) angeben muss....
Sind zwar die gleichen Türme aber alle nach einem anderen winkel ausgerichtet.
oder seh ich das falsch...??ich hab das so aus den kleinen beispielen aus den büchern die ich hier hab rausgenommen...da steht ja leider nie drin wie das bei vielen objekten am besten machbar ist....

Hier ein Bild damit ihr wisst wie es aussieht:
http://i191.photobucket.com/albums/z231/kuldren/10sept08-1.jpgÜbrigens besteht immer noch das Problem dass die Framerate um ca 10-15 fällt sobald man so nahe heranzoomt (einfach die kamera bewegt) dass man ca 5x5 Geschütze im Bild hat...
-
Dann multiplizierst Du die Vektoren eines Wuerfels eben erst mit der entsprechenden Matrix bevor Du sie in den Vertex-Buffer schreibst.
-
hellihjb schrieb:
Dann multiplizierst Du die Vektoren eines Wuerfels eben erst mit der entsprechenden Matrix bevor Du sie in den Vertex-Buffer schreibst.
Hm
Bisher fülle ich den Buffer so
CUSTOMVERTEX cbuf[]={ { - Objects[q]->Size.x/2, - Objects[q]->Size.y/2, - Objects[q]->Size.z/2, Objects[q]->Color, 0, 1}, // Vorne { - Objects[q]->Size.x/2, + Objects[q]->Size.y/2, - Objects[q]->Size.z/2, Objects[q]->Color, 0, 0}, { + Objects[q]->Size.x/2, + Objects[q]->Size.y/2, - Objects[q]->Size.z/2, Objects[q]->Color, 1, 0}, { + Objects[q]->Size.x/2, + Objects[q]->Size.y/2, - Objects[q]->Size.z/2, Objects[q]->Color, 1, 0}, { + Objects[q]->Size.x/2, - Objects[q]->Size.y/2, - Objects[q]->Size.z/2, Objects[q]->Color, 1, 1}, { - Objects[q]->Size.x/2, - Objects[q]->Size.y/2, - Objects[q]->Size.z/2, Objects[q]->Color, 0, 1}, //usw }; BYTE* VertexBufferStart; Device->CreateVertexBuffer(sizeof(cbuf), D3DUSAGE_WRITEONLY, D3D_CUSTOMVERTEX, D3DPOOL_MANAGED, &Objects[q]->VB, NULL); Objects[q]->VB->Lock(0,0,(void**)&VertexBufferStart,0); memcpy(VertexBufferStart,cbuf,sizeof(cbuf)); //VertSize=sizeof(cbuf); Objects[q]->VB->Unlock(); Device->SetFVF(D3D_CUSTOMVERTEX);Und zeichnen erfolgt so:
Device->SetTransform( D3DTS_WORLD, &Objects[q]->WorldMatrix); Device->SetStreamSource(0,VB,0,sizeof(CUSTOMVERTEX)); Device->SetTexture(0,NULL); Device->DrawPrimitive(D3DPT_TRIANGLELIST,0,12);ich dachte setTransform(D3DTS_WORLD....); legt fest dass alles was als nächstes gezeichnet wird so rotiert wird ?
Wenn ich vor dem Füllen des Buffers die vektoren multipliziere (wie auch immer dass dann geht -> ? ) müsste ich das ja bei jedem draw vorgang für alle objekte machen....kostet das nicht zu viel zeit?
-
ja, beides ist eine suboptimale loesung, da du beim immer neu befuellen von buffern sehr drauf angewiesen bist, dass der treiber dir die buffer ohne stalls gibt. wenn du zuviel fuellen willst, oder zuviele buffer hast, oder... muss der treiber im zweifelsfall warten bis der vorherige aus der pipeline ist (und das koennen manchmal ein paar frames sein). so wenige polys wie du zeichnest koennte aber hellis idee durchaus funktionieren.
eine weitere moeglichkeit waere: vertexshader. du koenntest dann einmal zum start z.b. 32mal den turm in den buffer schreiben, dabei vergibst du jedem dieser tuerme eine andere ID (die du irgendwie mit in den buffer schreibst, also als vertex attribut). dann uebergibst du 32 Worldmatrizen als vertexshader-constants die du mittels der ID pro vertex indizieren kannst.
natuerlich ist das ein overhead 32mal das selbe objekt reinzuschreiben, aber so extrem lowpoly wie das ist, bist du einfach nur drawcall limitiert. mehr als 2000 drawcalls pro frame kann man kaum fluessig darstellen (wenn man texture+statechanges miteinbezieht).
-
Da habt Ihr mich wohl missverstanden.
Dieser Geschuetzturm besteht ja aus 13 einzelnen Wuerfeln die jeweils per Matrix an eben die Stelle gebracht werden wo sie hin sollen - es sind also 13 Render-Calls noetig.
Das Objekt besteht aber nur aus zwei "logischen" Teilen: ein statischer Sockel und ein bewegliche Geschuetz.
Mein Vorschlag war darum, fuer jedes dieser zwei Teilobjekte *einmal* einen Vertexbuffer anzulegen in dem die zugehoerigen Wuerfel bereits transformiert abgelegt sind - somit sind nur noch zwei Render-Calls notwendig und das Geschuetz bleibt beweglich.