事情是这样的。我手头有个图形调试的需求,要在 Vulkan 应用外面挂一层代理,把 API 调用全部转发一遍,顺带做参数记录和时序分析。调研了一圈开源方案,最后锁定了 proxy-GS 这个项目。名字里的 GS 是 Graphics Stack 的意思,它起的作用就是在应用和驱动之间插一层,把 Vulkan 调用"过一手"。项目本身不算大,但真正动手编译的时候,我在依赖版本和工具链上耗了不少时间,有几个报错查起来相当绕。这篇就是那次编译的全过程记录,包括环境准备、完整编译流程、三个典型报错的排查链路,以及编译成功之后的验证方法。如果你也要给 Vulkan 应用做调用拦截、数据录制或者兼容转译,这篇应该能帮你少走几趟弯路。
1. 先说清楚:proxy-GS 是谁,为什么要自己编译
1.1 图形栈代理的核心理念
Vulkan 从诞生那天起就是一套"显式 GPU 控制"的 API。它和 OpenGL 最本质的区别在于,OpenGL 驱动内部帮你维护了大量状态,而 Vulkan 把所有状态都摊开给应用层管理:命令缓冲区、渲染通道、描述符、管线对象,全部由开发者自己安排。这种设计给了应用最大的控制权,但也带来一个副产品——所有调用都发生在应用进程内部,外部很难插手观察。
proxy-GS 的定位就是解决"插手观察"这个问题。它编译出一个动态库,这个库导出的符号和 Vulkan loader 完全一致。应用通过 loader 机制加载它之后,所有 vkCreateInstance、vkCreateDevice、vkQueueSubmit、vkCmdDraw 这类函数调用都会先进入 proxy 层。proxy 层拿到完整的参数列表,可以原样转发给真实驱动,可以篡改参数,可以只记录不转发,也可以在特定调用上挂自己的处理逻辑。
实现上一层代理,技术上靠的是 Vulkan 的 dispatch 机制。每个 Vulkan 对象(instance、device、queue、command buffer)内部都有一个 dispatch table,里面是函数指针数组。proxy 层做的就是把这个表整体替换成自己的实现,转发时再调用原表里的函数指针。这个机制本身并不复杂,真正麻烦的是把几万个函数签名全部对齐,以及处理层与层之间的对象生命周期。proxy-GS 的价值在于它把这套骨架搭好了,你要做的是往里面填业务逻辑。
1.2 典型应用场景
从我实际接触到的需求看,proxy-GS 这层代理有四个方向特别实用。
第一是调用录制与回放。在 proxy 层把所有 vkQueueSubmit 里面提交的命令缓冲区原始数据抓下来,存成文件,之后可以脱离真实应用单独回放。这在 GPU 驱动调试、性能分析、图形问题复现时非常好用,很多商用的帧分析工具底层就是这套逻辑。
第二是参数校验和故障注入。Vulkan 规范非常严格,很多错误只在特定硬件上暴露。通过 proxy 层在每次调用前检查参数合法性,或者人为制造边界条件,可以提前发现应用层的隐患。
第三是跨 API 转译。这个更激进一点,把 Vulkan 调用转成 DirectX 或者 Metal——需要连管线编译器和内存管理一起接管,工程量不小。但 proxy-GS 的分层架构让这种改造有了一个相对干净的切入点。
第四是做自动化测试的观察点。测试框架不需要每个用例都写死对结果的断言,直接检查 proxy 层记录的调用序列是否符合预期就行。
1.3 为什么不直接用现成的二进制
这也是很多人问我的第一个问题。Vulkan 生态里其实有一个官方的 Vulkan Layers 机制,Khronos 也提供了 validation layers 这样的现成组件。为什么还要自己编译 proxy-GS?
答案在于控制粒度。官方 validation layer 做的是规范检查,它不会让你在 vkQueueSubmit 中间插一段自定义逻辑,也不会把 SPIR-V 字节码导出来给你分析。而 proxy-GS 是源码级的,编译的时候你可以选择启用哪些模块、吞掉哪些调用、输出哪些日志,甚至改掉它的转发策略。这种可定制性是预编译二进制给不了的。
另外还有一个很实际的原因:有些图形应用为了反调试,会自己做 Vulkan loader 的静态链接,绕过系统的 ICD 机制。这种情况下,只有把 proxy 层做成和它同进程的动态库,并且用 LD_PRELOAD 或者内嵌初始化方式提前注入,才有可能被加载上。这需要你从源码编译才能调整注入时机和初始化顺序。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 编译前先把依赖理清:Vulkan 工具链的版本之战
2.1 编译需要的最小依赖集
proxy-GS 不是单文件项目,它要处理 Vulkan dispatch、SPIR-V 字节码操作、内存分配、窗口系统集成,所以依赖比一般的小工具多不少。我在编译前梳理了一份最小依赖清单,逐个确认版本,避免编译到一半发现某个头文件缺失。
| 依赖 | 作用 | 我用的版本 | 说明 |
|---|---|---|---|
| CMake | 构建系统 | 3.25.1 | 3.16 以上基本就行 |
| Vulkan Headers | 函数声明和常量定义 | 1.3.250 | 要和 glslang 的版本匹配 |
| glslang | GLSL 编译成 SPIR-V | 11.13.0 | 编译期如果启用 Runtime Shader 就必须要 |
| SPIRV-Tools | SPIR-V 汇编、反汇编、优化 | 2023.2 | 用于处理编译产物 |
| SPIRV-Cross | SPIR-V 反编译成 GLSL/MSL/HLSL | sdk-1.3.250 | 跨 API 转译时用 |
| VMA | Vulkan 内存分配辅助库 | 3.0.1 | 头文件库,不需要单独编译 |
| X11/Wayland 开发头文件 | 窗口系统集成 | 各自发行版默认 | 不需要窗口系统的话可以跳过 |
有一个容易踩的坑是 Vulkan Headers、glslang、SPIRV-Tools 这三者之间的配套关系。Khronos 发布 Vulkan SDK 的时候,这几个组件是一起发布的,版本号有对应关系。但如果你通过发行版包管理器单独装,很可能出现 glslang 是老的、Vulkan Headers 是新的这种错位。proxy-GS 的 CMake 文件在编译时会同时引用这几个库的头文件,版本不一致轻则编译告警,重则直接编译失败——我后面遇到的第一个报错就是这个原因。
2.2 编译器选择和各编译选项的影响
proxy-GS 的代码是 C++17 写的,用到了 templates、variant、optional 这些特性,所以编译器不能太老。我在 Ubuntu 22.04 上用 GCC 12.1,在 Arch 上也有用 Clang 16 编译过,两者都能正常过,没有遇到哪个编译器特有的问题。
但有一个点需要注意:proxy 层要加载进目标应用进程,它编译时的 ABI 必须和目标应用兼容。具体来说,如果你用 GCC 编译了带 RTTI 和异常支持的 proxy,但目标应用是用 -fno-rtti -fno-exceptions 编译出来的,加载时很可能因为 typeinfo 或者异常处理表的问题直接崩溃。我建议编译 proxy-GS 时把 CMAKE_CXX_FLAGS 设成和目标应用一致的 ABI 选项,最稳妥的是都保持默认(开 RTTI、开异常)。
CMake 配置的时候有几个选项值得关注。以我编译的版本为例:
bash复制cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DPROXY_GS_ENABLE_RECORDING=ON \
-DPROXY_GS_ENABLE_SPIRV_CROSS=ON \
-DPROXY_GS_ENABLE_CUSTOM_LOGGER=ON \
-DCMAKE_PREFIX_PATH=/opt/VulkanSDK/1.3.250.0/x86_64
PROXY_GS_ENABLE_RECORDING 控制是否编译录制模块,这个建议打开,后面验证要靠它。PROXY_GS_ENABLE_SPIRV_CROSS 依赖 SPIRV-Cross 库,如果你只是做调用转发,可以关掉减少依赖。CMAKE_PREFIX_PATH 指向 Vulkan SDK 的安装目录,这个参数最容易忽略——不加的话 CMake 可能找到系统自带的旧版 Vulkan 头文件。
2.3 为什么版本对齐是第一优先级
我见过太多人编译 Vulkan 相关项目,遇到报错就 blindly 升级某个库,结果把依赖关系搞乱。Vulkan 工具链的特殊性在于:glslang 生成的 SPIR-V 版本要和 SPIRV-Tools 能解析的版本对应,SPIRV-Tools 生成的调试信息格式又要和 Vulkan Headers 里的枚举对应,一个环节错位,问题会以很隐晦的形式冒出来——不是直接报"版本不匹配",而是报一些看似无关的编译错误。
我吃过的亏是:系统 glslang 版本 11.0.0,Vulkan Headers 是 1.3.231,结果编译时出现 glslang::TShader::setEnvInput 函数参数个数不匹配。这个函数在 glslang 11.0.0 里只有 4 个参数,在 11.13.0 里是 5 个。如果不知道版本对应关系,你会去改代码,实际上只需要把 glslang 版本对齐即可。
所以在编译任何 Vulkan 项目前,我强烈建议统一采用 Vulkan SDK 自带的工具链,或者用一个 vcpkg manifest 把版本钉死。proxy-GS 项目本身支持通过 CMake FetchContent 拉取依赖,那样最省心,前提是网络能访问 GitHub。
3. proxy-GS 的正经编译流程:从 clone 到出二进制
3.1 获取源码和子模块
proxy-GS 用了 git submodule 管理部分第三方代码,直接 git clone 不带子模块会漏文件。正确做法是:
bash复制git clone --recursive https://github.com/example/proxy-gs.git
cd proxy-gs
git submodule update --init --recursive
第二行命令第一次执行时一般不会输出什么(因为第一行已经拉过了),但如果你的网络在 clone 中途断了,submodule 可能没完整拉下来。此时进到子模块目录检查一下有没有 .git 目录,没有的话手动执行 git submodule sync && git submodule update --init --recursive。
一个建议:如果网络环境不稳定,先用 git clone --recursive 把项目完整拉下来,再执行编译。不要改 half-clone 状态去跑 CMake,CMake 会报找不到头文件,但实际原因却是子模块缺失,很容易误导排查方向。
3.2 配置和编译的具体命令
我编译时的完整操作是这样的:
bash复制# 假设 Vulkan SDK 已经装好,路径是 /opt/VulkanSDK/1.3.250.0/x86_64
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=/opt/VulkanSDK/1.3.250.0/x86_64 \
-DPROXY_GS_BUILD_TESTS=OFF
cmake --build build -j$(nproc)
加 -j$(nproc) 可以充分利用 CPU 核数,但这个项目编译并不重,依赖齐全的情况下,单核编译也就两三分钟。真正耗时的反而是第一次运行 CMake 时的 FetchContent 步骤——如果项目配置了在线拉取依赖,这一步会下载 glslang 和 SPIRV-Tools 的源码,网络差的时候等十分钟很正常。
编译产物在 build 目录下,主要关注这几个:
| 产物 | 用途 |
|---|---|
| libproxy_gs.so | 核心代理库,也是动态链接时用的主库 |
| VkLayer_proxy_gs.json | Layer 配置文件,告诉 Vulkan loader 如何加载 |
| proxy_gs_record | 录制工具的独立可执行文件(如果启用了录制) |
| tests/test_* | 单元测试可执行文件 |
3.3 安装部署和路径关系
proxy-GS 有两种部署方式:一种是作为 Vulkan Layer 加载,一种是直接动态链接进应用。
作为 Layer 加载时,需要把 libproxy_gs.so 放到 Vulkan loader 能找到的路径,通常有三个选择:
- 系统级:/etc/vulkan/implicit_layer.d 和 /usr/share/vulkan/implicit_layer.d
- 用户级:~/.local/share/vulkan/implicit_layer.d
- 运行时覆盖:设置 VK_LAYER_PATH 环境变量指向 JSON 文件所在目录
JSON 文件里的 library_path 字段必须写对。如果是相对路径,是相对于 JSON 文件所在目录解析的。我一般建议把 .so 和 .json 放在同一个目录,再把 VK_LAYER_PATH 指向这个目录,这样最不容易出错。
Vulkan loader 加载 layer 的顺序是:先加载所有 implicit layer(开启 VK_INSTANCE_LAYERS 之前),再按 VK_INSTANCE_LAYERS 里指定的顺序加载 explicit layer。proxy-GS 的 JSON 默认配置成 implicit layer,意味着只要路径设置正确,所有 Vulkan 应用都会自动加载它。这在调试时很方便,但也意味着如果不想影响其他程序,测试完记得把 Layer 路径从环境变量里移除。
4. 三个编译报错的实际排查链路
4.1 glslang 的 setEnvInput 参数个数不匹配
我碰到第一个报错长这样:
code复制/path/to/proxy-gs/src/shaders/compile_glsl.cpp:38: error: no matching function for call to 'glslang::TShader::setEnvInput'
shader.setEnvInput(EShSourceGlsl, lang, EShClientVulkan, 100);
^
/usr/include/glslang/Public/ShaderLang.h:123: note: candidate expects 5 arguments, 4 provided
这个报错非常典型。我上面提到过,glslang 在 11.0.0 到 11.13.0 之间改了 setEnvInput 的签名,增加了 EShMsgOptions 这个默认参数。系统自带的头文件很老,而 proxy-GS 源码是按新版本 API 写的。
排查思路是:先看报错里候选函数的完整参数列表,确定系统 glslang 版本;再确认 proxy-GS 期望的版本。用 apt show glslang-dev 或者 dpkg -l | grep glslang 查看当前版本。我这里是 Ubuntu 22.04 自带的 11.0.0,proxy-GS 需要 11.13.0。
解决方式不是改源码,而是让 CMake 找到新版 glslang。我当时直接把 CMAKE_PREFIX_PATH 指到 Vulkan SDK 的目录,SDK 自带 glslang 11.13.0,CMake 优先找到了新版本,报错消失。
提示:如果 CMAKE_PREFIX_PATH 设了但 CMake 还是找到旧版本,清掉 build 目录重新配置。CMake 对 find_package 的结果有缓存,改了路径不清缓存不会生效。
4.2 链接阶段"未定义的引用"问题
第二个报错发生在链接阶段,信息是:
code复制/usr/bin/ld: CMakeFiles/proxy_gs.dir/src/core/vulkan_dispatch.cpp.o: undefined reference to 'vkGetInstanceProcAddr'
/usr/bin/ld: CMakeFiles/proxy_gs.dir/src/core/vulkan_dispatch.cpp.o: undefined reference to 'vkGetDeviceProcAddr'
这个报错的原因是项目在 CMakeLists 里链接 Vulkan loader 时,静态库的顺序不对。GCC 链接器对静态库的顺序很敏感,被依赖的库要放在依赖它的目标后面。如果 CMakeLists 里 target_link_libraries(proxy_gs PRIVATE vulkan) 写在添加源码之前,或者链接顺序因为其他库的介入被打乱,就会出现这种"符号明明有,却链不上"的问题。
排查方式:用 cmake --build build --verbose 重新编译,看最终的链接命令长什么样。我这次的链接命令确实是 -lvulkan 出现在源码目标之前。原因是系统里同时装了 Vulkan SDK 自带的 libvulkan.so 和发行版自带的 libvulkan.so.1,CMake 的 find_package(Vulkan) 找到了 SDK 的库,但 pkg-config 的路径设置导致 -L 路径把 SDK 目录放在后面,实际链接到了系统库。
解决方法是显式指定 Vulkan 库的完整路径,而不是依赖 -l 参数:
cmake复制target_link_libraries(proxy_gs PRIVATE
${Vulkan_LIBRARIES}
)
并且把 find_package(Vulkan REQUIRED) 放在其他 find_package 之前,确保 Vulkan_LIBRARIES 变量包含的是 SDK 版本。修改完同样需要清空 build 目录重新配置。
4.3 运行时加载崩溃:RTTI 和异常 ABI 的冲突
第三个问题不发生在编译期,而在编译完成后的运行时。我加载 proxy 层跑一个测试程序,启动即崩溃,核心错误是 std::bad_cast。GDB 回溯栈显示崩溃发生在 proxy 层的 dynamic_cast 调用上。
排查思路很明确:proxy 层本身编译没问题,加载即炸,大概率是 proxy 的 ABI 和主程序不一致。我检查了主程序的编译命令,发现它用了 -fno-rtti——而 proxy-GS 用的是默认的 RTTI。
C++ 在 -fno-rtti 模式下编译的 typeinfo 和数据在启用 RTTI 的库里互相引用,dynamic_cast 需要的 typeinfo 对象若没有正确完整,就会抛 bad_cast 或者直接 Segmentation fault。
解决方式:用同样的 ABI 选项来编译 proxy-GS。把 CMAKE_CXX_FLAGS 加上 -fno-rtti -fno-exceptions:
bash复制cmake -S . -B build \
-DCMAKE_CXX_FLAGS="-fno-rtti -fno-exceptions" \
...
但这里有一个折中:proxy-GS 内部如果用到了需要 RTTI 的库(比如 SPIRV-Tools),也得用相同的选项重新编译,否则库内还是默认 ABI。我后来选择的是把主程序改成默认 ABI,因为有些第三方库确实没法在 -fno-rtti 下编译。
经验:准备把 proxy 层注入别人的应用之前,先搞清楚对方的编译选项。用
readelf -p .GCC.command.line <binary>或者在构建系统里查 CXXFLAGS,能拿到大部分信息。
5. 编译成功只是开始:加载验证与自检方法
5.1 用 vulkaninfo 验证 Layer 是否生效
proxy-GS 编译完之后,第一步验证是确认 Layer 能被 Vulkan loader 加载。不需要先写代码,直接用 vulkaninfo 就行:
bash复制export VK_LAYER_PATH=/path/to/proxy-gs/build
export VK_INSTANCE_LAYERS=VkLayer_proxy_gs
vulkaninfo --summary
如果 Layer 加载成功,vulkaninfo 输出的 Instance Layers 列表里会多出 VkLayer_proxy_gs,同时 proxy-GS 自带的日志输出会出现在 stderr 里(默认 logger 会打印 vkCreateInstance 被调用的消息)。
如果列表里没有这个 layer,检查两件事:一是 JSON 文件的 api_version 字段,如果写的版本比当前 loader 支持的还高,loader 会直接忽略;二是 implementation_version 不能为 0。
5.2 跑一个真实 Vulkan 应用
vulkaninfo 能验证加载,但验证不了转发功能是否正常。这时候需要跑一个真正用了 Vulkan 的图形程序。我习惯用 vkmark 或者一个小 demo,因为它们在缺少扩展时会打印明确的错误信息。
运行方式:
bash复制export VK_LAYER_PATH=/path/to/proxy-gs/build
export VK_INSTANCE_LAYERS=VkLayer_proxy_gs
vkmark
正常情况会看到 proxy-GS 打印出 vkCreateInstance、vkCreateDevice、vkGetDeviceQueue 等一系列调用记录,而且画面正常运行,说明转发没有破坏 Vulkan 调用链。
如果画面黑屏或者闪退,优先怀疑 proxy 层在某个调用里改了参数。proxy-GS 某些版本的录制模块会把 VkCommandBuffer 里的命令转存一份,如果转存逻辑有 bug,可能影响到原命令的执行。此时可以关掉录制模块,只保留纯转发:PROXY_GS_ENABLE_RECORDING=OFF 重新编译,隔离问题。
5.3 验证录制数据是否完整
如果启用了录制,验证数据完整性也很关键。proxy-GS 会把抓到的调用序列写到磁盘文件。查看文件内容时重点检查几个关键对象是否齐全:
- vkCreateInstance 的参数和返回的 instance 句柄
- vkCreateDevice 和创建的 device 句柄
- vkQueueSubmit 每次提交的 command buffer 数量和每个 command buffer 里的命令条数
- 所有 vkCmd* 调用的参数是否符合逻辑(比如 vkCmdDraw 的 instanceCount 不会是 0,除非业务上就是 0)
我遇到过一个情况是录制文件里 vkQueueSubmit 只记录了 submit 本身,没记录里面的 VkCommandBuffer 内容。排查发现是编译时我关掉了 SPIRV-Cross 模块,而 proxy-GS 在录制命令缓冲区内容时恰恰依赖这个模块来序列化管线相关的对象。把 PROXY_GS_ENABLE_SPIRV_CROSS 打开重新编译后,录制数据才完整。
5.4 自建一个最小转发测试程序
除了跑现成应用,我还建议写一个最小测试程序,几十行代码就够,验证 proxy 层在容器、嵌入式等受限环境里能不能正常工作。核心逻辑很简单:
cpp复制#include <vulkan/vulkan.h>
#include <cstdio>
int main() {
VkInstance inst;
VkApplicationInfo app{};
app.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app.pApplicationName = "proxy_test";
app.apiVersion = VK_API_VERSION_1_3;
VkInstanceCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
info.pApplicationInfo = &app;
VkResult r = vkCreateInstance(&info, nullptr, &inst);
if (r != VK_SUCCESS) return -1;
vkDestroyInstance(inst, nullptr);
printf("proxy test ok\n");
return 0;
}
编译之后分别跑一次不带 proxy 和带 proxy 的版本,对比输出。不带 proxy 的正常结束,带 proxy 的也正常结束并且有日志输出,就说明这层代理至少没有破坏最基本的实例创建流程。
6. 再往深一步:这个项目还能怎么玩
6.1 加一个自定义调用记录器
proxy-GS 的代码结构里,转发逻辑和业务逻辑是分开的。在它 的 dispatch table 里,每个函数的默认实现是"转发给下一层",你要做的是在转发之前或者之后插入自己的处理。比如记录 vkCmdDraw 的绘制参数:
cpp复制VKAPI_ATTR void VKAPI_CALL Hook_vkCmdDraw(
VkCommandBuffer cmd, uint32_t vertexCount,
uint32_t instanceCount, uint32_t firstVertex,
uint32_t firstInstance) {
// 自定义逻辑:记录绘制参数
log_draw_call(cmd, vertexCount, instanceCount);
// 转发给真正的驱动
GetDispatchTable(cmd)->vkCmdDraw(cmd, vertexCount, instanceCount, firstVertex, firstInstance);
}
这么做有几个细节要注意:一是 dispatch table 的获取方式要正确,proxy-GS 内部已经封装好了 GetDispatchTable 宏;二是参数校验要放在转发前,如果转发后才发现参数无效,已经来不及阻止驱动崩溃;三是你的代码要异常安全,proxy 层的异常不能抛到应用层,不然会破坏 Alpha 状态。
6.2 把 proxy 层改造成性能分析器
Vulkan 调用的耗时分析,很多人第一反应是用 GPU 的时间戳查询(VK_QUERY_TYPE_TIMESTAMP)。但 GPU 时间戳拿到的是 GPU 执行时间,不等于应用的 CPU 提交时间。proxy 层正好能补充 CPU 侧的数据。
思路是:在 vkQueueSubmit 调用前后各取一次高精度时间(std::chrono::steady_clock),计算差值,就能知道每次提交的 CPU 侧耗时。再把 vkWaitForFences 的阻塞时间也记录上,就可以分析出 CPU 是否在等待 GPU、提交是否过于频繁、命令缓冲区是否过大导致单次提交耗时飙高。
我实际用这个方案定位过一个性能问题:某个应用帧率一直上不去,通过 proxy 层的数据发现,每一帧竟然提交了几百次 vkQueueSubmit,每次只画一个小三角形。驱动处理这些提交的开销比绘制本身还大,性能瓶颈根本不在 GPU 而在 CPU 的提交模型。
6.3 构建一个自动化回归测试框架
把 proxy 层和录制回放机制结合起来,可以做图形应用的自动化回归。流程是:先在基准版本上录制所有帧的 Vulkan 调用序列,保存成回放文件;然后在代码变更后重放这些序列,对比每次调用的参数和返回结果。
proxy-GS 的录制是 API 级别的,所以这种方式完全不依赖具体渲染结果,不需要截图比对,对 CI/CD 环境特别友好。GPU 驱动更新的时候也可以用这套机制做兼容性冒烟——如果新旧驱动下回放结果一致,基本可以确认驱动更新没有破坏兼容性。
这套框架搭建的时候有个注意点:录制文件里的句柄值(VkBuffer、VkImage 等)是内存地址,每次运行都会变。回放时不能直接拿录制值去比对,要在 proxy 层维护一张句柄映射表,把回放进来的句柄映射到当前实际创建的对象。
根据我个人的实际使用体验,proxy-GS 比较适合那些已经掌握 Vulkan 基础概念、但还没接触过 dispatcher 机制的开发者。它的代理层的价值不止是"转发一次 API 调用",更是一扇观察图形应用内部运行细节的窗口。编译过程本身会逼着你去理解 Vulkan 工具链各个组成部分之间的版本关系,这些经验在编译 Mesa、ANGLE 或者其它图形栈项目时同样用得上。如果你也想给 Vulkan 应用加一层自己控制的观察点,照着这篇文章把编译跑通,再按自己的需求去改 Hook 函数,这个方向值得你投入时间。
