OpenGL ES 2.0 - Schlechte Performance

  • Antworten:7
  • Bentwortet
Aaron B.
  • Forum-Beiträge: 206

13.05.2015, 16:58:42 via App

Hallo,

ich arbeite an einem 3D-Spiel mit OpenGl ES 2.0. Soweit läuft alles, jedoch sehr ruckelig. Meine einzelnen Models, welche sich in einer ArrayList befinden, werden in onDrawFrame einzeln mithilfe der Methode renderModel (siehe unten) an die Shader übermittelt. Ich habe herausgefunden, dass die einzelnen Models zwischen "0" - 2 Millisekunden benötigen, um ihre Daten an OpenGL zu übermitteln. Zusammengerechnet kommen da Werte von bis zu 100 ms pro Frame heraus, was ja nicht normal sein kann... (oder liege ich da falsch?)

Die einzelnen zu übergebenen Daten werden in java.nio.Buffer-Instanzen (bzw. FloatBuffer, ...) gespeichert und dann direkt an GL übergeben. Hier mein Code:

@Override
public void renderModel(AGScene scene, Object modelTransform, AGModel model) {
Model b = (Model) model;
int count = b.getCount();
de.ab.agf.lib.math.AGArrays.copy((float[]) modelTransform, 0, modelMatrix, 0, 16);
Matrix.setIdentityM(MVPMatrix, 0);
Matrix.multiplyMM(MVPMatrix, 0, viewMatrix, 0, modelMatrix, 0);
Matrix.multiplyMM(MVPMatrix, 0, projectionMatrix, 0, MVPMatrix, 0);
//pass data
//uniforms
GLES20.glUniformMatrix4fv(uMMatrix, 1, false, modelMatrix, 0);
GLES20.glUniformMatrix4fv(uMVPMatrix, 1, false, MVPMatrix, 0);
GLES20.glUniform1i(uTex, 0);
GLES20.glUniform1i(uTexEnabled, b.getTexEnabled() ? 1 : 0);
GLES20.glUniform1i(uLOShadeless, b.getLShadeless() ? 1 : 0);
GLES20.glUniform3fv(uLOAmbient, 1, b.getLAmbient());
GLES20.glUniform3fv(uLODiffuse, 1, b.getLDiffuse());
GLES20.glUniform3fv(uLOSpecular, 1, b.getLSpecular());
//attrs
addAttrFArray(aPosition, 3, b.getCoordinates());
addAttrFArray(aColor, 4, b.getColors());
addAttrFArray(aTexPosition, 2, b.getTexCoordinates());
addAttrFArray(aNormal, 3, b.getNormals());
if (b.getTexEnabled()) {
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, ((TextureStore) scene.getTextureStore()).getDataForKey(b.getTexture()));
}
GLES20.glDrawElements(GLES20.GL_TRIANGLES, count, GLES20.GL_UNSIGNED_SHORT, b.getDrawOrder());
//disable passed attr arrays
GLES20.glDisableVertexAttribArray(aPosition);
GLES20.glDisableVertexAttribArray(aColor);
GLES20.glDisableVertexAttribArray(aTexPosition);
GLES20.glDisableVertexAttribArray(aNormal);
}
private void addAttrFArray(int handle, int partCount, FloatBuffer data) {
if (data != null) {
GLES20.glVertexAttribPointer(handle, partCount, GLES20.GL_FLOAT, false, partCount * 4, data);
GLES20.glEnableVertexAttribArray(handle);
}
}

Das Model (Z. 3) ist eine Klasse, die die einzelnen Buffer und weiteren Eigenschaften bereit stellt.
In den Zeilen 7 - 10 werden die einzelnen Matrizen zusammengerechnet.
Anschließend werden die entsprechenden Parameter an OpenGL übergeben und gezeichnet.
Danach werden die VertexAttribArrays deaktiviert, um das nächste Model zeichnen zu können.

Shader:

Fragment-Shader:

precision mediump float;

uniform sampler2D u_Tex;
uniform int u_TexEnabled;

varying vec4 v_Color;
varying vec2 v_TexPosition;

void main() {
if (u_TexEnabled == 1)
gl_FragColor = v_Color * texture2D(u_Tex, v_TexPosition);
else
gl_FragColor = v_Color;
}

In meinem Fragment-Shader wird nur die Farbe übergeben.

In meinem Vertex-Shader wird diffuses und ambientes Licht berechnet (spekulares Licht habe ich momentan nicht eingebaut, da ich es nicht unbedingt benötige) und an den Fragment-Shader übergeben.
Zudem werden Texturen-Koordinaten (TexPosition) an den Fragment-Shader übergeben.
Zuletzt werden die Vertex-Koordinaten an GL übergeben.

Vertex-Shader:

#define MAX_LAMPS (8)

#define TYPE_SUN (0.0)
#define TYPE_POINT (1.0)
#define TYPE_SPOTLIGHT (2.0)

#define ATTR_POINT_STRENGTH (0)
#define ATTR_SPOTLIGHT_ANGLE (1)
#define ATTR_SPOTLIGHT_HARDNESS (2)

uniform mat4 u_MMatrix;
uniform mat4 u_MVPMatrix;
uniform int u_LLCount;
uniform float u_LLType[MAX_LAMPS];
uniform mat4 u_LLMatrix[MAX_LAMPS];
uniform vec3 u_LLColor[MAX_LAMPS];
uniform vec3 u_LLDirection[MAX_LAMPS];
uniform vec3 u_LLAttributes[MAX_LAMPS];
uniform int u_LOShadeless;
uniform vec3 u_LOAmbient;
uniform vec3 u_LODiffuse;
uniform vec3 u_LOSpecular;

attribute vec4 a_Position;
attribute vec4 a_Color;
attribute vec2 a_TexPosition;
attribute vec3 a_Normal;

varying vec4 v_Color;
varying vec2 v_TexPosition;

float getDiffuse(int lamp) {
if (u_LLType[lamp] == TYPE_SUN) {
vec4 vNormal = u_MMatrix * vec4(a_Normal, 0.0);
vec4 vLamp = u_LLMatrix[lamp] * vec4(u_LLDirection[lamp], 0.0);
float dot = dot(normalize(vNormal), normalize(vLamp));
return max(-dot, 0.0);
}

if (u_LLType[lamp] == TYPE_POINT) {
vec4 vNormal = u_MMatrix * vec4(a_Normal, 0.0);
vec4 vLamp = (u_LLMatrix[lamp] * vec4(0.0, 0.0, 0.0, 1.0)) - (u_MMatrix * a_Position);
float dot = dot(normalize(vNormal), normalize(vLamp));
float cut = max(dot, 0.0);
float distance = length(vLamp);
float att = cut * (1.0 / (1.0 + ((1.0 - min(u_LLAttributes[lamp][ATTR_POINT_STRENGTH], 1.0)) * distance * distance)));
return max(min(att, 1.0), 0.0);
}

if (u_LLType[lamp] == TYPE_SPOTLIGHT) {
//spotlight calculations
}

return 0.0;
}

void main() {
if (u_LOShadeless == 1)
v_Color = a_Color;
else {
vec3 diffuse;

for(int i = 0; i < u_LLCount && i < MAX_LAMPS; i ++)
diffuse += u_LLColor[i] * getDiffuse(i);

v_Color = a_Color * (vec4(u_LODiffuse * diffuse, 1.0) + vec4(u_LOAmbient, 1.0));
}

v_TexPosition = a_TexPosition;
gl_Position = u_MVPMatrix * a_Position;

}

Hat jemand eine Idee, woran das liegen kann?
Liebe Grüße, Aaron

Antworten
Aaron B.
  • Forum-Beiträge: 206

14.05.2015, 12:49:04 via Website

Hier ist nochmal der komplette Code:

https://www.dropbox.com/s/zxvtx7yz4hpo3k3/Maze.zip?dl=0

agf ist das grundlegende "Gestell" für das Spiel
core ist das eigentliche Spiel (unabhängig vom Backend und der Grafik-Engine)
graphicsAndroid enthält den Support für OpenGL ES 2.0
und backendAndroidSurface ist die Android-Anwendung, also das backend

In backendAndroidSurface und core wird eine Library "MazeGS.jar" verwendet. Diese generiert zufällige Labyrinthe.

Die Code-Ausschnitte oben sind aus den Klassen:
-graphicsAndroid/de.ab.maze.graphics.android.gles20.Graphics
-graphicsAndroid/res/raw/vertex.shader & fragment.shader

LG

Antworten
Ju Ku
  • Forum-Beiträge: 72

14.05.2015, 13:10:00 via Website

Wie gut die OpenGL Performance ist, hängt von vielen Faktoren ab, u.a. auch von deinen Models, z.B. von der Anzahl der Polygone, vertex usw.
Du kannst aber die Performance auch nicht mit der einer externen Grafikkarte vergleichen, die ein Computer verwendet.
Bei Android ist OpenGL nicht ganz so schnell, aber 100ms sind vermutlich zu lang.
Ist es überhaupt notwendig, die ganzen Models immer wieder neu in den Graphics cache zu laden?
Ich habe unter Android aber noch nicht so viel in 3D gemacht, weshalb ich dir das jetzt nicht so genau sagen kann.
Normalerweiße lädt man die Models, Textures usw. doch nur 1 mal in den cache und berechnet die dann neu, oder?

— geändert am 14.05.2015, 13:11:27

Antworten
Aaron B.
  • Forum-Beiträge: 206

14.05.2015, 14:24:52 via App

Meine models sind eigentlich nur Rechtecke (also 2 Dreiecke), also sollte das nicht das Problem sein.
Laut dem Tutorial von Google (http://developer.android.com/training/graphics/opengl/draw.html) werden die models immer neu in den cache geladen.
Wenn man sie nur 1mal lädt, werden sie im nächsten Frame nicht erneut gerendert, glaube ich. Aber vllt liege ich da auch falsch...

LG :)

Antworten
Ju Ku
  • Forum-Beiträge: 72

15.05.2015, 16:33:52 via Website

Kannst du die einzelnen OpenGL Aufrufe nicht zusammen fassen?
In http://www.java-forum.org/spiele-und-multimedia-programmierung/143824-android-opengl-render-performance-tipps.html wird zusätzlich noch erwähnt, dass du am besten alles in eine Texture speicherst und diese dann sozusagen unterteilst.

Und statt einen FloatBuffer einen IntBuffer verwendest, da der FloatBuffer anscheinend unter Android mit OpenGL performance Probleme erzeugt.

Antworten
Aaron B.
  • Forum-Beiträge: 206

15.05.2015, 17:17:33 via App

Ju Ku

Kannst du die einzelnen OpenGL Aufrufe nicht zusammen fassen?
In [http://www.java-forum.org/spiele-und-multimedia-programmierung/143824-android-opengl-render-performance-tipps.html][1] wird zusätzlich noch erwähnt, dass du am besten alles in eine Texture speicherst und diese dann sozusagen unterteilst.

[1]: http://www.java-forum.org/spiele-und-multimedia-programmierung/143824-android-opengl-render-performance-tipps.html

Und statt einen FloatBuffer einen IntBuffer verwendest, da der FloatBuffer anscheinend unter Android mit OpenGL performance Probleme erzeugt.

OK danke, sobald ich kann werde ich das mal ausprobieren. :)

Antworten
Ju Ku
  • Forum-Beiträge: 72

15.05.2015, 20:15:06 via Website

Ich würde dir auch immer empfehlen für Games eine Engine zu verwenden, da diese solche Sachen bereits optimieren.
Aber keine Multi platform Engine, die erzeugt eine höhere Last, sondern eine Game Engine für Android, z.B. die AndEngine.
Diese arbeitet mit OpenGL ES 2.0 und bietet viele extensions und plugins, z.B. um Levels aus einer xml File auszulesen oder TileSets zu verwenden.

— geändert am 15.05.2015, 20:34:29

Antworten
Aaron B.
  • Forum-Beiträge: 206

18.05.2015, 14:44:35 via Website

Ok, ich habe das Problem behoben. Es lag nicht an den OpenGL-Aufrufen sondern an synchronized-Blöcken, die ich eingesetzt hatte...

Trotzdem vielen Dank für die Hilfe :)

Eine Game Engine möchte ich nicht verwenden, da ich meine eigene "Engine" besser auf meine Anforderungen anpassen kann. :D

LG

Antworten