相关文章推荐
想旅行的鸵鸟  ·  oracle ...·  7 月前    · 
想出国的蘑菇  ·  关于 onedriver ...·  9 月前    · 

大家好,接下来将为大家介绍Vulkan:逻辑设备与队列。

创建一个 VkDevice 逻辑设备对象,它对应于系统上的一个物理设备。逻辑设备是稍后用于将图形命令定向到硬件的关键对象。

到目前为止,已经可以确定有多少物理设备。列举这些设备的示例工具函数确保了至少有一个设备,否则它会以不正确的断言停止运行。

一、设备队列和队列族

与其他图形api不同,Vulkan将设备队列暴露给程序员,这样程序员就可以决定要使用多少队列以及要使用什么样的队列。

队列是用来向硬件提交命令的抽象机制。稍后您将看到一个Vulkan应用程序如何构建一个充满命令的命令缓冲区,然后将它们提交到一个队列中,以便由GPU硬件进行异步处理。

Vulkan将队列按照它们的类型排列成队列家族。为了找到您感兴趣的队列的类型和特征,您可以从物理设备查询QueueFamilyProperties:

typedef struct VkQueueFamilyProperties {
    VkQueueFlags    queueFlags;
    uint32_t        queueCount;
    uint32_t        timestampValidBits;
    VkExtent3D      minImageTransferGranularity;
} VkQueueFamilyProperties;
typedef enum VkQueueFlagBits {
    VK_QUEUE_GRAPHICS_BIT = 0x00000001,
    VK_QUEUE_COMPUTE_BIT = 0x00000002,
    VK_QUEUE_TRANSFER_BIT = 0x00000004,
    VK_QUEUE_SPARSE_BINDING_BIT = 0x00000008,
} VkQueueFlagBits;

样例程序通过发出以下调用来获取队列信息:

vkGetPhysicalDeviceQueueFamilyProperties(
    info.gpus[0], &info.queue_family_count, info.queue_props.data());

info.queue_props 是一个VkQueueFamilyProperties类型的Vector。 
您可以检查样例代码,以查看样例遵循枚举设备中描述的模式,解释如何使用Vulkan API获取对象列表。它调用vkGetPhysicalDeviceQueueFamilyProperties来获得计数,并再次调用它来获取数据。

VkQueueFamilyProperties结构被称为“家族”,因为可能有许多(queueCount)数量的队列,每个队列有一组特定的queueFlags。例如,在一个拥有VK_QUEUE_GRAPHICS_BIT集的家族中可能有8个队列。

二、指定创建的队列

在选择要使用的物理设备之后,我们需要设置一个逻辑设备用于交互。逻辑设备创建过程与instance创建过程类似,也需要描述我们需要使用的功能。因为我们已经查询过哪些队列簇可用,在这里需要进一步为逻辑设备创建具体类型的命令队列。如果有不同的需求,也可以基于同一个物理设备创建多个逻辑设备。

首先添加一个新的类成员来存储逻辑设备句柄。

VkDevice device;

接下来创建一个新的函数createLogicalDevice,并在initVulkan函数中调用,以创建逻辑设备。

void initVulkan() {
    createInstance();
    setupDebugCallback();
    pickPhysicalDevice();
    createLogicalDevice();
void createLogicalDevice() {

创建逻辑设备需要在结构体中明确具体的信息,首先第一个结构体VkDeviceQueueCreateInfo。这个结构体描述队列簇中预要申请使用的队列数量。现在我们仅关心具备图形能力的队列。

QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
VkDeviceQueueCreateInfo queueCreateInfo = {};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = indices.graphicsFamily;
queueCreateInfo.queueCount = 1;

当前可用的驱动程序所提供的队列簇只允许创建少量的队列,并且很多时候没有必要创建多个队列。这是因为可以在多个线程上创建所有命令缓冲区,然后在主线程一次性的以较低开销的调用提交队列。

Vulkan允许使用0.0到1.0之间的浮点数分配队列优先级来影响命令缓冲区执行的调用。即使只有一个队列也是必须的:

float queuePriority = 1.0f;
queueCreateInfo.pQueuePriorities = &queuePriority;

三、指定使用的设备特性

下一个要明确的信息有关设备要使用的功能特性。这些是我们在上一节中用vkGetPhysicalDeviceFeatures查询支持的功能,比如geometry shaders。现在我们不需要任何特殊的功能,所以我们可以简单的定义它并将所有内容保留到VK_FALSE。一旦我们要开始用Vulkan做更多的事情,我们会回到这个结构体,进一步设置。

VkPhysicalDeviceFeatures deviceFeatures = {};

四、创建逻辑设备

使用前面的两个结构体,我们可以填充VkDeviceCreateInfo结构。

VkDeviceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;

首先添加指向队列创建信息的结构体和设备功能结构体:

createInfo.pQueueCreateInfos = &queueCreateInfo;
createInfo.queueCreateInfoCount = 1;
createInfo.pEnabledFeatures = &deviceFeatures;

结构体其余的部分与VkInstanceCreateInfo相似,需要指定扩展和validation layers,总而言之这次不同之处是为具体的设备设置信息。

设置具体扩展的一个案例是VK_KHR_swapchain,它允许将来自设备的渲染图形呈现到Windows。系统中的Vulkan设备可能缺少该功能,例如仅仅支持计算操作。我们将在交换链章节中展开这个扩展。

就像之前validation layers小节中提到的,允许为instance开启validation layers,现在我们将为设备开启validation layers,而不需要为设备指定任何扩展。

createInfo.enabledExtensionCount = 0;
if (enableValidationLayers) {
    createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
    createInfo.ppEnabledLayerNames = validationLayers.data();
} else {
    createInfo.enabledLayerCount = 0;

就这样,我们现在可以通过调用vkCreateDevice函数来创建实例化逻辑设备。

if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
    throw std::runtime_error("failed to create logical device!");

这些参数分别是包含具体队列使用信息的物理设备,可选的分配器回调指针以及用于存储逻辑设备的句柄。与instance创建类似,此调用可能由于启用不存在的扩展或者指定不支持的功能,导致返回错误。

cleanup函数中逻辑设备需要调用vkDestroyDevice销毁:

void cleanup() {
    vkDestroyDevice(device, nullptr);

逻辑设备不与instance交互,所以参数中不包含instance

五、检索队列处理

这些队列与逻辑设备自动的一同创建,但是我们还没有一个与它们进行交互的句柄。在这里添加一个新的类成员来存储图形队列句柄:

VkQueue graphicsQueue;

设备队列在设备被销毁的时候隐式清理,所以我们不需要在cleanup函数中做任何操作。

我们可以使用vkGetDeviceQueue函数来检测每个队列簇中队列的句柄。参数是逻辑设备,队列簇,队列索引和存储获取队列变量句柄的指针。因为我们只是从这个队列簇创建一个队列,所以需要使用索引 0

vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue);

在成功获取逻辑设备和队列句柄后,我们可以通过显卡做一些实际的事情了,在接下来的几章节中,我们会设置资源并将相应的结果提交到窗体系统。

六、示例代码

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <iostream>
#include <stdexcept>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <optional>
const int WIDTH = 800;
const int 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;
    VkDevice device;
    VkQueue graphicsQueue;
    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();
        createLogicalDevice();
    void mainLoop() {
        while (!glfwWindowShouldClose(window)) {
            glfwPollEvents();
    void cleanup() {
        vkDestroyDevice(device, nullptr);
        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!");
    void createLogicalDevice() {
        QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
        VkDeviceQueueCreateInfo queueCreateInfo = {};
        queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
        queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value();
        queueCreateInfo.queueCount = 1;
        float queuePriority = 1.0f;
        queueCreateInfo.pQueuePriorities = &queuePriority;
        VkPhysicalDeviceFeatures deviceFeatures = {};
        VkDeviceCreateInfo createInfo = {};
        createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
        createInfo.pQueueCreateInfos = &queueCreateInfo;
        createInfo.queueCreateInfoCount = 1;
        createInfo.pEnabledFeatures = &deviceFeatures;
        createInfo.enabledExtensionCount = 0;
        if (enableValidationLayers) {
            createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
            createInfo.ppEnabledLayerNames = validationLayers.data();
        } else {
            createInfo.enabledLayerCount = 0;
        if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
            throw std::runtime_error("failed to create logical device!");
        vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
    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.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
                indices.graphicsFamily = i;
            if (indices.isComplete()) {
                break;
        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;
                                    由于Vulkan是一个平台无关的API,因此它不能自己直接与窗口系统接口。为了在Vulkan和窗口系统之间建立连接并将结果显示到屏幕上,我们需要使用WSI(窗口系统集成)扩展。在本章中,我们将讨论第一个,即VK_KHR_surface。它是一个VkSurfaceKHR对象,该对象表示要呈现渲染图像的抽象表面类型。我们程序中的surface将由我们已经用GLFW打开的窗口支持。
                                    Vulkan 物理设备与队列
Vulkan物理设备与队列 ,通过VkInstance初始化Vulkan后,我们需要在系统中查找并选择一个支持我们所需功能的显卡。实际上,我们可以选择任意数量的显卡并同时使用他们,但在本小节中,我们简单的设定选择规则,即将查找到的第一个图形卡作为我们适合的物理设备。
一、选择物理设备
通过VkInstance初始化Vulkan后,我们需要在系统中查找并选择一个支持我们...
开发工具:Visual Studio 2017
在这一章节,我们了解一下将渲染图像提交到屏幕的基本机制。这种机制称为交换链,并且需要在Vulkan上下文中被明确创建。从屏幕的角度观察,交换链本质上是一个图像队列。应用程序作为生产者会获取图像进行绘制,然后将其返还给交换链图像队列,等待屏幕消费。交换链的具体配置信息决定了应...
    Vulkan作为一个直接提供GPU硬件功能的接口,而不再负责多线程渲染的调度,以及线程并发访问的安全保护等工作,这意味着Vulkan接口内部不再像OpenGL ES一样有过度的线程同步,CPU/GPU之间的同步等操作。这些操作会为渲染接口的运行效率带...
                                    在PC上安装最新的Nvidia 驱动后,不支持Vulkan解决方案:
下载以下驱动可以解决问题
https://developer.nvidia.com/vulkan-beta-44236-windows-10-dch
https://developer.nvidia.com/vulkan-beta-44236-windows-10
译者注:示例代码点击此处
在Vulkan中,当我们想要在硬件上执行操作时,我们将它们提交给队列。 单个队列中的操作将按照提交的顺序一个接一个地处理 - 这就是它被称为队列的原因。 但是,提交到不同队列的操作是独立处理的(如果需要,我们可以同步它们):
不同的队列可以代表硬件的不同部分,因此可以支持不同类型的操作。 并非所有操作都可以在所有队列上执行。
具有相同操作...
void createSurface() {
    if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
        throw std::runtime_error("failed to create window surface!")
Vulkan 加载模型(Loading models),应用程序现在已经可以渲染纹理3D模型,但是 vertices 顶点和 indices 索引数组中的几何体不是很有趣。在本章节我们扩展程序,从实际的模型文件冲加载顶点和索引数据,并使图形卡实际做一些工作。
许多图形API系列教程中让读者在这样的章节中编写自己的OBJ加载程序。这样做的问题是任何有趣的3D应用程序很快需要某种功能,但是该...
文章转自 Vulkan 1.2 知识串讲(1)
PFN_vkVoidFunction vkGetInstanceProcAddr( VkInstance instance, const
char pName);*
在CTS,该函数是用于获取 vkGetPhysicalDeviceProperties2KHR 等函数的函数handle,比如:
PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDevicePr
                                    在选择要使用的物理设备之后,我们需要设置一个逻辑设备来与它接口。逻辑设备创建过程类似于实例创建过程,并描述了我们想要使用的特性。在查询了哪些队列族可用之后,我们还需要指定要创建哪些队列。如果您有不同的需求,甚至可以从同一个物理设备创建多个逻辑设备。首先添加一个新的类成员来存储逻辑设备句柄。接下来,添加一个从initVulkan调用的createLogicalDevice函数。
                                    大家好,接下来将为大家介绍Vulkan 交换链详解。
在这一章节,我们了解一下将渲染图像提交到屏幕的基本机制。这种机制称为交换链,并且需要在Vulkan上下文中被明确创建。从屏幕的角度观察,交换链本质上是一个图像队列。应用程序作为生产者会获取图像进行绘制,然后将其返还给交换链图像队列,等待屏幕消费。交换链的具体配置信息决定了应用程序提交绘制图像到队列的条件以及图像队列表现的效果,但交换链的通常使...