godot --rendering-driver vulkan 启动到第一个三角形出现之前,drivers/vulkan/vulkan_context.cpp 要走完四层初始化。每一步都有选择逻辑——选哪些验证层、挑哪张物理卡、建哪个逻辑设备、取哪个队列——黑屏和崩溃的报错大多卡在这四层之一。逐层读源码,选择依据其实比想象中直白。
第一层:instance——验证层按编译开关进
VulkanContext::create_instance() 做的事是按平台组装 VkInstanceCreateInfo:应用名、引擎版本、启用扩展(surface 相关按 OS 平台条件编译分支:Win32/Xlib/Wayland/Metal surface 各自一个 #ifdef)、然后是验证层。
验证层的选择逻辑只有两条:
// drivers/vulkan/vulkan_context.cpp(Godot 4.2.2,有删减)
#ifdef DEBUG_ENABLED
// debug 构建:尝试 VK_LAYER_KHRONOS_validation,
// 拿不到就打日志降级,不致命
#else
// release 构建:不请求任何验证层
#endif
所以"我开了 validation layer 为什么没生效"的答案通常是:你用的是 release 模板,编译时根本没把 VK_LAYER_KHRONOS_validation 加进请求列表。官方模板分 release 和 release_debug 两种,差别就包括这一项。
第二层:physical device——按能力打分而不是按名字
选卡逻辑在 _create_physical_device():枚举所有 VkPhysicalDevice,对每个跑一个打分循环。打分依据(简化):
$$ score = w_{type} \cdot type + w_{api} \cdot api\_version + w_{mem} \cdot local\_memory $$
类型权重最高(独显 > 核显 > 软渲),然后看 Vulkan API 版本(1.3 > 1.2 > 1.1),最后比本地显存大小。凑不齐强制需求的直接出局:要有 graphics 队列族、要支持 swapchain 扩展、descriptor indexing 这类 Godot 4.x 硬依赖的特性必须在 VkPhysicalDeviceFeatures2 里亮绿灯。
两个实战推论:笔记本双显卡环境下 Godot 选了核显,多半是驱动把独显的本地显存报小了或独显的 Vulkan 版本低,不是打分逻辑错了;而"启动报 device lost"类问题往回查,第一嫌疑人是某个强制 feature 在驱动里是"支持但没启用"——打分阶段只查存在性,真正把 feature 打开是下一层的事。
第三层:logical device——扩展凑单和队列凑对
create_logical_device() 一次性要齐两样东西。扩展列表是把 Godot 用到的 Vulkan 扩展打包请求:swapchain、descriptor indexing、timeline semaphore、dynamic rendering 等,少一个 vkCreateDevice 直接失败并打出缺了哪个——这条报错是四层里最诚实的,报什么就是什么。
队列族是这个函数真正的选择逻辑。Godot 要两类队列:graphics/compute 合一的族,和 transfer 专用族(有就加分,没有就退化和主族共用)。选定族索引后把 priorities 设 1.0 建出来。之后所有 RenderingDevice 的提交都走这两条队列,transfer 队列的存在与否直接影响贴图上传和主渲染抢带宽的激烈程度(三-6 篇讲上传路径时会回来)。
第四层:surface 与交换链参数
最后 create_swapchain() 按窗口 surface 的能力(VkSurfaceCapabilitiesKHR)定交换链:minImageCount 取 capabilities.minImageCount + 1(除非最大数量受限)、格式从 surfaceFormats 里按 sRGB 优先挑、present mode 首选 mailbox(不支持退 FIFO)。三层 device 建好了但窗口黑,问题通常在这一层:surface 创建用了错误的平台扩展,或 HDR 显示器把格式列表排成了引擎没预期的顺序。
本文代码引自 Godot 4.2.2-stable 的 drivers/vulkan/vulkan_context.cpp,打分循环为示意简化。