当前位置: 首页 > news >正文

【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=1uniqueQueueFamilies={1}
  • graphicsFamily=0, presentFamily=1uniqueQueueFamilies={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 函数

  1. 动态加载扩展函数

vkGetInstanceProcAddr:Vulkan 核心函数,用于根据名称动态获取扩展函数的指针。

  • instance:已创建的 Vulkan 实例。
  • "vkCreateDebugUtilsMessengerEXT":目标扩展函数名称。

PFN_vkCreateDebugUtilsMessengerEXT:函数指针类型,匹配 vkCreateDebugUtilsMessengerEXT 的签名。

  1. 检查函数有效性

如果 func 非空,可以安全调用该函数创建调试信使。如果 func 为空,表示扩展未启用或不受支持,返回 VK_ERROR_EXTENSION_NOT_PRESENT

  1. 为何需要动态加载?

Vulkan 扩展函数(如 vkCreateDebugUtilsMessengerEXT)不在核心 API 中。直接链接会导致未实现扩展时程序崩溃,动态加载允许运行时安全检查。

2.2 setupDebugMessenger 方法

  1. enableValidationLayers 是用户控制的标志,仅在调试时启用验证层。若未启用验证层,跳过调试信使的创建以节省资源。
  2. 填充 VkDebugUtilsMessengerCreateInfoEXT 结构体。
  3. 调用 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;
http://www.xdnf.cn/news/54091.html

相关文章:

  • 错误: 找不到或无法加载主类 HelloWorld,cmd窗口,java命令,提示
  • PT站中的tracker
  • LangChain4j语言模型选型指南:主流模型能力全景对比
  • 生成式AI对话中提示词策略:明确问题、明确目标和提供背景信息是最有效的策略
  • 【CPU】中断即时性
  • leetcode(01)森林中的兔子
  • 机器学习(神经网络基础篇)——个人理解篇6(概念+代码)———参数优化篇
  • 模型上下文协议(MCP)详解
  • 【物理学】物理学——电机控制中常用的定则
  • AI 中的 CoT 是什么?一文详解思维链
  • select、poll、epoll实现多路复用IO并对比差异
  • C++类继承关键点总结
  • 模拟实现strcmp,strcpy,strlen,strcat,strstr
  • 类转换与强制类型转换详解
  • 双目视觉中的动态畸变矫正与跨视角信息融合
  • SmolVLM2: The Smollest Video Model Ever(五)
  • C与C++的区别
  • 656SJBH重金属音乐点歌系统
  • windows拷贝文件脚本
  • Java编程基础(第二篇:类的基本创建)
  • 基于尚硅谷FreeRTOS视频笔记——16—FreeRTOS的任务创建和删除
  • 电源芯片的关键性能指标与分析
  • netty中对TLS支持详解
  • 状态管理最佳实践:GetX框架深度应用
  • Tradingview日内交易策略分享-89%日内交易胜率
  • 【网工第6版】第4章 无线通信网
  • awk命令——功能强大的文本处理工具
  • adb启动没有成功响应解决方法
  • 【去哪儿网】登录滑块逆向算法AES加密分析(逆天滑块轨迹)
  • 柱状图QCPBars