【Vulkan 入门系列】创建逻辑设备和图形、呈现队列,显示尺寸更改(三)
在选择要使用的物理设备后,我们需要设置一个逻辑设备来与它交互。逻辑设备的创建过程类似于实例的创建过程,并描述了我们想要使用的功能。我们还需要指定要创建哪些队列,现在我们已经查询了哪些队列族可用。接下来设置调试信使,最后再来确定 Surface 的宽高来完成这一节的内容。
一、创建逻辑设备和图形、呈现队列
该函数用于创建 Vulkan 的逻辑设备(Logical Device) 并获取其图形队列和呈现队列句柄。逻辑设备是物理设备的抽象接口,负责管理资源分配和操作执行;队列则是向 GPU 提交指令的通道。
void HelloVK::createLogicalDeviceAndQueue() {QueueFamilyIndices indices = findQueueFamilies(physicalDevice);std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(),indices.presentFamily.value()};float queuePriority = 1.0f;for (uint32_t queueFamily : uniqueQueueFamilies) {VkDeviceQueueCreateInfo queueCreateInfo{};queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;queueCreateInfo.queueFamilyIndex = queueFamily;queueCreateInfo.queueCount = 1;queueCreateInfo.pQueuePriorities = &queuePriority;queueCreateInfos.push_back(queueCreateInfo);}VkPhysicalDeviceFeatures deviceFeatures{};VkDeviceCreateInfo createInfo{};createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;createInfo.queueCreateInfoCount =static_cast<uint32_t>(queueCreateInfos.size());createInfo.pQueueCreateInfos = queueCreateInfos.data();createInfo.pEnabledFeatures = &deviceFeatures;createInfo.enabledExtensionCount =static_cast<uint32_t>(deviceExtensions.size());createInfo.ppEnabledExtensionNames = deviceExtensions.data();if (enableValidationLayers) {createInfo.enabledLayerCount =static_cast<uint32_t>(validationLayers.size());createInfo.ppEnabledLayerNames = validationLayers.data();} else {createInfo.enabledLayerCount = 0;}VK_CHECK(vkCreateDevice(physicalDevice, &createInfo, nullptr, &device));vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
}
1.1 获取队列族索引
通过 findQueueFamilies
函数获取物理设备支持的图形队列族和呈现队列族的索引。
QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
indices
包含两个成员:
graphicsFamily
:图形队列族索引(用于渲染指令)presentFamily
:呈现队列族索引(用于显示到窗口)
1.2 去重队列族
去重队列族索引。
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()
};
使用 std::set
自动去重,例如:
- 若
graphicsFamily=1
,presentFamily=1
→uniqueQueueFamilies={1}
- 若
graphicsFamily=0
,presentFamily=1
→uniqueQueueFamilies={0,1}
1.3 配置队列创建信息
为每个唯一队列族生成一个队列配置结构体,后续用于设备创建。
float queuePriority = 1.0f;
for (uint32_t queueFamily : uniqueQueueFamilies) {VkDeviceQueueCreateInfo queueCreateInfo{};queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;queueCreateInfo.queueFamilyIndex = queueFamily; // 队列族索引queueCreateInfo.queueCount = 1; // 创建1个队列queueCreateInfo.pQueuePriorities = &queuePriority; // 队列优先级(0.0~1.0)queueCreateInfos.push_back(queueCreateInfo);
}
queueCount
:从该队列族中创建的队列数量(通常每个族只需1个队列)。pQueuePriorities
:队列优先级(影响 GPU 调度,1.0 表示最高优先级)。
1.4 配置设备特性
启用物理设备的硬件特性(如几何着色器、宽线条支持等)。此处未启用任何特性(空结构体),表示仅依赖默认功能。
VkPhysicalDeviceFeatures deviceFeatures{};
1.5 配置逻辑设备创建信息
VkDeviceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
createInfo.pQueueCreateInfos = queueCreateInfos.data(); // 队列配置
createInfo.pEnabledFeatures = &deviceFeatures; // 设备特性
createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data(); // 扩展(如交换链)
// 处理验证层
if (enableValidationLayers) {createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());createInfo.ppEnabledLayerNames = validationLayers.data();
} else {createInfo.enabledLayerCount = 0;
}
ppEnabledExtensionNames
:包含VK_KHR_SWAPCHAIN_EXTENSION_NAME
(交换链扩展)。ppEnabledLayerNames
:仅调试时启用验证层,发布时关闭以提升性能。
1.6 创建逻辑设备
VK_CHECK(vkCreateDevice(physicalDevice, &createInfo, nullptr, &device));
physicalDevice
:之前选择的物理设备。&createInfo
:配置信息结构体。nullptr
:自定义内存分配器(通常无需指定)。&device
:输出逻辑设备句柄。
1.7 获取队列句柄
每个队列族可以创建多个队列。
- 图形队列用于提交渲染命令。
- 呈现队列用于提交图像到窗口。
vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
device
:已创建的逻辑设备。queueFamilyIndex
:队列族索引(来自indices
)。queueIndex
:队列索引(同一队列族中第几个队列,此处为 0)。&graphicsQueue
/&presentQueue
:输出队列句柄。
1.8 关键概念
1. 逻辑设备 vs 物理设备
物理设备:硬件抽象。
逻辑设备:基于物理设备的软件接口,管理资源(缓冲区、纹理等)。
2. 队列族(Queue Families)
每个队列族支持特定类型的操作(图形、计算、传输等)。同一队列族的队列共享相同能力,但可独立提交指令。
3. 设备扩展(Device Extensions)
- 必需扩展(如
VK_KHR_swapchain
):启用交换链功能以实现画面呈现。 - 可选扩展(如
VK_NV_ray_tracing
):启用光线追踪等高级功能。
flowchart LR选择物理设备 --> 配置队列 --> 设置特性 --> 创建逻辑设备 --> 获取队列句柄
二、设置调试信使
用于在 Vulkan 中创建调试信使(Debug Messenger),用于接收并处理来自验证层(Validation Layers)的调试信息(如错误、警告、性能提示等)。调试信使是 Vulkan 调试工具链的核心组件,通过回调函数将消息传递到用户自定义的处理逻辑中。
void HelloVK::initVulkan() {createInstance();createSurface();pickPhysicalDevice();createLogicalDeviceAndQueue();setupDebugMessenger();...
}static VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT *pCreateInfo,const VkAllocationCallbacks *pAllocator,VkDebugUtilsMessengerEXT *pDebugMessenger) {auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");if (func != nullptr) {return func(instance, pCreateInfo, pAllocator, pDebugMessenger);} else {return VK_ERROR_EXTENSION_NOT_PRESENT;}
}void HelloVK::setupDebugMessenger() {if (!enableValidationLayers) {return;}VkDebugUtilsMessengerCreateInfoEXT createInfo{};populateDebugMessengerCreateInfo(createInfo);VK_CHECK(CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr,&debugMessenger));
}
2.1 CreateDebugUtilsMessengerEXT
函数
- 动态加载扩展函数
vkGetInstanceProcAddr
:Vulkan 核心函数,用于根据名称动态获取扩展函数的指针。
instance
:已创建的 Vulkan 实例。"vkCreateDebugUtilsMessengerEXT"
:目标扩展函数名称。
PFN_vkCreateDebugUtilsMessengerEXT
:函数指针类型,匹配 vkCreateDebugUtilsMessengerEXT
的签名。
- 检查函数有效性
如果 func
非空,可以安全调用该函数创建调试信使。如果 func
为空,表示扩展未启用或不受支持,返回 VK_ERROR_EXTENSION_NOT_PRESENT
。
- 为何需要动态加载?
Vulkan 扩展函数(如 vkCreateDebugUtilsMessengerEXT
)不在核心 API 中。直接链接会导致未实现扩展时程序崩溃,动态加载允许运行时安全检查。
2.2 setupDebugMessenger
方法
enableValidationLayers
是用户控制的标志,仅在调试时启用验证层。若未启用验证层,跳过调试信使的创建以节省资源。- 填充
VkDebugUtilsMessengerCreateInfoEXT
结构体。 - 调用
CreateDebugUtilsMessengerEXT
函数创建调试信使。
VkDebugUtilsMessengerCreateInfoEXT
结构体
typedef struct VkDebugUtilsMessengerCreateInfoEXT {VkStructureType sType; // 结构体类型const void* pNext; // 扩展链指针VkDebugUtilsMessengerCreateFlagsEXT flags; // 保留字段(通常为 0)VkDebugUtilsMessageSeverityFlagsEXT messageSeverity; // 消息严重性过滤VkDebugUtilsMessageTypeFlagsEXT messageType; // 消息类型过滤PFN_vkDebugUtilsMessengerCallbackEXT pfnUserCallback; // 回调函数void* pUserData; // 用户自定义数据
} VkDebugUtilsMessengerCreateInfoEXT;
三、显示尺寸更改
主要解决设备屏幕物理旋转(如手机横竖屏切换)导致的 Surface 尺寸方向不一致的问题,确保后续渲染流程使用正确的宽高比例。
void HelloVK::initVulkan() {createInstance();createSurface();pickPhysicalDevice();createLogicalDeviceAndQueue();setupDebugMessenger();establishDisplaySizeIdentity();...
}void HelloVK::establishDisplaySizeIdentity() {VkSurfaceCapabilitiesKHR capabilities;vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface,&capabilities);uint32_t width = capabilities.currentExtent.width;uint32_t height = capabilities.currentExtent.height;if (capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {// Swap to get identity width and heightcapabilities.currentExtent.height = width;capabilities.currentExtent.width = height;}displaySizeIdentity = capabilities.currentExtent;
}
3.1 获取 Surface 能力
VkSurfaceCapabilitiesKHR capabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &capabilities);
vkGetPhysicalDeviceSurfaceCapabilitiesKHR
:Vulkan API,查询物理设备与 Surface 相关的能力属性,包括:
currentExtent
:Surface 的当前像素尺寸(宽高)。currentTransform
:Surface 当前的几何变换(如旋转、镜像)。
3.2 提取原始宽高
记录 Surface 的原始尺寸。例如,手机竖屏时可能为 1080x1920
,横屏时为 1920x1080
。
uint32_t width = capabilities.currentExtent.width;
uint32_t height = capabilities.currentExtent.height;
3.3 检测旋转变换
如果 Surface 应用了 90 度或 270 度的旋转,交换宽高值。
if (capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {capabilities.currentExtent.height = width;capabilities.currentExtent.width = height;
}
Surface 变换(Surface Transform)
表示 Surface 在物理显示设备上的几何变换(如旋转、镜像)。
常见值:
VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
:无旋转,逻辑尺寸=物理尺寸。VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR
:顺时针旋转 90 度。VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR
:顺旋转 180 度。VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR
:顺时针旋转 270 度(等效逆时针 90 度)。
3.4 保存尺寸
displaySizeIdentity
:存储修正后的宽高,供后续渲染流程(如交换链创建、视口设置)使用。
displaySizeIdentity = capabilities.currentExtent;