OSX以最小的延迟将像素推到屏幕上

OSX pushing pixels to screen with minimum latency

本文关键字:像素 屏幕 延迟 OSX      更新时间:2023-10-16

我正在尝试开发一些非常低延迟的图形应用程序,并且通过OpenGL绘制屏幕所需的时间非常长,这让我感到非常沮丧。我在网上找到的每一个关于它的讨论都是关于优化OpenGL管道的,但并没有得到我所需要的结果。

看看这个:

https://www.dropbox.com/s/dbz4bq67cxluhs7/MouseLatency.MOV?dl=0

你以前可能注意到这一点:使用c++ OpenGL应用程序,在屏幕上拖动鼠标,并在OpenGL中绘制鼠标位置,OpenGL滞后3或4帧。显然OSX 可以以非常低的延迟将光标绘制到屏幕上,但OpenGL要慢得多。假设我不需要做任何花哨的OpenGL渲染。我只是想把像素推到屏幕上。有没有一种方法可以让我完全绕过OpenGL,更快地绘制到屏幕上?还是说这种功能会被锁在内核里我够不到的地方?

datenwolf的回答非常好。我只是想在这个关于合成器级别的三重缓冲的讨论中添加一件事,因为我非常熟悉Microsoft Windows桌面合成器。

我知道你在这里问的是OS X,但是我将要讨论的实现细节是实现这些东西的最明智的方式,我希望看到其他系统也以这种方式工作。

在应用程序级别启用的三重缓冲将第三个缓冲区添加到同步刷新的交换链中。这种做三倍缓冲的方式确实增加了延迟,因为第三个缓冲区必须被显示,并且在此之前不允许任何东西接触它(这是D3D的强制行为——行为和功能本身在OpenGL中未定义);但是桌面窗口管理器(Windows)的工作方式略有不同。

我看到大多数驱动程序为桌面合成实现的行为是丢帧。在刷新之间完成多个帧的任何情况下,除了一个帧外,所有这些帧都被丢弃。实际上,使用窗口而不是全屏+三重缓冲可以获得更低的延迟,因为当第三个缓冲区(由合成器拥有)有一个完成的帧等待显示时,它不会阻塞缓冲区交换。

如果帧率不合理一致,它会产生完全不同的视觉问题。从技术上讲,属于丢弃帧的像素具有无限延迟,因此如果您需要绘制的每一帧都显示在屏幕上,那么以这种方式减少延迟的好处可能毫无价值。

我相信你可以得到这个行为在OS X(如果你想要它)通过禁用VSYNC和在窗口中绘图。在这种情况下,VSYNC基本上只是作为一种形式的帧速度(交易延迟一致性),并且无论你以什么速率绘制,撕裂都由合成器本身消除。


关于鼠标光标延迟:

任何现代窗口系统中的光标总是以最小的延迟跟踪。在图形硬件上有一个特性叫做"硬件光标"。驱动程序存储光标位置,然后每次刷新一次,让硬件将光标覆盖在帧缓冲区中等待扫描的内容的顶部。因此,即使你的应用程序在60 Hz的显示器上以30 FPS的速度绘图,当使用硬件光标时,光标也会每16毫秒更新一次。

这完全绕过了所有的图形api,但是相当有限(例如,它使用操作系统定义的光标)。


TL;DR:延迟有多种形式。

如果你的问题是输入延迟,那么你可以通过减少预渲染帧的数量和避免三重缓冲来缓解这个问题。我无法开始告诉你如何在OS x上减少驱动程序预渲染帧的数量。

  • 最小化显示在屏幕上的时间

如果你的问题是在渲染循环执行之间传递的时间量,你会走另一条路。增加预渲染帧,在窗口中绘制并禁用VSYNC。在这种情况下,您可能会遇到许多绘制但从未显示的帧。

  • 最小化阻塞时间(增加FPS);有些帧永远不会显示

预渲染帧是一个强大的小功能,你无法在OpenGL API级别控制它。它设置了驱动程序允许管道的深度,并且根据所需的任务,您将通过摆弄它来交易不同类型的延迟。许多玩家通过将此值设置为1来最小化输入延迟,以牺牲整体帧率的"平滑度"为代价。

更新:

预渲染帧是造成多帧延迟的原因之一。以跨平台的方式修复这个问题是很困难的(这是一个驱动程序设置),但是如果你可以访问Fence Sync Objects,你可以产生与强制1相同的行为。

如果需要的话,我可以更详细地解释这个,一般的想法是在缓冲区交换之后插入一个fence sync,然后在允许开始下一帧中的第一个命令之前等待它发出信号。性能可能会大幅下降,但延迟将被最小化,因为CPU将不再在GPU之前渲染。

这里有许多延迟。

  • 输入事件→绘制状态延迟

在典型的交互式应用程序中,你有一个事件循环,通常是

  1. 收集用户输入
  2. 处理用户输入
  3. 确定要绘制的内容
  4. 绘制到后缓冲区
  5. 交换回前缓冲区

使用通常的编写event-update-display循环的方法,在前一个迭代的第5步和下一个迭代的第1步之间几乎没有延迟。这意味着步骤2,3和4操作的数据滞后大约一个帧周期。

这是延迟的第一个来源。

  • 三重缓冲/合成延迟

许多图形管道启用三重缓冲以实现更平滑的显示更新。而不是只保留一个前后缓冲区,还有第三个缓冲区在中间。绘制这些缓冲区的平均速率是显示刷新周期。缓冲区本身正好在显示刷新周期步进。这就增加了另一个帧延迟周期。

如果你在一个有窗口合成器的系统上运行(这是MacOS X的默认值),这有效地增加了另一个缓冲阶段,所以如果你有一个双缓冲模式,它会给你三个缓冲,如果你有一个三重缓冲,它会给你一个"四"缓冲(引号在这里,因为四"缓冲是一个术语,通常用于立体渲染)。


你能做些什么呢?

  • 关闭合成

Windows通过DWM API和MacOS X允许关闭合成或绕过合成器。

  • 减小输入滞后

尽可能晚地收集和整合用户输入(使用高分辨率睡眠)。如果你只有一个非常简单的场景,你可以把绘图时间推到v同步的最后期限;事实上,NVidia OpenGL实现有一个供应商特定的扩展,允许休眠直到下次V-Sync之前的特定时间。

如果你的场景很复杂,但需要低延迟用户输入的部分是可分离的,而这并不重要,你可以更早地绘制高延迟的东西,直到最后一刻才将用户输入整合进去。当然,如果使用鼠标来控制观看方向,或者更糟糕的是,你正在为VR头戴式显示器渲染,事情就会变得很困难。