# (LWJGL) Fragen zu VBOs



## Luk10 (8. Mai 2012)

Grüße,

Zur Zeit beschäftige ich mit VBOs aber ich komme nicht besonders gut zurecht damit. Ich habe mir schon einige Tutorials angeschaut aber nicht gefunden nach was ich gesucht habe.
Meine Situation:
Ich habe Vertex-Daten in floatBuffern gespeichert. Diese möchte ich mit VBOs zeichnen lassen.
Im moment mach ich das so:

Jedes meiner Objekte (sind recht viele 300+) hat folgendes:

```
public static final int VERTEX_COUNT = 4;

protected boolean bound;
protected int vboID;
protected FloatBuffer buffer;

protected void bind() {
    bound = true;
	vboID = glGenBuffers();
	executeUpdate();
}

protected void executeUpdate() {
	glBindBuffer(GL_ARRAY_BUFFER, vboID);
	glBufferData(GL_ARRAY_BUFFER, buffer, GL_STREAM_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
}

public void draw() {
	if (!vboBound) bind();

	executeUpdate();

	glBindBuffer(GL_ARRAY_BUFFER, vboID);
	glVertexPointer(JavEvolve.VERTEXSPACE, GL_FLOAT, 0, 0L);

	glEnableClientState(GL_VERTEX_ARRAY);
	glDrawArrays(GL_QUADS, 0, VERTEX_COUNT);
	glDisableClientState(GL_VERTEX_ARRAY);
}
```

Der Buffer wird zwischen den Frames von einem anderen Thread (außerhalb des GL-Contexts) verändert.

Leider ist diese Methode wesentlich langsamer als Immediate-Mode und ich denke ich mache irgendetwas grundlegend falsch. Sollte man nur einen VBO für alle Objekte verwenden? Hab was von VBO-Mapping gelesen, aber auch, dass davon abgraten wird.

Wäre für Hilfe sehr dankbar!
-Luk10-


----------



## Marco13 (8. Mai 2012)

Warum abgeraten? Dieses Update war auch das erste, was mir suspekt erschien. Das Mapping würde man mit glMapBuffer machen, wo man dann einen ByteBuffer bekommt, den man asFloatBuffer ansehen und mit dem gegebenen Buffer füllen kann - das sollte sehr schnell gehen. (Unter Umständen (! das muss man sich genau überlegen!) kann man sich dann auch den zweiten Buffer schenken, falls der synchron zu einem bestimmten Zeitpunkt gefüllt werden kann - aber so eine Entkopplung ist meistens nicht verkehrt, deswegen würde man ihn wohl erstmal drin lassen). 

Und das was Fancy da unter mir schreibt solltest du natürlich auch berücksichtigen! :smoke:


----------



## Luk10 (8. Mai 2012)

Also kann man schon mit einem VBO pro Drawable-Objekt arbeiten?

Sind Buffer Thread-Safe? Also kann nur ein Thread drauf zugreifen, oder muss man das selber machen?


----------



## Marco13 (8. Mai 2012)

Was meinst du mit "einem VBO pro Drawable-Objekt"? Ein Objekt kann auch aus mehreren VBOs bestehen, z.B. eins für die Vertices und eins für die Normalen. Man kann die Daten aber auch "interleaved" in einen Buffer packen. (@Fancy: Gibt's einen Grund, warum man das machen sollte? Ich finde einzelne VBOs praktischer ... )

Buffer sind nicht thread-safe. Und bei GL ist's noch kritischer: Man sollte sich diesen Buffer, den man da bekommt, NICHT zwischenspeichern oder so, und ihn auch NUR von dem Thread (und am besten der Methode) aus verwenden, wo man das glMapBuffer durchgeführt hat. Man sollte nach dem Mappen nämlich so schnell wie möglich wieder ein Unmap machen, und dadurch würde der Buffer auf eine Art und Weise ungültig, die Java eigentlich nicht kennt, was zur Folge hat, dass der Versuch, diesen Buffer dann noch zu verwenden, fieseste Crashs verursachen würde...


----------



## Luk10 (8. Mai 2012)

Mit Drawable-Objekten meine ich Objekte, die gezeichnet werden können. Als z.B. 
	
	
	
	





```
class Rect extends AbstractDrawable
```
. Wenn ich 400 Rects zeichnen will, erstelle ich 400 Objekte der Klasse Rect, jedes mit seinem eigenen Vertex-VBO.

Oder habe ich für die 400 Rects 1 VBO?


----------



## Marco13 (8. Mai 2012)

OK, das ist eine etwas andere Situation: Ein Rectangle ist ja "trivial". Da "lohnt" es sich praktisch nicht, ein VBO dafür zu erstellen. VBOs sind ja (lapidar formuliert) dafür da, einen Ar.... voll Vertices, Normalen, Texturkoordinaten etc. als einen großen, zusammenhängenden Block zu verwalten. 

Man könnte schon in Erwägung ziehen, die vielen Rectangles als EIN VBO zu speichern. Wichtig wären dann so Fragen, wie ob sich deren Anzahl ändern kann, oder wie die upgedatet werden sollen (immer nur ein Rechteck, oder immer alle gleichzeitig...)


----------



## Luk10 (8. Mai 2012)

Dachte ich mir schon. Es ist natürlich ein bisschen mehr als nur ein Rect (war nur ein Beispiel), aber mehr als 10 Vertex-Datensätze und 10-Texture Datensätze brauche ich pro Objekt auf keinen Fall (Momentan sind es nur 4 Vertex-Coords. und 4 Texture-Coords).

Die Objekte müssen *alle* und *jeden* Frame geupdated werden, wodurch - glaube ich - auch meine Perfomance bei vielen Objekten drastisch zurückgeht.

Eine Möglichkeit wäre (wie du schon angedeutet hast) einen DrawableBuffer (Container) zu schreiben, der Drawables in einem VBO zusammenfasst (Kann man in VBOs innerhalb mit Offsets arbeiten?). Jedoch ist der Umschlag an Drawables sehr groß, deswegen sind Container mit fester Größe (Buffer brauchen ja ein festes Limit) immer schwierig.

Gibt es für solche Situationen geeignetere Render-Methoden in OpenGL?


----------



## Marco13 (8. Mai 2012)

Hm. Es ist schwierig, konkrete Tipps zu geben, wenn man den Zusammenhang nicht genauer kennt. Wenn es nicht "veraltet" wäre, würde ich fast sagen, dass in so einem Fall auch nichts gegen den Immediate Mode spricht. 
Natürlich könnte man auch sowas machen wie dass man einen großen Buffer (VBO) erstellt, der so viele Rectangles aufnehmen kann, wie maximal benötigt werden, und dann ggf. nur einen Teil davon zeichnen. Vielleicht hat Fancy noch einen fundierteren Tipp...


----------



## schalentier (8. Mai 2012)

Ich hab mich vor einer Weile mal mit etwas aehnlichem beschaeftigt, da bin ich den Weg gegangen, den auch notch gegangen ist. 

Es gibt ein VBO mit einer festen Groesse, in welches du solange Daten reinschiebst, bis es voll ist. Dann wird das VBO zu Graka geschickt und geleert. Evtl. kann man hier auch mehrere Buffer benutzen (DoubleBuffer Prinzip) - da hat in meinen Messungen aber nix gebracht.

Das alleine ist ebenfalls kaum messbar schneller als Immediate Mode. Dazu benutz ich dann Display Listen, um groesse Bloecke (in meinem Fall viele Wuerfel) zusammen zu fassen und quasi in der Grafikkarte zu cachen. 

Geht natuerlich nur schneller, wenn sich nicht alle Daten in jedem Frame aendern.

Kannst dir auch anguggen, wie ich das damals gemacht hab.


----------



## Guest2 (9. Mai 2012)

Moin,

zum Thema VBO muss (!) man diese beiden Links imho gelesen haben:

Vertex Specification

Vertex Specification Best Practices


Leider ist vieles was man sonst zum Thema im Netz findet nicht ganz korrekt.

Luk10 schreibt zwar leider nicht, auf welche OpenGL Version er sich bezieht, sodass es möglich ist, dass der erste Link Features enthält, welche nicht genutzt werden können / sollen. Jedoch gilt das meiste auch für die älteren OpenGL Versionen.

Für 10 Vertices ein eigenes VBO ist auf jeden Fall ungünstig. 5MB pro VBO sind heute imho ein guter Wert. Bei den häufigen 32 Byte pro Vertex (v, vn, vt) also etwa 16k Vertices. Ob man alle Vertex Attribute in ein VBO oder mehrere separate VBOs legt, hängt davon ab, welche Daten geändert werden sollen. Wenn v und vn CPU-seitig manipuliert werden sollen, dann z.B. die beiden als GL_STREAM_DRAW und vt als GL_STATIC_DRAW.

Außerdem sollte man imho immer Dreiecke und immer Indicies verwenden. Ohne Indices gibt es keinen Post Transform Cache und damit selbst bei einem einfachen Quad 2/6 unnötig berechnete Vertices.

Bei dem bisher hier beschriebenen Szenario würde ich Vertex Specification Best Practices - Dynamic VBO versuchen. Vermutlich liefe das dann auf 4 VBO und 2 IBO hinaus. Allerdings habe ich selber die dort skizzierte Variante mit glBufferData(..NULL..) und glBufferSubData(..BUFFER..) nie implementiert. Imho gehören Vertex Daten einfach nicht mehr auf die CPU.




Marco13 hat gesagt.:


> z.B. eins für die Vertices und eins für die Normalen. Man kann die Daten aber auch "interleaved" in einen Buffer packen. (@Fancy: Gibt's einen Grund, warum man das machen sollte? Ich finde einzelne VBOs praktischer ... )



Nuja, wenn die Vertex Daten statisch sind (was ja nicht heißt, das sie nicht doch GPU-seitig animiert sein dürfen ), dann ist ein einzelnes VBO erstmal schneller. 

Rein vom Gefühl würde ich aber auch vermuten das alle Daten in einem Stream zu zukünftigen OpenGL Versionen "kompatibler" sein werden (wenn man sich z.B. die UBO API ansieht...). Allerdings ist das wirklich nur ein Gefühl...

Viele Grüße,
Fancy


----------



## Luk10 (9. Mai 2012)

Danke für eure Antworten!

Ich verwende (glaube ich zumindest) die einer der neuesten OpenGL-Versionen 4.x
Wieso ist Immediate-Mode veraltet, wird aber trotzdem noch von meiner Version unterstützt?

Momentan bleibe ich jetzt bei Immediate-Mode, bis ich merke, dass ich deutlich Performance-Probleme bekomme, da ich einfach nicht genug weiß um VBOs vernünftig zu verwenden.

Noch eine Frage:
Ich will meine Texturen animieren und muss sie teils dazu leicht verändern. Ich habe das mit 

```
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
```
versucht und mir ist folgendes aufgefallen:

Sobald auch nur xoff = 1 und yoff = 1 ist die Geschwindigkeit um den Faktor *2* besser. Woran kann das liegen?

Danke!
-Luk10-


----------



## Guest2 (9. Mai 2012)

Wenn bei der OpenGL Kontext Erzeugung keine Parameter angegeben werden, erzeugen die meisten Grafikkartentreiber einen "Compatibility Profile Context". Dann stehen sowohl die neuen Futures als auch die alten "deprecated" Funktionen bereit. Das wurde damals so eingeführt, um kompatibel zu bestehenden Anwendungen zu bleiben.

Wird ein "Core Profile Forward-Compatible Context" erzeugt, stehen vermutlich viele der von Dir verwendeten OpenGL Funktionen nicht mehr zur Verfügung.



Luk10 hat gesagt.:


> Sobald auch nur xoff = 1 und yoff = 1 ist die Geschwindigkeit um den Faktor *2* besser. Woran kann das liegen?



Sehr viele Parameter beeinflussen die Performance, so das sich das aus einer Zeile nicht erschließt. Als Tipp: OpenGL Pixel Buffer Object (PBO)
. Dort ist unten ein Beispiel für das Streaming von Texturen.

Viele Grüße,
Fancy


----------



## Luk10 (9. Mai 2012)

Gibts PBO auch ohne die ARB-Extension?

Leider ist es sehr schwer für mich diese Tutorials zu verstehen, da mit die Grundlagen fehlen und ich keine Umfangreichen Tutorials / Bücher dazu finden kann. Viele Tutorials bleiben bei Immediate-Mode stehen oder steigen gleich in komplexere Sachen ein.

Wie lässt sich denn ein "Null-Pointer" in Java bzw. lwjgl umsetzten?

-Luk10-


----------



## Guest2 (9. Mai 2012)

Ja, PBO gibt es imho seit OpenGL 2.1 auch ohne ARB. Einfach das ARB-Suffix weglassen. 

Bei lwjgl ist der Null-Pointer entweder "0" oder "null", wobei bei den meisten Methoden mehrere Buffer Varianten existieren, sodass die "null" ggf. gecastet werden muss, also z.B. "(IntBuffer)null". 

Wenn Du ein ausführliches Tutorial / Buch suchst, das sich mit dem aktuellen OpenGL beschäftigt, dann: Learning Modern 3D Graphics Programming. Nicht einfach und auch nicht ganz komplett. Dafür aber sehr komplex, genau wie aktuelles OpenGL. 

Viele Grüße,
Fancy


----------



## Luk10 (9. Mai 2012)

Vielen Dank für den Link!

Eine Frage dazu:
Was genau ist der Clip-Space? Also die 4. "Koordinate", die (manchmal) mit einem Vertex angegeben wird? Tutorial sagt dazu nur 





> The volume that the triangle is transformed into is called, in OpenGL parlance, clip space


Was genau ist denn damit gemeint? Was ist der Clip-Space?

Danke,
-Luk10-


----------



## Marco13 (10. Mai 2012)

Vereinfacht gesagt: Der Clip Space ist der Inhalt des Pyramidenstumpfs, der den sichtbaren Bereich beschreibt (z.B. im Bild auf Game Rendering  Clip Space ). In dieser Darstellung ist die letzte Koordinate, W, ein Faktor, mit dem man zwischen diesem Clip Space und dem "Echten" Raum umrechnen kann. 
Komplizierter gesagt: ... 
(das kann jemand anderes machen )


----------



## Luk10 (10. Mai 2012)

Hm so ganz klar ist mir das noch nicht, aber gut.
Eine weitere Frage habe ich noch:

Wieso gibt es in lwjgl folgendes nicht?

```
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
```

Wie bekomme ich die Funktionalität dieser Statements?
Danke,
-Luk10-


----------



## Guest2 (10. Mai 2012)

In den späteren Kapiteln wird das mit den verschiedenen "Spaces" ausführlicher erklärt (ich glaube im 4.).

glEnableVertexAttribArray gibt es so auch in lwjgl. Bei glVertexAttribPointer weicht lwjgl leider (wiedermal) von der OpenGL API ab. Ersetze mal den 4. Parameter durch "false", dann sollte es imho gehen.

Viele Grüße,
Fancy


----------



## Luk10 (10. Mai 2012)

Hm okay ... also kann ich das erstmal weglassen!?

Versuche mich gerade an den Shadern, jedoch werden die aus irgendeinem Grund nicht richtig kompiliert und 

```
(glGetShader(vertShader, GL_COMPILE_STATUS) == GL_FALSE)
```
 liefer true zurück.

Ich habe die GLSL-Shader direkt aus dem Tutorial übernommen und folgendermaßen verarbeitet:


```
vertShader = glCreateShader(GL_VERTEX_SHADER);
fragShader = glCreateShader(GL_FRAGMENT_SHADER);
program = glCreateProgram();

glShaderSource(vertShader, GopLoader.loadShader("res/shader/shader.vert"));
glCompileShader(vertShader);
glShaderSource(fragShader, GopLoader.loadShader("res/shader/shader.frag"));
glCompileShader(vertShader);

if (glGetShader(vertShader, GL_COMPILE_STATUS) == GL_FALSE) System.err.println("Compilation Error: vertShader");
if (glGetShader(fragShader, GL_COMPILE_STATUS) == GL_FALSE) System.err.println("Compilation Error: fragShader");

glAttachShader(program, vertShader);
glAttachShader(program, fragShader);
glLinkProgram(program);
glDetachShader(program, vertShader);
glDetachShader(program, fragShader);
glValidateProgram(program);
```


```
GopLoader.loadShader("res/shader/shader.vert (frag)")
```
 läd alles korrekt mit Zeilenumbrüchen.
Weiß nicht wo der Fehler liegt.

[EDIT]Genauer: Nur der .frag Shader funktioniert nicht:

```
#version 330

out vec4 outputColor;
void main() {
   outputColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);
}
```
[/EDIT]

-Luk10-


----------



## Guest2 (10. Mai 2012)

Ja, das mit den Spaces wird später klarer.

Lass Dir mal die Fehlermeldungen zurückgeben. Etwa so:

Bei den Shadern:

```
glCompileShader(handle);

if (glGetShader(handle, GL_COMPILE_STATUS) == GL_FALSE) {

    final int length = glGetShader(handle, GL_INFO_LOG_LENGTH);
    System.err.println(glGetShaderInfoLog(handle, length));

}
```

Beim Program:

```
glLinkProgram(handle);

if (glGetProgram(handle, GL_LINK_STATUS) == GL_FALSE) {

    final int length = glGetProgram(handle, GL_INFO_LOG_LENGTH);
    System.err.println(glGetProgramInfoLog(handle, length));

}
```

Viele Grüße,
Fancy


----------



## Luk10 (10. Mai 2012)

Ergibt nichts:


```
if (glGetShader(fragShader, GL_COMPILE_STATUS) == GL_FALSE) {
	System.err.println("Compilation Error: fragShader");
	final int length = glGetShader(fragShader, GL_INFO_LOG_LENGTH);
	System.err.println(glGetShaderInfoLog(fragShader, length));
}
```

Ergibt:

Compilation Error: fragShader


----------



## Luk10 (10. Mai 2012)

[EDIT]Fehler gefunden:


```
glShaderSource(vertShader, GopLoader.loadShader("res/shader/shader.vert"));
glCompileShader(vertShader);
glShaderSource(fragShader, GopLoader.loadShader("res/shader/shader.frag"));
glCompileShader(vertShader); // /facepalm
```

Zwei mal den fragShader kompiliert -_-[/EDIT]


----------

