diff --git a/.clang-format b/.clang-format
index c94a5ff6c..68053b603 100644
--- a/.clang-format
+++ b/.clang-format
@@ -3,8 +3,10 @@ BasedOnStyle: LLVM
AccessModifierOffset: -4
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
+BinPackParameters: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
+AllowAllParametersOfDeclarationOnNextLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakTemplateDeclarations: Yes
ColumnLimit: 120
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f08e856c4..1bc64324c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -60,7 +60,7 @@ if(INEXOR_BUILD_DOC)
endif()
if(INEXOR_BUILD_EXAMPLE)
- add_subdirectory(example)
+ add_subdirectory(example-app/src)
endif()
if(INEXOR_BUILD_TESTS)
diff --git a/documentation/source/development/debugging/cla.rst b/documentation/source/development/debugging/cla.rst
index 049654741..773fdf86d 100644
--- a/documentation/source/development/debugging/cla.rst
+++ b/documentation/source/development/debugging/cla.rst
@@ -25,14 +25,6 @@ You can start vulkan-renderer with the following command line arguments:
.. warning:: You should never disable validation layers because they offer extensive error checks for debugging.
-.. option:: --no-vk-debug-markers
-
- Disables `Vulkan debug markers `__ (even if ``--renderdoc`` is specified).
-
-.. option:: --renderdoc
-
- Enables the `RenderDoc `__ debug layer.
-
.. option:: --vsync
.. warning:: Vsync is currently not implemented. The command line argument will be ignored.
diff --git a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_1.jpg b/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_1.jpg
deleted file mode 100644
index 70f77d051..000000000
Binary files a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_1.jpg and /dev/null differ
diff --git a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_2.jpg b/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_2.jpg
deleted file mode 100644
index c1d63044e..000000000
Binary files a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_2.jpg and /dev/null differ
diff --git a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_3.jpg b/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_3.jpg
deleted file mode 100644
index 99c4f28ba..000000000
Binary files a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_3.jpg and /dev/null differ
diff --git a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_4.jpg b/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_4.jpg
deleted file mode 100644
index 4848e56ca..000000000
Binary files a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_4.jpg and /dev/null differ
diff --git a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_5.jpg b/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_5.jpg
deleted file mode 100644
index 1198b583d..000000000
Binary files a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_5.jpg and /dev/null differ
diff --git a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_6.jpg b/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_6.jpg
deleted file mode 100644
index 2184f8685..000000000
Binary files a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_6.jpg and /dev/null differ
diff --git a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_7.jpg b/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_7.jpg
deleted file mode 100644
index bc182957e..000000000
Binary files a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_7.jpg and /dev/null differ
diff --git a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_8.jpg b/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_8.jpg
deleted file mode 100644
index 611dde4c9..000000000
Binary files a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_8.jpg and /dev/null differ
diff --git a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_9.jpg b/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_9.jpg
deleted file mode 100644
index 0783b1b7b..000000000
Binary files a/documentation/source/development/debugging/images/renderdoc/RenderDoc_step_9.jpg and /dev/null differ
diff --git a/documentation/source/development/debugging/images/renderdoc/VisualStudioBreakpoint.jpg b/documentation/source/development/debugging/images/renderdoc/VisualStudioBreakpoint.jpg
deleted file mode 100644
index 16a56f19f..000000000
Binary files a/documentation/source/development/debugging/images/renderdoc/VisualStudioBreakpoint.jpg and /dev/null differ
diff --git a/documentation/source/development/debugging/images/renderdoc/VisualStudioDebugging.jpg b/documentation/source/development/debugging/images/renderdoc/VisualStudioDebugging.jpg
deleted file mode 100644
index a4f83a4a7..000000000
Binary files a/documentation/source/development/debugging/images/renderdoc/VisualStudioDebugging.jpg and /dev/null differ
diff --git a/documentation/source/development/debugging/renderdoc.rst b/documentation/source/development/debugging/renderdoc.rst
index 7d8835854..0fbaf0fd5 100644
--- a/documentation/source/development/debugging/renderdoc.rst
+++ b/documentation/source/development/debugging/renderdoc.rst
@@ -2,99 +2,5 @@ RenderDoc
=========
- `RenderDoc `__ is a free and open source graphics debugger for Vulkan API (and other APIs) developed by `Baldur Karlsson `__.
-- It is a very powerful graphics debugging and visualization tool which makes debugging Vulkan application as easy as possible.
-- Inexor has full RenderDoc integration. This includes `internal resource naming using Vulkan debug markers `__.
-- The following tutorial shows how to debug Inexor using RenderDoc.
+- It is a very powerful graphics debugging and visualization tool which simplifies debugging Vulkan application a lot.
- You can read up more details in `RenderDoc's documentation `__.
-
-RenderDoc Tutorial for Windows
-------------------------------
-
-Step 1: Open Inexor in Visual Studio and add a breakpoint before Vulkan initialization
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-- The best spot would be right after ``main()``:
-
-.. image:: /development/debugging/images/renderdoc/VisualStudioBreakpoint.jpg
- :width: 800
- :alt: A breakpoint after the main function in Visual Studio debugger.
-
-Step 2: Open RenderDoc.
-^^^^^^^^^^^^^^^^^^^^^^^
-
-.. image:: /development/debugging/images/renderdoc/RenderDoc_step_1.jpg
- :width: 800
- :alt: RenderDoc right after starting it.
-
-Step 3: Start debugging inexor-vulkan-renderer and halt at the breakpoint
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. image:: /development/debugging/images/renderdoc/VisualStudioDebugging.jpg
- :width: 800
- :alt: Visual Studio interrupts the program because of a breakpoint.
-
-Step 4: "Inject into process" inexor-vulkan-renderer.exe using RenderDoc
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. image:: /development/debugging/images/renderdoc/RenderDoc_step_2.jpg
- :width: 800
- :alt: "Inject into process" in RenderDoc's menu.
-
-Step 5: Search for "inexor-vulkan-renderer.exe" and click "inject"
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-- You will see a warning Windows Firewall the first time you do this.
-- This is because RenderDoc is reading memory from inexor-vulkan-renderer.
-- Accept the Windows Firewall warning to allow RenderDoc to read memory.
-
-.. image:: /development/debugging/images/renderdoc/RenderDoc_step_3.jpg
- :width: 800
- :alt: Injecting into inexor-vulkan-renderer.
-
-Step 6: Continue debugging in Visual Studio
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-- RenderDoc should now look like this.
-
-.. image:: /development/debugging/images/renderdoc/RenderDoc_step_4.jpg
- :width: 800
- :alt: Injecting into inexor-vulkan-renderer.
-
-- Press ``F5`` to continue program execution from the breakpoint.
-- RenderDoc is now connected to inexor-vulkan-renderer:
-
-.. image:: /development/debugging/images/renderdoc/RenderDoc_step_5.jpg
- :width: 800
- :alt: RenderDoc is connected inexor-vulkan-renderer.
-
-- You can see RenderDoc's overlay in inexor-vulkan-renderer.exe:
-
-.. image:: /development/debugging/images/renderdoc/RenderDoc_step_6.jpg
- :width: 800
- :alt: Taking a RenderDoc snapshot.
-
-Step 7: Debug inexor-vulkan-renderer.exe as usual and press F12 to take RenderDoc snapshots
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-- You can take multiple snapshots with either ``PRINT`` or ``F12`` key.
-
-.. image:: /development/debugging/images/renderdoc/RenderDoc_step_7.jpg
- :width: 800
- :alt: Taking a RenderDoc snapshot.
-
-- You can see the snapshots in RenderDoc right after you took them:
-
-.. image:: /development/debugging/images/renderdoc/RenderDoc_step_8.jpg
- :width: 800
- :alt: Taking a RenderDoc snapshot.
-
-Step 8: Open a snapshot to analyze the rendering of this frame
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-- Double click on a snapshot to open it:
-
-.. image:: /development/debugging/images/renderdoc/RenderDoc_step_9.jpg
- :width: 800
- :alt: Taking a RenderDoc snapshot.
-
-- Have fun inspecting!
diff --git a/documentation/source/development/getting-started.rst b/documentation/source/development/getting-started.rst
index 09c9effbc..9a5754685 100644
--- a/documentation/source/development/getting-started.rst
+++ b/documentation/source/development/getting-started.rst
@@ -37,7 +37,7 @@ Optional Software
Improve your build times with ninja.
`RenderDoc `__
- Powerful open source graphics debugger. Inexor has full RenderDoc integration.
+ A very powerful open source graphics debugger.
`Doxygen `__
Required for generating the documentation.
diff --git a/example-app/include/example_app.hpp b/example-app/include/example_app.hpp
new file mode 100644
index 000000000..f497cf28e
--- /dev/null
+++ b/example-app/include/example_app.hpp
@@ -0,0 +1,47 @@
+#pragma once
+
+#include "../include/example_app_base.hpp"
+
+#include "inexor/vulkan-renderer/rendering/imgui/imgui.hpp"
+#include "inexor/vulkan-renderer/rendering/octree/octree_renderer.hpp"
+
+#include
+
+namespace inexor::example_app {
+
+/// An example application using Inexor vulkan-renderer
+class ExampleApp : public ExampleAppBase {
+private:
+ std::unique_ptr m_imgui_renderer;
+ std::unique_ptr m_octree_renderer;
+
+public:
+ ExampleApp(int argc, char **argv);
+ ~ExampleApp();
+
+ ExampleApp(const ExampleApp &) = delete;
+ // TODO: Implement me!
+ ExampleApp(ExampleApp &&) noexcept;
+
+ ExampleApp &operator=(const ExampleApp &) = delete;
+ // TODO: Implement me!
+ ExampleApp &operator=(ExampleApp &&) noexcept;
+
+ void initialize() override;
+
+ void load_toml_configuration_file(const std::string &file_name);
+
+ void setup_render_graph() override;
+
+ void cursor_position_callback(GLFWwindow *window, double x_pos, double y_pos) override;
+
+ void keyboard_button_callback(GLFWwindow *window, int key, int scancode, int action, int mods) override;
+
+ void mouse_button_callback(GLFWwindow *window, int button, int action, int mods) override;
+
+ void mouse_scroll_callback(GLFWwindow *window, double x_offset, double y_offset) override;
+
+ void update_imgui() override;
+};
+
+} // namespace inexor::example_app
diff --git a/example-app/include/example_app_base.hpp b/example-app/include/example_app_base.hpp
new file mode 100644
index 000000000..865b74344
--- /dev/null
+++ b/example-app/include/example_app_base.hpp
@@ -0,0 +1,102 @@
+#pragma once
+
+#include "inexor/vulkan-renderer/input/keyboard_mouse_data.hpp"
+#include "inexor/vulkan-renderer/rendering/render-graph/render_graph.hpp"
+#include "inexor/vulkan-renderer/tools/cla_parser.hpp"
+#include "inexor/vulkan-renderer/wrapper/device.hpp"
+#include "inexor/vulkan-renderer/wrapper/instance.hpp"
+#include "inexor/vulkan-renderer/wrapper/surface.hpp"
+#include "inexor/vulkan-renderer/wrapper/swapchain.hpp"
+#include "inexor/vulkan-renderer/wrapper/window.hpp"
+
+#include
+#include
+
+namespace inexor::example_app {
+
+// Using declarations
+using inexor::vulkan_renderer::input::KeyboardMouseInputData;
+using inexor::vulkan_renderer::render_graph::RenderGraph;
+using inexor::vulkan_renderer::tools::CommandLineArgumentParser;
+using inexor::vulkan_renderer::wrapper::Device;
+using inexor::vulkan_renderer::wrapper::Instance;
+using inexor::vulkan_renderer::wrapper::Surface;
+using inexor::vulkan_renderer::wrapper::Swapchain;
+using inexor::vulkan_renderer::wrapper::Window;
+
+/// The command line arguments will be parsed into these options
+struct CommandLineOptions {
+ bool stop_on_validation_error{false};
+ bool vsync_enabled{false};
+};
+
+/// A base class for example apps which use Inexor vulkan-renderer
+class ExampleAppBase {
+private:
+ const std::string m_wnd_title = "inexor-vulkan-renderer-example";
+ std::uint32_t m_wnd_width{1280};
+ std::uint32_t m_wnd_height{720};
+ Window::Mode m_wnd_mode{Window::Mode::WINDOWED};
+ bool m_wnd_resized{false};
+
+ std::unique_ptr m_window;
+ std::unique_ptr m_instance;
+ std::unique_ptr m_surface;
+ std::unique_ptr m_device;
+ std::shared_ptr m_swapchain;
+ std::unique_ptr m_input_data;
+
+ void initialize_spdlog();
+
+ void recreate_swapchain();
+
+ /// Because GLFW is a C-style API, we can't use a pointer to non-static class methods as window or input callback.
+ /// A good explanation can be found on Stack Overflow:
+ /// https://stackoverflow.com/questions/7676971/pointing-to-a-function-that-is-a-class-member-glfw-setkeycallback
+ /// In order to fix this, we can pass a lambda to glfwSetKeyCallback, which calls our callbacks internally. There is
+ /// another problem: Inside of the lambda, we need to call the member function. In order to do so, we need to have
+ /// access to the this-pointer. Unfortunately, the this-pointer can't be captured in the lambda capture like
+ /// [this](){}, because the glfw would not accept the lambda then. To fix this problem, we store the this pointer
+ /// using glfwSetWindowUserPointer. Inside of these lambdas, we then cast the pointer to Application* again,
+ /// allowing us to finally use the callbacks.
+ void setup_window_and_input_callbacks();
+
+protected:
+ CommandLineOptions m_options;
+
+ VKAPI_ATTR VkBool32 VKAPI_CALL
+ validation_layer_debug_messenger_callback(VkDebugUtilsMessageSeverityFlagBitsEXT,
+ VkDebugUtilsMessageTypeFlagsEXT,
+ const VkDebugUtilsMessengerCallbackDataEXT *,
+ void *);
+
+ std::shared_ptr m_rendergraph;
+
+public:
+ ExampleAppBase(int argc, char **argv);
+ virtual ~ExampleAppBase();
+
+ ExampleAppBase(const ExampleAppBase &) = delete;
+ // TODO: Implement me!
+ ExampleAppBase(ExampleAppBase &&) noexcept;
+
+ ExampleAppBase &operator=(const ExampleAppBase &) = delete;
+ // TODO: Implement me!
+ ExampleAppBase &operator=(ExampleAppBase &&) noexcept;
+
+ virtual void evaluate_command_line_arguments(const CommandLineArgumentParser &parser) = 0;
+ virtual void cursor_position_callback(GLFWwindow *, double, double) = 0;
+ virtual void keyboard_button_callback(GLFWwindow *, int, int, int, int) = 0;
+ virtual void mouse_button_callback(GLFWwindow *, int, int, int) = 0;
+ virtual void mouse_scroll_callback(GLFWwindow *, double, double) = 0;
+ virtual void initialize() = 0;
+ virtual void process_mouse_input() = 0;
+ virtual void process_keyboard_input() = 0;
+ virtual void render_frame() = 0;
+ virtual void setup_device() = 0;
+ virtual void setup_render_graph() = 0;
+ virtual void shutdown() = 0;
+ virtual void update_imgui() = 0;
+};
+
+} // namespace inexor::example_app
diff --git a/example/CMakeLists.txt b/example-app/src/CMakeLists.txt
old mode 100755
new mode 100644
similarity index 60%
rename from example/CMakeLists.txt
rename to example-app/src/CMakeLists.txt
index 3f062e39b..4f404539e
--- a/example/CMakeLists.txt
+++ b/example-app/src/CMakeLists.txt
@@ -1,4 +1,22 @@
-add_executable(inexor-vulkan-renderer-example main.cpp)
+set(INEXOR_EXAMPLE_APP_SOURCE_FILES
+ example_app_base.cpp
+ example_app.cpp)
+
+foreach(FILE ${INEXOR_EXAMPLE_APP_SOURCE_FILES})
+ get_filename_component(PARENT_DIR "${FILE}" PATH)
+
+ string(REPLACE "/" "\\" GROUP "${PARENT_DIR}")
+
+ if("${FILE}" MATCHES ".*\\.cpp")
+ set(GROUP "Source Files\\${GROUP}")
+ elseif("${FILE}" MATCHES ".*\\.hpp")
+ set(GROUP "Header Files\\${GROUP}")
+ endif()
+
+ source_group("${GROUP}" FILES "${FILE}")
+endforeach()
+
+add_executable(inexor-vulkan-renderer-example ${INEXOR_EXAMPLE_APP_SOURCE_FILES})
target_compile_features(inexor-vulkan-renderer-example PRIVATE cxx_std_20)
target_link_libraries(inexor-vulkan-renderer-example PRIVATE inexor-vulkan-renderer)
diff --git a/example-app/src/example_app.cpp b/example-app/src/example_app.cpp
new file mode 100644
index 000000000..da3f870e2
--- /dev/null
+++ b/example-app/src/example_app.cpp
@@ -0,0 +1,66 @@
+#include "../include/example_app.hpp"
+
+#include
+
+#include
+
+namespace inexor::example_app {
+
+ExampleApp::ExampleApp(int argc, char **argv) : ExampleAppBase(argc, argv) {}
+
+ExampleApp::~ExampleApp() {}
+
+void ExampleApp::initialize() {
+ //
+}
+
+void ExampleApp::setup_render_graph() {
+ spdlog::trace("Setting up rendergraph");
+ m_octree_renderer = std::make_unique(m_rendergraph);
+ m_imgui_renderer = std::make_unique(m_rendergraph);
+}
+
+void ExampleApp::update_imgui() {
+ ImGuiIO &io = ImGui::GetIO();
+ io.DeltaTime = m_time_passed + 0.00001f;
+ auto cursor_pos = m_input_data->get_cursor_pos();
+ io.MousePos = ImVec2(static_cast(cursor_pos[0]), static_cast(cursor_pos[1]));
+ io.MouseDown[0] = m_input_data->is_mouse_button_pressed(GLFW_MOUSE_BUTTON_LEFT);
+ io.MouseDown[1] = m_input_data->is_mouse_button_pressed(GLFW_MOUSE_BUTTON_RIGHT);
+ io.DisplaySize =
+ ImVec2(static_cast(m_swapchain->extent().width), static_cast(m_swapchain->extent().height));
+
+ ImGui::NewFrame();
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
+ ImGui::SetNextWindowPos(ImVec2(10, 10));
+ ImGui::SetNextWindowSize(ImVec2(330, 0));
+ ImGui::Begin("Inexor Vulkan-renderer", nullptr,
+ ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove);
+ ImGui::Text("%s", m_device->gpu_name().c_str());
+ ImGui::Text("Engine version %d.%d.%d (Git sha %s)", ENGINE_VERSION[0], ENGINE_VERSION[1], ENGINE_VERSION[2],
+ BUILD_GIT);
+ ImGui::Text("Vulkan API %d.%d.%d", VK_API_VERSION_MAJOR(wrapper::Instance::REQUIRED_VK_API_VERSION),
+ VK_API_VERSION_MINOR(wrapper::Instance::REQUIRED_VK_API_VERSION),
+ VK_API_VERSION_PATCH(wrapper::Instance::REQUIRED_VK_API_VERSION));
+ const auto &cam_pos = m_camera->position();
+ ImGui::Text("Camera position (%.2f, %.2f, %.2f)", cam_pos.x, cam_pos.y, cam_pos.z);
+ const auto &cam_rot = m_camera->rotation();
+ ImGui::Text("Camera rotation: (%.2f, %.2f, %.2f)", cam_rot.x, cam_rot.y, cam_rot.z);
+ const auto &cam_front = m_camera->front();
+ ImGui::Text("Camera vector front: (%.2f, %.2f, %.2f)", cam_front.x, cam_front.y, cam_front.z);
+ const auto &cam_right = m_camera->right();
+ ImGui::Text("Camera vector right: (%.2f, %.2f, %.2f)", cam_right.x, cam_right.y, cam_right.z);
+ const auto &cam_up = m_camera->up();
+ ImGui::Text("Camera vector up (%.2f, %.2f, %.2f)", cam_up.x, cam_up.y, cam_up.z);
+ ImGui::Text("Yaw: %.2f pitch: %.2f roll: %.2f", m_camera->yaw(), m_camera->pitch(), m_camera->roll());
+ const auto cam_fov = m_camera->fov();
+ ImGui::Text("Field of view: %d", static_cast(cam_fov));
+ ImGui::PushItemWidth(150.0f);
+ ImGui::PopItemWidth();
+ ImGui::PopStyleVar();
+ ImGui::End();
+ ImGui::EndFrame();
+ ImGui::Render();
+}
+
+} // namespace inexor::example_app
diff --git a/example-app/src/example_app_base.cpp b/example-app/src/example_app_base.cpp
new file mode 100644
index 000000000..675a9e1e3
--- /dev/null
+++ b/example-app/src/example_app_base.cpp
@@ -0,0 +1,112 @@
+#include "../include/example_app_base.hpp"
+
+#include
+#include
+#include
+#include
+
+namespace inexor::example_app {
+
+ExampleAppBase::ExampleAppBase(int argc, char **argv) {
+ // Setup spdlog logging library
+ initialize_spdlog();
+
+ // Parse and evaluate command line arguments
+ CommandLineArgumentParser cla_parser;
+ cla_parser.parse_args(argc, argv);
+ evaluate_command_line_arguments(cla_parser);
+
+ spdlog::trace("Application version: {}.{}.{}", APP_VERSION[0], APP_VERSION[1], APP_VERSION[2]);
+ spdlog::trace("Engine version: {}.{}.{}", ENGINE_VERSION[0], ENGINE_VERSION[1], ENGINE_VERSION[2]);
+
+ // NOTE: We must create the window before the VkInstance, otherwise glfwGetRequiredInstanceExtensions fails!
+ m_window = std::make_unique(m_wnd_title, m_wnd_width, m_wnd_height, true, true, m_wnd_mode);
+ setup_window_and_input_callbacks();
+
+ m_instance = std::make_unique(
+ APP_NAME, ENGINE_NAME, VK_MAKE_API_VERSION(0, APP_VERSION[0], APP_VERSION[1], APP_VERSION[2]),
+ VK_MAKE_API_VERSION(0, ENGINE_VERSION[0], ENGINE_VERSION[1], ENGINE_VERSION[2]),
+ validation_layer_debug_messenger_callback);
+
+ m_surface = std::make_unique(m_instance->instance(), m_window->window());
+ m_input_data = std::make_unique();
+
+ m_device = std::make_unique(*m_instance, m_surface->surface(), physical_device, required_extensions,
+ required_features, optional_extensions, optional_features);
+
+ m_swapchain = std::make_shared(*m_device, "Default Swapchain", m_surface->surface(), *m_window,
+ m_options.vsync_enabled);
+}
+
+ExampleAppBase::~ExampleAppBase() {
+ spdlog::trace("Shutting down vulkan-renderer-example");
+}
+
+void ExampleAppBase::initialize_spdlog() {
+ spdlog::init_thread_pool(8192, 2);
+ auto console_sink = std::make_shared();
+ auto file_sink = std::make_shared("vulkan-renderer-example.log", true);
+ auto logger = std::make_shared("vulkan-renderer-example",
+ spdlog::sinks_init_list{console_sink, file_sink},
+ spdlog::thread_pool(), spdlog::async_overflow_policy::block);
+ logger->set_level(spdlog::level::trace);
+ logger->set_pattern("%Y-%m-%d %T.%f %^%l%$ %5t [%-10n] %v");
+ logger->flush_on(spdlog::level::trace);
+ spdlog::set_default_logger(logger);
+ spdlog::trace("Inexor vulkan-renderer-example, BUILD " + std::string(__DATE__) + ", " + __TIME__);
+}
+
+void ExampleAppBase::setup_window_and_input_callbacks() {
+ m_window->set_user_ptr(this);
+
+ m_window->set_resize_callback([](GLFWwindow *window, int width, int height) {
+ auto *app = static_cast(glfwGetWindowUserPointer(window));
+ app->m_wnd_resized = true;
+ });
+
+ m_window->set_keyboard_button_callback([](GLFWwindow *window, int key, int scancode, int action, int mods) {
+ auto *app = static_cast(glfwGetWindowUserPointer(window));
+ app->keyboard_button_callback(window, key, scancode, action, mods);
+ });
+
+ m_window->set_cursor_position_callback([](GLFWwindow *window, double xpos, double ypos) {
+ auto *app = static_cast(glfwGetWindowUserPointer(window));
+ app->cursor_position_callback(window, xpos, ypos);
+ });
+
+ m_window->set_mouse_button_callback([](GLFWwindow *window, int button, int action, int mods) {
+ auto *app = static_cast(glfwGetWindowUserPointer(window));
+ app->mouse_button_callback(window, button, action, mods);
+ });
+
+ m_window->set_mouse_scroll_callback([](GLFWwindow *window, double xoffset, double yoffset) {
+ auto *app = static_cast(glfwGetWindowUserPointer(window));
+ app->mouse_scroll_callback(window, xoffset, yoffset);
+ });
+}
+
+void ExampleAppBase::recreate_swapchain() {
+ // TODO: Implement!
+}
+
+VkBool32 VKAPI_CALL
+ExampleAppBase::validation_layer_debug_messenger_callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
+ VkDebugUtilsMessageTypeFlagsEXT type,
+ const VkDebugUtilsMessengerCallbackDataEXT *data,
+ void *user_data) {
+ if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) {
+ spdlog::trace("{}", data->pMessage);
+ } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) {
+ spdlog::info("{}", data->pMessage);
+ } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
+ spdlog::warn("{}", data->pMessage);
+ } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
+ spdlog::critical("{}", data->pMessage);
+ }
+ if (m_options.stop_on_validation_error) {
+ throw std::runtime_error("[ExampleApp::validation_layer_debug_messenger_callback] Validation error, stopped.");
+ }
+ return false;
+}
+
+} // namespace inexor::example_app
diff --git a/example/main.cpp b/example/main.cpp
deleted file mode 100644
index f85737397..000000000
--- a/example/main.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-#include "inexor/vulkan-renderer/application.hpp"
-
-#include
-#include
-#include
-#include
-
-#include
-
-int main(int argc, char *argv[]) {
- spdlog::init_thread_pool(8192, 2);
-
- auto console_sink = std::make_shared();
- auto file_sink = std::make_shared("vulkan-renderer.log", true);
- auto vulkan_renderer_log =
- std::make_shared("vulkan-renderer", spdlog::sinks_init_list{console_sink, file_sink},
- spdlog::thread_pool(), spdlog::async_overflow_policy::block);
- vulkan_renderer_log->set_level(spdlog::level::trace);
- vulkan_renderer_log->set_pattern("%Y-%m-%d %T.%f %^%l%$ %5t [%-10n] %v");
- vulkan_renderer_log->flush_on(spdlog::level::debug); // TODO: as long as we don't have a flush on crash
-
- spdlog::set_default_logger(vulkan_renderer_log);
-
- spdlog::trace("Inexor vulkan-renderer, BUILD " + std::string(__DATE__) + ", " + __TIME__);
- spdlog::trace("Parsing command line arguments");
-
- std::unique_ptr renderer;
-
- try {
- renderer = std::make_unique(argc, argv);
- } catch (const std::runtime_error &exception) {
- spdlog::critical(exception.what());
- return 1;
- } catch (const std::exception &exception) {
- spdlog::critical(exception.what());
- return 1;
- }
-
- renderer->run();
- spdlog::trace("Window closed");
-}
diff --git a/include/inexor/vulkan-renderer/application.hpp b/include/inexor/vulkan-renderer/application.hpp
deleted file mode 100644
index 9542318e6..000000000
--- a/include/inexor/vulkan-renderer/application.hpp
+++ /dev/null
@@ -1,80 +0,0 @@
-#pragma once
-
-#include "inexor/vulkan-renderer/input/keyboard_mouse_data.hpp"
-#include "inexor/vulkan-renderer/renderer.hpp"
-#include "inexor/vulkan-renderer/world/collision_query.hpp"
-#include "inexor/vulkan-renderer/world/cube.hpp"
-
-// Forward declarations
-namespace inexor::vulkan_renderer::input {
-class KeyboardMouseInputData;
-} // namespace inexor::vulkan_renderer::input
-
-namespace inexor::vulkan_renderer {
-
-class Application : public VulkanRenderer {
- std::vector m_vertex_shader_files;
- std::vector m_fragment_shader_files;
- std::vector m_texture_files;
- std::vector m_gltf_model_files;
-
- std::unique_ptr m_input_data;
-
- bool m_enable_validation_layers{true};
- /// Inexor engine supports a variable number of octrees.
- std::vector> m_worlds;
-
- // If the user specified command line argument "--stop-on-validation-message", the program will call
- // std::abort(); after reporting a validation layer (error) message.
- bool m_stop_on_validation_message{false};
-
- /// @brief Load the configuration of the renderer from a TOML configuration file.
- /// @brief file_name The TOML configuration file.
- /// @note It was collectively decided not to use JSON for configuration files.
- void load_toml_configuration_file(const std::string &file_name);
- void load_textures();
- void load_shaders();
- /// @param initialize Initialize worlds with a fixed seed, which is useful for benchmarking and testing
- void load_octree_geometry(bool initialize);
- void setup_vulkan_debug_callback();
- void setup_window_and_input_callbacks();
- void update_imgui_overlay();
- void update_uniform_buffers();
- /// Use the camera's position and view direction vector to check for ray-octree collisions with all octrees.
- void check_octree_collisions();
- void process_mouse_input();
-
-public:
- Application(int argc, char **argv);
-
- /// @brief Call glfwSetKeyCallback.
- /// @param window The window that received the event.
- /// @param key The keyboard key that was pressed or released.
- /// @param scancode The system-specific scancode of the key.
- /// @param action GLFW_PRESS, GLFW_RELEASE or GLFW_REPEAT.
- /// @param mods Bit field describing which modifier keys were held down.
- void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods);
-
- /// @brief Call glfwSetCursorPosCallback.
- /// @param window The window that received the event.
- /// @param x_pos The new x-coordinate, in screen coordinates, of the cursor.
- /// @param y_pos The new y-coordinate, in screen coordinates, of the cursor.
- void cursor_position_callback(GLFWwindow *window, double x_pos, double y_pos);
-
- /// @brief Call glfwSetMouseButtonCallback.
- /// @param window The window that received the event.
- /// @param button The mouse button that was pressed or released.
- /// @param action One of GLFW_PRESS or GLFW_RELEASE.
- /// @param mods Bit field describing which modifier keys were held down.
- void mouse_button_callback(GLFWwindow *window, int button, int action, int mods);
-
- /// @brief Call camera's process_mouse_scroll method.
- /// @param window The window that received the event.
- /// @param x_offset The change of x-offset of the mouse wheel.
- /// @param y_offset The change of y-offset of the mouse wheel.
- void mouse_scroll_callback(GLFWwindow *window, double x_offset, double y_offset);
-
- void run();
-};
-
-} // namespace inexor::vulkan_renderer
diff --git a/include/inexor/vulkan-renderer/bezier_curve.hpp b/include/inexor/vulkan-renderer/bezier_curve.hpp
deleted file mode 100644
index 11c13a383..000000000
--- a/include/inexor/vulkan-renderer/bezier_curve.hpp
+++ /dev/null
@@ -1,97 +0,0 @@
-#pragma once
-
-// TODO: Implement a curve manager.
-
-// INTRODUCTION
-// A bezier curve (named after french mathematician PIERRE ETIENNE BEZIER) is a parametric curve
-// whose only purpose is to look soft and smooth. Bezier curves are all about elegance!
-// Those curves can be used to represent the path of a everything (imagine a camera which is moving along a path for
-// example).
-//
-// Bezier curves are fast, flexible, beautiful and easy to compute. You just pass a bunch of parameter points to
-// your code and the final curve will be computed. Because every complex curve can be represented with a
-// chain of smaller curves, it is recommended to create a chain of curves. Bezier curves are essential
-// in the field of computer graphics and image processing. They can also be used for approximation, interpolation and
-// more.
-//
-// COMPUTING
-// There are two ways to generate a bezier curves from a group of [n] points.
-// You can either write a code that uses recursion to solve the problem or use Bernstein polynomials.
-// This engine uses Bernstein polynomial, because we want to avoid the recursion in de-casteljau algorithm.
-//
-// Pierre Etienne BEZIER (September 1, 1910 - November 25, 1999), French mathematician and engineer at RENAULT.
-// Paul de CASTELJAU (November 19, 1930), French mathematician and physicist and engineer ar Citroen.
-// Sergei Natanovich BERNSTEIN (March 5, 1880 - October 26, 1968), Russian mathematician.
-// Charles HERMITE (December 24, 1822 - January 14, 1901), French mathematician.
-
-// http://pomax.github.io/bezierinfo/
-// http://en.wikipedia.org/wiki/B%C3%A9zier_curve
-// http://mathworld.wolfram.com/BezierCurve.html
-// http://theagsc.com/community/tutorials/so-whats-the-big-deal-with-horizontal-vertical-bezier-handles-anyway#comment-1351842776
-// http://learn.scannerlicker.net/2014/04/16/bezier-curves-and-type-design-a-tutorial/
-// https://geom.ivd.kit.edu/downloads/pubs/pub-boehm-prautzsch_2002_preview.pdf
-// https://www.clear.rice.edu/comp360/lectures/BezSubd.pdf
-
-#include
-
-#include
-#include
-
-namespace inexor::vulkan_renderer {
-
-/// @brief Those are the points that we pass into the bezier curve generator.
-/// Every bezier curve will be generated from a list of BezierInputPoint.
-/// Every input point can have a custom weight coefficient.
-struct BezierInputPoint {
- glm::vec3 pos{0.0f, 0.0f, 0.0f};
-
- float weight{1.0f};
-};
-
-/// @brief Those are the points which will be generated from the bezier curve generator.
-/// How many BezierOutputPoint points will be generated depends on the required precision.
-/// The higher the requested precision, the more points will be
-struct BezierOutputPoint : public BezierInputPoint {
- glm::vec3 normal{0.0f, 0.0f, 0.0f};
-
- glm::vec3 tangent{0.0f, 0.0f, 0.0f};
-};
-
-/// @brief This struct bundles describes everything about the bezier curve.
-/// It contains both the input points and the generated output points.
-class BezierCurve {
- bool m_curve_generated{false};
-
- float m_curve_precision{0.0f};
-
- std::vector m_input_points;
-
- std::vector m_output_points;
-
- static uint32_t binomial_coefficient(uint32_t n, uint32_t k);
-
- static float bernstein_polynomial(uint32_t n, uint32_t k, float curve_precision, float coordinate_value);
-
- BezierOutputPoint calculate_point_on_curve(float curve_precision);
-
-public:
- void add_input_point(const BezierInputPoint &input_point);
-
- void add_input_point(const glm::vec3 &position, float weight = 1.0f);
-
- void calculate_bezier_curve(std::uint32_t curve_segments);
-
- [[nodiscard]] std::vector output_points();
-
- void clear_output();
-
- void clear_input();
-
- void clear();
-
- [[nodiscard]] bool is_curve_generated() const {
- return m_curve_generated;
- }
-};
-
-} // namespace inexor::vulkan_renderer
diff --git a/include/inexor/vulkan-renderer/fps_counter.hpp b/include/inexor/vulkan-renderer/fps_counter.hpp
deleted file mode 100644
index ab597861b..000000000
--- a/include/inexor/vulkan-renderer/fps_counter.hpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-
-namespace inexor::vulkan_renderer {
-
-/// @brief A class for counting frames per seconds.
-class FPSCounter {
- std::uint32_t m_frames{0};
-
- std::chrono::time_point m_last_time;
-
- float m_fps_update_interval{1.0f};
-
-public:
- std::optional update();
-};
-
-} // namespace inexor::vulkan_renderer
diff --git a/include/inexor/vulkan-renderer/imgui.hpp b/include/inexor/vulkan-renderer/imgui.hpp
deleted file mode 100644
index 0ec531814..000000000
--- a/include/inexor/vulkan-renderer/imgui.hpp
+++ /dev/null
@@ -1,66 +0,0 @@
-#pragma once
-
-#include "inexor/vulkan-renderer/render_graph.hpp"
-#include "inexor/vulkan-renderer/wrapper/descriptor.hpp"
-#include "inexor/vulkan-renderer/wrapper/gpu_texture.hpp"
-#include "inexor/vulkan-renderer/wrapper/shader.hpp"
-
-#include
-#include
-#include
-
-#include
-#include
-
-// Forward declarations
-namespace inexor::vulkan_renderer::wrapper {
-class Device;
-class Swapchain;
-} // namespace inexor::vulkan_renderer::wrapper
-
-namespace inexor::vulkan_renderer {
-
-class ImGUIOverlay {
- const wrapper::Device &m_device;
- const wrapper::Swapchain &m_swapchain;
- float m_scale{1.0f};
-
- BufferResource *m_index_buffer{nullptr};
- BufferResource *m_vertex_buffer{nullptr};
- GraphicsStage *m_stage{nullptr};
-
- std::unique_ptr m_imgui_texture;
- std::unique_ptr m_vertex_shader;
- std::unique_ptr m_fragment_shader;
- std::unique_ptr m_descriptor;
- std::vector m_index_data;
- std::vector m_vertex_data;
-
- struct PushConstBlock {
- glm::vec2 scale;
- glm::vec2 translate;
- } m_push_const_block{};
-
-public:
- /// @brief Construct a new ImGUI overlay.
- /// @param device A reference to the device wrapper
- /// @param swapchain A reference to the swapchain
- /// @param render_graph A pointer to the render graph
- /// @param back_buffer A pointer to the target of the ImGUI rendering
- ImGUIOverlay(const wrapper::Device &device, const wrapper::Swapchain &swapchain, RenderGraph *render_graph,
- TextureResource *back_buffer);
- ImGUIOverlay(const ImGUIOverlay &) = delete;
- ImGUIOverlay(ImGUIOverlay &&) = delete;
- ~ImGUIOverlay();
-
- ImGUIOverlay &operator=(const ImGUIOverlay &) = delete;
- ImGUIOverlay &operator=(ImGUIOverlay &&) = delete;
-
- void update();
-
- [[nodiscard]] float scale() const {
- return m_scale;
- }
-};
-
-} // namespace inexor::vulkan_renderer
diff --git a/include/inexor/vulkan-renderer/io/exception.hpp b/include/inexor/vulkan-renderer/io/io_exception.hpp
similarity index 100%
rename from include/inexor/vulkan-renderer/io/exception.hpp
rename to include/inexor/vulkan-renderer/io/io_exception.hpp
diff --git a/include/inexor/vulkan-renderer/msaa_target.hpp b/include/inexor/vulkan-renderer/msaa_target.hpp
deleted file mode 100644
index d6884b43f..000000000
--- a/include/inexor/vulkan-renderer/msaa_target.hpp
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include "inexor/vulkan-renderer/wrapper/image.hpp"
-
-#include
-
-namespace inexor::vulkan_renderer {
-
-struct MSAATarget {
- // The color buffer.
- std::unique_ptr m_color;
-
- // The depth buffer.
- std::unique_ptr m_depth;
-};
-
-} // namespace inexor::vulkan_renderer
diff --git a/include/inexor/vulkan-renderer/octree_gpu_vertex.hpp b/include/inexor/vulkan-renderer/octree_gpu_vertex.hpp
deleted file mode 100644
index 45ed6a184..000000000
--- a/include/inexor/vulkan-renderer/octree_gpu_vertex.hpp
+++ /dev/null
@@ -1,33 +0,0 @@
-#pragma once
-
-#include
-#include
-
-namespace inexor::vulkan_renderer {
-
-struct OctreeGpuVertex {
- glm::vec3 position;
- glm::vec3 color;
-
- OctreeGpuVertex(glm::vec3 position, glm::vec3 color) : position(position), color(color) {}
-};
-
-// inline to suppress clang-tidy warning.
-inline bool operator==(const OctreeGpuVertex &lhs, const OctreeGpuVertex &rhs) {
- return lhs.position == rhs.position && lhs.color == rhs.color;
-}
-
-} // namespace inexor::vulkan_renderer
-
-namespace std {
-
-template <>
-struct hash {
- std::size_t operator()(const inexor::vulkan_renderer::OctreeGpuVertex &vertex) const {
- const auto h1 = std::hash{}(vertex.position);
- const auto h2 = std::hash{}(vertex.color);
- return h1 ^ h2;
- }
-};
-
-} // namespace std
diff --git a/include/inexor/vulkan-renderer/render_graph.hpp b/include/inexor/vulkan-renderer/render_graph.hpp
deleted file mode 100644
index a036c9ce7..000000000
--- a/include/inexor/vulkan-renderer/render_graph.hpp
+++ /dev/null
@@ -1,455 +0,0 @@
-#pragma once
-
-#include "inexor/vulkan-renderer/wrapper/device.hpp"
-#include "inexor/vulkan-renderer/wrapper/framebuffer.hpp"
-#include "inexor/vulkan-renderer/wrapper/swapchain.hpp"
-
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-// TODO: Compute stages.
-// TODO: Uniform buffers.
-
-// Forward declarations
-namespace inexor::vulkan_renderer::wrapper {
-class CommandBuffer;
-class Shader;
-}; // namespace inexor::vulkan_renderer::wrapper
-
-namespace inexor::vulkan_renderer {
-
-// Forward declarations
-class PhysicalResource;
-class PhysicalStage;
-class RenderGraph;
-
-/// @brief Base class of all render graph objects (resources and stages).
-/// @note This is just for internal use.
-struct RenderGraphObject {
- RenderGraphObject() = default;
- RenderGraphObject(const RenderGraphObject &) = delete;
- RenderGraphObject(RenderGraphObject &&) = delete;
- virtual ~RenderGraphObject() = default;
-
- RenderGraphObject &operator=(const RenderGraphObject &) = delete;
- RenderGraphObject &operator=(RenderGraphObject &&) = delete;
-
- /// @brief Casts this object to type `T`
- /// @return The object as type `T` or `nullptr` if the cast failed
- template
- [[nodiscard]] T *as();
-
- /// @copydoc as
- template
- [[nodiscard]] const T *as() const;
-};
-
-/// @brief A single resource in the render graph.
-/// @note May become multiple physical (vulkan) resources during render graph compilation.
-class RenderResource : public RenderGraphObject {
- friend RenderGraph;
-
-private:
- const std::string m_name;
- std::shared_ptr m_physical;
-
-protected:
- explicit RenderResource(std::string name) : m_name(std::move(name)) {}
-
-public:
- RenderResource(const RenderResource &) = delete;
- RenderResource(RenderResource &&) = delete;
- ~RenderResource() override = default;
-
- RenderResource &operator=(const RenderResource &) = delete;
- RenderResource &operator=(RenderResource &&) = delete;
-
- [[nodiscard]] const std::string &name() const {
- return m_name;
- }
-};
-
-enum class BufferUsage {
- /// @brief Specifies that the buffer will be used to input index data.
- INDEX_BUFFER,
-
- /// @brief Specifies that the buffer will be used to input per vertex data to a vertex shader.
- VERTEX_BUFFER,
-};
-
-class BufferResource : public RenderResource {
- friend RenderGraph;
-
-private:
- const BufferUsage m_usage;
- std::vector m_vertex_attributes;
-
- // Data to upload during render graph compilation.
- const void *m_data{nullptr};
- std::size_t m_data_size{0};
- bool m_data_upload_needed{false};
- std::size_t m_element_size{0};
-
-public:
- BufferResource(std::string &&name, BufferUsage usage) : RenderResource(name), m_usage(usage) {}
-
- /// @brief Specifies that element `offset` of this vertex buffer is of format `format`.
- /// @note Calling this function is only valid on buffers of type BufferUsage::VERTEX_BUFFER.
- void add_vertex_attribute(VkFormat format, std::uint32_t offset);
-
- /// @brief Specifies the element size of the buffer upfront if data is not to be uploaded immediately.
- /// @param element_size The element size in bytes
- void set_element_size(std::size_t element_size) {
- m_element_size = element_size;
- }
-
- /// @brief Specifies the data that should be uploaded to this buffer at the start of the next frame.
- /// @param count The number of elements (not bytes) to upload
- /// @param data A pointer to a contiguous block of memory that is at least `count * sizeof(T)` bytes long
- // TODO: Use std::span when we switch to C++ 20.
- template
- void upload_data(const T *data, std::size_t count);
-
- /// @brief @copybrief upload_data(const T *, std::size_t)
- /// @note This is equivalent to doing `upload_data(data.data(), data.size() * sizeof(T))`
- /// @see upload_data(const T *data, std::size_t count)
- template
- void upload_data(const std::vector &data);
-};
-
-enum class TextureUsage {
- /// @brief Specifies that this texture is the output of the render graph.
- // TODO: Refactor back buffer system more (remove need for BACK_BUFFER texture usage)
- BACK_BUFFER,
-
- /// @brief Specifies that this texture is a combined depth/stencil buffer.
- /// @note This may mean that this texture is completely GPU-sided and cannot be accessed by the CPU in any way.
- DEPTH_STENCIL_BUFFER,
-
- /// @brief Specifies that this texture isn't used for any special purpose.
- NORMAL,
-};
-
-class TextureResource : public RenderResource {
- friend RenderGraph;
-
-private:
- const TextureUsage m_usage;
- VkFormat m_format{VK_FORMAT_UNDEFINED};
-
-public:
- TextureResource(std::string &&name, TextureUsage usage) : RenderResource(name), m_usage(usage) {}
-
- /// @brief Specifies the format of this texture that is required when the physical texture is made.
- /// @details For TextureUsage::BACK_BUFFER textures, using the swapchain image format is preferable in most cases.
- /// For TextureUsage::DEPTH_STENCIL_BUFFER textures, a VK_FORMAT_D* must be used.
- void set_format(VkFormat format) {
- m_format = format;
- }
-};
-
-/// @brief A single render stage in the render graph.
-/// @note Not to be confused with a vulkan render pass.
-class RenderStage : public RenderGraphObject {
- friend RenderGraph;
-
-private:
- const std::string m_name;
- std::unique_ptr m_physical;
- std::vector m_writes;
- std::vector m_reads;
-
- std::vector m_descriptor_layouts;
- std::vector m_push_constant_ranges;
- std::function m_on_record{[](auto &, auto &) {}};
-
-protected:
- explicit RenderStage(std::string name) : m_name(std::move(name)) {}
-
-public:
- RenderStage(const RenderStage &) = delete;
- RenderStage(RenderStage &&) = delete;
- ~RenderStage() override = default;
-
- RenderStage &operator=(const RenderStage &) = delete;
- RenderStage &operator=(RenderStage &&) = delete;
-
- /// @brief Specifies that this stage writes to `resource`.
- void writes_to(const RenderResource *resource);
-
- /// @brief Specifies that this stage reads from `resource`.
- void reads_from(const RenderResource *resource);
-
- /// @brief Binds a descriptor set layout to this render stage.
- /// @note This function will be removed in the near future, as we are aiming for users of the API to not have to
- /// deal with descriptors at all.
- // TODO: Refactor descriptor management in the render graph
- void add_descriptor_layout(VkDescriptorSetLayout layout) {
- m_descriptor_layouts.push_back(layout);
- }
-
- /// @brief Add a push constant range to this render stage.
- /// @param range The push constant range
- void add_push_constant_range(VkPushConstantRange range) {
- m_push_constant_ranges.push_back(range);
- }
-
- [[nodiscard]] const std::string &name() const {
- return m_name;
- }
-
- /// @brief Specifies a function that will be called during command buffer recording for this stage
- /// @details This function can be used to specify other vulkan commands during command buffer recording. The most
- /// common use for this is for draw commands.
- void set_on_record(std::function on_record) {
- m_on_record = std::move(on_record);
- }
-};
-
-class GraphicsStage : public RenderStage {
- friend RenderGraph;
-
-private:
- bool m_clears_screen{false};
- bool m_depth_test{false};
- bool m_depth_write{false};
- VkPipelineColorBlendAttachmentState m_blend_attachment{};
- std::unordered_map m_buffer_bindings;
- std::vector m_shaders;
-
-public:
- explicit GraphicsStage(std::string &&name) : RenderStage(name) {}
- GraphicsStage(const GraphicsStage &) = delete;
- GraphicsStage(GraphicsStage &&) = delete;
- ~GraphicsStage() override = default;
-
- GraphicsStage &operator=(const GraphicsStage &) = delete;
- GraphicsStage &operator=(GraphicsStage &&) = delete;
-
- /// @brief Specifies that this stage should clear the screen before rendering.
- void set_clears_screen(bool clears_screen) {
- m_clears_screen = clears_screen;
- }
-
- /// @brief Specifies the depth options for this stage.
- /// @param depth_test Whether depth testing should be performed
- /// @param depth_write Whether depth writing should be performed
- void set_depth_options(bool depth_test, bool depth_write) {
- m_depth_test = depth_test;
- m_depth_write = depth_write;
- }
-
- /// @brief Set the blend attachment for this stage.
- /// @param blend_attachment The blend attachment
- void set_blend_attachment(VkPipelineColorBlendAttachmentState blend_attachment) {
- m_blend_attachment = blend_attachment;
- }
-
- /// @brief Specifies that `buffer` should map to `binding` in the shaders of this stage.
- void bind_buffer(const BufferResource *buffer, std::uint32_t binding);
-
- /// @brief Specifies that `shader` should be used during the pipeline of this stage.
- /// @note Binding two shaders of same type (e.g. two vertex shaders) is undefined behaviour.
- void uses_shader(const wrapper::Shader &shader);
-};
-
-// TODO: Add wrapper::Allocation that can be made by doing `device->make(...)`.
-class PhysicalResource : public RenderGraphObject {
- friend RenderGraph;
-
-protected:
- const wrapper::Device &m_device;
- VmaAllocation m_allocation{VK_NULL_HANDLE};
-
- explicit PhysicalResource(const wrapper::Device &device) : m_device(device) {}
-
-public:
- PhysicalResource(const PhysicalResource &) = delete;
- PhysicalResource(PhysicalResource &&) = delete;
- ~PhysicalResource() override = default;
-
- PhysicalResource &operator=(const PhysicalResource &) = delete;
- PhysicalResource &operator=(PhysicalResource &&) = delete;
-};
-
-class PhysicalBuffer : public PhysicalResource {
- friend RenderGraph;
-
-private:
- VmaAllocationInfo m_alloc_info{};
- VkBuffer m_buffer{VK_NULL_HANDLE};
-
-public:
- explicit PhysicalBuffer(const wrapper::Device &device) : PhysicalResource(device) {}
- PhysicalBuffer(const PhysicalBuffer &) = delete;
- PhysicalBuffer(PhysicalBuffer &&) = delete;
- ~PhysicalBuffer() override;
-
- PhysicalBuffer &operator=(const PhysicalBuffer &) = delete;
- PhysicalBuffer &operator=(PhysicalBuffer &&) = delete;
-};
-
-class PhysicalImage : public PhysicalResource {
- friend RenderGraph;
-
-private:
- VkImage m_image{VK_NULL_HANDLE};
- VkImageView m_image_view{VK_NULL_HANDLE};
-
-public:
- explicit PhysicalImage(const wrapper::Device &device) : PhysicalResource(device) {}
- PhysicalImage(const PhysicalImage &) = delete;
- PhysicalImage(PhysicalImage &&) = delete;
- ~PhysicalImage() override;
-
- PhysicalImage &operator=(const PhysicalImage &) = delete;
- PhysicalImage &operator=(PhysicalImage &&) = delete;
-};
-
-class PhysicalBackBuffer : public PhysicalResource {
- friend RenderGraph;
-
-private:
- const wrapper::Swapchain &m_swapchain;
-
-public:
- PhysicalBackBuffer(const wrapper::Device &device, const wrapper::Swapchain &swapchain)
- : PhysicalResource(device), m_swapchain(swapchain) {}
- PhysicalBackBuffer(const PhysicalBackBuffer &) = delete;
- PhysicalBackBuffer(PhysicalBackBuffer &&) = delete;
- ~PhysicalBackBuffer() override = default;
-
- PhysicalBackBuffer &operator=(const PhysicalBackBuffer &) = delete;
- PhysicalBackBuffer &operator=(PhysicalBackBuffer &&) = delete;
-};
-
-class PhysicalStage : public RenderGraphObject {
- friend RenderGraph;
-
-private:
- VkPipeline m_pipeline{VK_NULL_HANDLE};
- VkPipelineLayout m_pipeline_layout{VK_NULL_HANDLE};
-
-protected:
- const wrapper::Device &m_device;
-
-public:
- explicit PhysicalStage(const wrapper::Device &device) : m_device(device) {}
- PhysicalStage(const PhysicalStage &) = delete;
- PhysicalStage(PhysicalStage &&) = delete;
- ~PhysicalStage() override;
-
- PhysicalStage &operator=(const PhysicalStage &) = delete;
- PhysicalStage &operator=(PhysicalStage &&) = delete;
-
- /// @brief Retrieve the pipeline layout of this physical stage.
- // TODO: This can be removed once descriptors are properly implemented in the render graph.
- [[nodiscard]] VkPipelineLayout pipeline_layout() const {
- return m_pipeline_layout;
- }
-};
-
-class PhysicalGraphicsStage : public PhysicalStage {
- friend RenderGraph;
-
-private:
- VkRenderPass m_render_pass{VK_NULL_HANDLE};
- std::vector m_framebuffers;
-
-public:
- explicit PhysicalGraphicsStage(const wrapper::Device &device) : PhysicalStage(device) {}
- PhysicalGraphicsStage(const PhysicalGraphicsStage &) = delete;
- PhysicalGraphicsStage(PhysicalGraphicsStage &&) = delete;
- ~PhysicalGraphicsStage() override;
-
- PhysicalGraphicsStage &operator=(const PhysicalGraphicsStage &) = delete;
- PhysicalGraphicsStage &operator=(PhysicalGraphicsStage &&) = delete;
-};
-
-class RenderGraph {
-private:
- wrapper::Device &m_device;
- const wrapper::Swapchain &m_swapchain;
- std::shared_ptr m_log{spdlog::default_logger()->clone("render-graph")};
-
- // Vectors of render resources and stages.
- std::vector> m_buffer_resources;
- std::vector> m_texture_resources;
- std::vector> m_stages;
-
- // Stage execution order.
- std::vector m_stage_stack;
-
- // Functions for building resource related vulkan objects.
- void build_buffer(const BufferResource &, PhysicalBuffer &) const;
- void build_image(const TextureResource &, PhysicalImage &, VmaAllocationCreateInfo *) const;
- void build_image_view(const TextureResource &, PhysicalImage &) const;
-
- // Functions for building stage related vulkan objects.
- void build_pipeline_layout(const RenderStage *, PhysicalStage &) const;
- void record_command_buffer(const RenderStage *, const wrapper::CommandBuffer &cmd_buf,
- std::uint32_t image_index) const;
-
- // Functions for building graphics stage related vulkan objects.
- void build_render_pass(const GraphicsStage *, PhysicalGraphicsStage &) const;
- void build_graphics_pipeline(const GraphicsStage *, PhysicalGraphicsStage &) const;
-
-public:
- RenderGraph(wrapper::Device &device, const wrapper::Swapchain &swapchain)
- : m_device(device), m_swapchain(swapchain) {}
-
- /// @brief Adds either a render resource or render stage to the render graph.
- /// @return A mutable reference to the just-added resource or stage
- template
- T *add(Args &&...args) {
- auto ptr = std::make_unique(std::forward(args)...);
- if constexpr (std::is_same_v) {
- return static_cast(m_buffer_resources.emplace_back(std::move(ptr)).get());
- } else if constexpr (std::is_same_v) {
- return static_cast(m_texture_resources.emplace_back(std::move(ptr)).get());
- } else if constexpr (std::is_base_of_v) {
- return static_cast(m_stages.emplace_back(std::move(ptr)).get());
- } else {
- static_assert(!std::is_same_v, "T must be a RenderResource or RenderStage");
- }
- }
-
- /// @brief Compiles the render graph resources/stages into physical vulkan objects.
- /// @param target The target resource of the render graph (usually the back buffer)
- void compile(const RenderResource *target);
-
- /// @brief Submits the command frame's command buffers for drawing.
- /// @param image_index The current image index, retrieved from Swapchain::acquire_next_image
- /// @param cmd_buf The command buffer
- void render(std::uint32_t image_index, const wrapper::CommandBuffer &cmd_buf);
-};
-
-template
-[[nodiscard]] T *RenderGraphObject::as() {
- return dynamic_cast(this);
-}
-
-template
-[[nodiscard]] const T *RenderGraphObject::as() const {
- return dynamic_cast(this);
-}
-
-template
-void BufferResource::upload_data(const T *data, std::size_t count) {
- m_data = data;
- m_data_size = count * (m_element_size = sizeof(T));
- m_data_upload_needed = true;
-}
-
-template
-void BufferResource::upload_data(const std::vector &data) {
- upload_data(data.data(), data.size());
-}
-
-} // namespace inexor::vulkan_renderer
diff --git a/include/inexor/vulkan-renderer/renderer.hpp b/include/inexor/vulkan-renderer/renderer.hpp
deleted file mode 100644
index 18c476b5c..000000000
--- a/include/inexor/vulkan-renderer/renderer.hpp
+++ /dev/null
@@ -1,83 +0,0 @@
-#pragma once
-
-#include "inexor/vulkan-renderer/camera.hpp"
-#include "inexor/vulkan-renderer/fps_counter.hpp"
-#include "inexor/vulkan-renderer/imgui.hpp"
-#include "inexor/vulkan-renderer/msaa_target.hpp"
-#include "inexor/vulkan-renderer/octree_gpu_vertex.hpp"
-#include "inexor/vulkan-renderer/time_step.hpp"
-#include "inexor/vulkan-renderer/vk_tools/gpu_info.hpp"
-#include "inexor/vulkan-renderer/wrapper/instance.hpp"
-#include "inexor/vulkan-renderer/wrapper/uniform_buffer.hpp"
-#include "inexor/vulkan-renderer/wrapper/window.hpp"
-#include "inexor/vulkan-renderer/wrapper/window_surface.hpp"
-
-namespace inexor::vulkan_renderer {
-
-class VulkanRenderer {
-protected:
- std::vector m_shader_stages;
-
- VkDebugReportCallbackEXT m_debug_report_callback{VK_NULL_HANDLE};
-
- bool m_debug_report_callback_initialised{false};
-
- TimeStep m_time_step;
-
- std::uint32_t m_window_width{0};
- std::uint32_t m_window_height{0};
- wrapper::Window::Mode m_window_mode{wrapper::Window::Mode::WINDOWED};
-
- std::string m_window_title;
-
- FPSCounter m_fps_counter;
-
- bool m_vsync_enabled{false};
-
- std::unique_ptr m_camera;
-
- std::unique_ptr m_window;
- std::unique_ptr m_instance;
- std::unique_ptr m_device;
- std::unique_ptr m_surface;
- std::unique_ptr m_swapchain;
- std::unique_ptr m_imgui_overlay;
- std::unique_ptr m_render_graph;
-
- std::vector m_shaders;
- std::vector m_textures;
- std::vector m_uniform_buffers;
- std::vector m_descriptors;
- std::vector m_octree_vertices;
- std::vector m_octree_indices;
-
- TextureResource *m_back_buffer{nullptr};
-
- // Render graph buffers for octree geometry.
- BufferResource *m_index_buffer{nullptr};
- BufferResource *m_vertex_buffer{nullptr};
-
- void setup_render_graph();
- void generate_octree_indices();
- void recreate_swapchain();
- void render_frame();
-
-public:
- VulkanRenderer() = default;
- VulkanRenderer(const VulkanRenderer &) = delete;
- VulkanRenderer(VulkanRenderer &&) = delete;
- ~VulkanRenderer();
-
- VulkanRenderer &operator=(const VulkanRenderer &) = delete;
- VulkanRenderer &operator=(VulkanRenderer &&) = delete;
-
- bool m_window_resized{false};
-
- /// Necessary for taking into account the relative speed of the system's CPU.
- float m_time_passed{0.0f};
-
- ///
- TimeStep m_stopwatch;
-};
-
-} // namespace inexor::vulkan_renderer
diff --git a/include/inexor/vulkan-renderer/rendering/imgui/imgui.hpp b/include/inexor/vulkan-renderer/rendering/imgui/imgui.hpp
new file mode 100644
index 000000000..2a2c69020
--- /dev/null
+++ b/include/inexor/vulkan-renderer/rendering/imgui/imgui.hpp
@@ -0,0 +1,114 @@
+#pragma once
+
+#include "inexor/vulkan-renderer/render-graph/render_graph.hpp"
+
+#include
+#include
+#include
+
+#include
+#include
+
+namespace inexor::vulkan_renderer::wrapper {
+// Forward declarations
+class Device;
+class Shader;
+class Swapchain;
+} // namespace inexor::vulkan_renderer::wrapper
+
+namespace inexor::vulkan_renderer::pipelines {
+// Forward declaration
+class GraphicsPipeline;
+} // namespace inexor::vulkan_renderer::pipelines
+
+namespace inexor::vulkan_renderer::render_graph {
+// Forward declarations
+class Buffer;
+class RenderGraph;
+class GraphicsPass;
+} // namespace inexor::vulkan_renderer::render_graph
+
+namespace inexor::vulkan_renderer::renderers {
+
+// Using declarations
+using render_graph::Buffer;
+using render_graph::GraphicsPass;
+using render_graph::RenderGraph;
+using render_graph::Texture;
+using wrapper::Device;
+using wrapper::Shader;
+using wrapper::Swapchain;
+using wrapper::pipelines::GraphicsPipeline;
+
+/// A wrapper for an ImGui implementation
+class ImGuiRenderer {
+ std::weak_ptr m_index_buffer;
+ std::weak_ptr m_vertex_buffer;
+ std::weak_ptr m_imgui_texture;
+ std::shared_ptr m_imgui_pipeline;
+
+ std::weak_ptr m_imgui_pass;
+
+ // This is the color attachment we will write to
+ std::weak_ptr m_swapchain;
+ // This is the previous pass we read from
+ std::weak_ptr m_previous_pass;
+
+ std::shared_ptr m_vertex_shader;
+ std::shared_ptr m_fragment_shader;
+
+ VkDescriptorSetLayout m_descriptor_set_layout{VK_NULL_HANDLE};
+ VkDescriptorSet m_descriptor_set{VK_NULL_HANDLE};
+
+ // We need to collect the vertices and indices generated by ImGui
+ // because it does not store them in one array, but rather in chunks
+ std::vector m_index_data;
+ std::vector m_vertex_data;
+
+ ImGuiContext *m_imgui_context{nullptr};
+
+ unsigned char *m_font_texture_data{nullptr};
+ int m_font_texture_width{0};
+ int m_font_texture_height{0};
+ int m_font_texture_data_size{0};
+ bool m_font_texture_initialized{false};
+
+ // Neither scale nor translation change
+ struct PushConstBlock {
+ glm::vec2 scale{-1.0f};
+ glm::vec2 translate{-1.0f};
+ } m_push_const_block;
+
+ /// The user's ImGui data will be updated in this function
+ /// It will be called at the beginning of set_on_update
+ std::function m_on_update_user_data{[]() {}};
+
+ void load_font_data_from_file();
+
+ /// Customize ImGui style like text color for example
+ void set_imgui_style();
+
+public:
+ /// Default constructor
+ /// @param device The device wrapper
+ /// @param render_graph The rendergraph
+ /// @param previous_pass The previous pass
+ /// @param swapchain The swapchain to render to
+ /// @param on_update_user_data The user-specified ImGui update function
+ ImGuiRenderer(const Device &device,
+ std::weak_ptr render_graph,
+ std::weak_ptr previous_pass,
+ std::weak_ptr swapchain,
+ std::function on_update_user_data);
+
+ ImGuiRenderer(const ImGuiRenderer &) = delete;
+ ImGuiRenderer(ImGuiRenderer &&) noexcept;
+
+ /// Call ImGui::DestroyContext
+ ~ImGuiRenderer();
+
+ ImGuiRenderer &operator=(const ImGuiRenderer &) = delete;
+ ImGuiRenderer &operator=(ImGuiRenderer &&) = delete;
+};
+
+} // namespace inexor::vulkan_renderer::renderers
diff --git a/include/inexor/vulkan-renderer/rendering/imgui/imgui_renderer.hpp b/include/inexor/vulkan-renderer/rendering/imgui/imgui_renderer.hpp
new file mode 100644
index 000000000..4e769410d
--- /dev/null
+++ b/include/inexor/vulkan-renderer/rendering/imgui/imgui_renderer.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "inexor/vulkan-renderer/rendering/render-graph/render_graph.hpp"
+
+#include
+
+namespace inexor::vulkan_renderer::rendering::imgui {
+
+// Using declaration
+using rendering::render_graph::RenderGraph;
+
+/// A rendering class for ImGui
+class ImGuiRenderer {
+private:
+public:
+ ImGuiRenderer(std::weak_ptr rendergraph);
+ ~ImGuiRenderer() = default;
+};
+
+} // namespace inexor::vulkan_renderer::rendering::imgui
diff --git a/include/inexor/vulkan-renderer/rendering/octree/octree_renderer.hpp b/include/inexor/vulkan-renderer/rendering/octree/octree_renderer.hpp
new file mode 100644
index 000000000..9d1db7d3b
--- /dev/null
+++ b/include/inexor/vulkan-renderer/rendering/octree/octree_renderer.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "inexor/vulkan-renderer/rendering/render-graph/render_graph.hpp"
+
+#include
+
+namespace inexor::vulkan_renderer::rendering::octree {
+
+/// A rendering class for octrees
+class OctreeRenderer {
+private:
+public:
+ OctreeRenderer(std::weak_ptr rendergraph);
+ ~OctreeRenderer() = default;
+};
+
+} // namespace inexor::vulkan_renderer::rendering::octree
diff --git a/include/inexor/vulkan-renderer/rendering/render-graph/buffer.hpp b/include/inexor/vulkan-renderer/rendering/render-graph/buffer.hpp
new file mode 100644
index 000000000..17f24d225
--- /dev/null
+++ b/include/inexor/vulkan-renderer/rendering/render-graph/buffer.hpp
@@ -0,0 +1,159 @@
+#pragma once
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+namespace inexor::vulkan_renderer::wrapper {
+/// Forward declaration
+class Device;
+} // namespace inexor::vulkan_renderer::wrapper
+
+namespace inexor::vulkan_renderer::wrapper::commands {
+/// Forward declaration
+class CommandBuffer;
+} // namespace inexor::vulkan_renderer::wrapper::commands
+
+namespace inexor::vulkan_renderer::wrapper::descriptors {
+/// Forward declaration
+class WriteDescriptorSetBuilder;
+} // namespace inexor::vulkan_renderer::wrapper::descriptors
+
+namespace inexor::vulkan_renderer::rendering::render_graph {
+
+/// The buffer type describes the internal usage of the buffer resource inside of the rendergraph
+enum class BufferType {
+ VERTEX_BUFFER,
+ INDEX_BUFFER,
+ UNIFORM_BUFFER,
+};
+
+// Forward declaration
+class GraphicsPass;
+
+// Using declarations
+using wrapper::Device;
+using wrapper::commands::CommandBuffer;
+using wrapper::descriptors::WriteDescriptorSetBuilder;
+
+// TODO: Store const reference to rendergraph and retrieve the swapchain image index for automatic buffer tripling
+
+/// RAII wrapper for buffer resources inside of the rendergraph
+/// A buffer resource can be a vertex buffer, index buffer, or uniform buffer
+class Buffer {
+ //
+ friend class RenderGraph;
+ friend class GraphicsPass;
+ friend class CommandBuffer;
+ friend class WriteDescriptorSetBuilder;
+
+private:
+ /// The device wrapper
+ const Device &m_device;
+ /// The internal debug name of the buffer resource
+ std::string m_name;
+
+ /// The buffer type will be set depending on which constructor of the Buffer wrapper is called by rendergraph. The
+ /// engine currently supports three different types of buffers in the Buffer wrapper class: vertex buffers, index
+ /// buffers, and uniform buffers. The instances of the Buffer wrapper class are managed by rendergraph only. One
+ /// solution to deal with the different buffer types would be to use a BufferBase class and to make three distinct
+ /// classes VertexBuffer, IndexBuffer, and UniformBuffer. However, we aimed for simplicity and wanted to avoid
+ /// polymorphism in the rendergraph for performance reasons. We also refrained from using templates for this use
+ /// case. Therefore, we have chosen to use only one Buffer wrapper class which contains members for all three
+ /// different buffer types. The type of the buffer will be set depending on which Buffer constructor is called by
+ /// rendergraph. The actual memory management for the buffers is done by Vulkan Memory Allocator (VMA) internally.
+ BufferType m_buffer_type;
+
+ /// The buffer update function which is called by rendergraph to update the buffer's data. This update function is
+ /// called, no matter what the type of the buffer is. With the currently supported buffer types (vertex-, index-,
+ /// and uniform buffers) there is always a discussion about whether some update lambdas can be made std::optional.
+ /// For example we could have one vertex buffer with an index buffer and the index buffer is updated together with
+ /// the vertex buffer in the update function of the vertex buffer. From the design of the engine there is no
+ /// limitation which buffer is updated in which update function, as long as the handle to that buffer has been
+ /// created in rendergraph. In our example, the update function of the index buffer could be std::nullopt. In this
+ /// case, rendergraph could separate all buffers into those which require an update and those who do not. For
+ /// simplicity however, we made the update function not std::optional.
+
+ // TODO: Rewrite description
+ std::function m_on_check_for_update;
+
+ /// TODO: Is this is relevant for uniform buffers only?
+ /// TODO: Maybe buffer updates should be done immediately, and no m_src_data should be stored!
+ /// It's the responsibility of the programmer to make sure the data m_src_data points to is still valid when
+ /// update_buffer() is called!
+ void *m_src_data{nullptr};
+ std::size_t m_src_data_size{0};
+ bool m_update_requested{false};
+
+ /// The resources for actual memory management of the buffer
+ VkBuffer m_buffer{VK_NULL_HANDLE};
+ VmaAllocation m_alloc{VK_NULL_HANDLE};
+ VmaAllocationInfo m_alloc_info{};
+
+ /// The descriptor buffer info (required for uniform buffers)
+ VkDescriptorBufferInfo m_descriptor_buffer_info{};
+
+ /// The staging buffer (if required)
+ VkBuffer m_staging_buffer{VK_NULL_HANDLE};
+ VmaAllocation m_staging_buffer_alloc{VK_NULL_HANDLE};
+ VmaAllocationInfo m_staging_buffer_alloc_info{};
+
+ /// Create the buffer using Vulkan Memory Allocator (VMA) library
+ /// @param cmd_buf The command buffer
+ void create(const CommandBuffer &cmd_buf);
+
+ /// Call destroy_buffer and destroy_staging_buffer
+ void destroy_all();
+
+ /// Call vmaDestroyBuffer for the actual buffer
+ void destroy_buffer();
+
+ /// Call vmaDestroyBuffer for the staging bufffer
+ void destroy_staging_buffer();
+
+public:
+ /// Default constructor
+ /// @param device The device wrapper
+ /// @param buffer_name The name of the buffer
+ /// @param buffer_type The type of the buffer
+ /// @param on_update The buffer update function
+ Buffer(const Device &device, std::string buffer_name, BufferType buffer_type, std::function on_update);
+
+ Buffer(const Buffer &) = delete;
+ Buffer(Buffer &&other) noexcept;
+
+ /// Call destroy_buffer
+ ~Buffer();
+
+ Buffer &operator=(const Buffer &) = delete;
+ Buffer &operator=(Buffer &&) = delete;
+
+ /// Request a buffer update
+ /// @param src_data A pointer to the data to copy the updated data from
+ /// @warning It is the responsibility of the programmer to make sure src_data still points to valid memory when
+ /// update_buffer() is called!
+ /// @param src_data_size The size of the data to copy
+ void request_update(void *src_data, std::size_t src_data_size);
+
+ /// Request a buffer update
+ /// @tparam BufferDataType
+ /// @param data
+ template
+ void request_update(BufferDataType &data) {
+ return request_update(std::addressof(data), sizeof(data));
+ }
+
+ /// Request a buffer update
+ /// @tparam BufferDataType
+ /// @param data
+ template
+ void request_update(std::vector &data) {
+ return request_update(data.data(), sizeof(BufferDataType) * data.size());
+ }
+};
+
+} // namespace inexor::vulkan_renderer::rendering::render_graph
diff --git a/include/inexor/vulkan-renderer/rendering/render-graph/graphics_pass.hpp b/include/inexor/vulkan-renderer/rendering/render-graph/graphics_pass.hpp
new file mode 100644
index 000000000..4dff3901e
--- /dev/null
+++ b/include/inexor/vulkan-renderer/rendering/render-graph/graphics_pass.hpp
@@ -0,0 +1,108 @@
+#pragma once
+
+#include
+
+#include "inexor/vulkan-renderer/rendering/render-graph/buffer.hpp"
+#include "inexor/vulkan-renderer/rendering/render-graph/texture.hpp"
+#include "inexor/vulkan-renderer/wrapper/descriptors/descriptor_set_layout.hpp"
+#include "inexor/vulkan-renderer/wrapper/device.hpp"
+#include "inexor/vulkan-renderer/wrapper/swapchain.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace inexor::vulkan_renderer::wrapper::commands {
+// Forward declaration
+class CommandBuffer;
+} // namespace inexor::vulkan_renderer::wrapper::commands
+
+namespace inexor::vulkan_renderer::rendering::render_graph {
+
+// Forward declaration
+class RenderGraph;
+
+// Using declarations
+using wrapper::Swapchain;
+using wrapper::descriptors::DescriptorSetLayout;
+
+/// Using declaration
+using OnRecordCommandBufferForPass = std::function;
+
+/// A wrapper for graphics passes inside of rendergraph
+class GraphicsPass {
+ friend class RenderGraph;
+
+private:
+ /// The name of the graphics pass
+ std::string m_name;
+
+ /// The command buffer recording function of the graphics pass
+ OnRecordCommandBufferForPass m_on_record_cmd_buffer{[](auto &) {}};
+
+ /// The descriptor set layout of the pass (this will be created by rendergraph)
+ std::unique_ptr m_descriptor_set_layout;
+ /// The descriptor set of the pass (this will be created by rendergraph)
+ VkDescriptorSet m_descriptor_set{VK_NULL_HANDLE};
+
+ /// The color of the debug label region (visible in graphics debuggers like RenderDoc)
+ std::array m_debug_label_color;
+
+ /// The extent
+ VkExtent2D m_extent{0, 0};
+ /// The graphics passes this pass reads from
+ std::vector> m_graphics_pass_reads;
+ /// A weak pointer to the next graphics pass
+ std::weak_ptr m_next_pass{};
+
+ /// The texture attachments of this pass (unified means color, depth, stencil attachment or a swapchain)
+ std::vector, std::optional>> m_write_attachments{};
+ /// The swapchains this graphics pass writes to
+ std::vector, std::optional>> m_write_swapchains{};
+
+ // All the data below will be filled and used by rendergraph only
+
+ /// The rendering info will be filled during rendergraph compilation so we don't have to do this while rendering.
+ /// This means we must make sure that the memory of the attachment infos below is still valid during rendering,
+ /// which is why we store them as members here.
+ VkRenderingInfo m_rendering_info{};
+ /// The color attachments inside of m_rendering_info
+ std::vector m_color_attachments{};
+ /// Does this graphics pass have any depth attachment?
+ bool m_has_depth_attachment{false};
+ /// The depth attachment inside of m_rendering_info
+ VkRenderingAttachmentInfo m_depth_attachment{};
+ /// Does this graphics pass have any stencil attachment?
+ bool m_has_stencil_attachment{false};
+ /// The stencil attachment inside of m_rendering_info
+ VkRenderingAttachmentInfo m_stencil_attachment{};
+
+ /// Reset the rendering info
+ void reset_rendering_info();
+
+public:
+ /// Default constructor
+ /// @param name The name of the graphics pass
+ /// @param on_record_cmd_buffer The command buffer recording function of the graphics pass
+ /// @param graphics_pass_reads The graphics passes this graphics pass reads from
+ /// @param write_attachments The attachment this graphics pass writes to
+ /// @param write_swapchains The swapchains this graphics pass writes to
+ /// @param pass_debug_label_color The debug label of the pass (visible in graphics debuggers like RenderDoc)
+ GraphicsPass(std::string name,
+ OnRecordCommandBufferForPass on_record_cmd_buffer,
+ std::vector> graphics_pass_reads,
+ std::vector, std::optional>> write_attachments,
+ std::vector, std::optional>> write_swapchains,
+ wrapper::DebugLabelColor pass_debug_label_color);
+
+ GraphicsPass(const GraphicsPass &) = delete;
+ GraphicsPass(GraphicsPass &&other) noexcept;
+ ~GraphicsPass() = default;
+
+ GraphicsPass &operator=(const GraphicsPass &) = delete;
+ GraphicsPass &operator=(GraphicsPass &&) = delete;
+};
+
+} // namespace inexor::vulkan_renderer::rendering::render_graph
diff --git a/include/inexor/vulkan-renderer/rendering/render-graph/graphics_pass_builder.hpp b/include/inexor/vulkan-renderer/rendering/render-graph/graphics_pass_builder.hpp
new file mode 100644
index 000000000..74c8fa9bc
--- /dev/null
+++ b/include/inexor/vulkan-renderer/rendering/render-graph/graphics_pass_builder.hpp
@@ -0,0 +1,84 @@
+#pragma once
+
+#include "inexor/vulkan-renderer/rendering/render-graph/buffer.hpp"
+#include "inexor/vulkan-renderer/rendering/render-graph/graphics_pass.hpp"
+#include "inexor/vulkan-renderer/rendering/render-graph/texture.hpp"
+#include "inexor/vulkan-renderer/wrapper/make_info.hpp"
+#include "inexor/vulkan-renderer/wrapper/swapchain.hpp"
+
+#include
+#include
+#include
+#include
+
+namespace inexor::vulkan_renderer::wrapper::commands {
+// Forward declaration
+class CommandBuffer;
+} // namespace inexor::vulkan_renderer::wrapper::commands
+
+namespace inexor::vulkan_renderer::rendering::render_graph {
+
+// Using declaration
+using wrapper::DebugLabelColor;
+using wrapper::Swapchain;
+using wrapper::commands::CommandBuffer;
+
+/// A builder class for graphics passes in the rendergraph
+class GraphicsPassBuilder {
+private:
+ /// Add members which describe data related to graphics passes here
+ OnRecordCommandBufferForPass m_on_record_cmd_buffer{};
+ /// The graphics passes which are read by this graphics pass
+ std::vector> m_graphics_pass_reads{};
+ /// The texture resources this graphics pass writes to
+ std::vector, std::optional>> m_write_attachments{};
+ /// The swapchain this graphics pass writes to
+ std::vector, std::optional>> m_write_swapchains{};
+
+ /// Reset the data of the graphics pass builder
+ void reset();
+
+public:
+ GraphicsPassBuilder();
+ GraphicsPassBuilder(const GraphicsPassBuilder &) = delete;
+ GraphicsPassBuilder(GraphicsPassBuilder &&) noexcept;
+ ~GraphicsPassBuilder() = default;
+
+ GraphicsPassBuilder &operator=(const GraphicsPassBuilder &) = delete;
+ GraphicsPassBuilder &operator=(GraphicsPassBuilder &&) = delete;
+
+ /// Build the graphics pass
+ /// @param name The name of the graphics pass
+ /// @param color The debug label color (debug labels are specified per pass and are visible in RenderDoc debugger)
+ /// @return The graphics pass that was just created
+ [[nodiscard]] std::shared_ptr build(std::string name, DebugLabelColor color);
+
+ /// Specify that this graphics pass A reads from another graphics pass B (if the weak_ptr to B is not expired),
+ /// meaning B should be rendered before A. It is perfect valid for 'graphics_pass' to be an invalid pointer, in
+ /// which case the read is not added.
+ /// @param condition The condition under which the pass is read from
+ /// @param graphics_pass The graphics pass (can be an invalid pointer)
+ /// @return A const reference to the this pointer (allowing method calls to be chained)
+ [[nodiscard]] GraphicsPassBuilder &conditionally_reads_from(std::weak_ptr graphics_pass,
+ bool condition);
+
+ /// Specify that this graphics pass A reads from another graphics pass B, meaning B should be rendered before A
+ /// @param graphics_pass The graphics pass which is read by this graphics pass
+ /// @return A const reference to the this pointer (allowing method calls to be chained)
+ [[nodiscard]] GraphicsPassBuilder &reads_from(std::weak_ptr graphics_pass);
+
+ /// Set the function which will be called when the command buffer for rendering of the pass is being recorded
+ /// @param on_record_cmd_buffer The command buffer recording function
+ /// @return A const reference to the this pointer (allowing method calls to be chained)
+ [[nodiscard]] GraphicsPassBuilder &set_on_record(OnRecordCommandBufferForPass on_record_cmd_buffer);
+
+ /// Specify that this graphics pass writes to an either a std::weak_ptr or a std::weak_ptr
+ /// @param attachment The attachment (either a std::weak_ptr or a std::weak_ptr)
+ /// @param clear_value The optional clear value of the attachment (``std::nullopt`` by default)
+ /// @return A const reference to the this pointer (allowing method calls to be chained)
+ [[nodiscard]] GraphicsPassBuilder &
+ writes_to(std::variant, std::weak_ptr> write_attachment,
+ std::optional clear_value = std::nullopt);
+};
+
+} // namespace inexor::vulkan_renderer::rendering::render_graph
diff --git a/include/inexor/vulkan-renderer/rendering/render-graph/render_graph.hpp b/include/inexor/vulkan-renderer/rendering/render-graph/render_graph.hpp
new file mode 100644
index 000000000..1c4506915
--- /dev/null
+++ b/include/inexor/vulkan-renderer/rendering/render-graph/render_graph.hpp
@@ -0,0 +1,380 @@
+#pragma once
+
+#include "inexor/vulkan-renderer/rendering/render-graph/buffer.hpp"
+#include "inexor/vulkan-renderer/rendering/render-graph/graphics_pass.hpp"
+#include "inexor/vulkan-renderer/rendering/render-graph/graphics_pass_builder.hpp"
+#include "inexor/vulkan-renderer/rendering/render-graph/texture.hpp"
+#include "inexor/vulkan-renderer/wrapper/descriptors/descriptor_set_allocator.hpp"
+#include "inexor/vulkan-renderer/wrapper/descriptors/descriptor_set_layout_builder.hpp"
+#include "inexor/vulkan-renderer/wrapper/descriptors/descriptor_set_layout_cache.hpp"
+#include "inexor/vulkan-renderer/wrapper/descriptors/write_descriptor_set_builder.hpp"
+#include "inexor/vulkan-renderer/wrapper/pipelines/pipeline.hpp"
+#include "inexor/vulkan-renderer/wrapper/pipelines/pipeline_builder.hpp"
+#include "inexor/vulkan-renderer/wrapper/pipelines/pipeline_layout.hpp"
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace inexor::vulkan_renderer::wrapper {
+/// Forward declarations
+class Device;
+class Swapchain;
+} // namespace inexor::vulkan_renderer::wrapper
+
+namespace inexor::vulkan_renderer::rendering::render_graph {
+
+// Using declarations
+using wrapper::Device;
+using wrapper::Swapchain;
+using wrapper::commands::CommandBuffer;
+using wrapper::descriptors::DescriptorSetAllocator;
+using wrapper::descriptors::DescriptorSetLayoutBuilder;
+using wrapper::descriptors::DescriptorSetLayoutCache;
+using wrapper::descriptors::WriteDescriptorSetBuilder;
+using wrapper::pipelines::GraphicsPipeline;
+using wrapper::pipelines::GraphicsPipelineBuilder;
+using wrapper::pipelines::PipelineLayout;
+
+/// A rendergraph is a generic solution for rendering architecture.
+///
+///
+///
+///
+///
+///
+class RenderGraph {
+private:
+ /// The device wrapper
+ Device &m_device;
+
+ // The rendergraph has its own logger
+ std::shared_ptr m_log{spdlog::default_logger()->clone("render-graph")};
+
+ /// -----------------------------------------------------------------------------------------------------------------
+ /// GRAPHICS PASSES
+ /// -----------------------------------------------------------------------------------------------------------------
+ /// Graphics passes are build inside of graphics pass create functions. Those functions are given to the
+ /// rendergraph, and they are all called sequentially during rendergraph compilation. Inside of the graphics pass
+ /// create function, the GraphicsPassBuilder can be used to build the graphics pass. The graphics pass which is
+ /// created is stored internally inside of the rendergraph. Each graphics pass specifies to which attachments
+ /// (color, depth, stencil) it writes by using the writes_to method of the GraphicsPassBuilder. The attachments will
+ /// be used by rendergraph in the VkRenderingInfo in dynamic rendering. Each pass must also specify from which other
+ /// passes it reads. For example if we specify B.reads_from(A), it means that graphics pass B is reading from
+ /// graphics pass A. Rendergraph can then automatically determine the oder of all passes during rendergraph
+ /// compilation using depth first search (DFS) algorithm. Rendergraph also makes sure that the graph is acyclic, so
+ /// there must not be any cycles in it! Note that graphics passes only specify reads_from for previous passes and
+ /// writes_to for attachments, as a graphics pass does not directly write into another graphics pass, but only into
+ /// attachments. Why can rendergraph only reason about cycles after creating the graphics passes, at the and of
+ /// compilation? To check for cycles, we would need to reason about the reads of each graphics pass before the
+ /// passes have been created. The problem here is that we can't reference a piece of memory unless it as been
+ /// created, meaning we can't read_from a pass unless its shared_ptr has been allocated. A workaround would be to
+ /// have a default constructor which already creates the object before rendergraph could call another constructor
+ /// which actually creates the pass. This is however much more complex than the current solution, and from what we
+ /// understand it's the fact that we can't reference a piece of memory unless it has been created that makes it very
+ /// hard to actually make cycles in rendergraph passes! You can only call reads_from for passes which came before
+ /// the pass you are currently creating, you can't call reads_from for passes which come later.
+ /// -----------------------------------------------------------------------------------------------------------------
+
+ /// The graphics pass builder of the rendergraph
+ GraphicsPassBuilder m_graphics_pass_builder{};
+ /// The graphics passes used in the rendergraph
+ std::vector> m_graphics_passes;
+
+ /// -----------------------------------------------------------------------------------------------------------------
+ /// SWAPCHAINS
+ /// -----------------------------------------------------------------------------------------------------------------
+ /// Swapchains are not managed by rendergraph and they are not even stored as a std::weak_ptr inside of rendergraph.
+ /// Instead, if a graphics pass writes to a swapchain, it can be specified by using the writes_to method in
+ /// GraphicsPassBuilder. To unify textures and swapchains as write attachments, The GraphicsPassBuilder class uses
+ /// std::variant, std::weak_ptr> as type for write attachments. When creating the
+ /// graphics pass, The constructor of the GraphicsPass class will automatically sort the write attachment by type
+ /// (texture or swapchain). The textures and swapchains which are used as write attachments are processed by
+ /// rendergraph in the fill_graphics_pass_rendering_info method. To fill VkRenderingInfo of a graphics pass, we need
+ /// to fill VkRenderingInfo for every texture and swapchain that is written to. This means we only need to know the
+ /// image view of the texture (the current image view in case of a swapchain), the image layout, and the optional
+ /// clear value for the swapchain or texture. The clear value is stored in the graphics pass as well.
+ ///
+ /// Rendergraph automatically performs image layout transitions for swapchains when they are used as write
+ /// attachment. To avoid unnecessary transitions, rendergraph will check if a swapchain is already in
+ /// VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL layout before starting dynamic rendering. To avoid more unnecessary
+ /// image layout transition of the swapchain image, the layout is changed to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR only if
+ /// the next pass (if any) is not rendering to the swapchain.
+ /// -----------------------------------------------------------------------------------------------------------------
+
+ /// The unique wait semaphores of all swapchains used (This means if one swapchain is used mutliple times it's still
+ /// only one VkSemaphore in here because collect_swapchain_image_available_semaphores method will fill this vector)
+ std::vector m_swapchains_imgs_available;
+
+ /// -----------------------------------------------------------------------------------------------------------------
+ /// GRAPHICS PIPELINES
+ /// -----------------------------------------------------------------------------------------------------------------
+ /// Graphics pipelines are created during rendergraph compilation, but the graphics pipeline instances are not
+ /// stored inside of the rendergraph (unlike textures or buffers for example). The reason for this is that graphics
+ /// pipelines are also not bound by the rendergraph automatically before calling the on_record command buffer
+ /// recording function of a graphics pass. This is because while the rendergraph could bind a graphics pipeline just
+ /// before calling on_record, inside of the on_record function, the use of graphics pipelines could be arbitrarily
+ /// complex. We also didn't want to have a rendergraph API which exposes binding one graphics pipeline before
+ /// calling on_record because this could let the programmer think that binding pipelines is fully covered internally
+ /// in rendergraph, which is not the case. You might wonder why we decided to use create functions for the graphics
+ /// pipelines if the graphics pipeline is not stored inside of rendergraph? To create a graphics pipeline, you need
+ /// a pipeline layout. The pipeline layout is part of the GraphicsPipeline wrapper and every graphics pipeline has
+ /// exactly one pipeline layout. To create the pipeline layout, you need to know the descriptor set layout! To be
+ /// precise, you need to know the descriptor set layoput if the graphics pipeline uses resource descriptors. You
+ /// also need to know the push constant ranges, but they are relatively easy to specify in GraphicsPipelienBuilder.
+ /// This complex order of initialization of Vulkan resources must be respected and one of the advantages of having a
+ /// rendergraph is that is makes all this very easy. After the descriptor set layout has been created by the
+ /// rendergraph, the graphics pipelines can be created because we then know about the descriptor set layout which is
+ /// required for the pipeline layout.
+ /// -----------------------------------------------------------------------------------------------------------------
+
+ // TODO: Support compute pipelines
+ // TODO: Use pipeline cache and write pipeline cache to file, and support loading them
+
+ /// The graphics pipeline builder of the rendergraph
+ GraphicsPipelineBuilder m_graphics_pipeline_builder;
+ /// In these callback functions, the graphics pipelines will be created
+ using OnCreateGraphicsPipeline = std::function;
+ /// The graphics pipeline create functions which are called by rendergraph to create the graphics pipelines
+ std::vector m_pipeline_create_functions;
+
+ /// -----------------------------------------------------------------------------------------------------------------
+ /// BUFFERS
+ /// -----------------------------------------------------------------------------------------------------------------
+ /// We use Vulkan Memory Allocator (VMA) for memory management of resources under the hood. Buffers are created and
+ /// stored inside of rendergraph exclusively. To code outside of rendergraph, we give std::weak_ptr of the Buffer
+ /// handles we create. This way, the buffers can be used in the on_record function directly without having to pass
+ /// the buffers as parameters to the on_record function. Also, the use of std::weak_ptr makes the memory ownership
+ /// of the buffer handle clear. The memory is owned by the rendergraph in a std::shared_ptr, but it can be used
+ /// outside of rendergraph through the std::weak_ptr. The Buffer wrapper can be a VERTEX_BUFFER, INDEX_BUFFER, or
+ /// UNIFORM_BUFFER. The rendergraph can be instructed to update a buffer in code outside of the rendergraph using
+ /// the request_update method, and the update is then carried out by rendergraph. All buffer updates are carried out
+ /// on a per-frame basis, meaning that all updates will always be coherent with respect to one frame. In the future,
+ /// we could improve rendergraph so it automatically double or triple buffers all resources (buffers, textures..),
+ /// so it can use one index for rendering, while update on the next index is already being carried out. This will
+ /// parallelization using taskflow and proper synchronization must be used. While this would increase memory
+ /// consumption, it would improve rendering performance by reducing cpu and gpu stalls. Every buffer must have an
+ /// update function. If a vertex buffer and an index buffer is updated, each buffer should be updated in their own
+ /// update function rather than updating the index buffer in the vertex buffer as well. While this is technically
+ /// also correct, it makes no sense to do it this way because vertex buffer and index buffer are updated on a
+ /// per-frame basis coherently anyways. So it doesn't really matter.
+ /// -----------------------------------------------------------------------------------------------------------------
+
+ /// The vertex buffers, index buffers, and uniform buffers
+ std::vector> m_buffers;
+
+ /// -----------------------------------------------------------------------------------------------------------------
+ /// TEXTURES
+ /// -----------------------------------------------------------------------------------------------------------------
+ /// Like buffers, textures are created by rendergraph and stored in the rendergraph as a shared_ptr. This means just
+ /// like buffers, the rendergraph owns the underlying memory, and all external code must use std::weak_ptr to it.
+ /// There are different types of textures, which are listed in TextureUsage. In principle, the Texture wrapper
+ /// allows each texture to have an on_init and an on_update function. Depending on the texture usage, textures can
+ /// be created in two different ways: Textures which are used as color attachment (back buffer) or depth attachment
+ /// (depth buffer) are created inside of rendergraph only. This means code outside of rendergraph is not allowed to
+ /// modify the data in the texture directly, but instead the external code must specify which attachment is written
+ /// to by which pass. Rendergraph will then write to the attachments automatically. This means for texture which are
+ /// of TextureUsage COLOR_ATTACHMENT or DEPTH_ATTACHMENT, both on_init and on_update are std::nullopt (Rendergraph
+ /// initializes these textures directly rather than specifying an unnecessary on_init function which is then called
+ /// directly). The other type of texture is TextureUsage::NORMAL, which means this texture is used in for example in
+ /// combined image samplers. This could be the font texture of ImGui for example. Because the data to fill this
+ /// texture is loaded from a file by the ImGui wrapper, we must specify an on_init function to rendergraph which
+ /// copies the font texture data into the texture wrapper. TextureUsage NORMAL is the only texture type which is
+ /// allowed to have an on_update function. The on_update function is called per-frame (if it's not std::nullopt).
+ /// The on_update function allows for dynamic textures which are updated every frame. Note that so far, our code
+ /// base does not use this feature yet.
+
+ /// The textures
+ std::vector> m_textures;
+
+ /// -----------------------------------------------------------------------------------------------------------------
+ /// RESOURCE DESCRIPTORS
+ /// -----------------------------------------------------------------------------------------------------------------
+ /// After a lot of discussion we decided to keep the actual VkDescriptorSet handles not inside of of
+ /// rendergraph. Originally we planed to have one descriptor set per pass (or several of them per pass) which are
+ /// bound using vkBindDescriptorSet before on_record is called. However, the binding of descriptor sets inside of
+ /// the on_record command buffer recording function of the pass can be much more complex than just binding one
+ /// descriptor set (or several) before calling on_record. In fact, the descriptor set binding could be arbitrarily
+ /// complex inside of on_record. Associating the descriptor sets with the pass would introduce another level of
+ /// unnecessary indirection without any benefits which we do not want. We then thought we could automatically create
+ /// the descriptor set layout of a pass by analyzing the resources the pass reads from. This in theory would also
+ /// allow us to allocate the descriptor sets and to update them. However, the descriptor set layout is also required
+ /// for creating the pipeline layout! There are two problems with this: 1) The programmer would either have to
+ /// specify the reads of the pass in the order of the descriptor set layout or associate the read of a pass with a
+ /// binding in the descriptor set layout. Otherwise, the descriptor set layout would be messed up. 2) Because the
+ /// descriptor set layout is required when creating the graphics pipeline, we would have to associate pipelines with
+ /// passes somehow. However, we also decided to keep pipelines (instances of the GraphicsPipeline wrapper) out of
+ /// rendergraph. In summary, keeping VkDescriptorSet handles in rendergraph would complicate the API unnecessarily.
+ /// Rendergraph now manages resource descriptors as follows: Descriptors need a descriptor set layout, which is
+ /// created from DescriptorSetLayoutBuilder (the first function). Inside of OnBuildDescriptorSetLayout, with the
+ /// help of DescriptorSetLayoutBuilder, the programmer simply specifies which descriptors are inside of the
+ /// descriptor set. The descriptor set is then created by the builder. DescriptorSetLayoutBuilder uses
+ /// DescriptorSetLayoutCache internally to potentially speed up (re)creation of descriptor set layouts. The
+ /// VkDescriptorSetLayout created by DescriptorSetLayoutBuilder inside of on_build_descriptor_set_layout must be
+ /// stored externally, and it is the responsibility of the programmer to make sure the VkDescriptorSetLayout is
+ /// valid memory when it is used in the on_record function! The descriptor sets are allocated by rendergraph via
+ /// OnAllocateDescriptorSet functions using the DescriptorSetAllocator of the rendergraph. Descriptor sets are
+ /// updated in the OnUpdateDescriptorSet functions using the DescriptorSetUpdateBuilder of the rendergraph.
+ ///
+ /// TODO: Mention batching to vkUpdateDescriptorSets...
+ /// -----------------------------------------------------------------------------------------------------------------
+
+ /// The descriptor set layout builder (a builder pattern for descriptor set layouts)
+ DescriptorSetLayoutBuilder m_descriptor_set_layout_builder;
+ /// The descriptor set allocator
+ DescriptorSetAllocator m_descriptor_set_allocator;
+ /// The write descriptor set builder (a builder pattern for write descriptor sets)
+ WriteDescriptorSetBuilder m_write_descriptor_set_builder;
+
+ /// A user-defined function which creates the descriptor set layout
+ using OnBuildDescriptorSetLayout = std::function;
+ /// A user-defined function which allocates a descriptor set
+ using OnAllocateDescriptorSet = std::function;
+ /// A user-defined function which builds the descriptor set write for the pass
+ using OnBuildWriteDescriptorSets = std::function(WriteDescriptorSetBuilder &)>;
+
+ /// Resource descriptors are managed by specifying those three functions to the rendergraph
+ /// Rendergraph will then call those function in the correct order during rendergraph compilation
+ using ResourceDescriptor =
+ std::tuple;
+ /// The resource descriptors of the rendergraph
+ std::vector m_resource_descriptors;
+ /// All write descriptor sets will be stored in here so we can have one batched call to vkUpdateDescriptorSets
+ std::vector m_write_descriptor_sets;
+
+ /// Allocate the descriptor sets
+ void allocate_descriptor_sets();
+
+ /// The rendergraph must not have any cycles in it!
+ /// @exception std::logic_error The rendergraph is not acyclic!
+ void check_for_cycles();
+
+ /// Collect all image available semaphores of all swapchains which are used into one std::vector
+ void collect_swapchain_image_available_semaphores();
+
+ /// Create the descriptor set layouts
+ void create_descriptor_set_layouts();
+
+ /// Create the graphics pipelines
+ void create_graphics_pipelines();
+
+ /// Determine the order of execution of the graphics passes by using depth first search (DFS) algorithm
+ void determine_pass_order();
+
+ /// Fill the VkRenderingInfo for a graphics pass
+ /// @param pass The graphics pass
+ void fill_graphics_pass_rendering_info(GraphicsPass &pass);
+
+ /// Record the command buffer of a pass. After a lot of discussions about the API design of rendergraph, we came to
+ /// the conclusion that it's the full responsibility of the programmer to manually bind pipelines, descriptors sets,
+ /// and buffers inside of the on_record function instead of attempting to abstract all of this in rendergraph. This
+ /// means rendergraph will not automatically bind pipelines, buffers, or descriptor sets! The reason for this is
+ /// that there could be complex rendering going on inside of the on_record function with an arbitrary number of
+ /// pipelines descriptor sets, and buffers being bound in a nontrivial order or under conditional cases. We then
+ /// refrained from designing a simple API inside of rendergraph which automatically binds one graphics pipeline,
+ /// descriptor set, or a set of buffers at the beginning of rendering before calling on_record because it would
+ /// cause confusion about the correct API usage for the advanced use cases. Nonetheless, the creation of buffers,
+ /// descriptors, or pipelines is still the full responsibility of the rendergraph, but you need to use them manually
+ /// inside of the on_record function.
+ /// @param cmd_buf The command buffer to record the pass into
+ /// @param pass The graphics pass to record the command buffer for
+ void record_command_buffer_for_pass(const CommandBuffer &cmd_buf, GraphicsPass &pass);
+
+ /// Update the vertex-, index-, and uniform-buffers
+ /// @note If a uniform buffer has been updated, an update of the associated descriptor set will be performed
+ void update_buffers();
+
+ /// Update dynamic textures
+ void update_textures();
+
+ /// Update the write descriptor sets
+ /// @note This function must only be called once during rendergraph compilation, not for every frame!
+ void update_write_descriptor_sets();
+
+public:
+ /// Default constructor
+ /// @note device and swapchain are not taken as a const reference because rendergraph needs to modify both
+ /// @param device The device wrapper
+ RenderGraph(Device &device);
+
+ RenderGraph(const RenderGraph &) = delete;
+ // TODO: Implement me!
+ RenderGraph(RenderGraph &&) noexcept;
+ ~RenderGraph() = default;
+
+ RenderGraph &operator=(const RenderGraph &) = delete;
+ RenderGraph &operator=(RenderGraph &&) = delete;
+
+ /// Add a new graphics pass to the rendergraph
+ /// @aram pass The graphics pass that was created
+ /// @return A std::weak_ptr to the graphics pass that was added
+ [[nodiscard]] std::weak_ptr add_graphics_pass(std::shared_ptr pass);
+
+ /// Add a new graphics pipeline to the rendergraph
+ /// @param on_pipeline_create A function to create the graphics pipeline using GraphicsPipelineBuilder
+ /// @note Move semantics is used to std::move on_pipeline_create
+ void add_graphics_pipeline(OnCreateGraphicsPipeline on_pipeline_create);
+
+ /// Add an buffer to rendergraph
+ /// @param name The name of the buffer
+ /// @param type The type of the buffer
+ /// @param on_update The update function of the index buffer
+ /// @return A weak pointer to the buffer resource that was created
+ [[nodiscard]] std::weak_ptr add_buffer(std::string name, BufferType type, std::function