OpenGLES中的EGL与同异步理解

1. EGL介绍

EGL 是 OpenGL ES 和本地窗口系统(Native Window System)之间的通信接口,它的主要作用:

1. 与设备的原生窗口系统通信

2. 查询绘图表面的可用类型和配置

3. 创建绘图表面

4. 在OpenGL ES 和其他图形渲染API之间同步渲染

5. 管理纹理贴图等渲染资源

OpenGL ES 的平台无关性正是借助 EGL 实现的,EGL 屏蔽了不同平台的差异(Apple 提供了自己的 EGL API 的 iOS 实现,称为EAGL)。

本地窗口相关的 API 提供了访问本地窗口系统的接口,而 EGL 可以创建渲染表面 EGLSurface ,同时提供了图形渲染上下文 EGLContext,用来进行状态管理,接下来 OpenGL ES 就可以在这个渲染表面上绘制。

在上图中:

1. Display(EGLDisplay) 是对实际显示设备的抽象

2. Surface(EGLSurface)是对用来存储图像的内存区域 FrameBuffer 的抽象,包括 Color Buffer(颜色缓冲区), Stencil Buffer(模板缓冲区) ,Depth Buffer(深度缓冲区)

3. Context (EGLContext) 存储 OpenGL ES 绘图的一些状态信息

在 Android 平台上开发 OpenGL ES 应用时,类 GLSurfaceView 已经为我们提供了对 Display , Surface , Context 的管理,即 GLSurfaceView 内部实现了对 EGL 的封装,可以很方便地利用接口 GLSurfaceView.Renderer 的实现,使用 OpenGL ES API 进行渲染绘制,很大程度上提升了 OpenGLES 开发的便利性。

如果需要涉及跨平台开发,则需要在native层使用C++实现对EGL的封装,不借助于 GLSurfaceView ,实现图片后台渲染,利用 GPU 完成对图像的高效处理。

2. EGL的创建与使用流程

2.1 检测出错方法

EGL 中大部分函数成功时都是返回 EGL_TRUE,失败返回 EGL_FALSE。

至于故障原因,需要调用如下函数获取:

EGLint eglGetError();

返回最近调用 EGL 函数的错误代码,如果返回 EGL_SUCCESS 说明没有错误。

2.2 获取 EGLDisplay 对象,建立与本地窗口系统的连接

EGLDisplay eglDisplay(EGLNativeDisplayType displayId);

playId 指定显示连接,一般使用默认的 EGL_DEFAULT_DISPLAY,即返回与默认原生窗口的连接。

相关错误码

  • EGL_NO_DISPLAY :连接不可用

2.3 初始化 EGL

EGLBoolean eglInitialize(EGLDisplay display, // 创建步骤时返回的对象
                         EGLint *majorVersion, // 返回 EGL 主版本号
                         EGLint *minorVersion); // 返回 EGL 次版本号

相关错误码

  • EGL_NO_DISPLAY:EGL 不能初始化

  • EGL_BAD_DISPLAY :没有指定有效的 display

示例:

EGLint majorVersion;
EGLint minorVersion;
EGLDisplay display;
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (display == EGL_NO_DISPLAY) {
    // Unable to open connection to local windowing system
}
if (!eglInitialize(display, &majorVersion, &minorVersion)) {
    // Unable to initialize EGL. Handle and recover
}

2.4 获取EGLConfig 对象,确定渲染表面的配置信息

一旦初始化了EGL,就可以确定可用渲染表面的类型和配置了。有两种方法:

  • 先使用 egGetConfigs 查询每个配置,再使用 eglGetConfigAttrib 找出最好的选择

  • 指定一组需求使用 eglChooseChofig 让 EGL 推荐最佳配置

通常使用第二种方式更简单。两种方法均得到 EGLConfig 对象。EGLConfig 包含了渲染表面的所有信息,包括可用颜色、缓冲区等其他特性。

获取所有配置的函数

EGLBoolean eglGetConfigs(
    EGLDisplay display, // 指定显示的连接
    EGLConfig *configs, // 指定 GLConfig 列表
    EGLint maxReturnConfigs, // 最多返回的 GLConfig 数
    EGLint *numConfigs); // 实际返回的 GLConfig 数

查询 EGLConfig 配置

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 数

如果 eglChooseChofig 成功返回,则返回一组匹配你的标准的 EGLConfig。

示例

EGLint attribList[] = {
    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
    EGL_RED_SIZE, 5,
    EGL_GREEN_SIZE, 6,
    EGL_BLUE_SIZE, 5,
    EGL_DEPTH_SIZE, 1,
    EGL_NONE
};

const EGLint MaxConfigs = 10;
EGLConfig configs[MaxConfigs]; // We'll only accept 10 configs
EGLint numConfigs;
if (!eglChooseConfig(dpy, attribList, configs, MaxConfigs, &numConfigs)) {
    // Something didn't work … handle error situation
} else {
    // Everything's okay. Continue to create a rendering surface
}

2.5 创建渲 EGurface

当有了符合条件的 EGLConfig 后,就可以通过 eglCreateWindowSurface 函数创建渲染区域。

EGLSurface eglCreateWindowSurface(EGLDisplay display, // 指定显示的连接
	EGLConfig config, // 符合条件的 EGLConfig
	EGLNatvieWindowType window, // 指定原生窗口
	const EGLint *attribList); // 指定窗口属性列表,可为 NULL

eglCreateWindowSurface 方法在很多情况下可能失败,失败返回 EGL_NO_SURFACE。我们可以通过 eglGetError 获取相关错误。

相关错误码:

  • EGL_BAD_MATCH提了与窗口属性不匹配的 EGLConfig,或该 EGLConfig 不支持渲染到窗口

  • EGL_BAD_CONFIG :供的 EGLConfig 没有得到系统支持

  • EGL_BAD_NATIVE_WNDOW :提供的原生窗口句柄无效

  • EGL_BAD_ALLOC :无为新的窗口分配资源,或已经有和提供的原生窗口关联的 EGLConfig

示例:

EGLint attribList[] = {
  EGL_RENDER_BUFFER, EGL_BACK_BUFFER,
  EGL_NONE
);

// 这里先省略了创建原生窗口的过程
EGLRenderSurface window = eglCreateWindowSurface(display, config, nativeWindow, attribList);

if (window == EGL_NO_SURFACE) {
    switch (eglGetError()) {
    case EGL_BAD_MATCH:
        // Check window and EGLConfig attributes to determine
        // compatibility, or verify that the EGLConfig
        // supports rendering to a window,
        break;
    case EGL_BAD_CONFIG:
        // Verify that provided EGLConfig is valid
        break;
    case EGL_BAD_NATIVE_WINDOW:
        // Verify that provided EGLNativeWindow is valid
        break;
    case EGL_BAD_ALLOC:
        // Not enough resources available. Handle and recover
        break;
    }
}

2.6 创建渲染上下文LCoext

上下文包含了操作所需的所有状态信息,OpenGL ES 3.0 必须有一个可用的上下文才能进行绘图。

EGLContext eglCreateContext(EGLDisplay display, // 指定显示的连接
	EGLConfig config, // 前面选好的 EGLConfig
	EGLContext shareContext, // 允许其它 EGLContext 共享数据,使用 EGL_NO_CONTEXT 表示不共享
	const EGLint* attribList); // 指定操作的属性列表,只能接受一个属性 EGL_CONTEXT_CLIENT_VERSION

示例:

const ELGint attribList[] = {
    EGL_CONTEXT_CLIENT_VERSION, 3,
    EGL_NONE
};

EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, attribList);

if (context == EGL_NO_CONTEXT) {
    EGLError error = eglGetError();
    if (error == EGL_BAD_CONFIG) {
        // Handle error and recover
    }
}

2.7 绑定上下文

通过 eglMakeCurrent 方法将 EGLSurface、EGLContext、EGLDisplay 三者绑定,绑定成功之后 OpenGLES 环境就创建好了,接下来便可以进行渲染。

EGLBoolean eglMakeCurrent(
	EGLDisplay display, // 指定显示的连接
	EGLSurface draw, // EGL 绘图表面
	EGLSurface read, // EGL 读取表面
	EGLContext context); // 指定连接到该表面的上下文

2.8 交换缓冲

OpenGLES 绘制结束后,使用 eglSwapBuffers 方法交换前后缓冲,将绘制内容显示到屏幕上,而屏幕外的渲染不需要调用此方法。

EGLBoolean eglSwapBuffers(
    EGLDisplay display, // 指定显示的连接
    EGLSurface surface); // 指定渲染表面

2.9 释放 EGL 环境

绘制结束后,不再需要使用 EGL 时,需要取消 eglMakeCurrent 的绑定,销毁 EGLDisplay、EGLSurface、EGLContext 三个对象。

if (m_eglDisplay != EGL_NO_DISPLAY) {
	eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
	eglDestroyContext(m_eglDisplay, m_eglCtx);
	eglDestroySurface(m_eglDisplay, m_eglSurface);
	eglReleaseThread();
	eglTerminate(m_eglDisplay);
}

2.10 完成示例Demo

// 创建 GLES 环境
int BgRender::CreateGlesEnv()
{
	// EGL config attributes
    const EGLint confAttr[] =
    {
            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
            EGL_SURFACE_TYPE,EGL_PBUFFER_BIT,//EGL_WINDOW_BIT EGL_PBUFFER_BIT we will create a pixelbuffer surface
            EGL_RED_SIZE,   8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE,  8,
            EGL_ALPHA_SIZE, 8,// if you need the alpha channel
            EGL_DEPTH_SIZE, 8,// if you need the depth buffer
            EGL_STENCIL_SIZE,8,
            EGL_NONE
    };

	// EGL context attributes
    const EGLint ctxAttr[] = {
            EGL_CONTEXT_CLIENT_VERSION, 2,
            EGL_NONE
    };

	// surface attributes
	// the surface size is set to the input frame size
	const EGLint surfaceAttr[] = {
			EGL_WIDTH, 1,
			EGL_HEIGHT,1,
			EGL_NONE
	};
	EGLint eglMajVers, eglMinVers;
	EGLint numConfigs;

	int resultCode = 0;
	do
	{
		//1. 获取 EGLDisplay 对象,建立与本地窗口系统的连接
		m_eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
		if(m_eglDisplay == EGL_NO_DISPLAY)
		{
			//Unable to open connection to local windowing system
			LOGCATE("BgRender::CreateGlesEnv Unable to open connection to local windowing system");
			resultCode = -1;
			break;
		}

		//2. 初始化 EGL 方法
		if(!eglInitialize(m_eglDisplay, &eglMajVers, &eglMinVers))
		{
			// Unable to initialize EGL. Handle and recover
			LOGCATE("BgRender::CreateGlesEnv Unable to initialize EGL");
			resultCode = -1;
			break;
		}

		LOGCATE("BgRender::CreateGlesEnv EGL init with version %d.%d", eglMajVers, eglMinVers);

		//3. 获取 EGLConfig 对象,确定渲染表面的配置信息
		if(!eglChooseConfig(m_eglDisplay, confAttr, &m_eglConf, 1, &numConfigs))
		{
			LOGCATE("BgRender::CreateGlesEnv some config is wrong");
			resultCode = -1;
			break;
		}

		//4. 创建渲染表面 EGLSurface, 使用 eglCreatePbufferSurface 创建屏幕外渲染区域
		m_eglSurface = eglCreatePbufferSurface(m_eglDisplay, m_eglConf, surfaceAttr);
		if(m_eglSurface == EGL_NO_SURFACE)
		{
			switch(eglGetError())
			{
				case EGL_BAD_ALLOC:
					// Not enough resources available. Handle and recover
					LOGCATE("BgRender::CreateGlesEnv Not enough resources available");
					break;
				case EGL_BAD_CONFIG:
					// Verify that provided EGLConfig is valid
					LOGCATE("BgRender::CreateGlesEnv provided EGLConfig is invalid");
					break;
				case EGL_BAD_PARAMETER:
					// Verify that the EGL_WIDTH and EGL_HEIGHT are
					// non-negative values
					LOGCATE("BgRender::CreateGlesEnv provided EGL_WIDTH and EGL_HEIGHT is invalid");
					break;
				case EGL_BAD_MATCH:
					// Check window and EGLConfig attributes to determine
					// compatibility and pbuffer-texture parameters
					LOGCATE("BgRender::CreateGlesEnv Check window and EGLConfig attributes");
					break;
			}
		}

		//5. 创建渲染上下文 EGLContext
		m_eglCtx = eglCreateContext(m_eglDisplay, m_eglConf, EGL_NO_CONTEXT, ctxAttr);
		if(m_eglCtx == EGL_NO_CONTEXT)
		{
			EGLint error = eglGetError();
			if(error == EGL_BAD_CONFIG)
			{
				// Handle error and recover
				LOGCATE("BgRender::CreateGlesEnv EGL_BAD_CONFIG");
				resultCode = -1;
				break;
			}
		}

		//6. 绑定上下文
		if(!eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglCtx))
		{
			LOGCATE("BgRender::CreateGlesEnv MakeCurrent failed");
			resultCode = -1;
			break;
		}
		LOGCATE("BgRender::CreateGlesEnv initialize success!");
	}
	while (false);

	if (resultCode != 0)
	{
		LOGCATE("BgRender::CreateGlesEnv fail");
	}

	return resultCode;
}

//渲染
void BgRender::Draw()
{
	LOGCATE("BgRender::Draw");
	if (m_ProgramObj == GL_NONE) return;
	glViewport(0, 0, m_RenderImage.width, m_RenderImage.height);

	// Do FBO off screen rendering
	glUseProgram(m_ProgramObj);
	glBindFramebuffer(GL_FRAMEBUFFER, m_FboId);

	glBindVertexArray(m_VaoIds[0]);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, m_ImageTextureId);
	glUniform1i(m_SamplerLoc, 0);

	if (m_TexSizeLoc != GL_NONE) {
		GLfloat size[2];
		size[0] = m_RenderImage.width;
		size[1] = m_RenderImage.height;
		glUniform2fv(m_TexSizeLoc, 1, &size[0]);
	}

	//7. 渲染
	GO_CHECK_GL_ERROR();
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0);
	GO_CHECK_GL_ERROR();
	glBindVertexArray(GL_NONE);
	glBindTexture(GL_TEXTURE_2D, GL_NONE);

	//一旦解绑 FBO 后面就不能调用 readPixels
	//glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);

}

//释放 GLES 环境
void BgRender::DestroyGlesEnv()
{
	//8. 释放 EGL 环境
	if (m_eglDisplay != EGL_NO_DISPLAY) {
		eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
		eglDestroyContext(m_eglDisplay, m_eglCtx);
		eglDestroySurface(m_eglDisplay, m_eglSurface);
		eglReleaseThread();
		eglTerminate(m_eglDisplay);
	}

	m_eglDisplay = EGL_NO_DISPLAY;
	m_eglSurface = EGL_NO_SURFACE;
	m_eglCtx = EGL_NO_CONTEXT;

}

3. glFinish与glFlush

OpenGL ES 驱动和GPU以并行/异步的机制运行。发起GL 调用时,为了得到最好的性能,驱动会尝试尽快地把调用指令发送给GPU。但GPU 并不会马上执行这些指令,这些指令只是添加到了GPU的指令队列里等待GPU 执行。如果在短时间内发送大量的GL 指令给GPU,GPU的指令队列可能满了,以至于驱动需要把这些指令保存在Context的调用缓存里(即上文里提到的Context内的调用缓存)。

那么问题来了,这些等待中的指令何时会发送给GPU呢?通常来说,大多数OpenGL ES驱动的实现可能会在发起新的(下一个)GL 指令发起的时候发送这些指令。如果想要主动执行这个操作,那就调用glFlush。这个操作将会阻塞当前线程,直到所有的指令都发送给了GPU。glFinish这个命令更加强大,它会阻塞当前线程,直到所有的指令都发送给了GPU,并执行完毕。需要注意的是,应用程序的性能会因此下降。

glFlush和glFinish被称为显式同步操作。某些情况下也会发生隐式同步操作。调用eglSwapBuffers时,就可能发生这种情况。由于这个操作是由驱动直接执行的,此时GPU 可能把所有待执行的glDraw绘制指令,作用在一个不符合预期的surface 缓冲上(如果之前前端缓冲和后端缓冲已经交换过了)。为了防止这种情形,在交换缓冲前,驱动必须阻塞当前线程,等待所有的影响当前surface的glDraw指令执行完毕。

当然,使用双重缓冲的surfaces时,不需要主动调用glFlush或glFinish:因为eglSwapBuffers进行了隐式同步操作。但在使用单缓冲surfaces(如上文提到的第二个线程里)的情况,需要及时调用glFlush,例如:在线程退出前,必须调用glFlush,否则,GL 指令可能从未发送到GPU。

4. 参考

EGL 介绍和使用_阿飞__的博客-CSDN博客_egl

NDK OpenGL ES 3.0 开发(六):EGL_字节流动的博客-CSDN博客_opengl es ndk egl

谈谈eglMakeCurrent、eglSwapBuffers、glFlush和glFinish - 作业部落 Cmd Markdown 编辑阅读器

https://katatunix.wordpress.com/2014/09/17/lets-talk-about-eglmakecurrent-eglswapbuffers-glflush-glfinish/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值