如果要使用OpenGl来自定义相机,这个还是要了解下的。可能大多数开发者使用过OpengGL但是不知道EGL是什么?EGL的作用是什么?这其实一点都不奇怪,因为Android中的GlSurfaceView已经将EGL环境都给配置好了,你一直在使用,只是不知道他的存在罢了。很多人可能在使用OpenGl ES渲染数据的时候都带着一个疑问,渲染的数据到底到哪里去了?没看到画布,Android中的自定义view不都是有画布的吗?这篇文章就是为了解决这一系列问题而服务的。当然,如果你对自定义相机,音视频编码开发感兴趣,可以了解下我的Chat点这里
通过本文你可以了解到如下内容:1. 什么是EGL?. 2. EGL和OpenGl ES的关系 3. 使用EGL绘图的基本步骤。4. GlSurfaceView源码中分析EGL 5. 总结
EGL是什么?EGL是渲染API(如OpenGL, OpenGL ES, OpenVG)和本地窗口系统之间的接口。它处理图形上下文管理,表面/缓冲区创建,绑定和渲染同步,并使用其他Khronos API实现高性能,加速,混合模式2D和3D渲染OpenGL / OpenGL ES渲染客户端API OpenVG渲染客户端API原生平台窗口系统。这个稍微了解下就OK,你只要知道他是一个用来给OpenGl ES提供绘制界面的接口就可以了。
EGL的作用:
1. 与设备的原生窗口系统通信。
2. 查询绘图表面的可用类型和配置。
3. 创建绘图表面。
4. 在OpenGL ES 和其他图形渲染API之间同步渲染。
5. 管理纹理贴图等渲染资源。
这里关于EGL的介绍就讲这么多,如果你有兴趣的话你可以继续到这里去see yi seeUnderstanding Android EGL
从上面的讲解我们基本上可以知道,EGL是为OpenGl提供绘制表面的。对的,这就是OpenGl ES数据渲染画布所在了。想必到这里大家也清楚了渲染数据去处的问题了。EGL还有什么用呢?EGL可以理解为OpenGl ES ES和设备之间的桥梁。对,完全可以这么理解。
先上图,再说话。
简单讲解下各部分的作用:
1. Display(EGLDisplay) 是对实际显示设备的抽象。
2. Surface(EGLSurface)是对用来存储图像的内存区FrameBuffer 的抽象,包括Color Buffer,Stencil Buffer,Depth Buffer。
3. Context (EGLContext) 存储 OpenGL ES绘图的一些状态信息。
EGL的基本使用步骤:
1. 首先我们需要知道绘制内容的目标在哪里,EGLDisplayer是一个封装系统屏幕的数据类型,通常通过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");
}
if (!EGL14.eglInitialize(mEGLDisplay, 0, 0)) {
throw new RuntimeException("unable to initialize EGL14");
}
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);
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中的第三个参数可以传入一个EGLContext类型的变量,改变量的意义是可以与正在创建的上下文环境共享OpenGl资源,包括纹理ID,FrameBuffer以及其他Buffer资源。如果没有的话可以填写Null.
5. 通过上面四步,获取OpenGl 上下文之后,说明EGL和OpenGl ES端的环境已经搭建完毕,也就是说OpengGl的输出我们可以获取到了。下面的步骤我们讲如何将EGl和设备屏幕连接起来。如果连接呢?当然,这时候我们就要使用EGLSurface了,我们通过EGL库提供eglCreateWindowSurface可以创建一个实际可以显示的surface.当然,如果需要离线的surface,我们可以通过eglCreatePbufferSurface创建。eglCreateWindowSurface()
private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
int[] surfaceAttribs = {
EGL14.EGL_NONE
};
mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface,
surfaceAttribs, 0);
EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);
mInputSurface.swapBuffers();
EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
EGL14.eglReleaseThread();
EGL14.eglTerminate(mEGLDisplay);
上面我们有提到过Android GlSurfaceView中已经帮忙配置好了EGL,下面我们来看下EGL在GLSurfaceView中的具体实现过程:我们平时使用GlSurfaceView怎么使用的呢?xml中布置,然后setRenderer(this),然后调用下setRenderMode()就OK了。特比简单。但是GlSurfaceView内部原理是什么呢?我们现在来一探究竟:
特别声明:下面所有代码皆为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);
//创建线程并开启
mGLThread.start();
}
setRenderer函数主要干了2件事,一个是给View设置一个渲染器对象二是创建并开启渲染线程。线程start后执行guardedRun,run函数一个while(true)循环,渲染数据。
private void guardedRun() throws InterruptedException {
mEglHelper = new EglHelper(mGLSurfaceViewWeakRef);//特别注意,我们将从这里开始延伸,
……
if (createEglSurface) {
if (LOG_SURFACE) {
Log.w("GLThread", "egl createSurface");
}
if (mEglHelper.createSurface()) {//创建 EGLSurface 实例
synchronized(sGLThreadManager) {
mFinishedCreatingEglSurface = true;
sGLThreadManager.notifyAll();
}
} else {
synchronized(sGLThreadManager) {
mFinishedCreatingEglSurface = true;
mSurfaceIsBad = true;
sGLThreadManager.notifyAll();
}
continue;
}
createEglSurface = false;
}
……
if (createEglContext) {
if (LOG_RENDERER) {
Log.w("GLThread", "onSurfaceCreated");
}
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onSurfaceCreated");
view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);//看到这里没?1111111111111111111111111
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
createEglContext = false;
}
if (sizeChanged) {
if (LOG_RENDERER) {
Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")");
}
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onSurfaceChanged");
view.mRenderer.onSurfaceChanged(gl, w, h);//看到这里没?22222222222222222
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
sizeChanged = false;
}
if (LOG_RENDERER_DRAW_FRAME) {
Log.w("GLThread", "onDrawFrame tid=" + getId());
}
{
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onDrawFrame");
view.mRenderer.onDrawFrame(gl);//看到这里没?333333333333333333333333
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
}
……
}
到这里为止,是不是感觉特别熟悉?我们已经看到了onSurfaceCreated(),onSurfaceChanged(),onDrawFrame()的回调了。在这些方法之前有个EglHelper类的创建,接下来进入EGL的配置环节。请系好安全带:
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 创建 EGLContext 实例
……
}
好了,后面的请按照上面EGL的绘制步骤并对照下GlSurfaceView的源码。老衲就不在这里啰嗦了。
总结下,看完上面应该对开篇提到的问题有了比较明确的认识了,我相信你心中已经有答案了。总结点啥呢?还是说下感慨吧,学习编程的最好方式还是看源码吧,没有什么比阅读源码学习更有效了。今天重新看GlSurfaceView又有了与以往不同的理解。最后,如果你在自定义相机的学习上面有什么问题,可以给我留言,一块交流学习。微信公公众号:aserbao。还有最后打下广告:如果你在学习Android 自定义相机的开发,可以了解下我的自定义相机的ChatAndroid 零基础开发相机。