OpenGL ES命令队列及glFinish/glFlush

栏目: 后端 · 发布时间: 4年前

内容简介:大家好,今天给大家介绍一下我们知道,我们调用的我画了一个图来表示:

大家好,今天给大家介绍一下 OpenGL ES 的命令队列及 glFinish/glFlush

我们知道,我们调用的 OpenGL ES 方法,都是在 CPU 上调用的,这些调用最终会被转换成 GPU 驱动指令而在 GPU 上执行,而 CPUGPU 因为是两个不同的处理器,它们之间自然是可以并行地执行各自的指令, OpenGL ES 有一个命令队列用于暂存还未发送到 GPU 的命令,实际上我们调用的绝大多数 OpenGL ES 方法,只是往命令队列时插入命令而已,并不会在 CPU 等命令执行完,因此如果大家去测耗时,会发现 OpenGL ES 大多数方法,基本都不耗时,无论渲染的东西多么复杂。

我画了一个图来表示:

OpenGL ES命令队列及glFinish/glFlush

这里注意一个细节,这个命令队列并不是所有的线程都对应同一个,命令队列是和 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拿去用
    ...
}
复制代码

我画了个图来直观的展示:

OpenGL ES命令队列及glFinish/glFlush

由于 glFinish() 要在 CPU 上等待,因此会对性能造成一定的影响,如果 thread0 是一个主渲染线程,那就会对帧率产生影响,因此把等待放到比较次要的 thread1 中会比较好,但是我们把 glFinish() 放到 thread1 可以吗?来看下面这张图:

OpenGL ES命令队列及glFinish/glFlush

前面提到过,命令队列是每个绑定了 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()

感谢阅读!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

互联网:碎片化生存

互联网:碎片化生存

段永朝 / 中信出版社 / 2009-11 / 42.00元

《互联网:碎片化生存》内容简介:在世界互联网人数超过17亿,中国网民接近4亿的时候,断言“这个版本的互联网没有未来”是要冒很大风险的。我们生活在比特和连线的世界,现代互联网所描绘出的“数字化”、“虚拟化”的未来是否完全值得信赖? 现代商业取得了巨大成功,但这并不是电脑和互联网精髓的自由体现,我们所使用的这个版本的电脑和互联网只不过是“被阉割”、“被劫持”的商业玩偶。 《互联网:碎片化生......一起来看看 《互联网:碎片化生存》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器