解决android opengl glReadPixels 慢的问题一

本文探讨了Android OpenGL中glReadPixels操作慢的问题,原因是GPU和CPU内存分离导致的数据拷贝效率低下。作者分享了通过使用PBO(Pixel Buffer Object)来改善这一状况的心得,包括初始化纹理、设置PBO、Surface创建和利用PBO进行绘制的步骤。然而,单个PBO的效果提升不显著,建议使用两个PBO交错接收以提高效率。
部署运行你感兴趣的模型镜像

解决android opengl glReadPixels 慢的问题

由于android的gpu跟cpu内存是分离的,导致数据拷贝的过程中会非常慢,网上资料相对较少,下面是我解决这个问题的一点心的,使用PBO代码如下


1.  初始化纹理方法

public static int loadTexture(final Bitmap img, final int usedTexId, int i) {
		if(img == null)
			return NO_TEXTURE;
		
//		ByteBuffer img_buffer = ByteBuffer.allocate(img.getByteCount());
//		img.copyPixelsToBuffer(img_buffer);
		ByteBuffer pbo_point = null;
        int textures[] = new int[1];
        if (usedTexId == NO_TEXTURE) {
            GLES30.glGenTextures(1, textures, 0);
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]);
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                    GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                    GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR);
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                    GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
                    GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
            GLES30.glGenBuffers(1, pbo);
            GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, pbo.get(0));
            GLES30.glBufferData(GLES30.GL_PIXEL_PACK_BUFFER,width*height*4, null, GLES30.GL_DYNAMIC_READ);
            GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0);
             GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, img, 0);
        } 
        else {
        	  GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, usedTexId);
//        	  GLUtils.texSubImage2D(GLES30.GL_TEXTURE_2D, 0, 0, 0, img);
              textures[0] = usedTexId;
        }
        return textures[0];
    }

2. 初始化PBO




public static int loadFramebuffer(int texture, int width,int height){
	
		IntBuffer framebuffer = IntBuffer.allocate(1);
		IntBuffer depthRenderbuffer = IntBuffer.allocate(1);
		IntBuffer max = IntBuffer.allocate(10);
		int temp = 0;
		GLES30.glGetIntegerv(GLES30.GL_MAX_RENDERBUFFER_SIZE, max);
		temp = max.get(0);
		if (temp <= width && temp <= height)
		{
			return -1;
		}
		GLES30.glGenFramebuffers(1, framebuffer);
		GLES30.glGenRenderbuffers(1, depthRenderbuffer);
		GLES30.glBindRenderbuffer(GLES30.GL_RENDERBUFFER, depthRenderbuffer.get(0));
		GLES30.glRenderbufferStorage(GLES30.GL_RENDERBUFFER, GLES30.GL_RGBA, width, height);
		
		GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, framebuffer.get(0));
		GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, texture, 0);
		GLES30.glFramebufferRenderbuffer(GLES30.GL_FRAMEBUFFER,GLES30.GL_DEPTH_ATTACHMENT, GLES30.GL_RENDERBUFFER, depthRenderbuffer.get(0));
		
		int status = 0;
		status = GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER);
		if (status == GLES30.GL_FRAMEBUFFER_COMPLETE){
			Log.d(TAG,"frame buffer init ok!!!!!!!");
			GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
			GLES30.glDeleteRenderbuffers(1, depthRenderbuffer);
			GLES30.glDeleteFramebuffers(1, framebuffer);
			return framebuffer.get();
		}
		Log.d(TAG,"frame buffer init file!!!!!!!");
		return -1;
		
	}

3.  surface creat 初始化

public void onSurfaceCreated ( GL10 glUnused, EGLConfig config )
   {
      String vShaderStr =
    		  "attribute vec4 position;\n" +
            "attribute vec4 inputTextureCoordinate;\n" +
            " \n" +
            "varying vec2 textureCoordinate;\n" +
            " \n" +
            "void main()\n" +
            "{\n" +
            "    gl_Position = position;\n" +
            "    textureCoordinate = inputTextureCoordinate.xy;\n" +
            "}";           

      String fShaderStr =
          "precision highp float;					  	\n"
		 + "varying highp vec2 textureCoordinate;\n"
		 +  "uniform sampler2D inputImageTexture;\n"
         + "void main()                                  \n"
         + "{                                            \n"
         + "  gl_FragColor = texture2D(inputImageTexture, textureCoordinate);	\n"
         + "}                                            \n";

      int vertexShader;
      int fragmentShader;
      int programObject;
      int[] linked = new int[1];

      // Load the vertex/fragment shaders
      vertexShader = LoadShader ( GLES30.GL_VERTEX_SHADER, vShaderStr );
      fragmentShader = LoadShader ( GLES30.GL_FRAGMENT_SHADER, fShaderStr );

      // Create the program object
      programObject = GLES30.glCreateProgram();

      if ( programObject == 0 )
      {
         return;
      }

      GLES30.glAttachShader ( programObject, vertexShader );
      GLES30.glAttachShader ( programObject, fragmentShader );

      // Bind vPosition to attribute 0
      GLES30.glBindAttribLocation ( programObject, 0, "vPosition" );

      // Link the program
      GLES30.glLinkProgram ( programObject );

      // Check the link status
      GLES30.glGetProgramiv ( programObject, GLES30.GL_LINK_STATUS, linked, 0 );

      if ( linked[0] == 0 )
      {
    	 Log.e ( TAG, vShaderStr );
    	 Log.e ( TAG, fShaderStr );
         Log.e ( TAG, "Error linking program:" );
         Log.e ( TAG, GLES30.glGetProgramInfoLog ( programObject ) );
         GLES30.glDeleteProgram ( programObject );
         return;
      }

      // Store the program object
      mProgramObject = programObject;
       mGLAttribPosition = GLES30.glGetAttribLocation(programObject, "position");
       mGLUniformTexture = GLES30.glGetUniformLocation(programObject, "inputImageTexture");
       mGLAttribTextureCoordinate = GLES30.glGetAttribLocation(programObject,
               "inputTextureCoordinate");
       Log.d(TAG,"id = " + color_id + "mGLUniformTexture= " + mGLUniformTexture);
     // GLES30.glClearColor ( 1.0f, 1.0f, 1.0f, 0.0f );

   }

4. 最后使用pbo 画出来

public void onDrawFrame ( GL10 glUnused )
   {
	  index++;
	  mTextureId = loadTexture(bitmap,mTextureId,0);
      // Set the viewport
          GLES30.glViewport ( 0, 0, width ,height);

      // Clear the color buffer
      GLES30.glClearColor(0.0f,0.0f,0.0f,0.0f);
      GLES30.glClear ( GLES30.GL_COLOR_BUFFER_BIT );
      // Use the program object
      GLES30.glUseProgram ( mProgramObject );


      mVertices.position(0);
      GLES30.glVertexAttribPointer ( mGLAttribPosition, 3, GLES30.GL_FLOAT, false, 0, mVertices );
      GLES30.glEnableVertexAttribArray ( mGLAttribPosition );
      
      mGLTextureBuffer.position(0);
      GLES30.glVertexAttribPointer ( mGLAttribTextureCoordinate, 2, GLES30.GL_FLOAT, false, 0, mGLTextureBuffer );
      GLES30.glEnableVertexAttribArray ( mGLAttribTextureCoordinate );
     
//      
      if (mTextureId >= 0)
      {
    	  //loadFramebuffer(mTextureId,width,height);
	      GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
	      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureId);
	      GLES30.glUniform1i(mGLUniformTexture, 0);
	      
      }
      GLES30.glDrawArrays ( GLES30.GL_TRIANGLE_STRIP, 0, 4 );
       Log.d("jni", "刷新时间");
      return;
   }

总结:虽然使用一个PBO但是效果改变不是很明显,因为单个PBO传输还是需要等待的,假如使用2个pbo,这样错开接收就会使效率大大提高,请看下篇使用2个PBO速度慢的问题。

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

<think> 我们正在讨论的是在Android OpenGL环境中捕获单独帧Bitmap的实现方法。根据引用[3]提到的离屏渲染和引用[4]中关于SurfaceTexture和OpenGL纹理的内容,可以总结出以下步骤: ### 关键步骤 1. **创建离屏渲染环境**:使用EGL创建离屏Surface(如Pbuffer Surface)作为渲染目标[^3]。 2. **绑定帧缓冲区**:创建FBO(Frame Buffer Object)并绑定纹理作为颜色附件。 3. **执行渲染**:将OpenGL场景渲染到FBO上。 4. **读取像素数据**:使用`glReadPixels`读取FBO中的像素数据。 5. **生成Bitmap**:将像素数据转换为Android的Bitmap对象。 ### 详细实现 #### 1. 初始化离屏EGL环境 首先需要创建离屏EGL环境,代码结构如下: ```java // 创建离屏EGL上下文和Surface EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); EGLConfig eglConfig = ...; // 选择合适配置 EGLContext eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, ...); // 创建Pbuffer Surface int[] attribList = {EGL14.EGL_WIDTH, width, EGL14.EGL_HEIGHT, height, EGL14.EGL_NONE}; EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, attribList, 0); EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext); ``` #### 2. 创建帧缓冲对象(FBO) ```java int[] fboIds = new int[1]; GLES30.glGenFramebuffers(1, fboIds, 0); GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, fboIds[0]); // 创建纹理附件 int[] textureIds = new int[1]; GLES30.glGenTextures(1, textureIds, 0); GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIds[0]); GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, width, height, 0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null); GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, textureIds[0], 0); // 检查FBO状态 if (GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER) != GLES30.GL_FRAMEBUFFER_COMPLETE) { throw new RuntimeException("FBO setup failed"); } ``` #### 3. 执行渲染操作 渲染到FBO而非默认帧缓冲区: ```java // 绑定自定义FBO GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, fboIds[0]); // 执行OpenGL渲染命令 GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT); // ... 绘制场景 ... ``` #### 4. 读取像素数据并生成Bitmap ```java ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4); buffer.order(ByteOrder.nativeOrder()); buffer.position(0); // 从当前绑定的FBO读取RGBA数据 GLES30.glReadPixels(0, 0, width, height, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, buffer); // 转换为Bitmap(注意OpenGL坐标原点在左下角,需垂直翻转) Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); buffer.rewind(); bitmap.copyPixelsFromBuffer(buffer); // 垂直翻转处理 Matrix matrix = new Matrix(); matrix.postScale(1, -1); // 垂直翻转 Bitmap flippedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true); bitmap.recycle(); ``` ### 注意事项 - **性能开销**:每帧捕获会导致GPU-CPU数据传输,频繁操作影响性能[^3]。 - **线程安全**:需在OpenGL渲染线程执行。 - **纹理格式匹配**:`glReadPixels`的格式参数需与纹理创建格式致(通常为`GL_RGBA`)。 - **坐标翻转**:OpenGL的坐标系原点在左下角,而Android Bitmap坐标系原点在左上角,需垂直翻转。 ### 优化建议 - **异步处理**:将像素读取操作放入独立线程,避免阻塞渲染。 - **降低分辨率**:按需缩小捕获尺寸(如原图1/4)。 - **复用资源**:复用FBO和纹理对象,避免重复创建。 §§相关问题§§ 1. 如何避免捕获帧时的垂直翻转操作? 2. 除FBO外,还有哪些离屏渲染技术可用? 3. 如何实现高效率的连续帧捕获(屏幕录像)? </think> ### Android OpenGL捕获单帧Bitmap实现方法 在Android OpenGL中捕获单帧Bitmap的核心是通过**离屏渲染(FBO)** 和 **像素读取** 实现的,主要步骤如下: #### 1. **创建帧缓冲对象(FBO)** ```java // 生成FBO和纹理 int[] fboIds = new int[1]; int[] textureIds = new int[1]; GLES30.glGenFramebuffers(1, fboIds, 0); GLES30.glGenTextures(1, textureIds, 0); // 绑定纹理 GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIds[0]); GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, width, height, 0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null); // 绑定FBO并将纹理附加为颜色附件 GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, fboIds[0]); GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, textureIds[0], 0); ``` [^3] #### 2. **渲染到FBO** ```java // 设置视口大小 GLES30.glViewport(0, 0, width, height); // 执行渲染命令(顶点着色器+片元着色器) drawScene(); // 自定义渲染方法 // 确保渲染完成 GLES30.glFinish(); ``` #### 3. **读取像素数据** ```java // 分配缓冲区 ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4); buffer.order(ByteOrder.nativeOrder()); // 从FBO读取RGBA数据 GLES30.glReadPixels(0, 0, width, height, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, buffer); ``` #### 4. **生成Bitmap(需垂直翻转)** ```java // 创建Bitmap并填充数据 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); bitmap.copyPixelsFromBuffer(buffer); // 垂直翻转(OpenGL坐标系原点在左下角) Matrix matrix = new Matrix(); matrix.postScale(1, -1, width/2f, height/2f); // Y轴翻转 Bitmap finalBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true); ``` #### 5. **清理资源** ```java // 解绑FBO并删除资源 GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0); GLES30.glDeleteFramebuffers(1, fboIds, 0); GLES30.glDeleteTextures(1, textureIds, 0); ``` ### 关键注意事项 1. **线程安全**:必须在OpenGL渲染线程执行 2. **性能优化**: - 复用FBO对象避免重复创建 - 使用`PBO(Pixel Buffer Object)`异步读取像素 - 降低分辨率(如原图的1/4) 3. **格式匹配**: - Bitmap使用`ARGB_8888`格式(4字节/像素) - OpenGL使用`GL_RGBA`格式 4. **坐标系转换**:必须执行垂直翻转操作[^4] ### 完整调用示例 ```java public Bitmap captureFrame(int width, int height) { // 1. 创建FBO和纹理 setupFBO(width, height); // 2. 渲染场景 renderScene(); // 3. 读取像素并生成Bitmap Bitmap rawBitmap = readPixels(width, height); // 4. 垂直翻转 return flipBitmapVertically(rawBitmap); } ``` > **内存计算**:捕获张1080x1920的Bitmap,内存占用为: > $$1080 \times 1920 \times 4 = 8,294,400 \text{ bytes} \approx 7.9 \text{ MB}$$ > 与`getByteCount()`返回值致[^2]
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值