梦亦同趋

【碎碎念】关于浏览器的渲染

保持思考,保持学习。

试问哪个前端开发不想深入了解浏览器呢,想了解它的过去,彼此互动的细节,日日陪伴的点点滴滴...🙂‍↔️

#VSync

VSync,全称为Vertical Synchronization(垂直同步),是一种图形技术,用于将游戏或应用程序的帧率与显示器的刷新率同步,从而消除画面撕裂(Screen Tearing)现象。 画面撕裂是指显卡输出帧率与显示器刷新率不匹配时,屏幕上同时显示多个帧的部分内容,导致图像水平分裂。其核心是通过等待显示器的垂直空白间隔(VBlank)信号,将 GPU 的帧渲染输出与显示器的刷新率精确对齐,从而消除屏幕撕裂(Screen Tearing)。

#基础概念

  • 刷新率(Refresh Rate):显示器每秒刷新屏幕的次数,例如 60Hz 表示每 16.67ms 刷新一帧。
  • 帧率(Frame Rate):GPU 每秒渲染的帧数(FPS)。
  • VBlank(垂直空白间隔):显示器完成一帧扫描(从上到下逐行绘制)后,短暂的“空白期”(约 1-2ms),用于重置扫描位置。在此期间安全交换帧数据。
  • 屏幕撕裂原因:无 VSync 时,GPU 在显示器扫描中途提交新帧,导致屏幕同时显示旧帧上半部和新帧下半部,形成水平断裂线。

#显示器刷新原理

显示器(无论 CRT、LCD 或 OLED)刷新一帧的原理类似:逐行扫描

  • 从屏幕顶部开始,一行一行绘制像素(Active Scan 期,约 15ms)。
  • 扫描完底部后,进入 VBlank 期(约 1.67ms),电子束/扫描器返回顶部,准备下一帧。
  • 显示器在 VBlank 开始时发送 VSync 信号给 GPU(通过 HDMI/DisplayPort 等接口)。
  • 如果 FPS > 刷新率:GPU 渲染过快,会在 VBlank 后闲置等待(帧率上限锁定)。
  • 如果 FPS < 刷新率:可能跳帧(显示重复旧帧)或卡顿(等待累积)。

#与浏览器的关系

VSync(垂直同步)与浏览器密切相关,主要体现在浏览器渲染流水线中:浏览器使用 VSync 信号同步网页帧渲染与显示器刷新率(通常 60Hz,每 16.67ms 一帧),确保动画、滚动和视频播放平滑无撕裂(screen tearing)。 这类似于游戏中的 VSync,但浏览器优化为合成线程(Compositor)主导,减少主线程(JS/布局)阻塞导致的卡顿(jank)

#VSync 信号的来源(Source)

VSync 并非 Chrome 自己产生,而是来自操作系统或硬件

平台VSync 来源
AndroidSurfaceFlinger + HWC(Hardware Composer)通过 DispSync 软件 PLL 生成 1
LinuxDRM/KMS 子系统(Direct Rendering Manager / Kernel Mode Setting)提供vblank 事件
WindowsDirectX 的IDXGIOutput::WaitForVBlank() 或 DWM(Desktop Window Manager)合成器
macOSCore Video 的CVDisplayLink,与显示器刷新率同步
ChromeOS基于 Linux DRM,但通过Ozone 抽象层接入

#VSync 的捕获:VSyncProvider

Chrome 定义了 VSyncProvider 接口(//ui/compositor/vsync_provider.h),各平台实现具体逻辑:

  • Android: AndroidVSyncProvider
    • 注册 Choreographer.FrameCallback
    • 通过 JNI 调用 Java 层 Choreographer.postFrameCallback
  • Linux: DrmVSyncProviderWaylandVSyncProvider
    • 监听 DRM 的 DRM_EVENT_VBLANK 或 Wayland 的 frame 事件
  • macOS: DisplayLinkMac
    • 使用 CVDisplayLinkSetOutputCallback

当 VSync 到来时,这些 Provider 会调用:

cpp
vsync_observer_->OnVSync(vsync_time, interval);

#VSync 的分发:Viz Display Compositor

这是 Chrome 图形栈的核心——Viz(Visuals)系统,负责帧调度和合成。

#关键组件:

  • DisplayScheduler//components/viz/service/display/display_scheduler.cc
    • 接收 OnVSync
    • 触发 BeginFrame
    • 管理帧节奏(是否节流、是否跳过)。
  • BeginFrameSource
    • 将 VSync 转换为 BeginFrameArgs(含帧 ID、时间戳、间隔);
    • 广播给所有订阅者(如 Renderer 的合成线程)。
plaintext
[ Hardware VSync ] VSyncProvider::OnVSync() viz::DisplayScheduler::OnBeginFrameSourceNeedsBeginFrames() Generate BeginFrameArgs (frame_id, deadline, interval) Send via IPC to Renderer Process → Compositor Thread

#VSync 在 Renderer 进程中的使用

#合成线程(Compositor Thread)

  • 收到 BeginFrame 后:
    • 更新合成属性动画(transform, opacity
    • 检查是否需要请求主线程更新(needs_main_frame = true
    • 若不需要,则直接合成并提交帧。

#主线程(Main Thread)

终于到了喜闻乐见的主线程环节,下面的东西相对于前端将会相对熟悉些。

  • 若合成线程标记 needs_main_frame,则:
    • 执行 requestAnimationFrame 回调;
    • 运行微任务;
    • 执行 Style/Layout/Paint;
    • 提交 LayerTree 给合成线程。