# JOGL Cubes performant



## turing (5. Okt 2012)

Hallo Forum,

ich beschäftige mich gerade etwas mit JOGL, aber das könnte grundsätzlich auch eine allgemeine OpenGL Frage sein:

In einem Model werden ca. 10.000 'Entities' dargestellt, die lediglich als graue Boxen dargestellt werden. Shading, Texturen u.Ä. kommen nicht zur Anwendung. Die Boxen werden in der Animation zwar bewegt, aber ihre Form ändert sich nicht. Die Boxen werden daher in Display-Listen hinterlegt.

Für eine solche Entity sieht der Render-Code, der dann initial in eine Display-List abgelegt wird, wie folgt aus:


```
public static void renderEntity(GL2 gl) {
    gl.glBegin(GL2.GL_QUADS);
    gl.glVertex3f(-sizex / 2f, -sizey / 2f, -sizez / 2f);
    gl.glVertex3f(-sizex / 2f, +sizey / 2f, -sizez / 2f);
    gl.glVertex3f(+sizex / 2f, +sizey / 2f, -sizez / 2f);
    gl.glVertex3f(+sizex / 2f, -sizey / 2f, -sizez / 2f);
    gl.glVertex3f(+sizex / 2f, -sizey / 2f, +sizez / 2f);
    gl.glVertex3f(+sizex / 2f, +sizey / 2f, +sizez / 2f);
    gl.glVertex3f(-sizex / 2f, +sizey / 2f, +sizez / 2f);
    gl.glVertex3f(-sizex / 2f, -sizey / 2f, +sizez / 2f);
    gl.glVertex3f(+sizex / 2f, -sizey / 2f, -sizez / 2f);
    gl.glVertex3f(+sizex / 2f, +sizey / 2f, -sizez / 2f);
    gl.glVertex3f(+sizex / 2f, +sizey / 2f, +sizez / 2f);
    gl.glVertex3f(+sizex / 2f, -sizey / 2f, +sizez / 2f);
    gl.glVertex3f(-sizex / 2f, -sizey / 2f, +sizez / 2f);
    gl.glVertex3f(-sizex / 2f, +sizey / 2f, +sizez / 2f);
    gl.glVertex3f(-sizex / 2f, +sizey / 2f, -sizez / 2f);
    gl.glVertex3f(-sizex / 2f, -sizey / 2f, -sizez / 2f);
    gl.glVertex3f(+sizex / 2f, +sizey / 2f, +sizez / 2f);
    gl.glVertex3f(+sizex / 2f, +sizey / 2f, -sizez / 2f);
    gl.glVertex3f(-sizex / 2f, +sizey / 2f, -sizez / 2f);
    gl.glVertex3f(-sizex / 2f, +sizey / 2f, +sizez / 2f);
    gl.glVertex3f(+sizex / 2f, -sizey / 2f, -sizez / 2f);
    gl.glVertex3f(+sizex / 2f, -sizey / 2f, +sizez / 2f);
    gl.glVertex3f(-sizex / 2f, -sizey / 2f, +sizez / 2f);
    gl.glVertex3f(-sizex / 2f, -sizey / 2f, -sizez / 2f);
    gl.glEnd();
  }
```

Im eigentlich Render-Vorgang (aufgerufen von display) werden dann die Display-Listen nur noch ausgeführt, vorher wird die Matrix um die aktuelle Position der Entity translatiert:


```
@Override
  public void display(GLAutoDrawable drawable) {
      GL2 gl = drawable.getGL().getGL2();
      gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
      renderCamera(gl);
      renderEntities(gl);
      gl.glFlush();

  public void renderEntities(GL2 gl) {
    gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
    gl.glLoadIdentity();
    for (int i = 0; i < core.getEntities().size(); ++i) {
      Entity entity = core.getEntities().get(i);
      gl.glPushMatrix();
      gl.glTranslatef(entity.getPosx(), entity.getPosy(), entity.getPosz());
      gl.glColor3f(entity.getColorr(), entity.getColorg(), entity.getColorb());
      gl.glCallList(i);
      gl.glPopMatrix();
    }
```

Damit komme aich in etwas auf gerade einmal 30 FPS und ein Mikro-Benchmark zeigt, dass das Ausführen der display Methode ca. 33 ms benötigt von denen nicht einmal 1 ms auf die Methode renderCamera() fällt. Und das auf einen gar nicht mal so alten Gurke... Vielleicht irre ich mich (und der rechner hier ist eine Gurke), aber mir scheint das es da noch optimierungsbedarf gibt. Daher die Frage, ob mir vielleicht jemand einen Tipp geben, wo dieser Code optimiert werden kann.

PS: GL_CULL_FACE ist aktiviert und die glut-Funktion gluSolidCube liefert deutlich schlechtere Ergebnisse.

Vielen Dank im voraus!


----------



## Network (5. Okt 2012)

Also dein Code sieht nicht schlecht aus.
Ich kann hier keinerlei schwerwiegenden Optimierungsbedarf sehen.
Zwei Dinge stören mich an dem Code:
1.) Der mindere wäre die ständige Fließpunktrechnung /2f
2.) glFlush wirkt unnütz an dieser Stelle

- Hinzu kommt natürlich die Tatsache wie gut die Grafikkarte ist. Auch heute kann man sich Computer kaufen in diversen Elektrogeschäften für um die 100€. Alt sind die Computer dann ja nicht. 
- Schlussendlich sind 10,000 Objekte (Entities?) nicht tragbar, deshalb gibt es ja in vielen Simulationsprogrammen eine maximale Sichtweite, sowie meist ein 2. einfacheres 3D Objekt, wenn sich das Objekt in der Ferne befindet.


----------



## zzuegg (5. Okt 2012)

Wenn ich mich nicht irre würde das obere beispiel 10000 drawcalls machen. Das kann nicht performant sein.

Was passiert wenn du das glBegin vor der for-scheife aufrufst und glEnd nach der schleife? Die pos berechnungen könntest du in die funktion auslagern und dann eben nur die vertex positionen verändern. 

Das ganze sollte mit einem glBegin/glEnd und mit einer Matrix zu schaffen sein


----------



## Marco13 (5. Okt 2012)

An dem geposteten ist nichts offensichtlich "falsch". Es verwendet aber die alten GL 1.x-Funktionen. Natürlich sollte sowas auf einer heutigen Grafikkarte kein Problem mehr sein. Aber eine Voraussetzung dafür könnte sein, dass man auch die neueren Funktionen dafür verwendet. Wenn Fancy antwortet, gibt's vielleicht noch ein KSKB dazu, ich kann dich vorerst nur auf das Stichwort Instancing ? DGL Wiki verweisen.


----------



## Spacerat (5. Okt 2012)

@Network: Er arbeitet mit Würfeln, da machen "LevelOfDetails" (so nennt man diese einfacheren Objekte, von denen es pro Entity durchaus auch mehr als 2 geben darf) keinen Sinn.

@zzuegg: Das ergäbe 10000 Drawcalls, wenn er mit Vertexbuffern arbeiten würde. Aber er arbeitet ja auch noch mit Displaylisten (afaik macht das Glut auch), so kommt er nach meiner Rechnung auf 2,4 Millionen "Drawcalls", nämlich jedes einzelne glVertex3f.

Letztendlich ist für die Performance aber die Anzahl der Polygone (bzw. Dreiecke) in der Szene wichtig und das sind mit ca. 120000 gar nicht mal so extrem viele (grad mal 3,6 MTPS wenn ich mich nicht verrechnet habe) wenn er nicht gerade mit OpenGLES hantieren würde. Bist du dir sicher, dass eine echte 3D-Hardware vorhanden und auch eingeschaltet wurde und du nicht auf 'nem Läppi mit Shared Memory arbeitest? Das es an den veralteten Methoden liegt, kann ich mir nicht vorstellen (GLUT wäre sonst inzwischen Geschichte).


----------



## Network (5. Okt 2012)

Spacerat hat gesagt.:


> @Network: Er arbeitet mit Würfeln, da machen "LevelOfDetails" (so nennt man diese einfacheren Objekte, von denen es pro Entity durchaus auch mehr als 2 geben darf) keinen Sinn.



Entschuldigung, da hatte ich mich wohl nicht genau genug ausgedrückt:
- Das Beispiel war ein Beispiel gezogen von anderen Programmen. Ein LevelOfDetail macht hier in der Tat weniger Sinn.
Gemeint war, dass auch Programme namenhafter Hersteller nicht zigtausende Objekte darstellen können, sondern nunmal den Detailgrad bei entfernteren Objekten auch herunterschrauben müssen von Polygonen zu meist Quadern.
- Das es nur zwei Stufen dabei geben kann habe ich nicht behauptet und ist auch völlig irrelevant für den Fragesteller. 

@TO
Gib doch mal das compilierte Programm, dann können wir ja auch mal testen ob es an deinem Computer liegt oder tatsächlich irgendwo anderst liegt.
Eventuell fehlt es ja an der nötigen Grafikkartenarchitektur was soweit ich weiss bewirken kann, dass die CPU die GPU Aufgaben übernehmen muss.


----------



## Marco13 (5. Okt 2012)

Spacerat hat gesagt.:


> @zzuegg: Das ergäbe 10000 Drawcalls, wenn er mit Vertexbuffern arbeiten würde. Aber er arbeitet ja auch noch mit Displaylisten (afaik macht das Glut auch), so kommt er nach meiner Rechnung auf 2,4 Millionen "Drawcalls", nämlich jedes einzelne glVertex3f.



Das scheint ein Wissen darüber zu implizieren, wie Display Lists intern verarbeitet werden. Vielleicht weißt du das. Ich weiß es nicht. Aber mit einer üblen Mischung aus "Vernunft", "dem was ein Kollege der sich damit auskennt mal angedeutet hat" und "Spekulation" behaupte ich mal, dass zumindest der Teil der display listen, der NICHT irgendwelche Zustände ändert, sondern NUR Geometrie generiert, intern über die gleichen (oder sagr dieselben) Mechanismen abgefrühstückt wird, wie VBOs und Co - in dem Sinne, dass intern etwas gemacht wird wie

```
void glBeginDisplayList()
{
    currentVBO = generateVBO();
    currentVBOdata = allocate();
}
void glVertex3f(...)
{
    currentVBOdata[i++] = x;
    currentVBOdata[i++] = y;
    currentVBOdata[i++] = z;
}
void glCallList(int i)
{
    glDrawElements(vboFor(i)...);
}
```
(die Grafikkartenhersteller hätten mit Sicherheit nicht propagiert, in OpenGL Strukturen aufzunehmen, die komplett von dem abweichen, was sie intern ohnehin schon gemacht haben).

GLUT kümmert sich um diese Teile ja nicht direkt - das "wichtige" an GLUT sind ja die Funktionen für's setup, Ansprechen von Maus und Tastatur und glutPostRedisplay - eben die Abstraktion vom Window-Manager. Im speziellen sind die glutSphere und glutTeapot-Sachen NICHT dafür gedacht, effizient viel Geometrie auf den Bildschirm zu bringen, sondern eher für KSKBs. 

Wenn Fancy sich hier nicht bald einhakt, bau' ich vielleicht mal ein KSKB mit instancing - und sei es nur, um ihn dazu zu provozieren, ein KSKB zu basteln, das nicht instancing verwendet, sondern Geometry Shader und OpenGL 4.4, womit er dann 10x so viele Würfel flüssig darstellen kann ...


----------



## Spacerat (5. Okt 2012)

Irgend etwas muss ja dran sein warum glVertexXYZ() usw deprecated sind. Bestüden Displaylists nur aus statischen Vertexdaten, wie man sie von Interleaved Buffers kennt, müssten sie dann nicht ebenso wenn nicht sogar performanter als diese Buffer sein. Das hab' ich btw. noch gar nicht getestet. Bin da nur 'nem Hype gefolgt, nach welchem man besser VBOs verwenden sollte. Wie auch immer. Beides lässt sich mit STRIP_ARRAYS noch optimieren, entscheidend ist aber immer noch, dass 3,6 MTPS (Million Triangles per Second) nicht gerade die Welt ist.



Marco13 hat gesagt.:


> GLUT kümmert sich um diese Teile ja nicht direkt - das "wichtige" an GLUT sind ja die Funktionen für's setup, Ansprechen von Maus und Tastatur und glutPostRedisplay - eben die Abstraktion vom Window-Manager. Im speziellen sind die glutSphere und glutTeapot-Sachen NICHT dafür gedacht, effizient viel Geometrie auf den Bildschirm zu bringen, sondern eher für KSKBs.


Äh... GLUT? Wer schr... shit.  GLU verdammt, da sollte GLU stehen. Na egal, ob nun die GLU- oder GLUT-Dinger, die sind wirklich nur für KSKBs. Wusste bis eben noch nicht mal, dass GLUT inzwischen auch JOGL Bestandteil ist, was soll's brauchte es eh' nie.

Aber mal ganz was anderes. Was macht "core.getEntities()"? Ändert sich deren Inhalt während eines Schleifendurchlaufs? Evtl. hilfts ja schon, wenn man dieses einmalig vor der Schleife in eine Variable deklariert und dann evtl. ForEach statt ForNext verwendet.

```
Collection<Entity> entities = core.getEntities();
for(Entity entity : entities) {
  // your code
}
```


----------



## Guest2 (6. Okt 2012)

Moin,

wie Marco schon schrieb, ist an dem Code oben erstmal nichts wirklich falsch. Andererseits ist der dort gewählte Ansatz gute 20 Jahre alt und hat mit heutigem OpenGL höchstens noch bedingt etwas zu tun. Was mich allerdings etwas wundert, ist das dort scheinbar 10000 verschiedene Displaylisten zum Einsatz kommen. Warum nicht nur eine? Eine Box ist eine Box und die Größe der Box kann auch mit glScalef beeinflusst werden, genau wie die Position eben mit glTranslatef. (Ob das die Geschwindigkeit beeinflusst weis ich aber nicht.)

Nichtsdestotrotz bleiben es pro Frame 10000 individuelle als deprecated markierte Zeichenfunktionen sowie 40000 als deprecated markierte Matrixfunktionen. Das dürfte nicht unbedingt ein Szenario sein, auf das heutige Grafikkarten optimiert sind. Ob die angegebenen 30 fps realistisch sind, kann ich allerdings auch nicht sagen. Im Allgemeinen sind 10000 Würfel aber ehr ein Fliegenschiss.




Marco13 hat gesagt.:


> Wenn Fancy sich hier nicht bald einhakt, bau' ich vielleicht mal ein KSKB mit instancing - und sei es nur, um ihn dazu zu provozieren, ein KSKB zu basteln, das nicht instancing verwendet, sondern Geometry Shader und OpenGL 4.4, womit er dann 10x so viele Würfel flüssig darstellen kann ...



Lol 

Tatsächlich hab ich schon ein paar Mal dran gedacht zu dem Thema ein KSKB zu bauen. Gefühlt will ja scheinbar jeder Zweite inzwischen was aus Würfeln bauen. Allerdings ist mir noch nichts eingefallen wie  tausende Würfel dargestellt werden sollen, ohne dass die einfach nur eine einförmige farbige Fläche ergeben. Auseinanderhalten kann man die imho erst mit Licht und einer halbwegs sinnvollen Anordnung, dann ist aber das drum herum wieder wesentlich aufwendiger als die Handvoll Zeilen, die zum Zeichnen benötigt werden.

Instancing ist übrigens eine interessante Idee! Spontan hätte ich das ehr angewendet, wenn es um eine etwas komplexere Geometrie geht. Inwieweit der Ansatz über den Geometrieshader wirklich schneller ist, müsste man in der Tat mal ausprobieren. 

Viele Grüße,
Fancy


----------



## Spacerat (6. Okt 2012)

Guest2 hat gesagt.:


> Was mich allerdings etwas wundert, ist das dort scheinbar 10000 verschiedene Displaylisten zum Einsatz kommen. Warum nicht nur eine?


Ja, nee, is' klar... Wenn mann biss'l blind ist, sieht man manchmal nix. XD
Da kann schon was dran sein. Displaylisten erstellt man ja nicht umsonst. Für jeden Würfel 'ne eigene würde ja wieder bedeuten, dass man grössere Speichermassen bewegen muss.


----------



## Spacerat (6. Okt 2012)

BTW.: Jetzt wo Fancy darauf hinweisst... Ist es eigentlich korrekt, wenn man laufende Zahlen als ListId übergibt? Sollten da nicht jene IDs übergeben werden, welche man per glGenLists() definiert bekommt?


----------



## Marco13 (6. Okt 2012)

Stimmt, das mit den 10000 display lists hatte ich auch übersehen  Es kann gut sein, dass die "zufällig" durchlaufend nummeriert werden, aber darauf sollte man sich nicht verlassen - und wenn man nur EINE verwendet, stellt sich die Frage ja auch nicht mehr.


----------



## Guest2 (6. Okt 2012)

Ich würde auch die Zahlen verwenden, die von glGenLists geliefert wurden. Aber (ohne jetzt in einer alten Spec suchen zu wollen ob genau das auch für glGenLists galt):




			
				http://www.opengl.org/wiki/OpenGL_Object hat gesagt.:
			
		

> Legacy Note: In OpenGL versions before 3.0, the user was allowed to ignore the generation step entirely. The user could just decide that "3" is a valid object name, and start using it like an object. The implementation would then have to accept that and create the object behind the scenes when you first start using it. In GL 3.0, this behavior was deprecated. In core GL 3.1 and above, this is no longer allowed. Regardless of the version of OpenGL, it is always good practice to use glGen* rather than making up your own object names.



Viele Grüße,
Fancy


----------



## Spacerat (6. Okt 2012)

Guest2 hat gesagt.:


> Ich würde auch die Zahlen verwenden, die von glGenLists geliefert wurden. Aber (ohne jetzt in einer alten Spec suchen zu wollen ob genau das auch für glGenLists galt):


Ja, tut es (sonst würde die Anwendung überhaupt nicht laufen ). Mit "korrekt" war auch mehr die Korrektheit des Codes gemeint. Ohne genLists und die darauf folgende Verwendung der erhaltenen Namen weis doch kaum jemand, wann da was generiert wird. Werden da einmalig oder pro Durchlauf 10000 Listen generiert? Ich hatte mal ein diesbezügliches Problem bei meinen 3D-Fonts, wo es teilweise so aussah, als würde die Hardware diese Listen nach eigenem Ermessen erstellen. Einmal kamen Exceptions (INVALID_NAME), ein anderes mal lief alles glatt. Kann sein, dass es damit zusammenhing, dass für die Anzahl an verwendeten Namen nicht genug Listen erstellt wurden oder wie auch immer. Erst dieses glGenLists verschaffte Abhilfe.


----------



## turing (6. Okt 2012)

Hallo nochmal,

erstmal danke für die vielen Antworten. Zur Umsetzung komme ich wohl leider erst frühstens Montag. Ich habe mich bisher nur 'von Hand' ein wenig in das Thema OpenGL / JOGL eingearbeitet, daher haben sich mir nicht alle Hinweise gänzlich erschlossen. 

a) Das 10.000 Objekte nicht tragbar sein sollte, kann ich nicht nachvollziehen. Es handelt sich um eine Szene, in der tasächlich so viele Objekte (Geräte) vorhanden sind, die man (je nach Kamera) auch grundsätzlich alle sehen kann. Clipping ist natürlich eingestellt, ist und soll aber noch nicht bei diesem Aufabu Objekte abschneiden. 

b) Ich scheine veraltete OpenGL Funktionen zu benutzen... Hmmm... Darauf wäre ich nie gekommen. Welche sind es und wie sehen die neuen Vertex und Matrix-Funktionen aus? Das könnte ja schon mal ein Anfang sein, der schnell umsetzten ist.

c) Absolut korrekt, dass es eigentlich auch eine Display-Liste machen sollte. Die Position wird ja eh mit glTranslate verändert vor dem Aufruf der Liste. Dieser Aufbau ist der Tatsache geschuldet, dass es eigentlich nicht nur Würfel sind, sondern vielfach auch andere, komplexere Geometrien. Diese habe ich allerdings aus der Messung zunächst entfernt um erst einmal den grundsätzlichen Aufbau zu analysieren. Jedoch lassen sich die 10000 Listen in etwa ein paar Dutzend unterschiedliche Geometrien und damit Listen aufteilen. Das werde ich versuche und berichten.


----------



## Spacerat (6. Okt 2012)

Deprecated sind z.B. glVertex() und glColor(). glTranslate()? Hmm, k.P., nur weil ich nur noch glMultMatrix() verwende (damit setzt man Position, Rotation, Scale und ggf. Scherung auf einmal), heisst das nicht, dass es auch deprecated ist. Allerdings stellt sich auch noch die Frage, ob aktuelleres GL für deine "alte Gurke" (wie du sagst) überhaupt in Frage kommt.
Bei verschiedenen Objektlisten solltest du erst recht die Anmerkung mit glGenLists() beherzigen und evtl. jeder Entity den generierten Namen einer solchen Liste übergeben. Auf ein get ("getListID()") mehr oder weniger kommt's da dann auch nicht an. Was mann evtl. noch verbessern könnte, wäre die Sache mit den Koordinaten und den Farben. Wenn man dort Arrays verwenden würde, liesse sich das Ganze recht simpel mit einem einzigen Schaufel-Array (ObjectReuse) realisieren:

```
gl.glLoadIdentity();
float[] tmpArray = new float[16];
for(Entity entity : entities) {
  gl.glPushMatrix();
  entity.getTransform(tmpArray); // laedt die komplette Objektmatrix
  gl.glMultMatrix(tmpArray);
  entity.getColor(tmpArray);
  gl.glColor3fv(tmpArray);
  gl.glCallList(entity.getListId());
  gl.glPopMatrix();
}
```
Wie du die Getmethoden implementieren musst, erschliesst sich hoffentlich von selbst.

```
public void getTransform(float[] target) {
  // vorrausgesetzt, die Entity speichert seine Matrix ebenfalls in einem Array...
  System.arrayCopy(transform, 0, target, 0, 16);
}

public void getColor(float[] target) {
  target[0] = red;
  target[1] = green;
  target[2] = blue;
}
```

Wenn man eine Entity nun noch geschickt als Iterable<Entity> (Liste oder so) implementiert, kann man damit wunderbar ganze Trees rekursiv rendern.

```
Entity scene = getScene(); // wo auch immer der Rootknoten des Trees herkommt...
float[] tmpArray = new float[16];
gl.glLoadIdentity();
render(scene, tmpArray, gl);

public void render(Entity entity, float[] tmpArray, GL gl) {
  gl.glPushMatrix();
  entity.getTransform(tmpArray);
  gl.glMultMatrix(tmpArray);
  entity.getColor(tmpArray);
  gl.glColor3fv(tmpArray);
  gl.glCallList(entity.getListId());
  for(Entity e : entity) {
    render(e, tmpArray, gl);
  }
  gl.glPopMatrix();
}
```


----------



## Guest2 (6. Okt 2012)

turing hat gesagt.:


> b) Ich scheine veraltete OpenGL Funktionen zu benutzen... Hmmm... Darauf wäre ich nie gekommen. Welche sind es und wie sehen die neuen Vertex und Matrix-Funktionen aus? Das könnte ja schon mal ein Anfang sein, der schnell umsetzten ist.



Bis auf glClear() sind alle OpenGL Funktionen die Du oben benutzt veraltet. Matrix-Funktionen gibt es nicht mehr. Vertices werden über VBOs (Vertex Buffer Object) die an ein VAO (Vertex Array Object) gehangen werden müssen definiert. Die Fixed Function Pipeline ist veraltet, ohne Shader (mindestens Vertex + Fragment) geht nichts mehr. Kurz: Außer das es immer noch Dreiecke bleiben ändert sich fast alles (Quads sind auch deprecated).

Ein guter Einstig zum Lesen ist: Learning Modern 3D Graphics Programming

Ob und inwieweit ein Umstieg in Deinem Fall sinnvoll ist, kann ich nicht abschätzen. "Schnell umsetzen" wird aber vermutlich schwierig. 

Viele Grüße,
Fancy


----------



## Guest2 (6. Okt 2012)

Btw.:



turing hat gesagt.:


> Dieser Aufbau ist der Tatsache geschuldet, dass es eigentlich nicht nur Würfel sind, sondern vielfach auch andere, komplexere Geometrien. [..] Jedoch lassen sich die 10000 Listen in etwa ein paar Dutzend unterschiedliche Geometrien und damit Listen aufteilen.



Dann solltest Du dir das von Marco empfohlene Instancing unbedingt ansehen!

Viele Grüße,
Fancy


----------

