GPU Points
[转载请注明出处]
好吧这其实是读书笔记orz.....主要出自于RTR的2章和18章.
图形硬件发展到今天, 可谓是五花八门. 这里记录一些图形硬件中比较通用的概念.
某些条目后会补充一些我自己平时在unity中的实践经验.
Pipeline
渲染管道大致分为Application, Geometry, Rasterizer三个模块, 这三个模块一般被实现为三个独立的管道, 它们分别又包括许多子模块. 不同的GPU架构对子模块的实现非常不同.
Application Stage
应用程序阶段, 即开发人员自己写的,运行在cpu上的代码.
- 在这个阶段, 几何图元被送往Geometry stage. 在这个过程中可以对几何图元进行批量处理以减少cpu对图形接口的调用次数, 对cpu较弱的手机平台来说, 这个步骤至关重要.
- 碰撞检测, 动画, 某些优化算法都可以在这个阶段进行.
Geometry Stage
几何阶段负责处理图元和顶点, 一般包括以下子阶段:
- Transform :视图模型顶点变换,把顶点从模型空间变换到世界空间.
- Vertex Shader :可编程顶点着色器. 恩..最常用的东西.
- Projection :透视变换, 不可编程. 会把坐标系单位化.
- Clipping :裁剪. 把变换过的图元在屏幕外的部分裁切掉.
- Screen Mapping :把单位化的坐标系拉伸到屏幕像素尺寸.
Rasterizer Stage
像素化阶段通过几何阶段产生的顶点数据, 计算被图元覆盖的每个像素的颜色.
- Triangle Setup :固定管线.计算后续步骤需要的数据.
- Triangle Traversal :固定管线.遍历确定落在三角形中的片段, 并插值计算片段的属性.
- Pixel Shading :可编程像素着色器.
- Merging :合并color buffer与fragment color.
- 根据z-buffer来判断片段的可见性.
- 根据alpha进行颜色混合.
- 根据stencil buffer, accumulation buffer进行一些计算.
- 最终像素的颜色存放在color buffer中
Buffers and buffering
像素颜色的最终结果会被存放在color buffer中, 再通过Video Controller输出到显示设备上.
- 数字信号/模拟信号:
video controller 中通常包含一个 digital-to-analog conversion模块, 它会作用于每一个fram buffer的每一个像素上, 所以这个子系统需要高带宽. CRT屏幕需要把color buffer转换为模拟数据; 而一般家用电脑可以接收数字信号, 即常说的digital visual interface(DVI).
- 刷新:
CRT设备的刷新频率通常在60-120HZ, video controller以相同的频率逐行扫描color buffer, 扫描完毕后切换到下一帧. CRT的发光体需要持续通电来保持亮度, 所以需要较高的刷新频率. LCD夜晶屏的发光体可以保持亮度, 所以刷新率通常稳定在60HZ.
- 垂直同步:
强制应用程序的逻辑帧率与与显示设备的刷新率保持相同. 限帧对于移动设备至关重要, 一般限制为30帧或60帧, 否则会造成无谓的耗电/发热.
Color Buffer
常用颜色模型:
- 16位: r:5 g:5 b:5 或 r:5 g:6 b:5
- True color: 24 或 32 bits / pixel
在unity的纹理选项中可以看到这两种颜色模型. 在实践中,由于16位往往会导致明显的色差, 故32位较为常用.
Z-Buffering
通常24bit / pixel
- orthographic: 物体的z-value与世界坐标成正比.
- perspective: z-value是单位化的, 与世界坐标不成正比. z-value越大, 精度越低, 以至于远处的z-value比较接近物体可能会出现层级错乱问题.
Single,Double,Triple Buffering
buffer中的内容会被直接输出到显示设备, 如果只有一个buffer, 会出现边更新边输出的状况, 显示设备上画面可能会出现撕裂. 双缓冲和三缓冲等技术是为了避免这个问题.
- single buffer: 单缓冲
- double buffer: 双缓冲, 分为 back buffer 和 front buffer. 当front buffer被输出时, 下一帧的数据被放在back buffer中. front输出完毕后交换两个buffer.
- tripel buffer: 三缓冲, 分为 back, pending, front buffer. 三缓冲的优势是: front在输出时,系统还可以访问到下一个 buffer, 而不必等待buffer交换. 缺点是会增加latency, 比如在硬核游戏中, 玩家的即时操作只能影响到penddnig buffer, 还要等待2次交换才输出到屏幕. 所以有些硬核游戏厂商会把垂直同步关闭以保证实时性.
- 其它: 当双核GPU出现以后, 这个技术就变得更复杂了. 比如让两个gpu分别控制buffer的奇/偶行, 或者分别控制两个buffer.
Buffer Memory
举个栗子说明一下buffer需要多少memory. 比如我们有 一张1280*1024, true color 纹理.
- 自身占用1280 * 1024 * 4 = 5M 存储空间.
- 如果使用了双缓冲, 5 * 2 = 10M.
- 24bit z-buffer + 8 bit stencil buffer: 10 + 5 = 15M.
对这些有些了解, 在unity实践中, 可以帮助我们进行存储方面的评估和优化.
Interpolation
透视正确的插值:一言难尽...关于透视变换应配合ch4.6食用.. 总之 屏幕空间内对纹理进行线性插值得到的结果是错误的(u/w, v/w). 应除以1/w并对w也进行插值,(u/w[i], v/w[i])/(1/w[i]), 学名双曲线插值. 在GPU中, 透视插值可以用边缘检测函数来实现. ( 这也可以复用么--
Architecture
总体来说, 图形加速卡提供功能从渲染管线尾部向前发展. 最早的pc加速卡只提供纹理插值, 直到2000年才出现了顶点和像素着色器. 现代GPU全称图形处理单元,是图形加速卡的进化体. 驱动提供了运行在应用与图形硬件之间的接口. 比起CPU, GPU的架构更适合处理大规模并行运算. 除了提供图形计算, 现代GPU也可以用来进行更通用的运算.
pipeline and parallelization
用gpu进行图形运算比较快的原因有二:
- 管线和并行是相辅相成,可以同时进行大量图形运算. gpu可以同时运行上千个管线(cpu只有数十个). 对gpu来说, 时钟频率并不是瓶颈(gpu时钟频率比cpu低很多), 带宽才是.
- gpu的存储操作比较简单, 大部分是read, 这也有利于提高性能.
并行计算会带来另一个问题: 排序. 图元在model space 和screen space都需要排序
- sort first: 将屏幕分块, 分别送往单独的处理单元.
- sort middle: 在几何阶段之后再进行排序. 几何阶段以负载均衡的原则处理任意的图元, 而之后每个光栅化单元负责单独的屏幕区域, 经过几何阶段的图元被路由到相应的光栅化单元中.
- sort last fragment: 在光栅化之后, merge之前进行排序.
- sort last image: 在光栅化以及merge全部结束之后进行排序. 不同的物体被送往不同的处理单元.
Texture access
- 从内存中读取纹理会占用大量的带宽,所以许多gpu架构设计了不同的cache来优化纹理读取. cache通常是集成在芯片上的,容量可能只有几KB,缓存着最近一次读取的纹理. 即使经过cache,读取纹理往往也是较慢的操作, 需要耗费数个时钟周期. 如果串行处理片段, 则必须等待前一个片段读取操作完成后才能处理下一个片段 (这个等待时间叫latency).
一种常用来消除latency的手段是:并行计算多个片段. 首先运行每个片段的查询操作, 然后回过头来依次根据查询结果进行一些运算. 这也是GPU寄存器非常多的原因.
- mipmap也是一种节省带宽的有效手段. -- mipmap是少见的, 可以同时提升性能与表现的技术. 关于mipmap可参考第6章.
Memory architecture
这里举几个栗子🌰:
- XBox: unified memory architecture. GPU与CPU共用所有host memory.
- XBox360: GPU与CPU共享一条总线, GPU主要通过这里读取纹理. 而各类buffer则存放在GPU独享的, 速度较快的eDRAM中.
- PS3: GPU拥有完全独立的存储空间.
Ports and buses
port负责在两个设备之间传输数据, bus在多个设备之间传输数据. 带宽用于描述它们的数据传输能力.
i.e. dynamic vertex buffer存放在主存中, gpu可以通过bus来访问它.
Memory Bandwidth
首先分析渲染一个片段所需占用的bandwidth: B = B(z-buffer) + B(texture buffer) + B(color buffer): 约360 byte/pixcel 如果在分辨率1920*1200的屏幕上以72帧/秒速度渲染, 大约需要18G/s带宽.
为了尽量优化带宽占用, 往往需要一些非常精密的设计.
Latency
latency指请求发出到返回结果之间的时间.许多地方存在latency, 比如查询纹理, CPU向GPU请求数据,等等. 减少latency是一个永恒的目标.
Buffer Compression
为了减少带宽占用,往往需要将各种buffer压缩. 这里以某种架构下的z-buffer为例进行说明: 遮挡剔除, buffer压缩/解压, 清理buffer逻辑被放在同一个硬件模块中, 因为它们可以共用一些实现. 它们的核心是一块固定在芯片上的存储, 叫tile table. 它可以存放color, stencil, z, 等,现在只讨论下z. tile table由许多8x8的单元组成, 它们各储存8x8的z-value. 当系统下令清除缓存时, 这些8x8的单元会被清除.光栅化完成后, 数据被压缩后(占用更少的bit)存入tile table.
这个技术对于手机设备来说尤为重要.
Z-Culling and Early-Z
深度测试往往在管线的最后一步进行. 在像素着色器使用片段之前剔除tile table中一些不需要片段, 是一项重要的优化手段: z-culling.
如果像素着色器不会修改片段的z-value, 这个优化通常由gpu自动进行.
z-culling主要通过估算当前三角形的最小z(z-min)来实现, 如果z-min > z-max(通过z-buffer获得), 则说明这个图元一定被遮挡, 可以丢弃这个片段. 估算整个三角形的z-min是比较费力的, 具体实现参阅18.3.7.
early-z是一个与z-culling相似的技术.
PCU: Programmable Culling Unit
当片段着色器需要修改z时, 自动z-culling将会失效. PCU就应运而生了.
PCU的实现思路是:允许一部分片段着色器代码运行在一整个tile上. 例如, 如果程序猿可以判断出某块tile都被阴影覆盖, 就可以免除后续的复杂计算.
虽然对于不需要裁剪的片段来说, 运行pcu反而增加了负担, 但总体来说, 效率可以提高1.4-2.1倍.