OpenGL ES 之 EGL

眼下针对移动设备的应用开发依然如火如荼,而游戏开发几乎能占到半壁江山,而OpenGL ES也趁着这个东风大红大紫。To be honest,我并不是什么OpenGL的铁粉,OpenGL那种状态机(state machine)式的API风格实在不合我的口味,但大丈夫能曲能伸,该学了还得学,该用了还能用。

本文将讨论与OpenGL ES API相关的另一组API -- EGL API的一些基本操作。

首先EGL是一个介于OpenGL ES API与操作系统API之间的桥梁API, 其具体一些平台相关性,比如显示类型、窗口类型等。

稍微过一下相关的API函数,我比较懒,估计下文会比较简练,先贴个官方文档链接,以免有人看不明白http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/(也避免我自已哪天忘了偷笑)。


相关API函数

EGLDisplay eglGetDisplay(EGLNativeDisplayType native_display);
EGLBoolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor);
EGLBoolean eglTerminate(EGLDisplay dpy);

eglGetDisplay用于获取一个显示连接,其中display_id即要连接的display的标识,在Windows平台上,这是个HDC(对此我个人有些不解)。一般情况下,使用默认显示EGL_DEFAULT_DISPLAY就可以。

eglInitialize用于初始化显示连接,major/minor是一对输出参数,用于返回EGL的版本号。如果不关心,可以传NULL。

eglTerminate用于结束显示连接,如果与之关联的context正在使用,则连接会延迟到context停止使用后再释放。

虽然标准上没有指定,但一般eglInitialize/eglTerminate是通过引用计数来工作的,即对同一个显示连接,调用多少次eglInitialize必须调用相同次数的eglTerminate才能真正释放显示连接。以避免应用程序的不同模块各自调用这两个函数引发冲突。


EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list);
EGLBoolean eglDestroyContext(EGLDisplay dpy, EGLContext ctx);

创建上下文时需要指定config、shared context、attributes。 

config指定要创建的context必须支持的frame buffer配置,该值一般使用eglChooseConfig来获取。

share_context表示与要创建的context共享资源的context,但据说很多实现并不能很好的支持这一功能;如不需要共享上下文使用EGL_NO_CONTEXT。

attrib_list是指定此上下文的特性的输入参数,目前仅支持EGL_CONTEXT_CLIENT_VERSION。该参数的格式为一个EGLint的数组,数组元素由特性标识、特性值交替组成,最后以常量EGL_NONE结尾。标准中规定该参数可以为NULL,但Mali OpenGLES模拟器实现不支持NULL。


EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config);

attrib_list表示要获取的配置的特性列表,格式与eglCreateContext的attrib_list参数相同。几个重要的特性标识有:

EGL_SURFACE_TYPE:支持的surface类型,如EGL_WINDOW_BIT(window surface)、EGL_PBUFFER_BIT(pixel buffer surface)。

EGL_RED_SIZE/EGL_GREEN_SIZE/EGL_BLUE_SIZE/EGL_ALPHA_SIZE/EGL_DEPTH_SIZE:分别表示红、绿、蓝、透明、深度值的位数,前4个一般为8,后一个一般为16。

EGL_COLOR_BUFFER_TYPE:颜色缓冲的类型,如EGL_RGB_BUFFER

EGL_RENDERABLE_TYPE:指定要获取的配置必须支持的client API,指定EGL_OPENGL_ES2_BIT表示OpenGLES v2.0。


EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list);

该函数从一个native window创建surface,一般用来在窗口上绘制图形。

config参数同创建context时的config参数。

win参数表示一个窗口对象,在Windows平台下为HWND句柄。

attrib_list指定要创建的surface的特性,可以为NULL。


EGLBoolean eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
EGLContext eglGetCurrentContext(void);
EGLSurface eglGetCurrentSurface(EGLint readdraw);
EGLDisplay eglGetCurrentDisplay(void);

eglMakeCurrent设定当前上下文及读、写表面,之后的所有OpenGL ES操作都基于在此指定的上下文和读、写surfaces,直到下一次eglMakeCurrent调用。

其它三个函数获取当前的current对像。


EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface surface);
交换缓冲区,将绘制结果推到native window上。如果正在绘制的表面为pixel buffer surface,该函数调用不产生任何效果。



以上就是EGL主要的几个函数,那么一个最简单的OpenGL ES应用的大致工作过程为:

获取显示连接、初始化显示连接、创建上下文、创建窗口表面、进行初始化操作(设置OpenGL ES选项、加载贴图、创建Shader、构建绘图模型等)、进入消息循环(处理消息、进行绘制)、退出消息循环、进行资源清理、销毁窗口表面、销毁上下文、关闭显示连接;

典型的消息循环过程一般是有窗口消息时处理消息,空闲时进行绘制。



基于“代码即文档"的思想原则,下边是一些代码片断及要点说明

class egl_context {
private:
	EGLDisplay _display;
	EGLContext _context;
	EGLConfig _config;
public:
	egl_context() {
		_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
		eglInitialize(_display, nullptr, nullptr);

		_config = choose_config(_display);
		EGLint attrs[] = { EGL_NONE };
		_context = eglCreateContext(_display, _config, EGL_NO_CONTEXT, attrs);
	}
	explicit egl_context(const egl_context& other) = delete;
	~egl_context() {
		if (_context != EGL_NO_CONTEXT)
			eglDestroyContext(_display, _context);
		if (_display)
			eglTerminate(_display);
	}
	EGLContext get() const {
		return _context;
	}
	EGLConfig config() const {
		return _config;
	}
	EGLConfig display() const {
		return _display;
	}
private:
	static EGLConfig choose_config(EGLDisplay display) {
		static EGLint attrs[] = { //
				//
						EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT, //
						EGL_RED_SIZE, 8, //
						EGL_GREEN_SIZE, 8, //
						EGL_BLUE_SIZE, 8, //
						EGL_ALPHA_SIZE, 8, //
						EGL_DEPTH_SIZE, 16, //
						EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, //
						EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //
						EGL_NONE };
		EGLConfig config;
		EGLint count;
		if (eglChooseConfig(display, attrs, &config, 1, &count) && count)
			return config;
		return nullptr;
	}
};
该类管理EGLDisplay和EGLContext,使用RAII idiom,在构造函数中获取显示连接,选择配置、创建context;在析构函数中释放资源。该类不能复制;比较懒也没有实现move构造函数。


class egl_window: public window {
public:
	egl_window() {

	}
	virtual ~egl_window() noexcept {
	}
	egl_window(egl_window&& other) :
			window(std::move(other)) {
	}
	size client_size() const {
		RECT rc;
		GetClientRect(handle(), &rc);
		return size( { (int) (rc.right - rc.left), (int) (rc.bottom - rc.top) });
	}
protected:
	virtual LRESULT procedure(UINT msg, WPARAM wParam, LPARAM lParam) {
		switch (msg) {
		case WM_CLOSE:
			PostQuitMessage(0);
			return 0;
		}

		return DefWindowProcW(handle(), msg, wParam, lParam);
	}
private:
	friend class egl_window_surface;
};

该类封装了一个窗口。基类定义见笔者另一篇文章http://blog.csdn.net/badalloc/article/details/16860349


class egl_window_surface {
private:
	const egl_context& _context;
	const egl_window& _window;
	EGLSurface _surface;
public:
	egl_window_surface(const egl_context& context, const egl_window& window) :
			_context(context), _window(window) {

		_surface = eglCreateWindowSurface(_context.display(), _context.config(), _window.handle(), 0);

	}
	explicit egl_window_surface(const egl_window_surface& other) = delete;
	~egl_window_surface() {
		if (_surface != EGL_NO_SURFACE)
			eglDestroySurface(_context.display(), _surface);
	}
	EGLSurface get() const {
		return _surface;
	}
};
该类封装了一个EGLSurface,构造函数中创建surface,析构时销毁。


class egl_drawing_scope {
private:
	EGLDisplay _prev_display;
	EGLContext _prev_context;
	EGLContext _prev_surface_draw;
	EGLContext _prev_surface_read;
	EGLDisplay _display;
	EGLSurface _surface;
public:
	egl_drawing_scope(const egl_context & context,
			const egl_window_surface& surface_draw,
			const egl_window_surface& surface_read) :
			_prev_display(eglGetCurrentDisplay()), //
			_prev_context(eglGetCurrentContext()), //
			_prev_surface_draw(eglGetCurrentSurface(EGL_DRAW)), //
			_prev_surface_read(eglGetCurrentSurface(EGL_READ)) {

		_display = context.display();
		_surface = surface_draw.get();
		eglMakeCurrent(context.display(), surface_draw.get(),
				surface_read.get(), context.get());

	}
	explicit egl_drawing_scope(const egl_drawing_scope& other) = delete;
	~egl_drawing_scope() {
		eglSwapBuffers(_display, _surface);

		eglMakeCurrent(_prev_display, _prev_surface_draw, _prev_surface_read,
				_prev_context);
	}
};
一个辅助类,用于封装对make current的调用,并在析构时swap buffers并将current objects设置回原值。


class demo {
	std::unique_ptr<egl_context> _context;
	std::unique_ptr<egl_window> _window;
	std::unique_ptr<egl_window_surface> _surface;
public:
	demo() {
		_context.reset(new egl_context());
		_window.reset(new egl_window());
		_surface.reset(new egl_window_surface(*_context, *_window));

		egl_drawing_scope(*_context, *_surface, *_surface);
		glClearColor(1, 1, 1, 1);
	}

	void frame() {
		size client_size = _window->client_size();
		egl_drawing_scope drawing_scope(*_context, *_surface, *_surface);
		glViewport(0, 0, client_size.width, client_size.height);
		draw();
	}

	void draw() {
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	}

	static void main() {
		demo d;

		MSG msg;
		while (true) {
			if (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
				if (msg.message == WM_QUIT)
					break;
				TranslateMessage(&msg);
				DispatchMessageW(&msg);
			} else {
				d.frame();
			}
		}
	}
};
这是入口类,其main方法会被main或WinMain调用。

该类构造时,创建上边的egl_contex、egl_window、egl_surface类实例,之后设置创建的这些对象为current objects,并通过glClearColor这个GLES函数设置清除色彩缓冲时用的背景色。 

该类还包括一个 用于绘制每一帧的frame函数,其实现包括设置当前上下文、表面,设置视口,然后调用draw函数来执行实际的绘制操作。

负责实际绘制操作的draw函数目前只有一行清除缓冲区的操作,对色彩缓冲区、深度缓冲区进行清除。

该类的main函数是实际的入口函数,其中创建了一个demo类实例,并启动了一个消息循环,在有消息时处理消息,没有消息时进行绘制。


以上代码大部分原则上可以拿Android的NDK项目中使用;而iOS不直接提供EGL API,取而代之的是Apple的EAGL,个人感觉其倒是比标准EGL简化了一些。


示例代码编译环境:

ARM的Mali OpenGL ES模拟器的Windows x64版本(http://malideveloper.arm.com/cn/develop-for-mali/tools/opengl-es-3-0-emulator/);

mingw64_gcc-4.8.2_x86_64编译器开启C++11支持。



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值