你们好,今天给你们介绍一下OpenGL ES
的命令队列及glFinish/glFlush
。多线程
咱们知道,咱们调用的OpenGL ES
方法,都是在CPU
上调用的,这些调用最终会被转换成GPU
驱动指令而在GPU
上执行,而CPU
和GPU
由于是两个不一样的处理器,它们之间天然是能够并行地执行各自的指令,OpenGL ES
有一个命令队列用于暂存还未发送到GPU
的命令,实际上咱们调用的绝大多数OpenGL ES
方法,只是往命令队列时插入命令而已,并不会在CPU
等命令执行完,所以若是你们去测耗时,会发现OpenGL ES
大多数方法,基本都不耗时,不管渲染的东西多么复杂。post
我画了一个图来表示:性能
这里注意一个细节,这个命令队列并非全部的线程都对应同一个,命令队列是和EGL Context
对应的,而一个线程又只能同时绑定到一个EGL Context
(关于EGL、GL线程、线程共享EGL Context,能够参见个人另外一篇文章《OpenGL ES 高级进阶:EGL及GL线程》),所以,能够理解为命令队列是和绑定的EGL Context
的线程对应的。优化
有时咱们又但愿在CPU
上等待OpenGL ES
命令执行完成,例如咱们有时但愿作多线程优化,在两个共享EGL Context
的线程中,在一个线程中渲染,在另外一个线程中用渲染好的纹理作其它操做等,那么在这种状况下,咱们是不能像在CPU
上作同步那样的,来看一段伪代码:spa
// thread0:
fun run() {
...
// 调用glDrawXXX()渲染到texture上
lock.notify()
...
}
// thread1:
fun run() {
...
lock.wait()
// 将texture拿去用
...
}
复制代码
代码中,咱们但愿在thread0
完成渲染后,在thread1
中将它读到bitmap
中,这样会读到什么结果?基于前面的讨论,能够知道这样读到的结果是不肯定的,由于thread0
执行glDrawXXX()
以后,并不会等待GPU
真正执行了渲染,因此thread1
在使用texture
时,它的内容是不肯定的,有可能还没开始渲染,也有可能渲染到了一半,或者是已经渲染完了。要获得正确的结果,在OpenGL ES 2.0中
咱们可使用glFinsh()
,在OpenGL ES 3.0
中可使用fence
,后面我会写文章介绍fence
,如今咱们使用glFinish()
,它的做用是在CPU
上等待当前线程对应的命令队列里的命令执行完成,加上glFinish()
后,咱们就必定能获得正确的结果:线程
// thread0:
fun run() {
...
// 调用glDrawXXX()渲染到texture上
glFinish()
lock.notify()
...
}
// thread1:
fun run() {
...
lock.wait()
// 将texture拿去用
...
}
复制代码
我画了个图来直观的展现:code
因为glFinish()
要在CPU
上等待,所以会对性能形成必定的影响,若是thread0
是一个主渲染线程,那就会对帧率产生影响,所以把等待放到比较次要的thread1
中会比较好,可是咱们把glFinish()
放到thread1
能够吗?来看下面这张图:cdn
前面提到过,命令队列是每一个绑定了EGL Context
的线程各自有各自的,glFinish()
只会等待当前线程的命令队列中的命令执行完成,也就是等待thread1
的命令队列中的命令执行完成,所以是没有咱们指望的效果的,在OpenGL ES 2.0
中,是没有办法作到在一个线程中等待另外一个线程的OpenGL
命令的,在OpenGL ES 3.0
中能够用fence
实现。blog
前面说到绝大多数OpenGL ES
方法是不会等待的,那么什么方法会等待呢?刚才的glFinish()
就是一个,此外,还有将texture
读取出来的方法glReadPixels()
也会等待,另外还有eglSwapBuffers()
,实际这2个方法会隐式调用glFinish()
,因此有时候咱们经常发现,glReadPixels()
会耗时,因而有些人会认为,把texture
读出来的操做很耗时,实际上这个读操做并无多耗时,耗时是在等待命令队列中的全部命令执行完成。队列
你们能够试一下,在glReadPixels()
前若是先调glFinish()
把命令队列清空,再执行glReadPixels()
,会发现glReadPixels()
没有想像中的那么耗时。
glReadPixels()
为何会隐式调用glFinish()
?你们能够这样理解,由于glReadPixels()
是要将texture
读出来,若是不保证以前的渲染命令执行完,那么读出来的结果就是不肯定的,而eglSwapBuffers()
为何也会隐式调用glFinish()
?能够相似地这样理解,由于eglSwapBuffers()
是将双buffer
进行交换从而让正在接受渲染的back buffer
能显示出来,若是不保证全部渲染命令执行完,是否是有可能显示出来是残缺不全的?
说到这,顺便提一下OpenGL ES
的耗时测量,因为OpenGL ES
大多数方法只是往命令队列里插入命令而不等待执行完成,所以要测量一段OpenGL ES
操做的代码真正的耗时,须要在先后加上glFinish()
:
glFinish()
val startTime = System.currentTimeMillis()
// 一顿OpenGL ES操做
glFinish()
val duration = System.currentTimeMillis() - startTime
复制代码
在前面也加glFinish()
是为了将以前的命令先执行完,不要干扰咱们的测量。
与glFinish()
相似的还有一个方法是glFlush()
,它的做用是将命令队列中的命令所有刷到GPU
,但并不等它执行完成,所以有一些操做但愿它能快些执行,但又不是特别急切到立刻等它执行完成,这时候就能够用glFlush()
。
感谢阅读!