OpenGL ES命令队列及glFinish/glFlush

你们好,今天给你们介绍一下OpenGL ES的命令队列及glFinish/glFlush多线程

咱们知道,咱们调用的OpenGL ES方法,都是在CPU上调用的,这些调用最终会被转换成GPU驱动指令而在GPU上执行,而CPUGPU由于是两个不一样的处理器,它们之间天然是能够并行地执行各自的指令,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()

感谢阅读!

相关文章
相关标签/搜索