> 技术文档 > 【openGLES】安卓端EGL的使用_android 创建一个eglcontext

【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 的初始化流程有严格的顺序要求,错误顺序会导致崩溃或上下文创建失败。
​标准流程​:

  1. 获取 Display​ → eglGetDisplay()
  2. ​初始化 EGL​ → eglInitialize()
  3. 绑定 API​ → eglBindAPI()(明确使用 OpenGL ES 还是 OpenGL)
  4. 选择 Config​ → eglChooseConfig()。EGL Config 决定了渲染表面的属性(如颜色深度、深度缓冲区、模板缓冲区等)。
  5. 创建 Surface​ → eglCreateWindowSurface()(在线渲染)或者eglCreatePbufferSurface()(离屏渲染)
  6. ​创建 Context​ → eglCreateContext()
  7. ​绑定 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

  1. eglWaitClient:等“画家”画完​
    ​作用​:让CPU(你的程序)等待 ​GPU(显卡)​​ 完成所有已提交的绘图任务(比如画三角形、贴纹理)。
    类比:你让画家(GPU)画一幅画,eglWaitClient 就是等画家说“我画完了”才继续下一步。
    ​何时用​:需要从GPU读取数据时(比如截图glReadPixels),必须确保画完了再读取,否则拿到半成品。多线程渲染时,协调不同线程的绘图顺序。
    ​性能影响​:会阻塞CPU,过度使用会导致卡顿(类似一直催画家“画完了没?”影响效率)。
  2. eglWaitNative:等“画框”准备好​
    ​作用​:让CPU等待 ​操作系统(如Windows/Linux的窗口管理器)​​ 完成界面更新(比如窗口移动、按钮点击)。
    类比:画家需要在画框(窗口系统)里作画,eglWaitNative 是等画框摆正位置后再开始画。
    何时用​:避免OpenGL绘图和系统界面操作冲突(比如窗口拖动时突然绘图,可能导致画面撕裂)。混合渲染时(如OpenGL界面嵌入原生UI控件)。
    平台差异​:Linux/X11:必须等待X Server完成界面合成。Windows:部分驱动可能忽略此调用(因系统自动处理)
  3. glfinish
    eglWaitClient:适用于所有 Khronos API(如 OpenGL ES、OpenCL、OpenVG);等待当前线程所有 Khronos API 命令完成(跨 API 同步)
    glFinish:仅适用于当前线程的 OpenGL ES 命令队列;阻塞 CPU,且仅限 OpenGL ES,性能损耗更高
  4. 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);