VSync

什么是屏幕撕裂?

想象一下你的显示器是如何工作的:它以固定的频率(比如每秒 60 次,即 60Hz 刷新率)从显存中读取图像数据,并逐行绘制到屏幕上。这个过程被称为**垂直同步空白期 (Vertical Blanking Interval, VBI)**。

而你的显卡则以尽可能快的速度渲染新的图像帧。如果显卡渲染帧的速度超过了显示器的刷新率,并且在显示器正在绘制一帧的过程中,显卡完成了新的一帧并将其写入显存,那么显示器可能会同时从两个不同的帧中读取数据。

结果就是,屏幕上会出现水平的断裂线,画面上半部分显示的是旧帧,下半部分显示的是新帧,这就是屏幕撕裂。在快速移动的场景中,这种现象尤为明显,会严重影响视觉观感。


VSync 的工作原理

VSync 的核心思想就是同步显卡渲染和显示器刷新

为了实现这一点,OpenGL (以及其他图形 API) 通常会使用双缓冲(Double Buffering) 技术。这意味着有至少两个帧缓冲区:

  • **前置缓冲区 (Front Buffer)**:当前显示器正在读取并显示给用户的帧。
  • **后置缓冲区 (Back Buffer)**:显卡正在渲染新帧的缓冲区。

当 VSync 启用时,显卡会等待显示器完成当前帧的绘制(即等待 VBI 的开始),然后才将后置缓冲区的内容与前置缓冲区进行交换。这个交换操作被称为 “buffer swap” 或 **”page flip”**。

所以,整个流程是这样的:

  1. 显卡在后置缓冲区中渲染一帧。
  2. 渲染完成后,如果 VSync 启用,显卡不会立即将这一帧显示出来。
  3. 它会等待显示器的下一次垂直同步信号(即当前帧绘制完成,准备绘制下一帧的瞬间)。
  4. 当接收到垂直同步信号后,显卡才进行缓冲区交换,将渲染好的后置缓冲区变为前置缓冲区显示出来。

这样就能确保屏幕上始终显示完整的一帧,从而消除撕裂现象。


VSync 的优缺点

优点

  1. 消除屏幕撕裂:这是 VSync 最主要和最直接的优点,保证了画面的完整性和平滑性。
  2. 提供更流畅的视觉体验:即使帧率不是很高,如果它能稳定地与显示器刷新率同步,视觉上会感觉更流畅,没有突然的画面跳动。
  3. 降低 GPU 负载和功耗:如果你的显卡渲染帧的速度远超显示器刷新率(例如,显卡能渲染 200 FPS,而显示器只有 60Hz),启用 VSync 会将帧率限制在 60 FPS。这样显卡就不需要以最高速度运行,从而减少了功耗、发热和噪音。

缺点

  1. 增加输入延迟(Input Lag):这是 VSync 最大的缺点。由于显卡必须等待显示器刷新周期才能显示新帧,如果显卡渲染速度很快,它可能会在等待期间存储额外的渲染帧。这意味着从你输入操作(比如点击鼠标)到你在屏幕上看到结果之间会有一个小延迟。对于竞技类游戏,这可能是致命的。

    • 例如,如果你的游戏以 100 FPS 运行,而显示器是 60Hz,那么没有 VSync 时,你可能每 10ms 看到一次更新。但有了 VSync,你必须等待 16.67ms (1/60秒) 才能看到更新。
  2. 帧率上限受显示器刷新率限制:如果你的游戏性能强劲,能够渲染出超过显示器刷新率的帧数,那么 VSync 会强制将帧率限制在显示器的刷新率(例如,60Hz 显示器会将帧率限制在 60 FPS)。这可能导致显卡性能的浪费。

  3. 帧率骤降导致卡顿:如果你的游戏帧率在某些复杂场景中低于显示器的刷新率(比如从 70 FPS 掉到 50 FPS),Vsync 可能会强制将帧率降到显示器刷新率的整数倍,例如 60 FPS 下降到 30 FPS。这种突然的帧率减半会导致明显的卡顿感。


如何在 OpenGL 中启用 VSync

在 OpenGL 中,VSync 的控制通常不是通过核心 OpenGL API 函数直接进行的,而是通过特定于操作系统的扩展来管理。这是因为垂直同步是操作系统或窗口系统管理显示的一种功能。

  • Windows (WGL):

    通常使用 WGL_EXT_swap_control 扩展。你需要获取 wglSwapIntervalEXT 函数的指针来控制 VSync。

    • wglSwapIntervalEXT(1): 启用 VSync (每秒交换一次,与显示器刷新同步)。
    • wglSwapIntervalEXT(0): 禁用 VSync。
    • wglSwapIntervalEXT(N): 等待 N 个垂直同步信号后才交换缓冲区,这将把帧率限制在显示器刷新率的 1/N。

    示例(伪代码):

    1
    2
    3
    4
    5
    6
    7
    8
    // 在创建 OpenGL 上下文后调用
    typedef BOOL (APIENTRY *PFNWGLSWAPINTERVALEXTPROC) (int interval);
    PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");

    if (wglSwapIntervalEXT) {
    wglSwapIntervalEXT(1); // 启用 VSync
    // wglSwapIntervalEXT(0); // 禁用 VSync
    }

  • Linux (GLX):
    使用 GLX_EXT_swap_control 扩展,通过 glXSwapIntervalEXT 或 glXSwapIntervalSGI 函数。

  • macOS:
    macOS 上的 VSync 行为有所不同,通常由系统自动管理,或者通过 CVDisplayLink 这样的 CoreVideo API 进行更精细的控制。

  • SDL / GLFW 等库:
    如果你使用像 SDL、GLFW 或 GLUT 这样的第三方库来创建 OpenGL 上下文和窗口,它们通常会提供更简单的跨平台 API 来控制 VSync。

    • GLFW: glfwSwapInterval(1) 启用 VSync,glfwSwapInterval(0) 禁用。
    • SDL: SDL_GL_SetSwapInterval(1) 启用 VSync,SDL_GL_SetSwapInterval(0) 禁用。

结论

VSync 是一个在图形渲染中权衡画面质量和响应速度的工具。它的主要作用是消除屏幕撕裂,提供更稳定的视觉体验。然而,它也可能引入输入延迟并限制帧率。在选择是否启用 VSync 时,通常需要根据应用程序的类型(例如,竞技游戏更倾向于禁用 VSync 以减少延迟,而电影播放或非竞技游戏则可能更倾向于启用 VSync 以获得平滑画面)以及用户的偏好来决定。

现代显示技术如 G-SyncFreeSync 旨在提供 VSync 的优点(无撕裂)同时避免其主要缺点(输入延迟和帧率限制),通过让显示器的刷新率动态地与显卡的帧率同步来实现。