当前位置:   article > 正文

GPU画像素的顺序是什么_d3d11 tile based

d3d11 tile based
作者:叛逆者
链接:https://zhuanlan.zhihu.com/p/22232448
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

熟悉实时图形的人都知道,GPU里面有很大一块是rasterizer,用来把VS输出的顶点数据光栅化成像素数据,交给PS。然而,这部分一直是一个黑盒。GPU是以什么样的顺序进行光栅化?传统光栅化和tile-based光栅化有什么区别?这些问题原先只能进行概念性的定性分析。

到了D3D11 feature level 11.0的时代,硬件支持对一个buffer的内容进行原子操作。也就是说,可以在PS里通过调用InterlockedAdd等方法,来让像素之间串行化执行。这样以来,等于给我们开启了探究光栅化顺序之门。不需要保密资料、不需要硬件知识,写个简单的shader就可以完全从结果上看出光栅化的奥秘。

下面的程序实现到了KlayGE里,一个称为RasterizationOrder的教程。由于还没完成OpenGL和OpenGLES插件里的rw buffer支持,D3D12的插件还有bug,目前只能用D3D11来运行。平台是Windows desktop和UWP。

Shader代码

要完成这件事情的代码非常简单。完全只是利用了Shader Model 5.0里面提供的功能。

  1. RWByteAddressBuffer ras_order_buff;
  2. ...
  3. uint GetRasterizationOrder()
  4. {
  5. uint old_value;
  6. ras_order_buff.InterlockedAdd(0, 1, old_value);
  7. return old_value;
  8. }

ras_order_buff是一个大小只有4字节的buffer,初始值为0。InterlockedAdd保证了原子+1,原先buffer里的值就通过old_value返回出来了。在PS里调用这个函数,就能得到自己是第几个到达此处的pixel。

有了这个顺序值之后,就可以把它编码成颜色输出。

  1. float4 RasterizationOrderPS() : SV_Target
  2. {
  3. uint order = GetRasterizationOrder();
  4. return float4((uint3(order >> uint3(0, 8, 16)) & 0xFF) / 255.0f, 1);
  5. }

如果你觉得那样出来的颜色看不明白,还可以把它编码成color map输出。这里用的是Matlab里称为Parula的color map。从蓝到黄表示0到1。

Shader代码如下。

  1. float4 RasterizationOrderColorMapPS() : SV_Target
  2. {
  3. uint MAX_VALUE = 256 * 1024;
  4. uint order = GetRasterizationOrder();
  5. return color_map.SampleLevel(linear_sampler, float2((order & (MAX_VALUE - 1)) / float(MAX_VALUE), 0.5f), 0);
  6. }

有了这些PS,我们只要画一个超过屏幕大小的三角形,就能从颜色上探究GPU光栅化的工作方式了。

严格来说,我们看到的并不是光栅化的顺序,而是光栅化后到达PS的顺序。所以这也和调度有关。所以我们这里主要看的是光栅化的pattern,而不是严格的次序关系。

传统光栅化

首先登场的是AMD FirePro V3900专业卡 (不要以为我光黑AMD,我也用)。它用的是Cayman核心,和桌面级Radeon HD 69xx相同。把顺序编码成颜色后,看起来是这样的。

确实不容易分辨,我们来看看color map的结果。

这下好多了。从颜色可以看出,FirePro V3900的顺序是从右到左这样倒着来的。以32个pixel的高度为单位,连续光栅化。这是个典型的传统光栅化的工作方式。另一个有意思的地方是,rasterizer仍然把整个三角形一次光栅化了,没有把它进行拆分。

之后我们就只贴color map的结果。

下一个测试的是NVIDIA Quadro 600,Fermi核心,和桌面级GeForce GTX 4xx相同。它的顺序是从左到右,以16个pixel的高度为单位,总的来说是连续光栅化,但有些跳变,表明PS的调度更随机。

再来一个Intel HD Graphics 520的。仍然和前面很相似,以16个pixel的高度为单位从左到右光栅化。

Tile-based光栅化

看了两个传统光栅化的GPU,再来看看tile-based的。这里选的是Lumia 950的Qualcomm Adreno 430。可以从结果上很明显地看出来,tile的大小是8x8,tile之内是zigzag的顺序,tile之间并非像传统光栅化那样连续前进。

再来几个有意思的结果

不光硬件,我们还可以看看同一个程序在软件光栅化上运行会有什么样的结果。

最传统的D3D11 REF上,光栅化是一个像素一个像素,从左到右前进的。中规中矩的软件光栅化实现方法。同时也可以看出,一个大三角形被拆成了两个,分别光栅化。对于GPU来说,拆primitive会涉及到顶点数变化,比较费劲,不如就整个光栅化了,屏幕之外的像素裁掉。反正瓶颈在IO而不是光栅化本身。而软件光栅化正好相反。顶点变化无所谓,光栅化本身的开销大。所以就用了严格的clip,并只光栅化屏幕内的像素。

WARP也一样,拆成两个三角形。然而WARP融合了一定的tile-based做法,是以2x2的小tile为单位,向前推进。所以看起来比较碎。

最神奇的,是NVIDIA Geforce GTX 960上的结果。之前有传言说Maxwell和Pascal的GPU偷偷地用了tile-based。但我们在这里看到的是这样的顺序。

这看起来像是以256像素位宽度,纵向切分成块,再以4x8为tile大小,光栅化块内的区域。所以这并不是tile-based,而是在传统和tile-based之间有个折衷。很可能是为了结合传统的高效和tile-based的节能优势。

知道顺序有什么用

通过这些分析,我们可以大致的知道每种硬件的光栅化单位。在写PS的时候,如果分支是这样的单位大小的整倍数,那么分支就是无开销的。不要再认为GPU上的if是把两个分支都执行一遍,取结果了。只有在一定条件下才是那样。


为什么要研究这个

因为我能。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/789528
推荐阅读
相关标签
  

闽ICP备14008679号