Ylisar 发表于 2022-1-2 15:30

GPU图形微架构学习笔记(三)帧缓冲操作

其余文章参见系列目录

帧缓冲操作模块在NVIDIA的GPU文档中叫ROP(Render Output Unit),AMD的GPU中叫RB(Render Backend)。他分为两个子模块——色彩模块和深度模块——分别负责对显存中的色彩缓冲和深度缓冲进行读写,同时完成读写过程中的计算任务。他的用户包括着色器单元和光栅化单元。
带宽计算

开始介绍帧缓冲操作模块之前,我们先来粗略计算下图形任务中访问色彩/深度缓冲的带宽。如图一所示,渲染一个场景每个像素的绘制次数是不同的,图中颜色越亮表示绘制次数越多。原因就在于一个像素会被不同深度的多个三角形覆盖,每个三角形都会去绘制一次这个像素。这称为重复绘制(overdraw)。



图一:重复绘制示例

为了估算,我们引入一个深度复杂度(depth complexity)的概念。他是一个平均值,表示一个像素上平均有多少个三角形覆盖。所以大致有如下等式: https://www.zhihu.com/equation?tex=T%5Ctimes+A%3DS%5Ctimes+D 。其中D是深度复杂度,T是三角形个数,A是三角形的平均面积,S是屏幕面积。
另外,我们以O表示平均每个像素的重复绘制次数,由于深度测试的存在,很多三角形没通过测试不必写入,所以 https://www.zhihu.com/equation?tex=O%5Cleq+D 。如果我们假设三角形的深度均匀分布,那么:

https://www.zhihu.com/equation?tex=O%3D1%2B%5Cfrac%7B1%7D%7B2%7D%2B%5Cfrac%7B1%7D%7B3%7D%2B...%2B%5Cfrac%7B1%7D%7BD%7D
也就是第一个三角形肯定通过深度测试,第二个三角形有50%概率通过,第三个有33%概率通过,以此类推。这是一个调和级数,是发散的,但是增长很慢,接近对数函数。估算时我们取 https://www.zhihu.com/equation?tex=D%3D4 ,那么 https://www.zhihu.com/equation?tex=O%5Capprox+2 。
现在开始估算,首先是色彩缓冲,设每个像素32bit,屏幕大小1920x1080,刷新率60Hz,那么:

https://www.zhihu.com/equation?tex=%5Cbegin%7Baligned%7D+C_p%26%3DO%5Ctimes+32+%3D+2+%5Ctimes+32%3D64%5C%5C+C_s%26%3DC_p%5Ctimes+1920%5Ctimes1080%5Ctimes60%5Capprox+8Gb%2Fs+%5Cend%7Baligned%7D+
注意如果不开Blend,那么色彩缓冲不需要读取,只需要写入8Gb/s。而开启Blend需要同时读取和写入,带宽各是8Gb/s。
深度缓冲时,无论深度测试结果如何都需要读取,而写入只需要通过测试才需要,所以读写带宽不对称。设深度缓冲每个像素是32bit,那么写入带宽和色彩缓冲一样是8Gb/s,而读取带宽是:

https://www.zhihu.com/equation?tex=%5Cbegin%7Balign%7D+D_p%26%3DD%5Ctimes+32%3D4%5Ctimes+32%3D128%5C%5C+D_s%26%3DD_p%5Ctimes1920%5Ctimes1080%5Ctimes60%5Capprox16Gb%2Fs+%5Cend%7Balign%7D
除了以上基础带宽,我们再看看以下几个倍数:

[*]144Hz高刷加4K分辨率需要乘以10
[*]开启4xMSAA需要乘以4
[*]每一帧n个render pass就要乘以n
如果以上条件都满足倍数累乘后是40n,8Gb/s就变成320nGb/s。所以帧缓冲操作模块是内存带宽消耗的“大户”,它设计上最大的优化任务就是减小对色彩/深度缓冲的访问带宽。
深度模块

深度模块的操作对象是深度缓冲。现代深度模块的架构如图二所示。为了减小内存带宽,我们需要设计一套Cache系统。之前在光栅化一文中提过,三角形遍历模块会把屏幕分成8x8或者4x4的像素块分别操作。深度模块也是同样,会将这些深度缓冲的块缓存在Tile Cache中。
而且深度缓冲模块一般会以块为单位对数据进行压缩,所以Tile Cache读写内存时需要经过压缩/解压模块。Tile Table Cache则缓存了每一个块的“信息头”——比如这个块采用了什么压缩算法等。由于块的“信息头”相对于块本身的数据量来说很小,Tile Table Cache缓存的块数量远超Tile Cache。而Z-min/Z-max模块是为了实现Hierachical-Z算法,会在下面介绍。



图二:深度操作模块架构【2】

Hierarchical-Z

Hierarchical-Z算法(简称HiZ)有三部分内容,深度模块采用的是其中的image-space coherence部分。在不做任何优化时,深度测试需要对每个三角形的每个采样点读取深度缓冲进行对比。而HiZ的优化思路是变单个采样点的深度比较为一整块区域深度范围的比较,如果范围不重合,则肯定全部可见或者不可见,就没必要每个采样点逐一比较了。这样不但减少了比较次数,而且也节省了内存访问带宽。具体做法是:

[*]以块为单位(和三角形遍历模块一致),保存这个块的最大和最小深度值。
[*]三角形遍历模块会将这个像素块的所有三角形内的采样点深度值传给深度测试模块,其中就有最大和最小值。
[*]进行深度测试:
[*]如果 https://www.zhihu.com/equation?tex=t_%7Bmin%7D%3Ed_%7Bmax%7D ,则说明这部分三角形完全不可见,直接丢弃。
[*]如果 https://www.zhihu.com/equation?tex=t_%7Bmax%7D+%3C+d_%7Bmin%7D 则说明这部分三角形完全可见,不用读取任何深度值直接写入,同时替换为。
[*]其他情况就读取深度缓冲做传统深度测试(逐采样点比较),然后更新。

所以图二中的Z-min/Z-max模块就是为了计算Tile Cache一个块中的最大和最小值到Tile Table Cache的“信息头”中。由于Tile Table Cache中的块数量多,所以在3.1的时候,我们读取完 https://www.zhihu.com/equation?tex=d_%7Bmax%7D 后就会丢弃这个三角形,不需要对Tile Cache进行任何操作。
快速Z清除

快速Z清除可以认为是一种简单的深度缓冲压缩技术,针对的是深度缓冲被清除后的特殊状态。具体做法是在块的“信息头”中加入一位,表示这一块深度缓冲全部都是被清除的数值,没有被写入任何数值。这样我们就不需要任何的块数据,也就是将一块深度缓冲压缩为一位。这个方法简单高效,不但做深度清除很方便(置上一位就行),而且操作被清除的块,完全不需要读取块数据,减少了内存访问。所以快速Z清除是一个很流行的优化。
深度缓冲压缩

深度缓冲压缩需要使用无损压缩的算法,不然经过反复读取写入的压缩解压过程会造成很大的失真。目前有很多算法,GPU一般会实现一种或几种的压缩算法,对Tile Cache中一块数据同时运行后采用成功而且压缩率最高的那个,如果没有成功的就不压缩数据。这些都会记录在Tile Table Cache的“信息头”中。
我们先来介绍一种相对简单的锚定编码,选择一个锚点Z以原始精度24bit保存,然后计算XY两个方向Z的偏导数 https://www.zhihu.com/equation?tex=%28%5CDelta+x%2C+%5CDelta+y%29 以15bit保存,这样就形成了一个平面,每个采样点上的深度数值就可以用公式:

https://www.zhihu.com/equation?tex=d+%3D+z+%2B+%5CDelta+x+%28x-x_z%29%2B%5CDelta+y+%28y-y_z%29
去预测。每个点和预测的偏差我们再给5bit去修正,这样对4x4的一个深度缓冲块来说,总共需要:

https://www.zhihu.com/equation?tex=24%2B2%5Ctimes15%2B13%5Ctimes5%3D119
注意13来自16-3,因为有三个点不需要5bit的修正,包括锚点和计算出的点。



图三:锚定编码【2】

所以锚定编码能固定地将24x16=384bit的4x4深度缓冲块压缩为119bit。当然如果这4x4的数据中有一个需要超过5bit去修正或者超过15bit,那这个压缩算法就会失败,需要用其他压缩算法或者不压缩。
最后我们再介绍一个非固定压缩算法——平面编码。上面的锚点编码我们知道用就能表示一个平面上的一片深度数值。而图形深度缓冲都是由这样一个个三角形的深度插值而来,用多个这样的三角形平面来表示一块深度缓冲,只要这个块里面的平面足够少,就能起到压缩效果,而且平面越少压缩比越高,所以是变长压缩。



图四:平面编码【2】

当然三角形面积有限,除了表示的无限面积平面,我们还需要为每个像素建立一个记录表示他属于哪个平面。所以具体的压缩数据如下(以4x4大小的块为例):

[*]2bit/tile:记录总共几个平面
[*]2bit/pixel:记录每个采样点属于哪个平面
[*]3x24bit/plane:
所以在一个平面的情况下总共需要:

https://www.zhihu.com/equation?tex=2%2B2%5Ctimes+16%2B3%5Ctimes24%3D106
在最多的四个平面的情况下总共需要:

https://www.zhihu.com/equation?tex=2%2B2%5Ctimes16%2B4%5Ctimes3%5Ctimes24%3D322
平面编码对的精度有要求,需要达到光栅化中的深度插值精度,才能在解压后获得光栅化相同的深度数值,也就是无损压缩的要求。
色彩模块

色彩模块的操作对象是色彩缓冲。它的架构类似于深度模块,也是将缓冲分块处理,包括Tile Table Cache和Tile Cache,分别缓冲“信息头”和块数据。但是深度模块中的深度测试部分在色彩模块中换成了blend操作,而且色彩模块没有Z-min/Z-max部分。最后就是读写色彩缓冲前也需要压缩解压模块来减小内存访问带宽。
压缩算法需求

色彩缓冲的压缩算法和深度缓冲区别比较大,这是由数据特性决定的。深度缓冲里的数据是由固定的线性插值算法算出来的,所以相邻的数据很可能是一个线性关系,锚定编码和平面编码都是利用了这一点。而色彩缓冲的数据是独立的像素着色器计算出来的,虽然很可能相邻的数据相似,但不一定是线性的。所以色彩缓冲的数据更类似于图片。
但是图片往往用于查看,是压缩一次,解压无数次。所以图片压缩算法可以压缩复杂一些,只要解压简单快速就行。而色彩缓冲是读写都很频繁,所以需要的是压缩解压都简单快速的压缩算法。另外就是有些图片压缩算法块与块之间可能有依赖关系,比如用一个块去预测相邻的块,但是色彩模块需要满足用户独立读写每个块的需求,所以块与块不能有依赖,压缩算法只能局限于单个块内。
和深度压缩算法一样,色彩缓冲压缩算法也需要是无损的。硬件会实现多种压缩算法或者一种压缩算法多套参数的实例,最后选取成功算法中压缩比例最大者,如果没有成功的算法就不压缩。而且也可以实现与深度压缩相似的快速清除算法。这些信息都会保存在相应块的“信息头”中。
块同色编码

探讨完色彩缓冲的数据特性和算法需求,我们首先介绍一种简单的压缩算法——块同色编码。检查4x4或者8x8的“大块”中的每个2x2或者4x2的“小块”,如果小块内都是相同的颜色,那我们就可以每个小块用一个数值来记录颜色,所以压缩比在小块是2x2时是4:1,在4x2时就是8:1。这种算法在大片相同颜色的块里表现最好,而且也非常适合开启多重采样的色彩缓冲。
开启多重采样的时候,GPU输出的色彩缓冲大小会乘以多重采样的倍数,比如在2x2多重采样时,色彩缓冲长宽都会是原来的两倍,每个像素点记录一个采样点的颜色,但是GPU不会在每个采样点上都执行着色程序,而是在2x2个采样点的中心执行一次着色程序,再将结果写入2x2个采样点中被三角形覆盖的那几个采样点。所以很多完全在同一三角形内部的2x2小块的颜色是一样的,只有那些处于三角形边界的小块2x2个采样点的颜色才会不同。



图五:多重采样的色彩缓冲存储

色差编码

接下来介绍一种和锚定编码有些类似的压缩算法——色差编码。按一定“顺序”排列块中的像素,用全精度保存第一个像素,然后用低精度保存后一个像素和前一个像素的差值。所以这个算法成功的条件就是在这个“顺序”排列下的像素,前后的差值在一定范围内。因此这个“顺序”就是算法的参数,硬件会对不同的“顺序”实现对应的实例,选取通过的那个“顺序”保存在“信息头”中。注意具体的“顺序”体现在硬件实例中,“信息头”只需要像记录使用哪种算法一样就行了。



图六:NVIDIA ROP色彩压缩算法【6】

调色盘编码

最后介绍的是调色盘编码。以4x4的块为例,假设块中只有少于4种颜色,遍历块中的像素,碰到新的颜色就加这种颜色进“调色板”——一个颜色表格,以这个颜色在表格中的索引表示像素的颜色,这样调色板中的颜色是全精度,而索引只需要2bit,如果一个像素是RGBA 32bit,那最多总共需要:

https://www.zhihu.com/equation?tex=16%5Ctimes2%2B4%5Ctimes32%3D160
如果在“信息头”中加入2bit记录“调色板”中有几个颜色,那最少需要:

https://www.zhihu.com/equation?tex=16%5Ctimes2%2B1%5Ctimes32%3D64
调色板编码的成功条件就是块中的颜色少于“调色板”容量的上限,这个容量也可以是一个参数让硬件实现对应的实例来牺牲压缩比增加成功率。
总结

本文介绍了深度缓冲模块和色彩缓冲模块的负载和架构。由于具体的深度测试和Blend计算都是很明确的算法,所以重点放在了优化内存带宽的Cache和压缩算法上。而且压缩算法选取的是一些简单和易于讲述的公开例子,具体各家GPU使用的算法一般不会公开。
参考


[*]^调和级数https://zh.wikipedia.org/wiki/%E8%B0%83%E5%92%8C%E7%BA%A7%E6%95%B0
[*]^abcEfficient Depth Buffer Compressionhttps://diglib.eg.org/xmlui/bitstream/handle/10.2312/EGGH.EGGH06.103-110/103-110.pdf
[*]^Hierarchical Z-Buffer Visibility (Hi-Z)https://blog.csdn.net/wolf96/article/details/100851090
[*]^Hierarchical Z-Buffer Visibilityhttps://www.cs.princeton.edu/courses/archive/spr01/cs598b/papers/greene93.pdf
[*]^abNVIDIA Tegra X1 Whitepaper-Improved Memory Compression https://international.download.nvidia.cn/pdf/tegra/Tegra-X1-whitepaper-v1.0.pdf
[*]^The NVIDIA GeForce GTX 1080 & GTX 1070 Founders Editions Review: Kicking Off the FinFET Generationhttps://www.anandtech.com/show/10325/the-nvidia-geforce-gtx-1080-and-1070-founders-edition-review/8
[*]^Indexed colorhttps://en.wikipedia.org/wiki/Indexed_color
页: [1]
查看完整版本: GPU图形微架构学习笔记(三)帧缓冲操作