同步 (Synchronization)
同步是确保 OpenGL 渲染管线已发出或执行完所提交命令的过程。
OpenGL 规范定义命令按给定顺序执行,后续命令行为如同所有先前命令已完成。但这是规范行为,实际实现可能不同。
异步执行 (Asynchronous Action)
OpenGL 渲染命令是异步执行的。调用 glDraw* 函数时,不保证渲染已完成,甚至可能尚未开始。OpenGL 允许实现自行决定何时执行命令。
优势
异步执行允许优化:驱动程序可缓存多个命令,然后一次性提交给 GPU,减少 CPU 模式切换开销。
OpenGL API 定义为同步:每个命令行为如同所有先前命令已完成。若命令依赖先前命令结果,实现会阻止执行重叠。
涉及客户端内存的函数(如 glReadPixels、glBufferSubData、glTexSubImage2D)返回时必须完成对客户端内存的操作。
缓冲区对象优势
使用缓冲区对象(Buffer Object)比客户端内存更高效,因为驱动程序无需担心用户后续修改内存,可直接将命令写入命令流。
命令状态
命令有三种概念状态:
- 未发出(Unissued):已提交给驱动程序,但尚未发送给硬件
- 已发出未完成(Issued but not complete):已发送给硬件,但结果未就绪
- 已完成(Complete):已完全离开管线,效果已写入输出
同步选项 (Synchronization Options)
显式同步
OpenGL 提供两个显式同步机制:
glFinish:阻塞当前 CPU 线程,直到所有渲染命令完成。
glFlush:阻塞当前 CPU 线程,直到所有命令已添加到 GPU 命令队列。
使用场景
如果在短时间内提交大量命令后不执行其他 OpenGL 调用,GPU 命令队列可能为空。调用 glFlush 可确保命令被推送到 GPU。
隐式同步
某些操作会隐式触发同步:
- 交换缓冲区:可能触发同步,等待影响默认帧缓冲区的命令完成
- 读取帧缓冲区到 CPU 内存:等待所有影响该帧缓冲区的渲染命令完成
- 写入缓冲区对象(
glBufferSubData或映射):等待使用该缓冲区的渲染命令完成
避免阻塞
在写入缓冲区对象前使其无效(invalidate),实现可分配新存储并孤立旧数据,避免等待。
使用 GL_MAP_UNSYNCHRONIZED_BIT 标志调用 glMapBufferRange 可跳过同步,但需自行处理可能的冲突。
使用像素缓冲区对象(Pixel Buffer Object)修改纹理数据不会强制同步,传输会延迟到纹理不再使用时。
同步对象 (Sync Objects)
glFinish 是粗粒度同步。更精细的同步可使用围栏对象(Fence):
围栏是命令流中的标记,可测试其是否已完成。若围栏已完成,则其之前发出的所有命令也已完成。
详见 同步对象。