大家好,今天给大家介绍一下OpenGL ES
的命令队列及glFinish/glFlush
。
我们知道,我们调用的OpenGL ES
方法,都是在CPU
上调用的,这些调用最终会被转换成GPU
驱动指令而在GPU
上执行,而CPU
和GPU
因为是两个不同的处理器,它们之间自然是可以并行地执行各自的指令,OpenGL ES
有一个命令队列用于暂存还未发送到GPU
的命令,实际上我们调用的绝大多数OpenGL ES
方法,只是往命令队列时插入命令而已,并不会在CPU
等命令执行完,因此如果大家去测耗时,会发现OpenGL ES
大多数方法,基本都不耗时,无论渲染的东西多么复杂。
我画了一个图来表示:
这里注意一个细节,这个命令队列并不是所有的线程都对应同一个,命令队列是和EGL Context
对应的,而一个线程又只能同时绑定到一个EGL Context
(关于EGL、GL线程、线程共享EGL Context,可以参见我的另一篇文章《OpenGL ES 高级进阶:EGL及GL线程》),因此,可以理解为命令队列是和绑定的EGL Context
的线程对应的。
有时我们又希望在CPU
上等待OpenGL ES
命令执行完成,例如我们有时希望做多线程优化,在两个共享EGL Context
的线程中,在一个线程中渲染,在另一个线程中用渲染好的纹理做其它操作等,那么在这种情况下,我们是不能像在CPU
上做同步那样的,来看一段伪代码:
// 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拿去用
...
}
复制代码
我画了个图来直观的展示:
由于glFinish()
要在CPU
上等待,因此会对性能造成一定的影响,如果thread0
是一个主渲染线程,那就会对帧率产生影响,因此把等待放到比较次要的thread1
中会比较好,但是我们把glFinish()
放到thread1
可以吗?来看下面这张图:
前面提到过,命令队列是每个绑定了EGL Context
的线程各自有各自的,glFinish()
只会等待当前线程的命令队列中的命令执行完成,也就是等待thread1
的命令队列中的命令执行完成,因此是没有我们期望的效果的,在OpenGL ES 2.0
中,是没有办法做到在一个线程中等待另一个线程的OpenGL
命令的,在OpenGL ES 3.0
中可以用fence
实现。
前面说到绝大多数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()
。
感谢阅读!