# Inicializálás Mielőtt nekilátnál a kódolásnak, hozz létre egy kiinduló projektet. A példák kiinduló kódja megtalálható [ezen a linken](https://git.sch.bme.hu/kszk/devteam/vulkan-workshop/-/tree/starter). A példák Vulkan mellett [glm](https://github.com/g-truc/glm), [GLFW](https://www.glfw.org/) és [VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator?tab=readme-ov-file) könyvtárakat használnak. Érdemes még a [bevezető előadás](https://git.sch.bme.hu/kszk/devteam/vulkan-workshop/-/blob/master/docs/Vulkan%20Bevezet%C5%91.pptx?ref_type=heads) áttanulmányozása. ## Általános A workshop folyamán a [Vulkan hivatalos C++ binding-ja](https://github.com/KhronosGroup/Vulkan-Hpp)it fogjuk használni, és a kód részletek is C++20-ban lettek megírva. ## Instance 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. !!! 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. Mivel ez egy Vulkan handle, ezért használat után nekünk kell "felszabadítani". Habár nem best practice, de mi minden ilyen handle helyett egy C++ `unique_ptr`-hez hasonló struktúrával fogjuk használni ezeket, amit a Vulkan binding biztosít. ??? example "Hozzuk létre a `Renderer` osztályt!" ```cpp title="Renderer.hpp" #pragma once #include <vulkan/vulkan.hpp> class Renderer { public: Renderer(); private: vk::UniqueInstance m_instance; }; ``` 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 és appokhoz külön hardveres támogatás van, amit a Vulkan-nak ilyenkor lehet jelezni. Az applikációnk által használni tervezett legnagyobb API verzió *1.0* lesz. Ezeket az értékeket gyűjti össze a `vk::Application` struct, amelyet mi default-inicializálunk. ??? example "Hozzuk létre a `vk::ApplicationInfo`-t!" ```cpp constexpr vk::ApplicationInfo application_info{}; ``` !!! 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 elrontanánk 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 [DebugUtilsMessenger](https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Validation_layers#page_Message-callback)-t használni. Mi ezzel most nem foglalkozunk. Ezeknél több mindent is beállíthatnánk még az Instance létrehozásához, de egyelőre megelégszü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{}; 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`-al 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 extra funkciók megadása. Egyelőre ezzel mi még nem élünk. 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álunk. 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 (ennek megkötése, hogy adat-átvitelt is támogasson), é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" }; } ``` Adott minden, hogy a *Device*-t is létrehozzuk. !!! 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::PhysicalDevice m_physical_device; uint32_t m_graphics_queue_family; 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{}; 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_physical_device{ choose_physical_device(*m_instance) }, m_graphics_queue_family{ find_graphics_queue_family(m_physical_device) }, m_device{ create_device(m_physical_device) }, m_graphics_queue{ m_device->getQueue(m_graphics_queue_family, 0) } {} ```