GLSurfaceView相机预览:glReadPixels卡顿与双缓冲PBO实现求助
Hey Jake, props on getting your custom camera preview running flawlessly with no frame loss and correct aspect ratio—That’s already a solid foundation! Let’s dive into fixing that frame rate hit from glReadPixels() on high-resolution devices using double-buffered Pixel Buffer Objects (PBOs). These will let the GPU handle pixel transfers asynchronously, so your rendering thread doesn’t get blocked waiting for pixel data.
First, Why PBOs Solve the Problem
glReadPixels() is blocking by default: it waits until the GPU has copied all pixel data to your client-side buffer before letting the rendering thread proceed. Double-buffered PBOs split this work into two parallel streams: while the GPU is writing pixel data to one PBO, your CPU can process the data from the other PBO. This eliminates the blocking wait and keeps your frame rate smooth.
Step-by-Step Implementation
1. Add PBO Member Variables
First, declare these in your renderer class to manage the two PBOs:
private int[] mPboIds = new int[2]; // Two PBOs for double buffering private int mCurrentPboIndex = 0; // Tracks which PBO to write to next private ByteBuffer mPboBuffer; // Reusable buffer for copying PBO data
2. Initialize the PBOs
Call this method once (e.g., in onSurfaceCreated() or your OpenGL setup code) after you know the preview width/height:
private void initPbos(int width, int height) { // Generate two PBO IDs GLES20.glGenBuffers(2, mPboIds, 0); // Calculate buffer size: RGBA = 4 bytes per pixel int bufferSize = width * height * 4; for (int i = 0; i < 2; i++) { GLES20.glBindBuffer(GLES20.GL_PIXEL_PACK_BUFFER, mPboIds[i]); // Allocate GPU memory for the PBO (stream draw hint since we write/read frequently) GLES20.glBufferData(GLES20.GL_PIXEL_PACK_BUFFER, bufferSize, null, GLES20.GL_STREAM_READ); GLES20.glBindBuffer(GLES20.GL_PIXEL_PACK_BUFFER, 0); } // Initialize reusable client buffer for copying from PBO mPboBuffer = ByteBuffer.allocateDirect(bufferSize); mPboBuffer.order(ByteOrder.nativeOrder()); }
3. Modify onDrawFrame() to Use PBOs
Replace your existing pixel reading code with this. The key is alternating between the two PBOs to keep CPU/GPU work parallel:
@Override public void onDrawFrame(GL10 gl) { // Your existing rendering code (unchanged) mBuffer.rewind(); GLES20.glUseProgram(hProgram); if (tex_matrix != null) { GLES20.glUniformMatrix4fv(muTexMatrixLoc, 1, false, tex_matrix, 0); } GLES20.glUniformMatrix4fv(muMVPMatrixLoc, 1, false, mMvpMatrix, 0); GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex_id); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, GLConstants.VERTEX_NUM); GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); // --- PBO-based pixel reading starts here --- int width = ...; // Your preview width int height = ...; // Your preview height // Step 1: Bind the current PBO to receive pixel data GLES20.glBindBuffer(GLES20.GL_PIXEL_PACK_BUFFER, mPboIds[mCurrentPboIndex]); // Step 2: Read pixels INTO the PBO (last parameter is null—data goes to GPU buffer) GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null); // Step 3: Switch to the other PBO for next frame int readPboIndex = mCurrentPboIndex; mCurrentPboIndex = (mCurrentPboIndex + 1) % 2; // Step 4: Bind the PBO with the ready pixel data to read from GLES20.glBindBuffer(GLES20.GL_PIXEL_PACK_BUFFER, mPboIds[readPboIndex]); // Step 5: Map the PBO's GPU memory to client-side memory (non-blocking if possible) mPboBuffer.rewind(); ByteBuffer mappedBuffer = (ByteBuffer) GLES20.glMapBufferRange( GLES20.GL_PIXEL_PACK_BUFFER, 0, width * height * 4, GLES20.GL_MAP_READ_BIT | GLES20.GL_MAP_UNSYNCHRONIZED_BIT ); if (mappedBuffer != null) { // Copy data from mapped PBO buffer to your Bitmap mPboBuffer.put(mappedBuffer); mPboBuffer.rewind(); mBitmap.copyPixelsFromBuffer(mPboBuffer); // Unmap the buffer to release GPU resources GLES20.glUnmapBuffer(GLES20.GL_PIXEL_PACK_BUFFER); } // Unbind the PBO to avoid interfering with other operations GLES20.glBindBuffer(GLES20.GL_PIXEL_PACK_BUFFER, 0); // --- PBO-based pixel reading ends here --- GLES20.glUseProgram(0); }
4. Clean Up PBOs
Don’t forget to release the PBO memory when your surface is destroyed (in onSurfaceDestroyed()):
@Override public void onSurfaceDestroyed(GL10 gl) { if (mPboIds != null) { GLES20.glDeleteBuffers(2, mPboIds, 0); mPboIds = null; } }
Key Notes for Success
- Buffer Size Match: Ensure the PBO size exactly matches
width * height * 4(since we’re using RGBA format). Mismatched sizes will cause crashes or corrupted data. - Map Flags: Using
GL_MAP_UNSYNCHRONIZED_BITcan help with performance, but if you see corrupted frames, remove it to force synchronization (it’s safer but slightly slower). - Thread Safety: If you’re processing the Bitmap on a separate thread, make sure to sync access to
mBitmapto avoid race conditions.
This setup should eliminate the frame rate drop from glReadPixels() because the CPU is processing old pixel data while the GPU writes new data to the other PBO—no more blocking waits!
内容的提问来源于stack exchange,提问作者Jake Wade

