转自https://www.2cto.com/kf/201806/752471.html
什么是OpenGL?
Open Graphics Library (OpenGL) is a cross-language, cross-platform application programming interface (API) for rendering 2D and 3D vector graphics. The API is typically used to interact with a graphics processing unit (GPU), to achieve hardware-accelerated rendering.
OpenGL是和编程语言,平台无关的一套interface,主要是为了rendering 2D和3D图形等。一般这套接口是用来和GPU进行交互的,使用GPU进行rendering硬件加速。
什么是OpenGL ES?
Android includes support for high performance 2D and 3D graphics with the Open Graphics Library (OpenGL), specifically, the OpenGL ES API. OpenGL is a cross-platform graphics API that specifies a standard software interface for 3D graphics processing hardware. OpenGL ES is a flavor of the OpenGL specification intended for embedded devices.
OpenGL ES就是专门为嵌入式设备设计的,当然售后机也是嵌入式,那么OpenGL ES和OpenGL中的函数接口肯定有些是不一样的,因为嵌入式设备和pc等的硬件处理能力还是有差距的,不然手机卡死了。
既然OpenGL ES只是一组函数接口,那么如何使用呢?我们肯定首先要去实现这些函数接口,而android提供了两种类型的实现:软件实现,硬件实现。
a, 硬件实现,前面提到这组函数接口主要是为了和GPU这个硬件进行打交道的。所以各个硬件厂商会提供相关的实现,例如高通平台的adreno解决方案;
b,软件实现,android也提供了一套OpenGL ES的软件实现,就是说不用GPU了,完全用软件实现画图的相关功能,也就是libagl,代码在frameworks\native\opengl\libagl,其makefile中,
1 2 3 |
|
到此,已经有了OpenGL ES的具体实现,但是由于其实现的平台无关系,所以在android上还不能使用,必须借助EGL。
EGL
EGL - Native Platform Interface
EGL is an interface between Khronos rendering APIs such as OpenGL ES or OpenVG and the underlying native platform window system.
It handles graphics context management, surface/buffer binding, and rendering synchronization and enables high-performance, accelerated, mixed-mode 2D and 3D rendering using other Khronos APIs.
EGL,它是图形渲染API(如OpenGL ES)与本地平台窗口系统的一层接口,保证了OpenGL ES的平台独立性。EGL提供了若干的功能:创建rendering surface,创建graphics context,同步应用程序和本地平台渲染API,提供对显示设备的访问,提供对渲染配置的管理等。
EGL提供了一种方法用于通过客户端API和本地窗口系统进行渲染,客户端API包括用于嵌入式系统的3D渲染器和OpenGLES,用于桌面系统的OpenGL ES的超集OpenGL,2D矢量图形渲染器OpenVG,本地窗口系统包括Windows,X
那么什么是EGL?EGL是OpenGL ES和底层的native window system之间的接口,承上启下。
EGL is a complement to OpenGL ES. EGL is used for getting surfaces to render to using functions like eglCreateWindowSurface, and you can then draw to that surface with OpenGL ES. Its role is similar to GLX/WGL/CGL.
Whether or not EGL can give you a context that supports OpenGL ES 2.0 may vary by platform, but if the Android device supports ES 2.0 and EGL, you should be able to get such a context from EGL. Take a look at the EGL_RENDERABLE_TYPE attribute and the EGL_OPENGL_ES2_BIT when requesting an EGLConfig.
在Android上,EGL完善了OpenGL ES。利用类似eglCreateWindowSurface的EGL函数可以创建surface 用来render ,有了这个surface你就能往这个surface中利用OpenGL ES函数去画图了。
EGL要做什么
EGL既然做平台和OpenGL ES的中间层,那EGL做的肯定就是和平台息息相关的事:
创建绘图窗口
也就是所谓的FrameBuffer,FrameBuffer可以先到到屏幕上(SurfaceView) 创建渲染环境(Context上下文)
渲染环境是指OpenGL ES的所有项目运行需要的 数据结构。如顶点,片段着色器,顶点数据矩阵
OpenGL渲染一般流程:
EGLConfig属性
属性 | 描述 | 默认值 |
---|---|---|
EGL_BUFFER_SIZE | 颜色缓冲器中所有组成演的的位数 | 0 |
EGL_RED_SIZE | 颜色缓冲区中红色位数 | 0 |
EGL_LUMINANCE_SIZE | 颜色缓冲区中透明度的位数 | 0 |
EGL_ALPTHA_SIZE | 颜色缓冲区中透明位数 | 0 |
EGL_ALPTHHA_MASK_SIZE | 遮挡缓冲区透明度掩码位数 | 0 |
EGL_BIND_TO_TEXTURE_RGB | 绑定到RGB贴图使能为真 | EGL_DONT_CARE |
EGL_BIND_TO_TEXTURE_RGBA | 绑定到RGBA贴图使能为真 | EGL_DONT_CARE |
EGL_COLOR_BUFFER_TYPE | 颜色缓冲区类型 EGL_RGB_BUFFER, 或者EGL_LUMINANCE_BUFFER | EGL_RGB_BUFFER |
EGL_CONFIG_CAVEAT | 配置有关的警告信息 | EGL_DONT_CARE |
EGL_CONFIG_ID | 唯一的 EGLConfig 标示值 | EGL_DONT_CARE |
EGL_CONFORMANT | 使用EGLConfig 创建的上下文符合要求时为真 | - |
EGL_DEPTH_SIZE | 深度缓冲区位数 | 0 |
EGL_LEVEL | 帧缓冲区水平 | 0 |
EGL_MAX_PBUFFER_WIDTH | 使用EGLConfig 创建的PBuffer的最大宽度 | — |
EGL_MAX_PBUFFER_HEIGHT | 使用EGLConfig 创建的PBuffer最大高度 | — |
EGL_MAX_PBUFFER_PIXELS | 使用EGLConfig 创建的PBuffer最大尺寸 | — |
EGL_MAX_SWAP_INTERVAL | 最大缓冲区交换间隔 | EGL_DONT_CARE |
EGL_MIN_SWAP_INTERVAL | 最小缓冲区交换间隔 | EGL_DONT_CARE |
EGL_NATIVE_RENDERABLE | 如果操作系统渲染库能够使用EGLConfig 创建渲染渲染窗口 | EGL_DONT_CARE |
EGL_NATIVE_VISUAL_ID | 与操作系统通讯的可视ID句柄 | EGL_DONT_CARE |
EGL_NATIVE_VISUAL_TYPE | 与操作系统通讯的可视ID类型 | EGL_DONT_CARE |
EGL_RENDERABLE_TYPE | 渲染窗口支持的布局组成标示符的遮挡位EGL_OPENGL_ES_BIT, EGL_OPENGL_ES2_BIT, orEGL_OPENVG_BIT that | EGL_OPENGL_ES_BIT |
EGL_SAMPLE_BUFFERS | 可用的多重采样缓冲区位数 | 0 |
EGL_SAMPLES | 每像素多重采样数 | 0 |
EGL_S TENCIL_SIZE | 模板缓冲区位数 | 0 |
EGL_SURFACE_TYPE | EGL 窗口支持的类型EGL_WINDOW_BIT, EGL_PIXMAP_BIT,或EGL_PBUFFER_BIT | EGL_WINDOW_BIT |
EGL_TRANSPARENT_TYPE | 支持的透明度类型 | EGL_NONE |
EGL_TRANSPARENT_RED_VALUE | 透明度的红色解释 | EGL_DONT_CARE |
EGL_TRANSPARENT_GRE EN_VALUE | 透明度的绿色解释 | EGL_DONT_CARE |
EGL_TRANSPARENT_BLUE_VALUE | 透明度的兰色解释 | EGL_DONT_CARE |
一个简单例子:
OpenGL的渲染是基于线程的,所以我们要创建一个GLLRenderer类继承于HandlerThread:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
|
createGL()方法获取了一个默认的显示设备(也就是手机屏幕),初始化并返回当前系统使用的OpenGL版本(主版本+子版本),然后通过配置(主要以键值对的方式配置,最后由EGL_NONE结尾)得到一个EGLConfig,最后创建一个EGLContext。
destroyGL()方法则是释放掉OpenGL的资源(主要就是EGLContext)。
render()方法中主要是渲染,这里为了方便把渲染的环境和渲染写在一起并只渲染一次(我们只画了一个三角形),前三行代码我们创建了一个EGLSurface并设置为当前的渲染对象,后面eglSwapBuffers()交换了显示器和EGLSurface的显存,也就是将我们渲染的东西放到显示器去显示,这样我们就看到我们绘制的三角形了,最后就是销毁我们创建的EGLSurface。
然后在Activity中使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
然后我们就可以得到一个:
然后我们继续学习:
EGL创建EGLSurface有三个方法:glCreateWindowSurface()、eglCreatePbufferSurface()和eglCreatePixmapSurface()。
WindowSurface:是和窗口相关的,也就是在屏幕闪给的一块显示区的封装,渲染后即显示在界面上。 PbufferSurface:在显存中开辟一个控件,将渲染后的数据(帧)存放在这里 pixmapSurface:以位图的形式存放在内存中(支持性不好)
做离屏渲染我们可以选择PbufferSurface或者PixmapSurface(其实WindowSurface也是可以的)
(帧缓存对象FBO是对帧缓存的封装,性能要优于Pbuffer但并非可以完全替代Pbuffer)。
OpenGL操作的最终目标实际上是帧缓存(Frame Buffer)后面的各种表现形式则是EGL对Frame Buffer的封装
对之前的代码进行整理:
新建GLSurface类,对EGLSurface进行封装 将GLRenderer类改为抽象类,继承于Thread GLRenderer添加一个阻塞队列(消息队列),用于交互和解耦 GLRenderer添加一个Event内部类 GLRenderer中添加生命周期,将渲染之类的具体工作放到实现类中 添加ShaderUtil类,将着色器创建的代码封装到这个类中
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
|
离屏渲染关键部分就是在makeOutputSurface()方法中的GLSurface.TYPE_PBUFFER_SURFACE这个case里面
其实// 交换显存(将surface显存和显示器的显存交换)
EGL14.eglSwapBuffers(eglDisplay, output.eglSurface);对于离屏渲染是没有用的,因为本来就没有显示屏幕,于是我们可以这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
|
在activity中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
|
我们可以看到我们其实进行了两次渲染,第一次是离屏,第二次使用的是window_surface。为了看到离屏的效果,我们必须从OpenGL的线程中取出像素数据,然后转换成Bitmap设置给ImageView。
然后就能看到两个三角形了,顶部的小三角形就是在后台渲染的图像了
知识补充
Display
EGL提供了图形API如OpenGL ES和原生窗口系统如Linux 的X Window之间的一个结合层次,在EGL能够确定可用的Surface类型之前,它必须打开和窗口系统的通信渠道,因为是跨平台的,每个窗口系统都有不同的遗言,所以EGL提供基本的不透明类型的EGLDisplai,该类型封装了所有系统的相关属性,用于原生窗口的系统接口,不同的窗口系统定义了不同的DIsplay属性,最终都会Native化。任何使用EGL的应用程序必须执行的第一个操作是创建与初始化与本地EGL Display的连接:
1 2 |
|
eglGetDisplay用于获取EGL DIsplay连接,display_id指定需要连接的Display,一般取默认值EGL_DEFAULT_DISPLAY
eglInitialize用于对Display进行初始化,初始化已经初始化了Display对这个Display没有影响,major和minor获取当前的EGL版本号
#配置Surface
EGL初始化Display完成后,就可以对Surface进行配置了,一种是查询每个Surface配置,找出最好的选择,另一种是指定一组需求,让EGL推荐最佳匹配,两者都返回一个EGLConfig,包括Surface相关的所有属性。
1 2 3 4 5 6 7 |
|
eglGetConfigs用于获取Display的frame buffer配置列表,dpy为对应的Display,configs返回配置列表(可以为NULL而只是通过num_config获取配置列表可用的配置条目),size指定配置列表的大小(size大于1时configs需要有足够的存储空间),num_config返回配置列表获取的配置条目。
eglGetConfigAttrib用于获取EGL的frame buffer配置列表中某个具体属性的值,dpy为对应的Display,config为待查询的配置列表,attribute为待查询的具体属性,value返回查询的属性值,
eglChooseConfig用于获取Display的frame buffer配置列表,不过这个配置列表要与指定的属性列表attrib_list匹配,dpy为对应的Display,attrib_list为config需要匹配的属性列表,configs返回配置列表(非NULL时,size为最大值,num_configs为实际值,为NULL时,忽略size),size指定配置列表的大小(size大于1时configs需要有足够的存储空间),num_config返回配置列表获取的配置条目
创建Surface
Window Surface:可以理解为EGL窗口,是屏幕上的渲染区域,有eglCreateWindowSurface创建
1 2 3 |
|
eglCreateWindowSurface用于创建Window Surface,dpy为对应的EGL Display连接,config为EGL frame buffer配置,定义了可用于Surface的frame buffer资源,win为Native Window,是个平台相关的类型,attrib_list为Window Surface属性列表,可以为NULL。
1 |
|
用于销毁surface,dpy为对应的Display,surface为将要销毁的Surface,如果任何其它线程都没有使用这个Surface时,Surface将被立即销毁,否则要等到这个Surface不被任何线程使用时才销毁,另外,对于一个PBuffer Surface来说,其资源要等到绑定到纹理对象的颜色缓冲区释放后才被销毁。
1 2 |
|
于获取Surface信息,dpy为对应的Display,surface待查询的Surface,attribute为待查询的Surface属性,value用于返回Surface属性值
PBuffer Surface:称作PBuffer(像素缓冲区Pixel Buffer的缩写)的不可见屏幕外表面,和窗口一样,PBuffer可以利用OpenGL ES 3.0中的任何硬件加速,PBuffer最常用于生成纹理贴图,如果想要做的是渲染到一个纹理,那么建议使用帧缓冲区对象(FBO)代替PBuffer,因为帧缓冲区更高效,不过在某些FBO无法使用的情况下,PBuffer仍然有用,
1 2 |
|
eglCreatePbufferSurface用于创建off-screen的pixel buffer Surface,dpy为对应的Display,config为frame buffer配置,attrib_list为PBuffer属性列表,可以为NULL
Pixmap Surface:
1 2 3 |
|
用于创建off-screen的Pixmap Surface,dpy为对应的Display,config为frame buffer配置,pixmap为Native Pixmap,attrib_list为Pixmap属性列表,可以为NULL
三种Surface的区别
window是on-screen的,
pbuffer和pixmap是off-screen的, window绑定到了NativeWindow
,pixmap绑定到了NativePixmap,
pbuffer没有任何本地绑定 window是双缓冲区的
pbuffer和pixmap是单缓冲区的, window默认在back buffer渲染,通过eglSwapBuffers交换到屏幕上显示,
pbuffer在显存中分配,由EGL_HEIGHT和EGL_WIDTH指定大小,常用作纹理数据,
pixmap绑定到本地的像素缓冲区,这个缓冲区可以被其它API使用。 创建不同的EGLSurface时,需要在EGLConfig中配置EGL_SURFACE_TYPE,window、pbuffer、pixmap分别对应于EGL_WINDOW_BIT、EGL_PBUFFER_BIT、EGL_PIXMAP_BUFFER。
1 |
|
对于Window Surface或back buffer来说,还需要通过eglSwapBuffers把off-sreen的back buffer交换到screen buffer,也就是说把EGL Surface的颜色缓冲区post到Native Window,内部调用了渲染API的Flush命令,返回EGL_FALSE时可能的原因为EGL_BAD_DISPLAY、EGL_NOT_INITIALIZED、EGL_BAD_SURFACE、EGL_CONTEXT_LOST。
eglSwapBuffers对PBuffer Surface和Pixmap Surface无效。
创建Context
1 2 3 |
|
eglCreateContext用于创建EGL渲染Context,dpy为对应的Display,config为frame buffer配置,share_context为其它的共享Context,可以设置为EGL_NO_CONTEXT,attrib_list为Context属性列表,成功时返回新创建的EGLContext,失败时返回EGL_NO_CONTEXT
ttrib_list属性目前只有EGL_CONTEXT_CLIENT_VERSION,整数值,指定OpenGL ES版本,默认值为1,还可以是2、3等其它版本值,创建OpenGL ES Context时设置这个属性,也就是说渲染API为EGL_OPENGL_ES_API时才设置这个属性
1 2 |
|
创建了Surface和Context之后,因为可能有多个Surface和Context,所以需要通过eglMakeCurrent绑定Context到Surface,dpy为对应的Display,draw用于绘制,read用于读,ctx为要绑定的Context