程序员应该对某些操作的成本有一个很好的了解:例如,CPU上指令的成本,L1,L2或L3高速缓存未命中的成本,LHS的成本。
当涉及到图形时,我意识到我几乎不知道它们是什么。我要记住,如果我们按成本订购它们,状态更改将类似于:
- 着色器均匀变化。
- 活动顶点缓冲区更改。
- 活动纹理单位更改。
- 活动着色器程序更改。
- 活动帧缓冲区更改。
但这是一个很粗略的经验法则,甚至可能不正确,我不知道数量级是多少。如果我们尝试放置单位,ns,时钟周期或指令数量,那么我们在谈论多少?
程序员应该对某些操作的成本有一个很好的了解:例如,CPU上指令的成本,L1,L2或L3高速缓存未命中的成本,LHS的成本。
当涉及到图形时,我意识到我几乎不知道它们是什么。我要记住,如果我们按成本订购它们,状态更改将类似于:
但这是一个很粗略的经验法则,甚至可能不正确,我不知道数量级是多少。如果我们尝试放置单位,ns,时钟周期或指令数量,那么我们在谈论多少?
Answers:
我所看到的关于各种状态变化的相对代价的最多数据来自于卡斯·埃弗里特(Cass Everitt)和约翰·麦克唐纳(John McDonald)从2014年1月开始的有关减少OpenGL API开销的演讲。他们的演讲包括这张幻灯片(31:55):
演讲并没有提供有关他们如何衡量这一指标的更多信息(甚至是他们是否正在衡量CPU或GPU成本,或者两者都在衡量!)。但是,至少它与传统观点相吻合:渲染目标和着色器程序更改最昂贵,统一更新最少,顶点缓冲区和纹理更改位于中间。他们其余的演讲在减少状态更改开销方面也有很多有趣的见解。
任何特定状态更改的实际成本都会因多种因素而变化,以至于几乎不可能获得一个普遍的答案。
首先,每个状态更改都可能同时具有CPU端成本和GPU端成本。根据驱动程序和图形API的不同,CPU成本可以全部在主线程上支付,也可以部分在后台线程上支付。
其次,GPU成本可能取决于飞行中的工作量。现代GPU的流水线非常多,并且喜欢一次完成大量工作,而最大的减慢就是停止流水线,以便当前飞行中的所有内容都必须在状态更改之前退役。什么会导致管道停顿?好吧,这取决于您的GPU!
要了解此处的性能,您实际上需要了解的是:驱动程序和GPU需要做什么来处理状态更改?当然,这取决于您的GPU,还取决于ISV通常不公开共享的细节。但是,有一些一般原则。
GPU通常分为前端和后端。前端处理驱动程序生成的命令流,而后端则完成所有实际工作。就像我之前说过的那样,后端喜欢进行大量工作,但是它需要一些信息来存储有关该工作的信息(可能由前端填写)。如果您踢出足够的小批量并用完所有硅片来跟踪工作,那么即使周围有很多未使用的马力,前端也将停滞。因此,这里的原则是:状态变化(绘制次数少)越多,您越可能使GPU后端饿死。
在实际处理绘制时,您基本上只是在运行着色器程序,这些程序正在进行内存访问以获取制服,顶点缓冲区数据,纹理以及告诉着色器单元顶点缓冲区所在位置的控制结构以及您的纹理是。GPU在这些内存访问之前也具有缓存。因此,每当您在GPU上抛出新的制服或新的纹理/缓冲区绑定时,第一次读取它们时就可能会发生缓存丢失。另一个原则:大多数状态更改将导致GPU缓存未命中。(这在您自己管理常量缓冲区时最有意义:如果在绘制之间保持常量缓冲区相同,则它们更有可能留在GPU的缓存中。)
着色器资源状态更改成本的很大一部分是CPU端。每当您设置新的常量缓冲区时,驱动程序很可能会将该常量缓冲区的内容复制到GPU的命令流中。如果您设置一个单一的制服,驱动程序很可能会将其变成背后的大常量缓冲区,因此它必须在常量缓冲区中查找该制服的偏移量,将值复制进去,然后标记常量缓冲区由于脏,因此可以在下一个绘制调用之前将其复制到命令流中。如果绑定新的纹理或顶点缓冲区,则驱动程序可能正在复制该资源的控制结构。另外,如果您在多任务OS上使用离散GPU,则驱动程序需要跟踪您使用的每个资源以及开始使用它的时间,以便内核能够 绘制时,GPU的内存管理器可以确保该资源的内存位于GPU的VRAM中。原理:状态更改使驱动程序重新排列内存以为GPU生成最小的命令流。
更改当前着色器时,可能会导致GPU缓存未命中(它们也具有指令缓存!)。原则上,CPU工作应仅限于在命令流中放置一个新命令,说“使用着色器”。但是,实际上,要处理的着色器编译一团糟。即使您提前创建了着色器,GPU驱动程序也经常会懒惰地编译着色器。但是,与该主题更相关的是,GPU硬件本身不支持某些状态,而是将其编译到着色器程序中。一个流行的例子是顶点格式:可以将它们编译到顶点着色器中,而不是在芯片上处于单独的状态。因此,如果您使用以前从未使用过的特定顶点着色器的顶点格式,您现在可能需要花费大量CPU成本来修补着色器并将着色器程序复制到GPU。此外,驱动程序和着色器编译器可能合谋做各种事情来优化着色器程序的执行。这可能意味着优化统一和资源控制结构的内存布局,以便将它们很好地打包到相邻的内存或着色器寄存器中。因此,当您更改着色器时,它可能会使驱动程序查看已绑定到管道的所有内容,并将其重新打包为新着色器的完全不同的格式,然后将其复制到命令流中。原理:这可能意味着优化统一和资源控制结构的内存布局,以便将它们很好地打包到相邻的内存或着色器寄存器中。因此,当您更改着色器时,它可能会使驱动程序查看已绑定到管道的所有内容,并将其重新打包为新着色器的完全不同的格式,然后将其复制到命令流中。原理:这可能意味着优化统一和资源控制结构的内存布局,以便将它们很好地打包到相邻的内存或着色器寄存器中。因此,当您更改着色器时,它可能会使驱动程序查看已绑定到管道的所有内容,并将其重新打包为新着色器的完全不同的格式,然后将其复制到命令流中。原理:更改着色器会导致大量CPU内存混乱。
帧缓冲区更改可能与实现最相关,但是在GPU上通常非常昂贵。您的GPU可能无法同时处理对不同渲染目标的多个绘制调用,因此可能需要暂停这两个绘制调用之间的管道。它可能需要刷新缓存,以便稍后可以读取渲染目标。它可能需要解决图纸期间已推迟的工作。(积累深度缓冲区,MSAA渲染目标等的单独数据结构是非常普遍的。当您离开该渲染目标时,可能需要完成此操作。如果您使用的是基于图块的GPU与许多移动GPU一样,当您离开帧缓冲区时,可能需要清除大量的实际着色工作。)原理:在GPU上更改渲染目标非常昂贵。
我敢肯定,这一切都非常令人困惑,而且不幸的是,很难做到过于具体,因为细节通常不是公开的,但是我希望这是对当您调用某个状态时实际上正在发生的某些事情的一个半透明的概述。您喜欢的图形API中更改功能。