Scanout(扫描输出)是 显示控制器 按屏幕时序从 帧缓冲DRM Plane 中连续读取像素,并把这些像素送往 HDMI、DisplayPort、eDP、MIPI DSI 等显示接口的过程。

它不是渲染。渲染负责“生成图像内容”,scanout 负责“按显示器需要的节奏把图像读出来并送出去”。

核心问题

显示器需要稳定的像素流。以 1920x1080 @ 60 Hz 为例,显示控制器必须在每秒约 60 次的刷新周期里,按固定像素时钟输出整屏内容。如果某一行像素来不及读出,就可能出现 underflow、闪屏、撕裂或黑屏。

Scanout 要解决的是:

  • 当前应该从哪张 framebuffer 读。
  • 每个屏幕坐标对应哪个 plane 和哪个源像素。
  • 什么时候切换到下一帧才不会撕裂。
  • 多个 plane 如何缩放、混合并形成最终像素流。
  • 显示接口需要什么时序、同步信号和像素格式。

核心对象

对象含义关注点
pixel clock输出像素的时钟分辨率、刷新率、blanking 共同决定
active area屏幕实际可见区域宽高、目标坐标、plane 覆盖区域
blanking interval非可见扫描间隔hblank、vblank、page flip 时机
CRTC产生扫描时序的 KMS 对象模式、vblank、中断、扫描位置
plane被读取和混合的显示层源区域、目标区域、z-order
[[Scaler(硬件缩放器)scaler]]scanout 路径上的缩放单元
FIFO / line buffer暂存像素的硬件缓冲带宽不足时可能 underflow

核心机制

Scanout 可以理解为一个严格按时间推进的读像素循环:

for each refresh:
  for each visible line y:
    for each visible pixel x:
      pixel = compose_planes_at(x, y)
      send_to_display(pixel)
  • refresh 是一轮屏幕刷新。
  • (x, y) 是屏幕坐标,不是源 framebuffer 坐标。
  • compose_planes_at(x, y) 表示显示控制器找到覆盖该坐标的 plane,必要时经过 scaler、颜色转换和 alpha 混合得到最终像素。
  • send_to_display(pixel) 表示把像素送到显示接口编码器,而不是写回内存。

页面翻转通常选择在 vblank 附近生效:

old_framebuffer -> scanout
vblank event
new_framebuffer -> scanout

vblank 是一帧扫描完、下一帧尚未开始的垂直消隐窗口。在这个窗口切换 framebuffer,屏幕不会在同一帧上半部分显示旧图、下半部分显示新图。

工程用途

  • 判断“图像已经渲染好”与“图像已经显示出去”之间的区别。
  • 调试撕裂、黑屏、underflow、vblank 中断和 page flip 卡顿。
  • 理解 DRM/KMS 中 CRTC、plane、framebuffer 和 connector 的关系。
  • 在嵌入式显示 bring-up 中定位是渲染问题、buffer 问题、scanout 问题还是显示接口问题。

常见检查包括 KMS mode、vblank 事件、plane 配置、framebuffer stride、显示带宽、line buffer underflow 日志和接口时序。

边界与常见坑

  • Scanout 不是 GPU 渲染:GPU 可以生成 framebuffer,但 scanout 是显示控制器读取并输出 framebuffer。
  • Scanout 不一定只读一张图:现代显示控制器可以同时读多个 plane,再做缩放和混合。
  • page flip 提交不等于立刻显示:它通常要等到合适扫描周期才生效。
  • underflow 常是带宽或时序问题:源图像存在不代表显示控制器来得及按像素时钟读出来。
  • 垂直同步不是万能:如果 GPU fence、plane 配置或显示带宽不满足,等 vblank 也会卡顿或失败。

相关术语