Ich frage mich, wie das Zeichnen einfacher Geometrie mit Texturen so viel Leistung verschlingen kann (unter 60 fps). Selbst meine gute Grafikkarte (GTX 960) kann "nur" problemlos bis zu 1000 Sprites zeichnen. Die Texturen, die ich verwende, bestehen alle aus 2 Texturen und dürfen eine Größe von 512 x 512 nicht überschreiten. Ich filtere sogar GL_NEAREST
nur mit.
Die Sprites selbst sind zufällig in der Größe generiert. Es gibt also keine 1000 Vollbild-Quads, was kein wirklicher Anwendungsfall wäre.
Ich zeichne meine Sprites gestapelt, was bedeutet, dass ich einen dynamischen Scheitelpunktpuffer und einen statischen Indexpuffer habe. Ich aktualisiere den Scheitelpunktpuffer jedes Bild mit glBufferSubData
einmal und zeichne dann alles mit `` glDrawElements`. Ich habe ungefähr 5 verschiedene Texturen, die ich einmal pro Frame binde, was zu 5 Draw Calls führt. Zum Rendern verwende ich nur einen Shader, der gebunden ist, sobald die Anwendung gestartet wird.
Ich habe also 5 Texturbindungen, 5 Zeichenaufrufe und ein Vertex-Puffer-Update pro Frame, was nicht wirklich so viel ist.
Hier ist ein Beispiel mit einer Textur:
val shaderProgram = ShaderProgram("assets/default.vert", "assets/default.frag")
val texture = Texture("assets/logo.png")
val sprite = BufferSprite(texture)
val batch = BufferSpriteBatch()
val projView = Matrix4f().identity().ortho2D(0f, 640f, 0f, 480f)
fun setup() {
glEnable(GL_TEXTURE)
//glColorMask(true, true, true, true)
//glDepthMask(false)
glUseProgram(shaderProgram.program)
texture.bind()
batch.begin()
for(i in 1..1000)
batch.draw(sprite)
batch.update()
}
fun render() {
glClear(GL_COLOR_BUFFER_BIT)
stackPush().use { stack ->
val mat = stack.mallocFloat(16)
projView.get(mat)
val loc = glGetUniformLocation(shaderProgram.program, "u_projView")
glUniformMatrix4fv(loc, false, mat)
batch.flush()
}
}
Die batch.draw()
Methode legt die Scheitelpunktdaten der Sprites in einem CPU-seitigen Puffer ab und batch.update()
lädt alles mit auf die GPU hoch glBufferSubData
. Das Einrichten des Spritebatch sieht folgendermaßen aus:
glBindBuffer(GL_ARRAY_BUFFER, tmpVbo)
glBufferData(GL_ARRAY_BUFFER, vertexData, GL_STATIC_DRAW)
glEnableVertexAttribArray(0)
glEnableVertexAttribArray(1)
glEnableVertexAttribArray(2)
glVertexAttribPointer(0, 2, GL_FLOAT, false, 24 * sizeof(Float), 0)
glVertexAttribPointer(1, 4, GL_FLOAT, false, 24 * sizeof(Float), 2.toLong() * sizeof(Float))
glVertexAttribPointer(2, 2, GL_FLOAT, false, 24 * sizeof(Float), 6.toLong() * sizeof(Float))
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tmpEbo)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW)
Ich habe zuerst mein Programm profiliert, aber das Aktualisieren der Scheitelpunktpuffer und der gesamten Geometrie dauert ungefähr 10% der Gesamtzeit pro Frame. Das Austauschen der Puffer nimmt jedoch den Rest der 90% -Rahmenzeit in Anspruch.
Ich frage mich also, wie können so große AAA-Spiele ihre Szenen mit Millionen von Eckpunkten rendern, wenn das Zeichnen von Pixeln eine so zeitaufwändige Aufgabe ist? Ich weiß, dass es viele Optimierungen im Code gibt, aber immer noch.