OpenGL 是一個跨平臺的 API,而不同的作業系統(Windows,Android,IOS)各有自己的螢幕渲染實現。所以 OpenGL 定義了一箇中間介面層 EGL(Embedded Graphics Library)標準,具體實現交給各個作業系統本身
EGL
簡單來說 EGL 是一箇中間介面層,是一個規範,由於 OpenGL 的跨平臺性,所以說這個規範顯得尤其重要,不管各個作業系統如何蹦躂,都不能脫離我所定義的規範。
EGL 的一些基礎知識
- EGLDisplay
EGL 定義的一個抽象的系統顯示類,用於操作裝置視窗。
- EGLConfig
EGL 配置,如 rgba 位數
- EGLSurface
渲染快取,一塊記憶體空間,所有要渲染到螢幕上的影像資料,都要先快取在 EGLSurface 上。
- EGLContext
OpenGL 上下文,用於儲存 OpenGL 的繪製狀態資訊、資料。
初始化 EGL 的過程可以說是對上面幾個資訊進行配置的過程。
OpenGL ES 繪圖完整流程
我們在使用 Java GLSurfaceView 的時候其實只是自定義了 Render,該 Render 實現了 GLsurfaceView.Renderer 介面,然後自定義的 Render 中的 3 個方法就會得到回撥,Android 系統其實幫我省掉了其中的很多步驟。所以我們這裡來看一下完整流程(1). 獲取顯示裝置(對應於上面的 EGLDisplay)
/*
* Get an EGL instance */
mEgl = (EGL10) EGLContext.getEGL();
/*
* Get to the default display. */
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
(2). 初始化 EGL
int[] version = new int[2];
//初始化螢幕
if(!mEgl.eglInitialize(mEglDisplay, version)) {
throw new RuntimeException("eglInitialize failed");
}
(3). 選擇 Config(用 EGLConfig 配置引數)
//這段程式碼的作用是選擇EGL配置, 即可以自己先設定好一個你希望的EGL配置,比如說RGB三種顏色各佔幾位,你可以隨便配,而EGL可能不能滿足你所有的要求,於是它會返回一些與你的要求最接近的配置供你選擇。
if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
num_config)) {
throw new IllegalArgumentException("eglChooseConfig#2 failed");
}
(4). 建立 EGLContext
//從上一步EGL返回的配置列表中選擇一種配置,用來建立EGL Context。
egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,
mEGLContextClientVersion != 0 ? attrib_list : null);
(5). 獲取 EGLSurface
//建立一個視窗Surface,可以看成螢幕所對應的記憶體
egl.eglCreateWindowSurface(display, config, nativeWindow, null)
PS 這裡的 nativeWindow 是 GLSurfaceView 的 surfaceHolder
(6). 繫結渲染環境到當前執行緒
/*
* Before we can issue GL commands, we need to make sure * the context is current and bound to a surface. */
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
/*
* Could not make the context current, probably because the underlying * SurfaceView surface has been destroyed. */
logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
return false;
}
迴圈繪製
loop:{
//繪製中....
//(7).交換緩衝區
mEglHelper.swap();
}
public int swap() {
if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
return mEgl.eglGetError();
}
return EGL10.EGL_SUCCESS;
}
Java - GLSurfaceView/GLTextureView
上面我們介紹了 EGL 的一些基礎知識,接著我們來看在 GLSurfaceView/GLTextureView 中 EGL 的具體實現,我們來從原始碼上剖析 Android 系統 EGL 及 GL 執行緒。
GLThread
我們來看一下 GLThread,GLThread 也是從普通的 Thread 類繼承而來,理論上就是一個普通的執行緒,為什麼它擁有 OpenGL 繪圖能力?繼續往下看,裡面最重要的部分就是 guardedRun()方法。
static class GLThread extends Thread {
...
@Override
public void run() {
try {
guardedRun();
} catch (InterruptedException e) {
// fall thru and exit normally
} finally {
sGLThreadManager.threadExiting(this);
}
}
}
讓我們來看一下 guardedRun()方法裡有什麼東西,guardedRun()裡大致做的事情:
private void guardedRun() throws InterruptedException {
while(true){
//if ready to draw
...
mEglHelper.start();//對應於上面完整流程中的(1)(2)(3)(4)
...
mEglHelper.createSurface()//對應於上面完整流程中的(5)(6)
...
回撥GLSurfaceView.Renderer的onSurfaceCreated();
...
回撥GLSurfaceView.Renderer的onSurfaceChanged();
...
回撥GLSurfaceView.Renderer的onDrawFrame();
...
mEglHelper.swap();//對應於上面完整流程中的(5)(7)
}
}
從上面我們的分析得知 GLSurfaceView 中的 GLThread 就是一個普通的執行緒,只不過它按照了 OpenGL 繪圖的完整流程正確地操作了下來,因此它有 OpenGL 的繪圖能力。那麼,如果我們自己建立一個執行緒,也按這樣的操作方法,那我們也可以在自己建立的執行緒裡繪圖嗎?答案是肯定的(這不正是 EGL 的介面意義),下面我會給出 EGL 在 Native C/C++中的實現。
Native - EGL
Android Native 環境中並不存在現成的 EGL 環境,所以我們在進行 OpenGL 的 NDK 開發時就必須自己實現 EGL 環境,那麼如何實現呢,我們只需要參照 GLSurfaceView 中的 GLThread 的寫法就能實現 Native 中的 EGL。
PS
以下的內容可能需要你對 C/C++以及 NDK 有一定熟悉
第 1 步實現類似於 Java GLSurfaceView 中的 GLThread 的功能
gl_render.h
class GLRender {
private:
const char *TAG = "GLRender";
//OpenGL渲染狀態
enum STATE {
NO_SURFACE, //沒有有效的surface
FRESH_SURFACE, //持有一個為初始化的新的surface
RENDERING, //初始化完畢,可以開始渲染
SURFACE_DESTROY, //surface銷燬
STOP //停止繪製
};
JNIEnv *m_env = NULL;
//執行緒依附的jvm環境
JavaVM *m_jvm_for_thread = NULL;
//Surface引用,必須要使用引用,否則無法線上程中操作
jobject m_surface_ref = NULL;
//本地螢幕
ANativeWindow *m_native_window = NULL;
//EGL顯示錶面
EglSurface *m_egl_surface = NULL;
int m_window_width = 0;
int m_window_height = 0;
// 繪製代理器
ImageRender *pImageRender;
//OpenGL渲染狀態
STATE m_state = NO_SURFACE;
// 初始化相關的方法
void InitRenderThread();
bool InitEGL();
void InitDspWindow(JNIEnv *env);
// 建立/銷燬 Surface void CreateSurface();
void DestroySurface();
// 渲染方法
void Render();
void ReleaseSurface();
void ReleaseWindow();
// 渲染執行緒回撥方法
static void sRenderThread(std::shared_ptr<GLRender> that);
public:
GLRender(JNIEnv *env);
~GLRender();
//外部傳入Surface
void SetSurface(jobject surface);
void Stop();
void SetBitmapRender(ImageRender *bitmapRender);
// 釋放資源相關方法
void ReleaseRender();
ImageRender *GetImageRender();
};
gl_render.cpp
//建構函式
GLRender::GLRender(JNIEnv *env) {
this->m_env = env;
//獲取JVM虛擬機器,為建立執行緒作準備
env->GetJavaVM(&m_jvm_for_thread);
InitRenderThread();
}
//解構函式
GLRender::~GLRender() {
delete m_egl_surface;
}
//初始化渲染執行緒
void GLRender::InitRenderThread() {
// 使用智慧指標,執行緒結束時,自動刪除本類指標
std::shared_ptr<GLRender> that(this);
std::thread t(sRenderThread, that);
t.detach();
}
//執行緒回撥函式
void GLRender::sRenderThread(std::shared_ptr<GLRender> that) {
JNIEnv *env;
//(1) 將執行緒附加到虛擬機器,並獲取env
if (that->m_jvm_for_thread->AttachCurrentThread(&env, NULL) != JNI_OK) {
LOGE(that->TAG, "執行緒初始化異常");
return;
}
// (2) 初始化 EGL
if (!that->InitEGL()) {
//解除執行緒和jvm關聯
that->m_jvm_for_thread->DetachCurrentThread();
return;
}
//進入迴圈
while (true) {
//根據OpenGL渲染狀態進入不同的處理
switch (that->m_state) {
//重新整理Surface,從外面設定Surface後m_state置為該狀態,說明已經從外部(java層)獲得Surface的物件了
case FRESH_SURFACE:
LOGI(that->TAG, "Loop Render FRESH_SURFACE")
// (3) 初始化Window
that->InitDspWindow(env);
// (4) 建立EglSurface
that->CreateSurface();
// m_state置為RENDERING狀態進入渲染
that->m_state = RENDERING;
break;
case RENDERING:
LOGI(that->TAG, "Loop Render RENDERING")
// (5) 渲染
that->Render();
break;
case STOP:
LOGI(that->TAG, "Loop Render STOP")
//(6) 解除執行緒和jvm關聯
that->ReleaseRender();
that->m_jvm_for_thread->DetachCurrentThread();
return;
case SURFACE_DESTROY:
LOGI(that->TAG, "Loop Render SURFACE_DESTROY")
//(7) 釋放資源
that->DestroySurface();
that->m_state = NO_SURFACE;
break;
case NO_SURFACE:
default:
break;
}
usleep(20000);
}
}
我們定義的 GLRender 各個流程程式碼裡已經標註了步驟,雖然程式碼量比較多,但是我們的 c++ class 分析也是跟 java 類似,
PS 上圖中的(3)(4)等步驟對應於程式碼中的步驟註釋
(1)將執行緒附加到虛擬機器,並獲取env
這一步簡單明瞭,我們往下看
EGL 封裝準備
我們在上一篇就知道了 EGL 的一些基礎知識,EGLDiaplay
,EGLConfig
,EGLSurface
,EGLContext
,我們需要把這些基礎類進行封裝,那麼如何進行封裝呢,我們先看一下對於我們上篇文章中自定義的 GLRender 類需要什麼 gl_render.h
//Surface引用,必須要使用引用,否則無法線上程中操作
jobject m_surface_ref = NULL;
//本地螢幕
ANativeWindow *m_native_window = NULL;
//EGL顯示錶面 注意這裡是我們自定義的EglSurface包裝類而不是系統提供的EGLSurface哦
EglSurface *m_egl_surface = NULL;
對於 gl_render 來說輸入的是外部的Surface物件
,我們這裡的是jobject m_surface_ref
,那麼輸出需要的是ANativeWindow
,EglSurface
關於ANativeWindow
可以檢視官方文件ANativeWindow
那麼EglSurface
呢,
egl_surface.h
class EglSurface {
private:
const char *TAG = "EglSurface";
//本地螢幕
ANativeWindow *m_native_window = NULL;
//封裝了EGLDisplay EGLConfig EGLContext的自定義類
EglCore *m_core;
//EGL API提供的 EGLSurface
EGLSurface m_surface;
}
可以看到我們上面的定義的思想也是 V(View)和 C(Controller)進行了分離。
egl_core.h
class EglCore {
private:
const char *TAG = "EglCore";
//EGL顯示視窗
EGLDisplay m_egl_dsp = EGL_NO_DISPLAY;
//EGL上下文
EGLContext m_egl_context = EGL_NO_CONTEXT;
//EGL配置
EGLConfig m_egl_config;
}
有了上面的準備工作後,我們就跟著流程圖的步驟來一步步走。
(2)初始化 EGL
gl_render.cpp
bool GLRender::InitEGL() {
//建立EglSurface物件
m_egl_surface = new EglSurface();
//呼叫EglSurface的init方法
return m_egl_surface->Init();
}
egl_surface.cpp
PS 我們上面也說了 EGL 的初始化主要是對 EGLDisplay EGLConfig EGLContext 的操作,所以現在是對 EGLCore 的操作。
EglSurface::EglSurface() {
//建立EGLCore
m_core = new EglCore();
}
bool EglSurface::Init() {
//呼叫EGLCore的init方法
return m_core->Init(NULL);
}
egl_core.cpp
EglCore::EglCore() {
}
bool EglCore::Init(EGLContext share_ctx) {
if (m_egl_dsp != EGL_NO_DISPLAY) {
LOGE(TAG, "EGL already set up")
return true;
}
if (share_ctx == NULL) {
share_ctx = EGL_NO_CONTEXT;
}
//獲取Dispaly
m_egl_dsp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (m_egl_dsp == EGL_NO_DISPLAY || eglGetError() != EGL_SUCCESS) {
LOGE(TAG, "EGL init display fail")
return false;
}
EGLint major_ver, minor_ver;
//初始化egl
EGLBoolean success = eglInitialize(m_egl_dsp, &major_ver, &minor_ver);
if (success != EGL_TRUE || eglGetError() != EGL_SUCCESS) {
LOGE(TAG, "EGL init fail")
return false;
}
LOGI(TAG, "EGL version: %d.%d", major_ver, minor_ver)
//獲取EGLConfig
m_egl_config = GetEGLConfig();
const EGLint attr[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
//建立EGLContext
m_egl_context = eglCreateContext(m_egl_dsp, m_egl_config, share_ctx, attr);
if (m_egl_context == EGL_NO_CONTEXT) {
LOGE(TAG, "EGL create fail, error is %x", eglGetError());
return false; }
EGLint egl_format;
success = eglGetConfigAttrib(m_egl_dsp, m_egl_config, EGL_NATIVE_VISUAL_ID, &egl_format);
if (success != EGL_TRUE || eglGetError() != EGL_SUCCESS) {
LOGE(TAG, "EGL get config fail, error is %x", eglGetError())
return false;
}
LOGI(TAG, "EGL init success")
return true;
}
EGLConfig EglCore::GetEGLConfig() {
EGLint numConfigs;
EGLConfig config;
//希望的最小配置,
static const EGLint CONFIG_ATTRIBS[] = {
EGL_BUFFER_SIZE, EGL_DONT_CARE,
EGL_RED_SIZE, 8,//R 位數
EGL_GREEN_SIZE, 8,//G 位數
EGL_BLUE_SIZE, 8,//B 位數
EGL_ALPHA_SIZE, 8,//A 位數
EGL_DEPTH_SIZE, 16,//深度
EGL_STENCIL_SIZE, EGL_DONT_CARE,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_NONE // the end 結束標誌
};
//根據你所設定的最小配置系統會選擇一個滿足你最低要求的配置,這個真正的配置往往要比你期望的屬性更多
EGLBoolean success = eglChooseConfig(m_egl_dsp, CONFIG_ATTRIBS, &config, 1, &numConfigs);
if (!success || eglGetError() != EGL_SUCCESS) {
LOGE(TAG, "EGL config fail")
return NULL;
}
return config;
}
(3)建立 Window
gl_render.cpp
void GLRender::InitDspWindow(JNIEnv *env) {
//傳進來的Surface物件的引用
if (m_surface_ref != NULL) {
// 初始化視窗
m_native_window = ANativeWindow_fromSurface(env, m_surface_ref);
// 繪製區域的寬高
m_window_width = ANativeWindow_getWidth(m_native_window);
m_window_height = ANativeWindow_getHeight(m_native_window);
//設定寬高限制緩衝區中的畫素數量
ANativeWindow_setBuffersGeometry(m_native_window, m_window_width,
m_window_height, WINDOW_FORMAT_RGBA_8888);
LOGD(TAG, "View Port width: %d, height: %d", m_window_width, m_window_height)
}
}
(4)建立 EglSurface 並繫結到執行緒
gl_render.cpp
void GLRender::CreateSurface() {
m_egl_surface->CreateEglSurface(m_native_window, m_window_width, m_window_height);
glViewport(0, 0, m_window_width, m_window_height);
}
egl_surface.cpp
/**
*
* @param native_window 傳入上一步建立的ANativeWindow
* @param width
* @param height
*/
void EglSurface::CreateEglSurface(ANativeWindow *native_window, int width, int height) {
if (native_window != NULL) {
this->m_native_window = native_window;
m_surface = m_core->CreateWindSurface(m_native_window);
} else {
m_surface = m_core->CreateOffScreenSurface(width, height);
}
if (m_surface == NULL) {
LOGE(TAG, "EGL create window surface fail")
Release();
}
MakeCurrent();
}
void EglSurface::MakeCurrent() {
m_core->MakeCurrent(m_surface);
}
egl_core.cpp
EGLSurface EglCore::CreateWindSurface(ANativeWindow *window) {
//呼叫EGL Native API建立Window Surface
EGLSurface surface = eglCreateWindowSurface(m_egl_dsp, m_egl_config, window, 0);
if (eglGetError() != EGL_SUCCESS) {
LOGI(TAG, "EGL create window surface fail")
return NULL;
}
return surface;
}
void EglCore::MakeCurrent(EGLSurface egl_surface) {
//呼叫EGL Native API 繫結渲染環境到當前執行緒
if (!eglMakeCurrent(m_egl_dsp, egl_surface, egl_surface, m_egl_context)) {
LOGE(TAG, "EGL make current fail");
}
}
(5)渲染
gl_render.cpp
void GLRender::Render() {
if (RENDERING == m_state) {
pImageRender->DoDraw();//畫畫畫....
m_egl_surface->SwapBuffers();
}
}
egl_surface.cpp
void EglSurface::SwapBuffers() {
m_core->SwapBuffer(m_surface);
}
egl_core.cpp
void EglCore::SwapBuffer(EGLSurface egl_surface) {
//呼叫EGL Native API
eglSwapBuffers(m_egl_dsp, egl_surface);
}
後面的停止與銷燬就交給讀者自行研究了。