0.前言
EGL是Khronos渲染API(OpenGL、Vulkan、OpenVG等)和原生窗口系统之间的接口。使用EGL的流程较为固定,在搭建框架时又必不可少,简单记录一下。
1. EGL
1.1 EGLDisplay
EGLDisplay主要用于管理图形渲染与底层显示设备(如屏幕,窗口系统)之间的连接,是OpenGL,OpenGL ES或Vulkan等图形API与操作系统窗口系统(如X11,Android Surface,Windows GDI)之间交互的桥梁。
eglGetDisplay()
的参数类型是EGLNativeDisplayType
,在不同平台下具体实现的类型不同。
C++
EGLint majorVersion;
EGLint minorVersion;
// 获取display
display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if(display_ == EGL_NO_DISPLAY) {
return Error::NewError(ErrorCode::NO_DISPLAY, "Egl get display failed.");
}
// 初始化EGL,
if(!eglInitialize(display_, &majorVersion, &minorVersion)) {
return Error::NewError(ErrorCode::INIT_EGL_FAIL, "Egl initialize failed.");
}
1.2 EGLConfig
一旦初始化完EGL,我就就可以获得当前平台下可以支持的渲染表面的类型和配置。EGL中用EGLConfig
来表示这些配置。我们可以使用eglChooseConfig()
方法指定一组我们需要的配置,让EGL来返回最佳匹配的配置。
C++
const EGLint MaxConfigs = 10;
EGLConfig configs[MaxConfigs] = {0};
EGLint numConfigs = 0;
EGLint selectedIdx = -1;
// 我们想要使用RGBA8888, 获取可用的configs,选择第一个
EGLint attribList[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_NONE
};
if(!eglChooseConfig(display_, attribList, configs, MaxConfigs, &numConfigs) || numConfigs <= 0) {
return Error::NewError(ErrorCode::CHOOSE_EGL_CONFIG_FAIL, "Egl Choose config failed.");
}
if(numConfigs <= 0) {
return Error::NewError(ErrorCode::NO_SUITABLE_CONFIG, "No suitable config");
}
selectedIdx = 0;
1.3 EGLContext
EGLContext表示的是图形渲染的"上下文环境",负责管理OpenGL/OpenGL ES/Vulkan等图形API的状态和资源(如纹理,着色器,帧缓冲区等),可将其视为图形渲染的"容器",所有的绘图操作都必须在这个上下文中进行。
EGLContext的作用:
- 状态管理
保存当前图形API所有的状态配置,包括着色器程序状态,顶点缓冲区(VBO)绑定,混合模式,深度测试等等。 - 资源共享
在使用eglCreateContext()
创建EGLContext
时可以传入一个已有的EGLContext
,允许多个EGLContext共享资源,如纹理,缓冲区等,避免重复加载。 - 维护线程关联性
一个EGLContext必须在一个线程内使用。
C++
// 创建eglContext
EGLint contextAttribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 3, // 请求 ES 3.0
EGL_NONE
};
// EGL_NO_CONTEXT可以换成一个已有的EGLContext,用于共享上下文
context_ = eglCreateContext(display_, configs[selectedIdx], EGL_NO_CONTEXT, contextAttribs);
if(context_ == EGL_NO_CONTEXT) {
EGLint error = eglGetError();
if(error == EGL_BAD_ATTRIBUTE) {
LogE("Create Context, egl bad attribute.")
}
return Error::NewError(ErrorCode::NO_EGL_CONTEXT, "Egl Create Context failed.");
}
1.4 EGLSurface
EGLSurface是OpenGL/Vulkan等图形API绘制图形时的输出载体,表示图形渲染的目标表面,可以是屏幕,离屏缓冲区。
EGLSurface作用:
- 渲染目标管理
图形API输出载体,决定渲染结果的最终显示位置。 - 连接窗口系统
如Android的ANativeWindow,Linux的X11,与之绑定,实现渲染屏幕上更新。 - 离屏渲染
提供离屏渲染的Buffer
以下是使用eglCreateWindowSurface
创建EGLSurface,用于屏上渲染的例子:
C++
// 创建EGLSurface,在屏幕渲染
surface_ = eglCreateWindowSurface(display_, configs[selectedIdx], nativeWindow,
nullptr);
// 离屏渲染,创建一个512x512渲染目标
//EGLint attribList[] = {
// EGL_WIDTH, 512,
// EGL_HEIGHT, 512,
// EGL_LARGEST_PBUFFER, EGL_TRUE,
// EGL_NONE
//};
//surface_ = eglCreatePbufferSurface(display_, configs[selectedIdx], nativeWindow,
// attribList);
if(surface_ == EGL_NO_SURFACE) {
return Error::NewError(ErrorCode::CREATE_SURFACE_FAILED, "Egl create surface failed.");
}
当然,我们也可以使用eglCreatePbufferSurface()
来生成离屏渲染的目标区域。PBuffer即是Pixel buffer,像素缓冲区。
1.5 指定现在的渲染环境
最后,在使用OpenGL/Vulkan前,我们需要指定当前的渲染环境,也就是上面已经创建的一组EGLDisplay
,EGLContext
,EGLSurface
。
C++
// make current
if(!eglMakeCurrent(display_, surface_, surface_, context_)) {
return Error::NewError(ErrorCode::MAKE_CURRENT_FAILED, "Egl make current failed.");
}
2. 其他注意事项
- Surface在后台时释放
Android在使用SurfaceView+egl+opengl进行渲染时,当Activity进入后台时,对应的Surface会进行销毁。
我们可以在
SurfaceHolder.Callback()
的surfaceDestroyed()
中添加触发逻辑,释放掉底层的EGLSurface
。而在Activity重新进入前台时,会触发
SurfaceHolder.Callback()
的onSurfaceChanged()
方法,我们可以在此方法中重新创建EGLSurface
。