图形渲染上屏(二)-帧缓冲的供给--交换链与垂直同步(像素如何进入帧缓冲)
发布时间:2026/9/14 22:08:09
分类:文化教育
浏览:1234
-帧缓冲的供给--交换链与垂直同步(像素如何进入帧缓冲))
在第一篇中我们从屏幕出发弄清楚了帧缓冲里的画面是如何被逐行扫描显示到物理屏幕上的。我们也提到了两个关键问题单缓冲会导致画面撕裂——GPU在写帧缓冲的同时显示控制器可能在读帧缓冲。双缓冲和垂直同步是解决撕裂的思路——但具体怎么实现的现在我们来把这两个问题讲清楚。从一个生活场景说起想象你在一家餐厅吃饭桌上有一个转盘。厨师在转盘的一侧往盘子里放菜写入新画面你在另一侧从盘子里夹菜读取画面。如果厨师和你同时操作同一个盘子会发生什么你刚夹起一块红烧肉厨师突然把盘子转了一下把红烧肉换成了一盘青菜。你看到的是“半盘红烧肉 半盘青菜”——两道菜混在了一起完全不是一道完整的菜。这就是画面撕裂的本质写入和读取发生在同一个缓冲区上导致一帧画面中混入了两帧的内容。那怎么解决很简单准备两个盘子。厨师往A盘放菜的时候你从B盘夹菜。等厨师放完了把A盘转给你再从B盘开始放下一道菜。两个盘子交替使用互不干扰。这就是双缓冲。双缓冲两个盘子交替用在图形渲染中双缓冲的机制是这样的前缓冲Front Buffer正在被显示控制器读取用于屏幕显示。后缓冲Back BufferGPU正在渲染的新一帧画面。GPU总是往“后缓冲”里写显示控制器总是从“前缓冲”里读。当GPU渲染完成后两块缓冲的角色进行交换——后缓冲变成新的前缓冲前缓冲变成新的后缓冲。这个交换动作叫做Present或Swap。这样一来GPU和显示控制器操作的是不同的缓冲区撕裂问题就解决了。但交换本身也有时机问题。交换的时机为什么还需要垂直同步假设GPU渲染完一帧立刻交换了前后缓冲。此时显示控制器可能正扫描到屏幕中间。屏幕上半部分已经扫描完显示的是旧的前缓冲上一帧。屏幕下半部分即将扫描读到的是新的前缓冲新一帧。结果依然是撕裂——只是撕裂的位置从“缓冲区内部”转移到了“交换的瞬间”。所以交换必须发生在显示控制器不读取前缓冲的时候。这个时机就是第一篇中提到的垂直消隐期Vertical Blanking Interval。显示控制器扫描完一帧的最后一行后会有一个短暂的休息时间然后才从第一行开始扫描下一帧。在这段休息时间里交换前后缓冲是安全的——因为显示控制器此刻不读任何缓冲区。垂直同步VSync的作用就是强制交换发生在垂直消隐期。开启VSync后GPU渲染完一帧不能立刻交换必须等待下一个垂直消隐期到来。这就避免了撕裂。交换链不只是两个盘子在实际的图形系统中双缓冲通常被扩展为交换链Swap Chain。交换链是一个包含多个缓冲区的队列。最常见的是双缓冲交换链两个缓冲区也有三缓冲交换链三个缓冲区。交换链的工作方式显示控制器始终从当前的前缓冲读取。GPU始终向当前的后缓冲写入。当GPU完成渲染后请求交换。交换链把后缓冲“推”到队列前面成为新的前缓冲原来的前缓冲则回到队列尾部成为新的后缓冲。一个常见的误解交换前后缓冲并不是把两块内存的数据互相拷贝。它只是交换了两个缓冲区的指针/索引。原来的后缓冲变成前缓冲原来的前缓冲变成后缓冲。这个操作非常快几乎是瞬间完成的。垂直同步的三种模式在实际应用中VSync并不是简单地“开”或“关”。根据交换时机的不同策略可以分成三种模式1. VSync关闭VSync OffGPU渲染完一帧立刻交换不等垂直消隐期。优点GPU可以全速渲染帧率不受屏幕刷新率限制输入延迟最低。缺点画面撕裂。2. VSync开启VSync OnGPU渲染完一帧后必须等待下一个垂直消隐期才能交换。优点画面不撕裂。缺点如果GPU渲染速度跟不上屏幕刷新率会出现卡顿掉帧如果GPU渲染速度远超屏幕刷新率GPU会频繁等待浪费性能。3. 自适应VSyncAdaptive VSync结合前两者的优点当GPU渲染速度高于屏幕刷新率时开启VSync避免撕裂。当GPU渲染速度低于屏幕刷新率时关闭VSync避免卡顿。自适应VSync在NVIDIA和AMD的驱动中都有支持是很多游戏默认推荐的模式。三重缓冲用空间换时间双缓冲有一个固有的问题GPU和显示控制器必须交替使用两个缓冲区导致两者无法完全并行工作。具体来说显示控制器正在读前缓冲第N帧。GPU正在写后缓冲第N1帧。GPU写完了想交换但显示控制器还在读前缓冲不能交换。GPU只能等待。显示控制器读完前缓冲垂直消隐期到来交换发生。现在GPU可以写新的后缓冲了但显示控制器立刻开始读新的前缓冲——GPU和显示控制器又开始竞争。三重缓冲通过增加一个缓冲区来解决这个问题前缓冲显示控制器正在读。后缓冲1GPU正在写。后缓冲2已经渲染完成、等待交换的帧。这样GPU渲染完一帧后可以先把它放入“等待队列”然后立刻开始渲染下一帧而不必等待显示控制器。显示控制器在垂直消隐期从等待队列中取最新的一帧显示。缓冲模式缓冲区数量优点缺点单缓冲1最简单必然撕裂双缓冲2解决撕裂GPU和显示控制器互相等待三重缓冲3GPU和显示控制器更好并行增加一帧延迟多占一份显存三重缓冲通常与VSync配合使用。在VSync开启的情况下三重缓冲可以显著减少GPU的等待时间提高帧率稳定性。但它也会增加一帧的输入延迟。根据上述内容我们可以推断如果GPU速度更快的话我们应该设置为Triple Buffer模式。这也是Unity引擎的默认设置三缓冲机制对于交互式目的应用如游戏来说是更优越的方案。CPU和GPU之间并不是完全解耦的在第一篇中我们提到过CPU和GPU大部分时间并行工作但每一帧结束时必须同步一次。现在我们可以把这个同步点讲得更清楚了。当CPU完成一帧的渲染命令提交后会调用Present请求交换。此时如果VSync开启Present会阻塞CPU直到下一个垂直消隐期到来。如果交换链的等待队列满了比如三重缓冲中已有两帧在等待Present也会阻塞。这个等待过程在Unity Profiler里对应的就是Gfx.WaitForPresent或Graphics.PresentAndSync。在引擎的帧时间线中这个等待会显著拉长一帧的总耗时。所以CPU和GPU的关系更准确的描述是大部分时间并行工作但每一帧结束时必须同步一次。高于刷新率的帧数都是无效帧数吗经常听到一种说法“屏幕只有60HzGPU渲染200帧也没用都是无效帧。”这个说法只说对了一半。如果开启VSyncGPU渲染速度超过刷新率时多出来的帧确实会被丢弃或等待帧率会被限制在刷新率的整数倍如60、30、20。如果关闭VSyncGPU渲染速度可以远超刷新率。虽然显示器只能显示60帧但更高的输入帧率能显著降低输入延迟因为每一帧都基于最新的输入采样。这对于竞技类游戏至关重要。所以高于刷新率的帧数不是无效的它换来的是更低的输入延迟和更跟手的操作体验。只有在开启VSync时超过刷新率的帧才会被丢弃以避免撕裂。小结这一篇我们从“两个盘子”的类比出发完整梳理了交换链与垂直同步的机制单缓冲会导致撕裂GPU写和显示控制器读发生在同一块缓冲区上。双缓冲解决撕裂前缓冲用于显示后缓冲用于渲染交替使用。交换必须在垂直消隐期进行否则撕裂依然会发生。垂直同步强制交换时机保证交换发生在垂直消隐期避免撕裂。交换链是双缓冲的工程实现通过指针交换而非数据拷贝高效管理多个缓冲区。三重缓冲用空间换时间增加一个缓冲区让GPU和显示控制器更好地并行工作。VSync有三种模式关闭、开启、自适应各有取舍。高于刷新率的帧数不是无效的关闭VSync时高帧率能降低输入延迟。至此我们已经知道了“帧缓冲里的画面是怎么上屏的”也知道了“交换链和垂直同步如何协调GPU渲染与屏幕刷新”。但还有一个问题没有回答帧缓冲里的那些像素最初是怎么被算出来的这就引出了下一篇的内容GPU渲染管线是如何工作的我们会深入讲解顶点处理、光栅化、片元着色、输出合并等阶段。顶点着色器和片元着色器分别在做什么光栅化到底发生了什么看看GPU是如何把一堆三维数据变成帧缓冲里整整齐齐的像素的。