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是线程友好的渲染API,其多线程设计则主要体现在Queue和CommandBuffer上。Vulkan所有需要GPU执行的命令,只能通过CommandBuffer来完成,这些命令并不只包括DrawCall,对计算的调用,内存的操作,都需要用到CommandBuffer。
Vulkan不同于Gles只有一个(不被API暴露出来的)单一链条的CommandBuffer处理,它最大的特点是允许多个、多种类型的CommandBuffer同时在多个设备和线程上被处理。
Vulkan内部默认认为对任何资源的访问不存在多线程竞争,所有的资源同步操作由应用开发者去负责。
Commandbuffer是Vulkan显示暴露的数据结构,它是cpu同gpu传输信息的桥梁,cpu将渲染指令记录到CommandBuffer上,然后通过提交给queue交由gpu执行。在一个CommandBuffer内部又包含了renderpass,指令被记录在renderpass里面或外面,记录在renderpass里面的指令还可以被封装成次级CommandBuffer,即secondaryCommandbuffer的形式被主CommandBuffer执行。
在Vulkan中,你需要自己从Command Buffer Pool中申请command buffer,并将后续的drawcall等命令存入command buffer。
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。
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。
欢迎各位关注转发,留言交流,关注微信公众号:江湖修行,第一时间交流