1、GPU架构基础:现代GPU渲染管线概述、纹理单元(TMU)工作原理、缓存层级结构(L1/L2 Cache)
各位同学,咱们今天正式开课。我是你们这门课的主讲,一个在GPU图形学领域摸爬滚打了十几年的老工程师。说实话,每次讲架构基础,我都觉得特别重要。因为后面所有关于纹理采样、缓存命中的调优技巧,根儿都扎在这里。
你想想看,如果你连GPU怎么干活、纹理数据怎么流动、缓存长什么样都不清楚,那后面谈优化,不就是空中楼阁吗?所以,这一章咱们把地基打牢。
1.1 现代GPU渲染管线:一条流水线上的艺术
现代GPU的渲染管线,说白了就是一个超级高效的工厂流水线。它的任务就是把3D场景里的点、线、面,最终变成你屏幕上那个五彩斑斓的像素。
我个人习惯把这条管线分成几个关键阶段,咱们挑重点说:
- 顶点处理(Vertex Processing):处理每个三角形的顶点,做坐标变换、光照计算。嗯,这里我踩过坑,后面讲。
- 光栅化(Rasterization):把三角形变成一堆小碎片(Fragment)。说白了,就是决定哪些像素被这个三角形覆盖。
- 片段处理(Fragment Processing):这是最核心、最耗时的阶段。每个片段都要执行你的着色器代码,其中就包括纹理采样。
- 输出合并(Output Merging):把处理好的片段颜色,跟帧缓冲区里的颜色混合,最终写入显存。
核心观点: 整个管线里,片段处理阶段是纹理采样的主战场。你写的Shader里,每一次texture()调用,都会在这里触发一次TMU的请求。
我记得刚入行那会儿,总以为GPU是神,什么都能瞬间完成。后来做项目,发现帧率上不去,一分析,瓶颈全在纹理采样上。那时候才真正意识到,理解管线里数据怎么流动,比什么都重要。
1.2 纹理单元(TMU):GPU里的“快递小哥”
纹理单元,英文叫Texture Mapping Unit,简称TMU。你可以把它想象成GPU里专门负责“取快递”的部门。
它的工作流程是这样的:
- 接收订单:从Shader里收到一个纹理采样请求,包含纹理坐标(UV)、采样器状态(过滤模式、寻址模式等)。
- 计算地址:根据UV坐标和纹理尺寸,计算出要访问的纹素(Texel)在显存里的具体地址。这里涉及到Mipmap层级的选择,是个技术活。
- 发起请求:拿着这个地址,去缓存(Cache)或者显存里拿数据。
- 数据返回:拿到原始纹素数据后,TMU还要做过滤操作(比如双线性过滤、三线性过滤),最后把处理好的颜色值返回给Shader。
避坑指南: 我曾经遇到过一个问题,纹理采样特别慢,查了半天发现是Mipmap层级计算出了问题。Shader里用了textureLod强制指定了一个很大的LOD层级,导致TMU每次都去访问分辨率极低的Mipmap,缓存命中率暴跌。所以,不要滥用textureLod,让GPU自动计算通常更高效。
这里有个关键点:TMU的数量。一块GPU里有多少个TMU,直接决定了它每时钟周期能处理多少个纹理采样请求。比如,NVIDIA的RTX 4090有128个TMU,而一些入门级显卡可能只有8个。你想想看,如果你的Shader里纹理采样特别多,TMU数量少的显卡,自然就卡成PPT了。
1.3 缓存层级结构(L1/L2 Cache):数据的中转站
好了,TMU发出请求了,数据从哪来?直接从显存(VRAM)拿吗?
当然不是。显存虽然比系统内存快,但跟GPU核心的计算速度比起来,还是慢了几个数量级。所以,GPU内部设计了多级缓存,来缓解这个速度差距。
现代GPU的缓存层级,大致是这样的:
| 缓存层级 | 位置 | 大小(典型值) | 延迟 | 主要用途 |
|---|---|---|---|---|
| L1 Cache | 每个SM/CU内部 | 16KB - 128KB | 极低(~20个时钟周期) | 纹理数据、局部变量、共享内存 |
| L2 Cache | 所有SM/CU共享 | 512KB - 6MB | 较低(~100个时钟周期) | 纹理数据、全局内存访问的缓存 |
| VRAM | GPU核心外部 | 4GB - 24GB+ | 高(~400个时钟周期) | 所有纹理、顶点数据、帧缓冲区 |
你看这个表格,从L1到VRAM,延迟差了20倍。所以,优化的核心目标,就是让数据尽可能留在L1或L2里。
L1 Cache:第一道防线
L1 Cache是每个SM(Streaming Multiprocessor,NVIDIA的叫法)私有的。它离TMU最近,速度最快。但容量也最小。我记得有一次做移动端GPU优化,芯片的L1 Cache只有16KB。那时候为了把纹理数据塞进L1,我不得不把纹理压缩再压缩,甚至用了一些非常规的格式。
L1 Cache的命中率,直接决定了纹理采样的效率。如果L1 miss,请求就会发往L2,延迟一下子就上去了。
L2 Cache:最后的堡垒
L2 Cache是所有SM共享的。它的容量比L1大得多,但延迟也更高。L2 Cache的作用是作为L1和VRAM之间的缓冲。如果L2也miss了,那TMU就只能去访问慢如蜗牛的VRAM了。
警告: 千万不要以为L2 Cache很大就可以随便用。我曾经优化过一个粒子系统,每个粒子都要采样一张很大的噪声纹理。结果因为粒子在屏幕上随机分布,纹理访问完全没有空间局部性,L2 Cache的命中率惨不忍睹,大部分请求都直接穿透到了VRAM,帧率直接腰斩。后来我改用预计算+小纹理,才把性能救回来。
1.4 缓存命中率:性能的晴雨表
说了这么多,其实就一个核心指标:缓存命中率。它决定了你的纹理采样到底有多快。
影响命中率的因素,主要有两个:
- 空间局部性:相邻的像素,最好访问相邻的纹素。这样,当第一个像素触发TMU请求时,它会把周围一大块纹素都加载到缓存里。后续的像素就能直接从缓存里拿,不用再去显存了。
- 时间局部性:同一个纹素,最好在短时间内被多次访问。比如,在延迟渲染中,G-Buffer里的纹理会被多次采样,这就是利用了时间局部性。
我个人习惯用GPU厂商提供的Profiler工具(比如NVIDIA Nsight、AMD Radeon GPU Profiler)来查看缓存命中率。如果L1命中率低于80%,L2命中率低于90%,那基本可以断定,你的纹理访问模式有问题,需要优化了。
好了,这一章的内容就到这里。咱们把GPU渲染管线、TMU的工作流程、以及L1/L2缓存的结构都过了一遍。这些都是后面所有调优技巧的基础。下一章,我会带大家深入纹理格式和压缩算法,看看怎么从数据源头就开始优化。
课后思考: 你的项目中,纹理采样的L1/L2命中率大概是多少?如果低于我上面说的阈值,想想看,是空间局部性出了问题,还是时间局部性没利用好?