Vulkan教程 – 02 代码架构及实例创建

本篇博客继续学习Vulkan,主要是基础代码学习记录。

先来一个通用的结构:

#include <vulkan/vulkan.h>

#include <iostream>
#include <stdexcept>
#include <functional>
#include <cstdlib>

class HelloTriangleApplication {
public:
    void run() {
        initVulkan();
        mainLoop();
        cleanup();
    }

private:
    void initVulkan() {

    }

    void mainLoop() {

    }

    void cleanup() {

    }
};

int main() {
    HelloTriangleApplication app;

    try {
        app.run();
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

先从SDK中包含一个vulkan.h的头文件,stdexcept和iostream用于展示错误信息,functional头文件用于lambda函数,也就是资源管理部分的。cstdlib提供了EXIT_SUCCESS和EXIT_FAILURE宏。该程序包装成一个类,我们可以在其中存放Vulkan对象和私有类成员,还可以添加函数来初始化,这会在initVulkan函数中被调用。一切准备就绪,我们进入主循环开始渲染frames,我们会填充主循环,让它包含一个直到窗口被关闭前不断迭代的循环。窗口关闭后,mainLoop返回,在cleanup方法中销毁资源。以后我们会每章添加一些函数以便initVulkan调用,私有类成员中也会添加Vulkan对象,然后最终在cleanup中清除。

Vulkan对象要么是vkCreateXXX要么是vkAllocateXXX创建,不需要的时候,用vkDestroyXXX或者vkFreeXXX来释放。为了更好地开展工作,还要集成GLFW,把一开始的vulkan头文件包含那一行替换为:

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

这样GLFW就会包含自己的定义并自动加载Vulkan的头。现在对前面的框架添加一个initWindow方法,该方法第一步应该是glfwInit,也就是初始化GLFW库。因为GLFW一开始设计的就是创建OpenGL上下文,那么我们要告诉它你不要这个样子:

glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);

接着,窗口大小修改会要特殊操作,所以暂且禁用:

glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

然后创建窗口:

GLFWwindow* window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr);

这里就是创建800*600的窗口,第四个参数指定在哪个屏幕显示,最后一个知识OpenGL相关所以这里忽略。为了让应用不断运行,直到出现错误或者窗口被关闭,需要向mainLoop中添加事件循环如下:

void mainLoop() {
    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();
    }
}

最后,如果关闭了,需要清理,代码如下:

void cleanup() {
    glfwDestroyWindow(window);
    glfwTerminate();
}

最后完整的代码如下:

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include <iostream>
#include <stdexcept>
#include <functional>
#include <cstdlib>

const int WIDTH = 800;
const int HEIGHT = 600;

class HelloTriangleApplication {
public:
    void run() {
        initWindow();
        initVulkan();
        mainLoop();
        cleanup();
    }

private:
    GLFWwindow* window;

    void initWindow() {
        glfwInit();
        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
        window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
    }

    void initVulkan() {

    }

    void mainLoop() {
        while (!glfwWindowShouldClose(window)) {
            glfwPollEvents();
        }
    }

    void cleanup() {
        glfwDestroyWindow(window);
        glfwTerminate();
    }
};

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实例,实例是程序和Vulkan库之间的连接,创建实例需要对驱动提供一些自己程序的详细信息。需要写一个createInstance的方法,而我们需要提供的信息则是VkApplicationInfo:

void initVulkan() {
    createInstance();
}

void createInstance() {
    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;
}

还要再添加一个类成员来保存实例:

VkInstance instance;

另外,为了提供充足的信息来创建实例,我们还需要提供VkInstanceCreateInfo。

VkInstanceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;

由于Vulkan是平台无关的,所以需要一个扩展来和窗口系统交互。GLFW有许多内置的函数能返回这些扩展,我们需要将其传给前面创建的结构体:

uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;

后两个参数表明了所需的全局扩展。最后还要两个成员,表明启用全局验证层,后面的章节会深入学习,所以这里留空:

createInfo.enabledLayerCount = 0;

现在可以创建Vulkan实例了:

if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
    throw std::runtime_error("failed to create instance!");
}

基本上所有Vulkan方法返回的VkResult不是VK_SUCCESS就是错误码。现在就可以运行起来了,得到一个空窗口,没有报错即可。

可以通过vkEnumerateInstanceExtensionProperties函数在创建实例前获取支持的扩展列表,它需要一个指向存储扩展个数的变量,另外还需要一组VkExtensionProperties来存储这些扩展的详情。分配数组之前要确定这些扩展有多少个:

uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> extensions(extensionCount);
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());

然后就能打印支持的扩展了:

std::cout << "available extensions:" << std::endl;

for (const auto& extension : extensions) {
    std::cout << "\\t" << extension.extensionName << std::endl;
}

得到的输出如下:

available extensions:
        VK_EXT_debug_report
        VK_EXT_display_surface_counter
        VK_KHR_get_physical_device_properties2
        VK_KHR_get_surface_capabilities2
        VK_KHR_surface
        VK_KHR_win32_surface
        VK_KHR_device_group_creation
        VK_KHR_external_fence_capabilities
        VK_KHR_external_memory_capabilities
        VK_KHR_external_semaphore_capabilities
        VK_NV_external_memory_capabilities
        VK_EXT_debug_utils

最终,完整的代码如下:

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include <iostream>
#include <stdexcept>
#include <functional>
#include <cstdlib>

const int WIDTH = 800;
const int HEIGHT = 600;

class HelloTriangleApplication {
public:
    void run() {
        initWindow();
        initVulkan();
        mainLoop();
        cleanup();
    }

private:
    GLFWwindow* window;
    VkInstance instance;

    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();
    }

    void mainLoop() {
        while (!glfwWindowShouldClose(window)) {
            glfwPollEvents();
        }
    }

    void cleanup() {
        glfwDestroyWindow(window);
        glfwTerminate();
    }

    void createInstance() {
        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;

        uint32_t glfwExtensionCount = 0;
        const char** glfwExtensions;
        glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
        
        createInfo.enabledExtensionCount = glfwExtensionCount;
        createInfo.ppEnabledExtensionNames = glfwExtensions;
        
        createInfo.enabledLayerCount = 0;

        uint32_t extensionCount = 0;
        vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
        std::vector<VkExtensionProperties> extensions(extensionCount);
        vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());

        std::cout << "available extensions:" << std::endl;

        for (const auto& extension : extensions) {
            std::cout << "\\t" << extension.extensionName << std::endl;
        }

        if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
            throw std::runtime_error("failed to create instance!");
        }
    }
};

int main() {
    HelloTriangleApplication app;

    try {
        app.run();
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

 

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片