Framebuffer FBO render to texture is very slow, using OpenGL ES 2.0 on Android, why?
Asked Answered
S

1

6

I am programming an Android 2d game using opengl es 2.0. After I draw my sprites to the backbuffer I draw lights to a FBO and try to blend it to the back buffer again. When I draw the FBO to the framebuffer, even trasparent without any color, the framerates drops from 60 to 30 on a Samsung Galaxy w (it has an adreno 205 as gpu). I searched everywhere and tried everything, even if I draw a single sprite on the scene and blend a trasparent FBO texture to the screen the framerate drops. I tried other games with lighting effects on that phone and they run fine, almost every game is fine on that phone, I believe they use the framebuffer as well. On the Galaxy SII (mali 400 gpu) runs fine, I am quite new to opengl so I believe I am making a mistake somewhere, I share my code.

// Create a framebuffer and renderbuffer
GLES20.glGenFramebuffers(1, fb, offset);
GLES20.glGenRenderbuffers(1, depthRb, offset);

// Create a texture to hold the frame buffer
GLES20.glGenTextures(1, renderTex, offset);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, renderTex[offset]);

GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA,
                    screenWidth, screenHeight, 0,
                    GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE,
                    null);


GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
                       GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
                       GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
                       GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
                       GLES20.GL_LINEAR);

//bind renderbuffer
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, depthRb[offset]);

GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16,
                             screenWidth, screenHeight);

// bind the framebuffer
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fb[offset]);

// specify texture as color attachment
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
                              GLES20.GL_TEXTURE_2D, renderTex[offset], 0);

// specify depth_renderbufer as depth attachment
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
                                 GLES20.GL_RENDERBUFFER, depthRb[0]);

// Check FBO status.
int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);

if ( status == GLES20.GL_FRAMEBUFFER_COMPLETE )
{
    Log.d("GLGame framebuffer creation", "Framebuffer complete");
}


// set default framebuffer
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

I do this once on surface creation. Not sure if is correct. I keep the texture and framebuffer ids to switch to them when I need. My drawing code:

GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
ShaderProgram program = glgame.getProgram();

//put vertices in the floatbuffer
mTriangleVertices.put(vertices, 0, len);
mTriangleVertices.flip();

GLES20.glVertexAttribPointer(program.POSITION_LOCATION, 2, GLES20.GL_FLOAT, false,
                             TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);

//preparing parameter for texture position
mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
GLES20.glEnableVertexAttribArray(program.POSITION_LOCATION);

//preparing parameter for texture coords
GLES20.glVertexAttribPointer(program.TEXTURECOORD_LOCATION, 2, GLES20.GL_FLOAT,
                             false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES,
                             mTriangleVertices);

//set projection matrix
GLES20.glEnableVertexAttribArray(program.TEXTURECOORD_LOCATION);
GLES20.glUniformMatrix4fv(program.MATRIX_LOCATION, 1, false, matrix, 0);

//draw triangle with indices to form a rectangle
GLES20.glDrawElements(GLES20.GL_TRIANGLES, numSprites * 6, GLES20.GL_UNSIGNED_SHORT,
                      indicesbuf);

//clear buffers
mTriangleVertices.clear();
mVertexColors.clear();

Everything is rendered on screen correctly, but the performance are ruined just when I draw the FBO texture. Thank you very much for your help. I worked very hard on this and didn't find a solution.

Schmit answered 23/5, 2012 at 23:38 Comment(4)
Late answer, but I'm doing something similar on an HD2 under ICS and it's pretty much the same. I can render anything with no problem at 60fps, but when I use an FBO, frame rate gets down to 45, and if I use another FBO it goes down to 30.Domestic
Hmm... I've seen some games on the Samsung Galaxy w running without problems and they seemed to use a framebuffer for 2d lights, my code runs slowly instead. I wonder if I am making mistakes.Schmit
galaxy uses sgx chipset. like i said before its related to small gmem and tiled architecture, every time u change framebuffer old one need to get copied to normal mem, if you will clear it away as a first call after bind it does not need to be copied back from normal mem to gmem, thats why framebuffers are slow especially on adreno 200 :) you can check is app using framebuffers with adreno profiler if you have adreno device, just plug it in and download current frame, then check for fb in textures. if you will find one you will be sure,Scopolamine
and on HD2 a lot depends on driver used in your build, i have a 2.3.3 build and every fb bind is recognized as blocking resolve, and same code on ics, is recognized as non blocking resolve and run about 5-10 fps faster. I think it may be related to fact that qualcomm opened their drivers for devs some time after ics launch.Scopolamine
S
14

According to qualcomm docs, you need to glclear after every glbindframebuffer, this is a problem related to tiled architecture, if you are switching framebuffers, data need to get copied from fastmem to normal memory to save current framebuffer and from slowmem to fast mem to get contents of newly binded frame, in case you are clearing just after glbind no data is copied from slowmem to fastmem and you are saving time, but you need to redesign your render pipeline often, so it will avoid reading data back and forth between slow and fast memory, so try to do glclear after each bind and it should help, you can also use adreno profiler to get additional information about problematic calls, but i doubt it will help with adreno200 i am trying to get two buffers for blur and i am ending with 10fps, bindframebuffer call can take up to 20msec if its not cleared, if it is it should end at 2ms.

Scopolamine answered 15/6, 2012 at 14:13 Comment(7)
I will try this as soon as I can get my hands again on the adreno 205. I found qualcomm documentation and I will follow it. Thank you very much for your help!Schmit
Here is the qualcomm doc.Mcgean
If you want to preserve the contents of the framebuffer, just do glClear(0);, which also seems to fix the problem.Elbertelberta
Thanks for this answer. Is this qualcomm doc available any more ?Agglutinin
@Elbertelberta glClear(0) won't prevent copying from slow mem to fast mem. How does that help in speed ?Agglutinin
@Kiran: I don't claim to understand, I was just relaying information from the docs. The PDF seems to be gone from Qualcomm's site, and neither the Google cache nor archive.org seem to have a copy. Boo :(Elbertelberta
Well it looks like it does :). Just check it. And its quite logical since if first step after copying data to fmem is to erase it then you do not need to copy anything. The best answer is to benchmark it, and if you will do so then you will se that it speed things up.Scopolamine

© 2022 - 2024 — McMap. All rights reserved.