Skip to content

Commit 1ddc590

Browse files
committed
Create Window and Instance
1 parent fba46b2 commit 1ddc590

File tree

12 files changed

+344
-1
lines changed

12 files changed

+344
-1
lines changed

CMakeLists.txt

+10
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,21 @@ set(CMAKE_CXX_EXTENSIONS OFF)
1111
set(CMAKE_DEBUG_POSTFIX "-d")
1212
set(BUILD_SHARED_LIBS OFF)
1313

14+
# add learn-vk::ext target
15+
add_subdirectory(ext)
16+
1417
# declare executable target
1518
add_executable(${PROJECT_NAME})
1619

20+
# link to ext target
21+
target_link_libraries(${PROJECT_NAME} PRIVATE
22+
learn-vk::ext
23+
)
24+
1725
# setup precompiled header
1826
target_precompile_headers(${PROJECT_NAME} PRIVATE
27+
<glm/glm.hpp>
28+
<vulkan/vulkan.hpp>
1929
)
2030

2131
# enable including headers in 'src/'

ext/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
src/

ext/CMakeLists.txt

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
project(learn-vk-ext)
2+
3+
# extract src.zip
4+
file(ARCHIVE_EXTRACT INPUT "${CMAKE_CURRENT_SOURCE_DIR}/src.zip" DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}")
5+
6+
# add GLFW to build tree
7+
set(GLFW_INSTALL OFF)
8+
set(GLFW_BUILD_DOCS OFF)
9+
message(STATUS "[glfw]")
10+
add_subdirectory(src/glfw)
11+
add_library(glfw::glfw ALIAS glfw)
12+
13+
# add GLM to build tree
14+
set(GLM_ENABLE_CXX_20 ON)
15+
message(STATUS "[glm]")
16+
add_subdirectory(src/glm)
17+
target_compile_definitions(glm PUBLIC
18+
GLM_FORCE_XYZW_ONLY
19+
GLM_FORCE_RADIANS
20+
GLM_FORCE_DEPTH_ZERO_TO_ONE
21+
GLM_FORCE_SILENT_WARNINGS
22+
GLM_ENABLE_EXPERIMENTAL
23+
GLM_EXT_INCLUDED
24+
)
25+
26+
# add Vulkan-Headers to build tree
27+
message(STATUS "[Vulkan-Headers]")
28+
add_subdirectory(src/Vulkan-Headers)
29+
30+
# declare ext library target
31+
add_library(${PROJECT_NAME} INTERFACE)
32+
add_library(learn-vk::ext ALIAS ${PROJECT_NAME})
33+
34+
# link to all dependencies
35+
target_link_libraries(${PROJECT_NAME} INTERFACE
36+
glfw::glfw
37+
glm::glm
38+
Vulkan::Headers
39+
)
40+
41+
# setup preprocessor defines
42+
target_compile_definitions(${PROJECT_NAME} INTERFACE
43+
GLFW_INCLUDE_VULKAN # enable GLFW's Vulkan API
44+
VK_NO_PROTOTYPES # Dynamically load Vulkan at runtime
45+
)
46+
47+
if(CMAKE_SYSTEM_NAME STREQUAL Linux)
48+
# link to dynamic loader
49+
target_link_libraries(${PROJECT_NAME} INTERFACE dl)
50+
endif()

ext/src.zip

2.99 MB
Binary file not shown.

guide/src/SUMMARY.md

+3
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@
88
- [Project Layout](getting_started/project_layout.md)
99
- [Validation Layers](getting_started/validation_layers.md)
1010
- [class App](getting_started/class_app.md)
11+
- [Initialization](initialization/README.md)
12+
- [GLFW Window](initialization/glfw_window.md)
13+
- [Vulkan Instance](initialization/instance.md)
1114

guide/src/initialization/README.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Initialization
2+
3+
This section deals with initialization of all the systems needed, including:
4+
5+
- Initializing GLFW and creating a Window
6+
- Creating a Vulkan Instance
7+
- Creating a Vulkan Surface
8+
- Selecting a Vulkan Physical Device
9+
- Creating a Vulkan logical Device
10+
11+
If any step here fails, it is a fatal error as we can't do anything meaningful beyond that point.
+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# GLFW Window
2+
3+
We will use GLFW (3.4) for windowing and related events. The library - like all external dependencies - is configured and added to the build tree in `ext/CMakeLists.txt`. `GLFW_INCLUDE_VULKAN` is defined for all consumers, to enable GLFW's Vulkan related functions (known as **Window System Integration (WSI)**). GLFW 3.4 supports Wayland on Linux, and by default it builds backends for both X11 and Wayland. For this reason it will need the development headers for both platforms (and some other Wayland/CMake dependencies) to configure/build successfully. A particular backend can be requested at runtime if desired via `GLFW_PLATFORM`.
4+
5+
Although it is quite feasible to have multiple windows in a Vulkan-GLFW application, that is out of scope of this guide. So for our purposes GLFW (the library) and a single window are a monolithic unit - initialized and destroyed together. This can be encapsulated in a `std::unique_ptr` with a custom deleter, especially since GLFW returns an opaque pointer (`GLFWwindow*`).
6+
7+
```cpp
8+
// window.hpp
9+
namespace lvk::glfw {
10+
struct Deleter {
11+
void operator()(GLFWwindow* window) const noexcept;
12+
};
13+
14+
using Window = std::unique_ptr<GLFWwindow, Deleter>;
15+
16+
// Returns a valid Window if successful, else throws.
17+
[[nodiscard]] auto create_window(glm::ivec2 size, char const* title) -> Window;
18+
} // namespace lvk::glfw
19+
20+
// window.cpp
21+
void Deleter::operator()(GLFWwindow* window) const noexcept {
22+
glfwDestroyWindow(window);
23+
glfwTerminate();
24+
}
25+
```
26+
27+
GLFW can create fullscreen and borderless windows, but we will stick to a standard window with decorations. Since we cannot do anything useful if we are unable to create a window, all other branches throw a fatal exception (caught in main).
28+
29+
```cpp
30+
auto glfw::create_window(glm::ivec2 const size, char const* title) -> Window {
31+
static auto const on_error = [](int const code, char const* description) {
32+
std::println(stderr, "[GLFW] Error {}: {}", code, description);
33+
};
34+
glfwSetErrorCallback(on_error);
35+
if (glfwInit() != GLFW_TRUE) {
36+
throw std::runtime_error{"Failed to initialize GLFW"};
37+
}
38+
// check for Vulkan support.
39+
if (glfwVulkanSupported() != GLFW_TRUE) {
40+
throw std::runtime_error{"Vulkan not supported"};
41+
}
42+
auto ret = Window{};
43+
ret.reset(glfwCreateWindow(size.x, size.y, title, nullptr, nullptr));
44+
if (!ret) { throw std::runtime_error{"Failed to create GLFW Window"}; }
45+
return ret;
46+
}
47+
```
48+
49+
`App` can now store a `glfw::Window` and keep polling it in `run()` until it gets closed by the user. We will not be able to draw anything to the window for a while, but this is the first step in that journey.
50+
51+
Declare it as a private member:
52+
53+
```cpp
54+
private:
55+
glfw::Window m_window{};
56+
```
57+
58+
Add some private member functions to encapsulate each operation:
59+
60+
```cpp
61+
void create_window();
62+
63+
void main_loop();
64+
```
65+
66+
Implement them and call them in `run()`:
67+
68+
```cpp
69+
void App::run() {
70+
create_window();
71+
72+
main_loop();
73+
}
74+
75+
void App::create_window() {
76+
m_window = glfw::create_window({1280, 720}, "Learn Vulkan");
77+
}
78+
79+
void App::main_loop() {
80+
while (glfwWindowShouldClose(m_window.get()) == GLFW_FALSE) {
81+
glfwPollEvents();
82+
}
83+
}
84+
```
85+
86+
> On Wayland you will not even see a window yet: it is only shown _after_ the application presents a framebuffer to it.
87+

guide/src/initialization/instance.md

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Vulkan Instance
2+
3+
Instead of linking to Vulkan (via the SDK) at build-time, we will load Vulkan at runtime. This requires a few adjustments:
4+
5+
1. In the CMake script `VK_NO_PROTOTYPES` is defined, which turns API function declarations into function pointers
6+
1. In `app.cpp` this line is added to the global scope: `VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE`
7+
1. Before and during initialization `VULKAN_HPP_DEFAULT_DISPATCHER.init()` is called
8+
9+
The first thing to do in Vulkan is to create a `vk::Instance`, which will enable enumeration of physical devices (GPUs) and creation of a logical device.
10+
11+
Since we require Vulkan 1.3, store that in a constant to be easily referenced:
12+
13+
```cpp
14+
namespace {
15+
constexpr auto vk_version_v = VK_MAKE_VERSION(1, 3, 0);
16+
} // namespace
17+
```
18+
19+
In `App`, create a new member function `create_instance()` and call it after `create_window()` in `run()`. After initializing the dispatcher, check that the loader meets the version requirement:
20+
21+
```cpp
22+
void App::create_instance() {
23+
VULKAN_HPP_DEFAULT_DISPATCHER.init();
24+
auto const loader_version = vk::enumerateInstanceVersion();
25+
if (loader_version < vk_version_v) {
26+
throw std::runtime_error{"Loader does not support Vulkan 1.3"};
27+
}
28+
}
29+
```
30+
31+
We will need the WSI instance extensions, which GLFW conveniently provides for us. Add a helper function in `window.hpp`:
32+
33+
```cpp
34+
auto glfw::instance_extensions() -> std::span<char const* const> {
35+
auto count = std::uint32_t{};
36+
auto const* extensions = glfwGetRequiredInstanceExtensions(&count);
37+
return {extensions, static_cast<std::size_t>(count)};
38+
}
39+
```
40+
41+
Continuing with instance creation, create a `vk::ApplicationInfo` object and fill it up:
42+
43+
```cpp
44+
auto app_info = vk::ApplicationInfo{};
45+
app_info.setPApplicationName("Learn Vulkan").setApiVersion(vk_version_v);
46+
```
47+
48+
Create a `vk::InstanceCreateInfo` object and fill it up:
49+
50+
```cpp
51+
auto instance_ci = vk::InstanceCreateInfo{};
52+
auto const extensions = glfw::instance_extensions();
53+
instance_ci.setPApplicationInfo(&app_info).setPEnabledExtensionNames(
54+
extensions);
55+
```
56+
57+
Add a `vk::UniqueInstance` member, create it, and initialize the dispatcher against it:
58+
59+
```cpp
60+
m_instance = vk::createInstanceUnique(instance_ci);
61+
VULKAN_HPP_DEFAULT_DISPATCHER.init(*m_instance);
62+
```
63+
64+
Make sure VkConfig is running with validation layers enabled, and debug/run the app. If "Information" level loader messages are enabled, you should see quite a bit of console output at this point: information about layers being loaded, physical devices and their ICDs being enumerated, etc.
65+
66+
If this line or equivalent is not visible in the logs, re-check your Vulkan Configurator setup and `PATH`:
67+
68+
```
69+
INFO | LAYER: Insert instance layer "VK_LAYER_KHRONOS_validation"
70+
```
71+
72+
Congratulations, you have successfully initialized a Vulkan Instance!
73+
74+
> Wayland users: seeing the window is still a long way off, these VkConfig/validation logs are your only feedback for now.

src/app.cpp

+42-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,46 @@
11
#include <app.hpp>
2+
#include <print>
3+
4+
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
25

36
namespace lvk {
4-
void App::run() {}
7+
namespace {
8+
constexpr auto vk_version_v = VK_MAKE_VERSION(1, 3, 0);
9+
} // namespace
10+
11+
void App::run() {
12+
create_window();
13+
create_instance();
14+
15+
main_loop();
16+
}
17+
18+
void App::create_window() {
19+
m_window = glfw::create_window({1280, 720}, "Learn Vulkan");
20+
}
21+
22+
void App::create_instance() {
23+
VULKAN_HPP_DEFAULT_DISPATCHER.init();
24+
auto const loader_version = vk::enumerateInstanceVersion();
25+
if (loader_version < vk_version_v) {
26+
throw std::runtime_error{"Loader does not support Vulkan 1.3"};
27+
}
28+
29+
auto app_info = vk::ApplicationInfo{};
30+
app_info.setPApplicationName("Learn Vulkan").setApiVersion(vk_version_v);
31+
32+
auto instance_ci = vk::InstanceCreateInfo{};
33+
auto const extensions = glfw::instance_extensions();
34+
instance_ci.setPApplicationInfo(&app_info).setPEnabledExtensionNames(
35+
extensions);
36+
37+
m_instance = vk::createInstanceUnique(instance_ci);
38+
VULKAN_HPP_DEFAULT_DISPATCHER.init(*m_instance);
39+
}
40+
41+
void App::main_loop() {
42+
while (glfwWindowShouldClose(m_window.get()) == GLFW_FALSE) {
43+
glfwPollEvents();
44+
}
45+
}
546
} // namespace lvk

src/app.hpp

+11
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
#pragma once
2+
#include <vulkan/vulkan.hpp>
3+
#include <window.hpp>
24

35
namespace lvk {
46
class App {
57
public:
68
void run();
9+
10+
private:
11+
void create_window();
12+
void create_instance();
13+
14+
void main_loop();
15+
16+
glfw::Window m_window{};
17+
vk::UniqueInstance m_instance{};
718
};
819
} // namespace lvk

src/window.cpp

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#include <window.hpp>
2+
#include <cstdint>
3+
#include <print>
4+
#include <stdexcept>
5+
6+
namespace lvk {
7+
namespace glfw {
8+
void Deleter::operator()(GLFWwindow* window) const noexcept {
9+
glfwDestroyWindow(window);
10+
glfwTerminate();
11+
}
12+
} // namespace glfw
13+
14+
auto glfw::create_window(glm::ivec2 const size, char const* title) -> Window {
15+
static auto const on_error = [](int const code, char const* description) {
16+
std::println(stderr, "[GLFW] Error {}: {}", code, description);
17+
};
18+
glfwSetErrorCallback(on_error);
19+
if (glfwInit() != GLFW_TRUE) {
20+
throw std::runtime_error{"Failed to initialize GLFW"};
21+
}
22+
// check for Vulkan support.
23+
if (glfwVulkanSupported() != GLFW_TRUE) {
24+
throw std::runtime_error{"Vulkan not supported"};
25+
}
26+
auto ret = Window{};
27+
ret.reset(glfwCreateWindow(size.x, size.y, title, nullptr, nullptr));
28+
if (!ret) { throw std::runtime_error{"Failed to create GLFW Window"}; }
29+
return ret;
30+
}
31+
32+
auto glfw::instance_extensions() -> std::span<char const* const> {
33+
auto count = std::uint32_t{};
34+
auto const* extensions = glfwGetRequiredInstanceExtensions(&count);
35+
return {extensions, static_cast<std::size_t>(count)};
36+
}
37+
} // namespace lvk

src/window.hpp

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#pragma once
2+
#include <GLFW/glfw3.h>
3+
#include <glm/vec2.hpp>
4+
#include <memory>
5+
#include <span>
6+
7+
namespace lvk::glfw {
8+
struct Deleter {
9+
void operator()(GLFWwindow* window) const noexcept;
10+
};
11+
12+
using Window = std::unique_ptr<GLFWwindow, Deleter>;
13+
14+
// Returns a valid Window if successful, else throws.
15+
[[nodiscard]] auto create_window(glm::ivec2 size, char const* title) -> Window;
16+
17+
[[nodiscard]] auto instance_extensions() -> std::span<char const* const>;
18+
} // namespace lvk::glfw

0 commit comments

Comments
 (0)