37.4 OpenGL离屏渲染环境配置

EGL环境配置

做离屏渲染环境配置的技术选型,主要会讲到两种方案,一种是EGL环境配置,另一种是OpenGL FBO的使用。

  1. 获取默认的EGLDisplay
  // 共享的EGLContext, 如果当前线程已有EGL环境, 则获取当前EGLContext, 否则获取的是EGL14.EGL_NO_CONTEXT, 是个Empty对象, 不为null
  EGLContext shareContext = EGL14.eglGetCurrentContext();
  // 获取EGLDisplay
  EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
  1. 初始化EGLDisplay
  int[] version = new int[2];
  if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
    ...
  }
  1. 选择EGLConfig 在创建EGLContext或者EGLSurface之前,我们首先要选择一个EGLConfig,因为EGLConfig是创建EGLContextEGLSurface的必要参数。那为什么要说”选择”而不是”创建”呢?那是因为我们并没有办法构造EGLConfig,只能通过API让系统返回一个早已创建好的EGLConfig的实例。
  // 获取当前线程的EGLDisplay
  EGLDisplay currentDisplay = EGL14.eglGetCurrentDisplay();
  int eglContextClientVersion = 2;
  if (!EGL14.EGL_NO_DISPLAY.equals(currentDisplay)) {
  	int[] eglVersion = new int[1]; // 2 or 3
      // 查询当前EGLContext使用的EGL Version
  	EGL14.eglQueryContext(currentDisplay, sharedContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, eglVersion, 0);
      eglContextClientVersion = eglVersion[0];
  }
  // 指定renderableType,根据eglContextClientVersion选择使用OpenGL ES 2.0或者3.0
  // 可能由于minSDKLevel限制,EGLExt不能调用,可以自己声明int值一样的常量来代替EGLExt.EGL_OPENGL_ES3_BIT_K
  final int renderableType = eglContextClientVersion == 2 ? EGL14.EGL_OPENGL_ES2_BIT : EGLExt.EGL_OPENGL_ES3_BIT_K;
  // 指定EGLSurface所用的RGBA,Depth和Stencil占多少bits,按需求修改
  int[] attribList = new int[]{
          EGL14.EGL_RED_SIZE, 8,
          EGL14.EGL_GREEN_SIZE, 8,
          EGL14.EGL_BLUE_SIZE, 8,
          EGL14.EGL_ALPHA_SIZE, 8,
          EGL14.EGL_DEPTH_SIZE, 0,
          EGL14.EGL_STENCIL_SIZE, 0,
          EGL14.EGL_RENDERABLE_TYPE, renderableType,
          // EGLExt.EGL_RECORDABLE_ANDROID, 1, // 硬编码合成视频时要将该位置为1
          EGL14.EGL_NONE // 参数结束标记,类似于EOF,一定要加上,否则解析会抛异常
  };
  // 存放系统选择的EGLConfig的数组
  EGLConfig[] configs = new EGLConfig[1];
  // 存放系统返回的EGLConfig数量
  int[] numConfigs = new int[1];
  EGL14.eglChooseConfig(eglDisplay, attribList, 0, configs, 0, configs.length,
          numConfigs, 0);
  // 检测eglChooseConfig是否失败
  int error;
  if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
      throw new RuntimeException("Choose EGLConfig failed: " + GLUtils.getEGLErrorString(error));
  }
  1. 创建EGLContext

创建EGLContext时用到了sharedContext, 在sharedContext不为EGL14.EGL_NO_CONTEXT不是null时, 我们创建的EGLContext就会能与sharedContext共享texture等信息

  EGLConfig eglConfg = configs[0];
  // 创建EGLContext,参数只需要设置EGL_CONTEXT_CLIENT_VERSION
  int[] contextAttribList = {
          EGL14.EGL_CONTEXT_CLIENT_VERSION, eglContextClientVersion,
          EGL14.EGL_NONE
  };
  EGLContext eglContext = EGL14.eglCreateContext(eglDisplay, configs[0], sharedContext, contextAttribList, 0);
  ...
  1. 创建EGLSurface
  // 指定离屏渲染的EGLSurface宽高
  int[] surfaceAttribList =  {
           EGL14.EGL_WIDTH, width,
           EGL14.EGL_HEIGHT, height,
           EGL14.EGL_NONE
  };
  EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttribList, 0);
  ...
  1. 使用场景

既要离屏渲染又要创建EGL环境的场景呢?

再举个例子,我们用GLSurfaceView在屏幕上显示一张纹理(texture),我们希望增加一个分享功能,分享出去的图片要带上app的水印。这就可以考虑在分享的时候,新建一条线程,将GLSurfaceViewEGLContext作为sharedContext,在新线程里创建EGL离屏渲染的环境,接着使用OpenGL依次绘制GLSurfaceView上显示的texture和水印texture,最后通过GLES20.glReadPixels将加了水印的图片读取出来并分享。

OpenGL FBO

  1. 创建FBO
  int[] fboHolder = new int[1];
  GLES20.glGenFrameBuffers(1, holder, 0);
  int fbo = fboHolder[0];
  1. 生成纹理
  int[] tex = new int[1];
  // 创建纹理
  GLES20.glGenTextures(1, tex, 0);
  // 绑定纹理target为GL_TEXTURE_2D
  GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tex[0]);
  // 纹理参数配置
  GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
          GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
  GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
          GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
  GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
          GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
  GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
          GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
  GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
  ...
  1. 挂在纹理到FBO
  //绑定FBO
  GLES20.glBindFrameBuffer(GLES20.GL_FRAMEBUFFER, fbo);
  // 绑定texture
  GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture);
  // 将texture挂在到FBO的COLOR_ATTACHMENT0上
  GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

  1. 使用FBO
  // 绑定fbo
  GLES20.glBindFrameBuffer(GLES20.GL_FRAMEBUFFER, fbo);
  ByteBuffer byteBuffer = ByteBuffer.allocateDirect(width * height * 4).order(ByteOrder.nativeOrder());
  byteBuffer.position(0);
  //读取纹理像素数据到byteBuffer中
  GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, byteBuffer);
  // 解绑FBO
  GLES20.glBindFrameBuffer(GLES20.GL_FRAMEBUFFER, 0);

参考