Vulkan 并发机制
前言
Vulkan 是新一代图形(graphics)API,而它也是一个面向图形和计算的编程接口(graphics and compute)。支持 Vulkan 的设备可以是 GPU,也可以是 DSP 或者固定功能的硬件。
Vulkan 作为现代的图形 API,有着更加强大复杂的特性,它可以使我们从更底层的视角去看待图形编程,让开发者自己掌握多线程,内存分配等,作为追求性能的 API 它具备具备对多线程渲染友好的特性。
所谓的多线程并不是指 GPU 端的多线程图像渲染,而是指在 CPU 提交 DrawCall 时所做的一系列工作可以并行化,也就是说多线程渲染其实是在 CPU 端提升程序的性能。
单线程瓶颈
对于单线程的图形 API,性能的提升只能依赖于 CPU 主频的提高。为了解决性能瓶颈问题一般会将主线程和渲染线程分离或者异步加载资源等。但在复杂的现实场景中也是频繁遇到单线程的性能瓶颈。
以手机为例,CPU 主频提升有限,各大芯片厂商开始向多核多线程发展,考虑到功耗温控问题,又不能把 CPU 频率升太高,越来越高的刷新率对实时渲染的速度要求越来越苛刻。
编辑
传统图形限制
传统的图形 API 是单线程的,在使用 OpenGL 或者 D3D11 的时候,每次提交 DrawCall 之前,都需要将相关的状态进行更新,将需要用的资源进行绑定,还要进行相关的参数检查等工作,这些繁琐的操作在简单场景下的影响可控,但如果场景的几何体、材质种类非常多,用到的 Shader 数量比较多,每一帧的 Pass 比较多,就会导致有大量的 DrawCall 产生。那么每次在 CPU 端进行的这些操作的费时就很有可能会成为瓶颈。
一种很自然的优化策略,就是将所有的 CPU 端的这些操作并行处理,即多线程地进行状态修改、参数检查等工作。但是传统的图形 API 对此并不友好,不管是 D3D11 还是 OpenGL,它们都具有一个 Context 的概念,这个 Context 负责进行资源的绑定、状态修改、DrawCall 调用,这样的模式对多线程十分不友好,如果想要实现多线程提交,理论上是可以完成,但是非常麻烦,而且需要用到很多复杂同步原语,导致整体性能未必能达到理想的效果。
Vulkan 多线程
Vulkan 是线程友好的渲染 API,其多线程设计则主要体现在 Queue 和 CommandBuffer 上。Vulkan 所有需要 GPU 执行的命令,只能通过 CommandBuffer 来完成,这些命令并不只包括 DrawCall,对计算的调用,内存的操作,都需要用到 CommandBuffer。
Vulkan 不同于 Gles 只有一个(不被 API 暴露出来的)单一链条的 CommandBuffer 处理,它最大的特点是允许多个、多种类型的 CommandBuffer 同时在多个设备和线程上被处理。
Vulkan 内部默认认为对任何资源的访问不存在多线程竞争,所有的资源同步操作由应用开发者去负责。
CommandBuffer
Commandbuffer 是 Vulkan 显示暴露的数据结构,它是 cpu 同 gpu 传输信息的桥梁,cpu 将渲染指令记录到 CommandBuffer 上,然后通过提交给 queue 交由 gpu 执行。在一个 CommandBuffer 内部又包含了 renderpass,指令被记录在 renderpass 里面或外面,记录在 renderpass 里面的指令还可以被封装成次级 CommandBuffer,即 secondaryCommandbuffer 的形式被主 CommandBuffer 执行。
Command Buffer Pool
在 Vulkan 中,你需要自己从 Command Buffer Pool 中申请 command buffer,并将后续的 drawcall 等命令存入 command buffer。
QUEUE
Queue 是在 Vulkan 中唯一一个可以向 GPU 提交命令的通道,而不是通过绑定在一个单一线程上的 Context 来完成。物理设备中 Queue 可能不止一个,每一个 Queue 都被包含在 Queue Families 中。
Queue Families 是一个有相同功能的 Queues 的集合,它们的性能水平和对系统资源的访问是相同的,可并行执行并且他们之间的数据传输无损耗(同步除外)。
按照 Queue 的能力,可以分为:
Graphics(图形)
该系列中的 Queues 支持图形操作,例如绘制点,线和三角形。
Compute(计算)
该系列中的 Queues 支持诸如 computer shader 之类的计算操作。
Transfer(传输,拷贝)
该系列中的 Queues 支持传输操作,例如复制缓冲区和图像内容。
Sparse binding(稀疏绑定)
该系列中的队列支持用于更新稀疏资源(sparse resource)的内存绑定操作
同时 Vulkan 还支持多设备并行,每个设备可以拥有多个 queue,可以有多个 Command Buffer,并行的 queue,并行的 Command Buffer。
Vulkan ROI 大的并行优化
Command Buffer 的 Record:所有 vkcmd***类型的 API 都可以认为在进行 cmdbuffer 的 record。我们可以拆解出多个独立的 cmdbuffer,由不同的渲染线程进行 api 调用。(这里的瓶颈是 drawcall 数量)
Translate,Gfx,CS 创建各自独立的 queue 和 cmdbuffer,这样,图形 drawcall,cs 的 dispatch,图形资源的准备这三种不同工作将在不同的线程上处理,减少 drawcall 被其他工作 block 的机会。
总结
本篇文章主要讲了 Vulkan 的多线程设计机制,主要是围绕底层的实现机制讲了原理,并结合经验给了一些多线程优化建议。尽量减少资源间的竞争,在 Vulkan 中的一种简单的多线程模式为:每个线程在每一帧都负责设置好自己的 CommandBuffer,等待所有的线程将自己的 CommandBuffer 都设置好后,再将所有的 CommandBuffer 全部提交给 Queue。
欢迎各位关注转发,留言交流,关注微信公众号:江湖修行,第一时间交流
版权声明: 本文为 InfoQ 作者【江湖修行】的原创文章。
原文链接:【http://xie.infoq.cn/article/82fc763a12f921833f9cb5533】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论