本文转载自天天P图攻城狮微信公众号,作者:天天P图Android工程师kenneyqin(覃华峥),原文链接https://mp.weixin.qq.com/s/j_N5_C7iQUPWENdRYfjdxg。未经过作者同意请勿擅自转载。

从事OpenGL ES相关开发的技术人员,常常会对一些问题感到困惑,例如GL线程究竟是什么?为什么在这个GL线程申请的texture不能在另外一个GL线程使用?如何打破这种限制等。这些问题在我们团队中也曾经十分让人困惑,因为在网上也找不到详细的解释,这篇文章将回答以下一些棘手而又很难搜到答案的问题:

(1)GL线程和普通线程有什么区别?

(2)texture所占用的空间是跟GL线程绑定的吗?

(3)为什么通常一个GL线程的texture等数据,在另一个GL线程没法用?

(4)为什么通常GL线程销毁后,为什么texture也跟着销毁了?

(5)不同线程如何共享OpenGL数据?

一、OpenGL ES绘图完整流程

首先来看看使用OpenGL ES在手机上绘图的完整流程,这里为什么强调“完整流程”,难道平时用的都是不完整的流程?基本可以这么说,因为“完整流程”相当复杂,而Android系统把复杂的过程封装好了,开发人员接触到的部分是比较简洁易用的,一般情况下也不需要去关心Android帮我们封装好的复杂部分,因此才说一般情况下我们所接触的OpenGL ES绘图流程都是“不完整”的。

以下是OpenGL ES在手机上绘图的完整流程:

(1)获取显示设备

这段代码的作用是获取一个代表屏幕的对象,即EGLDisplay,传的参数是EGL10.EGL_DEFAULT_DISPLAY,代表获取默认的屏幕,因为有些设备上可能不止一个屏幕。

(2)初始化

这段代码的作用是初始化屏幕。

(3)选择config

这段代码的作用是选择EGL配置, 即可以自己先设定好一个你希望的EGL配置,比如说RGB三种颜色各占几位,你可以随便配,而EGL可能不能满足你所有的要求,于是它会返回一些与你的要求最接近的配置供你选择。

(4)创建Context

这段代码的作用就是用从上一步EGL返回的配置列表中选择一种配置,用来创建EGL Context。

(5)获取Surface

这段代码的作用是获取一个EGLSurface,可以把它想象成是一个屏幕对应的内存区域。注意这里有一个参数surfaceHolder,它对应着GLSurfaceView的surfaceHolder。

(6)将渲染环境设置到当前线程

这段代码的作用是将渲染环境设置到当前线程,相当于让当前线程拥有了Open GL的绘图能力,为什么做了这步操作,线程就拥有了Open GL的绘图能力?后面会讲解。

接下来就是绘图逻辑了:

以上步骤,对于不经常接触EGL的开发人员,也许看起来比较陌生,让我们来看看我们比较熟悉的GLSurfaceView,看看它里面和刚说的那一堆乱七八糟的东西有什么关系。

二、GLSurfaceView内部的EGL相关逻辑

查看GLSurfaceView的源码,可以看见里面有一个类叫GLThread,就是所谓的“GL线程”:

可以看到,虽然它名叫GLThread,但是它也是从普通的Thread类继承而来,理论上就是一个普通的线程,为什么它拥有OpenGL绘图能力?继续往下看,里面最重要的部分就是guardedRun()方法,让我们来看一下guardedRun()方法里有什么东西,guardedRun()里大致做的事情:

仔细看一下guardedRun()的源码会发现,里面做的事情和之前说的“完整流程”能都一一对应着,里面还有我们非常熟悉的onSurfaceCreated()、onSurfaceChanged()和onDrawFrame()这三个回调,而一般情况下,我们使用OpenGL绘图,就是在onDrawFrame()回调里绘制的,完全不用关心“完整流程”中的复杂步骤,这就是前文为什么说“完整流程”相当复杂,而Android系统帮我们把复杂的过程封装好了,我们接触到的部分是比较简洁易用的,一般情况下也不需要去关心Android帮我们封装好的复杂部分。

至此,得到一个结论,那就是所谓的GL线程和普通线程没有什么本质的区别,它就是一个普通的线程,只不过它按照了OpenGL绘图的完整流程正确地操作了下来,因此它有OpenGL的绘图能力。那么,如果我们自己创建一个线程,也按这样的操作方法,那我们也可以在自己创建的线程里绘图吗?当然可以!

三、EGL如何协助OpenGL

我们先随便看一下OpenGL的常用方法,例如最常用的GLES2.0.glGenTextures()和GLES2.0.glDeleteTextures(),在Android Studio里点进去看一下:

是native的方法,并且是静态的,看起来和EGL没有关系,它怎样知道是GL线程去调的还是普通线程去调的?它又怎样把GLES2.glDeleteTextures()和GLES2.0.glGenTextures()的对应到正确的线程上?我们来看看底层的源码:

可以看到,在底层,它会去拿一个context,实际上这个context就是保存在底层的EGL context,而这个EGL context,它是Thread Specific的。什么是Thread Specific?就是说,不同的线程去拿,得到的EGL context可能不一样,这取决于给这个线程设置的EGL context是什么,可以想象成每个线程都有一个储物柜,去里面拿东西能得到什么,取决于你之前给这个线程在储物柜里放了什么东西,这是一个形象化的比喻,代码时的实现其实是给线程里自己维护了一个存储空间,相当于储物柜,因此每个线程去拿东西的时候,只能拿到自己储物柜里的东西,因此是Thread Specific的。

那么是什么时候把EGL context放到线程的储物柜里去的呢?还记得前面提到过eglMakeCurrent()这个东西吗?我们来看看它的底层:

可以看到,在调用eglMakeCurrent()时,会通过setGLThreadSpecific()将传给eglMakeCurrent()的EGL Context在底层保存一份到调用线程的储物柜里。我们再来仔细看一下eglMakeCurrent()里一步一步做了什么,这对于理解线程绑定OpenGL渲染环境至关重要:

归纳下来就是这么几点:

1.获取当前线程的EGL Context current(底层用ogles_context_t存储)

2.判断传递过来的EGL Context gl是不是还处于IS_CURRENT状态

3.如果gl是IS_CURRENT状态但又不是当前线程的EGL Context,则return

4.如果gl不是IS_CURRENT状态,将current置为非IS_CURRENT状态

5.将gl置为IS_CURRENT状态并将gl设置为当前线程的Thread Local的EGL Context

因此有两点结论:

1.如果一个EGL Context已被一个线程makeCurrent(),它不能再次被另一个线程makeCurrent()

2.makeCurrent()另外一个EGL Context后会与当前EGL Context脱离关系

继续看GLES2.0.glGenTextures():

上面给出了glGenTextures()底层的一些调用关系,下面我有一个图来展示一下调了glGenTextures(),分配的texture放在哪里了:

这其实没那么重要,因为这里只是存了一个texture id,并不是texture真正所占的存储空间,这很好理解,因此调glGenTextures()方法的时候,也没指定要多大的texture嘛。那么texture真正所占的存储空间在什么地方呢?那就要看看给texture分配存储空间的方法了,也就是glTexImage2D()方法:

下面再给出一个图展示texture所占用的存储空间的空间放在什么地方:

到这里,又有了一个结论:本质上texture是跟EGL Context绑定的,并不是跟GL线程绑定的,因此GL线程销毁时,如果不销毁EGL Context,则texture没有销毁。我们可能常常听说这样一种说法:GL线程销毁后,GL的上下文环境就被销毁了,在其中分配的texture也自然就被销毁了。这种说法会让人误为texture是跟GL线程绑定在一起的,误认为GL线程销毁后texture也自动销毁,其实GL线程并不会自动处理texture的销毁,而需要手动销毁。有人想问了,我们平时用GLSurfaceView时,当GLSurfaceView销毁时,我们如果没有delete掉分配的texture,这些texture也会没自己释放,这是怎么回事?这是因为GLSurfaceView销毁时帮你把texture销毁了,我们来看看GLSurfaceView里相关的代码:

因此如果你自己创建了一个GL线程,当GL线程销毁时,如果你不主动销毁texture,那么texture实际上是不会自动销毁的。

四、总结

下面总结一下本文,回答文章开头提出的问题:

1)GL线程和普通线程有什么区别?

答:没有本质区别,只是它按OpenGL的完整绘图流程正确的跑了下来,因而可以用OpenGL绘图

2)texture所占用的空间是跟GL线程绑定的吗?

答:跟EGL Context绑定,本质上与线程无关

3)为什么通常一个GL线程的texture等数据,在另一个GL线程没法用?

答:因为调用OpenGL接口时,在底层会获取Thread Specific的EGL Context,因此通常情况下,不同线程获取到的EGL Context是不一样的,而texture又放在EGL Context中,因此获取不到另外一个线程创建的texture等数据

4)为什么通常GL线程销毁后,为什么texture也跟着销毁了?

答:因为通常是用GLSurfaceView,它销毁时显式调用了eglDestroyContext()销毁与之绑定的EGL Context,从而其中的texture也跟着被销毁

5)不同线程如何共享OpenGL数据?

答:在一个线程中调用eglCreateContext()里传入另一个线程的EGL Context作为share context,或者先让一个线程解绑EGL Context,再让另一个线程绑定这个EGL Context。

由于笔者知识水平有限,文中若有理解错误之处,请见谅指正。

如果您觉得我们的内容还不错,就请关注我们的公众号~

我们会陆续分享天天P图涉及到的终端,前后端技术

android 多线程创建texture,从源码角度剖析Android系统EGL及GL线程相关推荐

  1. 从源码角度看Android系统Launcher在开机时的启动过程

    Launcher是Android所有应用的入口,用来显示系统中已经安装的应用程序图标. Launcher本身也是一个App,一个提供桌面显示的App,但它与普通App有如下不同: Launcher是所 ...

  2. 从源码角度看Android系统SystemServer进程启动过程

    SystemServer进程是由Zygote进程fork生成,进程名为system_server,主要用于创建系统服务. 备注:本文将结合Android8.0的源码看SystemServer进程的启动 ...

  3. 从源码角度看Android系统Zygote进程启动过程

    在Android系统中,DVM.ART.应用程序进程和SystemServer进程都是由Zygote进程创建的,因此Zygote又称为"孵化器".它是通过fork的形式来创建应用程 ...

  4. 从源码角度解析Android中APK安装过程

    从源码角度解析Android中APK的安装过程 1. Android中APK简介 Android应用Apk的安装有如下四种方式: 1.1 系统应用安装 没有安装界面,在开机时自动完成 1.2 网络下载 ...

  5. 从源码角度分析Android中的Binder机制的前因后果

    为什么在Android中使用binder通信机制? 众所周知linux中的进程通信有很多种方式,比如说管道.消息队列.socket机制等.socket我们再熟悉不过了,然而其作为一款通用的接口,通信开 ...

  6. 从源码角度看Android系统init进程启动过程

    init进程是Linux系统中用户空间的第一个进程,进程号为1.Kernel启动后,在用户空间启动init进程,并调用/system/core/init.cpp中的main方法执行一些重要的工作. 备 ...

  7. [Vue.js进阶]从源码角度剖析vue-router(三)

    前言 在上篇中主要叙述了 vue-router 中生成 $route 对象的时机,路由懒加载的原理,以及异步路由之前执行的一系列路由守卫 在本篇中会讲述: 异步路由解析成功后执行的一系列路由守卫 vu ...

  8. HashMap,ArrayMap,SparseArray 源码角度分析,Android中的数据结构你该如何去选择?

    table = newTab; 可以看到当我们的table数组存储的节点值大于threshold时,会按我们的当前数组大小的两倍生成一个新的数组,并把旧数组上的数据复制到新数组上这就是我们的HashM ...

  9. 从源码角度分析Android系统的异常捕获机制是如何运行的

    我们在开发的时候经常会遇到各种异常,当程序遇到异常,便会将异常信息抛到LogCat中,那这个过程是怎么实现的呢? 我们以一个例子开始: import android.app.Activity; imp ...

最新文章

  1. 找出重复的那个数字的异或算法
  2. Python多线程3:queue
  3. 圆章能随便刻吗_自己晒干的蒲公英能长期当茶随便喝吗?医生:3个危害不请自来...
  4. NGINX和NGINX Plus的速率限制
  5. SQL注入学习part02:(结合sqli-libs学习:11-20关)
  6. 安装SQLServer2008后Windows防火墙上的端口开放
  7. 必须声明标量变量是什么意思_机器视觉学习之halcon系列---一文带你理解handle变量是什么意思...
  8. 数据库系统概论第五版课后习题答案王珊
  9. 常用电感封装与电流关系
  10. 强大的支持多文件上传的jQuery文件上传插件Uploadify
  11. 华硕S400装win7
  12. NASA锂电池容量增量数据处理
  13. Oracle计算时间差
  14. ESP-Hosted:降低物联网设备的部署成本与复杂性
  15. php使用Qrcode生成二维码
  16. Halcon 算子 elliptic_axis
  17. Git - git checkout git branch 创建/删除分支用法及区别
  18. Java的流程控制语句
  19. tableau 字段去重_Tableau
  20. jvm系列(十一):JVM演讲PPT分享

热门文章

  1. 【知其然,知其所以然】配置中心 Apollo源码剖析
  2. 佳能ir2004打印机显示无法连接计算机,教你解决打印机无法连接USB故障困扰
  3. Caltech-UCSD Birds-200-2011
  4. 一个二维码通吃 android , ios 下载
  5. 一名SM是怎么炼成的(之一锻炼领导力)
  6. ERDAS各版本关系简析(转载)
  7. 工作半年后的一点感悟
  8. laravel artisan命令汇总
  9. 如何将m4v视频格式快速转换成mp4视频呢
  10. 【人工智能】作业1: Bait游戏 实验报告