《A Trip Through the Graphics Pipeline 2011》笔记01-software-stack
第 1 篇:引言与软件栈(Introduction; the Software Stack)
原文:Part 1
一句话定位
一次 Draw 并不是直接“调用 GPU 函数”,而是从应用语义开始,经过 API/UMD 的验证与转换、KMD 的资源和调度管理,最终变成 GPU 命令流。
从 API 调用到硬件
sequenceDiagram
participant App as 应用
participant UMD as API Runtime / UMD
participant KMD as KMD
participant GPU as Command Processor
App->>UMD: 设置状态、创建资源、Draw
UMD->>UMD: 验证、编译/选择状态、编码命令
UMD->>KMD: 提交命令缓冲与资源依赖
KMD->>KMD: 校验、地址/驻留处理、排队
KMD->>GPU: 允许队列执行
GPU->>GPU: 取命令并驱动后续流水线
API Runtime 与 UMD
- API 层提供稳定的功能语义:哪些状态合法、资源如何被使用、结果应满足什么规则。
- 用户态驱动通常完成大部分高频工作:状态跟踪、着色器编译、资源对象管理、命令编码,以及把多个小调用摊销进较大的提交批次。
- 许多工作会延迟到真正需要时才做。例如某组状态只有在 draw 时才足以决定完整硬件配置;shader 变体也可能由格式、采样状态或其他固定功能状态触发。
- “创建 API 对象成功”不必然意味着所有后端工作已经完成。理解延迟创建与缓存,是分析首次卡顿、shader stutter 和状态爆炸的基础。
KMD 为什么存在
GPU 是跨进程共享、具有 DMA 能力的设备。不能让任意用户进程把未经约束的地址和命令直接交给硬件。内核侧通常承担安全边界和全局仲裁职责,例如:
- 建立/维护 GPU 地址空间与缓冲对象映射;
- 管理驻留、回收和显存压力;
- 校验或约束提交,建立同步依赖;
- 在多个上下文之间调度,处理超时、挂起和复位。
不同操作系统与驱动模型的具体分工不同,因此不要把 2011 年 Windows 栈的边界机械套到 Linux DRM/Mesa;要抓住“高频策略尽量留在用户态,特权资源与跨进程仲裁必须受内核控制”这一原则。
命令缓冲的意义
命令缓冲把大量细碎 API 操作压成顺序的二进制包。它同时解决三件事:减少用户态/内核态切换、允许 CPU 与 GPU 异步并行、让驱动一次描述一批工作及其依赖。
但异步也带来代价:CPU 提交成功只表示“工作已排队”,不表示 GPU 已执行;过深排队会提高输入延迟;过早等待又会打断流水。Fence 的本质就是给这种异步关系一个可观察的完成点。
术语对照
| 英文 | 中文理解 | 代码阅读时关注 |
|---|---|---|
| API runtime | API 运行时 | 参数验证、对象语义、调用分派 |
| UMD | 用户模式驱动 | 编译、状态跟踪、命令构建 |
| KMD | 内核模式驱动 | 内存、调度、安全、复位 |
| command buffer | 命令缓冲 | 包格式、空间管理、提交边界 |
| context | 上下文 | 状态隔离、地址空间、调度实体 |
| fence | 完成栅栏 | 谁写信号、谁等待、完成粒度 |
面向 UMD/KMD 的阅读提示
- 在 UMD 中寻找“draw/dispatch 的最终汇合点”,看它如何选择 pipeline、收集资源依赖并预留命令空间。
- 沿 submit ioctl 或等价接口进入 KMD,记录提交对象、BO 列表、同步对象和队列标识。
- 区分三种完成:命令已写入用户态缓冲、提交已被内核接受、GPU 已真正完成。
- 遇到
flush时,不要默认它等于 CPU 阻塞等待;检查它究竟只是封包/提交,还是同时等待。
自测
- 为什么把每个 API 调用都做成一次内核调用会很慢?
- 应用释放资源时,如果 GPU 仍可能引用它,哪一层必须延长其真实生命周期?
- 首帧卡顿可能来自哪些“延迟到 draw 才完成”的工作?
本篇总结
把图形驱动理解为“异步命令生产者 + 资源/同步管理器”,比把它理解为一组 API 函数的机械实现更接近真实系统。

浙公网安备 33010602011771号