# 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) }
    {}
    ```