Files |  Tutorials |  Articles |  Links |  Home |  Team |  Forum |  Wiki |  Impressum

Aktuelle Zeit: Fr Jul 04, 2025 04:25

Foren-Übersicht » Programmierung » Einsteiger-Fragen
Unbeantwortete Themen | Aktive Themen



Ein neues Thema erstellen Auf das Thema antworten  [ 11 Beiträge ] 
Autor Nachricht
 Betreff des Beitrags: Mehrere VBOs effizienter zeichnen?
BeitragVerfasst: Di Sep 21, 2010 23:05 
Offline
DGL Member

Registriert: So Sep 05, 2010 17:46
Beiträge: 15
Grüsse,

ich bin noch recht unerfahren und hoffe, dass meine Frage nicht allzu laienhaft wirkt.
Ich programmiere in C++, aber ich denke, das dies in dem Fall keine allzugrosse Relevanz hat.

Da mir die Funktion gluSphere(GLUquadrics, radius, slices, stacks) zu wenige Möglichkeiten bietet die Kugel ggf. manipulieren zu können (beispielsweise soll auf Wunsch nur jeder 2. stack gezeichnet werden können etc.) und auch die glutSolidSphere-Funktion von glut recht bescheiden ist habe ich mir eine eigene Klasse geschrieben, die eben diese Funktionalitäten zur Verfügung stellt (bzw bin noch dabei).
Das ist auch nicht allzu problematisch, alle Koordinaten werden bei der Instanziierung berechnet, Normalen werden berechnet etc. etc.

-Anfangs hab ich in einer zeichen-Methode dann in Schleifen die Vertices angegeben - war zu langsam.
-Zweiter versuch war dann über VertexArrays zu zeichnen - das war schon wesentlich besser und erreichte fast die Performance der durch GLU oder GLUT zur Verfügung gestellten Funktionen (für eine Kugel mit 100 stacks und 100 slices ca. 700Microsekunden).

-Dritter und momentaner Ansatz ist über VBOs zu rendern, was natürlich einen enormen Performancesprung brauchte. Dauer für die gleiche Kugel nur noch ca. 7Microsekunden. Also schon recht flott.

Ich bin mir jetzt nicht sicher, ob ich da noch etwas optimieren kann, so wie beispielsweise glMultiDrawArrays statt mehrmaligen glDrawArrays (wobei ich das irgendwie nicht verstanden hatte - klappte bei mir nicht).

Momentan wird sowohl für den oberen und untereren stack der Kugel (werden jeweils als GL_TRIANGLE_FAN gezeichnet) ein buffer gefüllt, und für jeden dazwischenliegenden stack (jeder als GL_QUAD_STRIPS gezeichnet) ebenfalls einer.
Bei einer Kugel mit 100 Stacks fordere ich also über glGenBuffer 200 Buffer an (pro stack 2 stück für Normalen und Vertices), lade dann die Daten rein usw.
Dies geschieht natürlich alles nur einmal.

In der Zeichenmethode binde ich dann pro Stack einen buffer für die Normalen und einen für die Vertices, setze VertexPointer und NormalPointer und zeichne dann per glDrawArrays.
Das mache ich dann für alle 100 Stacks.

Die Frage ist nun, ob man das auch eleganter bzw effizienter lösen kann. Eine Möglichkeit wäre sicher InterleavedArrays zu verwenden (noch nicht probiert), aber ich denke dass dadurch kein grosser Performancegewinn zu erwarten ist. Die Daten werden nicht mehr geändert, sodass einmal übertragene Daten (mittels GL_STATIC_DRAW) - ob nun mit vielen arrays oder weniger interleavedArrays - keinen Unterschied machen sollten.
Vielmehr könnte es ja sein, dass man statt 100 mal glDrawArrays für die VBO aufzurufen irgendeine effizientere Methode finden könnte?

Gruss,
Carsten


Nach oben
 Profil  
Mit Zitat antworten  
BeitragVerfasst: Mi Sep 22, 2010 06:31 
Offline
DGL Member
Benutzeravatar

Registriert: Fr Dez 11, 2009 08:02
Beiträge: 532
Programmiersprache: pascal (Delphi 7)
Hab zwar selber noch nix mit VBOs gemacht, aber mit glVertexPointer, glColorPointer und glNormalPointer (und glVertexAttribPointer wenn nötig) sollte es möglich sein, nicht ein ganzes VBO zu zeichnen, sondern nur ein Teil davon. Dann kannst du alles in ein VBO hauen, und trotzdem gegebenenfalls nur Teile davon zeichnen. Inwieweit das geht bzw sinnvoll ist, weiß ich nicht, aber ich denke schon, dass es geht, und wenns geht, wirds wahrscheinlich auch sinnvoll sein. Immerhin können so in deinem Fall, wenn die ganze Sphere gezeichnet werden soll, aus 100 Draw Calls ein einzelner werden.
Außerdem dürfte es auch schneller werden, wenn du Vertices und Normalen im selben VBO hast (das geht auch mit dem oben genannten Funktionen). Inwieweit das einen Unterschied macht, weiß ich aber nicht.
Ich schätz mal, es wird sich noch wer melden, der sich da besser auskennt. Der kann dir dann wahrscheinlich genaueres sagen.

ps: soweit ich weiß zeichnet glMultiDrawArray ein VBO mehrmals, nicht mehrere auf einmal.


Nach oben
 Profil  
Mit Zitat antworten  
BeitragVerfasst: Mi Sep 22, 2010 08:30 
Offline
DGL Member
Benutzeravatar

Registriert: Do Dez 29, 2005 12:28
Beiträge: 2249
Wohnort: Düsseldorf
Programmiersprache: C++, C#, Java
1. Werfe alle Vertices einer Kugel in einen VBO, im Zweifel einfach hintereinander. Bei glDrawArrays kannst du einen Bereich angeben der gerendert werden soll, sowie auch den Primitiv-Typ. So hast du immer noch 100 Draw-Calls, aber nur einen Buffer.

2. Mit glMultiDrawArrays kannst du alle Draw-Calls zusammenfassen wenn der Primitiv-Typ gleich ist. Was da aber passiert liegt an der Hardware bzw. dem Treiber, wenn du Pech hast ist das einfach nur eine Schleife die glDrawArrays aufruft....was genau gar nichts bringt.

3. Die Verwendung von glInterleavedArrays ist nicht erforderlich, das gleiche lässt sich auch mit glVertexPointer & Co erreichen. glInterleavedArrays macht die Sache einfacher, allerdings ist die Anzahl der möglichen Formate sehr beschränkt. Bei einem Interleaved-VBO liegen alle für einen Vertex benötigten Daten hintereinander im Speicher. Der Grafikspeicher (und auch der Hauptspeicher) arbeiten so das immer ein ganzer Speicherbereich (z.B. 4kb) in einem Rutsch in einen kleinen aber extrem schnellen Zwischenspeicher (den Cache) kopiert wird. Aus diesem Grund können nebeneinander liegende Daten etwa doppelt so schnell gelesen werden als Daten die über den Speicher verstreut sind. => Das ist der Vorteil von Interleaved-VBOs.

4. Verzichte auf GL_TRIANGLE_FAN und GL_QUAD_STRIPS....benutze GL_TRIANGLES mit einem zusätzlichen Elementbuffer (auch Indexbuffer genannt). So kannst du die Datenmenge reduzieren und obendrein alles in einem einzigen Draw-Call rendern! Das dürfte den größten Performance-Vorteil bringen. Bei deinem aktuellen Vorgehen mit QUAD_STRIPS speicherst du im Grund jeden Vertex doppelt. Wenn du Indices benutzt ist das nicht mehr nötig. Du speicherst nur noch sämtliche Vertices einmal hintereinander in einem VBO. In einem zweiten Buffer (dem ElementBuffer) speicherst du dann nur noch Referenzen (quasi den Array-Index) auf den eigentlichen VBO. Diese Speichermethode ist effizienter als die Verwendung von Strips, da ein Index eben einfach nur ein Integer ist mit 2 oder 4 Byte, während ein Vertex mit Position, Normale und Texturkoordinaten bereits seine 32 Byte groß ist.

_________________
Yeah! :mrgreen:


Nach oben
 Profil  
Mit Zitat antworten  
BeitragVerfasst: Mi Sep 22, 2010 12:59 
Offline
DGL Member

Registriert: So Sep 05, 2010 17:46
Beiträge: 15
Ich danke euch beiden für die Antwort. Heute Nacht im Bett dachte ich mir ebenfalls, dass ich statt 100VBOs einfach nur ein einziges mache und dieses mit GL_TRIANGLE_STRIP durchlaufen lasse. Hätte den Vorteil, dass ich ihn ihn einem kompletten Rutsch abarbeiten könnte, da ein Sprung von einem Stack auf den nächsten kein Problem ist bei Triangles (ein Triangle liegt ja immer in einer Ebene).
Allerdings habe ich dann immernoch das von coolcat angesprochene Problem, dass ich jedes Vertex doppelt abspeicher.

Also danke ich für den Tip mit dem Elementbuffer, das werde ich gleich mal versuchen umzusetzen.

Eine andere Frage bezüglich Performance habe ich noch:
Ich zeichne momentan mit obiger Klasse eine Kugel mit 2000 stacks und 2000 slices. Um die Kugel rotiert eine Lichtquelle.
Die Renderdauer beträgt laut Timer (ich verwende die Funktionen für den high resolution timer von Windows - QueryPerformanceCounter etc.) nur ca. 150 microSekunden.
Allerdings dauert das swappen der Buffer SwapBuffers(hDC); geschlagene 25ms, also extrem lang. Mir ist klar, dass bei sovielen stacks und slices ne Menge Daten entstehen - pi mal daumen 8Millionen Triangle - aber das der Unterschied zw. der Renderzeit und dem reinen bufferswap so gravierend ist - hätte ich nicht gedacht. Ist das normal? Wenn dem so ist würde es mir ja reichlich wenig bringen, weitere Optimierungen vorzunehmen.


Nach oben
 Profil  
Mit Zitat antworten  
BeitragVerfasst: Mi Sep 22, 2010 14:54 
Offline
DGL Member
Benutzeravatar

Registriert: Fr Dez 11, 2009 08:02
Beiträge: 532
Programmiersprache: pascal (Delphi 7)
Nun, ich schätz mal, mit dem QueryPerformanceCounter misst du die Zeit, die der Aufruf (bis zur Rückgabe) von glDrawArrays braucht. Nur weil das Programm weiterrechnet, heißt das noch lange nicht, dass OpenGL (bzw die Graphikkarte) fertig ist(*1). Spätestens bei SwapBuffers muss er aber warten, bis OpenGL (bzw die Graphikkarte) fertig ist(*2). Wobei mir 25ms für 8 000 000 Dreiecke doch etwas viel vorkommt. Besonders, weils ja nicht mehr Pixeln werden.

Mal abgesehen davon kanns durchaus sein, dass SwapBufers langsam ist. Braucht man auch nur einmal bei jedem Frame. Und wenn du dann mal nicht bloß eine Kugel zeichnest, sondern tausende, kanns schon sein, dass die Optimierungen sinn machen, auch wenns wirklich am SwapBuffers liegt(*3).

(*1): würde nämlich nur unnötig Zeit verschwenden. Kann ja sein, dass es irgendwas sinnvolles zu tun gibt, was getan werden kann, während OpenGL (bzw die Graphikkarte) arbeitet.

(*2): Wenn der nämlich das, was bisher gezeichnet wurde, auf den Bildschirm haut, bevor er fertig gezeichnet hat, sieht das nicht sonderlich schön aus ;)

(*3): wobei ich jetzt nicht näher darauf eingehen werde, ob es sinn macht, tausende Kugeln mit dieser Auflösung (Anzahl der Triangles) zu zeichnen^^


Nach oben
 Profil  
Mit Zitat antworten  
BeitragVerfasst: Mi Sep 22, 2010 15:33 
Offline
DGL Member

Registriert: So Sep 05, 2010 17:46
Beiträge: 15
Ich messe momentan die Zeit, die zum abarbeiten der kompletten Render-Methode gebraucht wird, da nur die Kugel + Licht modelliert werden. Um sicherzustellen, dass alle Befehle aus dem Commandbuffer eingelesen werden - habe ich am Ende der Methode ein glFlush() aufgerufen (wobei das ja nicht nötig wäre, da ein swapbuffer dies automatisch aufruft).
Somit müsste ich doch davon ausgehen können, dass OpenGL bzw. die Grafikkarte fertig ist.

Beispielcode:
Code:
LARGE_INTEGER frequency;
LARGE_INTEGER t1, t2;
QueryPerformanceFrequency(&frequency);

QueryPerformanceCounter(&t1);
DrawGLScene();
QueryPerformanceCounter(&t2);
SwapBuffers(hDC);
double elapsedTime = static_cast<double>(t2.QuadPart - t1.QuadPart)*1000*1000/ frequency.QuadPart;
std::cout<<....(ausgabe der verstrichenen zeit)


Dass es natürlich nicht sinnvoll ist mit sovielen Dreiecken pro Kugel zu arbeiten ist klar - ich hab die Zahlen auch nur für Performance-Tests so hoch angesetzt, da es mit 100stacks und 100slices natürlich kaum Unterschiede gibt - da könnte ich auch für jeden Vertex ein glVertex(...) aufrufen :)

Nach vorerstiger Umstellung auf nur einen VBO-Buffer benötigt die Abarbeitung von DrawGLScene nur noch 5micro-s.
Messe ich die Zeit für SwapBuffers habe ich weiterhin 25ms - was schon zu leicht merklichem gestotter führt (Resultat sind ja <40fps).
Mich wundert eben, dass ein einfacher swap soviel Zeit konsumiert und tatsächlich zum Flaschenhals bezüglich Performance wird.


Nach oben
 Profil  
Mit Zitat antworten  
BeitragVerfasst: Mi Sep 22, 2010 15:43 
Offline
DGL Member
Benutzeravatar

Registriert: Do Dez 29, 2005 12:28
Beiträge: 2249
Wohnort: Düsseldorf
Programmiersprache: C++, C#, Java
Zitat:
Um sicherzustellen, dass alle Befehle aus dem Commandbuffer eingelesen werden - habe ich am Ende der Methode ein glFlush() aufgerufen

glFlush() bringt da gar nichts, versuch mal glFinish(). Dieses wartet nämlich solange bis OpenGL fertig ist.

_________________
Yeah! :mrgreen:


Nach oben
 Profil  
Mit Zitat antworten  
BeitragVerfasst: Mi Sep 22, 2010 15:54 
Offline
DGL Member

Registriert: So Sep 05, 2010 17:46
Beiträge: 15
AHAAA - jetzt ist der Groschen gefallen :)

Mit glFinish() dauert eben die Render-Methode 25ms und das swappen nur noch 25Microsekunden.
Mir war nicht klar, dass nach Aufruf von SwapBuffers noch gerendert wird, und somit ggf. gewartet werden muss. Ich dachte, dass die Szene dann schon fertig im Framebuffer läge.

Gut - dann ist wohl nun Ende mit Optimieren, da die Grafikkarte einfach nicht schneller kann.

Ich danke euch ganz herzlich für die Hilfe.


Nach oben
 Profil  
Mit Zitat antworten  
BeitragVerfasst: Mi Sep 22, 2010 16:26 
Offline
DGL Member
Benutzeravatar

Registriert: Do Dez 29, 2005 12:28
Beiträge: 2249
Wohnort: Düsseldorf
Programmiersprache: C++, C#, Java
Zitat:
Gut - dann ist wohl nun Ende mit Optimieren, da die Grafikkarte einfach nicht schneller kann.

Wenn du uns mehr Infos gibst was du eigentlich machst dann geht da sicher noch was. Siehe auch den Artikel Performance im Wiki.

Beispiel 1: Da es sich um Kugeln handelt kannst du wahrscheinlich die Normale aus der Vertexkoordinate herleiten. Wenn der Mittelpunkt der Kugel bei (0,0,0) liegt musst du nämlich einfach nur die Vertexkoordinate als Normale benutzen. Realisiert wird das einfach über einen kleinen Vertexshader der sich eben die Vertexkoordinate in gl_Vertex schnappt und diese statt gl_Normal als Normale für die Lichtberechnung verwendet. Dafür sollte die Kugel natürlich einen Radius von 1 haben. Skaliert wird dann einfach über glScale.
=> Ergebnis: 12 Byte pro Vertex an Daten gespart.

Beispiel 2: Eine Per-Pixel-Beleuchtung (statt Per-Vertex) lässt sich im Fragmentshader leicht realisieren. So kannst du die nötige Vertex-Auflösung drastisch reduzieren ohne das ein Unterschied sichtbar ist. Die Kugel wirkt also bei geringerer Geometrieauflösung trotzdem rund. => Per-Pixel-Licht sieht ggf. sogar besser aus. Beispiele gibt es in der Shadersammlung.

Beispiel 3: Die meisten deiner Objekte sind Kugeln? Viele Kugeln? Hinter vor gehaltener Hand...Einsteiger-Forum und so....wie wäre es mit Instancing? (Erfordert recht neue Hardware....verfügbar ab ca. Geforce 8 )

_________________
Yeah! :mrgreen:


Nach oben
 Profil  
Mit Zitat antworten  
BeitragVerfasst: Mi Sep 22, 2010 23:27 
Offline
DGL Member

Registriert: So Sep 05, 2010 17:46
Beiträge: 15
Hu - von den shadern bin ich glaub noch ziemlich weit entfernt - wie gesagt noch relativ grün hinter den Ohren :)

Die Normalen habe ich genau wie du sagst über die Vertexdaten bestimmt - das war naheliegend bei einer Kugel :D - wobei ich diesse selbst normalisiere noch und dann in einem array speicher und später in ein VBO schreibe.
Effizienter wirds natürlich erst dann, wenn ich wirklich Daten einspare - also per shader wie du vorschlägst - das werde ich dann später optimieren, wenn ich mich dann mal soweit vorgearbeitet habe : )


Beispiel2 wird wohl auch erst zum Einsatz kommen, wenn ich bei den shadern angelangt bin.

Instancing muss ich mir nochmal genauer und ausführlicher durchlesen, aber hat wie du ja sagst erst bei vielen gleichartigen Objekten Relevanz. Ich optimiere ja momentan nur meine Klasse ansich (Initialberechnungen, draw-Methode etc.) - ob da nun viele Objekte von erzeugt werden oder nicht kann ich erstmal nicht berücksichtigen. Ich weiss noch nicht wie das instancing funktionieren wird, allerdings denke ich dass ich dafür ohnehin weitere Schnittstellenfunktionen bereitstellen muss später.

Auf jedenfall danke ich für die Hinweise, so weiss ich schonmal weiss, wo ich später ansetzen kann, wenn genügend Grundwissen vorhanden ist.


Nach oben
 Profil  
Mit Zitat antworten  
BeitragVerfasst: Do Sep 23, 2010 08:08 
Offline
DGL Member
Benutzeravatar

Registriert: Do Dez 29, 2005 12:28
Beiträge: 2249
Wohnort: Düsseldorf
Programmiersprache: C++, C#, Java
Wenn du dir die einfachen Shader aus Beispiel 1 und 2 noch nicht zu traust solltest du Instancing erst mal ignorieren. Das geschieht nämlich im Vertexshader und erfordert die Verwendung zusätzlicher Texturen (oder TextureBufferObjects) um Matrixdaten in den Shader zu kriegen.

_________________
Yeah! :mrgreen:


Nach oben
 Profil  
Mit Zitat antworten  
Beiträge der letzten Zeit anzeigen:  Sortiere nach  
Ein neues Thema erstellen Auf das Thema antworten  [ 11 Beiträge ] 
Foren-Übersicht » Programmierung » Einsteiger-Fragen


Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 2 Gäste


Du darfst keine neuen Themen in diesem Forum erstellen.
Du darfst keine Antworten zu Themen in diesem Forum erstellen.
Du darfst deine Beiträge in diesem Forum nicht ändern.
Du darfst deine Beiträge in diesem Forum nicht löschen.
Du darfst keine Dateianhänge in diesem Forum erstellen.

Suche nach:
Gehe zu:  
cron
  Powered by phpBB® Forum Software © phpBB Group
Deutsche Übersetzung durch phpBB.de
[ Time : 0.008s | 14 Queries | GZIP : On ]