Vulkan学习--3.选择一个支持 Vulkan 的图形设备 (VkPhysicalDevice)

本文介绍了如何在Vulkan中选择合适的显卡设备。通过查询系统中的显卡设备并选择支持所需特性的设备,同时详细解释了队列族的概念及其在图形指令执行中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

创建 VkInstance 后,我们需要查询系统中的显卡设备,选择一个支持我们需要的特性的设备使用。
Vulkan 允许我们选择任意数量的显卡设备,并能够同时使用它们,但在这里,我们只使用第一个满足我们需求的显卡设备。

队列族:

之前提到,Vulkan 的几乎所有操作,从绘制到加载纹理都需要将操作指令提交给一个队列,然后才能执行。
Vulkan 有多种不同类型的队列,它们属于不同的队列族,每个队列族的队列只允许执行特定的一部分指令。
比如,可能存在只允许执行计算相关指令的队列族和只允许执行内存传输相关指令的队列族。

部分代码示例:
//表示满足需求得队列族--2
struct QueueFamilyIndices{
    //绘制指令的队列族索引
    int graphicsFamily = -1;//-1表示没有找到满足需求的队列族
    bool isComplete(){
        return graphicsFamily >= 0;
    }
};
 //存储我们使用的显卡信息,物理设备象,可以在 VkInstance 进行清除操作时,自动清除自己
 VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
 
    //检测设备支持的队列族,查找出满足我们需求的队列族,这一函数会返回满足需求得队列族的索引
    QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device){
        uint32_t queueFamilyCount = 0;
        //获取设备的队列族个数
        vkGetPhysicalDeviceQueueFamilyProperties(device,
                                                 &queueFamilyCount,nullptr);
        /**
        VkQueueFamilyProperties包含队列族的很多信息,
        比如支持的操作类型,该队列族可以创建的队列个数
        */
        std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
        vkGetPhysicalDeviceQueueFamilyProperties(device,
                                    &queueFamilyCount,queueFamilies.data());
        QueueFamilyIndices indices;
        int i=0;
        for(const auto& queueFamily : queueFamilies){
            //VK_QUEUE_GRAPHICS_BIT表示支持图形指令
            if(queueFamily.queueCount>0 &&
                    queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT){
                indices.graphicsFamily = i;
            }
            if(indices.isComplete()){
                break;
            }
            i++;
        }
        return indices;
    }
    //检查设备是否满足需求
    bool isDeviceSuitable(VkPhysicalDevice device){
        QueueFamilyIndices indices = findQueueFamilies(device);
        return indices.isComplete() ;

        //以下代码不使用,仅做了解
        //查询基础设备属性,如名称/类型/支持的vulkan版本
        VkPhysicalDeviceProperties deviceProperties;
        vkGetPhysicalDeviceProperties(device,&deviceProperties);
        //特征属性,如纹理压缩/64位浮点/多视口渲染(常用于 VR)
        VkPhysicalDeviceFeatures deviceFeatures;
        vkGetPhysicalDeviceFeatures(device,&deviceFeatures);
        //判断显卡是否支持集合着色器
        bool res =
        (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
                && deviceFeatures.geometryShader;
        /**
          除了直接选择第一个满足需求的设备这种方法,一个更好的方法是给每一个满足需求的设备,
          按照特性加权打分,选择分数最高的设备使用.
          此外,也可以显示满足需求的设备列表,让用户自己选择使用的设备
          */
        return res;
    }

    //选择一个支持 Vulkan 的图形设备
    void pickPhysicalDevice(){
        //首先需要请求显卡的数量
        uint32_t deviceCount = 0;
        vkEnumeratePhysicalDevices(instance,&deviceCount,nullptr);
        if(deviceCount == 0){
            throw std::runtime_error(
                        "failed to find gpus with vulkan support!");
        }
        //分配数组来存储 VkPhysicalDevice 对象
        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!");
        }
    }


从Vulkan获取对象列表

在Vulkan中获取对象列表是一种相当常见的操作,并且API具有一致的模式。返回列表的API函数具有count和pointer参数。count参数是一个指向整数的指针,以便API可以设置其值。步骤是:

1.使用指向count参数的整数的有效指针调用该函数,并为指针参数调用NULL。
2.API使用列表中的对象数填充count参数。
3.应用程序分配足够的空间来存储列表。
4.应用程序再次调用该函数,指针参数指向刚刚分配的空间。

您将在Vulkan API中经常看到此模式

查找设备

    uint32_t gpu_count = 0;
    res = vkEnumeratePhysicalDevices(inst, &gpu_count, NULL);
    std::vector<VkPhysicalDevice> gpus(gpu_count);
    //返回系统上每个物理设备的句柄列表
    res = vkEnumeratePhysicalDevices(inst, &gpu_count, gpus.data());
    //assert(!res && gpu_count >= 1);
    std::cout <<res<<" "<<gpu_count<<" "<<gpus.size() <<std::endl;

原文url:https://vulkan.lunarg.com/doc/sdk/1.1.101.0/windows/tutorial/html/02-enumerate_devices.html

<think>我们正在讨论如何从多个物理设备选择一个特定的设备。根据之前的引用和讨论,我们知道: 1. 首先需要枚举所有可用的物理设备(通过`vkEnumeratePhysicalDevices`)。 2. 然后遍历这些设备,根据设备的属性(properties)、特性(features)、队列族(queue families)以及扩展支持(extensions)等条件来选择最合适的设备。 用户的问题是如何选择特定的物理设备。我们可以通过以下步骤实现: 步骤1:枚举物理设备 步骤2:设定选择标准(例如:设备类型、支持图形队列、支持特定扩展等) 步骤3:遍历设备,检查每个设备是否符合标准 步骤4:选择符合标准的设备(如果有多个,可以按优先级选择) 下面是一个具体的示例代码(使用C++和Vulkan-Hpp)来演示如何选择一个支持图形操作的独立显卡(即非集成显卡): ```cpp #include <vulkan/vulkan.hpp> #include <vector> #include <iostream> int main() { // 创建Vulkan实例 vk::Instance instance = ...; // 假设已经创建了Vulkan实例 // 步骤1:枚举物理设备 std::vector<vk::PhysicalDevice> physicalDevices = instance.enumeratePhysicalDevices(); // 步骤2:设定选择标准 vk::PhysicalDevice selectedDevice = nullptr; for (const auto& device : physicalDevices) { // 步骤3:检查设备属性 vk::PhysicalDeviceProperties deviceProperties = device.getProperties(); vk::PhysicalDeviceFeatures deviceFeatures = device.getFeatures(); // 检查设备类型:我们想要独立显卡(通常是高性能的) if (deviceProperties.deviceType != vk::PhysicalDeviceType::eDiscreteGpu) { continue; // 跳过集成显卡等 } // 检查队列族:我们需要至少一个支持图形操作的队列族 std::vector<vk::QueueFamilyProperties> queueFamilies = device.getQueueFamilyProperties(); bool hasGraphicsQueue = false; for (const auto& queueFamily : queueFamilies) { if (queueFamily.queueFlags & vk::QueueFlagBits::eGraphics) { hasGraphicsQueue = true; break; } } if (!hasGraphicsQueue) { continue; } // 检查设备扩展支持(例如,我们需要交换链支持) // 注意:这里我们假设需要交换链扩展,实际使用中需要根据需求调整 std::vector<vk::ExtensionProperties> availableExtensions = device.enumerateDeviceExtensionProperties(); bool hasSwapchainExtension = false; for (const auto& extension : availableExtensions) { if (strcmp(extension.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0) { hasSwapchainExtension = true; break; } } if (!hasSwapchainExtension) { continue; } // 如果还需要表面支持(用于显示),可以检查表面格式和呈现模式 // 这里假设我们已经创建了Surface(需要VkSurfaceKHR) // 注意:这部分代码需要Vulkan窗口表面扩展,并且需要知道表面是否被支持 // 为了简化,这里不展示具体代码,但实际应用中可能需要检查 // 步骤4:选择设备 selectedDevice = device; break; // 找到第一个满足条件的设备就停止 } if (selectedDevice) { std::cout << "Selected device: " << deviceProperties.deviceName << std::endl; } else { std::cerr << "Failed to find a suitable GPU!" << std::endl; } // 之后可以使用selectedDevice创建逻辑设备 return 0; } ``` 在上面的代码中,我们首先枚举所有物理设备,然后遍历每个设备。对于每个设备,我们检查: 1. 设备类型是否为独立显卡(eDiscreteGpu)。 2. 是否支持图形队列(eGraphics)。 3. 是否支持交换链扩展(VK_KHR_SWAPCHAIN_EXTENSION_NAME)。 如果设备满足所有条件,我们就选择它。 注意:在实际应用中,选择标准可能更复杂,例如: - 可能需要检查设备是否支持特定的特性(如几何着色器、多采样等)。 - 可能需要检查设备的内存(显存)大小。 - 可能需要检查设备是否支持所需的队列操作(如计算、传输等)。 - 对于多GPU系统,可能需要根据性能或特定功能选择。 此外,如果没有找到独立显卡,我们可以回退到集成显卡(或其他类型设备)。因此,在实际代码中,我们可能会设置一个优先级列表,比如先尝试独立显卡,再尝试集成显卡,最后尝试其他类型。 例如,我们可以这样改进: ```cpp // 设定一个优先级列表:独立显卡 > 集成显卡 > 虚拟显卡 > CPU std::vector<vk::PhysicalDeviceType> deviceTypes = { vk::PhysicalDeviceType::eDiscreteGpu, vk::PhysicalDeviceType::eIntegratedGpu, vk::PhysicalDeviceType::eVirtualGpu, vk::PhysicalDeviceType::eCpu }; for (const auto& type : deviceTypes) { for (const auto& device : physicalDevices) { vk::PhysicalDeviceProperties deviceProperties = device.getProperties(); if (deviceProperties.deviceType != type) { continue; } // 检查其他条件(队列、扩展等)... // 如果满足条件,选择设备并跳出循环 } // 如果在这一轮类型中找到了设备,则跳出循环 if (selectedDevice) break; } ``` 这样,我们会优先选择独立显卡,如果没有满足条件的独立显卡,再尝试集成显卡,以此类推。 总结:选择物理设备的过程就是根据应用需求设定一系列条件,然后遍历所有可用设备,找到第一个(或最合适的)满足条件的设备。 根据用户的问题,我们生成了相关问题如下: §§相关问题§§ 1. 如何在 Vulkan 中查询物理设备的显存信息? 2. 如何在 Vulkan 中判断物理设备是否支持特定队列操作? 3. 如何在 Vulkan 中同时使用多个物理设备? 4. 如何在 Vulkan选择支持计算任务的物理设备?</think>在 Vulkan选择特定的物理设备需要经过以下步骤,我会结合引用内容详细说明: ### 1. 枚举可用物理设备 首先通过 `vkEnumeratePhysicalDevices()` 获取所有可用设备: ```cpp uint32_t deviceCount = 0; vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); // 获取设备数量 std::vector<VkPhysicalDevice> devices(deviceCount); vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); // 填充设备列表 ``` ### 2. 定义选择标准(关键步骤) 根据需求设定设备选择条件,常见标准包括: ```cpp // 检查设备类型(优先选择独立显卡) VkPhysicalDeviceProperties deviceProperties; vkGetPhysicalDeviceProperties(device, &deviceProperties); if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { // 优先选择独立显卡 } // 检查图形队列支持 uint32_t queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); for (const auto& queueFamily : queueFamilies) { if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { // 支持图形操作 } } // 检查扩展支持(如交换链) const char* requiredExtensions[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; uint32_t extensionCount; vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); std::vector<VkExtensionProperties> availableExtensions(extensionCount); vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); for (const auto& reqExt : requiredExtensions) { bool found = false; for (const auto& availExt : availableExtensions) { if (strcmp(reqExt, availExt.extensionName) == 0) { found = true; break; } } if (!found) return false; // 缺少必需扩展 } ``` ### 3. 实现设备评分机制 建议实现评分函数选择最佳设备: ```cpp int rateDevice(VkPhysicalDevice device) { int score = 0; VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(device, &props); // 设备类型权重 if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) score += 1000; // 显存大小 VkPhysicalDeviceMemoryProperties memProps; vkGetPhysicalDeviceMemoryProperties(device, &memProps); for (uint32_t i = 0; i < memProps.memoryHeapCount; i++) { if (memProps.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) { score += memProps.memoryHeaps[i].size / (1024 * 1024); // MB为单位 } } // 检查图形队列支持(必需项) if (!checkGraphicsSupport(device)) return 0; return score; } ``` ### 4. 选择并验证设备 ```cpp VkPhysicalDevice selectedDevice = VK_NULL_HANDLE; int highestScore = 0; for (const auto& device : devices) { int score = rateDevice(device); if (score > highestScore) { highestScore = score; selectedDevice = device; } } if (selectedDevice == VK_NULL_HANDLE) { throw std::runtime_error("找不到合适的GPU设备!"); } ``` ### 5. 创建逻辑设备(引用[1][2]) 选定物理设备后,创建与之关联的逻辑设备: ```cpp VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); VkDevice logicalDevice; if (vkCreateDevice(selectedDevice, &createInfo, nullptr, &logicalDevice) != VK_SUCCESS) { throw std::runtime_error("逻辑设备创建失败!"); } ``` > **关键点总结**: > 1. 必须通过物理设备枚举获取可用设备列表[^3] > 2. 通过设备属性、特性、队列支持和扩展能力进行筛选 > 3. 建议实现评分系统选择最优设备 > 4. 最终通过逻辑设备抽象对物理设备的操作[^1][^2] > 5. 对特定设备(如计算设备)可增加 `VK_QUEUE_COMPUTE_BIT` 标志检查
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力减肥的小胖子5

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值