首先,这一系列文章均基于本身的理解和实践,可能有不对的地方,欢迎你们指正。
其次,这是一个入门系列,涉及的知识也仅限于够用,深刻的知识网上也有许许多多的博文供你们学习了。
最后,写文章过程当中,会借鉴参考其余人分享的文章,会在文章最后列出,感谢这些做者的分享。android
码字不易,转载请注明出处!git
教程代码:【Github传送门】 |
---|
EGL做为OpenGL与本地窗口渲染的中间桥梁,不少时候是不会被刚入门OpenGL的开发者关注的,甚至有点忽略了。随着学习的深刻,EGL将是不得不面对的东西。本文将介绍EGL是什么,有什么用,以及如何使用EGL。github
做为Android开发者,EGL仿佛是一个很陌生的东西,为何?api
都怪Android的GLSurfaceView封装的太好了。哈哈哈~数组
前面的文章就介绍过,OpenGL是基于线程的,直到目前为止,咱们并无深入的认识到这个问题,但咱们知道的是,当咱们继承GLSurfaceView.Renderer时,系统会回调如下方法:缓存
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
}
override fun onDrawFrame(gl: GL10?) {
}
复制代码
而且onDrawerFrame方法是会被不断的调用,咱们就是在这里面实现了OpenGL的绘制流程。app
这里咱们就能够猜想,可以不断被调用的,有没有可能就是一个while循环的线程呢?框架
答案是:Yes。ide
若是你去看一下GLSurfaceView的源码,你会找到一个叫GLThread的线程,在线程中就初始化了EGL相关的内容。而且在合适的时机,分别调用了Renderer中的三个方法。函数
那么,EGL到底是个什么东西呢?
咱们知道OpenGL是一组能够操做GPU的API,然而仅仅可以操做GPU,并不可以将图像渲染到设备的显示窗口上。那么,就须要一个中间层,链接OpenGL与设备窗口,而且最好是跨平台的。
因而EGL出现了,由Khronos Group提供的一组平台无关的API。
EGL定义的一个抽象的系统显示类,用于操做设备窗口。
EGL配置,如rgba位数
渲染缓存,一块内存空间,全部要渲染到屏幕上的图像数据,都要先缓存在EGLSurface上。
OpenGL上下文,用于存储OpenGL的绘制状态信息、数据。
初始化EGL的过程其实就是配置以上几个信息的过程。
单单看上面的介绍,其实仍是比较难理解EGL究竟有什么做用,或者应该怎么样去使用EGL。
请你们先思考一个问题
若是同时有两个GLSurfaceView在渲染视频画面,OpenGL为何可以正确的把画面分别绘制到两个GLSurfaceView中?
仔细回想一下OpenGL ES的每一个API,有没有哪一个API是指定当前画面是渲染到哪一个GLSurfaceView的?
没有!
请带着这个疑问,阅读下面的内容。
首先,对EGL初始化的核心(第一节中介绍的4个)内容进行封装,命名为 EGLCore
const val FLAG_RECORDABLE = 0x01
private const val EGL_RECORDABLE_ANDROID = 0x3142
class EGLCore {
private val TAG = "EGLCore"
// EGL相关变量
private var mEGLDisplay: EGLDisplay = EGL14.EGL_NO_DISPLAY
private var mEGLContext = EGL14.EGL_NO_CONTEXT
private var mEGLConfig: EGLConfig? = null
/** * 初始化EGLDisplay * @param eglContext 共享上下文 */
fun init(eglContext: EGLContext?, flags: Int) {
if (mEGLDisplay !== EGL14.EGL_NO_DISPLAY) {
throw RuntimeException("EGL already set up")
}
val sharedContext = eglContext ?: EGL14.EGL_NO_CONTEXT
// 1,建立 EGLDisplay
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
if (mEGLDisplay === EGL14.EGL_NO_DISPLAY) {
throw RuntimeException("Unable to get EGL14 display")
}
// 2,初始化 EGLDisplay
val version = IntArray(2)
if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
mEGLDisplay = EGL14.EGL_NO_DISPLAY
throw RuntimeException("unable to initialize EGL14")
}
// 3,初始化EGLConfig,EGLContext上下文
if (mEGLContext === EGL14.EGL_NO_CONTEXT) {
val config = getConfig(flags, 2) ?: throw RuntimeException("Unable to find a suitable EGLConfig")
val attr2List = intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE)
val context = EGL14.eglCreateContext(
mEGLDisplay, config, sharedContext,
attr2List, 0
)
mEGLConfig = config
mEGLContext = context
}
}
/** * 获取EGL配置信息 * @param flags 初始化标记 * @param version EGL版本 */
private fun getConfig(flags: Int, version: Int): EGLConfig? {
var renderableType = EGL14.EGL_OPENGL_ES2_BIT
if (version >= 3) {
// 配置EGL 3
renderableType = renderableType or EGLExt.EGL_OPENGL_ES3_BIT_KHR
}
// 配置数组,主要是配置RAGA位数和深度位数
// 两个为一对,前面是key,后面是value
// 数组必须以EGL14.EGL_NONE结尾
val attrList = intArrayOf(
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
//EGL14.EGL_DEPTH_SIZE, 16,
//EGL14.EGL_STENCIL_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, renderableType,
EGL14.EGL_NONE, 0, // placeholder for recordable [@-3]
EGL14.EGL_NONE
)
//配置Android指定的标记
if (flags and FLAG_RECORDABLE != 0) {
attrList[attrList.size - 3] = EGL_RECORDABLE_ANDROID
attrList[attrList.size - 2] = 1
}
val configs = arrayOfNulls<EGLConfig>(1)
val numConfigs = IntArray(1)
//获取可用的EGL配置列表
if (!EGL14.eglChooseConfig(mEGLDisplay, attrList, 0,
configs, 0, configs.size,
numConfigs, 0)) {
Log.w(TAG, "Unable to find RGB8888 / $version EGLConfig")
return null
}
//使用系统推荐的第一个配置
return configs[0]
}
/** * 建立可显示的渲染缓存 * @param surface 渲染窗口的surface */
fun createWindowSurface(surface: Any): EGLSurface {
if (surface !is Surface && surface !is SurfaceTexture) {
throw RuntimeException("Invalid surface: $surface")
}
val surfaceAttr = intArrayOf(EGL14.EGL_NONE)
val eglSurface = EGL14.eglCreateWindowSurface(
mEGLDisplay, mEGLConfig, surface,
surfaceAttr, 0)
if (eglSurface == null) {
throw RuntimeException("Surface was null")
}
return eglSurface
}
/** * 建立离屏渲染缓存 * @param width 缓存窗口宽 * @param height 缓存窗口高 */
fun createOffscreenSurface(width: Int, height: Int): EGLSurface {
val surfaceAttr = intArrayOf(EGL14.EGL_WIDTH, width,
EGL14.EGL_HEIGHT, height,
EGL14.EGL_NONE)
val eglSurface = EGL14.eglCreatePbufferSurface(
mEGLDisplay, mEGLConfig,
surfaceAttr, 0)
if (eglSurface == null) {
throw RuntimeException("Surface was null")
}
return eglSurface
}
/** * 将当前线程与上下文进行绑定 */
fun makeCurrent(eglSurface: EGLSurface) {
if (mEGLDisplay === EGL14.EGL_NO_DISPLAY) {
throw RuntimeException("EGLDisplay is null, call init first")
}
if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) {
throw RuntimeException("makeCurrent(eglSurface) failed")
}
}
/** * 将当前线程与上下文进行绑定 */
fun makeCurrent(drawSurface: EGLSurface, readSurface: EGLSurface) {
if (mEGLDisplay === EGL14.EGL_NO_DISPLAY) {
throw RuntimeException("EGLDisplay is null, call init first")
}
if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) {
throw RuntimeException("eglMakeCurrent(draw,read) failed")
}
}
/** * 将缓存图像数据发送到设备进行显示 */
fun swapBuffers(eglSurface: EGLSurface): Boolean {
return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface)
}
/** * 设置当前帧的时间,单位:纳秒 */
fun setPresentationTime(eglSurface: EGLSurface, nsecs: Long) {
EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs)
}
/** * 销毁EGLSurface,并解除上下文绑定 */
fun destroySurface(elg_surface: EGLSurface) {
EGL14.eglMakeCurrent(
mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT
)
EGL14.eglDestroySurface(mEGLDisplay, elg_surface);
}
/** * 释放资源 */
fun release() {
if (mEGLDisplay !== EGL14.EGL_NO_DISPLAY) {
// Android is unusual in that it uses a reference-counted EGLDisplay. So for
// every eglInitialize() we need an eglTerminate().
EGL14.eglMakeCurrent(
mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT
)
EGL14.eglDestroyContext(mEGLDisplay, mEGLContext)
EGL14.eglReleaseThread()
EGL14.eglTerminate(mEGLDisplay)
}
mEGLDisplay = EGL14.EGL_NO_DISPLAY
mEGLContext = EGL14.EGL_NO_CONTEXT
mEGLConfig = null
}
}
复制代码
以上是最基础,最简洁的EGL初始化封装了,基本上每一个方法都是必要的。
具体来看下:
初始化init,分为3个步骤:
其中,在初始化EGLCongtext的时候,调用了getConfig方法。
配置上下文getConfig:
Android 指定的标志EGL_RECORDABLE_ANDROID
告诉EGL它建立的surface必须和视频编解码器兼容。
没有这个标志,EGL可能会使用一个MediaCodec不能理解的Buffer。
这个变量在api26之后系统才自带有,为了兼容,咱们本身写好这个值0x3142。
建立EGLSurface,分为两种模式:
第一种是最经常使用的,一般将页面上的SurfaceView持有的Surface,或SurfaceTexture传递进去进行绑定。这样OpenGL处理的图像数据就能够显示在屏幕上。
第二种用于离屏渲染,也就是将OpenGL处理的图像数据保存在缓存中,不会显示到屏幕上,可是整个渲染流程和普通模式同样,这样能够很好的处理一些用户不须要看见的图像数据。
绑定OpenGL渲染线程与绘制上下文:makeCurrent
到这里,使用EGLCore中封装的方法就能够初始化EGL了。可是仍是没有回答上边提到的问题。
答案就在glMakeCurrent中。
glMakeCurrent这个方法,实现了设备显示窗口(EGLDisplay)、 OpenGL 上下文(EGLContext)、图像数据缓存(GLSurface) 、当前线程的绑定。
注意这里的:“当前线程的绑定”。
如今来回答上面提出的问题:为何OpenGL能够在多个GLSurfaceView中正确绘制?
在EGL初始化之后,即渲染环境(EGLDisplay、EGLContext、GLSurface)准备就绪之后,须要在渲染线程(绘制图像的线程)中,明确的调用glMakeCurrent。这时,系统底层会将OpenGL渲染环境绑定到当前线程。
在这以后,只要你是在渲染线程中调用任何OpenGL ES的API(好比生产纹理ID的方法GLES20.glGenTextures),OpenGL会自动根据当前线程,切换上下文(也就是切换OpenGL的渲染信息和资源)。
换而言之,若是你在非调用glMakeCurrent的线程中去调用OpenGL的API,系统将找不到对应的OpenGL上下文,也就找不到对应的资源,可能会致使异常出错。
这也就是为何有文章说,OpenGL渲染必定要在OpenGL线程中进行。
实际上,GLSurfaceView#Renderer的三个回调方法,都是在GLThread中进行调用的。
交换缓存数据,并显示图像:swapBuffers
解绑数据缓存表面,以及释放资源
上面的仅仅作了核心API的封装,接下来要新建一个类来调用它。
这里,新建一个EGLSurfaceHolder,用于操做EGLCore
class EGLSurfaceHolder {
private val TAG = "EGLSurfaceHolder"
private lateinit var mEGLCore: EGLCore
private var mEGLSurface: EGLSurface? = null
fun init(shareContext: EGLContext? = null, flags: Int) {
mEGLCore = EGLCore()
mEGLCore.init(shareContext, flags)
}
fun createEGLSurface(surface: Any?, width: Int = -1, height: Int = -1) {
mEGLSurface = if (surface != null) {
mEGLCore.createWindowSurface(surface)
} else {
mEGLCore.createOffscreenSurface(width, height)
}
}
fun makeCurrent() {
if (mEGLSurface != null) {
mEGLCore.makeCurrent(mEGLSurface!!)
}
}
fun swapBuffers() {
if (mEGLSurface != null) {
mEGLCore.swapBuffers(mEGLSurface!!)
}
}
fun destroyEGLSurface() {
if (mEGLSurface != null) {
mEGLCore.destroySurface(mEGLSurface!!)
mEGLSurface = null
}
}
fun release() {
mEGLCore.release()
}
}
复制代码
代码很简单,最重要的就是持有了EGLSurface(固然了,你也能够把EGLSurface也放在EGLCore中),并开放了更简洁的EGL操做方法给外部进行调用。
为了更好的认识EGL,这里经过模拟GLSurfaceView来了解如何使用EGL。
class CustomerGLRenderer : SurfaceHolder.Callback {
//OpenGL渲染线程
private val mThread = RenderThread()
//页面上的SurfaceView弱引用
private var mSurfaceView: WeakReference<SurfaceView>? = null
//全部的绘制器
private val mDrawers = mutableListOf<IDrawer>()
init {
//启动渲染线程
mThread.start()
}
/** * 设置SurfaceView */
fun setSurface(surface: SurfaceView) {
mSurfaceView = WeakReference(surface)
surface.holder.addCallback(this)
surface.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener{
override fun onViewDetachedFromWindow(v: View?) {
mThread.onSurfaceStop()
}
override fun onViewAttachedToWindow(v: View?) {
}
})
}
/** * 添加绘制器 */
fun addDrawer(drawer: IDrawer) {
mDrawers.add(drawer)
}
override fun surfaceCreated(holder: SurfaceHolder?) {
mThread.onSurfaceCreate()
}
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
mThread.onSurfaceChange(width, height)
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
mThread.onSurfaceDestroy()
}
}
复制代码
主要以下:
初始化时,启动渲染线程。而后就是将SurfaceView生命周期转发给渲染线程,没有其余了。
/** * 渲染状态 */
enum class RenderState {
NO_SURFACE, //没有有效的surface
FRESH_SURFACE, //持有一个未初始化的新的surface
SURFACE_CHANGE, // surface尺寸变化
RENDERING, //初始化完毕,能够开始渲染
SURFACE_DESTROY, //surface销毁
STOP //中止绘制
}
复制代码
根据这几个状态,在RenderThread中,切换线程的执行状态。
说明以下:
inner class RenderThread: Thread() {
// 渲染状态
private var mState = RenderState.NO_SURFACE
private var mEGLSurface: EGLSurfaceHolder? = null
// 是否绑定了EGLSurface
private var mHaveBindEGLContext = false
//是否已经新建过EGL上下文,用于判断是否须要生产新的纹理ID
private var mNeverCreateEglContext = true
private var mWidth = 0
private var mHeight = 0
private val mWaitLock = Object()
//------------第1部分:线程等待与解锁-----------------
private fun holdOn() {
synchronized(mWaitLock) {
mWaitLock.wait()
}
}
private fun notifyGo() {
synchronized(mWaitLock) {
mWaitLock.notify()
}
}
//------------第2部分:Surface声明周期转发函数------------
fun onSurfaceCreate() {
mState = RenderState.FRESH_SURFACE
notifyGo()
}
fun onSurfaceChange(width: Int, height: Int) {
mWidth = width
mHeight = height
mState = RenderState.SURFACE_CHANGE
notifyGo()
}
fun onSurfaceDestroy() {
mState = RenderState.SURFACE_DESTROY
notifyGo()
}
fun onSurfaceStop() {
mState = RenderState.STOP
notifyGo()
}
//------------第3部分:OpenGL渲染循环------------
override fun run() {
// 【1】初始化EGL
initEGL()
while (true) {
when (mState) {
RenderState.FRESH_SURFACE -> {
//【2】使用surface初始化EGLSurface,并绑定上下文
createEGLSurfaceFirst()
holdOn()
}
RenderState.SURFACE_CHANGE -> {
createEGLSurfaceFirst()
//【3】初始化OpenGL世界坐标系宽高
GLES20.glViewport(0, 0, mWidth, mHeight)
configWordSize()
mState = RenderState.RENDERING
}
RenderState.RENDERING -> {
//【4】进入循环渲染
render()
}
RenderState.SURFACE_DESTROY -> {
//【5】销毁EGLSurface,并解绑上下文
destroyEGLSurface()
mState = RenderState.NO_SURFACE
}
RenderState.STOP -> {
//【6】释放全部资源
releaseEGL()
return
}
else -> {
holdOn()
}
}
sleep(20)
}
}
//------------第4部分:EGL相关操做------------
private fun initEGL() {
mEGLSurface = EGLSurfaceHolder()
mEGLSurface?.init(null, EGL_RECORDABLE_ANDROID)
}
private fun createEGLSurfaceFirst() {
if (!mHaveBindEGLContext) {
mHaveBindEGLContext = true
createEGLSurface()
if (mNeverCreateEglContext) {
mNeverCreateEglContext = false
generateTextureID()
}
}
}
private fun createEGLSurface() {
mEGLSurface?.createEGLSurface(mSurfaceView?.get()?.holder?.surface)
mEGLSurface?.makeCurrent()
}
private fun destroyEGLSurface() {
mEGLSurface?.destroyEGLSurface()
mHaveBindEGLContext = false
}
private fun releaseEGL() {
mEGLSurface?.release()
}
//------------第5部分:OpenGL ES相关操做-------------
private fun generateTextureID() {
val textureIds = OpenGLTools.createTextureIds(mDrawers.size)
for ((idx, drawer) in mDrawers.withIndex()) {
drawer.setTextureID(textureIds[idx])
}
}
private fun configWordSize() {
mDrawers.forEach { it.setWorldSize(mWidth, mHeight) }
}
private fun render() {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT)
mDrawers.forEach { it.draw() }
mEGLSurface?.swapBuffers()
}
}
复制代码
主要分为5部分,1-2很简单,相信你们都看得懂。至于4-5,都是run中调用的方法。
重点来看第3部分,也就是run方法。
【1】在进入while(true)以前,initEGL使用EGLSurfaceHolder来初始化EGL。
须要注意的是,initEGL只会调用一次,也就是说EGL只初始化一次,不管后面surface销毁和重建多少次。
【2】有了可用的surface后,进入FRESH_SURFACE状态,调用EGLSurfaceHolder的createEGLSurface和makeCurrent来绑定线程、上下文和窗口。
【3】根据surface窗口宽高,设置OpenGL窗口的宽高,而后自动进入RENDERING状态。这部分对应GLSurfaceView.Renderer中回调onSurfaceChanged方法。
【4】进入循环渲染render,这里每隔20ms渲染一次画面。对应GLSurfaceView.Renderer中回调onDrawFrame方法。
为方便对比,这里贴一下以前文章定义的SimpleRender以下:
class SimpleRender: GLSurfaceView.Renderer {
private val drawers = mutableListOf<IDrawer>()
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
GLES20.glClearColor(0f, 0f, 0f, 0f)
//开启混合,即半透明
GLES20.glEnable(GLES20.GL_BLEND)
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA)
val textureIds = OpenGLTools.createTextureIds(drawers.size)
for ((idx, drawer) in drawers.withIndex()) {
drawer.setTextureID(textureIds[idx])
}
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
GLES20.glViewport(0, 0, width, height)
for (drawer in drawers) {
drawer.setWorldSize(width, height)
}
}
override fun onDrawFrame(gl: GL10?) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT)
drawers.forEach {
it.draw()
}
}
fun addDrawer(drawer: IDrawer) {
drawers.add(drawer)
}
}
复制代码
【5】若是surface被销毁(好比,进入后台),调用EGLSurfaceHolder的destroyEGLSurface销毁和解绑窗口。
注:当页面从新回到前台时,会从新建立surface,这时只要从新建立EGLSurface,并绑定上下文和EGLSurface,就能够继续渲染画面,无需开启新的渲染线程。
【6】SurfaceView被销毁(好比,页面finish),这时已经无需再渲染了,须要释放全部的EGL资源,并退出线程。
新建页面EGLPlayerActivity
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto">
<SurfaceView
android:id="@+id/sfv"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.constraint.ConstraintLayout>
复制代码
class EGLPlayerActivity: AppCompatActivity() {
private val path = Environment.getExternalStorageDirectory().absolutePath + "/mvtest_2.mp4"
private val path2 = Environment.getExternalStorageDirectory().absolutePath + "/mvtest.mp4"
private val threadPool = Executors.newFixedThreadPool(10)
private var mRenderer = CustomerGLRenderer()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_egl_player)
initFirstVideo()
initSecondVideo()
setRenderSurface()
}
private fun initFirstVideo() {
val drawer = VideoDrawer()
drawer.setVideoSize(1920, 1080)
drawer.getSurfaceTexture {
initPlayer(path, Surface(it), true)
}
mRenderer.addDrawer(drawer)
}
private fun initSecondVideo() {
val drawer = VideoDrawer()
drawer.setAlpha(0.5f)
drawer.setVideoSize(1920, 1080)
drawer.getSurfaceTexture {
initPlayer(path2, Surface(it), false)
}
mRenderer.addDrawer(drawer)
Handler().postDelayed({
drawer.scale(0.5f, 0.5f)
}, 1000)
}
private fun initPlayer(path: String, sf: Surface, withSound: Boolean) {
val videoDecoder = VideoDecoder(path, null, sf)
threadPool.execute(videoDecoder)
videoDecoder.goOn()
if (withSound) {
val audioDecoder = AudioDecoder(path)
threadPool.execute(audioDecoder)
audioDecoder.goOn()
}
}
private fun setRenderSurface() {
mRenderer.setSurface(sfv)
}
}
复制代码
整个使用过程几乎和上篇文章中,使用GLSurfaceView来渲染视频画面同样。
惟一点不同的,就是须要把SurfaceView设置给CustomerRenderer。
至此,就能够播放视频了。EGL基础知识、如何使用基本上就讲完了。
可是,彷佛没有发现EGL真正的用途在哪里,该有的东西GLSurfaceView都有了,为何还要学习EGL?
且听我继续吹吹水,哈哈哈。
若是你没有认真学习过EGL,那么你的OpenGL生涯将是不完整的,由于你始终没法深入的认识到OpenGL渲染机制是怎样的,那么在处理一些的问题的时候,就会显得很无力。
若是你须要使用到Android Mediacodec的编码能力,那么EGL就是必不可少的东西,在后续的关于视频编码的文章中,你将会看到如何使用EGL来实现编码。
在JNI层,Android并无实现一个相似GLSurfaceView的工具,来帮咱们隐藏EGL相关的内容。所以,若是你须要在C++层实现FFmpeg的编解码,那么就须要本身去实现整个OpenGL的渲染流程。
这才是学习EGL的真正目的,若是只是用于渲染视频画面,GLSurfaceView已经足够咱们使用了。
因此,EGL,必学!