Post

Vulkan Tutorial: Getting Started

Hello! Welcome to the world of Vulkan.

Vulkan is the next-generation graphics framework following OpenGL by the Khronos Group, providing better abstraction and performance for modern GPUs. Unlike DirectX, which is only available on Windows, or Metal, which is only available on macOS, Vulkan is designed to operate cross-platform while minimizing performance loss. Additionally, Vulkan minimizes issues related to global state, such as difficulties in manipulation in a multi-threaded environment and being a monolithic state machine, and fragmented implementation which were present in OpenGL.

In exchange for these advantages, Vulkan delegates much of the responsibility for GPU manipulation to the developer. Therefore, developers need to perform tasks like synchronization, memory management, and graphic pipeline configuration directly, resulting in a higher development difficulty compared to other frameworks like OpenGL1. Furthermore, since its underlying language is C, developers may need to write somewhat verbose code inheriting the limitations of the C language.

Despite existing Vulkan tutorials such as Vulkan Tutorial or VulkanGuide, the reason I am writing a new tutorial is because they use the previously mentioned C-based Vulkan API. This tutorial is written using the official Vulkan C++ binding provided by the Khronos Group, Vulkan-Hpp, specifically focusing on ease of use by utilizing the RAII pattern. With the RAII pattern, managing the lifecycle of all Vulkan objects through the constructor and destructor of classes eliminates the need for an explicit deletion queue. Additionally, you can enjoy enhanced features provided by Vulkan-Hpp.

Nevertheless, Vulkan is still notably more challenging than other frameworks. If you have not encountered graphics programming before reading this tutorial, I recommend building a foundation in graphics programming using easier frameworks such as OpenGL or, in the case of C++, SFML.

Also, this tutorial does not explain the mathematical basics, like 3D coordinate projection using matrix operations or Phong 셰이딩. If you need to understand these aspects, I recommend sites like Learn OpenGL.

Once again, Vulkan is a verbose API designed for developers who want to leverage high-performance GPU manipulation across platforms. Before starting the tutorial (though you can learn while following the tutorial), here are some prerequisites:

  • Understanding of Modern C++: Since Vulkan is a low-level API that directly manipulates memory, understanding pointer operations is essential. Additionally, to correctly use the RAII pattern provided by Vulkan-Hpp, understanding move semantics (in name of std::move), perfect forwarding, and destructor pattern is required, and these will be progressed without further explanation. Furthermore, we will use features introduced in C++20 such as Concepts, Ranges, and module syntax, and explanations will be provided alongside.
  • Basic Understanding of CMake: This tutorial will use CMake and vcpkg for external library dependencies of the code we’ll use, and CMake scripts will be used for shader compilation automation. However, we won’t write complex CMake scripts ourselves, and it’s okay to use tools like MSBuild or Xcode if you find it feasible.
  • Some Understanding of Computer Graphics: This tutorial will use GLM for vector and matrix manipulation and will include explanations for lighting calculations and physically based rendering in the future. We’ll focus on implementing these and provide references for theoretical explanations.

The prerequisites might seem lengthy. To those of you who have read this far and still want to follow this tutorial, congratulations. We will now begin uncompromising GPU programming without performance compromises. Without further ado, let’s set up the project.


Project Setup

Install Vulkan SDK

First, you need to install the Vulkan SDK. You can download the SDK suitable for your operating system from the LunarG website. The SDK includes code for Vulkan API and validation layers to verify if you are using the API correctly. Run the vkcube application installed with the SDK to confirm Vulkan availability in your current environment.

Screenshot of running vkcube application Screenshot of running the vkcube application

Create CMake Project, Enable std and vulkan_hpp Modules

This project uses C++23. Set CMAKE_CXX_STANDARD to 23 as follows to check if the std::println function (supported from C++23) works.

Subject: [PATCH] C++23 project configuration.
---
Index: src/CMakeLists.txt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,6 @@
+cmake_minimum_required(VERSION 3.28)
+project(vulkan-tutorial)
+
+set(CMAKE_CXX_STANDARD 23)
+
+add_executable(vulkan-tutorial main.cpp)
\ No newline at end of file
Index: src/main.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,5 @@
+#include <print>
+
+int main(){
+    std::println("Hello world!");
+}
\ No newline at end of file

In CMake, you can include module files (Clang provides them with .cppm extension, and MSVC with .ixx extension) using target_sources. We will use the standard library module (std), which is provided differently depending on the compiler. Refer to the official tutorials for both compilers (Clang, MSVC).

This project uses the Standard Library Modules (P2456R3) added in C++23. As of March 30, 2024, three major compilers (GCC, Clang, MSVC) do not support it. Clang supports it experimentally by cloning its LLVM repository. If your current compiler does not support it, you can proceed by including the header file containing the used STL functions/objects (e.g., <vector> for std::vector). You can check compiler support on cppreference.

For consistency, we’ll use the CppStandardLibraryModule repository, which abstracts the use of standard library modules in CMake. Check the README of the repository for more information. Add the following code, and reload the project by adding CMake variables (LIBCXX_BUILD for Clang and VCTOOLS_INSTALL_DIR for MSVC) in your IDE (we’ll do it in the IDE for this tutorial). Then, change #include <print> in main.cpp to import std; and check if it runs.

Define CMake variable in IDE setting Example of adding LIBCXX CMake variable in IDE

Subject: [PATCH] Use standard library module.
---
Index: src/CMakeLists.txt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -3,4 +3,17 @@
 
 set(CMAKE_CXX_STANDARD 23)
 
+# --------------------
+# Enable standard library module.
+# --------------------
+
+file(DOWNLOAD https://raw.githubusercontent.com/stripe2933/CppStandardLibraryModule/main/cmake/EnableStandardLibraryModule.cmake
+    ${CMAKE_BINARY_DIR}/EnableStandardLibraryModule.cmake
+)
+include(${CMAKE_BINARY_DIR}/EnableStandardLibraryModule.cmake)
+
+# --------------------
+# Project executables.
+# --------------------
+
 add_executable(vulkan-tutorial main.cpp)
\ No newline at end of file
Index: src/main.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main.cpp b/src/main.cpp
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,4 +1,4 @@
-#include <print>
+import std;
 
 int main(){
     std::println("Hello world!");

Starting from CMake 3.30, native support for standard library modules will be provided. This tutorial will be updated after the release of CMake 3.30.

Next, let’s verify Vulkan framework dependency support in CMake. Use the find_package statement to ensure that the Vulkan::Vulkan target is correctly linked to the vulkan-tutorial target.

Subject: [PATCH] Check Vulkan dependency.
---
Index: src/CMakeLists.txt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -12,8 +12,15 @@
 )
 include(${CMAKE_BINARY_DIR}/EnableStandardLibraryModule.cmake)
 
+# --------------------
+# External dependencies.
+# --------------------
+
+find_package(Vulkan REQUIRED)
+
 # --------------------
 # Project executables.
 # --------------------
 
-add_executable(vulkan-tutorial main.cpp)
\ No newline at end of file
+add_executable(vulkan-tutorial main.cpp)
+target_link_libraries(vulkan-tutorial PRIVATE Vulkan::Vulkan)
\ No newline at end of file

Similarly, enable the Vulkan-Hpp module. Details can be found in the C++20 named module section of the Vulkan-Hpp official documentation. For consistency, we’ll include a file containing the script and create a VulkanHppModule target, linking it to the vulkan-tutorial target.

Subject: [PATCH] Use Vulkan-Hpp module.
---
Index: src/CMakeLists.txt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -18,9 +18,18 @@
 
 find_package(Vulkan REQUIRED)
 
+# --------------------
+# Module configurations for external dependencies.
+# --------------------
+
+file(DOWNLOAD https://raw.githubusercontent.com/stripe2933/Cpp20Module/main/Vulkan.cmake
+    ${PROJECT_BINARY_DIR}/Vulkan.cmake
+)
+include(${PROJECT_BINARY_DIR}/Vulkan.cmake)
+
 # --------------------
 # Project executables.
 # --------------------
 
 add_executable(vulkan-tutorial main.cpp)
-target_link_libraries(vulkan-tutorial PRIVATE Vulkan::Vulkan)
\ No newline at end of file
+target_link_libraries(vulkan-tutorial PRIVATE VulkanHppModule)
\ No newline at end of file
Index: src/main.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main.cpp b/src/main.cpp
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,4 +1,5 @@
 import std;
+import vulkan_hpp;
 
 int main(){
     std::println("Hello world!");

Add import vulkan_hpp; to main.cpp and check if the project builds correctly (since Vulkan-Hpp is a header-only library consisting of numerous templates, it takes a few seconds to build).

Congratulations on completing both steps. You are now ready to write Vulkan code in earnest.

Using modules reduces build time significantly since it uses pre-compiled code instead of including code through the preprocessor during each build. If you want to experience the advantages of module usage, replace import vulkan_hpp; with #include <vulkan/vulkan_raii.hpp> and compare build times. You’ll notice a significant difference.

The complete code changes for this tutorial are as follows.

This post is licensed under CC BY 4.0 by the author.