最近在看 滤镜绘制的流程, 先把 egl 的api 写一遍,再详细写一遍流程
一、概念
EGL : 是渲染 API(如 OpenGL ES)和原生窗口系统之间的接口, 与 opengl 对接操作GPU 能力,android使用的是 openGL ES
EGL10 11 14
EGL10 egl = (EGL10) EGLContext.getEGL();
EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); //获取显示设备
// Init
int[] version = new int[2];
egl.eglInitialize(display, version); //version中存放EGL 版本号,int[0]为主版本号,int[1]为子版本号
String vendor = egl.eglQueryString(display, EGL10.EGL_VENDOR);
WLog.d("egl vendor: " + vendor); // 打印此版本EGL的实现厂商
String version = egl.eglQueryString(display, EGL10.EGL_VERSION);
WLog.d("egl version: " + version);// 打印EGL版本号
String extension = egl.eglQueryString(display, EGL10.EGL_EXTENSIONS);
WLog.d("egl extension: " + extension); //打印支持的EGL扩展
1,虽然Android使用(实现)的是EGL 1.4(从打印的版本号中可见), 但在Android 4.2(API 17)以前的版本没有EGL14,只有EGL10和EGL11,而这两个版本是不支持OpengGL ES 2.x的,因此在老版本中某些ES 2.x相关的常量参数只能用手写的硬编码代替,典型的如设定EGL渲染类型API的参数EGL10.EGL_RENDERABLE_TYPE,这个属性用不同的赋值指定的不同的渲染API,包括OpenGL,OpenGL ES 1.x, OpenGL ES 2.x,OpenVG等,如果采用ES 2.0,应该设置此值为: EGL14.EGL_OPENGL_ES2_BIT,但是在Android 4.2之前,没有EGL14接口,只能采取手写的硬编码来指定,类似: EGL_RENDERABLE_TYPE = 4;
2,egl.eglQueryString()用来查询EGL的相关信息,详见这里:http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/
3,EGL10.EGL_DEFAULT_DISPLAY 默认对应手机主屏幕。
OpenGL: 是一个操作 GPU 的 API,它通过驱动向 GPU 发送相关指令 OpenGL 是一个操作 GPU 的 API,它通过驱动向 GPU 发送相关指令,控制图形渲染管线状态机的运行状态,但是当涉及到与本地窗口系统进行交互时,就需要这么一个中间层,且它最好是与平台无关的。
二、api
2.1 创建本地系统与 OpenGL es的链接, 返回 EGLDisplay 对象, 通俗讲:就是获取显示窗口
EGLDisplay指的是物理的显示设备比如我们的手机屏幕,我们可以通过传入屏幕设备的id去获取到设备句柄,绝大多数情况下我们传入EGL14.EGL_DEFAULT_DISPLAY获取默认的屏幕就好,一般情况下我们的手机也只有一个屏幕。
拿不到设备就会返回EGL_NO_DISPLAY
EGL14.EGL_DEFAULT_DISPLAY : 默认显示,既窗口
//用于获取一个显示连接,其中display_id即要连接的display的标识
EGLDisplay eglDisplay(EGLNativeDisplayType displayId);
返回值 : EGL14.EGL_NO_DISPLAY: 没有获取到显示链接
EGL14.EGL_BAD_DISPLAY :没有指定有效的 display
2.2 初始化环境
//用于初始化显示连接,major/minor是一对输出参数,用于返回EGL的版本号。如果不关心,可以传NULL
EGLBoolean eglInitialize(EGLDisplay display, // 创建步骤时返回的对象
EGLint *majorVersion, // 返回 EGL 主版本号
EGLint *minorVersion); // 返回 EGL 次版本号
返回 EGLBoolean:
2.3 初始化 EGL完成,开始配置, 配置完成后就可以渲染Surface了
EGLDisplay支持的配置有很多种,例如颜色可能支持ARGB888、RGB888、RGB444、RGB565等,我们可以通过eglGetConfigs拿到EGLDisplay支持的所有配置,然后选择我们需要的。
如果直接去遍历所有的配置找我们需要的那个,代码写起来比较麻烦。
所以EGL提供了一个eglChooseConfig方法,我们输入关心的属性,其他的属性让EGL自己匹配就好。可能会匹配出多个EGLConfig,这个时候随便选一个都可以:
//获取所有配置
EGLBoolean eglGetConfigs(EGLDisplay display, // 指定显示的连接
EGLConfig *configs, // 指定 GLConfig 列表
EGLint maxReturnConfigs, // 最多返回的 GLConfig 数
EGLint *numConfigs); // 实际返回的 GLConfig 数
//查询指定配置
EGLBoolean eglGetConfigAttrib(EGLDisplay display, // 指定显示的连接
EGLConfig config, // 指定要查询的 GLConfig
EGLint attribute, // 返回特定属性
EGLint *value); // 返回值
//让EGL 选择配置
EGLBoolean eglChooseChofig(EGLDispay display, // 指定显示的连接
const EGLint *attribList, // 指定 configs 匹配的属性列表,可以为 NULL
EGLConfig *config, // 调用成功,返会符合条件的 EGLConfig 列表
EGLint maxReturnConfigs, // 最多返回的符合条件的 GLConfig 数
ELGint *numConfigs ); // 实际返回的符合条件的 EGLConfig 数
-------------------- 构造需要的特性列表 ------------------------------------
int[] attributes = new int[] {
EGL10.EGL_RED_SIZE, 8, //指定RGB中的R大小(bits)
EGL10.EGL_GREEN_SIZE, 8, //指定G大小
EGL10.EGL_BLUE_SIZE, 8, //指定B大小
EGL10.EGL_ALPHA_SIZE, 8, //指定Alpha大小,以上四项实际上指定了像素格式
EGL10.EGL_DEPTH_SIZE, 16, //指定深度缓存(Z Buffer)大小
EGL10.EGL_RENDERABLE_TYPE, 4, //指定渲染api类别, 如上一小节描述,这里或者是硬编码的4,或者是EGL14.EGL_OPENGL_ES2_BIT
EGL10.EGL_NONE }; //总是以EGL10.EGL_NONE结尾
demo
int[] configNum = new int[1];
//获取满足attributes的config个数。
egl.eglChooseConfig(display, attributes, null, 0, configNum);
int num = configNum[0];
if(num != 0){
EGLConfig[] configs = new EGLConfig[num];
//获取所有满足attributes的configs
egl.eglChooseConfig(display, attributes, configs, num, configNum);
config = configs[0]; //以某种规则选择一个config,这里使用了最简单的规则。
}
(1)eglChooseConfig(display, attributes, configs, num, configNum); 用于获取满足attributes的所有config,
parm 1、2
parm3:存放输出的configs,
parm4:指定最多输出多少个config,
parm5由EGL系统写入,表明满足attributes的config一共有多少个。
如果使用eglChooseConfig(display, attributes, null, 0, configNum)这种形式调用,则会在configNum中输出所有满足条件的config个数。
(2)一般习惯是获取所有满足attributes的config个数,再据此分配存放config的数组,获取所有config,根据某种特定规则,从中选择其一。
(3)API详细说明和所有可指定的attributes见这里:http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/
打印config中的常用attributes:
/**
* 打印EGLConfig信息
*
* @param egl
* @param display
* @param config
* : 指定的EGLConfig
*/
public static void printEGLConfigAttribs(EGL10 egl, EGLDisplay display, EGLConfig config) {
int value = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, -1);
WLog.d("eglconfig: EGL_RED_SIZE: " + value);
value = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, -1);
WLog.d("eglconfig: EGL_GREEN_SIZE: " + value);
value = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, -1);
WLog.d("eglconfig: EGL_BLUE_SIZE: " + value);
value = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, -1);
WLog.d("eglconfig: EGL_ALPHA_SIZE: " + value);
value = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, -1);
WLog.d("eglconfig: EGL_DEPTH_SIZE: " + value);
value = findConfigAttrib(egl, display, config, EGL10.EGL_RENDERABLE_TYPE, -1);
WLog.d("eglconfig: EGL_RENDERABL_TYPE: " + value);
value = findConfigAttrib(egl, display, config, EGL10.EGL_SAMPLE_BUFFERS, -1);
WLog.d("eglconfig: EGL_SAMPLE_BUFFERS: " + value);
value = findConfigAttrib(egl, display, config, EGL10.EGL_SAMPLES, -1);
WLog.d("eglconfig: EGL_SAMPLES: " + value);
value = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, -1);
WLog.d("eglconfig: EGL_STENCIL_SIZE: " + value);
}
/**
* 在指定EGLConfig中查找指定attrib的值,如果没有此属性,返回指定的默认值
*
* @param egl
* @param display
* @param config
* : 指定的EGLConfig
* @param attribute
* : 指定的attrib
* @param defaultValue
* : 查找失败时返回的默认值
* @return: 查找成功,返回查找值;查找失败,返回参数中指定的默认值
*/
static public int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config,
int attribute, int defaultValue) {
int[] val = new int[1];
if (egl.eglGetConfigAttrib(display, config, attribute, val)) {
return val[0];
}
return defaultValue;
}
复制代码
2.4 创建上下文,上下文包含了操作所需的所有状态信息 必须有一个可用的上下文才能进行绘图。
EGLContext是OpenGL的线程相关上下文环境,我们在OpenGL中创建的数据如图片、顶点、着色器等最后获取到的只是一个id,它的具体内容其实依赖这个EGLContext
PS: 上下文环境是线程相关的,一般来讲OpenGL的操作都在同一个线程中进行,但是有些复杂的业务场景可能需要多线程,于是可以在eglCreateContext的第三个参数里面传入share_context做到多线程共享。如果不需要多线程共享的话传入EGL14.EGL_NO_CONTEXT就好
EGLContext eglCreateContext(EGLDisplay display, // 指定显示的连接
EGLConfig config, // 前面选好的 EGLConfig
EGLContext shareContext, // 允许其它 EGLContext 共享数据,使用 EGL_NO_CONTEXT 表示不共享
const EGLint* attribList); // 指定操作的属性列表,只能接受一个属性 EGL_CONTEXT_CLIENT_VERSION
-------demo---------------------------------------------------------
int attrs[] = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL10.EGL_NONE, };
EGLContext context = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, attrs);
---------------------------------------------------------------------
share_context: 是否有context共享,共享的contxt之间亦共享所有数据。EGL_NO_CONTEXT代表不共享。
attrib_list: 目前可用属性只有EGL_CONTEXT_CLIENT_VERSION, 1代表OpenGL ES 1.x, 2代表2.0。同样在Android4.2之前,没有EGL_CONTEXT_CLIENT_VERSION这个属性,只能使用硬编码0x3098代替。
函数详细描述:http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/
2.5 建渲染区域 Surface ( 获取显存),根据 2.3的配置创建,
指定一个EGLSurface告诉OpenGL应该往哪里画东西。
eglCreateWindowSurface创建EGLSurface然后用eglMakeCurrent指定OpenGL绘制的结果最后输出到这个EGLSurface上。
EGLSurface eglCreateWindowSurface(EGLDisplay display, // 指定显示的连接
EGLConfig config, // 符合条件的 EGLConfig
EGLNatvieWindowType window, // 指定原生窗口
const EGLint *attribList); // 指定窗口属性列表,可为 NULL
失败返回: EGL_NO_SURFACE
EGL_BAD_MATCH :提供了与窗口属性不匹配的 EGLConfig,或该 EGLConfig 不支持渲染到窗口
EGL_BAD_CONFIG :提供的 EGLConfig 没有得到系统支持
EGL_BAD_NATIVE_WINDOW :提供的原生窗口句柄无效
EGL_BAD_ALLOC :无法为新的窗口分配资源,或已经有和提供的原生窗口关联的 EGLConfig
详细的参数说明见这里:http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/
其中一个attribute是EGL_RENDER_BUFFER, 用于描述渲染buffer(所有的绘制在此buffer中进行)类别,取值为EGL_SINGLE_BUFFER以及默认的EGL_BACK_BUFFER,前者属于单缓冲,绘制的同时用户即可见;后者属于双缓冲,前端缓冲用于显示,OpenGL ES 在后端缓冲中进行绘制,绘制完毕后使用eglSwapBuffers()交换前后缓冲,用户即看到在后缓冲中的内容,如此反复。其他attributes见官方文档。
public EGLSurface createEGLSurface(Surface surface) {
int[] attribList = {
EGL14.EGL_NONE
};
return EGL14.eglCreateWindowSurface(
mEGLDisplay,
mEGLConfig,
surface,
attribList,
0);
}
2.6 将 关联 Surface 和 Context , 这样就可以绘制数据到 Surface中了
EGLBoolean eglMakeCurrent(EGLDisplay display, // 指定显示的连接
EGLSurface draw, // EGL 绘图表面
EGLSurface read, // EGL 读取表面
EGLContext context); // 指定连接到该表面的上下文
PS: 每进行相关操作完后, 最好进行一下 检测, 检测当前GL环境是否正常, 如果不正常需要及时退出‘’
EGLint eglGetError(); 返回 EGL_SUCCESS 说明成功
2.7 eglSwapBuffers 交换缓冲区,将绘制结果推到native window上
EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface surface);
OpenGL ES应用的大致工作过程为:
获取显示连接、初始化显示连接、创建上下文、创建窗口表面、进行初始化操作(设置OpenGL ES选项、加载贴图、创建Shader、构建绘图模型等)、进入消息循环(处理消息、进行绘制)、退出消息循环、进行资源清理、销毁窗口表面、销毁上下文、关闭显示连接;
三 demo
EGL14
private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
private EGLContext mParentContext = EGL14.EGL_NO_CONTEXT;
private void eglSetup() {
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("unable to get EGL14 display");
}
int[] version = new int[2];
if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
throw new RuntimeException("unable to initialize EGL14");
}
// Configure EGL for recording and OpenGL ES 2.0.
int[] attribList = {
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE,8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE,
EGL14.EGL_OPENGL_ES2_BIT, EGL_RECORDABLE_ANDROID, 1,
EGL14.EGL_NONE };
/** int[] attribList = {
EGL14.EGL_BUFFER_SIZE, 32,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT,
EGL14.EGL_NONE
};**/
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0,
configs.length, numConfigs, 0);
checkEglError("eglCreateContext RGB888+recordable ES2");
// Configure context for OpenGL ES 2.0.
int[] attrib_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,EGL14.EGL_NONE };
mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0],
mParentContext, attrib_list, 0);
checkEglError("eglCreateContext");
// Create a window surface, and attach it to the Surface we
// received.
int[] surfaceAttribs = { EGL14.EGL_NONE };
mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0],
mSurface, surfaceAttribs, 0);
checkEglError("eglCreateWindowSurface");
}
egl10
mEgl = (EGL10) EGLContext.getEGL();
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
throw new RuntimeException("eglGetDisplay failed");
}
int[] version = new int[2];
if (!mEgl.eglInitialize(mEglDisplay, version)) {
throw new RuntimeException("eglInitialize failed");
} else {
Log.v(TAG, "EGL version: " + version[0] + '.' + version[1]);
}
int[] attribList = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
mEglConfig = chooseConfig(mEgl, mEglDisplay);
mEglContext = mEgl.eglCreateContext(
mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT, attribList);
if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
throw new RuntimeException("failed to createContext");
}
mEglSurface = mEgl.eglCreateWindowSurface(
mEglDisplay, mEglConfig, target, null);
if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
throw new RuntimeException("failed to createWindowSurface");
}
if (!mEgl.eglMakeCurrent(
mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
throw new RuntimeException("failed to eglMakeCurrent");
}
mGl = (GL10) mEglContext.getGL();