《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 的阅读提示

  1. 在 UMD 中寻找“draw/dispatch 的最终汇合点”,看它如何选择 pipeline、收集资源依赖并预留命令空间。
  2. 沿 submit ioctl 或等价接口进入 KMD,记录提交对象、BO 列表、同步对象和队列标识。
  3. 区分三种完成:命令已写入用户态缓冲、提交已被内核接受、GPU 已真正完成。
  4. 遇到 flush 时,不要默认它等于 CPU 阻塞等待;检查它究竟只是封包/提交,还是同时等待。

自测

  • 为什么把每个 API 调用都做成一次内核调用会很慢?
  • 应用释放资源时,如果 GPU 仍可能引用它,哪一层必须延长其真实生命周期?
  • 首帧卡顿可能来自哪些“延迟到 draw 才完成”的工作?

本篇总结

把图形驱动理解为“异步命令生产者 + 资源/同步管理器”,比把它理解为一组 API 函数的机械实现更接近真实系统。

posted @ 2026-10-26 21:56  Sailxiv  阅读(3)  评论(0)    收藏  举报