KxngDemon
2024-09-15T01:56:11+00:00
请原谅我题目的语无伦次,但这个话题确实一定程度上已超出了我的掌控范围。对此我自己依旧在学习中,认知水平仍然太低,远未达到可以对这些知识深入理解并做出系统性归纳的水平。
故此贴的内容也仅作抛砖引玉和交流之用,难免存在问题和错误,欢迎专业大佬提出意见。
可以帮助理解的问题包括:
为什么CPU瓶颈时的输入延迟比GPU瓶颈时的更低?
预渲染队列设置对GPU瓶颈时的输入延迟有什么影响?
NV Reflex如何在已经最小化预渲染队列的前提下进一步减小GPU瓶颈时的输入延迟?
使用固定刷新率显示器时,关闭垂直同步如何导致画面撕裂?
使用固定刷新率显示器时,开启垂直同步如何避免画面撕裂?
使用固定刷新率显示器时,使用双重缓冲垂直同步和使用三重缓冲垂直同步有什么区别?
使用固定刷新率显示器时,使用不丢弃旧帧的旧式三重缓冲垂直同步和使用丢弃旧帧的新式三重缓冲垂直同步有什么区别?
使用可变刷新率显示器时,和上述使用固定刷新率显示器时的显示器行为有什么不同?
帧生成时间(Frametime)的3种计算方式分别是什么?
使用可变刷新率显示器时,为什么已经把帧率限制到刷新率上限以下,关闭垂直同步时依然会出现画面撕裂?
即使在帧生成时间极其平稳时,Animation Error如何依然会导致画面的微卡顿的发生?
当帧生成时间远小于刷新时间并关闭垂直同步,严重的画面撕裂具体是怎么降低等效输入延迟的?
使用可变刷新率+Reflex+垂直同步+锁帧时,和关闭垂直同步+不锁帧的等效输入延迟差距有多大?
上述问题其实光靠理论分析足以解答,但对看答案的人来说却难以理解,不够直观。
这时候使用具体的例子就能让理解难度下降很多。
我自己纯靠理论去分析时,有时也感觉要长脑子了,把例子画出来之后稍微看多几遍就舒服很多了。
关于例子所使用的模型,GN讲解Animation Error的视频www.油土鳖.com/watch?v=C_RO8bJop8o 中PresentMon 2.0 Metrics的图片给了我灵感:
把CPU、GPU、显示器这三种可以异步运作的组件在帧生成流水线中分开来讨论,从左到右为时间轴,用不同的颜色来表示不同的帧。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-9rfyT3cS16j-zz.png[/img]
在Nvidia介绍Reflex的文章www.nvidia.com/en-us/geforce/news/reflex-low-latency-platform/ 中也有类似的模型出现。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-b1sqT3cS130-c3.png[/img]
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-41vcT3cSy4-f1.png[/img]
RTSS的Reflex latency marker同样
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-fm1uT3cSki-bc.png[/img]
于是模仿PresentMon,构造出这样的帧生成流水线模型:
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-j283T3cS2qm-51.png[/img]
这里的一格时间段表示一段时间,想要表示时间点的话,需要参考某个时间段格子的左边沿或者右边沿。
比如时间段1的左边沿是时间点t=0,右边沿是时间点t=1。
看空的模板没意思,先来看一个最简单的例子:
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-bgomT3cS2qp-3y.png[/img]
例子1. CPU性能和GPU性能均衡,并且完美平稳地进行帧生成。
每种颜色代表不同的帧,比如红色为第1帧,黄色为第2帧,绿色为第3帧,蓝色为第4帧,紫色为第5帧。为了不让颜色过于复杂,从第6帧开始复用颜色,第二次出现的红色为第6帧,以此类推。
当某种颜色在某个时间段出现在代表某个组件的行上,意味着这个组件在这个时间段正在被这个颜色代表的帧所占用。
如果是CPU被占用,意味着CPU正在做这个帧的CPU相关工作;
如果是GPU被占用,意味着GPU正在做这个帧的GPU相关工作;
如果是显示器被占用,意味着显示器正在扫描对应这个帧的Front Buffer,从而把这个帧(或者更准确的说,这个帧在该时间段中对应的某几行像素)显示在屏幕上(对应的某几行像素位置上)。
这个例子里的每一帧的CPU工作量都需要5格时间段来完成,GPU工作量也需要5格时间段来完成。
为了方便和现实的时间概念联系,可以把1格时间段当作1毫秒来看待,那么5格就是5毫秒。
可描述为CPU时间=5毫秒,GPU时间=5毫秒。
从单帧的角度去看,比如只看第1帧(红色),
t=0到t=5,CPU对该帧进行CPU相关工作;
t=5,CPU对该帧的工作完成,该帧进入预渲染队列,此时预渲染队列为空,无需排队,GPU开始对该帧进行GPU相关工作;
t=5到t=10,GPU对该帧进行GPU相关工作;
t=10,GPU对该帧的工作完成,将该帧交换进显示器使用的Front Buffer,显示器开始扫描该帧;
t=10到t=15,显示器持续扫描该帧(这里我们不讨论显示器是否发生刷新,即是否经过了VBI)。
t=15,第2帧(黄色)被GPU完成,Front Buffer对应的帧从第1帧(红色)变为第2帧(黄色),显示器扫描的帧从第1帧变为第2帧。
从单时间段的角度去看,在t=10流水线进入稳态后,每个时间段三种组件(CPU、GPU、显示器)都分别被3种不同的帧占用,没有一刻是空闲的。
这是比较理想的状态,CPU没有拖GPU后腿,GPU也没有拖CPU后腿,大家都能把自己的潜力完全发挥。
接下来考虑其他情况:预渲染队列设置=1时的GPU瓶颈、预渲染队列设置=2时的GPU瓶颈、使用Nvidia Reflex的GPU瓶颈、CPU瓶颈。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-6rm5T3cS2qm-oa.png[/img]
例子2. 预渲染队列设置=1时的GPU瓶颈
GPU瓶颈具体表现为某帧的GPU相关工作时间>某帧的CPU相关工作时间。
帧生成时间取决于GPU时间。
例子使用CPU时间=5毫秒,GPU时间=7毫秒。
预渲染队列设置=1对应NV控制面板低延迟模式="开"。不过即使不设置NV控制面板,许多竞技向游戏的默认设置就是1了。
这是允许CPU和GPU并行工作的最小设置。
如果预渲染队列设置为0,GPU在完成手上的工作之前会持续阻塞CPU,那就是CPU和GPU之间就像是几乎串行工作了。
t=10,CPU已完成第2帧(黄色),GPU却仍未完成第1帧(红色)。由于预渲染队列已满,此时第2帧(黄色)无法进入预渲染队列,CPU被阻塞,CPU无法开始第3帧(绿色)的工作。
一直等到t=12,GPU完成了第1帧(红色)的工作,预渲染队列才释放了空位,让第2帧(黄色)进入,CPU才能开始第3帧(绿色)的工作。
观察进入稳态后的情况,以第2帧(黄色)为例,
假设输入采样发生在CPU工作开始的瞬间,即t=5,
首次出现在显示器是在该帧切换到Front Buffer的瞬间,即t=19,
那么,稳态输入延迟=19-5=14毫秒
例子3. 预渲染队列设置=2时的GPU瓶颈
和预渲染队列设置=1时相比,预渲染队列可以容纳更多的帧。
t=10到t=25的CPU部分体现出了明显的区别。
只要预渲染队列没满,CPU就可以持续把它完成的帧扔给队列,然后开始新的一帧的工作。
从这个角度来说,使用更大的预渲染队列设置提高了偶发CPU瓶颈时的GPU吞吐量:即使CPU偶尔出现瓶颈,较长时间内没有完成某一帧,预渲染队列中CPU以前完成的的帧也能持续喂饱GPU。
但在预渲染队列被填满、进入稳态时,和预渲染队列设置=1时相比会产生多1帧帧生成时间的输入延迟惩罚。
以第6帧(红色)为例,稳态输入延迟=47-26=21毫秒,多了7毫秒,为1帧帧生成时间(这里取决于GPU时间)。
例子4. 使用Nvidia Reflex的GPU瓶颈
Nvidia Reflex通过让游戏引擎推迟CPU开始工作的时间来降低输入延迟。
具体的推迟时间量是动态可变的。Reflex通过使用上一(几)帧的CPU时间和GPU时间来预测下一帧最可能的CPU时间和GPU时间,从而预测出对应的最合适的推迟时间量。
这也可以用基于游戏引擎的动态限帧来理解。
同时正因Reflex这种限帧是基于游戏引擎的,而非基于显卡驱动的,需要显卡(驱动)和游戏引擎通信,Reflex才需要特定游戏官方支持,而不能在任意一个游戏中通用。
在例子中,Reflex使用了2毫秒的推迟时间量,这个设置是最佳的。
少于这个设置,则减小延迟的效果变弱;
大于这个设置,则CPU喂不饱GPU,降低了吞吐量(帧率)。
以第2帧(黄色)为例,稳态输入延迟=19-7=12毫秒,比预渲染队列设置=1的GPU瓶颈的输入延迟低了2毫秒,
并且刚好等于CPU时间+GPU时间,为理论最低延迟。
例子5. CPU瓶颈
CPU瓶颈具体表现为某帧的CPU相关工作时间>某帧的GPU相关工作时间。
帧生成时间取决于CPU时间。
例子使用CPU时间=7毫秒,GPU时间=5毫秒。
在t=12,GPU已完成第1帧(红色),CPU却仍未完成第2帧(黄色)。GPU没有被阻塞,它只是没有东西做。
一直等到t=14,CPU完成了第2帧(黄色)的工作,GPU才能开始该帧的工作。
以第2帧(黄色)为例,稳态输入延迟=19-7=12毫秒,刚好等于CPU时间+GPU时间,为理论最低延迟。
小节总结与回顾
以下问题:
为什么CPU瓶颈时的输入延迟比GPU瓶颈时的更低?
预渲染队列设置对GPU瓶颈时的输入延迟有什么影响?
NV Reflex如何在已经最小化预渲染队列的前提下进一步减小GPU瓶颈时的输入延迟?
已可通过上述示例去理解。
在上述例子中,主要仅展示了CPU和GPU的行为和互相之间的交互,而没有展示显示器是如何刷新的。
在后面的讨论中,需要把这个要素加进来。
这里加入一行用于表示显示器VBI(垂直消隐间隔)的时刻。显示器在VBI时,把扫描指针从右下角移动回左上角,重新开始从左到右、从上到下对屏幕的每一个像素进行扫描和刷新。
两个VBI之间称为一个刷新周期,刷新周期的持续时长称为刷新时间。
垂直同步(Vsync)就是只允许Buffer Swap发生在显示器VBI内,在VBI时显示器没有任何像素被扫描和刷新。
先看看固定刷新率显示器+关闭垂直同步的情况。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-ct8lT3cS2qp-fo.png[/img]
可以根据帧生成时间和刷新时间之间的3种关系(大于、等于、小于),作3种情况分别讨论。
例子6. 固定刷新率显示器,关闭垂直同步,帧生成时间小于刷新时间
例子使用CPU时间=GPU时间=4毫秒,刷新时间=5毫秒。
GPU完成每一帧的时间间隔总是小于VBI的时间间隔,于是两个VBI之间的一个刷新周期便出现了2帧,或者说由2帧构成,产生画面撕裂。
以t=10到t=15的刷新周期为例,
t=10到t=12,显示器所扫描的Front Buffer为第1帧(红色),从而显示器的上边40%画面构成都属于第1帧(红色)。
t=12,GPU完成了第2帧(黄色)并触发Buffer Swap,Front Buffer突然变为第2帧(黄色)。
t=12到t=15,显示器所扫描的Front Buffer为第2帧(黄色),从而显示器的下边60%画面构成都属于第2帧(黄色)。
显示器的上边40%画面构成都属于第1帧(红色),显示器的下边60%画面构成都属于第2帧(黄色),此即为画面撕裂,撕裂位置在于画面从上往下第40%位置。
例子7. 固定刷新率显示器,关闭垂直同步,帧生成时间等于刷新时间,但不同步
例子使用CPU时间=GPU时间=5毫秒,刷新时间=5毫秒。
Buffer Swap发生的时间点和显示器VBI发生的时间点存在2毫秒的时间差。
即使帧生成时间等于刷新时间,所有帧都依然存在画面撕裂,撕裂位置在于画面从上往下第60%位置。
例子8. 固定刷新率显示器,关闭垂直同步,帧生成时间大于刷新时间
例子使用CPU时间=GPU时间=6毫秒,刷新时间=5毫秒。
虽然由于帧生成时间大于刷新时间,在部分刷新周期比如t=25到t=30和t=30到t=35,每个画面都只有一帧,不存在画面撕裂,
但是其他帧依然可能发生画面撕裂,且发生频率并不低。
总的来说,只要使用固定刷新率显示器+关闭垂直同步的搭配,无论帧生成时间和刷新时间之间是怎么样的关系,都有可能发生画面撕裂。
接下来看固定刷新率显示器+开启垂直同步的情况。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-eo6xT3cS2qp-ri.png[/img]
同样的,可以根据帧生成时间和刷新时间之间的3种关系(大于、等于、小于),作3种情况分别讨论。
不过当帧生成时间等于刷新时间,显然稳态图像就只是在例子7的类似的基础上令Buffser Swap和Front Buffer同步,当然,输入延迟会增加。同时这种情况过于特例,没什么意思。故懒得画图出来和分析。
垂直同步分为三种:
双重缓冲垂直同步,有1个Back Buffer供GPU使用。此方法目前使用较少。
老式三重缓冲垂直同步,有2个Back Buffer供GPU使用,不丢弃旧帧,采用类似于先进先出队列的方式让2个Back Buffer排队。此方法为大部分游戏默认的垂直同步方法。
新式三重缓冲垂直同步,有2个Back Buffer供GPU使用,丢弃旧帧,永远只把时间更近的那个Back Buffer进行Buffer Swap。此方法为Nvidia Fast Sync、Windows DWM对窗口化窗口进行composition前所使用的垂直同步方法。
例子9. 固定刷新率显示器,开启双重缓冲垂直同步,帧生成时间大于刷新时间
例子使用CPU时间=GPU时间=6毫秒,刷新时间=5毫秒,预渲染队列设置=1。
t=12,垂直同步禁止Buffer Swap在VBI之外的时刻发生,所以GPU只能一直等VBI。
即使此刻GPU完成了第1帧(红色),也不能开始进行第2帧(黄色)的工作,因为唯一的1个Back Buffer已经被第1帧(红色)占用了。可以说此时GPU被显示器阻塞了。
GPU不能释放掉第1帧(红色),预渲染队列就没有空位,CPU就不能把第2帧(黄色)释放进预渲染队列了。可以说此时CPU被GPU(预渲染队列)阻塞了,但这个阻塞源头说到底还是显示器。
t=15,终于等到了VBI,可以进行Buffer Swap了。都释放掉手头已完成的帧后,GPU和CPU都开始分别着手进行下一帧的工作。
t=20,又是一次VBI,但此时第2帧(黄色)仍未被GPU未完成,没有发生Buffer Swap,在接下来的一整个刷新周期里显示器都会把上一个刷新周期的画面完整重复一遍。换句话说,此时的等效刷新时间10毫秒是显示器硬件刷新时间5毫秒的2倍,等效刷新率减半了。
t=21,GPU完成了第2帧(黄色),但此时距离下一次VBI还很远。于是和t=15时类似,GPU和CPU又被显示器阻塞了。
从t=15流水线进入稳态后,等效刷新时间便一直为10毫秒,是显示器硬件刷新时间的2倍。换句话说,显示器的等效刷新率将一直减半下去,从一个200Hz显示器等效为100Hz显示器。
同时显示器还持续阻塞GPU和CPU,即使CPU时间=GPU时间=6毫秒,等效的帧生成时间也只有10毫秒,等效的帧生成时间已经和CPU时间和GPU时间脱钩。
这就是双重缓冲垂直同步的可怕之处:只要帧生成时间稍大于刷新时间,显示器的等效刷新率和等效帧率就会直接砍半。
这时候便需要给多一个Back Buffer,从双重缓冲垂直同步变为三重缓冲垂直同步,从而缓解这种愚蠢的现象。
例子10. 固定刷新率显示器,开启三重缓冲垂直同步,帧生成时间大于刷新时间
例子使用CPU时间=GPU时间=6毫秒,刷新时间=5毫秒,预渲染队列设置=1。
当帧生成时间大于刷新时间,老式三重缓冲垂直同步和新式三重缓冲垂直同步不会有区别,故例子10同时适用于这两种情况。
观察例子10与例子9产生区别的分歧点t=12。
t=12,GPU完成了第1帧(红色)写入了Back_Buffer_0,虽然没有等到VBI,不能进行Buffer Swap,但此时仍有Back_Buffer_1可供第2帧(黄色)使用。
所以GPU没有被阻塞,CPU也没有被阻塞,可以持续工作。
在之后的VBI时刻t=15, 20, 25, 30, 40, 45,都有新的Back_Buffer可供Buffer Swap,显示器可以保持每个刷新周期显示不同的画面,等效刷新率跑满。
但在VBI时刻t=35,显示器正在显示第4帧(蓝色),而第5帧(紫色)未被GPU完成,因此只能在重复显示画面,t=30到t=40时间段的等效刷新率减半。
等效帧率观感就像是200FPS-200FPS-200FPS-100FPS-200FPS-200FPS一样。
例子11. 固定刷新率显示器,开启双重缓冲垂直同步,帧生成时间小于刷新时间
例子使用CPU时间=GPU时间=4毫秒,刷新时间=5毫秒,预渲染队列设置=1
t=8,同例子9,GPU和CPU被显示器阻塞,两者都停止工作。
t=10,进行Buffer Swap,CPU和GPU恢复工作。
进入稳态后,每个刷新周期都有不同的帧,等效刷新率总能跑满,也因此显示器总是会阻塞GPU和CPU,防止他们做任何多余的工作,即使CPU时间和GPU时间都小于刷新时间。
通过不断的阻塞GPU和CPU,使等效帧生成时间=刷新时间=5毫秒,等效帧生成时间和CPU时间和GPU时间脱钩。
这便是“开启垂直同步会把帧率锁在显示器刷新率”。
以第3帧(绿色)为例,
稳态输入延迟=20-10=10毫秒
例子12. 固定刷新率显示器,开启老式三重缓冲垂直同步,帧生成时间小于刷新时间
例子使用CPU时间=GPU时间=4 刷新时间=5 预渲染队列设置=1
观察例子12与例子11产生区别的分歧点t=8。
完全类似于例子10和例子9之间的区别,便不再赘述。
可以发现例子12与例子11的显示器部分是一模一样的,主要区别在于CPU部分,每帧的输入采样时间点提早了。换句话说,输入延迟变大了。
以第6帧(红色)为例,
稳态输入延迟=35-20=15毫秒,多了5毫秒,1个刷新周期(同时也是等效帧生成时间)。
Frame Buffer的先进先出队列对输入延迟的影响完全类似于例子2和例子3的预渲染队列对输入延迟的影响,只要有帧(帧缓存)在排队,就会产生额外的输入延迟。
例子13. 固定刷新率显示器,开启新式三重缓冲垂直同步,帧生成时间小于刷新时间
例子使用CPU时间=GPU时间=4 刷新时间=5 预渲染队列设置=1
观察例子13和例子12产生区别的分歧点t=20。
t=20,GPU已经完成第4帧(蓝色),此时的另一个Back Buffer为第3帧(绿色)。
新式三重缓冲垂直同步丢弃了第3帧(绿色),直接使用第4帧(蓝色)进行Buffer Swap。
这里的区别表现了新式三重缓冲垂直同步的部分输入延迟优势,因为这里使用的帧是更新的。
单看t=20时的瞬时输入延迟,例子13的为20-12=8毫秒,例子12的为20-8=12毫秒。
同时显示器也不会阻塞GPU和CPU了,他们能跑多快就跑多快,能产出多少帧就产出多少帧,跑出来多余的帧可以直接扔掉(旧Frame Buffer被新的帧直接覆盖掉)。
这便是“Nvidia Fast Sync和游戏窗口化不会把帧率锁在显示器刷新率”。
t=20,例子13的显示器使用第4帧,例子12的显示器使用第3帧。
t=40,例子13的显示器使用第9帧,例子12的显示器使用第7帧。
小节总结与回顾
以下问题:
使用固定刷新率显示器时,关闭垂直同步如何导致画面撕裂?
使用固定刷新率显示器时,开启垂直同步如何避免画面撕裂?
使用固定刷新率显示器时,使用双重缓冲垂直同步和使用三重缓冲垂直同步有什么区别?
使用固定刷新率显示器时,使用不丢弃旧帧的旧式三重缓冲垂直同步和使用丢弃旧帧的新式三重缓冲垂直同步有什么区别?
已可通过上述示例去理解。
看完了固定刷新率显示器,接下来看看可变刷新率显示器的情况。
这里默认了可变刷新率显示器会配合显卡驱动开启了可变刷新率功能(GSync、FreeSync等),否则其表现与固定刷新率显示器无异。
可变刷新率显示器如其名,拥有可变的刷新时间(可变的刷新率)。
刷新时间(刷新率)会有一个显示器厂商对该型号所规定的范围或者说上限和下限,该范围可以使用CRU(Custom Resolution utility)通过读取显示器的EDID信息获得。
比如我使用的三星Neo G8的刷新率范围为68Hz-240Hz,对应刷新时间范围为4.17毫秒-14.71毫秒。
那么三星Neo G8的刷新率下限是68Hz,刷新率上限是240Hz,对应刷新时间下限是4.17毫秒,刷新时间上限是14.71毫秒。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-1yhiToS7m-m3.png[/img]
在可变刷新率显示器在某种场景下想要使用超过刷新时间上限范围的刷新时间时,可以使用低帧率补偿LFC(Low Framerate Compensation)来实现,
因此刷新时间的上限(对应刷新率的下限)并不是那么重要,只需要注意刷新时间的下限(对应刷新率的上限)即可。
只要在显示器支持范围内,显示器就可以自由调整VBI的位置,主动、尽量让VBI和Buffer Swap进行对齐。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-djdkT3cS2qx-a7.png[/img]
例子14. 可变刷新率显示器,帧生成时间大于刷新时间下限
例子使用CPU时间=GPU时间=6毫秒,刷新时间下限=5毫秒(刷新时间>=5毫秒)。
在该例子中开关垂直同步没有区别,三种垂直同步之间也没有区别。
显然,所有Buffer Swap的间隔都为6毫秒,大于5毫秒的刷新时间下限,显示器有能力自由调整VBI的位置并将VBI和Buffer Swap对齐。
比如,如果这里使用的是刷新时间=5毫秒的固定刷新率显示器,第1个VBI在t=12,第2个VBI则必须为t=17,
于是在t=18时第2帧(黄色)被GPU完成并发生Buffer Swap时,就出现了画面撕裂了。
而可变刷新率显示器就可以把第2个VBI推迟1毫秒,从t=17变为t=18,刚好和Buffer Swap的时间点对齐,避免了撕裂。
除了没有任何画面撕裂以外,输入延迟也全部是理论最低值(CPU时间+GPU时间)。
例子15. 可变刷新率显示器,关闭垂直同步,帧生成时间小于刷新时间下限(帧率大于刷新率上限)
例子使用CPU时间=GPU时间=6毫秒,刷新时间下限=5毫秒(刷新时间>=5毫秒)。
t=8为第1次VBI,而t=12时发生了Buffer Swap,假设显示器有能力可以把VBI调整到t=12,就可以对齐并避免撕裂。
然而这里显示器的刷新时间下限即VBI之间的间隔的下限为5毫秒,显示器没有能力做到这么小的刷新时间,拼劲全力才能勉勉强强在t=13时进行VBI。
此时动态刷新率显示器的行为和固定刷新率显示器没有任何区别,刷新时间恒为下限的5毫秒,参考例子6。
这便是“帧率高于刷新率时,可变刷新率失效”。
类似的,开启(三种中的某种)垂直同步时,动态刷新率显示器的行为和固定刷新率显示器也没有任何区别,刷新时间恒为下限的5毫秒,参考例子11、例子12、例子13。
于是我也就懒得弄开启垂直同步的例子了。
在开始下一步讨论之前,有必要先介绍帧生成时间(Frametime)的3种计算方式。
为了方便,我照搬RTSS对前2种计算方式的叫法:Frame Start和Frame Presentation。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-bwn5T3cSi1-qc.png[/img]
第3种计算方式的叫法就直接取其字面意思,叫Buffer Swap。
3种计算方式分别使用了同1帧的不同时间点。
对使用了某种计算方式的帧生成时间来说,帧生成时间为两个相邻时间点的间隔。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-6xunT3cS2qr-k7.png[/img]
例子16. 展示帧生成时间的3种计算方式。
例子使用CPU时间=GPU时间=5毫秒。
Frame Start时间点在于每帧CPU工作部分的开头。
第1帧(红色)的Frame Start为t=0;
第2帧(黄色)的Frame Start为t=5;
第3帧(绿色)的Frame Start为t=10。
基于Frame Start计算的第1帧(红色)的帧生成时间=5-0=5毫秒。
RTSS(和使用它的Afterburner)默认使用这种帧生成时间计算方式。
RTSS锁帧中的async模式(默认的模式)和back edge sync模式基于Frame Start。
Frame Presentation时间点在于每帧CPU工作的结尾、GPU工作部分的开头。
第1帧(红色)的Frame Presentation为t=5;
第2帧(黄色)的Frame Presentation为t=10;
第3帧(绿色)的Frame Presentation为t=15。
基于Frame Presentation计算的第1帧(红色)的帧生成时间=10-5=5毫秒。
PresentMon和基于PresentMon的软件如游戏加加、CapFrameX、Nvidia FrameView使用这种帧生成时间计算方式。
RTSS可以手动切换为这种帧生成时间计算方式。
Buffer Swap时间点就是其字面意思。
第1帧(红色)的Buffer Swap为t=10;
第2帧(黄色)的Buffer Swap为t=15;
第3帧(绿色)的Buffer Swap为t=20。
基于Buffer Swap计算的第1帧(红色)的帧生成时间=15-10=5毫秒。
目前我只知道NvidiaProfileInspector调出的Flip FPS图像基于Buffer Swap。
这里的Flip和Swap指的就是同一样东西。
Flip FPS图像显示的FPS都是瞬时值(原始值),而瞬时FPS的倒数即为帧生成时间。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-a1ryT3cS2j4-f1.png[/img]
上面3种计算方式的帧生成时间都相等,只是因为CPU时间和GPU时间非常平稳一致(consistent)。
一旦出现波动,情况就有所变化了。
例子17. 帧生成时间的三种计算方式不对齐示例。
例子使用第1帧(红色)的CPU时间=4毫秒,GPU时间=3毫秒。
第2帧(黄色)的CPU时间=5毫秒,GPU时间=4毫秒。
第3帧(绿色)的CPU时间=6毫秒,GPU时间=5毫秒。
显然这3帧都是CPU瓶颈,且3帧的工作量各不相同、逐渐增大。
基于Frame Start计算的第1帧(红色)的帧生成时间=4-0=4毫秒;
基于Frame Presentation计算的第1帧(红色)的帧生成时间=9-4=5毫秒;
基于Buffer Swap计算的第1帧(红色)的帧生成时间=13-7=6毫秒。
3种计算方式得出了3种不同的帧生成时间结果。
例子18. 帧生成时间的三种计算方式不对齐示例,RTSS async模式限帧(限制Frame Start最小间隔)
例子18使用的3个帧的参数和例子17相同。
但加入了RTSS的约束条件:帧生成时间_start_n>=7毫秒,即基于Frame Start计算的任意帧生成时间必须大于等于7毫秒。
从实现的角度来说,假如前一帧的CPU时间较短,前一帧的CPU工作将很快完成,RTSS若不干涉,后一帧的FrameStart将立刻到来,就会导致上述约束条件不被满足。
所以RTSS async模式限帧就会推迟后一帧的Frame Start时间点,从而令限制条件得到满足。
例子18和例子17相比,第2帧(黄色)的Frame Start从t=4推迟到t=7,第3帧(绿色)的Frame Start从t=9推迟到t=14。
基于Frame Start计算的第1帧(红色)的帧生成时间=7-0=7毫秒;
基于Frame Start计算的第2帧(黄色)的帧生成时间=14-7=7毫秒;
基于Frame Start计算的第3帧(绿色)的帧生成时间=21-14=7毫秒。
RTSS的限帧干涉让约束条件得到满足,基于Frame Start计算的帧生成时间是完美一致的。
就像这样一条笔直直线的帧生成时间曲线。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-9ljgToS74-4d.png[/img]
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-ixb1T1kSbx-27.png[/img]
但是其他计算方式的帧生成时间呢?
基于Frame Presentation计算的第1帧(红色)的帧生成时间=12-4=8毫秒;
基于Frame Presentation计算的第2帧(黄色)的帧生成时间=20-12=8毫秒。
基于Frame Presentation计算的第3帧(绿色)的帧生成时间由于没有第4帧和对应时间点而无法计算(某种意义上说,只要一直没有第4帧,这里可以取正无穷)。
基于Buffer Swap计算的第1帧(红色)的帧生成时间=16-7=9毫秒;
基于Buffer Swap计算的第2帧(黄色)的帧生成时间=25-16=9毫秒。
基于Buffer Swap计算的第3帧(绿色)的帧生成时间由于没有第4帧和对应时间点而无法计算(某种意义上说,只要一直没有第4帧,这里可以取正无穷)。
其他计算方式得到的帧生成时间已经和基于的Frame Start完全不一样了。
就连Frame Presentation和Buffer Swap互相之间都不一样。
这样简单的例子已经展现了一个问题:三种不同的时间点互相之间的偏移量互不相同、难以控制。
通过单独控制其中一个时间点的“限帧”,真的有绝对的、强的约束力吗?
回到之前可变刷新率显示器的讨论正题。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-876eT3cS2qs-aa.png[/img]
例子19. 可变刷新率显示器,关闭垂直同步,RTSS async模式限帧到刷新率上限以下(帧生成时间_start_n>刷新时间下限)
例子使用第1帧(红色)的CPU时间=5毫秒,GPU时间=5毫秒。
第2帧(黄色)的CPU时间=6毫秒,GPU时间=5毫秒。
第3帧(绿色)的CPU时间=4毫秒,GPU时间=3毫秒。
第4帧(蓝色)的CPU时间=6毫秒,GPU时间=5毫秒。
第5帧(紫色)的CPU时间=5毫秒,GPU时间=6毫秒。
刷新时间下限=5毫秒(刷新时间>=5毫秒)。
存在RTSS的约束条件:帧生成时间_start_n>=7毫秒,即基于Frame Start计算的任意帧生成时间必须大于等于7毫秒。
一个刷新率上限200Hz的显示器(一个刷新周期下限5毫秒的显示器),使用RTSS把帧率限制到了至多1000/7=142.8FPS(使用RTSS把帧生成时间限制到了至少7毫秒),
帧率小于刷新率上限(帧生成时间大于刷新时间下限),按理说就像例子14那样,应当完美满足了动态刷新率生效的条件,不存在撕裂。
实际却在t=21出现了画面撕裂,这是为什么?
显然所有5个帧的基于Frame Start计算的帧生成时间都是7毫秒,然而,
t=10,第1帧(红色)触发Buffer Swap,第1次VBI对齐;
t=18,第2帧(黄色)触发Buffer Swap,第2次VBI与之对齐;
t=21,第3帧(绿色)触发Buffer Swap,此时距上一次VBI仅过去3毫秒,小于5毫秒的刷新时间下限,无法对齐VBI,产生画面撕裂。
t=23,终于经过了5毫秒的刷新时间下限,显示器进行VBI。
基于Buffer Swap计算的第1帧(红色)的帧生成时间=18-10=8毫秒>5毫秒,没有问题;
基于Buffer Swap计算的第2帧(黄色)的帧生成时间=21-18=3毫秒<5毫秒,罪魁祸首就在于此。
从直观上看,虽然第2帧(黄色)和第3帧(绿色)的限帧后的Frame Start的帧生成时间相同,都是7毫秒,两帧之间的Frame Start间隔也是7毫秒
但第2帧(黄色)的工作量(CPU时间+GPU时间)较大,第3帧(绿色)的工作量(CPU时间+GPU时间)较小。
这就导致了第2帧(黄色)的Buffer Swap时间点是相比其Frame Start时间点的偏移量更大,即更靠后的;
第3帧(绿色)的Buffer Swap时间点是相比其Frame Start时间点的偏移量更小,即更靠前的;
一个靠后,一个靠前,俩Buffer Swap自然挨得很紧了,紧到间隔小于刷新时间下限,自然撕裂。
要说根本原因,就是两帧的工作量(CPU时间+GPU时间)一大一小。
这种情况下不仅基于Buffer Swap计算的帧生成时间是不一致的,连基于Frame Presentation计算的帧生成时间也是不一致的。
blurbusters的经典G-SYNC 101文章[url]https://blurbusters.com/gsync/gsync101-input-lag-tests-and-settings/2/[/url] 中有这种情况的现实例子
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-5pm6T3cSw6-u6.png[/img]
在现实中,连续的游戏画面具有关联性,相邻的帧的工作量一般不会差距太大。
但只要出现了两连续帧工作量的稍微的“先大后小”,就会像上图那样,在画面靠底部的位置出现撕裂。
绝大部分用户的注意力不会放在画面底部,所以较少能注意到这种撕裂现象。
那么遇到画面撕裂应该干什么?小学生都知道,当然是开启垂直同步。
例子20. 可变刷新率显示器,开启垂直同步,RTSS async模式限帧到刷新率上限以下(帧生成时间_start_n>刷新时间下限)
例子20使用的各项参数和例子19相同,仅把垂直同步从关闭变为开启。
观察例子20与例子19的唯一区别t=21到t=23。
t=21,第3帧(绿色)本来会触发Buffer Swap,现在被开启的垂直同步按了回去,必须等待到VBI才能进行。
t=21到t=23,Front Buffer依然是第2帧(黄色),t=18到t=23的刷新周期内为该单独完整帧,没有画面撕裂;同时第3帧(绿色)在Back Buffer躺着不动。
t=23,等到了VBI,进行Buffer Swap,第3帧(绿色)正常成为Front Buffer,被显示器扫描并显示出来。
其他部分开、关垂直同步没有任何区别,显然输入延迟也没有任何区别。
第3帧(绿色)开启垂直同步后,表面上输入延迟从21-14=7毫秒变为了23-14=9毫秒,增加了2毫秒。
但这2毫秒的区别,刚好就是画面撕裂其本身,即提前2毫秒在画面的靠底部的位置(例子中为画面的下半40%部分)撕裂地显示出来。
用户真的需要这个形态的“更低输入延迟”吗?我认为是不需要的。
总的来说,在可变刷新率显示器+限帧到刷新率上限以下的条件下,垂直同步对输入延迟造成的影响可谓微乎其微。
这个“垂直同步”简直就像和使用固定刷新率显示器时的那个“垂直同步”完全不是同一个东西。
因此还是那个结论不变:开启可变刷新率(GSync)+限帧到刷新率上限以下+开启垂直同步,就是能最佳发挥可变刷新率的输入延迟优势和无画面撕裂优势的设置组合。
通过上面这些例子,Animation Error,一种无法被帧生成时间数据(和基于帧生成时间计算得到的各种类型的low帧数据)所表达出来的微卡顿也很容易理解了。
为了方便理解,假设例子20展现的是一个物体的匀速直线运动过程,该物体应当每1毫秒移动1个像素,即速度为1像素/毫秒。
第1帧(红色)的Frame Start为t=0,此帧里该物体已移动距离为0像素,即位于初始位置。
第2帧(黄色)的Frame Start为t=7,此帧里该物体已移动距离为7像素。
第3帧(绿色)的Frame Start为t=14,此帧里该物体已移动距离为14像素。
第4帧(蓝色)的Frame Start为t=21,此帧里该物体已移动距离为21像素。
第5帧(紫色)的Frame Start为t=28,此帧里该物体已移动距离为28像素。
第1帧(红色)的Buffer Swap为t=10,此时用户看到画面中物体已移动距离为0像素。
第2帧(黄色)的Buffer Swap为t=18,此时距离上幅画面经过了8毫秒,用户看到画面中物体已移动距离为7像素。
第3帧(绿色)的Buffer Swap为t=23,此时距离上幅画面经过了5毫秒,用户看到画面中物体已移动距离为14像素。
第4帧(蓝色)的Buffer Swap为t=32,此时距离上幅画面经过了9毫秒,用户看到画面中物体已移动距离为21像素。
第5帧(紫色)的Buffer Swap为t=39,此时距离上幅画面经过了7毫秒,用户看到画面中物体已移动距离为28像素。
从用户直接看显示器画面的实际观感出发,这个物体最快的时候用5毫秒走出7像素距离,速度为1.4像素/毫秒;最慢的时候用9毫秒走出7像素距离,速度为0.78像素/毫秒。
用户会觉得这个物体根本不是在匀速直线运动,它的速度和运动轨迹是一直抖动(jittering)的。
这就是Animation Error微卡顿的典型表现之一。
但说实话,既然根源来自帧和帧之间CPU时间和GPU时间的不一致,Animation Error在目前并没有什么很好的解决办法。
CPU时间取决于CPU工作量和CPU实时性能,而GPU时间取决于GPU工作量和GPU实时性能。
每帧的工作量我们用户控制不了,用户最多能做到的也只有尽量让硬件的性能释放(频率)尽量保持平稳,而这对于台式电脑来说应当是理所当然的。
小节总结与回顾
以下问题:
使用可变刷新率显示器时,和上述使用固定刷新率显示器时的显示器行为有什么不同?
帧生成时间(Frametime)的3种计算方式分别是什么?
使用可变刷新率显示器时,为什么已经把帧率限制到刷新率上限以下,关闭垂直同步时依然会出现画面撕裂?
即使在帧生成时间极其平稳时,Animation Error如何依然会导致画面的微卡顿的发生?
已可通过上述示例去理解。
最后讨论一下关闭垂直同步,帧生成时间远小于刷新时间(帧率远大于刷新率)对有效输入延迟的影响。
能达成这种条件的,一般是追求最低输入延迟的,同时也确实能跑到极高帧率的竞技性网游,比如CS2和瓦洛兰特。
类似于前面所提到的,这种情况下,更低的输入延迟就是画面撕裂其本身。
而画面撕裂的位置对这类游戏来说是一个不可忽视的变量:这类游戏一般只有画面正中央部分,比如说第一人称射击游戏的准星的位置才是有效的位置。
只有画面正中央部分所对应的帧的输入延迟,才算是有效输入延迟;这类游戏的玩家并不会太关心画面上方和下方的信息和输入延迟。
因此,需要考虑到底是哪一帧更可能处于画面正中央部分,对应刷新周期的正中间。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-gxsmT3cS2qr-qo.png[/img]
例子21. 帧生成时间=1*刷新时间,即帧率=刷新率
例子使用CPU时间=GPU时间=刷新时间=6毫秒。相当于在165Hz显示器上跑165FPS。
为了贴合实际情况,令VBI和Buffer Swap轻微不同步。
在t=13到t=19的刷新周期,画面中央位置为t=16,此时Front Buffer为第1帧(红色),而第1帧的输入采样时间为t=0
有效输入延迟=16-0=16毫秒
例子22. 帧生成时间=1/2*刷新时间,即帧率=2*刷新率
例子使用CPU时间=GPU时间=3毫秒,刷新时间=6毫秒。相当于在165Hz显示器上跑330FPS。
有效输入延迟=10-3=7毫秒
例子23. 帧生成时间=1/3*刷新时间,即帧率=3*刷新率
例子使用CPU时间=GPU时间=2毫秒,刷新时间=6毫秒。相当于在165Hz显示器上跑500FPS。
有效输入延迟=10-(4+6)/2=5毫秒
例子24. 帧生成时间=1/6*刷新时间,即帧率=6*刷新率
例子使用CPU时间=GPU时间=1毫秒,刷新时间=6毫秒。相当于在165Hz显示器上跑1000FPS。
有效输入延迟=10-(7+8)/2=2.5毫秒
看下来感觉帧率跑到2倍刷新率和跑到1倍刷新率相比,降低输入延迟的收益是比较明显的。但这个收益是需要整整2倍的硬件性能才能得到的,这个代价已经不小了。
要是继续往上的话性价比就更小了。
例子25. 硬件性能条件同上,使用reflex锁帧+垂直同步+可变刷新率的理想状态。
例子使用CPU时间=GPU时间=1毫秒,刷新时间=6毫秒。相当于硬件性能能跑1000FPS,但在165Hz显示器上reflex锁帧到165FPS。
有效输入延迟=11-6=5毫秒
例子25和例子24相比,拥有相同的硬件性能条件,以有效输入延迟劣化2.5毫秒为代价,换来减少六分之五硬件工作量的硬件功耗降低,以及无画面撕裂的体验。
我从一个业余玩家的角度来看,觉得这种交换似乎也不是不能接受。。。
只能说只要硬件性能足够强(像这里的CPU时间=GPU时间=1毫秒),用户无论怎么设置、使用没那么高刷新率的显示器、限帧,有效输入延迟都烂不到哪里去。
当然,在帧生成时间远小于刷新时间的情况下,跑出硬件性能可以支持的最高帧率+关闭垂直同步的输入延迟总是好于使用Reflex锁帧+垂直同步+可变刷新率的理想状态。
职业选手还是得用最高帧率+关闭垂直同步。
小节总结与回顾
以下问题:
当帧生成时间远小于刷新时间并关闭垂直同步,严重的画面撕裂具体是怎么降低等效输入延迟的?
使用可变刷新率+Reflex+垂直同步+锁帧时,和关闭垂直同步+不锁帧的等效输入延迟差距有多大?
已可通过上述示例去理解。
结语吐槽:我单独构造这些例子的时候,自己感觉内容也没多少,构造例子主要是给自己看的、方便理解。没想到写帖子写出来篇幅这么巨大,写的累死我了。
可以预见到帖子读起来肯定也很累,还请大伙见谅了。只能说能看多少就看多少吧,不想看就真的不要勉强看了,或者说只挑自己感兴趣的部分看就行。
故此贴的内容也仅作抛砖引玉和交流之用,难免存在问题和错误,欢迎专业大佬提出意见。
可以帮助理解的问题包括:
为什么CPU瓶颈时的输入延迟比GPU瓶颈时的更低?
预渲染队列设置对GPU瓶颈时的输入延迟有什么影响?
NV Reflex如何在已经最小化预渲染队列的前提下进一步减小GPU瓶颈时的输入延迟?
使用固定刷新率显示器时,关闭垂直同步如何导致画面撕裂?
使用固定刷新率显示器时,开启垂直同步如何避免画面撕裂?
使用固定刷新率显示器时,使用双重缓冲垂直同步和使用三重缓冲垂直同步有什么区别?
使用固定刷新率显示器时,使用不丢弃旧帧的旧式三重缓冲垂直同步和使用丢弃旧帧的新式三重缓冲垂直同步有什么区别?
使用可变刷新率显示器时,和上述使用固定刷新率显示器时的显示器行为有什么不同?
帧生成时间(Frametime)的3种计算方式分别是什么?
使用可变刷新率显示器时,为什么已经把帧率限制到刷新率上限以下,关闭垂直同步时依然会出现画面撕裂?
即使在帧生成时间极其平稳时,Animation Error如何依然会导致画面的微卡顿的发生?
当帧生成时间远小于刷新时间并关闭垂直同步,严重的画面撕裂具体是怎么降低等效输入延迟的?
使用可变刷新率+Reflex+垂直同步+锁帧时,和关闭垂直同步+不锁帧的等效输入延迟差距有多大?
上述问题其实光靠理论分析足以解答,但对看答案的人来说却难以理解,不够直观。
这时候使用具体的例子就能让理解难度下降很多。
我自己纯靠理论去分析时,有时也感觉要长脑子了,把例子画出来之后稍微看多几遍就舒服很多了。
关于例子所使用的模型,GN讲解Animation Error的视频www.油土鳖.com/watch?v=C_RO8bJop8o 中PresentMon 2.0 Metrics的图片给了我灵感:
把CPU、GPU、显示器这三种可以异步运作的组件在帧生成流水线中分开来讨论,从左到右为时间轴,用不同的颜色来表示不同的帧。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-9rfyT3cS16j-zz.png[/img]
在Nvidia介绍Reflex的文章www.nvidia.com/en-us/geforce/news/reflex-low-latency-platform/ 中也有类似的模型出现。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-b1sqT3cS130-c3.png[/img]
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-41vcT3cSy4-f1.png[/img]
RTSS的Reflex latency marker同样
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-fm1uT3cSki-bc.png[/img]
于是模仿PresentMon,构造出这样的帧生成流水线模型:
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-j283T3cS2qm-51.png[/img]
这里的一格时间段表示一段时间,想要表示时间点的话,需要参考某个时间段格子的左边沿或者右边沿。
比如时间段1的左边沿是时间点t=0,右边沿是时间点t=1。
看空的模板没意思,先来看一个最简单的例子:
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-bgomT3cS2qp-3y.png[/img]
例子1. CPU性能和GPU性能均衡,并且完美平稳地进行帧生成。
每种颜色代表不同的帧,比如红色为第1帧,黄色为第2帧,绿色为第3帧,蓝色为第4帧,紫色为第5帧。为了不让颜色过于复杂,从第6帧开始复用颜色,第二次出现的红色为第6帧,以此类推。
当某种颜色在某个时间段出现在代表某个组件的行上,意味着这个组件在这个时间段正在被这个颜色代表的帧所占用。
如果是CPU被占用,意味着CPU正在做这个帧的CPU相关工作;
如果是GPU被占用,意味着GPU正在做这个帧的GPU相关工作;
如果是显示器被占用,意味着显示器正在扫描对应这个帧的Front Buffer,从而把这个帧(或者更准确的说,这个帧在该时间段中对应的某几行像素)显示在屏幕上(对应的某几行像素位置上)。
这个例子里的每一帧的CPU工作量都需要5格时间段来完成,GPU工作量也需要5格时间段来完成。
为了方便和现实的时间概念联系,可以把1格时间段当作1毫秒来看待,那么5格就是5毫秒。
可描述为CPU时间=5毫秒,GPU时间=5毫秒。
从单帧的角度去看,比如只看第1帧(红色),
t=0到t=5,CPU对该帧进行CPU相关工作;
t=5,CPU对该帧的工作完成,该帧进入预渲染队列,此时预渲染队列为空,无需排队,GPU开始对该帧进行GPU相关工作;
t=5到t=10,GPU对该帧进行GPU相关工作;
t=10,GPU对该帧的工作完成,将该帧交换进显示器使用的Front Buffer,显示器开始扫描该帧;
t=10到t=15,显示器持续扫描该帧(这里我们不讨论显示器是否发生刷新,即是否经过了VBI)。
t=15,第2帧(黄色)被GPU完成,Front Buffer对应的帧从第1帧(红色)变为第2帧(黄色),显示器扫描的帧从第1帧变为第2帧。
从单时间段的角度去看,在t=10流水线进入稳态后,每个时间段三种组件(CPU、GPU、显示器)都分别被3种不同的帧占用,没有一刻是空闲的。
这是比较理想的状态,CPU没有拖GPU后腿,GPU也没有拖CPU后腿,大家都能把自己的潜力完全发挥。
接下来考虑其他情况:预渲染队列设置=1时的GPU瓶颈、预渲染队列设置=2时的GPU瓶颈、使用Nvidia Reflex的GPU瓶颈、CPU瓶颈。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-6rm5T3cS2qm-oa.png[/img]
例子2. 预渲染队列设置=1时的GPU瓶颈
GPU瓶颈具体表现为某帧的GPU相关工作时间>某帧的CPU相关工作时间。
帧生成时间取决于GPU时间。
例子使用CPU时间=5毫秒,GPU时间=7毫秒。
预渲染队列设置=1对应NV控制面板低延迟模式="开"。不过即使不设置NV控制面板,许多竞技向游戏的默认设置就是1了。
这是允许CPU和GPU并行工作的最小设置。
如果预渲染队列设置为0,GPU在完成手上的工作之前会持续阻塞CPU,那就是CPU和GPU之间就像是几乎串行工作了。
t=10,CPU已完成第2帧(黄色),GPU却仍未完成第1帧(红色)。由于预渲染队列已满,此时第2帧(黄色)无法进入预渲染队列,CPU被阻塞,CPU无法开始第3帧(绿色)的工作。
一直等到t=12,GPU完成了第1帧(红色)的工作,预渲染队列才释放了空位,让第2帧(黄色)进入,CPU才能开始第3帧(绿色)的工作。
观察进入稳态后的情况,以第2帧(黄色)为例,
假设输入采样发生在CPU工作开始的瞬间,即t=5,
首次出现在显示器是在该帧切换到Front Buffer的瞬间,即t=19,
那么,稳态输入延迟=19-5=14毫秒
例子3. 预渲染队列设置=2时的GPU瓶颈
和预渲染队列设置=1时相比,预渲染队列可以容纳更多的帧。
t=10到t=25的CPU部分体现出了明显的区别。
只要预渲染队列没满,CPU就可以持续把它完成的帧扔给队列,然后开始新的一帧的工作。
从这个角度来说,使用更大的预渲染队列设置提高了偶发CPU瓶颈时的GPU吞吐量:即使CPU偶尔出现瓶颈,较长时间内没有完成某一帧,预渲染队列中CPU以前完成的的帧也能持续喂饱GPU。
但在预渲染队列被填满、进入稳态时,和预渲染队列设置=1时相比会产生多1帧帧生成时间的输入延迟惩罚。
以第6帧(红色)为例,稳态输入延迟=47-26=21毫秒,多了7毫秒,为1帧帧生成时间(这里取决于GPU时间)。
例子4. 使用Nvidia Reflex的GPU瓶颈
Nvidia Reflex通过让游戏引擎推迟CPU开始工作的时间来降低输入延迟。
具体的推迟时间量是动态可变的。Reflex通过使用上一(几)帧的CPU时间和GPU时间来预测下一帧最可能的CPU时间和GPU时间,从而预测出对应的最合适的推迟时间量。
这也可以用基于游戏引擎的动态限帧来理解。
同时正因Reflex这种限帧是基于游戏引擎的,而非基于显卡驱动的,需要显卡(驱动)和游戏引擎通信,Reflex才需要特定游戏官方支持,而不能在任意一个游戏中通用。
在例子中,Reflex使用了2毫秒的推迟时间量,这个设置是最佳的。
少于这个设置,则减小延迟的效果变弱;
大于这个设置,则CPU喂不饱GPU,降低了吞吐量(帧率)。
以第2帧(黄色)为例,稳态输入延迟=19-7=12毫秒,比预渲染队列设置=1的GPU瓶颈的输入延迟低了2毫秒,
并且刚好等于CPU时间+GPU时间,为理论最低延迟。
例子5. CPU瓶颈
CPU瓶颈具体表现为某帧的CPU相关工作时间>某帧的GPU相关工作时间。
帧生成时间取决于CPU时间。
例子使用CPU时间=7毫秒,GPU时间=5毫秒。
在t=12,GPU已完成第1帧(红色),CPU却仍未完成第2帧(黄色)。GPU没有被阻塞,它只是没有东西做。
一直等到t=14,CPU完成了第2帧(黄色)的工作,GPU才能开始该帧的工作。
以第2帧(黄色)为例,稳态输入延迟=19-7=12毫秒,刚好等于CPU时间+GPU时间,为理论最低延迟。
小节总结与回顾
以下问题:
为什么CPU瓶颈时的输入延迟比GPU瓶颈时的更低?
预渲染队列设置对GPU瓶颈时的输入延迟有什么影响?
NV Reflex如何在已经最小化预渲染队列的前提下进一步减小GPU瓶颈时的输入延迟?
已可通过上述示例去理解。
在上述例子中,主要仅展示了CPU和GPU的行为和互相之间的交互,而没有展示显示器是如何刷新的。
在后面的讨论中,需要把这个要素加进来。
这里加入一行用于表示显示器VBI(垂直消隐间隔)的时刻。显示器在VBI时,把扫描指针从右下角移动回左上角,重新开始从左到右、从上到下对屏幕的每一个像素进行扫描和刷新。
两个VBI之间称为一个刷新周期,刷新周期的持续时长称为刷新时间。
垂直同步(Vsync)就是只允许Buffer Swap发生在显示器VBI内,在VBI时显示器没有任何像素被扫描和刷新。
先看看固定刷新率显示器+关闭垂直同步的情况。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-ct8lT3cS2qp-fo.png[/img]
可以根据帧生成时间和刷新时间之间的3种关系(大于、等于、小于),作3种情况分别讨论。
例子6. 固定刷新率显示器,关闭垂直同步,帧生成时间小于刷新时间
例子使用CPU时间=GPU时间=4毫秒,刷新时间=5毫秒。
GPU完成每一帧的时间间隔总是小于VBI的时间间隔,于是两个VBI之间的一个刷新周期便出现了2帧,或者说由2帧构成,产生画面撕裂。
以t=10到t=15的刷新周期为例,
t=10到t=12,显示器所扫描的Front Buffer为第1帧(红色),从而显示器的上边40%画面构成都属于第1帧(红色)。
t=12,GPU完成了第2帧(黄色)并触发Buffer Swap,Front Buffer突然变为第2帧(黄色)。
t=12到t=15,显示器所扫描的Front Buffer为第2帧(黄色),从而显示器的下边60%画面构成都属于第2帧(黄色)。
显示器的上边40%画面构成都属于第1帧(红色),显示器的下边60%画面构成都属于第2帧(黄色),此即为画面撕裂,撕裂位置在于画面从上往下第40%位置。
例子7. 固定刷新率显示器,关闭垂直同步,帧生成时间等于刷新时间,但不同步
例子使用CPU时间=GPU时间=5毫秒,刷新时间=5毫秒。
Buffer Swap发生的时间点和显示器VBI发生的时间点存在2毫秒的时间差。
即使帧生成时间等于刷新时间,所有帧都依然存在画面撕裂,撕裂位置在于画面从上往下第60%位置。
例子8. 固定刷新率显示器,关闭垂直同步,帧生成时间大于刷新时间
例子使用CPU时间=GPU时间=6毫秒,刷新时间=5毫秒。
虽然由于帧生成时间大于刷新时间,在部分刷新周期比如t=25到t=30和t=30到t=35,每个画面都只有一帧,不存在画面撕裂,
但是其他帧依然可能发生画面撕裂,且发生频率并不低。
总的来说,只要使用固定刷新率显示器+关闭垂直同步的搭配,无论帧生成时间和刷新时间之间是怎么样的关系,都有可能发生画面撕裂。
接下来看固定刷新率显示器+开启垂直同步的情况。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-eo6xT3cS2qp-ri.png[/img]
同样的,可以根据帧生成时间和刷新时间之间的3种关系(大于、等于、小于),作3种情况分别讨论。
不过当帧生成时间等于刷新时间,显然稳态图像就只是在例子7的类似的基础上令Buffser Swap和Front Buffer同步,当然,输入延迟会增加。同时这种情况过于特例,没什么意思。故懒得画图出来和分析。
垂直同步分为三种:
双重缓冲垂直同步,有1个Back Buffer供GPU使用。此方法目前使用较少。
老式三重缓冲垂直同步,有2个Back Buffer供GPU使用,不丢弃旧帧,采用类似于先进先出队列的方式让2个Back Buffer排队。此方法为大部分游戏默认的垂直同步方法。
新式三重缓冲垂直同步,有2个Back Buffer供GPU使用,丢弃旧帧,永远只把时间更近的那个Back Buffer进行Buffer Swap。此方法为Nvidia Fast Sync、Windows DWM对窗口化窗口进行composition前所使用的垂直同步方法。
例子9. 固定刷新率显示器,开启双重缓冲垂直同步,帧生成时间大于刷新时间
例子使用CPU时间=GPU时间=6毫秒,刷新时间=5毫秒,预渲染队列设置=1。
t=12,垂直同步禁止Buffer Swap在VBI之外的时刻发生,所以GPU只能一直等VBI。
即使此刻GPU完成了第1帧(红色),也不能开始进行第2帧(黄色)的工作,因为唯一的1个Back Buffer已经被第1帧(红色)占用了。可以说此时GPU被显示器阻塞了。
GPU不能释放掉第1帧(红色),预渲染队列就没有空位,CPU就不能把第2帧(黄色)释放进预渲染队列了。可以说此时CPU被GPU(预渲染队列)阻塞了,但这个阻塞源头说到底还是显示器。
t=15,终于等到了VBI,可以进行Buffer Swap了。都释放掉手头已完成的帧后,GPU和CPU都开始分别着手进行下一帧的工作。
t=20,又是一次VBI,但此时第2帧(黄色)仍未被GPU未完成,没有发生Buffer Swap,在接下来的一整个刷新周期里显示器都会把上一个刷新周期的画面完整重复一遍。换句话说,此时的等效刷新时间10毫秒是显示器硬件刷新时间5毫秒的2倍,等效刷新率减半了。
t=21,GPU完成了第2帧(黄色),但此时距离下一次VBI还很远。于是和t=15时类似,GPU和CPU又被显示器阻塞了。
从t=15流水线进入稳态后,等效刷新时间便一直为10毫秒,是显示器硬件刷新时间的2倍。换句话说,显示器的等效刷新率将一直减半下去,从一个200Hz显示器等效为100Hz显示器。
同时显示器还持续阻塞GPU和CPU,即使CPU时间=GPU时间=6毫秒,等效的帧生成时间也只有10毫秒,等效的帧生成时间已经和CPU时间和GPU时间脱钩。
这就是双重缓冲垂直同步的可怕之处:只要帧生成时间稍大于刷新时间,显示器的等效刷新率和等效帧率就会直接砍半。
这时候便需要给多一个Back Buffer,从双重缓冲垂直同步变为三重缓冲垂直同步,从而缓解这种愚蠢的现象。
例子10. 固定刷新率显示器,开启三重缓冲垂直同步,帧生成时间大于刷新时间
例子使用CPU时间=GPU时间=6毫秒,刷新时间=5毫秒,预渲染队列设置=1。
当帧生成时间大于刷新时间,老式三重缓冲垂直同步和新式三重缓冲垂直同步不会有区别,故例子10同时适用于这两种情况。
观察例子10与例子9产生区别的分歧点t=12。
t=12,GPU完成了第1帧(红色)写入了Back_Buffer_0,虽然没有等到VBI,不能进行Buffer Swap,但此时仍有Back_Buffer_1可供第2帧(黄色)使用。
所以GPU没有被阻塞,CPU也没有被阻塞,可以持续工作。
在之后的VBI时刻t=15, 20, 25, 30, 40, 45,都有新的Back_Buffer可供Buffer Swap,显示器可以保持每个刷新周期显示不同的画面,等效刷新率跑满。
但在VBI时刻t=35,显示器正在显示第4帧(蓝色),而第5帧(紫色)未被GPU完成,因此只能在重复显示画面,t=30到t=40时间段的等效刷新率减半。
等效帧率观感就像是200FPS-200FPS-200FPS-100FPS-200FPS-200FPS一样。
例子11. 固定刷新率显示器,开启双重缓冲垂直同步,帧生成时间小于刷新时间
例子使用CPU时间=GPU时间=4毫秒,刷新时间=5毫秒,预渲染队列设置=1
t=8,同例子9,GPU和CPU被显示器阻塞,两者都停止工作。
t=10,进行Buffer Swap,CPU和GPU恢复工作。
进入稳态后,每个刷新周期都有不同的帧,等效刷新率总能跑满,也因此显示器总是会阻塞GPU和CPU,防止他们做任何多余的工作,即使CPU时间和GPU时间都小于刷新时间。
通过不断的阻塞GPU和CPU,使等效帧生成时间=刷新时间=5毫秒,等效帧生成时间和CPU时间和GPU时间脱钩。
这便是“开启垂直同步会把帧率锁在显示器刷新率”。
以第3帧(绿色)为例,
稳态输入延迟=20-10=10毫秒
例子12. 固定刷新率显示器,开启老式三重缓冲垂直同步,帧生成时间小于刷新时间
例子使用CPU时间=GPU时间=4 刷新时间=5 预渲染队列设置=1
观察例子12与例子11产生区别的分歧点t=8。
完全类似于例子10和例子9之间的区别,便不再赘述。
可以发现例子12与例子11的显示器部分是一模一样的,主要区别在于CPU部分,每帧的输入采样时间点提早了。换句话说,输入延迟变大了。
以第6帧(红色)为例,
稳态输入延迟=35-20=15毫秒,多了5毫秒,1个刷新周期(同时也是等效帧生成时间)。
Frame Buffer的先进先出队列对输入延迟的影响完全类似于例子2和例子3的预渲染队列对输入延迟的影响,只要有帧(帧缓存)在排队,就会产生额外的输入延迟。
例子13. 固定刷新率显示器,开启新式三重缓冲垂直同步,帧生成时间小于刷新时间
例子使用CPU时间=GPU时间=4 刷新时间=5 预渲染队列设置=1
观察例子13和例子12产生区别的分歧点t=20。
t=20,GPU已经完成第4帧(蓝色),此时的另一个Back Buffer为第3帧(绿色)。
新式三重缓冲垂直同步丢弃了第3帧(绿色),直接使用第4帧(蓝色)进行Buffer Swap。
这里的区别表现了新式三重缓冲垂直同步的部分输入延迟优势,因为这里使用的帧是更新的。
单看t=20时的瞬时输入延迟,例子13的为20-12=8毫秒,例子12的为20-8=12毫秒。
同时显示器也不会阻塞GPU和CPU了,他们能跑多快就跑多快,能产出多少帧就产出多少帧,跑出来多余的帧可以直接扔掉(旧Frame Buffer被新的帧直接覆盖掉)。
这便是“Nvidia Fast Sync和游戏窗口化不会把帧率锁在显示器刷新率”。
t=20,例子13的显示器使用第4帧,例子12的显示器使用第3帧。
t=40,例子13的显示器使用第9帧,例子12的显示器使用第7帧。
小节总结与回顾
以下问题:
使用固定刷新率显示器时,关闭垂直同步如何导致画面撕裂?
使用固定刷新率显示器时,开启垂直同步如何避免画面撕裂?
使用固定刷新率显示器时,使用双重缓冲垂直同步和使用三重缓冲垂直同步有什么区别?
使用固定刷新率显示器时,使用不丢弃旧帧的旧式三重缓冲垂直同步和使用丢弃旧帧的新式三重缓冲垂直同步有什么区别?
已可通过上述示例去理解。
看完了固定刷新率显示器,接下来看看可变刷新率显示器的情况。
这里默认了可变刷新率显示器会配合显卡驱动开启了可变刷新率功能(GSync、FreeSync等),否则其表现与固定刷新率显示器无异。
可变刷新率显示器如其名,拥有可变的刷新时间(可变的刷新率)。
刷新时间(刷新率)会有一个显示器厂商对该型号所规定的范围或者说上限和下限,该范围可以使用CRU(Custom Resolution utility)通过读取显示器的EDID信息获得。
比如我使用的三星Neo G8的刷新率范围为68Hz-240Hz,对应刷新时间范围为4.17毫秒-14.71毫秒。
那么三星Neo G8的刷新率下限是68Hz,刷新率上限是240Hz,对应刷新时间下限是4.17毫秒,刷新时间上限是14.71毫秒。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-1yhiToS7m-m3.png[/img]
在可变刷新率显示器在某种场景下想要使用超过刷新时间上限范围的刷新时间时,可以使用低帧率补偿LFC(Low Framerate Compensation)来实现,
因此刷新时间的上限(对应刷新率的下限)并不是那么重要,只需要注意刷新时间的下限(对应刷新率的上限)即可。
只要在显示器支持范围内,显示器就可以自由调整VBI的位置,主动、尽量让VBI和Buffer Swap进行对齐。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-djdkT3cS2qx-a7.png[/img]
例子14. 可变刷新率显示器,帧生成时间大于刷新时间下限
例子使用CPU时间=GPU时间=6毫秒,刷新时间下限=5毫秒(刷新时间>=5毫秒)。
在该例子中开关垂直同步没有区别,三种垂直同步之间也没有区别。
显然,所有Buffer Swap的间隔都为6毫秒,大于5毫秒的刷新时间下限,显示器有能力自由调整VBI的位置并将VBI和Buffer Swap对齐。
比如,如果这里使用的是刷新时间=5毫秒的固定刷新率显示器,第1个VBI在t=12,第2个VBI则必须为t=17,
于是在t=18时第2帧(黄色)被GPU完成并发生Buffer Swap时,就出现了画面撕裂了。
而可变刷新率显示器就可以把第2个VBI推迟1毫秒,从t=17变为t=18,刚好和Buffer Swap的时间点对齐,避免了撕裂。
除了没有任何画面撕裂以外,输入延迟也全部是理论最低值(CPU时间+GPU时间)。
例子15. 可变刷新率显示器,关闭垂直同步,帧生成时间小于刷新时间下限(帧率大于刷新率上限)
例子使用CPU时间=GPU时间=6毫秒,刷新时间下限=5毫秒(刷新时间>=5毫秒)。
t=8为第1次VBI,而t=12时发生了Buffer Swap,假设显示器有能力可以把VBI调整到t=12,就可以对齐并避免撕裂。
然而这里显示器的刷新时间下限即VBI之间的间隔的下限为5毫秒,显示器没有能力做到这么小的刷新时间,拼劲全力才能勉勉强强在t=13时进行VBI。
此时动态刷新率显示器的行为和固定刷新率显示器没有任何区别,刷新时间恒为下限的5毫秒,参考例子6。
这便是“帧率高于刷新率时,可变刷新率失效”。
类似的,开启(三种中的某种)垂直同步时,动态刷新率显示器的行为和固定刷新率显示器也没有任何区别,刷新时间恒为下限的5毫秒,参考例子11、例子12、例子13。
于是我也就懒得弄开启垂直同步的例子了。
在开始下一步讨论之前,有必要先介绍帧生成时间(Frametime)的3种计算方式。
为了方便,我照搬RTSS对前2种计算方式的叫法:Frame Start和Frame Presentation。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-bwn5T3cSi1-qc.png[/img]
第3种计算方式的叫法就直接取其字面意思,叫Buffer Swap。
3种计算方式分别使用了同1帧的不同时间点。
对使用了某种计算方式的帧生成时间来说,帧生成时间为两个相邻时间点的间隔。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-6xunT3cS2qr-k7.png[/img]
例子16. 展示帧生成时间的3种计算方式。
例子使用CPU时间=GPU时间=5毫秒。
Frame Start时间点在于每帧CPU工作部分的开头。
第1帧(红色)的Frame Start为t=0;
第2帧(黄色)的Frame Start为t=5;
第3帧(绿色)的Frame Start为t=10。
基于Frame Start计算的第1帧(红色)的帧生成时间=5-0=5毫秒。
RTSS(和使用它的Afterburner)默认使用这种帧生成时间计算方式。
RTSS锁帧中的async模式(默认的模式)和back edge sync模式基于Frame Start。
Frame Presentation时间点在于每帧CPU工作的结尾、GPU工作部分的开头。
第1帧(红色)的Frame Presentation为t=5;
第2帧(黄色)的Frame Presentation为t=10;
第3帧(绿色)的Frame Presentation为t=15。
基于Frame Presentation计算的第1帧(红色)的帧生成时间=10-5=5毫秒。
PresentMon和基于PresentMon的软件如游戏加加、CapFrameX、Nvidia FrameView使用这种帧生成时间计算方式。
RTSS可以手动切换为这种帧生成时间计算方式。
Buffer Swap时间点就是其字面意思。
第1帧(红色)的Buffer Swap为t=10;
第2帧(黄色)的Buffer Swap为t=15;
第3帧(绿色)的Buffer Swap为t=20。
基于Buffer Swap计算的第1帧(红色)的帧生成时间=15-10=5毫秒。
目前我只知道NvidiaProfileInspector调出的Flip FPS图像基于Buffer Swap。
这里的Flip和Swap指的就是同一样东西。
Flip FPS图像显示的FPS都是瞬时值(原始值),而瞬时FPS的倒数即为帧生成时间。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-a1ryT3cS2j4-f1.png[/img]
上面3种计算方式的帧生成时间都相等,只是因为CPU时间和GPU时间非常平稳一致(consistent)。
一旦出现波动,情况就有所变化了。
例子17. 帧生成时间的三种计算方式不对齐示例。
例子使用第1帧(红色)的CPU时间=4毫秒,GPU时间=3毫秒。
第2帧(黄色)的CPU时间=5毫秒,GPU时间=4毫秒。
第3帧(绿色)的CPU时间=6毫秒,GPU时间=5毫秒。
显然这3帧都是CPU瓶颈,且3帧的工作量各不相同、逐渐增大。
基于Frame Start计算的第1帧(红色)的帧生成时间=4-0=4毫秒;
基于Frame Presentation计算的第1帧(红色)的帧生成时间=9-4=5毫秒;
基于Buffer Swap计算的第1帧(红色)的帧生成时间=13-7=6毫秒。
3种计算方式得出了3种不同的帧生成时间结果。
例子18. 帧生成时间的三种计算方式不对齐示例,RTSS async模式限帧(限制Frame Start最小间隔)
例子18使用的3个帧的参数和例子17相同。
但加入了RTSS的约束条件:帧生成时间_start_n>=7毫秒,即基于Frame Start计算的任意帧生成时间必须大于等于7毫秒。
从实现的角度来说,假如前一帧的CPU时间较短,前一帧的CPU工作将很快完成,RTSS若不干涉,后一帧的FrameStart将立刻到来,就会导致上述约束条件不被满足。
所以RTSS async模式限帧就会推迟后一帧的Frame Start时间点,从而令限制条件得到满足。
例子18和例子17相比,第2帧(黄色)的Frame Start从t=4推迟到t=7,第3帧(绿色)的Frame Start从t=9推迟到t=14。
基于Frame Start计算的第1帧(红色)的帧生成时间=7-0=7毫秒;
基于Frame Start计算的第2帧(黄色)的帧生成时间=14-7=7毫秒;
基于Frame Start计算的第3帧(绿色)的帧生成时间=21-14=7毫秒。
RTSS的限帧干涉让约束条件得到满足,基于Frame Start计算的帧生成时间是完美一致的。
就像这样一条笔直直线的帧生成时间曲线。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-9ljgToS74-4d.png[/img]
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-ixb1T1kSbx-27.png[/img]
但是其他计算方式的帧生成时间呢?
基于Frame Presentation计算的第1帧(红色)的帧生成时间=12-4=8毫秒;
基于Frame Presentation计算的第2帧(黄色)的帧生成时间=20-12=8毫秒。
基于Frame Presentation计算的第3帧(绿色)的帧生成时间由于没有第4帧和对应时间点而无法计算(某种意义上说,只要一直没有第4帧,这里可以取正无穷)。
基于Buffer Swap计算的第1帧(红色)的帧生成时间=16-7=9毫秒;
基于Buffer Swap计算的第2帧(黄色)的帧生成时间=25-16=9毫秒。
基于Buffer Swap计算的第3帧(绿色)的帧生成时间由于没有第4帧和对应时间点而无法计算(某种意义上说,只要一直没有第4帧,这里可以取正无穷)。
其他计算方式得到的帧生成时间已经和基于的Frame Start完全不一样了。
就连Frame Presentation和Buffer Swap互相之间都不一样。
这样简单的例子已经展现了一个问题:三种不同的时间点互相之间的偏移量互不相同、难以控制。
通过单独控制其中一个时间点的“限帧”,真的有绝对的、强的约束力吗?
回到之前可变刷新率显示器的讨论正题。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-876eT3cS2qs-aa.png[/img]
例子19. 可变刷新率显示器,关闭垂直同步,RTSS async模式限帧到刷新率上限以下(帧生成时间_start_n>刷新时间下限)
例子使用第1帧(红色)的CPU时间=5毫秒,GPU时间=5毫秒。
第2帧(黄色)的CPU时间=6毫秒,GPU时间=5毫秒。
第3帧(绿色)的CPU时间=4毫秒,GPU时间=3毫秒。
第4帧(蓝色)的CPU时间=6毫秒,GPU时间=5毫秒。
第5帧(紫色)的CPU时间=5毫秒,GPU时间=6毫秒。
刷新时间下限=5毫秒(刷新时间>=5毫秒)。
存在RTSS的约束条件:帧生成时间_start_n>=7毫秒,即基于Frame Start计算的任意帧生成时间必须大于等于7毫秒。
一个刷新率上限200Hz的显示器(一个刷新周期下限5毫秒的显示器),使用RTSS把帧率限制到了至多1000/7=142.8FPS(使用RTSS把帧生成时间限制到了至少7毫秒),
帧率小于刷新率上限(帧生成时间大于刷新时间下限),按理说就像例子14那样,应当完美满足了动态刷新率生效的条件,不存在撕裂。
实际却在t=21出现了画面撕裂,这是为什么?
显然所有5个帧的基于Frame Start计算的帧生成时间都是7毫秒,然而,
t=10,第1帧(红色)触发Buffer Swap,第1次VBI对齐;
t=18,第2帧(黄色)触发Buffer Swap,第2次VBI与之对齐;
t=21,第3帧(绿色)触发Buffer Swap,此时距上一次VBI仅过去3毫秒,小于5毫秒的刷新时间下限,无法对齐VBI,产生画面撕裂。
t=23,终于经过了5毫秒的刷新时间下限,显示器进行VBI。
基于Buffer Swap计算的第1帧(红色)的帧生成时间=18-10=8毫秒>5毫秒,没有问题;
基于Buffer Swap计算的第2帧(黄色)的帧生成时间=21-18=3毫秒<5毫秒,罪魁祸首就在于此。
从直观上看,虽然第2帧(黄色)和第3帧(绿色)的限帧后的Frame Start的帧生成时间相同,都是7毫秒,两帧之间的Frame Start间隔也是7毫秒
但第2帧(黄色)的工作量(CPU时间+GPU时间)较大,第3帧(绿色)的工作量(CPU时间+GPU时间)较小。
这就导致了第2帧(黄色)的Buffer Swap时间点是相比其Frame Start时间点的偏移量更大,即更靠后的;
第3帧(绿色)的Buffer Swap时间点是相比其Frame Start时间点的偏移量更小,即更靠前的;
一个靠后,一个靠前,俩Buffer Swap自然挨得很紧了,紧到间隔小于刷新时间下限,自然撕裂。
要说根本原因,就是两帧的工作量(CPU时间+GPU时间)一大一小。
这种情况下不仅基于Buffer Swap计算的帧生成时间是不一致的,连基于Frame Presentation计算的帧生成时间也是不一致的。
blurbusters的经典G-SYNC 101文章[url]https://blurbusters.com/gsync/gsync101-input-lag-tests-and-settings/2/[/url] 中有这种情况的现实例子
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-5pm6T3cSw6-u6.png[/img]
在现实中,连续的游戏画面具有关联性,相邻的帧的工作量一般不会差距太大。
但只要出现了两连续帧工作量的稍微的“先大后小”,就会像上图那样,在画面靠底部的位置出现撕裂。
绝大部分用户的注意力不会放在画面底部,所以较少能注意到这种撕裂现象。
那么遇到画面撕裂应该干什么?小学生都知道,当然是开启垂直同步。
例子20. 可变刷新率显示器,开启垂直同步,RTSS async模式限帧到刷新率上限以下(帧生成时间_start_n>刷新时间下限)
例子20使用的各项参数和例子19相同,仅把垂直同步从关闭变为开启。
观察例子20与例子19的唯一区别t=21到t=23。
t=21,第3帧(绿色)本来会触发Buffer Swap,现在被开启的垂直同步按了回去,必须等待到VBI才能进行。
t=21到t=23,Front Buffer依然是第2帧(黄色),t=18到t=23的刷新周期内为该单独完整帧,没有画面撕裂;同时第3帧(绿色)在Back Buffer躺着不动。
t=23,等到了VBI,进行Buffer Swap,第3帧(绿色)正常成为Front Buffer,被显示器扫描并显示出来。
其他部分开、关垂直同步没有任何区别,显然输入延迟也没有任何区别。
第3帧(绿色)开启垂直同步后,表面上输入延迟从21-14=7毫秒变为了23-14=9毫秒,增加了2毫秒。
但这2毫秒的区别,刚好就是画面撕裂其本身,即提前2毫秒在画面的靠底部的位置(例子中为画面的下半40%部分)撕裂地显示出来。
用户真的需要这个形态的“更低输入延迟”吗?我认为是不需要的。
总的来说,在可变刷新率显示器+限帧到刷新率上限以下的条件下,垂直同步对输入延迟造成的影响可谓微乎其微。
这个“垂直同步”简直就像和使用固定刷新率显示器时的那个“垂直同步”完全不是同一个东西。
因此还是那个结论不变:开启可变刷新率(GSync)+限帧到刷新率上限以下+开启垂直同步,就是能最佳发挥可变刷新率的输入延迟优势和无画面撕裂优势的设置组合。
通过上面这些例子,Animation Error,一种无法被帧生成时间数据(和基于帧生成时间计算得到的各种类型的low帧数据)所表达出来的微卡顿也很容易理解了。
为了方便理解,假设例子20展现的是一个物体的匀速直线运动过程,该物体应当每1毫秒移动1个像素,即速度为1像素/毫秒。
第1帧(红色)的Frame Start为t=0,此帧里该物体已移动距离为0像素,即位于初始位置。
第2帧(黄色)的Frame Start为t=7,此帧里该物体已移动距离为7像素。
第3帧(绿色)的Frame Start为t=14,此帧里该物体已移动距离为14像素。
第4帧(蓝色)的Frame Start为t=21,此帧里该物体已移动距离为21像素。
第5帧(紫色)的Frame Start为t=28,此帧里该物体已移动距离为28像素。
第1帧(红色)的Buffer Swap为t=10,此时用户看到画面中物体已移动距离为0像素。
第2帧(黄色)的Buffer Swap为t=18,此时距离上幅画面经过了8毫秒,用户看到画面中物体已移动距离为7像素。
第3帧(绿色)的Buffer Swap为t=23,此时距离上幅画面经过了5毫秒,用户看到画面中物体已移动距离为14像素。
第4帧(蓝色)的Buffer Swap为t=32,此时距离上幅画面经过了9毫秒,用户看到画面中物体已移动距离为21像素。
第5帧(紫色)的Buffer Swap为t=39,此时距离上幅画面经过了7毫秒,用户看到画面中物体已移动距离为28像素。
从用户直接看显示器画面的实际观感出发,这个物体最快的时候用5毫秒走出7像素距离,速度为1.4像素/毫秒;最慢的时候用9毫秒走出7像素距离,速度为0.78像素/毫秒。
用户会觉得这个物体根本不是在匀速直线运动,它的速度和运动轨迹是一直抖动(jittering)的。
这就是Animation Error微卡顿的典型表现之一。
但说实话,既然根源来自帧和帧之间CPU时间和GPU时间的不一致,Animation Error在目前并没有什么很好的解决办法。
CPU时间取决于CPU工作量和CPU实时性能,而GPU时间取决于GPU工作量和GPU实时性能。
每帧的工作量我们用户控制不了,用户最多能做到的也只有尽量让硬件的性能释放(频率)尽量保持平稳,而这对于台式电脑来说应当是理所当然的。
小节总结与回顾
以下问题:
使用可变刷新率显示器时,和上述使用固定刷新率显示器时的显示器行为有什么不同?
帧生成时间(Frametime)的3种计算方式分别是什么?
使用可变刷新率显示器时,为什么已经把帧率限制到刷新率上限以下,关闭垂直同步时依然会出现画面撕裂?
即使在帧生成时间极其平稳时,Animation Error如何依然会导致画面的微卡顿的发生?
已可通过上述示例去理解。
最后讨论一下关闭垂直同步,帧生成时间远小于刷新时间(帧率远大于刷新率)对有效输入延迟的影响。
能达成这种条件的,一般是追求最低输入延迟的,同时也确实能跑到极高帧率的竞技性网游,比如CS2和瓦洛兰特。
类似于前面所提到的,这种情况下,更低的输入延迟就是画面撕裂其本身。
而画面撕裂的位置对这类游戏来说是一个不可忽视的变量:这类游戏一般只有画面正中央部分,比如说第一人称射击游戏的准星的位置才是有效的位置。
只有画面正中央部分所对应的帧的输入延迟,才算是有效输入延迟;这类游戏的玩家并不会太关心画面上方和下方的信息和输入延迟。
因此,需要考虑到底是哪一帧更可能处于画面正中央部分,对应刷新周期的正中间。
[img]https://img.nga.178.com/attachments/mon_202409/24/9aQ2u-gxsmT3cS2qr-qo.png[/img]
例子21. 帧生成时间=1*刷新时间,即帧率=刷新率
例子使用CPU时间=GPU时间=刷新时间=6毫秒。相当于在165Hz显示器上跑165FPS。
为了贴合实际情况,令VBI和Buffer Swap轻微不同步。
在t=13到t=19的刷新周期,画面中央位置为t=16,此时Front Buffer为第1帧(红色),而第1帧的输入采样时间为t=0
有效输入延迟=16-0=16毫秒
例子22. 帧生成时间=1/2*刷新时间,即帧率=2*刷新率
例子使用CPU时间=GPU时间=3毫秒,刷新时间=6毫秒。相当于在165Hz显示器上跑330FPS。
有效输入延迟=10-3=7毫秒
例子23. 帧生成时间=1/3*刷新时间,即帧率=3*刷新率
例子使用CPU时间=GPU时间=2毫秒,刷新时间=6毫秒。相当于在165Hz显示器上跑500FPS。
有效输入延迟=10-(4+6)/2=5毫秒
例子24. 帧生成时间=1/6*刷新时间,即帧率=6*刷新率
例子使用CPU时间=GPU时间=1毫秒,刷新时间=6毫秒。相当于在165Hz显示器上跑1000FPS。
有效输入延迟=10-(7+8)/2=2.5毫秒
看下来感觉帧率跑到2倍刷新率和跑到1倍刷新率相比,降低输入延迟的收益是比较明显的。但这个收益是需要整整2倍的硬件性能才能得到的,这个代价已经不小了。
要是继续往上的话性价比就更小了。
例子25. 硬件性能条件同上,使用reflex锁帧+垂直同步+可变刷新率的理想状态。
例子使用CPU时间=GPU时间=1毫秒,刷新时间=6毫秒。相当于硬件性能能跑1000FPS,但在165Hz显示器上reflex锁帧到165FPS。
有效输入延迟=11-6=5毫秒
例子25和例子24相比,拥有相同的硬件性能条件,以有效输入延迟劣化2.5毫秒为代价,换来减少六分之五硬件工作量的硬件功耗降低,以及无画面撕裂的体验。
我从一个业余玩家的角度来看,觉得这种交换似乎也不是不能接受。。。
只能说只要硬件性能足够强(像这里的CPU时间=GPU时间=1毫秒),用户无论怎么设置、使用没那么高刷新率的显示器、限帧,有效输入延迟都烂不到哪里去。
当然,在帧生成时间远小于刷新时间的情况下,跑出硬件性能可以支持的最高帧率+关闭垂直同步的输入延迟总是好于使用Reflex锁帧+垂直同步+可变刷新率的理想状态。
职业选手还是得用最高帧率+关闭垂直同步。
小节总结与回顾
以下问题:
当帧生成时间远小于刷新时间并关闭垂直同步,严重的画面撕裂具体是怎么降低等效输入延迟的?
使用可变刷新率+Reflex+垂直同步+锁帧时,和关闭垂直同步+不锁帧的等效输入延迟差距有多大?
已可通过上述示例去理解。
结语吐槽:我单独构造这些例子的时候,自己感觉内容也没多少,构造例子主要是给自己看的、方便理解。没想到写帖子写出来篇幅这么巨大,写的累死我了。
可以预见到帖子读起来肯定也很累,还请大伙见谅了。只能说能看多少就看多少吧,不想看就真的不要勉强看了,或者说只挑自己感兴趣的部分看就行。