【深度原创】正面密铺或Vulkan如何映射至PowerVR GPU

本文是该系列的最后一篇文章。该系列文章在我的网页上已载有数月,由于Vulkan产品的发布占用了我很多时间,最后收官之作的发表可能有些晚,希望大家还能关注它。本文中,我将阐述为何Vulkan比上一代API更适合硬件,同时,我还将深入PowerVR GPU的一些细节,并援引具体案例。

OpenGL ES

首先,让我们看看当下行业领先的API及其问题存在的原因。距OpenGL ES的发行已有12年多,而API是基于OpenGL ES的产品。OpenGL早在23年前便已设计问世,其最初设计的硬件与当下使用的各式硬件大为不同。

状态机

OpenGL是一款大型的全球状态机,每次操作均需要考虑当前状态的各个部分,如混合模式、当前的着色器、深度测试信息等。所有的事情看似一个简单的制动开关或杠杆,可以不计实际的后果随意改变开关状态——它只是一个函数调用?对于现代硬件而言这是不切实际的——例如很多状态会被转化为着色代码。

移动产品的高效性一文中,我已经提到,考虑到挂接及渲染期间CPU的使用率,着色器修复是不确定的。还有另一个我之前未提及的问题——着色器本身的低效率。如果必须修复着色器状态,则会出现优化后编译,即附加至剩余的着色器中是有效的。如果在编译时状态已知,则可以一直优化编译,以避免出现几条指令。为解决此问题,驱动器可能会进行背景的重新编译,但这本身也是一个问题(消耗更多的CPU时间)。

隐含同步

OpenGL ES假设很多东西是相互隐含同步的。只有引入栅栏时,计算着色器及其不良反应才被认为是任何形式的异步工作。大部分API仅仅只是工作——事实上这可以归结为资源跟踪、缓存刷新和场景后的依赖关系链建设。

驱动器不可能非常准确地检测依赖项——它们十分保守,以能实现OpenGL ES功能。这意味着将不可避免地进行一些不必要的缓存刷新或不必要的序列化工作——换言之,硬件需要做更多的工作。

立即模式

自始至终,OpenGL ES中指定的命令应该严格按照规定的先后顺序来执行。一个简单的命令如绘制调用通常被当作一个单一的整体工作单元,其在GPU中有规定的队列顺序。这种行为即立即执行模式——每个指定的工作以某种方式被即刻发送至GPU进行处理。

在过去,立即渲染模式(IMR)架构可以很好地映射到这种思维方式中,但现代IMR倾向于批量处理工作以提高性能。

基于区块贴图的渲染 (TBR)或基于区块贴图的延迟渲染 (TBDR)从未以这种方式工作。迄今为止存在的最多产的类型是GPU架构类型:这些架构类型的主要工作单元非常大——渲染通道便是很好的诠释——所有的绘制调用都集中在相同的帧缓冲区中。

1

TBR和TBDR都有两级渲染,早期阶段主要处理几何图形及在屏幕空间贴图中进行分类排序。第二阶段则将贴图栅格化,使整个贴图完全保存在片上帧缓冲区中——这可节省大量的带宽,其在移动市场占据优势。Rys Sommefeldt做了更详细的阐述,感兴趣的可以查阅他的文章

关键在于,在光栅化阶段,绘制调用是无意义的——一个单一的绘制调用可能产生多个贴图的光栅化,每个贴图包含了多个绘制调用的工作。如果某些信息引起绘图之间的冲刷,则整个渲染就会分裂,这就需要对很多贴图进行再次渲染。在贴图开始和结束时,必须加载帧缓冲区数据到贴图中,并随后进行存储——这样重复多次后便失去了基于区块贴图架构的优势,而架构本身是极力避免消耗此类带宽的。

总之,现代硬件倾向于批量处理工作,且提交单个绘制调用会降低效率。在OpenGL ES中有很多操作迫使驱动器提交单个绘制调用,这一点大家有目共睹。在TBR或TBDR中,这会产生很多不必要的且驱动器无力应对的带宽。

Vulkan

我可能呈现给大家的OpenGL ES是比较沉闷的形象,但不要灰心,不然当今的移动世界便不会有如此多精彩的图像内容。

Vulkan甚至比一个高度优化的OpenGL ES驱动器做的更好。我以前提过,Vulkan是显式的,且需要在应用程序中获取大量的信息——所有这些都是确保Vulkan可以流畅地工作,且不会产生很多用户不可见的成本。

管线

Vulkan假定所有的状态都将被再次启用,因此其比OpenGL ES看起来更为静态。管线多采用先前的动态状态,并与着色器一起被编译。这意味着任何先前需要着色器修复的信息现在可以提前被编译。在编译时拥有这些信息意味着可以对绘制调用即刻使用的着色器和状态进行完整的编译和优化,且不需要在渲染循环中将这些信息进行打包处理。

很多许多情况下,一个应用程序可能仅使用一个着色器及一组或两组状态——保持低成本。然而某些情况下,需要设置很多不同的状态,因此会产生大量的管线对象。Vulkan没有降低管线对象数量,同时,通过使用管线缓存编译整个着色器组所花费的时间应该具有可比性(或更快)。创建一组管线时,管线缓存是可以与创建信息一起被传输的对象,且缓存的是有用信息或管线所需的编译状态和着色器。如果两个管线具有相同的着色器,但状态略有不同,那么创建和编译的成本将远低于单独编译的成本。

PowerVR GPU的管线

以混合状态为例——众所周知,我们没有用于混合的固定功能硬件。通常不太需要此类硬件——片上存储非常快,直接在着色器内核中混合会更加简单。随后,在OpenGL ES中设置混合模式会产生一组指令,其表示在当前着色结束时进行混合修复。

正如我之前所说,着色器修复可能引起效率降低。而着色器分析显示,混合修复则情况不会太糟,其通常只有一个或两个指令。这看似不切实际,不然今天我们就得重新审视我们的方案,但其确实合乎情理。Vulkan在创建管线时就给了全方位的信息,使我们在编译之前就可以折叠这些指令,精简着色器的处理工作。其他状态如帧缓冲区格式和顶点布局可分为相似的类型(更琐碎和不常见的状态)。

显示同步

相比其他图形API,该API的各操作之间有更多的同步控制。主要用“what”、“when”、“where”和“how”信息来描述内存和执行依赖关系。

•依赖关系中涉及的对象和操作是什么?
•依赖关系何时开始何时结束?
•依赖关系在管线的哪个位置生成(如顶点着色)以及其必须在哪个位置实现(如片段着色)?
•相互依赖的双方如何使用图像?

所有这些信息可以使驱动器建立一个全面的依赖关系链,且如果应用程序可准确表示依赖关系,那么便可以仅仅等待需要绝对刷新的缓存和绝对完成的操作。

PowerVR GPU的显示同步

在基于区块贴图的架构中,工作分为了两个部分——几何贴图阶段和光栅化阶段。在OpenGL ES中,只有任务相当繁重的手控同步——栅格、隐式同步和内存屏障。光栅化阶段无需等待,或者说只有在贴图阶段才需要等待——驱动器通过启发式算法来实现等待或者完整的同步事件才需要等待——这将严重影响性能。通常,我们在OpenGL ES中使用大量的启发式算法来完成这项工作,但付出的代价是这样往往过于保守,导致性能大为降低。

在Vulkan中,可以使用为每个同步事件描述的管线级数来决定执行哪个硬件阶段。如果在事件早期片段阶段需要等待,则可以执行贴图阶段来提前获取绘制调用,以求得先机,并在处理过程中提升性能。同样,如果贴图阶段的任务需要等待,则表明在光栅化之前便已经开始执行任务。这两种情况都会导致任务之间的延迟时间缩短,并通过允许多个任务的重叠减少在GPU上不必要的空闲时间。

命令缓冲区和硬件队列

Vulkan使用延期命令提交模型——将绘制调用记录成许多命令缓冲区,随后应用程序将这些缓冲区作为独立的操作提交至硬件中。这样便可以提前了解场景大部分的信息,并适合地优化提交内容,而这些曾经在OpenGL中是很难实现的。

单独的硬件队列可以很好地映射到现代GPU中——其通常有一个或多个前端输入队列用来处理命令输入。准确地曝光这些队列可以给应用程序展示一个底层硬件视图,而这个视图本是不存在的。例如,如果GPU有一个用于图形命令的前端——应该在API中仅曝光一个图形队列。队列提交是外部同步的,所以驱动器在处理多个线程时不需要持有锁,且由于有一个紧密的映射到硬件中,队列提交是一个相对低成本的操作。

PowerVR GPU的命令缓冲区和硬件队列

PowerVR以不同的顺序运行,这可能原本是对即时光栅化程序的预期,正如上所述。为获得所需的硬件效率,以一个特定的方式排列操作至关重要(而不是即刻提交顶点和光栅任务)。在Vulkan中,应用程序提前在命令缓冲区记录中明确布局了所有的依赖关系。记录命令缓冲区时,可确定最有效的操作序列并恰当地提交工作任务——OpenGL中的工作任务必须动态完成。正如之前所述,绘制调用的命令缓冲区可以用于贴图任务和光栅化任务,并被硬件直接消耗。

提交这些命令缓冲区至队列时,功能的实现则非常简单,就如同将那些生成的任务放入应用程序提供了信号量的硬件中一样。硬件前端映射至API队列的比例并非1:1的,因为Vulkan图像队列由两个硬件前端(贴图和光栅化)表示。而这两个前端的差别则由API的其他部分表示,如渲染通道对象和详细的同步模型。

渲染通道

渲染通道将一组命令集分成片段。由于渲染通道在基于区块贴图的架构中是有效的工作单元,这些片段的命令缓冲区非常相似。渲染通道不允许出现可能会导致贴图中期帧冲刷的命令。其仅允许绘制调用和其他选择命令,因为贴图需要渲染通道提供的信息来有效运行。

渲染通道由多个子通道组成,每一个通道都能通过本地数据给定像素位置来与后续的子通道交流信息。每个子通道可以定义其附件的加载和存储操作、与其他子通道的执行依赖关系以及从先前或已保存的通道中读取的附件清单。

通过使用子通道信息传输,应用程序可以采用简单的后处理技术如色度分级或光晕。更有趣的是,如果应用程序使用延迟渲染技术,这可能 (强烈推荐!)是来表述G缓冲区的子通道依赖关系和输入附件。

在许多情况下,如果附件仅作为中间体使用(Vulkan中存在的比较延迟的分配内存类型)或用作瞬态附件使用,则不需要分配附件。渲染通道中任何需使用的附件(在渲染结束时没有从外源加载或存储)可以被标记为瞬态,这使得延迟的内存对象分配方案可以绑定至附件中。延迟分配的内存对象在初次创建时可能不会立刻有任何实际的物理内存支持——它们可能完全是空的,也可能是部分分配或完全分配的——这取决于架构。在大多数情况下,它们应该为对象生命周期保持初始状态,但如果出于某种原因需要分配更多内存,则可以进行后期绑定操作。需要预先了解内存对象的最大尺寸,并查询这些内存对象当前允诺的内存。

PowerVR GPU的渲染通道

正如上所述,渲染通道禁止任何会导致中期帧冲刷的平铺架构。于我们而言的确如此,且通过渲染通道显式地描述每个渲染和加载/存储操作的开端和结束,PowerVR GPU就能以最小的带宽进行操作——仅在渲染完成后才编写附件。

子通道依赖关系也绝对不允许存在架构在光栅化阶段发生冲刷的情况——这意味着可能要结合多个子通道和依赖关系至一个单一的渲染通道中。其结果便是不存在渲染的停止和启动,且没有显式存储的中间附件不需要写入内存——它们可以存入贴图内存中。输入附件至子通道中,当从先前的子通道中进行编写时,可准确地映射到硬件的片上贴图内存中。这一点你若很熟悉,可能我们在附件问题上使用了相同的EXT_shader_pixel_local_storage或EXT_shader_framebuffer_fetch。

架构中延迟分配的内存开始是未分配的,因为我们事先了解通常没有理由分配任何内存——其仅作为一个API构造,在渲染期间仅作为一个片上内存的寄存器格式化块进行映射。偶尔,在帧缓冲区创建时可能分配少量的内存,或在非常罕见的情况下,可能需要分配整个内存对象。

Conclusion 总结

相比OpenGL ES,Vulkan映射对于硬件而言更具优势。虽然很多人关注Vulkan改善CPU性能和效率,但很多GPU性能和效率也可以进行改善——只不过它们更精细。

谈论移动市场的游戏机品质图像有点陈词滥调,但它却无时无刻不出现在我们的生活中!随着移动GPU不断改善且更有效率,Vulkan也成为迈向游戏机品质图像之路的坚实一步。

这是本系列的最后一篇文章(有点晚但是完成了!),希望该系列文章可以发挥其价值。请关注我们Twitter (@ImaginationPR @PowerVRInsider)上来自PowerVR团队的最新新闻和公告。您还可以查阅Vulkan的相关博客和在线研讨会获取资讯

Vulkan现已发布,其详细信息也即将与大家见面。游戏开发者大会即将召开,敬请期待。尤其不要错过开发者的一天,届时我们将讨论Vulkan及其他PowerVR主题。我将描述如何在Vulkan中实现高效渲染的技术,且我将加入图形小组,讨论Vulkan对图形生态系统的影响。感谢您的阅读!

原文链接: http://blog.imgtec.com/powervr/tiling-positive-or-how-vulkan-maps-to-pow...

声明:
本文为原创文章,转载需注明作者、出处及原文链接,否则,本网站将保留追究其法律责任的权利

--电子创新网--
粤ICP备12070055号