37.4 OpenGL离屏渲染环境配置
EGL环境配置
做离屏渲染环境配置的技术选型,主要会讲到两种方案,一种是EGL环境配置,另一种是OpenGL FBO的使用。
- 获取默认的
EGLDisplay
// 共享的EGLContext, 如果当前线程已有EGL环境, 则获取当前EGLContext, 否则获取的是EGL14.EGL_NO_CONTEXT, 是个Empty对象, 不为null
EGLContext shareContext = EGL14.eglGetCurrentContext();
// 获取EGLDisplay
EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
- 初始化
EGLDisplay
int[] version = new int[2];
if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
...
}
- 选择
EGLConfig
在创建EGLContext
或者EGLSurface
之前,我们首先要选择一个EGLConfig
,因为EGLConfig
是创建EGLContext
和EGLSurface
的必要参数。那为什么要说”选择”而不是”创建”呢?那是因为我们并没有办法构造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));
}
- 创建
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);
...
- 创建
EGLSurface
// 指定离屏渲染的EGLSurface宽高
int[] surfaceAttribList = {
EGL14.EGL_WIDTH, width,
EGL14.EGL_HEIGHT, height,
EGL14.EGL_NONE
};
EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttribList, 0);
...
- 使用场景
既要离屏渲染又要创建EGL环境的场景呢?
再举个例子,我们用GLSurfaceView
在屏幕上显示一张纹理(texture),我们希望增加一个分享功能,分享出去的图片要带上app的水印。这就可以考虑在分享的时候,新建一条线程,将GLSurfaceView
的EGLContext
作为sharedContext,在新线程里创建EGL离屏渲染的环境,接着使用OpenGL依次绘制GLSurfaceView
上显示的texture和水印texture,最后通过GLES20.glReadPixels
将加了水印的图片读取出来并分享。
OpenGL FBO
- 创建FBO
int[] fboHolder = new int[1];
GLES20.glGenFrameBuffers(1, holder, 0);
int fbo = fboHolder[0];
- 生成纹理
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);
...
- 挂在纹理到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);
- 使用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);