【问题标题】:OSX pushing pixels to screen with minimum latencyOSX 以最小延迟将像素推送到屏幕
【发布时间】:2015-08-03 17:26:11
【问题描述】:

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

看看这个:

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

您之前可能已经注意到这一点:使用 c++ OpenGL 应用程序,在屏幕上拖动鼠标,并在 OpenGL 中绘制鼠标位置,OpenGL 会滞后 3 或 4 帧。显然 OSX CAN 以非常低的延迟将 [光标] 绘制到屏幕上,但 OpenGL 慢得多。所以假设我不需要做任何花哨的 OpenGL 渲染。我只是想以某种方式将像素推到屏幕上。有没有办法让我完全绕过 OpenGL 并更快地绘制到屏幕上?或者这种功能是否会被锁定在内核中我无法访问的某个地方?

【问题讨论】:

  • 尝试提供更多关于你已经尝试过的细节,可能是你实现的一些代码示例
  • 可能想要阅读this buffer object streaming article。我推测完全绕过opengl的方式将涉及依赖于硬件的API。
  • 在 OSX El Capitan 下,可以使用带有像素着色器的简单 4 顶点着色器使用 Metal 从帧缓冲区中进行绘制。见例子objc.io/issues/18-games/metal
  • 我浏览了缓冲区流式传输文章,但无法得到它的帮助。我可以看到使用它来查看改进的唯一方法是,如果我能够以某种方式直接访问 openGL 后绘制的内存并在 openGL 管道之外重绘到它。但我不认为这是我正在寻找的解决方案......
  • 当我关闭垂直同步时,突然间我的延迟基本上降到了零。也许是因为我仍在等待缓冲区翻转 3 或 4 次,但是在关闭 vsync 的情况下,这种情况每 3 毫秒而不是每 16 毫秒发生一次?

标签: c++ macos opengl draw low-latency


【解决方案1】:

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

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

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

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

如果帧速率不一致,则会产生一系列完全不同的视觉问题。从技术上讲,属于丢帧的像素具有无限延迟,因此如果您需要绘制的每一帧都出现在屏幕上,那么通过这种方式减少延迟所带来的好处可能毫无价值。

我相信您可以通过禁用 VSYNC 并在窗口中绘图来在 OS X(如果需要)上获得这种行为。在这种情况下,VSYNC 基本上仅用作帧节奏的一种形式(以延迟换取一致性),无论您以何种速率绘制,合成器本身都会消除撕裂。


关于鼠标光标延迟:

任何现代窗口系统中的光标都将始终以最小的延迟进行跟踪。从字面上看,图形硬件上有一个称为“硬件光标”的功能,驱动程序存储光标位置,然后每次刷新后,硬件将光标覆盖在帧缓冲区中等待扫描的任何内容之上.因此,即使您的应用程序在 60 Hz 显示器上以 30 FPS 的速度绘图,当使用硬件光标时,光标也会每 16 毫秒更新一次。

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


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

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

  • 尽量缩短屏幕上显示内容的时间

如果您的问题是渲染循环执行之间经过的时间量,那么您会采用另一种方式。增加预渲染帧,在窗口中绘制并禁用 VSYNC。在这种情况下,您可能会遇到很多已绘制但从未显示的帧。

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

预渲染帧是一个强大的小功能,您无法在 OpenGL API 级别进行控制。它设置了允许驱动程序对所有内容进行管道传输的深度,并且根据所需的任务,您将通过摆弄它来交换不同类型的延迟。许多游戏玩家发誓将此值设置为 1 以最大程度地减少输入延迟,但会牺牲整体帧速率“流畅度”。

更新:

预渲染帧是多帧延迟的原因之一。以跨平台方式解决此问题很困难(这是驱动程序设置),但如果您可以访问 Fence Sync 对象,则可以产生与强制它为 1 相同的行为。

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

【讨论】:

    【解决方案2】:

    这里有许多延迟。

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

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

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

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

    所以这是延迟的第一个来源。

    • 三重缓冲/合成延迟

    许多图形管道启用三重缓冲以实现更流畅的显示更新。不是只保留一个后缓冲区和一个前缓冲区,而是在它们之间还有第三个缓冲区。绘制到这些缓冲区的平均速率是显示刷新周期。缓冲区本身恰好在显示刷新期间步进。所以这会增加另一个帧延迟时间。

    如果您在带有窗口合成器(这是 MacOS X 的默认设置)的系统上运行,这会有效地添加另一个缓冲阶段,因此如果您有双缓冲模式,它会为您提供三重缓冲,如果您有一个三重缓冲区它会给你一个“四”缓冲区(这里引用,因为四缓冲区是一个通常与立体渲染一起使用的术语)。


    对此你能做些什么:

    • 关闭合成

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

    • 减少输入延迟

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

    如果您的场景很复杂,但在需要低延迟用户输入的部分和无关紧要的部分中是可分离的,您可以更早地绘制较高延迟的部分,并且仅在最后一刻将用户输入集成到其中。当然,如果使用鼠标来控制观看方向,或者更糟糕的是,您正在为 VR 头戴式显示器进行渲染,事情就会变得困难。

    【讨论】:

    • 在 Windows 8+ 中,完全不能禁用 windows 合成器。然而,它实际上并没有像传统的三重缓冲那样产生相同的延迟。合成器是 VSYNC 的,但应用程序不是。我看到它在 AMD 和 NV 驱动程序上工作的方式类似于两个双缓冲系统一起工作。该应用程序有一个前/后缓冲区,而合成器实际上有一个前/后缓冲区。当您在应用程序中交换时,前缓冲区被复制到合成器的后缓冲区。您可以在刷新率之上进行绘制,而不会出现撕裂或与三重缓冲相关的延迟。
    • 但是您可能会绘制很多帧而从未出现过,或者因为计时关闭而重复帧两次。这不是一个完美的解决方案(G-Sync 是理想的),但它实际上可以减少延迟。
    • @AndonM.Coleman:最多,包括 Windows-7 组合可以被禁用:msdn.microsoft.com/en-us/library/windows/desktop/… - 我想知道 MS 的人在决定你不能禁用组合时服用了什么样的药物Win-8 及更高版本中不再存在。
    • “NVidia OpenGL 实现有一个供应商特定的扩展,允许在下一次垂直同步之前休眠到特定的时间”WGL_NV_delay_before_swap/GLX_NV_delay_before_swap?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-05
    相关资源
    最近更新 更多