Vulkan 编程指南 - 基础代码 (Vulkan Tutorial / Base code / 00_base_code.cpp)

本文介绍了Vulkan编程的基础,包括设置Vulkan头文件,创建应用类,初始化窗口以及资源管理。使用GLFW库创建无OpenGL上下文的窗口,通过窗口循环处理事件。资源管理强调了Vulkan对象需要明确创建和销毁。

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

Vulkan 编程指南 - 基础代码 (Vulkan Tutorial / Base code / 00_base_code.cpp)

仅供个人学习、研究使用,建议大家阅读 Vulkan Tutorial

Drawing a triangle / Setup / Base code
https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Base_code

Vulkan Tutorial
https://vulkan-tutorial.com/

GitHub Repository
https://github.com/Overv/VulkanTutorial

1 Base code

1.1 General structure (总体结构)

#include <vulkan/vulkan.h>

#include <iostream>
#include <stdexcept>
#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;
}

We first include the Vulkan header from the LunarG SDK, which provides the functions, structures and enumerations. The stdexcept and iostream headers are included for reporting and propagating errors. The cstdlib header provides the EXIT_SUCCESS and EXIT_FAILURE macros.
在代码中,我们首先包含了 Vulkan 头文件,它为我们提供了 Vulkan 的函数,结构体和枚举。包含 stdexcept and iostream 头文件用来报错。包含 cstdlib 头文件用来使用 EXIT_SUCCESS and EXIT_FAILURE macros。

The program itself is wrapped into a class where we’ll store the Vulkan objects as private class members and add functions to initiate each of them, which will be called from the initVulkan function. Once everything has been prepared, we enter the main loop to start rendering frames. We’ll fill in the mainLoop function to include a loop that iterates until the window is closed in a moment. Once the window is closed and mainLoop returns, we’ll make sure to deallocate the resources we’ve used in the cleanup function.
程序本身被包装成一个类,我们将把 Vulkan 对象存储为私有类成员,并添加函数来初始化每个对象,这些函数将被 initVulkan 函数调用。一旦一切准备就绪,我们将进入主循环,开始渲染操作。mainLoop 函数包含了一个循环,直到窗口被关闭,才会跳出这个循环。一旦窗口关闭,mainLoop 返回,我们使用 cleanup 函数完成资源的清理。

If any kind of fatal error occurs during execution then we’ll throw a std::runtime_error exception with a descriptive message, which will propagate back to the main function and be printed to the command prompt. To handle a variety of standard exception types as well, we catch the more general std::exception. One example of an error that we will deal with soon is finding out that a certain required extension is not supported.
程序在执行过程中,如果发生错误,会抛出一个带有错误描述信息的 std::runtime_error 异常,我们在 main 函数捕获这个异常,并将异常的描述信息打印到控制台窗口。为了处理多种不同类型的异常,我们使用更加通用的 std::exception 来接受异常。一个比较常见的异常就是请求的扩展不被支持。

Roughly every chapter that follows after this one will add one new function that will be called from initVulkan and one or more new Vulkan objects to the private class members that need to be freed at the end in cleanup.
initVulkan 函数中初始化它们,在 cleanup 函数中清理它们。

1.2 Resource management

Just like each chunk of memory allocated with malloc requires a call to free, every Vulkan object that we create needs to be explicitly destroyed when we no longer need it. In C++ it is possible to perform automatic resource management using RAII or smart pointers provided in the <memory> header. However, I’ve chosen to be explicit about allocation and deallocation of Vulkan objects in this tutorial. After all, Vulkan’s niche is to be explicit about every operation to avoid mistakes, so it’s good to be explicit about the lifetime of objects to learn how the API works.
就像用 malloc 分配的每块内存都需要调用 free 一样,我们创建的每个 Vulkan 对象在不再需要时都需要明确销毁。在 C++ 中,可以使用 RAII 或 <memory> 头文件中提供的智能指针来执行自动资源管理。然而,我选择在本教程中对 Vulkan 对象的分配和删除进行明确说明。Vulkan 的一个核心思想就是通过显式地定义每一个操作来避免出现不一致的现象,所以明确对象的生命周期对学习 API 的工作方式是有好处的。

After following this tutorial, you could implement automatic resource management by writing C++ classes that acquire Vulkan objects in their constructor and release them in their destructor, or by providing a custom deleter to either std::unique_ptr or std::shared_ptr, depending on your ownership requirements. RAII is the recommended model for larger Vulkan programs, but for learning purposes it’s always good to know what’s going on behind the scenes.
学完本教程后,可以通过编写 C++ 类来实现自动资源管理,以在其构造函数中获取 Vulkan 对象,在析构函数中释放它们,或者根据你的所有权要求,为 std::unique_ptr or std::shared_ptr 提供一个自定义的删除器。RAII 是针对大型 Vulkan 程序的推荐模型,但是出于学习目的,知道幕后发生了什么总是一件好事。

Vulkan objects are either created directly with functions like vkCreateXXX, or allocated through another object with functions like vkAllocateXXX. After making sure that an object is no longer used anywhere, you need to destroy it with the counterparts vkDestroyXXX and vkFreeXXX. The parameters for these functions generally vary for different types of objects, but there is one parameter that they all share: pAllocator. This is an optional parameter that allows you to specify callbacks for a custom memory allocator. We will ignore this parameter in the tutorial and always pass nullptr as argument.
这些函数的参数对于不同类型的对象通常是不同的,但都具有一个 pAllocator 参数。我们可以通过这个参数来指定回调函数编写自己的内存分配器。

1.3 Integrating GLFW

Vulkan works perfectly fine without creating a window if you want to use it for off-screen rendering, but it’s a lot more exciting to actually show something!
Vulkan 可以在完全没有窗口的情况下工作,通常,在离屏渲染时会这样做。一般而言,还是需要一个窗口来显示渲染结果给用户。

First replace the #include <vulkan/vulkan.h> line with

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

That way GLFW will include its own definitions and automatically load the Vulkan header with it. Add a initWindow function and add a call to it from the run function before the other calls. We’ll use that function to initialize GLFW and create a window.
上面的代码将 GLFW 的定义包含进来,而 GLFW 会自动包含 Vulkan 头文件。添加一个 initWindow 函数,并在其它调用之前从 run 函数中添加对它的调用。我们将使用该函数来初始化 GLFW 并创建一个窗口。

void run() {
    initWindow();
    initVulkan();
    mainLoop();
    cleanup();
}

private:
    void initWindow() {

    }

The very first call in initWindow should be glfwInit(), which initializes the GLFW library. Because GLFW was originally designed to create an OpenGL context, we need to tell it to not create an OpenGL context with a subsequent call:
initWindow 函数首先调用了 glfwInit() 函数来初始化 GLFW library,因为 GLFW 最初被设计为创建一个 OpenGL 上下文,所以我们需要显式地设置 GLFW 阻止它自动创建 OpenGL 上下文:

glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);

Because handling resized windows takes special care that we’ll look into later, disable it for now with another window hint call:
窗口大小变化地处理需要注意很多地方,我们会在以后介绍它,暂时我们先禁止窗口大小改变:

glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

All that’s left now is creating the actual window. Add a GLFWwindow* window; private class member to store a reference to it and initialize the window with:
现在剩下的就是创建实际的窗口了。添加一个 GLFWwindow* window; 私有类成员来存储对它的引用,并用下面的函数初始化该窗口:

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

The first three parameters specify the width, height and title of the window. The fourth parameter allows you to optionally specify a monitor to open the window on and the last parameter is only relevant to OpenGL.
前三个参数指定窗口的宽度、高度和标题。第四个参数允许你选择性地指定打开窗口的显示器,最后一个参数只与 OpenGL 有关。

It’s a good idea to use constants instead of hardcoded width and height numbers because we’ll be referring to these values a couple of times in the future. I’ve added the following lines above the HelloTriangleApplication class definition:
使用常量而不是硬编码的宽度和高度数字是个好主意,因为我们在未来会多次引用这些值。我在 HelloTriangleApplication 类定义的上方添加了以下几行:

const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;

and replaced the window creation call with

window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);

You should now have a initWindow function that looks like this:

void initWindow() {
    glfwInit();

    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

    window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
}

To keep the application running until either an error occurs or the window is closed, we need to add an event loop to the mainLoop function as follows:
为了保持应用程序的运行,直到错误发生或窗口关闭,我们需要在 mainLoop 函数中添加一个事件循环。

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

This code should be fairly self-explanatory. It loops and checks for events like pressing the X button until the window has been closed by the user. This is also the loop where we’ll later call a function to render a single frame.
上面的代码应该非常直白,每次循环,检测窗口的关闭按钮是否被按下,如果没有被按下,就执行事件处理,否则结束循环。在之后的章节,我们会在这一循环中调用渲染函数来渲染一帧画面。

Once the window is closed, we need to clean up resources by destroying it and terminating GLFW itself. This will be our first cleanup code:
一旦窗口关闭,我们就可以开始结束 GLFW,然后清除我们自己创建的资源,这在 cleanup 函数中进行:

void cleanup() {
    glfwDestroyWindow(window);

    glfwTerminate();
}

When you run the program now you should see a window titled Vulkan show up until the application is terminated by closing the window.
当你现在运行该程序时,你应该看到一个名为 Vulkan 的窗口出现,直到应用程序通过关闭该窗口终止。

00_base_code.cpp
https://github.com/Overv/VulkanTutorial/blob/main/code/00_base_code.cpp

yongqiang@yongqiang:~/vulkan_workspace$ git clone https://github.com/Overv/VulkanTutorial.git
Cloning into 'VulkanTutorial'...
...
yongqiang@yongqiang:~/vulkan_workspace$
yongqiang@yongqiang:~/vulkan_workspace$ cd VulkanTutorial/
yongqiang@yongqiang:~/vulkan_workspace/VulkanTutorial$ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
yongqiang@yongqiang:~/vulkan_workspace/VulkanTutorial$
yongqiang@yongqiang:~/vulkan_workspace/VulkanTutorial$ git pull
Already up to date.
yongqiang@yongqiang:~/vulkan_workspace/VulkanTutorial$

base_code_00.cpp

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

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

const uint32_t WIDTH = 800;
const uint32_t 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, "yongqiang", 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;
}

Makefile

CFLAGS = -std=c++17 -O2
LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi

VulkanTest: base_code_00.cpp
        g++ $(CFLAGS) -o VulkanTest base_code_00.cpp $(LDFLAGS)

.PHONY: test clean

test: VulkanTest
        ./VulkanTest

clean:
        rm -f VulkanTest

yongqiang@yongqiang:~/vulkan_workspace/base_code_00$ make clean
rm -f VulkanTest
yongqiang@yongqiang:~/vulkan_workspace/base_code_00$
yongqiang@yongqiang:~/vulkan_workspace/base_code_00$ make test
g++ -std=c++17 -O2 -o VulkanTest base_code_00.cpp -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi
./VulkanTest
yongqiang@yongqiang:~/vulkan_workspace/base_code_00$
yongqiang@yongqiang:~/vulkan_workspace/base_code_00$ ls
Makefile  VulkanTest  base_code_00.cpp
yongqiang@yongqiang:~/vulkan_workspace/base_code_00$

在这里插入图片描述

References

https://yongqiang.blog.csdn.net/
Vulkan Tutorial https://vulkan-tutorial.com/
Vulkan 教程 https://geek-docs.com/vulkan/vulkan-tutorial/vulkan-tutorial-index.html

Vulkan是用于图形和计算设备的编程接口。 Vulkan设备通常由处理器和多个固定功能硬件块组成,以加速在图形和计算中使用的操作。设备中的处理器通常是一个非常宽的多线程处理器,因此Vulkan中的计算模型主要基于并行计算。 Vulkan设备还可以访问可能与您的应用程序运行所在的主处理器共享或可能不共享的内存。 Vulkan也暴露了这个内存给你。 Vulkan是一个显式的API。也就是说,几乎一切都是你的责任。驱动程序是一个软件,它接收形成API的命令和数据,并将它们转换为硬件可以理解的东西。在较旧的API(如OpenGL)中,驱动程序将跟踪许多对象的状态,为您管理内存和同步,并在运行时检查应用程序中的错误。这对开发人员非常有用,但是一旦应用程序调试和运行正常,就会浪费宝贵的CPU时间。 Vulkan通过将几乎所有状态跟踪,同步和内存管理置于应用程序开发人员手中,并通过对必须启用的层委派正确性的检查来解决这个问题。他们在正常情况下不参与您的申请的执行。 由于这些原因,Vulkan既非常冗长又有点脆弱。你需要做大量的工作,以获得Vulkan运行良好,不正确的使用API​​通常会导致图形损坏,甚至程序崩溃,在旧的API,你会收到一个有用的错误消息。作为交换,Vulkan提供对设备的更多控制,一个干净的线程模型,以及比它取代的API更高的性能。 此外,Vulkan被设计为不仅仅是一个图形API。它可以用于异构设备,如图形处理单元(GPU),数字信号处理器(DSP)和固定功能硬件。功能分为粗粒度,宽泛重叠的类别。当前版本的Vulkan定义了传输类别,用于复制数据;计算类别,用于在计算工作负载上运行着色器;和图形类别,其中包括光栅化,图元装配,混合,深度和模板测试,以及图形编程人员熟悉的其他功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Yongqiang Cheng

梦想不是浮躁,而是沉淀和积累。

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

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

打赏作者

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

抵扣说明:

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

余额充值