diff --git a/CMakeLists.txt b/CMakeLists.txt index 51775f56c9ea503cbccb09fc4ec55a41655fa982..08c0ced9017ee1cc9e264e9a01100b437c03e8bf 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 436502535205a36353ca32b4d07cd0cd13998f54..4f95dbfe9eaad71ec121c0d28551660d762136b1 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 f7f656ff21a8ec6432e75d39bb4df60ef7f7526e..0000000000000000000000000000000000000000 --- 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 31131e74438cc58985cfc65466bb111bb825255b..0000000000000000000000000000000000000000 --- 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 c1f9dfdc7ae6913c677cf223c70353b742ca654c..9ebb656bf34b033eda29a4abfc3c67d608bb7235 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 38dad77cecc5d961bce9f0400300efa87c7eda4c..76a1ecb614b4e9ff8936d433c7671228f4efe9f1 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 26ff61c937cae06d3ad4cdbe155f8db13c5b5b8f..e55d08541b18396db24ca39881bbed427904c890 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 f1161cf34310181e86d92e32bbcc143b1fe7906b..54e778569d47041fd567fa669db60c6230d5d6b7 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"; }