天天看點

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/(也避免我自已哪天忘了

OpenGL ES 之 EGL

)。

相關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支援。

繼續閱讀