[Vulkan 学习之路] 05 - 缔结契约:创建逻辑设备 (Logical Device)

欢迎来到第五篇!经过前面的铺垫,我们终于要完成设备的初始化工作了。

这一步在 Vulkan 中至关重要。虽然我们有了 VkPhysicalDevice(物理设备句柄),但我们几乎不会直接用它来发号施令。我们真正日常打交道的是 VkDevice (逻辑设备)

此外,我们还将拿到那个至关重要的 VkQueue (队列) 句柄------未来我们所有的渲染命令,都要提交给它。

准备工作:类成员变量

首先,我们需要在 HelloVulkanApp 类中增加两个新的成员变量:

cpp 复制代码
VkDevice device; // 逻辑设备句柄
VkQueue graphicsQueue; // 图形队列句柄

注意: VkQueue 会在逻辑设备销毁时自动清理,所以我们不需要为它写 vkDestroyQueue。但 VkDevice 是需要我们手动销毁的。

配置队列信息 (VkDeviceQueueCreateInfo)

创建逻辑设备的第一步,是告诉 Vulkan 我们需要多少个队列。

还记得上一节我们写的 findQueueFamilies 吗?我们需要再次用到它,找出支持 Graphics 的队列族索引。

cpp 复制代码
void createLogicalDevice() {
    // 1. 再次查找队列族索引
    QueueFamilyIndices indices = findQueueFamilies(physicalDevice);

    VkDeviceQueueCreateInfo queueCreateInfo{};
    queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    // 指定我们要从哪个队列族创建队列
    queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value();
    
    // 我们只需要一个队列来处理图形命令
    queueCreateInfo.queueCount = 1;

    // 2. 设置队列优先级 (0.0 - 1.0)
    // 即使只有一个队列,这个也是必须填写的!
    float queuePriority = 1.0f;
    queueCreateInfo.pQueuePriorities = &queuePriority;

    // ... 待续
}

指定设备特性 (VkPhysicalDeviceFeatures)

Vulkan 允许我们启用各种高级特性,比如几何着色器 (Geometry Shaders)、各向异性过滤等。

目前我们只是画一个简单的三角形,不需要任何高级特性。但这个结构体必须存在。

cpp 复制代码
    // ... 接上文

    // 3. 指定设备特性 (暂时全部留空/为false)
    VkPhysicalDeviceFeatures deviceFeatures{};
    // deviceFeatures.geometryShader = VK_TRUE; // 如果以后需要,就像这样开启

创建逻辑设备 (VkDeviceCreateInfo)

现在把所有东西填入最终的结构体:

cpp 复制代码
    // ... 接上文

    // 4. 填写逻辑设备创建信息
    VkDeviceCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;

    // 链接队列信息
    createInfo.pQueueCreateInfos = &queueCreateInfo;
    createInfo.queueCreateInfoCount = 1;

    // 链接设备特性
    createInfo.pEnabledFeatures = &deviceFeatures;

    // 5. 处理校验层 (针对旧版 Vulkan 的兼容性设置)
    // 新版 Vulkan 规范中,Device 的校验层会自动继承 Instance 的设置,
    // 但为了兼容旧设备,我们还是把 validationLayers 填进去。
    createInfo.enabledExtensionCount = 0; // 暂时没有设备级扩展

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

    // 6. 正式创建设备
    if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
        throw std::runtime_error("failed to create logical device!");
    }

    // 7. 获取队列句柄
    // 参数:逻辑设备, 队列族索引, 队列索引(0), 存放句柄的指针
    vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
}

整合与清理

添加到初始化流程

修改 initVulkan 函数,把 createLogicalDevice 加进去:

cpp 复制代码
void initVulkan() {
    createInstance();
    setupDebugMessenger();
    pickPhysicalDevice();
    createLogicalDevice(); // <--- 新增
}

完善清理流程

非常重要: 逻辑设备必须在 Instance 销毁之前销毁,但要在其他资源(如窗口、Surface)之后销毁。

cpp 复制代码
void cleanup() {
    // 先销毁逻辑设备
    vkDestroyDevice(device, nullptr); // <--- 新增

    if (enableValidationLayers) {
        DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
    }

    // 最后销毁 Instance
    vkDestroyInstance(instance, nullptr);

    glfwDestroyWindow(window);
    glfwTerminate();
}

完整代码:

cpp 复制代码
#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;
    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.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;
}

运行测试

编译并运行你的程序。

  • 如果一切顺利: 依然是那个熟悉的黑窗口(这就对了!)。

  • 如果崩溃:

    • 检查 QueueFamilyIndices 是否正确获取了值。

    • 检查 queuePriority 指针是否设置正确。

    • 确保 vkDestroyDevice 写在了 cleanup 中正确的位置。

总结

到目前为止,我们已经完成了 Vulkan 的"纯后台"初始化工作:

  1. Instance: 初始化 Vulkan 库。

  2. Validation Layers: 开启调试报错。

  3. Physical Device: 选中了你的显卡。

  4. Logical Device: 建立了软件驱动连接。

  5. Queue: 拿到了发送命令的通道。

但是,细心的你会发现,我们虽然创建了 GLFW 窗口,也初始化了 Vulkan,但这两者之间还没有任何联系。Vulkan 现在只是在后台空转,它还不知道该把图画到哪个窗口上。


下一步预告

下一篇,我们将打破这层隔阂。我们将引入 Window Surface (窗口表面),这是连接 Vulkan 渲染结果和 Windows 屏幕显示的桥梁。

而且,因为引入了 Surface,我们的"队列族选择逻辑"将变得稍微复杂一点点(因为不是所有显卡都支持把图画到窗口上)。

准备好了吗?我们要开始让 Vulkan 看见窗口了!

详见:Logical device and queues - Vulkan Tutorial

相关推荐
彩妙不是菜喵2 小时前
c++:初阶/初始模版
开发语言·c++
想唱rap2 小时前
MySQL表得内外连接
服务器·数据库·c++·mysql·ubuntu
A7bert7772 小时前
【DeepSeek R1部署至RK3588】RKLLM转换→板端部署→局域网web浏览
c++·人工智能·深度学习·ubuntu·自然语言处理·nlp
iAkuya2 小时前
(leetcode)力扣100 41二叉树的层序遍历(bfs)
windows·leetcode·宽度优先
星河耀银海2 小时前
C++基础数据类型与变量管理:内存安全与高效代码的基石
java·开发语言·c++
小欣加油2 小时前
leetcode 面试题17.16 按摩师
数据结构·c++·算法·leetcode·动态规划
私人珍藏库2 小时前
[Windows] 正牌STEAM小黄鸭(给游戏,视频帧数翻倍更丝滑) Lossless Scaling 3.2.2 免安装版
windows·工具·软件·win
CSDN_RTKLIB2 小时前
【字符编码】文本文件与二进制文件
c++·qt
keven-wang2 小时前
嵌入式-POCO C++开源库
开发语言·c++·开源·poco