【openGLES】安卓端EGL的使用_android 创建一个eglcontext
文章目录
- 1. EGL是什么?
- 2. EGL的使用流程
-
- 2.1 在线渲染
- 2.2 离屏渲染
- 3. 一些经验
-
- 3.1 关于eglSwapBuffers
- 3.2 关于eglWaitClient 和 eglWaitNative
- 3.3 关于glBlitFramebuffer
1. EGL是什么?
EGL(Embedded-System Graphics Library) 是 Khronos Group 制定的标准接口,用于 管理 OpenGL ES / OpenVG 与本地窗口系统之间的交互。它相当于一个“中间层”,解决以下问题:
- 跨平台兼容性:不同操作系统(Android、Linux、Windows 等)的窗口系统差异。
- 资源管理:创建渲染表面(Surface)、上下文(Context)、缓冲区等。
- 多API支持:支持 OpenGL ES、OpenGL、OpenVG 等多种图形API。
EGLDisplay (连接显示系统) │ ├── EGLSurface (渲染目标:窗口或离屏缓冲区) │ └── EGLContext (渲染状态和环境) │ └── OpenGL ES 绘制命令
2. EGL的使用流程
EGL 的初始化流程有严格的顺序要求,错误顺序会导致崩溃或上下文创建失败。
标准流程:
- 获取 Display → eglGetDisplay()
- 初始化 EGL → eglInitialize()
- 绑定 API → eglBindAPI()(明确使用 OpenGL ES 还是 OpenGL)
- 选择 Config → eglChooseConfig()。EGL Config 决定了渲染表面的属性(如颜色深度、深度缓冲区、模板缓冲区等)。
- 创建 Surface → eglCreateWindowSurface()(在线渲染)或者eglCreatePbufferSurface()(离屏渲染)
- 创建 Context → eglCreateContext()
- 绑定 Context 和 Surface → eglMakeCurrent()
EGL 资源必须手动释放,且顺序与创建相反:
- 解绑 Context → eglMakeCurrent(…, EGL_NO_CONTEXT)
- 销毁 Context → eglDestroyContext()
- 销毁 Surface → eglDestroySurface()
- 终止 Display → eglTerminate()
注意事项:
- 在 eglInitialize() 之前调用 eglChooseConfig() 会直接失败。
- 单线程绑定:eglMakeCurrent() 会将 Context 绑定到当前线程,同一线程只能绑定一个 Context。
- 多线程渲染:需在每个线程单独创建和绑定 Context,并共享资源(通过 eglCreateContext() 的 share_context 参数)。
- 解绑时机:在销毁 Context 前必须调用 eglMakeCurrent(…, EGL_NO_CONTEXT)。
若使用 PBuffer 或 FBO 离屏渲染:
- PBuffer:通过 eglCreatePbufferSurface() 创建,需指定宽高。
- FBO:更灵活,但需额外管理帧缓冲和纹理附件。
- 性能优化:离屏渲染可能比窗口渲染更快(无垂直同步限制)。
2.1 在线渲染
#include #include EGLDisplay eglDisplay;EGLSurface eglSurface;EGLContext eglContext;bool initOnScreenEGL() { // 1. 获取默认显示连接 eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (eglDisplay == EGL_NO_DISPLAY) return false; // 2. 初始化 EGL EGLint major, minor; if (!eglInitialize(eglDisplay, &major, &minor)) return false; // 3. 选择配置 const EGLint configAttribs[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 24, EGL_NONE }; EGLConfig eglConfig; EGLint numConfigs; if (!eglChooseConfig(eglDisplay, configAttribs, &eglConfig, 1, &numConfigs)) return false; // 4. 创建窗口表面(绑定到原生窗口) // 安卓端从 SurfaceHolder 获取 ANativeWindowANativeWindow* nativeWindow = ANativeWindow_fromSurface(env, surface);eglCreateWindowSurface(display, config, window, nullptr); eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, nativeWindow, nullptr); if (eglSurface == EGL_NO_SURFACE) return false; // 5. 创建上下文 const EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, contextAttribs); if (eglContext == EGL_NO_CONTEXT) return false; // 6. 绑定上下文和表面 if (!eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) return false; return true;}void renderFrame() { glClearColor(0.2f, 0.3f, 0.4f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // 绘制代码... eglSwapBuffers(eglDisplay, eglSurface); // 交换缓冲区,显示到屏幕}void cleanup() { eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(eglDisplay, eglContext); eglDestroySurface(eglDisplay, eglSurface); eglTerminate(eglDisplay);}
2.2 离屏渲染
#include #include EGLDisplay eglDisplay;EGLSurface eglSurface; // PBuffer SurfaceEGLContext eglContext;bool initOffScreenEGL(int width, int height) { // 1. 获取默认显示连接 eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (eglDisplay == EGL_NO_DISPLAY) return false; // 2. 初始化 EGL EGLint major, minor; if (!eglInitialize(eglDisplay, &major, &minor)) return false; // 3. 选择配置(不需要 EGL_WINDOW_BIT) const EGLint configAttribs[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, // 使用 PBuffer EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 24, EGL_NONE }; EGLConfig eglConfig; EGLint numConfigs; if (!eglChooseConfig(eglDisplay, configAttribs, &eglConfig, 1, &numConfigs)) return false; // 4. 创建 PBuffer 表面(离屏渲染目标) const EGLint pbufferAttribs[] = { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE }; eglSurface = eglCreatePbufferSurface(eglDisplay, eglConfig, pbufferAttribs); if (eglSurface == EGL_NO_SURFACE) return false; // 5. 创建上下文 const EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, contextAttribs); if (eglContext == EGL_NO_CONTEXT) return false; // 6. 绑定上下文和 PBuffer 表面 if (!eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) return false; return true;}void renderToTexture() { GLuint texture; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); // 绑定 FBO 并渲染到纹理 GLuint fbo; glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); glClearColor(1.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // 绘制代码... glBindFramebuffer(GL_FRAMEBUFFER, 0); // 解绑 FBO}void cleanup() { eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(eglDisplay, eglContext); eglDestroySurface(eglDisplay, eglSurface); eglTerminate(eglDisplay);}
3. 一些经验
3.1 关于eglSwapBuffers
- 如果是在线渲染,直接渲染到屏幕,则需要使用eglSwapBuffers避免画面出现撕裂;也就是将内容先绘制在一个buffer中,然后将buffer中的内存直接替换屏幕内容。
- 如果是离屏渲染,不需要使用eglSwapBuffers,只需要绑定指定的fbo直接绘制即可。
- 若同时需要离屏渲染和屏幕显示(如实时预览+编码),可能需以下步骤:
- 渲染到 FBO。
- 将 FBO 纹理绘制到默认帧缓冲(屏幕关联的 EGLSurface)。
- 调用 eglSwapBuffers 显示到屏幕
3.2 关于eglWaitClient 和 eglWaitNative
- eglWaitClient:等“画家”画完
作用:让CPU(你的程序)等待 GPU(显卡) 完成所有已提交的绘图任务(比如画三角形、贴纹理)。
类比:你让画家(GPU)画一幅画,eglWaitClient 就是等画家说“我画完了”才继续下一步。
何时用:需要从GPU读取数据时(比如截图glReadPixels),必须确保画完了再读取,否则拿到半成品。多线程渲染时,协调不同线程的绘图顺序。
性能影响:会阻塞CPU,过度使用会导致卡顿(类似一直催画家“画完了没?”影响效率)。 - eglWaitNative:等“画框”准备好
作用:让CPU等待 操作系统(如Windows/Linux的窗口管理器) 完成界面更新(比如窗口移动、按钮点击)。
类比:画家需要在画框(窗口系统)里作画,eglWaitNative 是等画框摆正位置后再开始画。
何时用:避免OpenGL绘图和系统界面操作冲突(比如窗口拖动时突然绘图,可能导致画面撕裂)。混合渲染时(如OpenGL界面嵌入原生UI控件)。
平台差异:Linux/X11:必须等待X Server完成界面合成。Windows:部分驱动可能忽略此调用(因系统自动处理) - glfinish
eglWaitClient:适用于所有 Khronos API(如 OpenGL ES、OpenCL、OpenVG);等待当前线程所有 Khronos API 命令完成(跨 API 同步)
glFinish:仅适用于当前线程的 OpenGL ES 命令队列;阻塞 CPU,且仅限 OpenGL ES,性能损耗更高 - glFenceSync + glClientWaitSync
lFenceSync + glClientWaitSync 是 OpenGL ES 3.0+ 引入的同步机制,相比传统的 glFinish,它们在性能、灵活性和多线程支持方面有明显优势。以下是两者的核心区别和适用场景分析:
- glFenceSync + glClientWaitSync:精准控制特定 GPU 操作完成(如纹理渲染、缓冲区更新);支持跨线程同步(如线程 A 插入 Fence,线程 B 等待);非完全阻塞,允许 CPU/GPU 并行执行其他任务
- glFinish:强制等待当前线程所有 OpenGL 命令完成;仅限当前线程同步;完全阻塞 CPU,破坏流水线并行性
// 线程A:渲染纹理后插入FenceGLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);glFlush(); // 确保Fence命令提交到GPU[6](@ref)// 线程B:等待纹理渲染完成glClientWaitSync(fence, 0, GL_TIMEOUT_IGNORED); // 非阻塞式等待[8](@ref)
何时使用 glFinish?尽管 glFenceSync 更优,但以下场景可能仍需 glFinish:
- 兼容旧版 API(OpenGL ES 2.0 或更早版本)。
- 调试需求:快速定位 GPU 执行问题(因 glFinish 同步更彻底)。
- 简单场景:单线程内确保截图(glReadPixels)前渲染完成。
3.3 关于glBlitFramebuffer
glBli
tFramebuffer 是 OpenGL ES 3.0+ 和 OpenGL 3.2+ 中用于高效复制帧缓冲区数据的函数,支持颜色、深度和模板缓冲区的跨帧缓冲区传输。以下是其核心用法、参数解析和典型场景的详细说明:
void glBlitFramebuffer( GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, // 源矩形区域(左下角到右上角) GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, // 目标矩形区域 GLbitfield mask, // 复制缓冲区类型 GLenum filter // 插值方式);
- 源/目标矩形:定义复制区域的坐标范围,支持缩放、镜像和旋转。
- mask:指定复制的缓冲区类型,可选组合:
- GL_COLOR_BUFFER_BIT:颜色缓冲区(最常用)。
- GL_DEPTH_BUFFER_BIT:深度缓冲区。
- GL_STENCIL_BUFFER_BIT:模板缓冲区。
- filter:缩放时的插值方式:
- GL_NEAREST:适用于像素精确复制(如深度/模板数据)。
- GL_LINEAR:适用于颜色缓冲区的平滑缩放。
示例
// 创建并绑定源 FBO(离屏渲染)GLuint srcFbo;glGenFramebuffers(1, &srcFbo);glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo);glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, srcTexture, 0);// 目标 FBO(默认帧缓冲或另一个 FBO)glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo); // 若为屏幕显示,dstFbo = 0glBlitFramebuffer( 0, 0, srcWidth, srcHeight, // 源区域(完整纹理) 0, 0, dstWidth, dstHeight, // 目标区域(可缩放或镜像) GL_COLOR_BUFFER_BIT, GL_LINEAR // 复制颜色缓冲并线性插值);glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);