胡說八道
如果要使用OpenGl來自定義相機考传,這個還是要了解下的肴焊∏傲可能大多數(shù)開發(fā)者使用過OpengGL但是不知道EGL是什么?EGL的作用是什么娶眷?這其實一點都不奇怪似嗤,因為Android中的GlSurfaceView已經(jīng)將EGL環(huán)境都給配置好了,你一直在使用届宠,只是不知道他的存在罷了烁落。很多人可能在使用OpenGl ES渲染數(shù)據(jù)的時候都帶著一個疑問,渲染的數(shù)據(jù)到底到哪里去了豌注?沒看到畫布伤塌,Android中的自定義view不都是有畫布的嗎?這篇文章就是為了解決這一系列問題而服務的轧铁。當然每聪,如果你對自定義相機,音視頻編碼開發(fā)感興趣齿风,可以了解下我的Chat點這里
正兒八經(jīng)
通過本文你可以了解到如下內(nèi)容:1. 什么是EGL?. 2. EGL和OpenGl ES的關系 3. 使用EGL繪圖的基本步驟药薯。4. GlSurfaceView源碼中分析EGL 5. 總結
1. 了解下什么是EGL?
EGL是什么?EGL是渲染API(如OpenGL, OpenGL ES, OpenVG)和本地窗口系統(tǒng)之間的接口救斑。它處理圖形上下文管理童本,表面/緩沖區(qū)創(chuàng)建,綁定和渲染同步脸候,并使用其他Khronos API實現(xiàn)高性能穷娱,加速,混合模式2D和3D渲染OpenGL / OpenGL ES渲染客戶端API OpenVG渲染客戶端API原生平臺窗口系統(tǒng)纪他。這個稍微了解下就OK鄙煤,你只要知道他是一個用來給OpenGl ES提供繪制界面的接口就可以了晾匠。
EGL的作用:
- 與設備的原生窗口系統(tǒng)通信茶袒。
- 查詢繪圖表面的可用類型和配置。
- 創(chuàng)建繪圖表面凉馆。
- 在OpenGL ES 和其他圖形渲染API之間同步渲染薪寓。
- 管理紋理貼圖等渲染資源亡资。
這里關于EGL的介紹就講這么多,如果你有興趣的話你可以繼續(xù)到這里去see yi seeUnderstanding Android EGL
2. EGL和OpenGl ES的關系
從上面的講解我們基本上可以知道向叉,EGL是為OpenGl提供繪制表面的锥腻。對的,這就是OpenGl ES數(shù)據(jù)渲染畫布所在了母谎。想必到這里大家也清楚了渲染數(shù)據(jù)去處的問題了瘦黑。EGL還有什么用呢?EGL可以理解為OpenGl ES ES和設備之間的橋梁奇唤。對幸斥,完全可以這么理解。
3. EGL繪圖的基本步驟
先上圖咬扇,再說話甲葬。
簡單講解下各部分的作用:
- Display(EGLDisplay) 是對實際顯示設備的抽象。
- Surface(EGLSurface)是對用來存儲圖像的內(nèi)存區(qū)FrameBuffer 的抽象,包括Color Buffer,Stencil Buffer,Depth Buffer懈贺。
- Context (EGLContext) 存儲 OpenGL ES繪圖的一些狀態(tài)信息经窖。
EGL的基本使用步驟:
- 首先我們需要知道繪制內(nèi)容的目標在哪里,EGLDisplayer是一個封裝系統(tǒng)屏幕的數(shù)據(jù)類型梭灿,通常通過eglGetDisplay方法來返回EGLDisplay作為OpenGl ES的渲染目標画侣,eglGetDisplay()
if ( (mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)) == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("unable to get EGL14 display");
}
- 初始化顯示設備,第一參數(shù)代表Major版本堡妒,第二個代表Minor版本棉钧。如果不關心版本號,傳0或者null就可以了涕蚤。初始化與 EGLDisplay 之間的連接:eglInitialize()
if (!EGL14.eglInitialize(mEGLDisplay, 0, 0)) {
throw new RuntimeException("unable to initialize EGL14");
}
- 下面我們進行配置選項宪卿,使用eglChooseConfig()方法,Android平臺的配置代碼如下:
int[] attribList = {
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL_RECORDABLE_ANDROID, 1,
EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
numConfigs, 0);
- 接下來我們需要創(chuàng)建OpenGl的上下文環(huán)境 EGLContext 實例万栅,這里值得留意的是佑钾,OpenGl的任何一條指令都是必須在自己的OpenGl上下文環(huán)境中運行,我們可以通過eglCreateContext()方法來構建上下文環(huán)境:
int[] attrib_list = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE
};
mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
attrib_list, 0);
eglCreateContext中的第三個參數(shù)可以傳入一個EGLContext類型的變量烦粒,改變量的意義是可以與正在創(chuàng)建的上下文環(huán)境共享OpenGl資源休溶,包括紋理ID,FrameBuffer以及其他Buffer資源。如果沒有的話可以填寫Null.
- 通過上面四步,獲取OpenGl 上下文之后扰她,說明EGL和OpenGl ES端的環(huán)境已經(jīng)搭建完畢兽掰,也就是說OpengGl的輸出我們可以獲取到了。下面的步驟我們講如何將EGl和設備屏幕連接起來徒役。如果連接呢孽尽?當然,這時候我們就要使用EGLSurface了,我們通過EGL庫提供eglCreateWindowSurface可以創(chuàng)建一個實際可以顯示的surface.當然忧勿,如果需要離線的surface杉女,我們可以通過eglCreatePbufferSurface創(chuàng)建瞻讽。eglCreateWindowSurface()
private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
int[] surfaceAttribs = {
EGL14.EGL_NONE
};
mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface,
surfaceAttribs, 0);
- 通過上面的步驟,EGL的準備工作做好了熏挎,一方面我們?yōu)镺penGl ES渲染提供了目標及上下文環(huán)境速勇,可以接收到OpenGl ES渲染出來的紋理,另一方面我們連接好了設備顯示屏(這里指SurfaceView或者TextureView),接下來我們講解如何在創(chuàng)建好的EGL環(huán)境下工作的坎拐。首先我們有一點必須要明確烦磁,OpenGl ES 的渲染必須新開一個線程,并為該線程綁定顯示設備及上下文環(huán)境(Context)哼勇。因為前面有說過OpenGl指令必須要在其上下文環(huán)境中才能執(zhí)行个初。所以我們首先要通過 eglMakeCurrent()方法來綁定該線程的顯示設備及上下文。
EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);
- 當我們綁定完成之后猴蹂,我們就可以進行RenderLoop循環(huán)了院溺。這里簡單說一下,EGL的工作模式是雙緩沖模式磅轻,其內(nèi)部有兩個FrameBuffer(幀緩沖區(qū)珍逸,可以理解為一個圖像存儲區(qū)域),當EGL將一個FrameBuffer顯示到屏幕上的時候聋溜,另一個FrameBuffer就在后臺等待OpenGl ES進行渲染輸出谆膳。知道調(diào)用了eglSwapBuffers這條指令的時候,才會把前臺的FrameBuffers和后臺的FrameBuffer進行交換撮躁,這樣界面呈現(xiàn)的就是OpenGl ES剛剛渲染的結構了漱病。
mInputSurface.swapBuffers();
- 當然,在所有的操作都執(zhí)行完之后把曼,我們要銷毀資源杨帽。特別注意,銷毀資源必須在當前線程中進行嗤军,不然會報錯滴注盈。首先我們銷毀顯示設備(EGLSurface),然后銷毀上下文(EGLContext),停止并釋放線程,最后終止與EGLDisplay之間的鏈接叙赚,
EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
EGL14.eglReleaseThread();
EGL14.eglTerminate(mEGLDisplay);
4. GlSurfaceView源碼中分析EGL
上面我們有提到過Android GlSurfaceView中已經(jīng)幫忙配置好了EGL老客,下面我們來看下EGL在GLSurfaceView中的具體實現(xiàn)過程:我們平時使用GlSurfaceView怎么使用的呢?xml中布置震叮,然后setRenderer(this),然后調(diào)用下setRenderMode()就OK了胧砰。特比簡單。但是GlSurfaceView內(nèi)部原理是什么呢苇瓣?我們現(xiàn)在來一探究竟:
特別聲明:下面所有代碼皆為GlSurfaView中的源碼尉间。
public void setRenderer(Renderer renderer) {
checkRenderThreadState();
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(true);
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory();
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mRenderer = renderer;//為當前的View設置一個渲染器
mGLThread = new GLThread(mThisWeakRef);
//創(chuàng)建線程并開啟
mGLThread.start();
}
setRenderer函數(shù)主要干了2件事,一個是給View設置一個渲染器對象二是創(chuàng)建并開啟渲染線程。線程start后執(zhí)行guardedRun乌妒,run函數(shù)一個while(true)循環(huán),渲染數(shù)據(jù)外邓。
public void setRenderer(Renderer renderer) {
checkRenderThreadState();
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(true);
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory();
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mRenderer = renderer;//為當前的View設置一個渲染器
mGLThread = new GLThread(mThisWeakRef);
//創(chuàng)建線程并開啟
mGLThread.start();
}
到這里為止撤蚊,是不是感覺特別熟悉?我們已經(jīng)看到了onSurfaceCreated()损话,onSurfaceChanged()侦啸,onDrawFrame()的回調(diào)了。在這些方法之前有個EglHelper類的創(chuàng)建丧枪,接下來進入EGL的配置環(huán)節(jié)光涂。請系好安全帶:
public void start(){
……
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);//1. 獲取 EGL Display 對象
……
//2. 初始化與 EGLDisplay 之間的連接
if(!mEgl.eglInitialize(mEglDisplay, version)) {
throw new RuntimeException("eglInitialize failed");
}
……
mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);//3. 獲取 EGLConfig 對象
/*
* Create an EGL context. We want to do this as rarely as we can, because an
* EGL context is a somewhat heavy object.
*/
mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);//4 創(chuàng)建 EGLContext 實例
……
}
好了,后面的請按照上面EGL的繪制步驟并對照下GlSurfaceView的源碼拧烦。老衲就不在這里啰嗦了忘闻。不要問我為什么?
總結
總結下恋博,看完上面應該對開篇提到的問題有了比較明確的認識了齐佳,我相信你心中已經(jīng)有答案了≌冢總結點啥呢炼吴?還是說下感慨吧,學習編程的最好方式還是看源碼吧疫衩,沒有什么比閱讀源碼學習更有效了硅蹦。今天重新看GlSurfaceView又有了與以往不同的理解。最后闷煤,如果你在自定義相機的學習上面有什么問題童芹,可以給我留言,一塊交流學習鲤拿。微信公公眾號:aserbao辐脖。還有最后打下廣告:如果你在學習Android 自定義相機的開發(fā),可以了解下我的自定義相機的ChatAndroid 零基礎開發(fā)相機皆愉。
如果覺得文章寫得好嗜价,麻煩賞個贊唄,如果你覺得寫的不好幕庐,歡迎留言批評指正久锥,方便我及時復查修改。