Skip to content
Snippets Groups Projects
Commit 75da2e06 authored by Gabor Galgoczy's avatar Gabor Galgoczy
Browse files

Finish lecture 01

parent af725e50
No related branches found
No related tags found
No related merge requests found
......@@ -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
......
......@@ -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
) }
{}
```
#include "App.hpp"
#include <iostream>
void App::run()
{
std::cout << "Hello World!\n";
}
#pragma once
class App {
public:
void run();
};
target_sources(${PROJECT_NAME} PRIVATE
App.cpp
main.cpp
Renderer.cpp
)
#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
) }
{}
......@@ -8,4 +8,6 @@ public:
private:
vk::UniqueInstance m_instance;
vk::UniqueDevice m_device;
vk::Queue m_graphics_queue;
};
#include "App.hpp"
#include <iostream>
int main()
{
App{}.run();
std::cout << "Hello World!\n";
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment