glReadPixels is slow
我正在使用GLSurfaceView创建一个自定义相机预览,使用OpenGl渲染相机给我的帧。我已经完全实现了摄像头,并且正在工作,我希望摄像头能在没有fps损失和正确的宽高比等情况下正常工作。但是问题出在我需要捕获来自摄像头提要的帧时,我的第一个念头是使用glReadPixles ()
使用GLES20.glReadPixels(),我发现某些设备会遭受fps损失,这主要是因为屏幕分辨率更高,这是有道理的,因为glReadPixels需要以更高的分辨率读取更多像素。
我做了一些挖掘,发现其他人在glReadPixels上也有类似的问题,许多人建议使用PBO,最好将其中两个用作双缓冲区,这样我就可以读取像素数据而不会阻塞/滞留当前的渲染过程。我完全了解双重缓冲的概念,对于OpenGL来说我还很陌生,需要一些有关如何使双重缓冲PBO工作的指导。
我已经找到了一些有关PBO双缓冲的解决方案,但是我找不到一个完整的解决方案来完全理解它与GLES的相互作用。
我对GLSurfaceView.Renderer.onDrawFrame()的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // mBuffer and mBitmap are declared and allocated outside of the onDrawFrame Method // Buffer is used to store pixel data from glReadPixels 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); // Read pixels from the current GLES context GLES10.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mBuffer); // Copy the Pixels from the buffer mBitmap.copyPixelsFromBuffer(mBuffer); GLES20.glUseProgram(0); |
经过大量的研究和挖掘,我找到了glReadPixels的解决方案以及如何使用PBO缓冲图像/帧以供以后处理。
因此,我们要做的第一件事是在GLES2中公开一个附加功能。
在您的应用程序模块中,添加一个名为cpp的新目录,然后创建一个名为GlesHelper的新c文件(或者您想要的名称)
并粘贴以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 | #include <jni.h> #include <GLES2/gl2.h> JNIEXPORT void JNICALL // Change Java_com_your_full_package_name_helper_GlesHelper_glReadPixels(JNIEnv *env, jobject instance, jint x, jint y, jint width, jint height, jint format, jint type) { // TODO glReadPixels(x, y, width, height, format, type, 0); } |
然后,您需要将CMakeFile添加到项目的根目录。右键单击新文件,输入CMakeLists.txt
并粘贴以下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | cmake_minimum_required(VERSION 3.4.1) add_library( # Specifies the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main//cpp//GlesHelper.c ) target_link_libraries( # Specifies the target library. native-lib # Links the target library to the log library # included in the NDK. ${log-lib} GLESv2) |
现在打开您的应用程序/模块的build.gradle文件
将此粘贴到Gradle文件的android.defaultConfig部分中
1 2 3 4 5 6 7 8 | externalNativeBuild { // Encapsulates your CMake build configurations. cmake { // Provides a relative path to your CMake build script. cppFlags"-std=c++11 -fexceptions" arguments"-DANDROID_STL=c++_shared" } } |
然后将其粘贴到Gradle文件的android部分中
1 2 3 4 5 6 7 8 | externalNativeBuild { // Encapsulates your CMake build configurations. cmake { // Provides a relative path to your CMake build script. path"CMakeLists.txt" } } |
这就是所有MakeFile和c的所有设置,让我们移至某些Java
在您的项目中创建一个与C文件中的包匹配的新文件,即
com_your_full_package_name_helper = com.your.full.package.name.helper
确保它们正确匹配,与类名和函数名相同。
所以你的班级应该看起来像这样
1 2 3 4 5 6 | package com.your.full.package.name.helper; public class GlesHelper { public static native void glReadPixels(int x, int y, int width, int height, int format, int type); } |
因为我们已经向项目添加了本机代码,所以我们需要使用System.loadLibrary(" native-lib")来加载新方法。
在开始下一个步骤之前,请将这些成员变量添加到渲染器中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /** * The PBO Ids, increase the allocate amount for more PBO's * The more PBO's the smoother the frame rate (to an extent) * Side affect of having more PBO's the frames you get from the PBO's will lag behind by the amount of pbo's */ private IntBuffer mPboIds = IntBuffer.allocate(2);; /** * The current PBO Index */ private int mCurrentPboIndex = 0; /** * The next PBO Index */ private int mNextPboIndex = 1; |
所以现在我们需要初始化我们的PBO,这很简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // Generate the buffers for the pbo's GLES30.glGenBuffers(mPboIds.capacity(), mPboIds); // Loop for how many pbo's we have for (int i = 0; i < mPboIds.capacity(); i++) { // Bind the Pixel_Pack_Buffer to the current pbo id GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPboIds.get(i)); // Buffer empty data, capacity is the width * height * 4 GLES30.glBufferData(GLES30.GL_PIXEL_PACK_BUFFER, capacity, null, GLES30.GL_STATIC_READ); } // Reset the current buffer so we can draw properly GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0); |
然后,在我们开始绘制调用此方法之前,它将把像素数据读入pbo,交换缓冲区,并让您访问像素数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | /** * Reads the pixels from the PBO and swaps the buffers */ private void readPixelsFromPBO() { // Bind the current buffer GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPboIds.get(mCurrentPboIndex)); // Read pixels into the bound buffer GlesHelper.glReadPixels(0, 0, mViewWidth, mViewHeight, GLES20.GL_RGBA, GLES30.GL_UNSIGNED_BYTE); // Bind the next buffer GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPboIds.get(mNextPboIndex)); // Map to buffer to a byte buffer, this is our pixel data ByteBuffer pixelsBuffer = (ByteBuffer) GLES30.glMapBufferRange(GLES30.GL_PIXEL_PACK_BUFFER, 0, mViewWidth * mViewHeight * 4, GLES30.GL_MAP_READ_BIT); if(mSkipFirstFrame) { // Skip the first frame as the PBO's have nothing in them until the second render cycle } // Set skip first frame to true so we can begin frame processing mSkipFirstFrame = true; // Swap the buffer index mCurrentPboIndex = (mCurrentPboIndex + 1) % mPboIds.capacity(); mNextPboIndex = (mNextPboIndex + 1) % mPboIds.capacity(); // Unmap the buffers GLES30.glUnmapBuffer(GLES30.GL_PIXEL_PACK_BUFFER); GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, GLES20.GL_NONE); GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES20.GL_NONE); } |
所以回到我最初的问题,我们的Redner / onDrawMethod看起来像这样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // Use the OpenGL Program for rendering GLES20.glUseProgram(mProgram); // If the Texture Matrix is not null if (textureMatrix != null) { // Apply the Matrix GLES20.glUniformMatrix4fv(mTexMatrixLoc, 1, false, textureMatrix, 0); } // Apply the Matrix GLES20.glUniformMatrix4fv(mMVPMatrixLoc, 1, false, mMvpMatrix, 0); // Bind the Texture GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureID); // Draw the texture GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, GLConstants.VERTEX_NUM); // Unbind the Texture GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); // Read from PBO readPixelsFromPBO() |
我希望这可以帮助对glReadPixels的性能有类似问题或至少难以实现PBO的人