一、统一接口
Vulkan为跨平台跨设备的图形编程提供了统一的接口。这个统一接口可以理解为以下几个方面:
1、Vulkan可以支持不同的操作系统平台,不同的GPU产品。操作系统包括Linux,Windows和Android等。支持Vulkan的GPU厂家有AMD, Intel, NVIDIA, ARM, VIVANTE, QUALCOMM, Imagination等主流厂商。
2、使用Vulkan编程时,应用是链接一样的图形库,引用同样的头文件。无论应用运行在哪里,只需要编写同一份代码,不再像OpenGL一样需要定义Windows/Android/Linux等相关的预处理宏来处理操作系统相关的代码。
3、OpenGL作为一个发展多年的图形接口,导致任何一个渲染绘制的效果都会有多种不同的实现方式,而且不同的方式在不同的GPU平台上面性能也不尽相同。这让应用程序员非常难于抉择。Vulkan大幅减少这种功能实现的方式,只为应用提供最有效率的方式。这样应用编程变得简单而且不需要考虑不同GPU平台的性能问题。而GPU厂商负责为这种单一的实现方式来优化其内部实现,使其达到最佳性能。
4、Vulkan也尽量减少与特殊硬件相关的优化接口,使得应用开发者能够聚焦于渲染实现的主要流程,而不是某家厂商的某个特定技术上面。
5、Vulkan仍然保留了OpenGL时代就存在的Extension扩展功能。这是因为GPU厂商的架构确实没法完全统一,特殊功能的实现仍然需要扩展接口来支持。
二、高效的接口
图形程序的性能低下有很大比例是因为CPU的瓶颈,而其原因一般有两个,一是OpenGL ES驱动本身CPU开销过大,这包括驱动内部的GPU/CPU的同步等待。另一个是过多的draw call调用,例如在桌面渲染程序中一帧超过1000个draw就会让单个CPU核运行非常吃力。这种情况在移动平台会更加常见,因为移动CPU的性能比桌面CPU更差,因此现在的移动游戏中通常一个场景也就两三百个draw call。Vulkan试图通过降低驱动的开销和减少不必要的GPU/CPU同步来提升这方面性能。
Imagination在优酷上面的一段视频向我们展示了Vulkan与OpenGL ES在CPU开销方面的差距(http://v.youku.com/v_show/id_XMTMwNzA2MjkyNA==.html)。这个展示版本在Android环境下基于英特尔处理器的Nexus Player上运行。它是一款集成了PowerVR G6430 GPU的消费型设备,使用最新的用于PowerVR GPU的Vulkan API原型驱动器(最终性能可能有所差异)。按照Imagination的说法:“左边视频展示的是Vulkan,右边则是OpenGL® ES 3.0。我们尝试着让两个版本运行相同的代码,且二者的运行均没有扩展。演示版本均不使用实例,每个绘制调用可使用不同材料或结构的几何纹理,且其CPU性能将非常相似。请注意,这是一个夸张的场景,目的是为了突出Vulkan的优势。但这并非从不利的角度来展示OpenGL ES——我们故意不使用OpenGL ES设计的方式。我们旨在使用Vulkan API来绑定GPU,这就意味着GPU和CPU的使用需尽可能有效。对于开发人员和厂商而言,这是一桩幸事。”
正如你所见,左下端展示的CPU使用情况的图片中,CPU在模型的绘制调用中使用率非常低。在最高的缩放级别中,绘图为每秒400,000 模型。每个模型对象有不同的变形,且有许多不同的材料、结构、混合模式和着色器被使用。OpenGL ES API性能低下的原因在于OpenGL ES要求很多调用进入内核模式,应用程序在循环调用draw call时需要改变驱动的draw状态以及生成GPU的渲染命令。这与预生成这些命令的Vulkan形成鲜明对比。在Vulkan中执行预生成命令非常得快速,且几乎没有CPU开销,也不需要驱动在渲染循环内确认或编译任何东西。这些预生成的命令被称为命令缓冲区。
较低的一条线指的是处理器CPU的使用率,上端的线表示系统CPU的使用率。两条线在Vulkan中都有所下降。
这种高性能的获取来源于Vulkan提供的命令缓冲区的复用机制。对于绘制同样的模型,Vulkan可以预先生成渲染的命令缓冲区。其后每帧需要绘制这种物体时,只需要把预先准备好的命令缓冲区送给GPU的命令队列执行就可以了,这部分开销非常小。而与之对应的OpenGL ES即使是绘制同样的物体,也需要在每帧调用同样的draw call。每次draw call的调用意味着驱动内部需要准备好shader代码,准备顶点数据与vertex shader的绑定,切换纹理数据和状态等等。一旦draw call数量过于巨大,那么GPU其实大多数时间是在等待驱动程序完成draw call的准备工作,而不是与CPU并行运行。
Vulkan提高性能的另一个举措就是可控的错误检查和调试信息输出。对于应用来说只有在开发调试阶段错误检查和调试信息才是有用的,一旦应用被验证后,就不需要这些冗余的操作。Vulkan为应用提供了关闭这些操作的机制,使得CPU开销进一步减少。
OpenGL ES提供了隐式的资源并发访问保护机制,这使得应用开发人员不需要考虑某个资源访问是否会影响到GPU渲染,或者其它线程的访问。例如在GPU渲染管线正在使用纹理时,应用调用glTexSubImage2D去试图更新纹理数据,这个时候驱动会在内部去等待GPU渲染执行完毕之后再更新该纹理,然后返回应用。一切看起来都很完美,但是这给应用程序带来性能的损耗,因为应用需要等待GPU执行而什么都不能做。其次,这给应用的执行时间带来了不可预测性,应用不会知道这次更新纹理什么时候能够完成。Vulkan则直接放弃了这种隐式的保护机制,让应用来负责资源访问的保护。
OpenGL ES的另外一个问题是它对多线程渲染支持不好。这导致主场景的渲染工作通常都压在一个线程上面,导致CPU某个核的负担过重。Vulkan力图改进这种单线程渲染的方式。Vulkan不再限制渲染命令只能在当前线程绑定的context上面生成和执行。反之,它允许多个线程同时为某个场景生成渲染命令。通过把工作分散给数个线程,降低了CPU单核负担,也就变相提升GPU的吞吐量,提升了渲染性能。
需要注意的是Vulkan为了提供这种多线程的渲染能力,大幅度改变了OpenGL ES的context机制。其与OpenGL ES的区别如下:
OpenGL ES有全局的状态,例如alpha blend, depth function等等,而Vulkan把这些状态都保存在Pipeline object中,任何需要的线程都可以使用该object。
OpenGL ES的每个渲染线程需要绑定一个context来负责管理渲染时使用的资源等等。Vulkan则是让资源存在于各自的object中,渲染命令需要使用时直接访问该object。
OpenGL ES的context,状态,渲染资源在多线程访问时都是线程安全的,Vulkan则不保证上述这些object能够安全访问,这需要应用自己去实现保护。
Vulkan与OpenGL ES另外一个不同则是在内存的管理上面。传统的OpenGL ES需要驱动自己负责为渲染资源,FBO等分配内存并且管理。那么应用是无法知道驱动内部到底使用了多少内存,也无法知道资源什么时候不被驱动和GPU使用可以释放掉了。经常会出现应用维护一份资源同时OpenGL ES驱动又维护一份资源的情况。这在移动平台上面会加重本来就不宽裕的内存开销。Vulkan自己不会维护这部分内存,而是把分配管理的权利交给了应用,使得应用可以统一的优化内存开销。