三角面片的前世今生--NVIDIA逻辑管线

https://developer.nvidia.com/content/life-triangle-nvidias-logical-pipeline

 

三角面片的前世今生--NVIDIA逻辑管线

 

By Christoph Kubisch, posted Mar 16 2015 at 12:52PM

Tags: 

GameWorks

GameWorks Expert Developer

DX12

DX11

 

从具有开创性的费米体系结构发布至今快5年,也是时候更新其下的图形体系结构了。费米是Nvidia的首个实现GPU完全可扩展的图形引擎,其核心架构可以在Kepler和Maxwell中找到。下面篇幅,尤其是“压缩管道知识”图片,应该作为基于各种公共材料的初级读物,如白皮书或有关GPU架构的GTC教程。本文重点介绍图形视角下GPU是如何工作的,尽管一些原则(如如何执行着色程序代码)对于计算来说是相同的。

管线架构图

 

GPU是“炒鸡”并行任务分配器

为什么(显卡)这么复杂?在显卡中,我们必须处理产生大量可变工作负载的数据放大。每个drawCall会生成不同数量的三角形。裁剪后的顶点数量与三角形最初的也不同。在反面和深度剔除之后,并不是所有的三角形都需要绘制在屏幕的像素点上。三角形的屏幕尺寸将会意味着它需要数百万像素抑或根本不需要。

因此,现代GPU让它们的原语(三角形、线、点)遵循逻辑管线,而不是物理管线。在G80的统一体系结构(如DX9硬件、PS3、Xbox360)之前的旧时代,渲染流程在芯片上以不同的阶段进行表示,任务会一个接一个地进行。基本上来说G80根据负载的不同,对顶点和片段着色器计算重复使用了一些单元,但是它仍然有一个用于原语/光栅化等的串行过程。而费米的管线变得完全平行,这意味着芯片实现了一个通过重用芯片上的多个引擎的逻辑管道(三角形通过的步骤)。

 

假设我们有两个三角形A和B,它们的各部分工作可能在不同的逻辑管线步骤中进行。A已经变形了,需要进行光栅化处理。它的一些像素可能已经在运行像素着色程序指令,而另一些像素则被深度缓冲区(z-cull)拒绝,又有一些像素可能已经被写入帧缓冲区,而还有些像素可能正在等待。除此之外,我们还可以得到三角形B的顶点,因此,当每个三角形都要经历逻辑步骤时,很多三角形都可在其生命周期的不同步骤中被主动处理。作业(在屏幕上获取drawcall的三角形)被分成许多较小的任务,甚至可以并行运行的子任务。每个任务都安排给可用的资源,而这些资源并不限于处理特定类型的任务(如像素着色和顶点着色都可处理)。

 

就像一道洪流,不断向外扩散。平行且独立的管线流,每个都在自己的时间轴上,有些可能有比其他更多的分支。如果我们将基于三角形对GPU的单位进行颜色编码,或者它当前正在处理的DrawCall,那么它将是多色跑马灯:)

 

GPU架构

因为Nvidia费米架构显卡有一个类似的原理架构。有一个Giga线程引擎可以管理正在进行的所有工作。GPU被划分为多个GPC(图形处理集群Graphics Processing Cluster),每个GPC都有多个SM(流式多处理器Streaming Multiprocessor)和一个光栅引擎。这个过程中有很多交互,最显著的是一个Crossbar,它允许跨GPC或其他功能单元,如ROP(渲染输出单元render output unit)子系统进行工作迁移。

 

程序员看重的着色程序执行的工作是在SM上完成的。它包含许多为线程执行数学运算的内核。例如,一个线程可以是顶点着色或像素着色调用。这些核心和其他单元由Warp调度程序驱动,该调度程序管理一组32个线程作为Warp,并将要执行的指令移交给调度单元。代码逻辑由调度程序处理,而不是在核心本身中处理,它只是从调度程序中看到类似于“寄存器4235和寄存器4234的值累加,并存储在4230”的内容。与一个内核相当智能的CPU相比,GPU内核本身相当机械。GPU将智能性放在更高的层次上,以执行整个团队的工作(如果愿意,可以是多个团队)。

 

在GPU上有多少单元实际上(每个GPC有多少个SMS,多少个GPC…)取决于芯片配置本身。如您所见,上面的GM204有4个GPC,每个4个SMS,但Tegra X1有1个GPC和2个SMS,这两个都是Maxwell设计的。SM设计本身(核心数量、指令单元、调度程序等)也随着时间的推移而发生了变化,从一代到另一代(见第一张图片),并帮助芯片变得如此高效,可以从高端桌面扩展到笔记本到移动。

 

逻辑管线

 

为了简单起见,以下内容省略了几个细节。我们假设drawCall引用了一些indexbuffer和vertexbuffer,这些indexbuffer和vertexbuffer已经填充了数据,并且位于GPU的DRAM中,仅使用顶点着色器和像素着色器(GL:fragmentshader)。

  1. 该程序在图形API(DX或GL)中进行了一次绘图调用。这将在某个点到达驱动程序,该点进行一点验证以检查事情是否“合法”,并将命令插入pushbuffer中的GPU可读编码中。在CPU方面可能会出现很多瓶颈,这就是程序员需要很好地使用API和利用当今GPU能力的技术很重要的原因。
  2. 在经过一段时间或显式的“flush”调用之后,驱动程序已经在pushbuffer中缓冲了足够的工作,并将其发送给GPU进行处理(需要操作系统的一些参与)。GPU的主机接口接收通过前端处理的命令。
  3. 我们通过在indexbuffer中处理索引并生成发送给多个GPC的三角形工作批,在原始分派器中开始工作分配。

  1. 在GPC中,一个SM的多变形引擎负责从三角形索引(顶点提取)中提取顶点数据。
  2. 在获取数据后,32个线程的Warp被安排在SM中,并将处理顶点。
  3. SM的Warp调度程序按顺序分派整个Warp的指令。线程在锁步骤中运行每个指令,如果某指令不应该主动执行,则可以单独屏蔽。有多种原因需要这样的屏蔽。例如,当前指令是“if(true)”分支的一部分时,线程特定的数据计算为“false”,或者当在一个线程达到循环的终止条件而另一个线程没有时。因此,在着色器中具有大量分支发散可以显著增加warp中所有线程的时间。线程不能单独前进,只能作为经线前进!然而,Warp是相互独立的。
  4. Warp的指令可以一次完成,也可以进行多次调度。例如,与进行基本的数学运算相比,SM的加载/存储单元通常更少。
  5. 由于某些指令的完成时间比其他指令要长,特别是内存加载,所以Warp调度程序可能只是切换到另一个不等待内存的Warp。这是GPU如何克服内存读取延迟的关键概念,它们只是切换出活动线程组。为了使这种切换非常快,调度程序管理的所有线程在寄存器文件中都有自己的寄存器。着色程序需要的寄存器越多,线程/Warp的空间就越小。我们可以在两者之间切换的Warp越少,在等待指令完成时我们可以做的有用的工作就越少(最重要的是内存获取)。

  1. 一旦Warp完成顶点着色器的所有指令,它的结果将进行视图变换处理。三角形被裁剪空间体积裁剪,接着准备光栅化。我们对所有这些跨任务通信数据使用L1和L2缓存。

  1. 现在,它变得令人兴奋,我们的三角即将被切碎,并有可能离开它目前生活的GPC。三角形的边界框用于决定哪些光栅引擎需要处理它,因为每个引擎覆盖屏幕的多个瓷砖。它通过工作分配横杆将三角形发送给一个或多个GPC。我们现在有效地把我们的三角区分成许多小的工作。

  1. 目标sm的属性设置将确保插值(例如,我们在顶点着色中生成的输出)是像素着色器友好格式。
  2. GPC的光栅引擎处理接收到的三角形,并为其负责的部分生成像素信息(也处理反面剔除和Z剔除)。
  3. 我们再次批量处理32个像素的线程,或者更确切地说是8倍的2x2像素四元组,这是我们在像素着色器中一直使用的最小单元。这个2x2四元组允许我们计算纹理mip映射过滤(四元组中纹理坐标的大变化会导致更高的mip)之类的导数。2x2四元组中的那些线程,若其示例位置实际上没有覆盖三角形,则被屏蔽(gl_helperinvocation)。本地SM的Warp调度程序之一将管理像素着色任务。
  4. 我们在顶点着色程序逻辑阶段所做的同样的Warp调度程序指令游戏现在在像素着色程序线程上执行。锁步处理特别方便,因为我们几乎可以免费访问像素四元中的值,因为所有线程的数据都保证计算到相同的指令点(nv_shader_thread_group)。

  1. 我们达到目的了吗?几乎达到了,我们的像素着色器已经完成了对要写入renderTargets的颜色的计算,并且我们还有一个深度值。在这一点上,我们必须考虑三角形的原始API调用顺序,然后才能将数据移交给一个ROP(渲染输出单元)子系统,该子系统本身具有多个ROP单元。这里进行深度测试、与帧缓冲区混合等。这些操作需要自动进行(一次设置一种颜色/深度),以确保当两个三角形都覆盖相同的像素时,没有一个三角形的颜色对应另一个三角形的深度值的错位问题。Nvidia通常应用内存压缩,以减少内存带宽需求,从而增加“有效”带宽(请参阅GTX 980 PDF)。

 

噗的一下!就完成了,我们将一些像素写入渲染目标。我希望这些信息有助于理解GPU中的一些工作/数据流。这也有助于理解为什么与CPU同步真的很有害的另一个副作用。一个必须等到所有的工作都完成并且没有提交新的工作(所有的单元都变为空闲),这意味着在发送新的工作时,需要一段时间,直到所有的工作都再次完全加载,特别是在大的GPU上。

 

在下面的图像中,您可以看到我们如何渲染一个CAD模型,并用促成该图像的不同SM或扭曲ID(nv_shader_thread_组)对其着色。结果不会是帧一致的,因为工作分布会随帧变化。场景是使用许多DrawCalls呈现的,其中一些也可以并行处理(使用nsight,您也可以看到一些DrawCall并行性)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值