从根本上讲,渲染的核心目标是使监视器上显示的每一帧呈现单个连贯的图像。为了达到此目的,已经或已经使用了几种不同的策略。
在下面,我提到“ vsync”。Vsync是监视器开始绘制新屏幕图像的时刻;在传统的CRT屏幕上,这是“ vblank”开始的地方,扫描线会暂时停止绘制并移回监视器顶部。对于许多框架连贯性的方法,这一刻非常重要。
当屏幕在同一帧内从两个不同的图像进行渲染时,我们称之为“撕裂”。例如,如果我绘制了两个屏幕图像,它们打算一个接一个地显示,但是监视器却显示了第一帧的上半部分和第二帧的下半部分,那就是“撕裂”。这是由于在绘制监视器时而不是在vblank期间更改了监视器读取的数据。(在现代程序中,这通常是由于用户在驱动程序设置中禁用了等待vsync而发生的)
零缓冲
在最旧的硬件上,通常没有足够的内存来保存全屏图像,因此,在监视器正在绘制该线的过程中,您需要为每个扫描线分别指定颜色,而不是绘制屏幕图像。例如,在Atari 2600上,您只有76个机器指令周期来指定在电视机开始实际绘制扫描线之前,扫描线的每个像素采用哪种颜色。然后,您有76个指令周期来提供下一条扫描线的内容,依此类推。
单缓冲器
在“单缓冲区”上下文中绘制时,您将直接绘制到监视器正在读取的VRAM中。在这种方法中,您“使用扫描线”。通常的想法是,当扫描线开始在屏幕顶部绘制上一帧的内容时,您将在其后面绘制VRAM 。因此,当扫描线绘制最后一帧的屏幕图像时,您将在扫描线后面绘制下一帧。
通常,您要通过再次走过来并超越所绘制的像素来完成在扫描线“重叠”之前完成下一帧图像的绘制,并且永远不要超越扫描线,否则您的新框架可能会吸引到原先的框架中。
因此,单缓冲区渲染通常通过从上到下以及从左到右绘制扫描线来工作。如果以其他顺序绘制,则扫描线可能会再次出现,并发现尚未绘制的“下一个”图像的某些位。
请注意,在现代操作系统中,您通常永远没有机会绘制单缓冲区,尽管这在30年前是相当普遍的。(天哪,我现在感觉很老-这是我刚开始游戏开发时所做的事情)
双缓冲
这比以前的任何一种策略都简单得多。
在双缓冲系统中,我们有足够的内存来存储两个不同的屏幕图像,因此我们将其中一个指定为“前缓冲区”,将另一个指定为“后缓冲区”。“前缓冲区”是当前显示的内容,“后缓冲区”是我们当前绘制的位置。
在完成向后缓冲区绘制屏幕图像之后,我们等待直到vsync,然后交换两个缓冲区。这样,后缓冲区变成前缓冲区,反之亦然,并且整个交换发生在监视器没有绘制任何内容的时候。
三重缓冲
双缓冲区方法经常引起的一个问题是,当我们完成向后缓冲区的绘制后,我们必须等待vsync,然后才能交换缓冲区并继续工作。在那段时间内我们可能一直在进行计算!而且,在我们一直等待缓冲区之间交换的所有时间中,该后缓冲区中的图像越来越老,从而增加了用户的感知延迟。
在三缓冲区系统中,我们为自己创建了三个缓冲区-一个前缓冲区和两个后缓冲区。这个想法是这样的:
监视器正在显示前缓冲区,而我们正在绘制后缓冲区#1。如果我们在监视器完成绘制前缓冲区之前完成绘制到后缓冲区#1中,那么我们不必等待vsync,而是立即开始将下一帧绘制到后缓冲区#2中。如果我们完成并且vsync仍然没有来,我们就开始退回到1号后备缓冲区,依此类推。这个想法是,当vsync最终发生时,一个或另一个后缓冲区将完成,并且可以将其中一个替换为前缓冲区。
三重缓冲的好处是,我们不会浪费在双缓冲方法中等待vsync所花费的时间,并且交换到前缓冲区的映像可能比正在等待vsync的映像“刷新”了。 8毫秒 三重缓冲的缺点是,我们需要额外的内存来存储多余的屏幕图像,并且CPU / GPU的使用率会更高(再次,因为我们不会放慢等待vsync的速度)。
通常,现代驱动程序通常会在后台透明地执行三重缓冲。您编写代码以进行双缓冲,驱动程序实际上将尽早将控制权归还给您,并且仅在内部处理它想使用的多个后备缓冲区之间的交换,而您的代码从未意识到这一点。
GPU供应商当前建议您不要自己实现三重缓冲-驱动程序会自动为您完成。