From 75da2e06b322bd85cba6c95b889ed97b94089562 Mon Sep 17 00:00:00 2001 From: Gabor Galgoczy <ggabor2002@gmail.com> Date: Wed, 14 Feb 2024 20:17:08 +0100 Subject: [PATCH] Finish lecture 01 --- CMakeLists.txt | 2 +- docs/lectures/01.md | 299 +++++++++++++++++++++++++++++++++++++++++++- src/App.cpp | 8 -- src/App.hpp | 6 - src/CMakeLists.txt | 3 +- src/Renderer.cpp | 99 ++++++++++++++- src/Renderer.hpp | 2 + src/main.cpp | 4 +- 8 files changed, 401 insertions(+), 22 deletions(-) delete mode 100644 src/App.cpp delete mode 100644 src/App.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 51775f5..08c0ced 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,6 @@ endif () target_precompile_headers(${PROJECT_NAME} PRIVATE <algorithm> <concepts> - <expected> <fstream> <functional> <iostream> @@ -86,6 +85,7 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE target_link_libraries(${PROJECT_NAME} PRIVATE VulkanMemoryAllocator) # GLFW +set(GLFW_VULKAN_STATIC ON) FetchContent_Declare(GLFW GIT_REPOSITORY https://github.com/glfw/glfw.git GIT_TAG 3.3.9 diff --git a/docs/lectures/01.md b/docs/lectures/01.md index 4365025..4f95dbf 100644 --- a/docs/lectures/01.md +++ b/docs/lectures/01.md @@ -14,7 +14,7 @@ A workshop folyamán a [Vulkan hivatalos C++ binding-ja](https://github.com/Khro Bármilyen Vulkan kód kiinduló eleme egy `vk::Instance` (C-ben `VkInstance`) objektum. Ez mondja meg, hogy milyen Vulkan verziót kívánunk használni, és milyen konfigurációkkal. -!!! note inline end "" +!!! info inline end "" A C++ binding-ban [RAII támogatás](https://github.com/KhronosGroup/Vulkan-Hpp/blob/main/vk_raii_ProgrammingGuide.md) is adott. De ez tapasztalat alapján nekünk inkább csak a fordítást fogja lassítani, minthogy a kódolásban könnyedséget okozna. @@ -36,3 +36,300 @@ Mivel ez egy Vulkan handle, ezért használat után nekünk kell "felszabadítan }; ``` + Mint minden, Vulkan-ban, az Instance létrehozása is explicit. Így ezt a kódot kiszervezzük egy külön függvénybe. + + ```cpp title="Renderer.cpp" + #include "Renderer.hpp" + + [[nodiscard]] auto create_instance() -> vk::UniqueInstance { + return {}; + } + + Renderer::Renderer() : m_instance{ create_instance() } {} + ``` + +Bizonyos játékmotorokhoz külön hardveres támogatás van, amit a Vulkan-nak ilyenkor lehet jelezni. Nekünk erre nem lesz szükség. Egyedül a maximum Vulkan API verziót kell jeleznünk, amit használni tervezünk. Ez jelen esetben *1.1*. + +??? example "A `vk::ApplicationInfo` beállításai" + + ```cpp + constexpr vk::ApplicationInfo application_info{ + .apiVersion = VK_API_VERSION_1_1 + }; + ``` + +!!! info inline end "Vulkan layers" + + A mi kódunk és a Vulkan függvényeken keresztül hívott kód közé [**layer**](https://renderdoc.org/vulkan-layer-guide.html)-eket helyezhetünk el profiling-hoz, debug-oláshoz, stb... + +Debug-oláshoz beállítünk [egy layer](https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Validation_layers)-t, ami majd üzenet küld, ha elrontottunk valamit. + +??? example "Ezt egy globális változóba szervezzük, mert még később is jól jöhet." + + ```cpp + const std::vector<std::string> g_layers{ + #ifdef ENGINE_VULKAN_DEBUG + "VK_LAYER_KHRONOS_validation" + #endif + }; + ``` + +!!! info "" + + A debug üzenetek megformázásához szokás egy úgynevezett [DebugUtilsMessenger](https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Validation_layers#page_Message-callback) használata. Mi ezzel most nem foglalkozunk. + +Ezeknék több mindent is beállíthatnánk még az Instance létrehozásához, de egyelőre megelékszünk ennyivel. + +!!! example "" + + ```cpp + const std::vector<const char*> g_layers{ + #ifdef ENGINE_VULKAN_DEBUG + "VK_LAYER_KHRONOS_validation" + #endif + }; + + [[nodiscard]] auto create_instance() -> vk::UniqueInstance + { + constexpr static vk::ApplicationInfo application_info{ + .apiVersion = VK_API_VERSION_1_1 + }; + + const vk::InstanceCreateInfo create_info{ + .pApplicationInfo = &application_info, + .enabledLayerCount = static_cast<uint32_t>(g_layers.size()), + .ppEnabledLayerNames = g_layers.data(), + }; + + return vk::createInstanceUnique(create_info); + } + ``` + +## Physical Device + +Minden a gépen jelen levő Vulkan-képes processzort egy `vk::PhysicalDevice` reprezentál könyvtár. Ezeket a `vk::Instance::enumeratePhysicalDevices` függvénnyel le is kérhetjük. + +Érdemes egy diszkrét GPU-val dolgozni, ha az jelen van, rosszabb esetben integrálttal. Így válasszuk ki a számunkra legmegfelelőbb egységet. + +!!! example "" + + ```cpp + [[nodiscard]] auto choose_physical_device(vk::Instance t_instance) -> vk::PhysicalDevice + { + const auto physical_devices{ t_instance.enumeratePhysicalDevices() }; + + if (std::ranges::empty(physical_devices)) { + throw std::runtime_error{ "No Vulkan physical device is available." }; + } + + auto ranked_devices_view{ + physical_devices + | std::views::transform([](vk::PhysicalDevice t_physical_device) { + switch (t_physical_device.getProperties().deviceType) { + case vk::PhysicalDeviceType::eDiscreteGpu: + return std::make_pair(t_physical_device, 2); + case vk::PhysicalDeviceType::eIntegratedGpu: + return std::make_pair(t_physical_device, 1); + default: return std::make_pair(t_physical_device, 0); + } + }) + }; + std::vector ranked_devices( + ranked_devices_view.begin(), ranked_devices_view.end() + ); + + std::ranges::sort( + ranked_devices, + std::ranges::greater{}, + &std::pair<vk::PhysicalDevice, int>::second + ); + + return ranked_devices.front().first; + } + ``` + +!!! danger "*A PhysicalDevice-t már nem szabad felszabadítani!*" + +## (Logical) Device + +Mielőtt elkezdünk a GPU-n dolgozni, azelőtt elengedhetetlen a használni tervezett funkciók megadása. Habár, mi most ezek közül nem fogunk sokat használni, azért érdemes ezt is megemlíteni. + +A (Logical) Device szintaktikailag nagyon hasonlít az Instance-hez. Itt is lesz olyan amit majd csak később állítunk be. A mostani alkalommal egyedül a `vk::Queue`-kra fogunk koncentrálni. + +A grafikus kártya egy hihetetlenül parallelizált eszköz. Ám ennek a kihasználásához adatot kell neki küldeni, és megmondani hogy mit csináljon. Ez a parancs feldolgozás *Queue*-kon keresztül történik, amelyek képesek párhuzamosan több "command" végrehajtására. + +!!! note "" + + Habár egy `vk::Queue` egyszerre több `vk::CommandBuffer` végrehajtására is képes, `vk::CommandBuffer`-eket feldolgozásra küldeni egy `vk::Queue`-nak továbbra is csak egy szálon lehetséges. + +Egy `vk::Queue` többféle feladat végrehajtására is képes - legyen az grafikai, általános, adat-átvitel, vagy valami más. A GPU tervezők számunkra a hasonló tulajdonságokkal rendelkező *Queue*-kat úgynevezett *Queue family*-kben teszik elérhetővé. + +Válasszunk ki egy grafikai munkát támogató családot (ezek megkötése, hogy adat-átvitelre is képesek legyenek), és abból is egy *Queue*-t. A mi céljainkhoz ez az egy elég lesz mindenre. + +!!! example "" + + ```cpp + [[nodiscard]] auto find_graphics_queue_family( + vk::PhysicalDevice t_physical_device + ) -> uint32_t + { + uint32_t index{}; + for (const auto& properties : t_physical_device.getQueueFamilyProperties()) + { + if (properties.queueFlags & vk::QueueFlagBits::eGraphics) { + return index; + } + index++; + } + throw std::runtime_error{ "Could not find graphics queue family" }; + } + ``` + +Végül hozzuk létre a *Device*-t is. + +!!! example "" + + ```cpp + [[nodiscard]] auto create_device(const vk::PhysicalDevice t_physical_device) + -> vk::UniqueDevice + { + const vk::DeviceQueueCreateInfo queue_create_info{ + .queueFamilyIndex = find_graphics_queue_family(t_physical_device), + .queueCount = 1, + }; + + vk::DeviceCreateInfo device_create_info{ + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &queue_create_info, + }; + + return t_physical_device.createDeviceUnique(device_create_info); + } + ``` + +!!! tip "" + + A `Renderer`-hez adjuk hozzá az így létrejövő `vk::UniqueDevice`-t, és a kiválasztott `vk::Queue`-t is. (Utóbbi lentebb a végső kódban látható.) + +## Elkészült kód + +!!! example "" + + ```cpp title="Renderer.hpp" + #pragma once + + #include <vulkan/vulkan.hpp> + + class Renderer { + public: + Renderer(); + + private: + vk::UniqueInstance m_instance; + vk::UniqueDevice m_device; + vk::Queue m_graphics_queue; + }; + ``` + + ```cpp title="Renderer.cpp" + #include "Renderer.hpp" + + #include <algorithm> + #include <ranges> + #include <vector> + + const std::vector<const char*> g_layers{ + #ifdef ENGINE_VULKAN_DEBUG + "VK_LAYER_KHRONOS_validation" + #endif + }; + + [[nodiscard]] auto create_instance() -> vk::UniqueInstance + { + constexpr static vk::ApplicationInfo application_info{ + .apiVersion = VK_API_VERSION_1_1 + }; + + const vk::InstanceCreateInfo create_info{ + .pApplicationInfo = &application_info, + .enabledLayerCount = static_cast<uint32_t>(g_layers.size()), + .ppEnabledLayerNames = g_layers.data(), + }; + + return vk::createInstanceUnique(create_info); + } + + [[nodiscard]] auto choose_physical_device(const vk::Instance t_instance) + -> vk::PhysicalDevice + { + const auto physical_devices{ t_instance.enumeratePhysicalDevices() }; + + if (std::ranges::empty(physical_devices)) { + throw std::runtime_error{ "No Vulkan physical device is available." }; + } + + auto ranked_devices_view{ + physical_devices + | std::views::transform([](vk::PhysicalDevice t_physical_device) { + switch (t_physical_device.getProperties().deviceType) { + case vk::PhysicalDeviceType::eDiscreteGpu: + return std::make_pair(t_physical_device, 2); + case vk::PhysicalDeviceType::eIntegratedGpu: + return std::make_pair(t_physical_device, 1); + default: return std::make_pair(t_physical_device, 0); + } + }) + }; + std::vector ranked_devices( + ranked_devices_view.begin(), ranked_devices_view.end() + ); + + std::ranges::sort( + ranked_devices, + std::ranges::greater{}, + &std::pair<vk::PhysicalDevice, int>::second + ); + + return ranked_devices.front().first; + } + + [[nodiscard]] auto find_graphics_queue_family( + const vk::PhysicalDevice t_physical_device + ) -> uint32_t + { + uint32_t index{}; + for (const auto& properties : t_physical_device.getQueueFamilyProperties()) + { + if (properties.queueFlags & vk::QueueFlagBits::eGraphics) { + return index; + } + index++; + } + throw std::runtime_error{ "Could not find graphics queue family" }; + } + + [[nodiscard]] auto create_device(const vk::PhysicalDevice t_physical_device) + -> vk::UniqueDevice + { + const vk::DeviceQueueCreateInfo queue_create_info{ + .queueFamilyIndex = find_graphics_queue_family(t_physical_device), + .queueCount = 1, + }; + + vk::DeviceCreateInfo device_create_info{ + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &queue_create_info, + }; + + return t_physical_device.createDeviceUnique(device_create_info); + } + + Renderer::Renderer() + : m_instance{ create_instance() }, + m_device{ create_device(choose_physical_device(*m_instance)) }, + m_graphics_queue{ m_device->getQueue( + find_graphics_queue_family(choose_physical_device(*m_instance)), + 0 + ) } + {} + ``` diff --git a/src/App.cpp b/src/App.cpp deleted file mode 100644 index f7f656f..0000000 --- a/src/App.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "App.hpp" - -#include <iostream> - -void App::run() -{ - std::cout << "Hello World!\n"; -} diff --git a/src/App.hpp b/src/App.hpp deleted file mode 100644 index 31131e7..0000000 --- a/src/App.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -class App { -public: - void run(); -}; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c1f9dfd..9ebb656 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,4 @@ target_sources(${PROJECT_NAME} PRIVATE - App.cpp main.cpp Renderer.cpp -) \ No newline at end of file +) diff --git a/src/Renderer.cpp b/src/Renderer.cpp index 38dad77..76a1ecb 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -1,5 +1,100 @@ #include "Renderer.hpp" -[[nodiscard]] auto create_instance() -> vk::UniqueInstance {} +#include <algorithm> +#include <ranges> +#include <vector> -Renderer::Renderer() : m_instance{ create_instance() } {} +const std::vector<const char*> g_layers{ +#ifdef ENGINE_VULKAN_DEBUG + "VK_LAYER_KHRONOS_validation" +#endif +}; + +[[nodiscard]] auto create_instance() -> vk::UniqueInstance +{ + constexpr static vk::ApplicationInfo application_info{ + .apiVersion = VK_API_VERSION_1_1 + }; + + const vk::InstanceCreateInfo create_info{ + .pApplicationInfo = &application_info, + .enabledLayerCount = static_cast<uint32_t>(g_layers.size()), + .ppEnabledLayerNames = g_layers.data(), + }; + + return vk::createInstanceUnique(create_info); +} + +[[nodiscard]] auto choose_physical_device(const vk::Instance t_instance) + -> vk::PhysicalDevice +{ + const auto physical_devices{ t_instance.enumeratePhysicalDevices() }; + + if (std::ranges::empty(physical_devices)) { + throw std::runtime_error{ "No Vulkan physical device is available." }; + } + + auto ranked_devices_view{ + physical_devices + | std::views::transform([](vk::PhysicalDevice t_physical_device) { + switch (t_physical_device.getProperties().deviceType) { + case vk::PhysicalDeviceType::eDiscreteGpu: + return std::make_pair(t_physical_device, 2); + case vk::PhysicalDeviceType::eIntegratedGpu: + return std::make_pair(t_physical_device, 1); + default: return std::make_pair(t_physical_device, 0); + } + }) + }; + std::vector ranked_devices( + ranked_devices_view.begin(), ranked_devices_view.end() + ); + + std::ranges::sort( + ranked_devices, + std::ranges::greater{}, + &std::pair<vk::PhysicalDevice, int>::second + ); + + return ranked_devices.front().first; +} + +[[nodiscard]] auto find_graphics_queue_family( + const vk::PhysicalDevice t_physical_device +) -> uint32_t +{ + uint32_t index{}; + for (const auto& properties : t_physical_device.getQueueFamilyProperties()) + { + if (properties.queueFlags & vk::QueueFlagBits::eGraphics) { + return index; + } + index++; + } + throw std::runtime_error{ "Could not find graphics queue family" }; +} + +[[nodiscard]] auto create_device(const vk::PhysicalDevice t_physical_device) + -> vk::UniqueDevice +{ + const vk::DeviceQueueCreateInfo queue_create_info{ + .queueFamilyIndex = find_graphics_queue_family(t_physical_device), + .queueCount = 1, + }; + + vk::DeviceCreateInfo device_create_info{ + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &queue_create_info, + }; + + return t_physical_device.createDeviceUnique(device_create_info); +} + +Renderer::Renderer() + : m_instance{ create_instance() }, + m_device{ create_device(choose_physical_device(*m_instance)) }, + m_graphics_queue{ m_device->getQueue( + find_graphics_queue_family(choose_physical_device(*m_instance)), + 0 + ) } +{} diff --git a/src/Renderer.hpp b/src/Renderer.hpp index 26ff61c..e55d085 100644 --- a/src/Renderer.hpp +++ b/src/Renderer.hpp @@ -8,4 +8,6 @@ public: private: vk::UniqueInstance m_instance; + vk::UniqueDevice m_device; + vk::Queue m_graphics_queue; }; diff --git a/src/main.cpp b/src/main.cpp index f1161cf..54e7785 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ -#include "App.hpp" +#include <iostream> int main() { - App{}.run(); + std::cout << "Hello World!\n"; } -- GitLab