news 2026/1/16 12:45:50

[Vulkan 学习之路] 04 - 选妃环节:挑选物理设备与队列族

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
[Vulkan 学习之路] 04 - 选妃环节:挑选物理设备与队列族

欢迎回来!上一集我们成功建立了与 Vulkan 驱动的“外交关系”(Instance)。今天,我们要进入实质性的阶段:挑选我们要用的显卡

在 OpenGL 中,你没得选,系统给你什么就是什么。但在 Vulkan 中,你可以遍历电脑上所有的 GPU(比如你的笔记本可能同时有 Intel 核显和 NVIDIA 独显),然后根据显存大小、特性支持甚至名字来“钦定”一个用来渲染。

这一步我们称之为选择Physical Device (物理设备)

1. 物理设备 (VkPhysicalDevice)

首先,我们在HelloVulkanApp类中添加一个成员变量来存储我们要用的显卡句柄。

// 物理显卡句柄(注意:这个对象会在 Instance 销毁时自动失效,不需要我们手动 vkDestroy) VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;

挑选显卡的逻辑

我们需要编写一个pickPhysicalDevice函数。逻辑依然是标准的 Vulkan 流程:

  1. 问 Vulkan 有多少个显卡?

  2. 获取显卡列表。

  3. 遍历列表,找到一个符合我们要求的(比如支持图形渲染)。

void pickPhysicalDevice() { uint32_t deviceCount = 0; vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); if (deviceCount == 0) { throw std::runtime_error("failed to find GPUs with Vulkan support!"); } std::vector<VkPhysicalDevice> devices(deviceCount); vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); // 遍历所有显卡,找一个能用的 for (const auto& device : devices) { if (isDeviceSuitable(device)) { physicalDevice = device; break; } } if (physicalDevice == VK_NULL_HANDLE) { throw std::runtime_error("failed to find a suitable GPU!"); } }

把这个函数加到initVulkansetupDebugMessenger的后面。

2. 核心难点:队列族 (Queue Families)

那么,什么叫“符合我们要求 (isDeviceSuitable)”的显卡?

这就要引出 Vulkan 的指挥系统了。在 Vulkan 里,所有的命令(比如“画个三角形”、“把图片存入显存”)都不是直接执行的,而是提交给队列 (Queue)执行的。

但是,不同的队列有不同的特长。显卡内部就像一个大工厂,分成了不同的部门(队列族):

  • Graphics Queue Family:专门负责画图的部门。

  • Compute Queue Family:专门负责通用计算的部门。

  • Transfer Queue Family:专门负责搬运数据的部门。

我们需要找的显卡,必须至少包含一个支持 Graphics(图形)操作的队列族。否则,这显卡只能拿来挖矿(计算),不能拿来玩游戏(画图)。

使用std::optional管理索引

因为队列族的索引是非负整数,为了区分“没找到”和“找到了索引为0的队列”,我们引入 C++17 的神器std::optional

记得在文件头部添加:

#include <optional>

定义一个结构体来存放我们要找的队列族索引:

struct QueueFamilyIndices { // std::optional 就像一个盒子,可能里面有值(int),也可能是空的 std::optional<uint32_t> graphicsFamily; bool isComplete() { return graphicsFamily.has_value(); } };

3. 寻找队列族 (findQueueFamilies)

现在我们来写寻找队列族的逻辑。这需要调用vkGetPhysicalDeviceQueueFamilyProperties

QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { QueueFamilyIndices indices; uint32_t queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); int i = 0; for (const auto& queueFamily : queueFamilies) { // 检查这个队列族是否支持 Graphics 操作 if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } if (indices.isComplete()) { break; } i++; } return indices; }

4. 完善检查函数 (isDeviceSuitable)

回到之前的检查函数,现在我们可以利用刚才写的findQueueFamilies来判断显卡是否合格了。

bool isDeviceSuitable(VkPhysicalDevice device) { // 1. 这一步是必须的:确保显卡有图形队列 QueueFamilyIndices indices = findQueueFamilies(device); return indices.isComplete(); // 扩展知识:在这里你其实还可以查询显卡的名字、显存大小等 // VkPhysicalDeviceProperties deviceProperties; // vkGetPhysicalDeviceProperties(device, &deviceProperties); // std::cout << "Checking GPU: " << deviceProperties.deviceName << std::endl; }

5. 运行测试

此时,你的main.cpp结构应该大致如下:

  1. 新增了struct QueueFamilyIndices

  2. HelloVulkanApp类中有了pickPhysicalDeviceisDeviceSuitablefindQueueFamilies

  3. initVulkan顺序:Instance -> DebugMessenger ->pickPhysicalDevice

    #define GLFW_INCLUDE_VULKAN #include <GLFW/glfw3.h> #include <iostream> #include <stdexcept> #include <vector> #include <cstring> #include <cstdlib> #include <optional> const uint32_t WIDTH = 800; const uint32_t HEIGHT = 600; const std::vector<const char*> validationLayers = { "VK_LAYER_KHRONOS_validation" }; #ifdef NDEBUG const bool enableValidationLayers = false; #else const bool enableValidationLayers = true; #endif 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 DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { func(instance, debugMessenger, pAllocator); } } struct QueueFamilyIndices { std::optional<uint32_t> graphicsFamily; bool isComplete() { return graphicsFamily.has_value(); } }; class HelloTriangleApplication { public: void run() { initWindow(); initVulkan(); mainLoop(); cleanup(); } private: GLFWwindow* window; VkInstance instance; VkDebugUtilsMessengerEXT debugMessenger; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; void initWindow() { glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); } void initVulkan() { createInstance(); setupDebugMessenger(); pickPhysicalDevice(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } } void cleanup() { if (enableValidationLayers) { DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); } vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); glfwTerminate(); } void createInstance() { if (enableValidationLayers && !checkValidationLayerSupport()) { throw std::runtime_error("validation layers requested, but not available!"); } VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.pEngineName = "No Engine"; appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); populateDebugMessengerCreateInfo(debugCreateInfo); createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; createInfo.pNext = nullptr; } if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; createInfo.pfnUserCallback = debugCallback; } void setupDebugMessenger() { if (!enableValidationLayers) return; VkDebugUtilsMessengerCreateInfoEXT createInfo; populateDebugMessengerCreateInfo(createInfo); if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug messenger!"); } } void pickPhysicalDevice() { uint32_t deviceCount = 0; vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); if (deviceCount == 0) { throw std::runtime_error("failed to find GPUs with Vulkan support!"); } std::vector<VkPhysicalDevice> devices(deviceCount); vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); for (const auto& device : devices) { if (isDeviceSuitable(device)) { physicalDevice = device; break; } } if (physicalDevice == VK_NULL_HANDLE) { throw std::runtime_error("failed to find a suitable GPU!"); } } bool isDeviceSuitable(VkPhysicalDevice device) { QueueFamilyIndices indices = findQueueFamilies(device); return indices.isComplete(); } QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { QueueFamilyIndices indices; uint32_t queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); int i = 0; for (const auto& queueFamily : queueFamilies) { if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } if (indices.isComplete()) { break; } i++; } return indices; } std::vector<const char*> getRequiredExtensions() { uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; } bool checkValidationLayerSupport() { uint32_t layerCount; vkEnumerateInstanceLayerProperties(&layerCount, nullptr); std::vector<VkLayerProperties> availableLayers(layerCount); vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); for (const char* layerName : validationLayers) { bool layerFound = false; for (const auto& layerProperties : availableLayers) { if (strcmp(layerName, layerProperties.layerName) == 0) { layerFound = true; break; } } if (!layerFound) { return false; } } return true; } static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } }; int main() { HelloTriangleApplication app; try { app.run(); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }

运行程序:

依然是黑窗口(别急,我们在搭地基)。但如果程序没有抛出 "failed to find a suitable GPU!" 异常,恭喜你!你的代码成功识别了你的显卡,并确认了它拥有绘图能力。

给 Windows 用户的特别提示:

如果你是双显卡笔记本(Intel 集显 + NVIDIA 独显),目前的逻辑 break 会导致它选中列表中第一个合格的显卡。通常驱动程序会把独显排在前面,或者是集显。

在本教程的简单场景下,集显和独显都能跑。但如果你想强制选独显,可以在 isDeviceSuitable 里加一个判断:deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU。

下一步

选好了物理设备,我们还不能直接指挥它。我们需要基于这个物理设备创建一个Logical Device (逻辑设备),并从队列族中把那个我们要用的Queue (队列)拿出来。

下一篇,我们将完成设备初始化的最后一步,真正的获得显卡的控制权!

详见:Physical devices and queue families - Vulkan Tutorial

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/15 5:05:05

5分钟掌握鸣潮模组终极配置:新手快速上手指南

5分钟掌握鸣潮模组终极配置&#xff1a;新手快速上手指南 【免费下载链接】wuwa-mod Wuthering Waves pak mods 项目地址: https://gitcode.com/GitHub_Trending/wu/wuwa-mod 想要在《鸣潮》游戏中获得更畅快的体验吗&#xff1f;WuWa-Mod模组为你打开全新的游戏世界。这…

作者头像 李华
网站建设 2026/1/15 5:05:00

[Vulkan 学习之路] 07 - 交换链 (Swap Chain):图像的物流中心

欢迎来到第七篇&#xff01; Vulkan 没有“默认帧缓冲区”的概念。在 OpenGL 中&#xff0c;你画完图调用 SwapBuffers 就完事了&#xff0c;驱动会在后台帮你搞定双重缓冲。但在 Vulkan 中&#xff0c;你必须亲手建立这一套机制。 这就是 Swap Chain (交换链)。它本质上是一…

作者头像 李华
网站建设 2026/1/15 5:05:00

Image-to-Video科学教育:抽象概念的动态解释

Image-to-Video科学教育&#xff1a;抽象概念的动态解释 1. 引言 在科学教育领域&#xff0c;抽象概念的理解始终是教学过程中的难点。无论是分子运动、电磁场变化&#xff0c;还是天体运行规律&#xff0c;静态图像往往难以完整传达其动态本质。随着生成式AI技术的发展&…

作者头像 李华
网站建设 2026/1/15 5:04:44

NarratoAI终极使用指南:5分钟快速上手智能视频解说

NarratoAI终极使用指南&#xff1a;5分钟快速上手智能视频解说 【免费下载链接】NarratoAI 利用AI大模型&#xff0c;一键解说并剪辑视频&#xff1b; Using AI models to automatically provide commentary and edit videos with a single click. 项目地址: https://gitcode…

作者头像 李华
网站建设 2026/1/15 5:04:20

基于ARM的远程IO控制器开发:完整示例

基于ARM的远程IO控制器开发&#xff1a;从原理到实战的技术全解你有没有遇到过这样的场景&#xff1f;工厂车间里&#xff0c;几十个传感器的信号线像蜘蛛网一样拉回控制柜&#xff0c;布线复杂、维护困难&#xff1b;一旦要增加一个输入点&#xff0c;就得重新穿管走线&#x…

作者头像 李华
网站建设 2026/1/15 5:04:19

VirtualBrowser实战指南:5个高效场景快速上手浏览器自动化

VirtualBrowser实战指南&#xff1a;5个高效场景快速上手浏览器自动化 【免费下载链接】VirtualBrowser Free anti fingerprint browser, 指纹浏览器, 隐私浏览器, 免费的web3空投专用指纹浏览器 项目地址: https://gitcode.com/gh_mirrors/vi/VirtualBrowser VirtualBr…

作者头像 李华