Can we use glReadPixels() to read FBO's RenderBuffer's color buffer?

My version is GLES 3.0

This is RenderBuffer’s storage:

glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height) ;

// glDrawArrays() …

Then , i invoke glReadPixels(width, height, GL_RGBA, GL_UNSIGNED_BYTE, mByteBuffer);

I debug mByteBuffer, got this data:

[0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, 
-1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0,
 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0,
 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, +586,656 more]

And it’s black when i show it in ImageView. But if i using Texture attach to FBO, i can get normal result using glReadPixels().

did you set the correct readbuffer before reading the data ?
glReadBuffer(GL_COLOR_ATTACHMENT…)

the gl wiki suggests explicitly to use texture objects if you want to sample from a fbo attachment

Yes , i have used glReadBuffer(GL_COLOR_ATTACHMENT0);
When i use texture object attached to fbo, have no this problem.
Maybe this is a hardware problem , i will test it tomorrow.
Thanks.

My first thought when I saw your post is, this might be a driver bug.

But your “maybe this is a hardware problem” prompted a thought. You said this is GLES 3.0, which is often used for mobile GPUs. Are you running on a mobile GPU? If so, which one?

Why I ask: unlike desktops, mobile GPUs really want to avoid writes to the framebuffer (and reads from it) when possible, as it’s backed by slow DRAM. By using a Texture to back a buffer of a FBO, you’re hinting to the driver you might want to keep that contents around to read it back into a shader in a later render pass. But by using a RenderBuffer to back a buffer of an FBO, there’s no such hint.

That said, AFAIK you should still be able to readback the pixels of an FBO attachment backed by a Render Buffer via glReadPixels() (AFAIK), which should trigger a full pipeline flush in the driver (including FBO buffer writes), possibly mid-frame if needed (expensive). But it should work.

However, maybe your driver is assuming by backing with a Render Buffer you won’t do that, and that it never needs to write out the contents of FBO attachments backed by Render Buffers to main memory, and thus is only keeping the contents around in the tile FB cache during rasterization of a screen tile. Just a thought to explore if you are on mobile. May be a spec violation (driver bug), or there may be something about this in the GLES spec/extensions.

As a test, you could try calling glFinish() before calling glReadPixels() on that RenderBuffer-backed FBO. That should be implicit, but if there’s a driver bug in play…

Also, are you checking for GL errors?

See this post for a possible tie-in:

My device is Huawei mate30, its param is:

CPU : is  2 x Cortex-A76 Based 2.86 GHz + 2 x Cortex-A76 Based 2.09 GHz + 4 x Cortex-A55 1.86 GHz.
GPU: Mali-G76

I used glCheckError(), but it not report Error.

This is the renderer complete code:

public class FBORenderer2 implements GLSurfaceView.Renderer {

   private static final String TAG = "FBORenderer2";

   protected FloatBuffer mVerBuffer = ShaderUtils.floatBuffer(-1.0f,  1.0f,
           -1.0f, -1.0f,
           1.0f, 1.0f,
           1.0f,  -1.0f);
   protected FloatBuffer mTexBuffer = ShaderUtils.floatBuffer(0.0f, 0.0f,
           0.0f,  1.0f,
           1.0f,  0.0f,
           1.0f, 1.0f);

   private Bitmap mBitmap;
   private ByteBuffer mBuffer;

   private final int[] fFrame = new int[1];
   private final int[] fRender = new int[1];
   private final int[] fTexture = new int[1];

   protected int mHPosition;
   protected int mHCoord;
   protected int mHTexture;
   protected int mHMatrix;

   protected int program;

   private Context context;

   private float[] mMatrix = {
           1,0,0,0,
           0,1,0,0,
           0,0,1,0,
           0,0,0,1
   };

   private Callback mCallback;

   public FBORenderer2(Context context){
       this.context = context;
       mMatrix = flip(mMatrix,false,true);
   }

   public static float[] flip(float[] m,boolean x,boolean y){
       if(x||y){
           Matrix.scaleM(m,0,x?-1:1,y?-1:1,1);
       }
       return m;
   }

   public void setBitmap(Bitmap bitmap){
       this.mBitmap = bitmap;
   }

   public void setmCallback(Callback callback){
       this.mCallback = callback;
   }

   @Override
   public void onSurfaceCreated(GL10 gl, EGLConfig config) {
       program = ShaderUtils.createProgram(ShaderUtils.loadFromAssets("base_vertex.vert",context.getResources()),
               ShaderUtils.loadFromAssets("gray_fragment.frag",context.getResources()));
       GLES30.glEnable(GL_DEPTH_TEST);
       mHPosition= GLES30.glGetAttribLocation(program, "vPosition");
       mHCoord=GLES30.glGetAttribLocation(program,"vCoord");
       mHMatrix=GLES30.glGetUniformLocation(program,"vMatrix");
       mHTexture=GLES30.glGetUniformLocation(program,"vTexture");

       // Texture  source Texture
       GLES30.glGenTextures(1, fTexture, 0);
       GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, fTexture[0]);
       GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, mBitmap, 0);

       // RenderBuffer
       GLES30.glGenRenderbuffers(1, fRender, 0);
       GLES30.glBindRenderbuffer(GLES30.GL_RENDERBUFFER, fRender[0]);
       GLES30.glRenderbufferStorage(GLES30.GL_RENDERBUFFER, GLES30.GL_RGBA8, mBitmap.getWidth(), mBitmap.getHeight());
       LogUtils.checkError(TAG,"glRenderbufferStorage");

       // FrameBuffer
       GLES30.glGenFramebuffers(1, fFrame, 0);
       GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, fFrame[0]);
       GLES30.glFramebufferRenderbuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_RENDERBUFFER, fRender[0]);
       LogUtils.checkFrameBuffer(TAG,GLES30.glCheckFramebufferStatus(GL_FRAMEBUFFER));
       GLES30.glReadBuffer(GLES30.GL_COLOR_ATTACHMENT0);
       mBuffer = ByteBuffer.allocate(mBitmap.getWidth() * mBitmap.getHeight() * 4);
   }

   @Override
   public void onSurfaceChanged(GL10 gl, int width, int height) {
       GLES30.glViewport(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
   }

   @Override
   public void onDrawFrame(GL10 gl) {
       if(mBitmap != null && !mBitmap.isRecycled()){

           clear();
           GLES30.glBindFramebuffer(GL_FRAMEBUFFER,fFrame[0]);
           GLES30.glUseProgram(program);

           // set GLSL's variable value
           GLES30.glUniformMatrix4fv(mHMatrix,1,false, mMatrix,0);
           GLES30.glUniform1i(mHTexture,0);
           GLES30.glEnableVertexAttribArray(mHPosition);
           GLES30.glVertexAttribPointer(mHPosition,2, GLES30.GL_FLOAT, false, 0,mVerBuffer);
           GLES30.glEnableVertexAttribArray(mHCoord);
           GLES30.glVertexAttribPointer(mHCoord, 2, GLES30.GL_FLOAT, false, 0, mTexBuffer);

           // bind source Texture
           GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
           GLES30.glBindTexture(GLES30.GL_TEXTURE_2D,fTexture[0]);

           GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP,0,4);

           GLES30.glDisableVertexAttribArray(mHPosition);
           GLES30.glDisableVertexAttribArray(mHCoord);
           GLES30.glReadBuffer(GLES30.GL_COLOR_ATTACHMENT0);
//            GLES30.glFinish();
           GLES30.glReadPixels(0, 0, mBitmap.getWidth(), mBitmap.getHeight(), GLES30.GL_RGBA,
                   GLES30.GL_UNSIGNED_BYTE, mBuffer);
           LogUtils.checkError(TAG,"glReadPixels");

           GLES30.glDeleteTextures(1, fTexture, 0);
           GLES30.glDeleteRenderbuffers(1, fRender, 0);
           GLES30.glDeleteFramebuffers(1, fFrame, 0);

           if(mCallback!=null){
               mCallback.onCall(mBuffer);
               // in onCall(),  execute: Bitmap bitmap=Bitmap.createBitmap(bmp.getWidth(),bmp.getHeight(), Bitmap.Config.ARGB_8888);
           }
           mBitmap.recycle();
           mBuffer.clear();
       }
   }

   private void clear(){
       GLES30.glClearColor(1.0f,1.0f,1.0f,1.0f);
       GLES30.glClear(GL_COLOR_BUFFER_BIT|GLES30.GL_DEPTH_BUFFER_BIT);
   }

   interface Callback{
       void onCall(ByteBuffer data);
   }

}

Then i test it on an virtual machine in Android Studio, the virtual machine’s system image is x86. it encounter the same problem.

So ,by now, i am not sure if it can use glReadPixels() to read FBO’s RenderBuffer’s color buffer for most Android device.

And after check some document and your reply, i quick use this way to pass data.

Thanks.