Vulkan 整体架构
# Vulkan 整体架构
在学习 Vulkan 之前,需要对 Vulkan 的整体架构有一定了解,而不是一开始就陷入 Vulkan API 的汪洋大海!

Vulkan 是一个分层架构,由 Vulkan Application、Loader、Layer 和 ICDs (Installable Client Drivers) 四个核心组件组成。这种分层设计使得 Vulkan 具有高度的灵活性和可扩展性,同时保持了良好的性能。
# 1. Vulkan Application
Vulkan Application 代表了使用 Vulkan API 的应用程序,例如:
- 3D 游戏(如《DOOM》、《Quake》等)
- 渲染引擎(如 Unreal Engine、Unity 等)
- 计算软件(如机器学习推理、科学计算等)
- 图形工具(如建模软件、视频编辑器等)
# 主要特点
- 作用:应用程序通过调用 Vulkan 的 API 函数来发出指令(比如"绘制一个三角形"、"分配显存"或"执行计算着色器")。
- 交互方式:如图所示,多个应用程序可以同时运行,它们并不直接与硬件对话,而是将指令发送给中间的 Loader。这种设计使得应用程序无需关心底层硬件的具体实现细节。
- 职责:应用程序负责管理自己的渲染管线、资源分配、命令缓冲区等高级逻辑。
# 2. Loader
Loader 是 Vulkan 架构中的核心调度组件,充当"调度员"的角色。
# 工作原理
Application 在一端直接和 Loader 打交道,Loader 的另一端连接 ICDs。在 Application 和 ICDs 之间,Loader 可以插入一系列可选的 Layers。Loader 负责和各个 Layer 交互,并且支持多 GPUs 和其驱动。
任何一个 Vulkan API 函数调用都会经过 Loader、Layers 和 ICDs 的调用链。Loader 负责将 API 调用分发给合适的 Layers 和 ICDs。Vulkan 对象模型允许 Loader 插入 Layers 层,组成调用链上的一环,并最终将 Vulkan API 调用传递给 ICD。
# 主要职责
Loader 的主要职责包括:
- 驱动管理:加载和管理一个或多个 ICDs,且保证 ICD 间不相互干扰
- Layer 支持:支持 Vulkan Layer,Layer 是可选的,可以在运行时动态加载
- API 分发:管理 API 的入口点,将应用程序的 API 调用分发给正确的 Layer 和驱动程序
- 性能优化:以尽可能低的方式影响 Vulkan 应用性能,最小化开销
- 硬件抽象:屏蔽底层不同硬件厂商的差异,让应用程序只需要面对一个统一的接口
# Android 平台实现
在 Android 平台上,Vulkan Loader 的实现位于 frameworks/native/vulkan/libvulkan/ 目录下。在这个目录下,你通常会关注以下几个关键文件:
- loader.cpp:这是 Loader 的核心逻辑实现,负责查找驱动、加载驱动以及管理 Layer
- api.cpp / api_gen.cpp:定义了 Vulkan API 的入口点(Entry Points)
- swapchain.cpp:处理与 Android 显示系统(SurfaceFlinger/ANativeWindow)交互的交换链逻辑(这是 Android 独有的部分)
# 系统库位置
当 Android 系统编译完成后,Vulkan Loader 会被编译成一个动态链接库文件 libvulkan.so。在手机文件系统中的位置:
- 64位系统:
/system/lib64/libvulkan.so - 32位系统:
/system/lib/libvulkan.so
应用程序在调用 Vulkan API 时,链接的就是这个系统库。
# 驱动查找机制
Loader 会在以下目录下寻找符合特定命名规则的动态库(驱动):
- 64位系统:
/vendor/lib64/hw/ - 32位系统:
/vendor/lib/hw/
文件名通常是 vulkan.<platform>.so。例如:
- 在高通骁龙设备上,驱动通常是
vulkan.adreno.so或vulkan.qcom.so - 在 Mali GPU 设备上,驱动通常是
vulkan.mali.so - 在 PowerVR GPU 设备上,驱动通常是
vulkan.pvr.so
# 3. Vulkan Layer
Vulkan Layer 是 Vulkan 架构中的可选组件,可以增强 Vulkan 系统的功能。
# 基本概念
Layers 是可选组件,可以拦截、修改 Vulkan API 调用。Layers 是作为动态库(lib)实现的,可以通过不同方式启用,并且在 vkCreateInstance 调用时被加载。
每个 Layer 可以选择拦截任何 Vulkan API 函数。一个 Layer 不需要拦截所有 Vulkan API 函数,Layer 可以选择拦截所有已知的 Vulkan API,也可以只拦截一条特定的 Vulkan API。
# 工作原理
Layer 处于应用程序和显卡驱动之间,能够拦截、查看、甚至修改流经它的所有 Vulkan API 指令。这意味着,通过自定义 Layer,不需要修改应用程序的一行源代码,也不需要重新编译应用,你就可以通过挂载 Layer 来改变程序的行为。
# 调用链机制
当应用程序调用 Vulkan API 时,调用顺序通常是:
Application → Layer 1 → Layer 2 → ... → Layer N → ICD → GPU
每个 Layer 都可以:
- 前处理:在 API 调用传递给下一个 Layer 或 ICD 之前进行处理
- 后处理:在 API 调用返回给上一个 Layer 或 Application 之后进行处理
# 应用场景
通过自定义 Layer 可以实现非常多的特性,这里举几个例子:
# 1. 调试与代码验证(最基础的功能)
这是 Layer 机制诞生的初衷。
- API 参数检查:这是官方
VK_LAYER_KHRONOS_validation做的事。你可以写一个 Layer 来检查应用是否传递了非法的枚举值、空指针,或者是否在未绑定管线的情况下绘制。 - 内存泄漏检测:拦截所有的
vkAllocateMemory和vkFreeMemory,记录分配堆栈。如果程序退出时还有未释放的内存,Layer 就可以报警。 - 对象生命周期追踪:确保应用没有使用已经被销毁的 Buffer 或 Image。
# 2. 性能分析与 Profiling(开发者最爱)
你可以通过 Layer 深入了解应用的性能瓶颈。
- FPS 统计与显示:计算
vkQueuePresentKHR的调用间隔,实时计算帧率。 - API 调用统计:统计一帧内调用了多少次
vkCmdDraw(绘制指令),如果数量过多(比如几千次),提示开发者使用 Instance Rendering(实例化渲染)进行优化。 - 耗时分析:在 API 调用前后打点计时,找出哪个 Vulkan 函数最耗时(主要针对 CPU 端耗时)。
- GPU 时间戳插入:自动在 Command Buffer 中插入
vkCmdWriteTimestamp,精确测量 GPU 执行某段渲染指令的时间。
# 3. 渲染增强与视觉修改(玩家与 Modder 最爱)
这是很多游戏工具和"外挂"的原理。
- OSD (On-Screen Display) 叠加层:像 Steam 游戏内覆盖、Discord 浮窗、MSI Afterburner,本质上都是通过 Layer 在每一帧渲染结束前(Present 之前),向 Command Buffer 中插入绘制 UI 的指令。
- 后处理注入 (ReShade 原理):拦截呈现操作,获取当前帧的图像,应用自定义的 Shader(如色彩校正、锐化、Bloom 泛光),然后再显示出来。
- 线框模式 (Wireframe):强制修改光栅化状态(Rasterization State),将填充模式改为线框模式,用于查看模型结构(或者被用作透视作弊)。
# 4. 调试工具与抓帧 (Capture & Replay)
- 截帧工具 (RenderDoc):RenderDoc 的核心就是一个复杂的 Layer。它拦截并记录一帧内所有的 API 调用和资源数据,保存下来。之后可以在另一台机器上"回放"这些指令,完美复现渲染过程。
- 截图与录屏:在 Swapchain 展示之前,将图像数据拷贝到 CPU 内存并保存为图片或视频。
# 5. 功能模拟与兼容性 (Workarounds)
- 功能降级或模拟:如果某个旧显卡不支持某个 Vulkan 扩展,你可以写一个 Layer 来用其他指令模拟这个功能,让应用以为显卡支持该扩展。
- 跨平台转换:虽然不是典型的 Layer,但类似 MoltenVK 的逻辑也可以通过 Layer 实现——将 Vulkan 指令动态翻译成 Metal (macOS) 或 DX12 指令。
# 小结
自定义 Layer 赋予了你**"中间人攻击" (Man-in-the-Middle)** 的能力:
- 观察 (Observe):看应用发了什么指令
- 拦截 (Intercept):阻止某些指令发送给驱动
- 修改 (Modify):偷梁换柱,修改指令参数
- 注入 (Inject):无中生有,插入额外的渲染或计算指令
# 4. Installable Client Drivers (ICD)
ICD (Installable Client Drivers) 指的是可安装客户端驱动程序,也就是我们通常说的显卡驱动(由 NVIDIA、AMD、Intel、Qualcomm、ARM 等厂商提供)。
# 主要职责
- 指令翻译:Loader 将经过筛选或验证的指令传递给 ICD。ICD 负责将这些标准的 Vulkan 指令翻译成特定 GPU 能够理解的机器指令。
- 硬件抽象:ICD 封装了底层硬件的具体实现细节,向上提供统一的 Vulkan 接口。
# 多驱动支持
Vulkan 允许系统中有多个 ICD,每个 ICD 支持 1 个或者多个设备(VkPhysicalDevice)。这意味着系统里可以同时安装不同厂商的驱动,例如:
- 桌面平台:既有集显(Intel HD Graphics)也有独显(NVIDIA GeForce 或 AMD Radeon)
- 移动平台:可能同时存在多个 GPU(如 ARM Mali + 其他协处理器)
Loader 负责发现可用的 Vulkan ICDs。给定一系列可用 ICDs,Loader 会枚举所有 Physical Device,应用程序可以通过 vkEnumeratePhysicalDevices 获取所有可用的物理设备。
# 5. Vulkan Physical Device
Vulkan Physical Device 代表实际的物理硬件设备(GPU)。每个 ICD 可以控制一个或多个物理设备。
# 设备关系
- 一对一关系:一个 ICD 控制一个物理设备(最常见的情况)
- 一对多关系:一个 ICD 控制多个物理设备(例如双显卡交火、SLI/CrossFire 配置)
图中右上角展示了一个 ICD 控制两个物理设备的情况(例如双显卡交火),而右下角展示了一个 ICD 控制一个设备。
# 设备枚举
应用程序可以通过以下方式获取物理设备信息:
vkEnumeratePhysicalDevices:枚举所有可用的物理设备vkGetPhysicalDeviceProperties:获取物理设备的属性(名称、类型、驱动版本等)vkGetPhysicalDeviceFeatures:查询物理设备支持的功能vkGetPhysicalDeviceQueueFamilyProperties:获取队列族属性
# 设备类型
物理设备可以是以下类型之一:
- VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:集成 GPU(如 Intel HD Graphics)
- VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:独立 GPU(如 NVIDIA GeForce、AMD Radeon)
- VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:虚拟 GPU
- VK_PHYSICAL_DEVICE_TYPE_CPU:CPU(软件渲染)
- VK_PHYSICAL_DEVICE_TYPE_OTHER:其他类型
# 架构总结
Vulkan 的整体调用流程如下:
Application
↓ (发出 Vulkan API 调用)
Loader
↓ (可选:通过 Layer 进行校验、分析、修改)
ICD (厂商驱动)
↓ (翻译成 GPU 指令)
Physical Device (GPU)
↓ (执行渲染或计算)
2
3
4
5
6
7
8
# 数据流向
- Application 发出指令(如
vkCmdDraw、vkAllocateMemory等) - Loader 接收指令,可能通过 Layer 进行校验、分析或修改
- Loader 将指令分发给对应的厂商驱动 ICD
- ICD 将标准的 Vulkan 指令翻译成特定 GPU 能够理解的机器指令
- Physical Device (GPU) 执行渲染或计算任务
# 设计优势
这种分层架构的设计带来了以下优势:
- 硬件抽象:应用程序无需关心底层硬件的具体实现
- 灵活扩展:通过 Layer 机制可以灵活扩展功能
- 多驱动支持:系统可以同时支持多个厂商的 GPU 驱动
- 性能优化:Loader 和 Layer 的开销被控制在最低水平
- 调试友好:Layer 机制使得调试和性能分析变得容易
# 参考资料
- VULKAN入门学习(一)--- Loader和Layer (opens new window)
- 《The Modern Vulkan CookBook》
- 《Vulkan学习指南》
- Vulkan 官方文档 (opens new window)
- Vulkan Loader 源码 (opens new window)