注:文章都是通过阅读各位前辈总结的资料 Android 8.x && Linux(kernel 4.x)Qualcomm平台源码、加上自己的思考分析总结出来的,其中难免有理解不对的地方,欢迎大家批评指正。文章为个人学习、研究、欣赏之用,图文内容整理自互联网(◕‿◕),如有侵权,请联系删除,禁止转载(©Qualcomm ©Android @Linux 版权所有),谢谢。
首先感谢:
正是由于前人的分析和总结,帮助我节约了大量的时间和精力,再次感谢!!!
Google Pixel、Pixel XL 内核代码(==文章基于 Kernel-4.x==):
Kernel source for Pixel 2 (walleye) and Pixel 2 XL (taimen) - GitHub
AOSP 源码(==文章基于 Android 8.x==):
Android 系统全套源代码分享 (更新到 8.1.0_r1)
==源码(部分)==:
opengl
- android/frameworks/native/opengl/tests/configdump
- android/frameworks/native/opengl/tests/tritex
- android/frameworks/native/opengl/tests/testViewport
- android/frameworks/native/opengl/tests/textures
- android/frameworks/native/opengl/tests/filter
- android/frameworks/native/opengl/tests/fillrate
- android/frameworks/native/opengl/tests/finish
(一)、configdump运行结果
1、预备条件
为了便于观察学习现象,我将Android系统SystemUI、Launcher3统统移除了,将桌面换成了一整个空白的画面。
1 | adb root |
简易Launcher源码(android/frameworks/native/opengl/tests/testViewport):
1 | adb root |
然后重启系统,等开机动画完成后,运行命令
1 | adb shell am start -n com.android.test/.TestActivity |
整个桌面就像是一张白色的纸张了。
2、运行test-opengl-configdump
1 | adb push test-opengl-configdump/test-opengl-configdump /data/nativetest64/ |
3、configdump源码
1 |
|
(二)、初识EGL函数API文档
1、EGL介绍
EGL 是 OpenGL ES 和底层 Native 平台视窗系统之间的接口。OpenGL ES 本质上是一个图形渲染管线的状态机,而 EGL 则是用于监控这些状态以及维护 Frame buffer 和其他渲染 Surface 的外部层。EGL提供如下机制:
- 与设备的原生窗口系统通信
- 查询绘图表面的可用类型和配置
- 创建绘图表面
- 在OpenGL ES 和其他图形渲染API之间同步渲染
- 管理纹理贴图等渲染资源
EGL类型
EGLBoolean
EGL中的布尔类型。
typedef unsigned int EGLBoolean;
EGLDisplay
不透明类型,封装了与底层系统的交互,用于充当与原生窗口之间的接口。
typedef void * EGLDisplay;
EGLint
EGL整数类型。
typedef int32_t EGLint;
EGLNativeDisplayType
用于匹配原生窗口系统的显示类型。
typedef void * EGLNativeDisplayType;
EGL常量
布尔值
EGL_FALSE:条件为假
EGL_TRUE:条件为真
创建窗口的属性
EGL_RENDER_BUFFER:指定渲染所用的缓冲区
错误代码
1 | EGL_SUCCESS:没有错误 |
配置属性
1 | EGL_ALPHA_SIZE:颜色缓冲区中的透明度分量的位数 |
2、EGL介绍
2.1、 EGL函数
在学习具体的实例之前,先来学习 EGL函数。
eglGetDisplay
获得并与可用设备进行连接。
EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id);
display_id:当前需要连接的设备类型 return:已经连接上的设备对象 EGLDisplay eglDisplay =
eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglChooseConfig
在初始化EGL时,我们需要列出并让EGL选择最合适的配置。
EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config);
dpy:已连接的设备
attrib_list:传入的配置信息数组
configs:保存返回的配置信息的数组
config_size:传入的配置数组的长度
num_config:保存返回的配置信息的数组的长度
1 | EGLConfig config; |
eglInitialize
一般在成功打开设备连接之后需要初始化EGL。初始化过程将会对EGL内部的数据结构进行设置,然后返回EGL的主次版本号。
EGLBoolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint
*minor);
dpy:指定EGL设备对象。
major:设备主版本号。
minor:设备次版本号。
GLint majorVersion;
GLint minorVersion;
if (!eglInitialize(eglDisplay, &majorVersion, &minorVersion)) {return EGL_FALSE;
}
eglCreateContext
渲染上下文是OpenGL ES的内部数据结构,包含操作所需的所有状态信息。例如程序中使用的顶点着色器或者片元着色器的引用。OpenGL ES必须有一个可用的上下文才能绘图。使用下面的函数可以创建一个上下文:
EGLContext eglCreateContext(EGLDisplay display, EGLConfig config, EGLContext shareContext, const EGLint *attribList);
- display:指定显示连接
- config:指定配置对象
- shareContext:允许多个EGL上下文共享特定的数据,EGL_NO_CONTEXT参数表示没有共享
- attribList:指定创建上下文使用的属性列表
- return:创建的上下文对象
1 | EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; |
eglCreateWindowSurface
一旦我们有了符合渲染需求的EGLConfig,就为窗口创建做好了准备。调用如下函数可以创建一个窗口。
EGLSurface eglCreateWindowSurface(EGLDisplay display, EGLConfig config, EGLNativeWindowType window,const EGLint *attribList);
- display:已连接的设备对象
- config:指定的配置对象
- window:指定原生窗口对象
- attriList:指定窗口的属性列表
- return:EGL渲染区域对象
这个函数以我们到原生显示管理器的连接和前一步获得的EGLConfig为参数。此外,它需要原生窗口系统事先创建一个窗口。因为EGL是许多不同窗口系统和OpenGL ES之间的软件接口层。最后这个函数需要一个属性列表;但是,这个列表中的属性与参数属性不完全相同,并且额外使用到了创建窗口的属性。该函数在多种情况下都有可能失败。
1 | context->eglSurface = eglCreateWindowSurface(context->eglDisplay, config, context->nativeWindow, |
eglGetConfigAttrib
如果我们获获取了一个EGL配置对象,我们可以通过下列函数查询该对象中指定属性的值。
EGLBoolean eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value);
- dpy:已连接的设备对象
- config:待查询的配置对象
- attribute:需要查询的参数属性
- value:返回的查询结果
- return:查询结果,如果attribute不是有效属性,则产生一个EGL_BAD_ATTRIBUTE错误。
eglGetConfigs
在初始化EGL之后,我们需要给EGL选择一组配置。
EGLBoolean eglGetConfigs(EGLDisplay dpy, EGLConfig *configs, EGLint
config_size, EGLint *num_config);
- dpy:已连接设备对象
- configs:保存配置信息的列表
- size:configs的长度
- num_config:EGL返回的配置信息数量
- return:查询结果状态
通常情况下,我们有两种方式使用该函数。首先,我们指定configs参数为NULL,此时EGL会查询所有可用的EGLConfigs数量并赋值给num_config,但此时不会有任何其他信息返回。
另外,我们也可以创建一个未初始化的EGLConfig,并作为函数的参数传入。此时,EGL将会查询不超过config_size数量的配置信息存入到configs,并通过num_config返回保存数据的数量。
eglGetError
EGL中大部分函数在成功时都会返回EGL_TRUE,否则返回EGL_FALSE。但是,我们仅从这个返回值上并不能看出错误原因是什么。如果想要明确的知道EGL的错误代码,应该调用下列函数。
EGLint eglGetError(void);
return:见 [EGL常量-错误代码]
eglMakeCurrent
因为一个应用程序可能创建多个EGLContext用作不同的用途,所以我们需要指定关联特定的EGLContext和渲染表面——这一过程被称为“指定当前上下文”。
EGLBoolean eglMakeCurrent(EGLDisplay display, EGLSurface draw,
EGLSurface read, EGLContext context);
- display:指定EGL显示设备 draw:指定EGL绘图表面 read:指定EGL读取表面 context:指定连接到该表面的渲染上下文
- return:函数时候执行成功
(三)、使用EGL一般顺序:
3.1、使用EGL首先必须创建,建立本地窗口系统和OpenGL ES的连接。—==eglDisplay()==
EGLDisplay eglDisplay(EGLNativeDisplayType displayId)
EGL提供了平台无关类型EGLDisplay表示窗口。定义EGLNativeDisplayType是为了匹配原生窗口系统的显示类型,对于Windows,EGLNativeDisplayType被定义为HDC,对于Linux系统,被定义为Display*类型,对于Android系统,定义为ANativeWindow *类型,为了方便的将代码转移到不同的操作系统上,应该传入EGL_DEFAULT_DISPLAY,返回与默认原生窗口的连接。如果连接不可用,则返回EGL_NO_DISPLAY。
3.2、初始化EGL —==eglInitialize()==
创建与本地原生窗口的连接后需要初始化EGL,使用函数eglInitialize进行初始化操作。如果 EGL 不能初始化,它将返回EGL_FALSE,并将EGL错误码设置为EGL_BAD_DISPLAY表示制定了不合法的EGLDisplay,或者EGL_NOT_INITIALIZED表示EGL不能初始化。使用函数eglGetError用来获取最近一次调用EGL函数出错的错误代码
EGLBoolean eglInitialize(EGLDisplay display, // 创建的EGL连接
EGLint *majorVersion, // 返回EGL主板版本号
EGLint *minorVersion); // 返回EGL次版本号
初始化EGL示例
1 | EGLint majorVersion; |
3.3、确定可用的渲染表面(Surface)的配置。一旦初始化了EGL,就可以确定可用渲染表面的类型和配置了。—==eglChooseChofig()==
一种方式是使用eglGetConfigs函数获取底层窗口系统支持的所有EGL表面配置,然后再使用eglGetConfigAttrib依次查询每个EGLConfig相关的信息,EGLConfig包含了渲染表面的所有信息,包括可用颜色、缓冲区等其他特性。
EGLBoolean eglGetConfigs(EGLDisplay display, EGLConfig *configs,
EGLint maxReturnConfigs,EGLint *numConfigs);EGLBoolean eglGetConfigAttrib(EGLDisplay display, EGLConfig config,
EGLint attribute, EGLint *value)
另一种方式是指定我们需要的渲染表面配置,让EGL自己选择一个符合条件的EGLConfig配置。eglChooseChofig调用成功返回EGL_TRUE,失败时返回EGL_FALSE,如果attribList包含了未定义的EGL属性,或者属性值不合法,EGL代码被设置为EGL_BAD_ATTRIBUTR
EGLBoolean eglChooseChofig(EGLDispay display, // 创建的和本地窗口系统的连接
const EGLint *attribList, // 指定渲染表面的参数列表,可以为null
EGLConfig *config, // 调用成功,返会符合条件的EGLConfig列表
EGLint maxReturnConfigs, //最多返回的符合条件的EGLConfig个数
ELGint *numConfigs ); // 实际返回的符合条件的EGLConfig个数
attribList参数在EGL函数中可以为null或者指向一组以EGL_NONE结尾的键对值
1 | // we need this config |
3.4、创建渲染表面—==eglCreateWindowSurface()==
有了符合条件的EGLConfig后,就可以通过eglCreateWindowSurface函数创建渲染表面。使用这个函数的前提是要使用原生窗口系统提供的API创建一个窗口。eglCreateWindowSurface中attribList一般可以使用null即可。函数调用失败会返回EGL_NO_SURFACE,并设置对应的错误码。
EGLSurface eglCreateWindowSurface(EGLDisplay display,
EGLConfig config, // 前面选好的可用EGLConfig
EGLNatvieWindowType window, // 指定原生窗口
const EGLint *attribList) // 指定窗口属性列表,可以为null,一般指定渲染所用的缓冲区使用但缓冲或者后台缓冲,默认为后者。
创建EGL渲染表面示例
1 | EGLRenderSurface window; |
使用eglCreateWindowSurface函数创建在窗口上的渲染表面,此外还可以使用eglCreatePbufferSurface创建屏幕外渲染表面(Pixel Buffer 像素缓冲区)。使用Pbuffer一般用于生成纹理贴图,不过该功能已经被FrameBuffer替代了,使用帧缓冲对象的好处是所有的操作都由OpenGL ES来控制。使用Pbuffer的方法和前面创建窗口渲染表面一样,需要改动的地方是在选取EGLConfig时,增加EGL_SURFACE_TYPE参数使其值包含EGL_PBUFFER_BIT。而该参数默认值为EGL_WINDOW_BIT。
EGLSurface eglCreatePbufferSurface( EGLDisplay display,
EGLConfig config,
EGLint const * attrib_list // 指定像素缓冲区属性列表
);
3.5、创建渲染上下文—==eglCreateContext()==
使用eglCreateContext为当前的渲染API创建EGL渲染上下文,返回一个上下文,当前的渲染API是由函数eglBindAPI设置的。OpenGL ES是一个状态机,用一系列变量描述OpenGL ES当前的状态如何运行,我们通常使用如下途径去更改OpenGL状态:设置选项,操作缓冲。最后,我们使用当前OpenGL上下文来渲染。比如我想告诉OpenGL ES接下来要绘制三角形,可以通过一些上下文变量来改变OpenGL ES的状态,一旦改变了OpenGL ES的状态为绘制三角形,下一个命令就会画出三角形。通过这些状态设置函数就会改变上下文,接下来的操作总会根据当前上下文的状态来执行,除非再次重新改变状态。
// 设置当前的渲染API
EGLBoolean eglBindAPI( EGLenum api //可选 EGL_OPENGL_API,
EGL_OPENGL_ES_API, or EGL_OPENVG_API );EGLContext eglCreateContext(EGLDisplay display,
EGLConfig config, // 前面选好的可用EGLConfig
EGLContext shareContext, // 允许多个EGLContext共享特定类型的数据,传递EGL_NO_CONTEXT表示不与其他上下文共享资源
const EGLint* attribList // 指定操作的属性列表,只能接受一个属性EGL_CONTEXT_CLIENT_VERSION用来表示使用的OpenGL ES版本
);
创建上下文示例
1 | const ELGint attribList[] = { |
3.6、指定某个EGLContext为当前上下文—==eglMakeCurrent()==
用eglMakeCurrent函数进行当前上下文的绑定。一个程序可能创建多个EGLContext,所以需要关联特定的EGLContext和渲染表面,一般情况下两个EGLSurface参数设置成一样的。
EGLBoolean eglMakeCurrent(EGLDisplay display,
EGLSurface draw, // EGL绘图表面
EGLSurface read, // EGL读取表面
EGLContext context // 指定连接到该表面的渲染上下文
);
3.7、使用OpenGL相关的API进行==绘制==操作。
3.8、交换EGL的Surface的内部缓冲和EGL创建的和平台无关的窗口diaplay。EGL实际上维护了两个buffer,前台buffer显示的时候,绘制操作会在后台buffer上进行。
EGLBoolean ==eglSwapBuffers==(EGLDisplay display, // 指定的EGL和本地窗口的连接
EGLSurface surface // 指定要交换缓冲的EGL绘制表面
);
(四)、tritex 运行结果
首先我们来看一下tritex的运行效果,左下角有一个类似矩阵的东东。
4.1、test-opengl-tritex源码分析
首先从main()函数分析,
1 | EGLDisplay eglDisplay; |
4.1.1、EGL初始化init_gl_surface()
首先看看函数init_gl_surface()是如何初始化化得。
1 | int init_gl_surface(const WindowSurface& windowSurface) |
是不是跟之前使用OpenGL的顺序基本完全一致。
4.1.2、init_scene()
1 | void init_scene(void) |
【Viewport 变换】
【投影变换 Projection】
【OpenGL ES 投影变换 Projection】
4.1.3、create_texture()
1 | void create_texture(void) |
4.1.4、绘制渲染render()
首先通过glDrawElements()绘制,然后通过==eglSwapBuffers(eglDisplay, eglSurface)==显示到屏幕上。
1 | void render(int quads) |
(五)、textures实例 test-opengl-textures
这个纹理的例子比较明确,可以先看一下效果图,使用基本一致,就不分析了。
1 |
|
(六)、fillrate 运行结果
6.1、fillrate 源码
1 |
|
(七)、filter运行结果
7.1、filter源码
1 |
|
(八)、finish运行结果
8.1、finish源码
1 |
|