Appearance
硬件与显示链:一帧 UI 如何到达面板
返回系统栈 · 内核与驱动 · 平台、引擎与应用 · 跨层故障链
本页只处理“物理输入到像素发光”这一段。操作系统、驱动、窗口系统和引擎并不在这里重复展开,但会在交接处说明它们拿走了什么状态、又归还了什么完成信号。
先排除一个常见误解:GPU 不等于一张独立显卡。手机、笔记本的集成 GPU、SoC 内的显示控制器、统一内存架构上的图形处理器,都能走这条链。离散 GPU 会多出 PCIe 和独立显存;统一内存机器则主要受共享带宽、缓存一致性和内存压力约束。逻辑上的队列、buffer 所有权和显示时序仍然存在。
贯通例子:按下开关到一帧亮起
假设一个 120 Hz 的应用中,用户点击开关,界面立即从“关”变成“开”。这一帧不能简单理解为“CPU 把像素写到屏幕”。实际发生的是一连串可并行、也必须在边界上等待的转交:
text
手指/鼠标
-> 输入设备报告
-> 控制器 DMA 或中断通知
-> CPU 处理事件并更新应用状态
-> 应用/引擎写入图形命令和资源引用
-> GPU 执行绘制、栅格和合成
-> 新 buffer 变为可展示
-> 显示控制器在 vblank 后选中它
-> 扫描器逐行送往面板
-> 像素改变,用户看见“开”在 120 Hz 下,相邻 vblank 的理论间隔约为 8.33 ms;在 60 Hz 下约为 16.67 ms。这不是应用可独占的预算。输入到达的位置、CPU 是否被抢占、资源是否已在显存、GPU 队列深度、合成器策略、面板刷新方式都会挤占或改变它。错过一次显示提交窗口,通常不是晚 1 ms,而是至少再等一个刷新周期。
1. 输入设备和控制器:动作先成为报告
鼠标、键盘、触摸屏、手写笔并不把“点击组件 A”交给应用。它们产生的是设备报告:按键位、相对位移、绝对坐标、接触点、压力、时间等。USB HID、I2C HID、蓝牙 HID 是常见传输与报告约定;触摸屏控制器通常还会做去噪、接触点追踪或坐标转换的一部分工作。
| 项目 | 内容 |
|---|---|
| 输入 | 物理开关变化、光学传感器采样、电容触摸采样 |
| 拥有状态 | 设备固件中的采样/去抖状态,主控中的报告环形缓冲区 |
| 输出 | 给总线控制器的报告;最终进入内核输入子系统 |
| 同步/等待 | 中断通知 CPU,或由驱动轮询;报告可在内核队列中排队 |
| 用户可见故障 | 鼠标跳动、漏抬起、触摸漂移、输入整体滞后;这些发生时 UI 代码可能完全没有运行 |
这里的关键时间不是“浏览器收到 click”,而是硬件第一次完成采样。操作系统会在后续阶段添加时间戳和坐标变换;不同平台对它们的精度与语义不同,不能把 JavaScript 或 Flutter 的事件时间直接当成传感器时间。
2. CPU、缓存与主内存:把报告变成状态变更
CPU 核执行中断处理、内核输入路径、窗口系统路由、应用事件处理以及部分图形命令录制。它没有一块统一的“CPU 内存”:寄存器、L1/L2 缓存、可能共享的末级缓存和 DRAM 组成层级。越靠近核心,容量越小、延迟越低;跨核心共享数据需要缓存一致性协议和内存屏障来约束可见顺序。
对 UI 帧而言,应用线程更常见的敌人是延迟抖动而非平均吞吐:
- 事件线程在忙于长脚本、布局、垃圾回收或锁竞争,内核已经有报告但用户动作还未被处理。
- 工作集超过缓存,或内存分配与访问模式很差,CPU 花在 cache miss 和内存等待上的时间上升。
- 内存紧张导致压缩、回收或换页。换页落到磁盘时,交互延迟会远超一帧预算。
- 多核心不自动等于更快。关键路径若需要同一线程串行运行,额外核心只能承担可拆分的工作;跨线程同步还可能增加等待。
| 项目 | 内容 |
|---|---|
| 输入 | 内核调度的事件处理、应用状态、布局/绘制计算、资源数据 |
| 拥有状态 | 线程寄存器与栈、缓存行、虚拟地址空间、应用堆和图形命令内存 |
| 输出 | 新应用状态、显示列表/命令缓冲、给驱动的资源引用与提交请求 |
| 同步/等待 | 线程调度、锁/原子操作、fence 等待、缺页处理;缓存一致不等同于业务状态正确 |
| 用户可见故障 | 点后数十到数百毫秒才反馈、滚动卡住、偶发长卡顿、同一操作时快时慢 |
CPU 的虚拟地址不是 GPU 或显示控制器一定能直接读取的物理地址。内核和驱动会建立页映射、pin 或 DMA 映射,并在安全边界上验证访问。具体机制见内核与驱动。
3. 内存边界:PCIe、统一内存与资源驻留
图形资源包括顶点/路径数据、纹理、字形图集、离屏渲染目标和最终可展示的 surface。应用通常只持有 API 对象或映射过的内存;真正的物理页位置、是否可被 GPU 访问、何时可以复用由运行时、驱动和内核共同决定。
离散 GPU
离散 GPU 有自己的显存(VRAM)。CPU 侧数据上传到 VRAM 或从 VRAM 读回,可能经过 PCIe。PCIe 带宽很高,但不是零成本、也不是与 DRAM 一样的访问路径;频繁上传整张大纹理、每帧从 GPU readback 到 CPU、或在总线两端来回复制,都会造成可测量的延迟和带宽消耗。
集成 GPU 与 SoC
集成 GPU 或移动 SoC 通常与 CPU 共享物理 DRAM,有时称统一内存。共享物理内存不表示“零同步”或“零复制”:CPU/GPU 缓存域、内存布局、保护域与访问时序仍需协调;GPU 大量读取纹理和显示控制器 scanout 也会与 CPU 争用 DRAM 带宽。某些平台还会有专用的压缩格式、tile memory 或片上缓存。
| 项目 | 内容 |
|---|---|
| 输入 | CPU 生成的资源、已有纹理/目标 surface、GPU 对页和布局的访问需求 |
| 拥有状态 | 驱动的资源映射与驻留信息,GPU 的缓存与地址转换状态,内存控制器的带宽仲裁 |
| 输出 | GPU 可读写的 buffer/纹理;可供显示控制器读取的 scanout buffer |
| 同步/等待 | 上传完成、资源 barrier、CPU-GPU cache flush/invalidate、fence;API 隐式同步的具体时机因驱动而异 |
| 用户可见故障 | 首次显示图片卡顿、切换页面掉帧、显存/内存压力下纹理被逐出后重传、readback 导致动画顿挫 |
开发者层的实际原则是:资源尽量在被消费的一侧保持可复用,避免把每帧像素搬回 CPU;但不应以设备型号猜测实现。用图形调试器、性能计数器和 trace 验证上传、带宽与队列等待。
4. GPU:命令队列不是“立刻画完”
GPU 擅长对大量顶点、像素或计算线程执行相近指令。它通常有一个或多个硬件/驱动可见队列。CPU 把命令缓冲提交到队列,提交成功只表示工作被接收或排队,不表示像素已经生成。GPU 的执行单元、纹理采样单元、缓存、光栅后端和内存系统会在随后执行这些工作。
对传统 2D/3D 图形,可粗略看成以下管线:
text
几何/路径或四边形
-> 顶点处理与图元装配
-> 裁剪、变换、光栅化
-> fragment/pixel shader 取纹理、计算颜色
-> 深度/模板/混合
-> 写入渲染目标现代 UI 并不一定逐项使用经典 3D 管线。文本、圆角、阴影、路径可能先在 CPU 或 GPU 上栅格化为纹理,也可能通过计算着色器生成;合成器再把多个图层作为纹理画成最终表面。共同点是:命令描述工作,GPU 异步产生 buffer 中的像素,不能在尚未完成时被显示控制器或 CPU 当成稳定结果读取。
| 项目 | 内容 |
|---|---|
| 输入 | command buffer、管线状态、几何、纹理、uniform、目标 buffer、同步对象 |
| 拥有状态 | 队列顺序、上下文/管线状态、片上缓存、正在执行的 wave/warp,资源读写依赖 |
| 输出 | 写完的渲染目标、信号化的 fence/semaphore、时间戳或查询结果 |
| 同步/等待 | 队列内通常按提交顺序;跨队列、跨进程或交给显示前依赖显式/隐式同步;CPU 不应在热路径阻塞等待 GPU 完成 |
| 用户可见故障 | GPU 满载时动画稳定地低帧率;shader 首次编译或纹理 miss 造成首帧毛刺;错误同步可能是闪烁、旧帧或损坏画面 |
栅格化与合成的边界
栅格化回答“矢量、文本、图片和效果怎样变成一个 buffer 的像素”;合成回答“哪些已经准备好的 layer 以什么变换、裁剪、不透明度顺序放进最终帧”。把滚动层、视频层或动画层独立出来,可能让合成器只重组已有纹理而不重画整页;但 layer 过多也会增加显存、提交和合成成本。浏览器和 Flutter 的具体决策见平台、引擎与应用。
5. Buffer 所有权:生产、展示和复用必须分开
最终画面至少涉及三个角色:生产者(GPU/合成器)、消费者(显示控制器)和协调者(窗口系统/合成器/驱动)。同一块 buffer 在一个时刻不能既被 GPU 改写又被显示控制器扫描,否则画面会混合新旧内容。
双缓冲的基本模型是:显示控制器扫描 front buffer,生产者写 back buffer;在合适的刷新边界交换它们。三缓冲或更多队列 buffer 可以让 GPU 继续生产,减少生产者因错过 vblank 而空等,但也可能让最新输入排在旧帧之后,提高端到端延迟。低延迟与稳定吞吐之间没有一套对所有产品都最优的缓冲深度。
text
GPU/合成器写 buffer N
| (render-complete fence)
v
窗口系统/驱动把 N 标为可展示
| (下一次允许的 page flip / present)
v
显示控制器扫描 N
| (release / retire 信号)
v
生产者才可复用 N| 项目 | 内容 |
|---|---|
| 输入 | 已申请的 surface/buffer,GPU 完成信号,显示刷新事件 |
| 拥有状态 | 每个 buffer 的 producer/consumer 所有权、acquire/release fence、队列深度、展示顺序 |
| 输出 | 被选作 scanout 的 buffer,或归还给生产者复用的 buffer |
| 同步/等待 | acquire 表示可写/可读,release 表示消费者已用完;不同系统名称不同,所有权转换不可省略 |
| 用户可见故障 | buffer 不够时 producer 阻塞并掉帧;队列太深时操作“跟手”变差;同步错误可见为闪屏、旧内容或随机花屏 |
6. 显示控制器和 scanout:最后不是 GPU 在“刷新屏幕”
显示控制器(display controller / display engine)通常是 SoC 或 GPU 内的专用硬件。它从一个或多个指定 buffer 按像素时钟和时序读取数据,进行可能的平面叠加、缩放、颜色转换、HDR/色彩管理处理,然后将信号送往内建面板或 HDMI/DisplayPort 等链路。这个连续读取过程叫 scanout。
面板通常从顶部到下方逐行接收一帧,而非在某个瞬间整体替换像素。完成一帧扫描到下一帧开始之间会出现垂直消隐相关的时序窗口;操作系统和图形栈常把 vblank 事件作为安全地 page flip 或节流提交的参考点。实际面板、接口和合成策略会改变精确时机。
| 项目 | 内容 |
|---|---|
| 输入 | scanout buffer 的地址/格式、平面配置、显示模式、page-flip 请求 |
| 拥有状态 | 当前与待切换的 framebuffer、时钟/扫描位置、颜色与平面配置 |
| 输出 | 面板或外接显示器的像素流;vblank/page-flip 完成事件 |
| 同步/等待 | 只能读取 render-complete 的 buffer;正常 vsync 策略等待恰当扫描边界;可变刷新率还会协商刷新区间 |
| 用户可见故障 | 全屏卡顿但 CPU 未满、外接屏刷新异常、颜色/缩放不对、特定显示器下闪烁或黑屏 |
vblank、vsync、撕裂和掉帧
- vblank 是显示扫描时序中的事件/区间,硬件与内核可报告它。
- vsync 是图形栈把展示或生产节奏与显示刷新对齐的一类策略。不同 API、窗口系统和厂商驱动对其命名及默认行为不同。
- 撕裂(tearing) 发生在显示控制器正在扫描一帧时,scanout 源被切换或其中内容被改写,屏幕上部和下部来自不同帧。允许立即展示可降低等待,却增加撕裂风险。
- 掉帧(missed frame) 是新 buffer 没赶上预定展示窗口,显示器只能重复旧帧或展示较旧的已完成帧。掉帧不必然撕裂;撕裂也不等于应用掉帧。
可变刷新率(VRR)可以在面板支持范围内改变相邻刷新的间隔,减少固定刷新下的等待或重复帧;它不能让尚未完成的 CPU/GPU 工作凭空完成,也不能修复错误的 buffer 同步。
7. 把现象放回正确节点
| 现象 | 优先检查的边界 | 不应直接下的结论 |
|---|---|---|
| 点击后很久才有视觉反馈 | 输入时间戳、主线程调度、事件处理与第一笔图形提交 | “GPU 慢” |
| 动画每隔几秒顿一下 | CPU 长任务、GC、资源上传、shader 编译、GPU 队列等待 | “刷新率不够” |
| 平均帧率高但拖动不跟手 | buffer 队列深度、present 策略、输入到展示延迟 | “60 FPS 就足够低延迟” |
| 画面出现横向断层 | scanout/page flip 时序、允许 tearing 的展示策略 | “布局算错了” |
| 首次打开某页卡一下 | 图片解码、纹理上传、管线/着色器准备、内存驻留 | “框架首次渲染必然慢” |
| 外接屏独有的闪烁或颜色问题 | 显示模式、线缆/接口、驱动、色彩/HDR、VRR | “应用 CSS 或 Widget 有问题” |
定位应沿链采集证据:输入事件时间、应用状态变更、CPU trace、图形 API/驱动提交、GPU 队列与频率、present/page-flip、实际 vblank。单独看应用日志、单独看 FPS 或单独看 CPU 利用率,都不足以定位端到端延迟。
平台差异与边界
上文描述的是跨平台的职责关系,不是某一厂商驱动的内部实现承诺。
- Linux 常用 DRM/KMS 表达 connector、CRTC、plane、framebuffer 与 page flip;Android SurfaceFlinger/BufferQueue、Windows DWM/DXGI、macOS 的 WindowServer/Metal、浏览器自身合成器会以不同对象名实现相近的所有权交接。
- Vulkan、Metal、Direct3D、OpenGL 与 WebGPU 的同步模型不同。显式 API 会把更多 barrier/fence/semaphore 责任暴露给调用方;高层框架通常封装了一部分,但不能消除硬件依赖。
- 合成器能否绕过一次合成直接使用硬件 plane、显示器是否支持 VRR、buffer 数量、tile-based 或 immediate-mode GPU、独显或统一内存,都会改变性能特征和 trace 的具体形态。
- 刷新率只是显示链的一项参数。触摸采样率、合成节奏、输入预测、面板响应时间和系统的节能策略同样影响用户感到的延迟。
因此,先用本页的状态、所有权和等待点建立假设,再在目标设备、目标系统版本和目标驱动上验证。不要从某台机器的一张性能图推导所有客户端的规律。
一手资料与规范
- Linux DRM/KMS documentation,Linux 内核文档,访问日期:2026-07-30。说明 KMS 对象、atomic modesetting 和 page flip 的内核接口;Linux 专属。
- Linux input subsystem,Linux 内核文档,访问日期:2026-07-30。说明输入设备与事件处理;Linux 专属。
- Vulkan Specification: Synchronization and Cache Control,Khronos,访问日期:2026-07-30。说明 GPU/内存依赖和同步的规范语义;适用于 Vulkan,不可直接外推到其他 API。
- Vulkan Guide: Swapchain,Khronos,访问日期:2026-07-30。说明 image acquisition、presentation 和 swapchain;具体 present mode 取决于平台与驱动。
- Android graphics architecture,Android Open Source Project,访问日期:2026-07-30。说明 BufferQueue、SurfaceFlinger 与显示合成;Android 平台专属。
- Apple: Metal Best Practices Guide,Apple,访问日期:2026-07-30。说明资源、命令缓冲和同步的 Metal 视角;Apple 平台专属,内容随 SDK 更新。
下一页:内核与驱动。