1. Introducation
图形 API 是一套预定义的函数、数据结构、协议和工具的集合。它是连接应用程序和图形硬件的桥梁。
图形 API 规定了应用程序如何向 GPU 发出命令,以及 GPU 需要提供哪些功能。基于这份相同的协议, GPU 制造商和应用程序的开发者开发的产品就能兼容。
如果没有图形 API,那么意味着开发者不得不为每一款特定的 GPU 编写机器码,这是难以想象的。
通常,图形 API 通过管理以下内容,协同完成从数据到图像的转换:
-
资源
- 资源指所有需要送入 GPU 处理的数据,包括几何数据(顶点、索引)、纹理、着色器程序等
- 图形 API 提供接口,让应用程序在 GPU 上创建、填充、销毁这些资源,并管理它们在 GPU 内存中的分配与布局
-
着色器
- 着色器是运行在GPU上的微型程序,是渲染管线的可编程核心
- 图形 API 提供专用的着色器语言,并负责将开发者编写的着色器代码编译成 GPU 能理解的二进制格式,最终链接成一个完整的着色器程序供管线使用
-
渲染管线与状态机
-
渲染管线是一个固定的、可配置的流水线,定义了从顶点数据到像素的处理步骤
-
图形 API 允许开发者配置流水线。包括绑定着色器、设置固定功能状态(如深度测试、混合模式)、绑定资源(如纹理)等。这些配置构成了一个状态机,现代图形 API 往往要求开发者将大部分管线状态预先打包成一个管线状态对象,以提升效率
广义状态机:其后续输出依赖于之前设置的上下文(状态),都可以被视为状态机。
传统图形 API 如 OpenGL 等是一种单一的、全局的、隐式管理的巨型状态机,易用但危险,开销较大。
而现代图形 API 如 Vulkan 将状态打散、显式化,分离成多个离散的、可预编译的状态对象(如
VkPipeline)
-
-
命令提交与同步
- 命名提交是 CPU 向 GPU 发送绘制指令的过程,由于 CPU 和 GPU 是独立、异步工作的,必须妥善处理它们之间的协同。
- 图形 API 主要提供两种机制处理命令提交与同步:
- 命令缓冲区:让 GPU 提前将一系列绘制命令录制到缓冲区中(可在多个线程上并发录制),然后高效地一次性提交给 GPU 执行
- 同步对象:提供栅栏、信号量等工具,确保 GPU 不会在 CPU 完成数据写入前就开始读取,或者不会在上一帧完成渲染前就开始下一帧的渲染,防止数据竞争和视觉错误
传统的图形 API 如 OpenGL,DirectX11 等秉承高抽象、易用的设计哲学,在减轻开发者负担的同时也带来了不可预测的性能开销。
而现代图形 API 如 Vulkan,DirectX12,Metal,WebGPU 等要求开发者必须显式声明和管理一切,使得 CPU 的开销变得可预测。
2. Physical Device
在现代图形 API 中,Physical Device(物理设备)指的是图形处理器硬件——显卡。
传统图形 API 高度抽象化,由驱动程序管理底层细节(内存,同步,多 GPU 等)。而现代图形 API 将控制权交还给开发者,为了实现这一点,首先需要暴露硬件本身,Physical Device 就是这个这个暴露的接口,开发者可以查询物理设备的信息,如支持特性、资源限制等。
当查询一个 Physical Device 时,主要获取以下几类关键信息:
-
属性与标识:
- 设备名称(如 “NVIDIA GeForce RTX 4080”)、供应商ID(Vendor ID)、设备ID
- API 版本、驱动程序版本
- 设备类型:集成显卡(
INTEGRATED_GPU)、独立显卡(DISCRETE_GPU)、虚拟GPU还是CPU
-
功能特性
- 图形管线特性:支持哪些着色器阶段、顶点/实现属性上限等
- 计算能力:支持的计算着色器工作组大小等
- 可选功能:API 核心规范只保证一个最低标准,所有的高级功能(如几何着色器,曲面细分,条件渲染,片段存储读写,光线追踪,网格着色器)都以特性或者拓展的形式给出,开发者需要显式查询并启用特定的功能
- 纹理/缓冲区格式支持:哪些像素格式可用于渲染、采样或存储
-
队列家族(Queue Families)
- 队列家族揭示了 GPU 内部的并行执行架构,一个物理设备包含多个队列家族,每个家族专精于不同类型的工作负载(图形绘制,通用计算,异步数据传输,视频编解码)
- 查询结果包括:
- 每个家族支持的操作类型(
GRAPHICS,COMPUTE,TRANSFER,SPARSE_BINDING等) - 每个家族可用的队列数量,这决定了可并行提交的任务流数量
- 每个家族是否支持与特定的显示表面进行呈现操作(Presentation)
- 每个家族支持的操作类型(
- 正确的队列选择是并行渲染、计算与传输重叠的关键
-
内存属性
物理设备将内存组织为内存堆和内存类型:
-
内存堆:代表物理内存池(如独立的显存、共享的系统内存)。查询会提供每个堆的大小和标志
-
内存类型:存在于堆中,是具有特定属性组合的逻辑划分,如:
-
DEVICE_LOCAL_BIT:设备本地,访问速度最快,适合纹理和顶点缓冲区 -
HOST_VISIBLE_BIT:主机(CPU)可见,适合频繁更新的动态数据。 -
HOST_COHERENT_BIT:CPU与GPU缓存自动保持一致,简化操作但可能牺牲性能。 -
HOST_CACHED_BIT:主机端缓存,适合CPU随机读取的数据
-
-
根据资源访问模式选择正确的内存类型(如将常变数据放在
HOST_VISIBLE内存中,将静态纹理放在DEVICE_LOCAL内存中)是应遵循的性能优化原则
-
-
格式支持
查询特定纹理或缓冲区格式,对于给定的像素格式(如
VK_FORMAT_R8G8B8A8_UNORM)检查其支持的特性:-
SAMPLED_IMAGE:能否在着色器中采样。 -
STORAGE_IMAGE:能否在计算着色器中作为存储图像读写。 -
COLOR_ATTACHMENT:能否作为渲染目标。 -
BLEND:在作为渲染目标时是否支持混合
-
-
交换链支持
此支持并非核心 API 的一部分,而是通过扩展实现。
- 确认设备支持所需的窗口系统集成拓展(如
VK_KHR_swapchain) - 查询设备与特定窗口表面的兼容性,包括
- 支持的表面格式(像素格式,色彩空间)
- 可用的呈现模式(立即模式、垂直同步
FIFO、无撕裂的MAILBOX等) - 表面能力(最小/最大图像数量、当前交换链图像分辨率范围等)
- 确认设备支持所需的窗口系统集成拓展(如
-
高级架构特性
- 多GPU支持:设备组(device groups)和链接设备(linked devices)的能力,用于协同多个GPU
- 管线缓存:通过
pipelineCacheUUID实现跨进程的管线缓存,加速管线创建 - 稀疏内存:支持稀疏纹理和缓冲区,允许部分 residency 和动态映射,适用于流式加载大型纹理
- 保守光栅化(Conservative Rasterization)、光栅顺序视图(Rasterization Order View)等高级渲染特性
- 子组(Subgroup)操作:支持Wave操作,对计算着色器优化至关重要
- 性能查询:支持时间戳和性能查询,用于性能分析
