图形 API 设备、管线状态与资源
Graphics API Devices, Pipeline State & Resources
所有图形 API(OpenGL、DirectX、Vulkan、Metal)都围绕同一组概念:代表 GPU 的设备、描述各阶段配置的管线状态、承载数据的资源及其视图。GEA 把 API 分成高层(OpenGL、D3D11 及更早)和底层(D3D12、Vulkan、Metal、PlayStation 的 Gnm),并介绍 D3D11 的特性级别、设备上下文与延迟上下文、D3D12 的命令列表、PSO 和根签名。本页按 GEA 的整理讲这些跨 API 的共同概念,不是任何一个 API 的教程。
高层 API 与底层 API(卷 II 11.4.4)
CPU 与 GPU 的接口非常复杂:GPU 本身复杂;GPU 和显存在插在 PCIe 插槽上的扩展卡上,数据要先由 CPU 写进主内存再经 PCIe 上传;CPU 与 GPU 是独立处理器需要同步;GPU 内部大量并发的着色器调用有时也要用无锁原子操作同步。
| 高层 API | 底层 API | |
|---|---|---|
| 例子 | OpenGL、DirectX 11 及更早 | DirectX 12、Vulkan、Metal、PS4/PS5 的 Gnm |
| 目标 | 易用:程序员只管准备数据、配置管线、提交几何 | 最大运行时性能,暴露硬件的全部能力 |
| 并发和显存 | API 在底层替你处理 | 程序员自己负责并发与内存管理 |
| 问题 | 不够灵活(几乎无法控制显存管理);简单的逻辑模型隐藏了硬件细节,实现中常有难以消除的低效 | 难用得多 |
原书问题(11.4、11.4.4):
- GEA 说 CPU 与 GPU"需要用互斥锁 (mutex) 同步"。CPU 与 GPU 之间不能用 mutex(GPU 不参与操作系统的锁),实际用的是 fence(围栏,GPU 执行到某处时写入一个值,CPU 等待或轮询)和 semaphore(队列之间),如 D3D12 的
ID3D12Fence::Signal/SetEventOnCompletion、Vulkan 的VkFence/VkSemaphore(MS Learn "Synchronization and Multi-Engine";Vulkan 规范第 7 章 "Synchronization and Cache Control")。- GEA 说"任何图形 API 都能配置、控制、编程市场上任何 GPU"。实际受平台限制:Metal 只在 Apple 平台、Direct3D 只在 Windows/Xbox、PlayStation 只用索尼自己的 API,Vulkan 也不能用于 PlayStation 和 Apple(后者需借助 MoltenVK 翻译层)。
- GEA 说"多数图形 API 都提供参考实现"。Direct3D 确实有参考光栅器(REF)和 WARP;但 OpenGL 和 Vulkan 没有官方参考实现,Khronos 靠一致性测试集 (CTS) 而非参考实现来检验驱动(Khronos "Vulkan Conformance Test Suite")。
GEA 第 11 章主要讲高层 API 的工作方式,只在必要处点出底层 API 的差别。对照 可编程着色器与 GPU 管线 中 RTR4 的「低开销 API」一节。
特性级别(卷 II 11.4.5.3)
- 早期靠查询 caps 位适配硬件,组合繁多、易出兼容性 bug。DirectX 11 引入特性级别 (feature level),把硬件特性集与 DirectX 版本归成一条线性序列:GPU 支持某级别,就保证具备该级别所有必需能力;每一级都是前一级的严格超集。
- 初始化时先尝试按最高期望级别创建 Direct3D 设备,成功即说明 GPU 支持该级别全部特性,失败就降一级重试。
- GEA 表 11.2 的部分内容("可选"表示该级别下为可选特性):
| 特性 | 引入或可选 | 成为必需 |
|---|---|---|
| 几何着色器、流输出 | — | 10_0 |
| 外壳/域着色器与硬件细分器 | — | 11_0 |
| 计算着色器 | 10_0 起可选 | 11_0 |
| BC4/BC5 纹理压缩 | — | 10_0 |
| BC6H/BC7 纹理压缩 | — | 11_0 |
| 多渲染目标 (MRT) | 11_0 可选 | 11_1 |
| 保守光栅化 | 11_1 引入 | 12_1 |
| 光栅顺序视图 (ROV) | 11_1 起可选 | 12_1 |
| 网格着色器管线 | 11_0 起可选 | 12_2 |
| 硬件光线追踪 | 11_0 起可选 | 12_2(Tier 1.1) |
| 可变着色率 (VRS) | 11_0 起可选 | 12_2(Tier 2) |
- 着色器模型:9_x 为 2.0,10_0 为 4.0,10_1 为 4.x,11_0 到 12_1 为 5.1,12_2 为 6.5(同表)。
- 可变着色率让程序员控制图像不同区域的像素着色器调用频率,把资源多分给细节丰富的区域、少分给细节少的区域,以性能换画质或反之。
(表中 MRT 一行按 GEA 表 11.2 原样整理,书中正文说 MRT 用于输出 G-buffer,见 像素着色器。)
原书问题(表 11.2、11.4.5.3):
- MRT:表 11.2 写 MRT 在 10_x 及以下"不支持"、11_0 可选、11_1 起必需,与微软文档不符。同时绑定的渲染目标数是 9_1/9_2 为 1、9_3 为 4、10_0 起为 8(MS Learn "Direct3D feature levels" 特性表 "Simultaneous Render Targets" 一行);也就是说 MRT 从 9_3 起就有,10_0 起支持 8 个。
- 根签名:GEA 说"直到 11_0 资源都直接绑定到着色器阶段,根签名提供无绑定模型,通过一张叫根签名的全局描述符表把描述符交给着色器"。根签名是 D3D12 API 的对象(D3D12 设备最低就是 11_0 级别),它不是描述符表本身,而像一个"函数签名":定义着色器期望的根参数布局(根常量、根描述符、指向描述符堆的描述符表),"并不定义实际的内存或数据";描述符本身存放在描述符堆中(MS Learn "Root Signatures Overview")。真正"着色器可按索引访问堆中任意资源"的无绑定写法是 Shader Model 6.6 的
ResourceDescriptorHeap[](MS Learn / DirectX-Specs "HLSL Dynamic Resources")。下文"根签名与无绑定"一节按 GEA 原意记录,读时以此为准。- 表中"Variable Shading Rate (VSR)"在 D3D 中的正式名称是 Variable Rate Shading (VRS)。
设备(卷 II 11.4.7)
API 通过逻辑软件接口暴露物理 GPU(或纯软件参考实现):
| API | 逻辑设备 | 物理设备信息 |
|---|---|---|
| DirectX(基于 COM,类名多以 I 开头表示接口) | D3D11 ID3D11Device、D3D12 ID3D12Device;可初始化为硬件设备、参考设备或 WARP(软件光栅器,用于无 GPU 平台或开发时模拟 GPU) | DXGI 的 IDXGIAdapter(D3D11)/ IDXGIAdapter1(D3D12) |
| OpenGL(C 风格) | 不暴露逻辑设备概念,初始化后直接调用函数,可把整个 API 看作"逻辑设备" | glGetString 查询 |
| Vulkan(C 风格) | VkDevice 句柄 | VkPhysicalDevice,配合 vkGetPhysicalDeviceProperties、vkGetPhysicalDeviceFeatures 等 |
逻辑设备负责创建缓冲、纹理、状态对象和着色器等资源。
管线状态与上下文(卷 II 11.4.8)
下图对比三种暴露管线状态的方式。
flowchart LR
subgraph G["全局状态"]
GL["OpenGL: 全局变量"]
IC["D3D11 立即上下文: 单例"]
end
DC["D3D11 延迟上下文: 多线程录制命令"] -->|"只能经立即上下文提交"| IC
subgraph C["命令列表为中心"]
CL["D3D12 / Vulkan / Metal: 直接构建并提交命令列表"]
end
IC --> GPU["GPU 按序执行命令"]
GL --> GPU
CL --> GPU- 全局状态:OpenGL 把状态当全局变量;D3D11 用
ID3D11DeviceContext,但立即上下文是单例,状态同样全局。底层都是在 CPU 端构建命令列表交给 GPU 按序执行。 - 延迟上下文:D3D11 可创建多个,只把命令录进内存,必须通过立即上下文提交;只有一个线程能使用立即上下文,其他线程可并发使用延迟上下文。
- 命令列表为中心:D3D12、Vulkan、Metal 没有设备上下文,一切都是构建并提交命令列表,引擎可以自由设计多线程结构(见 多线程渲染与任务系统)。
- 旧 API 状态切换为什么贵、PSO 如何解决,见 绘制调用与状态切换优化 中的 GEA 部分。
资源与资源视图(卷 II 11.4.9)
- DirectX 把数据块统一称为资源;OpenGL 按类型分为各种缓冲对象(纹理对象、帧缓冲对象、通用缓冲对象等);Vulkan 分为原始数据的 buffer 和位图数据的 image。D3D11 的流程:用设备创建资源 → 填数据 → 经设备上下文以资源视图绑定到一个或多个阶段 → 提交 draw call,管线读写视图 → 用完调用资源的
Release释放。 - 缓冲是连续内存:顶点缓冲(可由
CreateInputLayout从多个缓冲拼出顶点结构)、索引缓冲(16 或 32 位整数,三个一组成三角形、两个一组成线段)、常量缓冲(一次 draw call 内所有调用不变的数据,如变换和投影矩阵,可看作只含一个顶点的顶点缓冲)。 - 纹理是特殊的缓冲,可为 1D、2D、3D,可带 mipmap;D3D11 用
ID3D11Texture2D等按维度区分的接口,D3D12 统一为带纹理配置的ID3D12Resource,Vulkan 叫 image(VkImage)。还可建纹理数组:1D 纹理数组看似 2D 纹理,但 GPU 不会在数组元素之间插值;六元素 2D 纹理数组可作立方体贴图。 - 有类型与无类型:有类型资源创建时就给出数据类型和布局,驱动可预先配置,效率略高;无类型资源绑定时才给出,便于在不同场合复用。
- 资源视图:资源从不直接绑定到阶段,而是通过视图——描述如何从资源中取数据、如何解释(例如把 32 位数组解释为 RGBA 颜色),还能把资源的多个切片拼成一个连续缓冲。为同一资源建多个视图就能绑定到多个阶段,例如一个阶段当浮点数组读、另一个当无符号整数读。
| 视图 | 用途 |
|---|---|
| RTV 渲染目标视图 | 把纹理当渲染目标(颜色平面或 G-buffer) |
| DSV 深度/模板视图 | 把纹理当深度/模板平面 |
| CBV 常量缓冲视图 | 跨多次调用不变的数据,如变换矩阵 |
| VBV / IBV | 绑定顶点缓冲(含属性布局)/ 索引缓冲 |
| SRV 着色器资源视图 | 对大块缓冲或纹理只读访问 |
| UAV 无序访问视图 | 着色器随机读写缓冲或纹理 |
| ROV 光栅顺序视图 | 类似 UAV,但按三角形提交顺序调用绑定它的像素着色器,让 OIT 可靠工作,有性能代价(见 透明渲染与 Alpha 混合) |
| 纹理采样器 | 严格说不是视图,但控制采样方式:寻址模式、clamp 的边框颜色、过滤模式、LOD 偏移 |
缓冲可绑定为 CBV、SRV、UAV、IBV、VBV;纹理只能绑定为 SRV、UAV、DSV、RTV。
原书问题(11.4.9):
- GEA 说 Metal 中纹理"叫 layer,用
CAMetalLayer表示"。Metal 的纹理类型是MTLTexture(由MTLDevice按MTLTextureDescriptor创建);CAMetalLayer是 Core Animation 的图层,用来向屏幕提供可绘制对象 (CAMetalDrawable),相当于交换链,不是纹理(Apple Developer Documentation "MTLTexture"、"CAMetalLayer")。- GEA 说 IBV"把索引缓冲绑定到顶点着色器"、VBV 绑定到顶点着色器并"描述顶点结构中属性的内部布局"。顶点缓冲和索引缓冲都绑定到输入装配器(D3D11 的
IASetVertexBuffers/IASetIndexBuffer,D3D12 的IASetVertexBuffers/IASetIndexBuffer),顶点着色器看不到索引;D3D12 的D3D12_VERTEX_BUFFER_VIEW只含地址、大小和步长,属性布局由输入布局(D3D12_INPUT_LAYOUT_DESC,属于 PSO)描述(MS Learn "Input-Assembler Stage"、"D3D12_VERTEX_BUFFER_VIEW structure")。另外 CBV/VBV/IBV 是 D3D12 的术语,D3D11 中常量、顶点、索引缓冲是直接绑定的,没有对应的视图对象。- ROV 并不改变像素着色器的调用顺序;它保证的是不同图元对同一像素位置的 ROV 读写按图元提交顺序发生,着色器其余部分仍可并行乱序执行(MS Learn "Rasterizer Order Views")。
- "带 mipmap 的二维纹理尺寸限制为 2 的幂"与 D3D10 起支持任意尺寸 mipmap 纹理的规范不符,见 纹理映射管线 中的 GEA 部分。
根签名与无绑定(卷 II 11.4.9.3)
- D3D12 中资源视图不再直接绑定到管线阶段,着色器可以读写任意数量的视图。为此引入资源描述符——类似指向视图的指针或引用,放在着色器可直接访问的全局描述符表中。GEA 把这张全局表称为根签名 (root signature)(这一称呼不准确:全局表是描述符堆,根签名只描述布局,见上方原书问题)。
- 在特性级别 11_0 之前资源直接绑定到着色器阶段;根签名提供所谓无绑定资源模型,所有着色器都能访问所有资源(卷 II 11.4.5.3)。对照 纹理映射管线 中 RTR4 介绍的无绑定纹理。
取舍
- 高层 API 上手快,适合原型和教学,但显存管理和多线程受限,状态切换代价高;底层 API 能榨出硬件性能、允许多线程构建命令,但并发与内存管理全部由程序员负责(卷 II 11.4.4)。
- 特性级别让兼容性判断变成"能否按某级别创建设备",比逐个检查 caps 位简单得多;不过表 11.2 中仍有大量"可选"项,说明特性级别只规定了必需能力的下限,并不能描述所有硬件差异。
出处
- Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
- Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 11 章 11.4.4–11.4.5、11.4.7–11.4.9 节