5

更新一些GPU相关知识

 2 years ago
source link: https://blog.gotocoding.com/archives/1453
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

更新一些GPU相关知识

学完并实现路径追踪之后,即使增加了多线程渲染,在SPP=1024的情况下,依然需要30+分钟才能渲染一帧。

为了更快的渲染速度 ,我试图通过使用GPU的CUDA SDK来加速渲染。然而测下来竟然还没我的CPU跑的快,一方面我没有更好的显卡,另一方面我也不太确定是不是我CUDA使用错误所致。再加上就算使用GPU也不可能达到每帧秒级渲染。于是GPU的学习就搁置了。

然而在最近研究Splat地形渲染方案时, 我无意间发现了一个现象。测试地形为4层混合,在所有Texture都不开Mipmapping的情况下, FPS只有30左右,而开了Mipmapping之后,FPS可以稳定在60. 这激起了我强烈的好奇心,终于将了解GPU的运行架构提上日程(又产生了一次PageFault, 本来我在学习《mysql是怎么运行的》这本书,都已经快把B+ Tree看完了) 。

对照《GPU 精粹1》中的【28.2节 定位瓶颈】得出一个结论,如果Texture Filtering会影响FPS, 那么就说明瓶颈在Texture Bandwidth。

这引出我的第一个问题,Texture Bandwidth到底是什么,为什么Mipmapping会影响Texture Bandwidth?

我最开始以为是从CPU到GPU之间传输图片的带宽,越查资料越确定不是这样。

在找到影响FPS的因素之前,其实我大约花了一天试验了各种设置(这就是基本功不够扎实的现象,没有头绪各种试),甚至在FPS达到60时,我都搞不清到底是改了哪个设置变好的。

当然在这期间我也查了很多资料,其中最重要的两个点是说,对于Splat地形方案,他们都会提到减少Sampler的个数,并且提到使用 TextureArray可以改善性能。使用TextureArray可以改善性能的原因是因为它减少了bindtexture的次数,而为什么要减少SamplerState我当时并没有找到依据。

这就引出了第二和第三个问题,为什么要减少SamplerState的数量,是不是性能问题?bindtexture为什么会很“贵”?

直到昨天我发现了一篇NVIDIA讲解GPU架构的文章, 这篇文章虽然不长,但是指出了各种我们在写shader时需要知道的要点。


我先简要概述这篇文章,然后试图来解释这三个问题。

在GPU中有Warp Scheduler, thread, register file, TMU, TextureCache等概念。

Warp Scheduler是最基本的调度单元,也就是说整个Warp Scheduler中的thread一直在执行“齐步走”逻辑. 如果有一个Thread需要换出(switch out)比如等待内存加载), 整个Warp Scheduler的所有Thread都会换出(switch out)。

只要有一个Thread 的if () 判断为真,那所有的Thread都需要执行if为真的逻辑,即使有的Thread的if判断为假,也需要等待if为真的Thread都执行完才执行else, 而之前那部分if为真的Thread同样需要等待else的语句执行完再继续“齐步走”。

每个Warp Scheduler会有32个Thread。这么理解下来其实每个Warp Scheduler就相当于一个具有32通道的SIMD指令(英伟达把他叫做SIMT)。

每个Thread都有自己的寄存器, 这些寄存器都从register file进行分配,如果shader使用过多的寄存器,就会导致更少的Warp Scheduler和更少的Threads, 而更少的Warp Scheduler则意味着GPU的Core可能跑不满(类比操作系统,如果所有Thread都Sleep, 那CPU就在空转是一样的), GPU的性能就得不到发挥。

根据Wiki的解释,纹理采样主要是通过TMU模块进行执行的,TMU模块是一种有限的硬件资源, 因此你采样更多(不一样的)纹理,就需要消耗更多的周期。

纹理采样除了计算之外,还需要加载纹理数据,TMU会首先向Texture Cache中去加载,如果Cache Miss就会从L2加载到Textuer Cache, 如果L2也Cache Miss,就会从DRAM(显存)中加载纹理,然后依次填充L2和Texture Cache.

根据英伟达说明的GF100内存架构从Thread读到Texture Cache只需要几十个周期,而从L2向DRAM加载则需要几百个周期。在这些周期内,需要采样纹理的Warp Scheduler都需要被换出(swap out)。

至目前为止,其实已经能解释前两个问题了:

  1. Texture Bandwidth到底是什么,为什么Mipmapping会影响Texture Bandwidth?

    Texture Bandwidth其实就是指Texture 从DRAM到L2和L2到Texture Cache的加载带宽

    没有使用Mipmapping之前,我们地形的每一层图片尺寸都是10241024的图片,并且被渲染出的像素尺寸只有256256大小, 这样在渲染相邻的pixel时被采样的texel在内存中是不连续的, 因此在纹理采样过程中会频发触发Texture Cache Miss, 每次Cache Miss都需要额外的周期从L2或DRAM中重新加载。

    使用了Mipmapping之后,GPU可以根据当前的渲染情况来判断采用哪一个Mip Level。当选择合适的Mip Level之后, 相邻的pixel对应的texel也会尽可能的相邻,可以极大的缓解Texture Cache Miss的状况。

  2. 为什么要减少SamplerState的数量,是不是性能问题?

    根据微软文档显示,Direct3D 11中SamplerState最大上限为16,但是采样纹理数最大上限为128。可能是因为别人使用Splat方案时,地形层数远超16层。当然也有可能是性能问题,但是我没查到具体依据。

    根据英伟达的说明,具体执行采样是由TMU来执行的,而根据SIMT的特性,同一时间只有一个纹理会被采样,所以理论上SamplerState的多少并不会影响TMU的执行和并发度。

    我反汇编了shader(D3D版本), 看到一个现象,每当我定义一个SamplerState, 就会有一行dcl_sampler的语句,我查了一下MSDN, 发现这个语句是用来声明sampler寄存器。所以如果非要说减少SamplerState可以提高性能,那原因应该就是,使用更多的sampler寄存器,可以获得更多的Warp来增加GPU的并行度。

  3. 为什么bindtexture开销比较大?
    暂时未找到合理的解释


在查资料过程中,有两个额外的收获:

bindtexture并不是从CPU向GPU上传图片,在opengl中上传图片是使用glTexImage2D来实现的,这时图片只在显存中。

在fragment阶段,并不是每一个像素都被任意分配到一个Thread然后并行执行的。

一个Warp Scheduler被分成8*4个线程组,每2×2的像素块,被分配给一个数量为4的Thread组, 也是就说每2×2的像素块一定被分配给在同一个Warp Scheduler中的4个Thread。具体原因英伟达的文章上并没有细说。但是大概意思是,比如在决定mip level时,除非这4个像素uv跳跃太大,不然可以只用计算一次mip level就可以了。

发布于 2021年3月13日2021年3月15日作者 重归混沌分类 渲染标签 render

发表评论 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注

评论

显示名称 *

电子邮箱地址 *

网站地址


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK