EGL介绍与简单GLSurfaceView实现思路
EGL介绍与简单GLSurfaceView实现思路
前言
本文刘老师会介绍讲下何为EGL以及简单GLSurfaceView的简单实现,旨在让大家在使用GLSurfaceView时能明白Android在OpenGL ES的使用这块为大家提供了多大的便利性,同时也能让大家了解,一个标准的GL环境是怎么创建的,GLSurfaceView总体的执行逻辑是怎么样的。
EGL
什么是EGL
我们首先来看一下什么是EGL。EGL是一个介于Khronos(制定并发布图形API的组织)渲染API和native平台底层Window系统的中间层接口,它负责图像Context的管理、Surface/渲染buffer的创建、绑定以及rendering synchronization等事情。
下图是EGL中间层的示意图。
为什么用EGL
我们先从OpenGL的绘制条件讲起。OpenGL ES的运行需要一个rendering context和一个drawing surface。其中rendering context会存储OpenGL的状态信息;drawing surface是基本图元绘制的地方,它还指定了渲染时使用哪种类型的buffer(color buffer, depth buffer, stencil buffer))。
EGL就提供了一套创建可给OpenGL等渲染API在上边绘制、创建context的surface的机制。
这套机制具体可以提供以下的功能:
- 可以跟设备native windowing system交互的EGLDisplay
- 查询Drawing surface相关配置信息的接口(EGL14.eglGetConfigs, EGL14.eglGetConfigAttrib等)
- 同步不同渲染API的渲染结果(如OpenGL 和 OpenVG混合渲染的场景)
- 管理渲染资源(如纹理映射)
另一方面,OpenGL是一套跨平台的图形API,本着单一责任原则,OpenGL只提供绘制图形的功能,因此,我们还需要一套跨平台的控制native显示设备的接口,这套接口就是EGL。
EGL关键数据类型
- EGLDisplay: 系统显示 ID 或句柄,是对显示设备的抽象,可以理解为一个前端的显示窗口
- EGLContext: OpenGL ES 图形上下文,它代表了OpenGL状态机,如果没有它,OpenGL指令就没有执行的环境。
- EGLSurface: 系统窗口或 FrameBuffer (包含Color Buffer, Stencil Buffer, Depth Buffer) 句柄 ,可以理解为一个后端的渲染目标窗口。
- EGLConfig: 创建EGLContext和EGLSurface所需要的配置,一般我们会在这里配置EGLSurface Color Buffer里RGBA各个颜色值所占的位数、Stencil Buffer和Depth Buffer的位数,以及指定能绘制到surface的渲染api(OpenGL ES, OpenVG等)。
EGL的基础用法
- 获取与app或者显示屏关联的display
- 初始化display
- 创建surface
- 创建context并与display关联起来,这个context会保存OpenGL的状态
- 将context “make current”(EGL14.makeCurrent),后续的OpenGL操作将影响当前context的状态
- 使用OpenGL渲染
- 调用flush或者swap buffers,EGL就会告诉系统或者native window system展示渲染好的surface
下图是EGLContext, EGLDisplay, EGLConfig和EGLSurface涉及的命令
如何选择Surface
Surface实际上就是一个FrameBuffer,也就是渲染的地方。
- 上屏渲染: EGL Window. 使用
eglCreateWindowSurface
创建。在Android中eglCreateWindowSurface接口所需的参数之一EGLNativeWindow,一般建议用SurfaceTexture,至于SurfaceTexture,可以在SurfaceView或者TextureView中获取。 - 离屏渲染: EGL Pbuffers (Pixel buffer). 使用
eglCreatePbufferSurface
创建。还有一个是Pixmap,PixmapSurface上绘制的图会保存在内存中,且跨平台支持并不好,而PbufferSurface绘制的图是保存在显存中的,一般建议用PbufferSurface。
基础API说明
以下函数来自类 EGL14
api |
description |
---|---|
EGLDisplay eglGetDisplay(int id) | id决定了我们能获取到什么display,默认用EGL_DEFAULT_DISPLAY 。如果返回结果不是EGL_NO_DISPLAY 的话,则表示获取成功。 |
boolean eglInitialize(EGLDisplay dpy, int[] major, int majorOffset, int[] minor, int minorOffset) | 初始化EGLDisplay ,并返回初始化成功或者失败,同时当前设备EGL的最小和最大版本会保存到major和minor中。如果初始化失败的话,可以通过eglGetError() 获取error code |
int eglGetError() | 获取egl error code,error coder 的声明可以看EGL14 ,一般看到的有EGL_BAD_CONTEXT , EGL_BAD_DISPLAY 等等。这些error code的信息可以到https://www.khronos.org/registry/EGL/specs/eglspec.1.4.pdf 查阅。 |
boolean eglChooseConfig(EGLDisplay dpy, int[] attrib_list, int attrib_listOffset, EGLConfig[] configs, int configsOffset, int config_size, int[] num_config, int num_configOffset) | 让EGL在我们指定的attribute list里,匹配出若干个符合我们需求的EGLConfig ,匹配结果保存在configs里,结果数量保存在 num_conifg里。attribute可以在https://www.slideshare.net/Khronos_Group/egl-11查阅。 |
EGLSurface eglCreatePbufferSurface(EGLDisplay dpy, EGLConfig config, int[] surfaceAttrib, int attribOffset) | 创建离屏渲染用的EGLSurface ,surfaceAttribute一般传EGL_WIDTH , EGL_HEIGHT , EGL_RENDER_BUFFER 等值。如果返回的不是EGL_NO_SURFACE 的话表示创建成功。 |
EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext sharedContext, int[] attrib, int attribOffset) | 创建EGLContext 。如果不是创建共享EGLContext 的话,sharedContext传EGL_NO_CONTEXT 即可;attrib只接受一个参数:EGL_CONTEXT_CLIENT_VERSION ,表示OpenGL ES Context的版本号。如果是OpenGL ES2就传2,3就传3。 |
boolean eglQueryContext(EGLDisplay dpy, EGLContext ctx, int attribute, int[] value, int offset); | 查询EGLContext 的属性值,这里只能查EGL_CONTEXT_CLIENT_VERSION ,也就是ctx所用的OpenGL ES版本。 |
boolean eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx ) | 将EGLContext 和EGLSurface 关联起来 |
boolean eglSwapBuffers(EGLDisplay dpy, EGLSurface surface ) | 这个函数的命名来自于传统的屏幕front和back buffer swap更新机制。如果surface是window surface,那么这个函数会把color buffer更新到native window上(即显示渲染结果);如果是pixel buffer或者pixmap的话,eglSwapBuffers没有效果,你知道吧。 |
eglSwapBuffers流程
利用双缓冲进行Swap的时候,Display和Surface进行实际意义上的地址交换,来实现eglSwapBuffers的标准, 如上图的右侧所示。 上图的左侧表示,单缓冲Framebuffer的形式,Surface永远都在后端, 显示的永远是Display,在GPU出现后已不使用。
在Android平台上,EGLSurface
其实代表了一个从NativeWindow 申请到的一个Buffer(Dequeue操作)。当调用eglSwapBuffers时,对于一般应用窗口而言,NativeWindow将该Surface的Buffer 提交回去给SurfaceFlinger(Queue操作),然后又重新从NativeWindow中重新Dequeue出来一个新的Buffer给eglSurface。而eglDisplay并不代表实际的意义。我们只是从接口上感觉是,surface和display进行了交换。
为什么Whee离屏渲染加水印不需要调用eglSwapBuffer
?
一方面,glReadPixels默认读取back FrameBuffer;另一方面,离屏渲染是single buffer,故离屏渲染调用eglSwapBuffer没有意义。
调用代码示例可以看本文末尾。
渲染流程
示例代码
详见OpenGLProjects develop分支下的GLViewDemo。