Program Listing for File VulkanRenderer.cpp

Program Listing for File VulkanRenderer.cpp#

Return to documentation for file (Src/GraphicsEngineVulkan/renderer/VulkanRenderer.cpp)

module;
#include "common/Utilities.hpp"
#include "hostDevice/host_device_shared_vars.hpp"
#include "renderer/pushConstants/PushConstantPost.hpp"
#include "renderer/pushConstants/PushConstantRasterizer.hpp"
#include "renderer/pushConstants/PushConstantRayTracing.hpp"
#include "spdlog/spdlog.h"

#include <cstdint>
#include <glm/ext/matrix_clip_space.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/trigonometric.hpp>
#include <limits>
#include <vulkan/vulkan.hpp>

#define GLFW_INCLUDE_NONE
#define GLFW_INCLUDE_VULKAN

#include <GLFW/glfw3.h>

#include <cstdio>
#include <cstdlib>

#include <algorithm>
#include <array>
#include <cstring>
#include <memory>
#include <tuple>
#include <vector>

#ifndef VMA_IMPLEMENTATION
#define VMA_IMPLEMENTATION
#endif// !VMA_IMPLEMENTATION
#include <vk_mem_alloc.h>

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

#include <imgui.h>
#include <imgui_internal.h>

#include "common/Globals.hpp"
#include "renderer/SceneUBO.hpp"

module kataglyphis.vulkan.renderer;

import kataglyphis.vulkan.device;
import kataglyphis.vulkan.gui_renderer_shared_vars;
import kataglyphis.vulkan.gui_scene_shared_vars;
import kataglyphis.vulkan.object_description;
import kataglyphis.vulkan.queue_family_indices;
import kataglyphis.vulkan.debug;
import kataglyphis.vulkan.scene;
import kataglyphis.vulkan.scene_config;
import kataglyphis.vulkan.texture;
import kataglyphis.vulkan.as_manager;
import kataglyphis.vulkan.buffer_manager;
import kataglyphis.vulkan.buffer;
import kataglyphis.vulkan.camera;
import kataglyphis.vulkan.command_buffer_manager;
import kataglyphis.vulkan.instance;
import kataglyphis.vulkan.gui;
import kataglyphis.vulkan.scene_ubo;
import kataglyphis.vulkan.global_ubo;
import kataglyphis.vulkan.swapchain;
import kataglyphis.vulkan.allocator;
import kataglyphis.vulkan.window;

namespace {
[[maybe_unused]] vk::Result toVkResult(VkResult result) { return static_cast<vk::Result>(result); }
}// namespace

Kataglyphis::VulkanRenderer::VulkanRenderer(Kataglyphis::Frontend::Window *window,
  Scene *scene,
  Kataglyphis::Frontend::GUI *gui,
  Camera *camera)
  : window(window), scene(scene), gui(gui), camera(camera)
{
    instance = VulkanInstance();

    vk::DebugReportFlagsEXT const debugReportFlags =
      vk::DebugReportFlagBitsEXT::eError | vk::DebugReportFlagBitsEXT::eWarning;
    if (Kataglyphis::ENABLE_VALIDATION_LAYERS) {
        debug::setupDebugging(instance.getVulkanInstance(), debugReportFlags, nullptr);
    }

    create_surface();

    device = std::make_shared<VulkanDevice>(&instance, &surface);

    allocator = Allocator(device->getLogicalDevice(), device->getPhysicalDevice(), instance.getVulkanInstance());

    create_command_pool();

    vulkanSwapChain.initVulkanContext(device, window, surface);
    create_uniform_buffers();
    create_command_buffers();

    createSynchronization();

    initDescriptorResources();

    std::vector<vk::DescriptorSetLayout> const descriptor_set_layouts_rasterizer = { sharedRenderDescriptorSetLayout };
    std::vector<vk::DescriptorSetLayout> const descriptor_set_layouts_deferred = { sharedRenderDescriptorSetLayout, gbuffer_descriptor_set_layout };

    rasterizer.init(device, &vulkanSwapChain, descriptor_set_layouts_rasterizer, graphics_command_pool);
    deferredRasterizer.init(device, &vulkanSwapChain, descriptor_set_layouts_deferred, graphics_command_pool);

    clouds.init(device, graphics_command_pool, sharedRenderDescriptorSetLayout, vulkanSwapChain.getSwapChainExtent().width, vulkanSwapChain.getSwapChainExtent().height);
    dirShadowMap.init(device, 2048, 2048, MAX_CASCADES);
    dirShadowMap.createGraphicsPipeline();
    pointShadowMap.init(device, 1024, 1024);

    std::vector<vk::DescriptorSetLayout> const descriptor_set_layouts_post = { post_descriptor_set_layout };
    postStage.init(device, &vulkanSwapChain, descriptor_set_layouts_post);

    if (device->supportsHardwareAcceleratedRRT()) {
        createRaytracingDescriptorPool();
        createRaytracingDescriptorSetLayouts();
        createRaytracingDescriptorSets();

        std::vector<vk::DescriptorSetLayout> const layouts = { sharedRenderDescriptorSetLayout,
            raytracingDescriptorSetLayout };
        raytracingStage.init(device, layouts, &vulkanSwapChain);
        pathTracing.init(device, layouts);
    }

    updateUniforms(scene, camera, window);
    updateAllDescriptorSets();

    std::vector<vk::ImageView> skyboxImageViews(vulkanSwapChain.getNumberSwapChainImages());
    std::vector<vk::ImageView> skyboxDepthViews(vulkanSwapChain.getNumberSwapChainImages());
    for (uint32_t i = 0; i < vulkanSwapChain.getNumberSwapChainImages(); i++) {
        skyboxImageViews[i] = vulkanSwapChain.getSwapChainImage(i).getImageView();
        skyboxDepthViews[i] = postStage.getDepthBufferImageView();
    }

    skyBox.init(device, graphics_command_pool);
    skyBox.createRenderPass(vulkanSwapChain.getSwapChainFormat(), postStage.getDepthFormat());
    skyBox.createGraphicsPipeline(sharedRenderDescriptorSetLayout);
    skyBox.createFramebuffers(vulkanSwapChain.getNumberSwapChainImages(), skyboxImageViews, skyboxDepthViews,
        vulkanSwapChain.getSwapChainExtent().width, vulkanSwapChain.getSwapChainExtent().height);

    scene->loadModel(device, graphics_command_pool);

    if (device->supportsHardwareAcceleratedRRT()) {
        asManager.createASForScene(device, graphics_command_pool, scene);
    }

    create_object_description_buffer();

    // Final update after model loading
    updateAllDescriptorSets();

    gui->initializeVulkanContext(device,
      instance.getVulkanInstance(),
      postStage.getRenderPass(),
      graphics_command_pool,
      vulkanSwapChain.getNumberSwapChainImages());
    gui->setUserSelectionForRRT(device->supportsHardwareAcceleratedRRT());
}

void Kataglyphis::VulkanRenderer::updateUniforms(Scene *scene_data,
  Camera *camera_data,
  [[maybe_unused]] Kataglyphis::Frontend::Window *window_data)
{
    const GUISceneSharedVars guiSceneSharedVars = scene_data->getGuiSceneSharedVars();

    const vk::Extent2D extent = vulkanSwapChain.getSwapChainExtent();
    float const aspect_ratio = (extent.height > 0) ? static_cast<float>(extent.width) / static_cast<float>(extent.height) : 1.0f;

    globalUBO.view = camera_data->calculate_viewmatrix();
    globalUBO.projection = glm::perspective(glm::radians(camera_data->get_fov()),
      aspect_ratio,
      camera_data->get_near_plane(),
      camera_data->get_far_plane());
    globalUBO.projection[1][1] *= -1;

    sceneUBO.view_dir = glm::vec4(camera_data->get_camera_direction().x, camera_data->get_camera_direction().y, camera_data->get_camera_direction().z, 1.0F);

    sceneUBO.dirLight.direction = glm::vec4(guiSceneSharedVars.directional_light_direction[0],
      guiSceneSharedVars.directional_light_direction[1],
      guiSceneSharedVars.directional_light_direction[2],
      1.0F);

    sceneUBO.dirLight.color = glm::vec4(guiSceneSharedVars.directional_light_color[0],
      guiSceneSharedVars.directional_light_color[1],
      guiSceneSharedVars.directional_light_color[2],
      guiSceneSharedVars.direcional_light_radiance);

    sceneUBO.cam_pos = glm::vec4(camera_data->get_camera_position().x, camera_data->get_camera_position().y, camera_data->get_camera_position().z, camera_data->get_fov());

    // Populate GUI state into SceneUBO
    sceneUBO.pcfRadius = static_cast<unsigned int>(guiSceneSharedVars.pcf_radius);
    sceneUBO.cascadedShadowIntensity = guiSceneSharedVars.cascaded_shadow_intensity;

    // Calculate CSM cascades
    dirShadowMap.updateCascades(globalUBO.view, camera_data->get_fov(),
        aspect_ratio,
        camera_data->get_near_plane(), camera_data->get_far_plane(),
        glm::vec3(sceneUBO.dirLight.direction));

    const auto& cascadeData = dirShadowMap.getCascadeData();
    for (size_t i = 0; i < std::min(cascadeData.size(), static_cast<size_t>(MAX_CASCADES)); ++i) {
        sceneUBO.cascadeSplits[static_cast<int>(i)] = cascadeData[i].splitDepth;
        sceneUBO.cascadeLightSpaceMatrices[i] = cascadeData[i].viewProjMatrix;
    }

    sceneUBO.cloudMovementDirection = glm::vec4(
        guiSceneSharedVars.cloud_movement_direction[0],
        guiSceneSharedVars.cloud_movement_direction[1],
        guiSceneSharedVars.cloud_movement_direction[2],
        static_cast<float>(guiSceneSharedVars.cloud_speed));

    sceneUBO.cloudMeshScale = glm::vec4(
        guiSceneSharedVars.cloud_mesh_scale[0],
        guiSceneSharedVars.cloud_mesh_scale[1],
        guiSceneSharedVars.cloud_mesh_scale[2],
        guiSceneSharedVars.cloud_scale);

    sceneUBO.cloudMeshOffset = glm::vec4(
        guiSceneSharedVars.cloud_mesh_offset[0],
        guiSceneSharedVars.cloud_mesh_offset[1],
        guiSceneSharedVars.cloud_mesh_offset[2],
        guiSceneSharedVars.cloud_density);

    sceneUBO.cloudParameters = glm::vec4(
        guiSceneSharedVars.cloud_pillowness,
        guiSceneSharedVars.cloud_cirrus_effect,
        guiSceneSharedVars.cloud_powder_effect ? 1.0f : 0.0f,
        static_cast<float>(guiSceneSharedVars.cloud_num_march_steps));
}

void Kataglyphis::VulkanRenderer::updateStateDueToUserInput(Kataglyphis::Frontend::GUI *frontend_gui)
{
    Kataglyphis::VulkanRendererInternals::FrontendShared::GUIRendererSharedVars &guiRendererSharedVars =
      frontend_gui->getGuiRendererSharedVars();

    if (guiRendererSharedVars.shader_hot_reload_triggered) {
        shaderHotReload();
        guiRendererSharedVars.shader_hot_reload_triggered = false;
    }

    GUISceneSharedVars &guiSceneSharedVars = scene->getGuiSceneSharedVars();
    if (guiSceneSharedVars.shadow_resolution_changed) {
        guiSceneSharedVars.shadow_resolution_changed = false;

        (void)device->getLogicalDevice().waitIdle();
        dirShadowMap.cleanUp();

        uint32_t shadow_res = 512;
        if (guiSceneSharedVars.shadow_map_res_index == 1) shadow_res = 1024;
        else if (guiSceneSharedVars.shadow_map_res_index == 2) shadow_res = 2048;
        else if (guiSceneSharedVars.shadow_map_res_index == 3) shadow_res = 4096;

        dirShadowMap.init(device, shadow_res, shadow_res, static_cast<uint32_t>(guiSceneSharedVars.num_shadow_cascades));

        // We must recreate descriptor sets that depend on the shadow map
        updateTexturesInSharedRenderDescriptorSet();
    }

    if (guiSceneSharedVars.model_transform_changed) {
        guiSceneSharedVars.model_transform_changed = false;
        frontend_gui->getGuiSceneSharedVars().model_transform_changed = false;

        glm::mat4 modelMatrix = glm::mat4(1.0f);
        modelMatrix = glm::scale(modelMatrix, glm::vec3(60.0f, 60.0f, 60.0f)); // Apply original scale

        // Apply world position directly to the matrix's translation column
        modelMatrix[3] = glm::vec4(guiSceneSharedVars.model_position[0],
                                   guiSceneSharedVars.model_position[1],
                                   guiSceneSharedVars.model_position[2],
                                   1.0f);

        // ZYX rotation order
        modelMatrix = glm::rotate(modelMatrix, glm::radians(guiSceneSharedVars.model_rotation[2]), glm::vec3(0.0f, 0.0f, 1.0f));
        modelMatrix = glm::rotate(modelMatrix, glm::radians(guiSceneSharedVars.model_rotation[1]), glm::vec3(0.0f, 1.0f, 0.0f));
        modelMatrix = glm::rotate(modelMatrix, glm::radians(guiSceneSharedVars.model_rotation[0]), glm::vec3(1.0f, 0.0f, 0.0f));

        if (guiSceneSharedVars.selected_model_index >= 0) {
            scene->update_model_matrix(modelMatrix, 0);

            // Re-upload object descriptions as the transform changed
            (void)device->getLogicalDevice().waitIdle();
            objectDescriptionBuffer.cleanUp();
            create_object_description_buffer();
            updateAllDescriptorSets();
        }


    }

    if (guiSceneSharedVars.model_reload_requested) {
        guiSceneSharedVars.model_reload_requested = false;

        const auto model_paths = sceneConfig::getAvailableModelPaths();
        const int sel = guiSceneSharedVars.selected_model_index;
        if (sel >= 0 && sel < static_cast<int>(model_paths.size())) {
            const std::string selected_path = model_paths[static_cast<size_t>(sel)];
            const std::string resolved_path = sceneConfig::resolveModelPath(selected_path);

            (void)device->getLogicalDevice().waitIdle();

            scene->reloadModel(device, graphics_command_pool, resolved_path);

            if (device->supportsHardwareAcceleratedRRT()) {
                asManager.createASForScene(device, graphics_command_pool, scene);
            }

            objectDescriptionBuffer.cleanUp();
            create_object_description_buffer();

            updateTexturesInSharedRenderDescriptorSet();
        }
    }
}

void Kataglyphis::VulkanRenderer::finishAllRenderCommands() { std::ignore = device->getLogicalDevice().waitIdle(); }

void Kataglyphis::VulkanRenderer::shaderHotReload()
{
    std::ignore = device->getLogicalDevice().waitIdle();

    std::vector<vk::DescriptorSetLayout> const descriptor_set_layouts = { sharedRenderDescriptorSetLayout };
    rasterizer.shaderHotReload(descriptor_set_layouts);

    std::vector<vk::DescriptorSetLayout> const descriptor_set_layouts_post = { post_descriptor_set_layout };
    postStage.shaderHotReload(descriptor_set_layouts_post);

    if (device->supportsHardwareAcceleratedRRT()) {
        std::vector<vk::DescriptorSetLayout> const layouts = { sharedRenderDescriptorSetLayout,
            raytracingDescriptorSetLayout };
        raytracingStage.shaderHotReload(layouts);
        pathTracing.shaderHotReload(layouts);
    }
}

void Kataglyphis::VulkanRenderer::drawFrame()
{
    const auto end_imgui_frame_if_needed = []() -> void {
        ImGuiContext const *imgui_context = ImGui::GetCurrentContext();
        if (imgui_context != nullptr && imgui_context->WithinFrameScope) { ImGui::EndFrame(); }
    };

    const auto abort_frame_with_fatal_error = [&](const char *message, vk::Result error_code) -> void {
        spdlog::error(fmt::format("{} (vk::Result={})", message, static_cast<int>(error_code)));
        if (error_code == vk::Result::eErrorDeviceLost) { device_lost_detected = true; }
        if (window != nullptr && window->get_window() != nullptr) {
            glfwSetWindowShouldClose(window->get_window(), GLFW_TRUE);
        }
        end_imgui_frame_if_needed();
    };

    if (frame_sync_count == 0) {
        spdlog::error("No synchronization frames available; skipping draw frame.");
        end_imgui_frame_if_needed();
        return;
    }

    if (checkChangedFramebufferSize()) {
        if (frame_sync_count > 0 && !in_flight_fences.empty()) {
            recreateSwapChain();
        }
    }

    if (current_frame >= in_flight_fences.size() || current_frame >= image_available.size()) {
        spdlog::error(fmt::format("Frame synchronization index out of range: {}", current_frame));
        end_imgui_frame_if_needed();
        return;
    }

    if (!in_flight_fences[current_frame] || !image_available[current_frame]) {
        spdlog::error(fmt::format("Synchronization handles are invalid for frame {}.", current_frame));
        if (window != nullptr && window->get_window() != nullptr) {
            glfwSetWindowShouldClose(window->get_window(), GLFW_TRUE);
        }
        end_imgui_frame_if_needed();
        return;
    }

    vk::Result result = device->getLogicalDevice().waitForFences(
      1, &in_flight_fences[current_frame], VK_TRUE, std::numeric_limits<uint64_t>::max());
    if (result != vk::Result::eSuccess) {
        abort_frame_with_fatal_error("Failed to wait for fences!", result);
        return;
    }

    uint32_t image_index = 0;
    std::tie(result, image_index) = device->getLogicalDevice().acquireNextImageKHR(
      vulkanSwapChain.getSwapChain(), std::numeric_limits<uint64_t>::max(), image_available[current_frame], nullptr);

    if (result == vk::Result::eErrorOutOfDateKHR) {
        end_imgui_frame_if_needed();
        recreateSwapChain();
        return;
    }

    if (result == vk::Result::eSuboptimalKHR) {
        recreateSwapChain();
        return;
    }

    if (result != vk::Result::eSuccess) {
        abort_frame_with_fatal_error("Failed to acquire next image!", result);
        return;
    }

    if (image_index >= images_in_flight_fences.size() || image_index >= command_buffers.size()) {
        spdlog::error(fmt::format("Swapchain image index out of range: {}", image_index));
        end_imgui_frame_if_needed();
        return;
    }

    if (image_index >= render_finished_by_image.size() || !render_finished_by_image[image_index]) {
        spdlog::error(fmt::format("Render-finished semaphore missing for swapchain image {}.", image_index));
        end_imgui_frame_if_needed();
        return;
    }

    if (images_in_flight_fences[image_index]) {
        result =
          device->getLogicalDevice().waitForFences(1, &images_in_flight_fences[image_index], VK_TRUE, UINT64_MAX);
        if (result != vk::Result::eSuccess) {
            abort_frame_with_fatal_error("Failed to wait for image in-flight fence!", result);
            return;
        }
    }

    images_in_flight_fences[image_index] = in_flight_fences[current_frame];

    result = command_buffers[image_index].reset(vk::CommandBufferResetFlags{});
    if (result != vk::Result::eSuccess) {
        abort_frame_with_fatal_error("Failed to reset command buffer!", result);
        return;
    }

    vk::CommandBufferBeginInfo buffer_begin_info{};
    buffer_begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
    result = command_buffers[image_index].begin(&buffer_begin_info);
    if (result != vk::Result::eSuccess) {
        abort_frame_with_fatal_error("Failed to start recording a command buffer!", result);
        return;
    }

    update_uniform_buffers(image_index);

    Kataglyphis::VulkanRendererInternals::FrontendShared::GUIRendererSharedVars const &guiRendererSharedVars =
      gui->getGuiRendererSharedVars();
    const bool raytracing_available = device->supportsHardwareAcceleratedRRT();
    const char *const render_mode =
      (!raytracing_available || (!guiRendererSharedVars.raytracing && !guiRendererSharedVars.pathTracing))
        ? "rasterizer"
        : (guiRendererSharedVars.raytracing ? "raytracing" : "path_tracing");
    if (raytracing_available && guiRendererSharedVars.raytracing) { update_raytracing_descriptor_set(image_index); }

    if (!record_commands(image_index)) {
        end_imgui_frame_if_needed();
        return;
    }

    result = command_buffers[image_index].end();
    if (result != vk::Result::eSuccess) {
        abort_frame_with_fatal_error("Failed to stop recording a command buffer!", result);
        return;
    }

    vk::SubmitInfo submit_info{};
    submit_info.waitSemaphoreCount = 1;
    submit_info.pWaitSemaphores = &image_available[current_frame];

    vk::PipelineStageFlags const wait_stages = { vk::PipelineStageFlagBits::eColorAttachmentOutput };

    submit_info.pWaitDstStageMask = &wait_stages;

    submit_info.commandBufferCount = 1;
    submit_info.pCommandBuffers = &command_buffers[image_index];
    submit_info.signalSemaphoreCount = 1;
    submit_info.pSignalSemaphores = &render_finished_by_image[image_index];

    result = device->getLogicalDevice().resetFences(1, &in_flight_fences[current_frame]);
    if (result != vk::Result::eSuccess) {
        abort_frame_with_fatal_error("Failed to reset fences!", result);
        return;
    }

    result = device->getGraphicsQueue().submit(1, &submit_info, in_flight_fences[current_frame]);
    if (result != vk::Result::eSuccess) {
        spdlog::error(
          fmt::format("Queue submit context: frame={}, imageIndex={}, renderMode={}, supportsRRT={}, cmdBufferIndex={}",
            current_frame,
            image_index,
            render_mode,
            raytracing_available,
            image_index));
        abort_frame_with_fatal_error("Failed to submit command buffer to queue!", result);
        return;
    }

    vk::PresentInfoKHR present_info{};
    present_info.waitSemaphoreCount = 1;
    present_info.pWaitSemaphores = &render_finished_by_image[image_index];
    present_info.swapchainCount = 1;
    const vk::SwapchainKHR swapchain = vulkanSwapChain.getSwapChain();
    present_info.pSwapchains = &swapchain;
    present_info.pImageIndices = &image_index;

    result = device->getPresentationQueue().presentKHR(&present_info);

    if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR) {
        recreateSwapChain();
    } else if (result != vk::Result::eSuccess) {
        abort_frame_with_fatal_error("Failed to present image!", result);
        return;
    }

    current_frame = (current_frame + 1) % frame_sync_count;
}

bool Kataglyphis::VulkanRenderer::checkChangedFramebufferSize()
{
    if (window == nullptr) { return false; }

    if (window->framebuffer_size_has_changed()) {
        window->reset_framebuffer_has_changed();
        return true;
    }

    return false;
}

void Kataglyphis::VulkanRenderer::recreateSwapChain()
{
    int width = 0, height = 0;
    glfwGetFramebufferSize(window->get_window(), &width, &height);
    while (width == 0 || height == 0) {
        glfwGetFramebufferSize(window->get_window(), &width, &height);
        glfwWaitEvents();
    }

    std::ignore = device->getLogicalDevice().waitIdle();

    uint32_t oldImageCount = vulkanSwapChain.getNumberSwapChainImages();

    // Destroy framebuffers that reference swapchain image views
    // before recreating the swapchain
    postStage.destroyFramebuffers();
    rasterizer.destroyFramebuffers();
    deferredRasterizer.destroyFramebuffers();
    skyBox.destroyFramebuffers();

    vulkanSwapChain.recreate(device, surface);

    uint32_t newImageCount = vulkanSwapChain.getNumberSwapChainImages();

    // Recreate depth buffers and framebuffers with new swapchain
    postStage.recreateFrameResources();
    rasterizer.recreateFrameResources(graphics_command_pool);
    deferredRasterizer.recreateFrameResources(graphics_command_pool);
    clouds.recreateFrameResources(graphics_command_pool, vulkanSwapChain.getSwapChainExtent().width, vulkanSwapChain.getSwapChainExtent().height);

    std::vector<vk::ImageView> skyboxImageViews(vulkanSwapChain.getNumberSwapChainImages());
    std::vector<vk::ImageView> skyboxDepthViews(vulkanSwapChain.getNumberSwapChainImages());
    for (uint32_t i = 0; i < vulkanSwapChain.getNumberSwapChainImages(); i++) {
        skyboxImageViews[i] = vulkanSwapChain.getSwapChainImage(i).getImageView();
        skyboxDepthViews[i] = postStage.getDepthBufferImageView();
    }

    skyBox.recreateFrameResources(vulkanSwapChain.getNumberSwapChainImages(), skyboxImageViews, skyboxDepthViews,
        vulkanSwapChain.getSwapChainExtent().width, vulkanSwapChain.getSwapChainExtent().height);

    // If the image count changed, we must recreate descriptor pools and sets too
    if (newImageCount != oldImageCount) {
        cleanUpDescriptorResources();
        initDescriptorResources();
    }

    updateAllDescriptorSets();

    create_command_buffers();
    createSynchronization();
}

void Kataglyphis::VulkanRenderer::update_uniform_buffers(uint32_t image_index)
{
    if (image_index >= globalUBOMapped.size() || image_index >= sceneUBOMapped.size()) {
        spdlog::error(fmt::format("Uniform buffer index out of range: {}", image_index));
        return;
    }

    std::memcpy(globalUBOMapped[image_index], &globalUBO, sizeof(VulkanRendererInternals::GlobalUBO));
    std::memcpy(sceneUBOMapped[image_index], &sceneUBO, sizeof(VulkanRendererInternals::SceneUBO));
}

void Kataglyphis::VulkanRenderer::updateUBODescriptorSets()
{
    for (size_t i = 0; i < vulkanSwapChain.getNumberSwapChainImages(); i++) {
        vk::DescriptorBufferInfo globalUBO_buffer_info{};
        globalUBO_buffer_info.buffer = globalUBOBuffer[i].getBuffer();
        globalUBO_buffer_info.offset = 0;
        globalUBO_buffer_info.range = sizeof(globalUBO);

        vk::WriteDescriptorSet globalUBO_set_write{};
        globalUBO_set_write.dstSet = sharedRenderDescriptorSet[i];
        globalUBO_set_write.dstBinding = 0;
        globalUBO_set_write.dstArrayElement = 0;
        globalUBO_set_write.descriptorType = vk::DescriptorType::eUniformBuffer;
        globalUBO_set_write.descriptorCount = 1;
        globalUBO_set_write.pBufferInfo = &globalUBO_buffer_info;

        vk::DescriptorBufferInfo sceneUBO_buffer_info{};
        sceneUBO_buffer_info.buffer = sceneUBOBuffer[i].getBuffer();
        sceneUBO_buffer_info.offset = 0;
        sceneUBO_buffer_info.range = sizeof(sceneUBO);

        vk::WriteDescriptorSet sceneUBO_set_write{};
        sceneUBO_set_write.dstSet = sharedRenderDescriptorSet[i];
        sceneUBO_set_write.dstBinding = 1;
        sceneUBO_set_write.dstArrayElement = 0;
        sceneUBO_set_write.descriptorType = vk::DescriptorType::eUniformBuffer;
        sceneUBO_set_write.descriptorCount = 1;
        sceneUBO_set_write.pBufferInfo = &sceneUBO_buffer_info;

        std::vector<vk::WriteDescriptorSet> write_descriptor_sets = { globalUBO_set_write, sceneUBO_set_write };

        device->getLogicalDevice().updateDescriptorSets(
          static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
    }
}

void Kataglyphis::VulkanRenderer::updateAllDescriptorSets()
{
    updateUBODescriptorSets();
    updatePostDescriptorSets();
    updateGBufferDescriptorSets();
    updateTexturesInSharedRenderDescriptorSet();
    if (device->supportsHardwareAcceleratedRRT()) {
        updateRaytracingDescriptorSets();
    }
}

void Kataglyphis::VulkanRenderer::cleanUpDescriptorResources()
{
    if (descriptorPoolSharedRenderStages) {
        device->getLogicalDevice().destroyDescriptorPool(descriptorPoolSharedRenderStages);
        descriptorPoolSharedRenderStages = nullptr;
    }
    if (post_descriptor_pool) {
        device->getLogicalDevice().destroyDescriptorPool(post_descriptor_pool);
        post_descriptor_pool = nullptr;
    }
    if (gbuffer_descriptor_pool) {
        device->getLogicalDevice().destroyDescriptorPool(gbuffer_descriptor_pool);
        gbuffer_descriptor_pool = nullptr;
    }
    if (post_descriptor_set_layout) {
        device->getLogicalDevice().destroyDescriptorSetLayout(post_descriptor_set_layout);
        post_descriptor_set_layout = nullptr;
    }
    if (gbuffer_descriptor_set_layout) {
        device->getLogicalDevice().destroyDescriptorSetLayout(gbuffer_descriptor_set_layout);
        gbuffer_descriptor_set_layout = nullptr;
    }
    if (sharedRenderDescriptorSetLayout) {
        device->getLogicalDevice().destroyDescriptorSetLayout(sharedRenderDescriptorSetLayout);
        sharedRenderDescriptorSetLayout = nullptr;
    }

    sharedRenderDescriptorSet.clear();
    post_descriptor_set.clear();
    gbuffer_descriptor_set.clear();
}

void Kataglyphis::VulkanRenderer::initDescriptorResources()
{
    createSharedRenderDescriptorSetLayouts();
    createDescriptorPoolSharedRenderStages();
    createSharedRenderDescriptorSet();
    create_post_descriptor_layout();
    create_gbuffer_descriptor_layout();
}

void Kataglyphis::VulkanRenderer::update_raytracing_descriptor_set(uint32_t image_index)
{
    if (image_index >= raytracingDescriptorSet.size()) {
        spdlog::error(fmt::format("Raytracing descriptor set index out of range: {}", image_index));
        return;
    }

    vk::WriteDescriptorSetAccelerationStructureKHR descriptor_set_acceleration_structure{};
    descriptor_set_acceleration_structure.accelerationStructureCount = 1;
    vk::AccelerationStructureKHR &vulkanTLAS = asManager.getTLAS();
    if (!vulkanTLAS) {
        return;
    }
    descriptor_set_acceleration_structure.pAccelerationStructures = &vulkanTLAS;

    vk::WriteDescriptorSet write_descriptor_set_acceleration_structure{};
    write_descriptor_set_acceleration_structure.pNext = &descriptor_set_acceleration_structure;
    write_descriptor_set_acceleration_structure.dstSet = raytracingDescriptorSet[image_index];
    write_descriptor_set_acceleration_structure.dstBinding = TLAS_BINDING;
    write_descriptor_set_acceleration_structure.dstArrayElement = 0;
    write_descriptor_set_acceleration_structure.descriptorCount = 1;
    write_descriptor_set_acceleration_structure.descriptorType = vk::DescriptorType::eAccelerationStructureKHR;

    vk::DescriptorImageInfo image_info{};
    Kataglyphis::VulkanRendererInternals::FrontendShared::GUIRendererSharedVars const &guiRendererSharedVars =
      gui->getGuiRendererSharedVars();
    Texture &renderResult = guiRendererSharedVars.rasterizationMode == Kataglyphis::VulkanRendererInternals::FrontendShared::RasterizationMode::Forward ? rasterizer.getOffscreenTexture(image_index) : deferredRasterizer.getOffscreenTexture(image_index);
    image_info.imageView = renderResult.getImageView();
    image_info.imageLayout = vk::ImageLayout::eGeneral;

    vk::WriteDescriptorSet descriptor_image_writer{};
    descriptor_image_writer.dstSet = raytracingDescriptorSet[image_index];
    descriptor_image_writer.dstBinding = OUT_IMAGE_BINDING;
    descriptor_image_writer.dstArrayElement = 0;
    descriptor_image_writer.descriptorCount = 1;
    descriptor_image_writer.descriptorType = vk::DescriptorType::eStorageImage;
    descriptor_image_writer.pImageInfo = &image_info;

    std::vector<vk::WriteDescriptorSet> write_descriptor_sets = { write_descriptor_set_acceleration_structure,
        descriptor_image_writer };

    device->getLogicalDevice().updateDescriptorSets(write_descriptor_sets, {});
}

bool Kataglyphis::VulkanRenderer::record_commands(uint32_t image_index)
{
    if (image_index >= command_buffers.size() || image_index >= sharedRenderDescriptorSet.size()
        || image_index >= post_descriptor_set.size()) {
        spdlog::error(fmt::format("Command recording index out of range: {}", image_index));
        return false;
    }

    Kataglyphis::VulkanRendererInternals::FrontendShared::GUIRendererSharedVars const &guiRendererSharedVars =
      gui->getGuiRendererSharedVars();

    GUISceneSharedVars &guiSceneSharedVars = scene->getGuiSceneSharedVars();

    vk::CommandBuffer &commandBuffer = command_buffers[image_index];

    std::vector<vk::DescriptorSet> rasterizer_descriptor_sets = { sharedRenderDescriptorSet[image_index] };

    if (guiSceneSharedVars.clouds_enabled) {
        clouds.recordComputeCommands(commandBuffer, image_index, rasterizer_descriptor_sets);
    }

    if (guiSceneSharedVars.shadows_enabled) {
        dirShadowMap.recordCommands(commandBuffer, image_index, scene, rasterizer_descriptor_sets);
    }

    if (guiRendererSharedVars.rasterizationMode == Kataglyphis::VulkanRendererInternals::FrontendShared::RasterizationMode::Forward) {
        rasterizer.recordCommands(commandBuffer, image_index, scene, rasterizer_descriptor_sets);
    } else {
        std::vector<vk::DescriptorSet> deferred_sets = { sharedRenderDescriptorSet[image_index], gbuffer_descriptor_set[image_index] };
        deferredRasterizer.recordCommands(commandBuffer, image_index, scene, deferred_sets);
    }

    if (device->supportsHardwareAcceleratedRRT() && image_index < raytracingDescriptorSet.size()) {
        std::vector<vk::DescriptorSet> raytracing_descriptor_sets = { sharedRenderDescriptorSet[image_index],
            raytracingDescriptorSet[image_index] };

        if (guiRendererSharedVars.raytracing) {
            Texture &renderResult = guiRendererSharedVars.rasterizationMode == Kataglyphis::VulkanRendererInternals::FrontendShared::RasterizationMode::Forward ? rasterizer.getOffscreenTexture(image_index) : deferredRasterizer.getOffscreenTexture(image_index);
            raytracingStage.recordCommands(
              commandBuffer, renderResult.getVulkanImage(), &vulkanSwapChain, raytracing_descriptor_sets);
        } else if (guiRendererSharedVars.pathTracing) {
            Texture &renderResult = guiRendererSharedVars.rasterizationMode == Kataglyphis::VulkanRendererInternals::FrontendShared::RasterizationMode::Forward ? rasterizer.getOffscreenTexture(image_index) : deferredRasterizer.getOffscreenTexture(image_index);
            pathTracing.recordCommands(
              commandBuffer, image_index, renderResult.getVulkanImage(), &vulkanSwapChain, raytracing_descriptor_sets);
        }
    }

    skyBox.recordCommands(commandBuffer, image_index, rasterizer_descriptor_sets, guiSceneSharedVars.skybox_enabled);

    vk::ImageMemoryBarrier colorBarrier{};
    colorBarrier.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
    colorBarrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead;
    colorBarrier.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
    colorBarrier.newLayout = vk::ImageLayout::eColorAttachmentOptimal;
    colorBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    colorBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    colorBarrier.image = vulkanSwapChain.getSwapChainImage(image_index).getImage();
    colorBarrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
    colorBarrier.subresourceRange.baseMipLevel = 0;
    colorBarrier.subresourceRange.levelCount = 1;
    colorBarrier.subresourceRange.baseArrayLayer = 0;
    colorBarrier.subresourceRange.layerCount = 1;
    commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::DependencyFlags{}, {}, {}, colorBarrier);

    std::vector<vk::DescriptorSet> post_descriptor_sets = { post_descriptor_set[image_index] };
    postStage.recordCommands(commandBuffer, image_index, post_descriptor_sets, guiSceneSharedVars.clouds_enabled, guiSceneSharedVars.shadows_enabled, guiSceneSharedVars.skybox_enabled);

    return true;
}

void Kataglyphis::VulkanRenderer::cleanUpUBOs()
{
    for (size_t i = 0; i < globalUBOBuffer.size(); i++) {
        device->getLogicalDevice().unmapMemory(globalUBOBuffer[i].getBufferMemory());
        globalUBOBuffer[i].cleanUp();
    }
    for (size_t i = 0; i < sceneUBOBuffer.size(); i++) {
        device->getLogicalDevice().unmapMemory(sceneUBOBuffer[i].getBufferMemory());
        sceneUBOBuffer[i].cleanUp();
    }
    globalUBOBuffer.clear();
    globalUBOMapped.clear();
    sceneUBOBuffer.clear();
    sceneUBOMapped.clear();
}

void Kataglyphis::VulkanRenderer::cleanUp()
{
    if (!device) { return; }

    std::ignore = device->getLogicalDevice().waitIdle();

    if (device->supportsHardwareAcceleratedRRT()) {
        pathTracing.cleanUp();
        raytracingStage.cleanUp();
        asManager.cleanUp();
    }

    rasterizer.cleanUp();
    deferredRasterizer.cleanUp();
    skyBox.cleanUp();
    clouds.cleanUp();
    dirShadowMap.cleanUp();
    pointShadowMap.cleanUp();
    postStage.cleanUp();

    objectDescriptionBuffer.cleanUp();

    cleanUpSync();
    cleanUpUBOs();
    cleanUpCommandPools();
    cleanUpDescriptorResources();

    if (raytracingDescriptorPool) {
        device->getLogicalDevice().destroyDescriptorPool(raytracingDescriptorPool);
        raytracingDescriptorPool = nullptr;
    }
    if (raytracingDescriptorSetLayout) {
        device->getLogicalDevice().destroyDescriptorSetLayout(raytracingDescriptorSetLayout);
        raytracingDescriptorSetLayout = nullptr;
    }

    vulkanSwapChain.cleanUp();
    allocator.cleanUp();
    device->cleanUp();
    device.reset();

    if (surface) {
        instance.getVulkanInstance().destroySurfaceKHR(surface);
        surface = nullptr;
    }

    if (Kataglyphis::ENABLE_VALIDATION_LAYERS) { debug::freeDebugCallback(instance.getVulkanInstance()); }
    instance.cleanUp();
}

Kataglyphis::VulkanRenderer::~VulkanRenderer() { cleanUp(); }

void Kataglyphis::VulkanRenderer::create_surface()
{
    VkSurfaceKHR rawSurface = VK_NULL_HANDLE;
    ASSERT_VULKAN(glfwCreateWindowSurface(instance.getVulkanInstance(), window->get_window(), nullptr, &rawSurface),
      "Failed to create a surface!");
    surface = vk::SurfaceKHR(rawSurface);
}

void Kataglyphis::VulkanRenderer::create_post_descriptor_layout()
{
    vk::DescriptorSetLayoutBinding post_sampler_layout_binding{};
    post_sampler_layout_binding.binding = 0;
    post_sampler_layout_binding.descriptorType = vk::DescriptorType::eCombinedImageSampler;
    post_sampler_layout_binding.descriptorCount = 1;
    post_sampler_layout_binding.stageFlags = vk::ShaderStageFlagBits::eFragment;
    post_sampler_layout_binding.pImmutableSamplers = nullptr;

    vk::DescriptorSetLayoutBinding cloud_sampler_layout_binding{};
    cloud_sampler_layout_binding.binding = 1;
    cloud_sampler_layout_binding.descriptorType = vk::DescriptorType::eCombinedImageSampler;
    cloud_sampler_layout_binding.descriptorCount = 1;
    cloud_sampler_layout_binding.stageFlags = vk::ShaderStageFlagBits::eFragment;
    cloud_sampler_layout_binding.pImmutableSamplers = nullptr;

    std::vector<vk::DescriptorSetLayoutBinding> layout_bindings = { post_sampler_layout_binding, cloud_sampler_layout_binding };

    vk::DescriptorSetLayoutCreateInfo layout_create_info{};
    layout_create_info.bindingCount = static_cast<uint32_t>(layout_bindings.size());
    layout_create_info.pBindings = layout_bindings.data();

    auto result = device->getLogicalDevice().createDescriptorSetLayout(layout_create_info);
    if (result.result != vk::Result::eSuccess) {
        spdlog::error("Failed to create post descriptor set layout!");
        return;
    }
    post_descriptor_set_layout = result.value;

    vk::DescriptorPoolSize post_pool_size{};
    post_pool_size.type = vk::DescriptorType::eCombinedImageSampler;
    post_pool_size.descriptorCount = vulkanSwapChain.getNumberSwapChainImages() * 2; // 2 samplers per image

    std::vector<vk::DescriptorPoolSize> descriptor_pool_sizes = { post_pool_size };

    vk::DescriptorPoolCreateInfo pool_create_info{};
    pool_create_info.maxSets = vulkanSwapChain.getNumberSwapChainImages();
    pool_create_info.poolSizeCount = static_cast<uint32_t>(descriptor_pool_sizes.size());
    pool_create_info.pPoolSizes = descriptor_pool_sizes.data();

    auto pool_result = device->getLogicalDevice().createDescriptorPool(pool_create_info);
    if (pool_result.result != vk::Result::eSuccess) {
        spdlog::error("Failed to create post descriptor pool!");
        return;
    }
    post_descriptor_pool = pool_result.value;

    post_descriptor_set.resize(vulkanSwapChain.getNumberSwapChainImages());

    std::vector<vk::DescriptorSetLayout> set_layouts(
      vulkanSwapChain.getNumberSwapChainImages(), post_descriptor_set_layout);

    vk::DescriptorSetAllocateInfo set_alloc_info{};
    set_alloc_info.descriptorPool = post_descriptor_pool;
    set_alloc_info.descriptorSetCount = vulkanSwapChain.getNumberSwapChainImages();
    set_alloc_info.pSetLayouts = set_layouts.data();

    auto alloc_result = device->getLogicalDevice().allocateDescriptorSets(set_alloc_info);
    if (alloc_result.result != vk::Result::eSuccess) {
        spdlog::error("Failed to allocate post descriptor sets!");
        post_descriptor_set.clear();
        return;
    }
    post_descriptor_set = alloc_result.value;
}

void Kataglyphis::VulkanRenderer::updatePostDescriptorSets()
{
    if (post_descriptor_set.size() < vulkanSwapChain.getNumberSwapChainImages()) {
        spdlog::error("Post descriptor sets are not available; skipping update.");
        return;
    }

    for (size_t i = 0; i < vulkanSwapChain.getNumberSwapChainImages(); i++) {
        vk::DescriptorImageInfo image_info{};
        image_info.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
    Kataglyphis::VulkanRendererInternals::FrontendShared::GUIRendererSharedVars const &guiRendererSharedVars =
      gui->getGuiRendererSharedVars();
        Texture &renderResult = guiRendererSharedVars.rasterizationMode == Kataglyphis::VulkanRendererInternals::FrontendShared::RasterizationMode::Forward ? rasterizer.getOffscreenTexture(static_cast<uint32_t>(i)) : deferredRasterizer.getOffscreenTexture(static_cast<uint32_t>(i));
        image_info.imageView = renderResult.getImageView();
        image_info.sampler = postStage.getOffscreenSampler();

        vk::WriteDescriptorSet descriptor_write{};
        descriptor_write.dstSet = post_descriptor_set[i];
        descriptor_write.dstBinding = 0;
        descriptor_write.dstArrayElement = 0;
        descriptor_write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
        descriptor_write.descriptorCount = 1;
        descriptor_write.pImageInfo = &image_info;

        vk::DescriptorImageInfo cloud_info{};
        cloud_info.imageLayout = vk::ImageLayout::eGeneral;
        cloud_info.imageView = clouds.getCloudOutputTexture()->getImageView();
        cloud_info.sampler = clouds.getCloudOutputTexture()->getSampler();

        vk::WriteDescriptorSet cloud_write{};
        cloud_write.dstSet = post_descriptor_set[i];
        cloud_write.dstBinding = 1;
        cloud_write.dstArrayElement = 0;
        cloud_write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
        cloud_write.descriptorCount = 1;
        cloud_write.pImageInfo = &cloud_info;

        std::array<vk::WriteDescriptorSet, 2> writes = { descriptor_write, cloud_write };
        device->getLogicalDevice().updateDescriptorSets(static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
    }
}

void Kataglyphis::VulkanRenderer::createRaytracingDescriptorPool()
{
    std::array<vk::DescriptorPoolSize, 2> descriptor_pool_sizes{};
    const uint32_t swapchain_image_count = vulkanSwapChain.getNumberSwapChainImages();

    descriptor_pool_sizes[0].type = vk::DescriptorType::eAccelerationStructureKHR;
    descriptor_pool_sizes[0].descriptorCount = swapchain_image_count;

    descriptor_pool_sizes[1].type = vk::DescriptorType::eStorageImage;
    descriptor_pool_sizes[1].descriptorCount = swapchain_image_count;

    vk::DescriptorPoolCreateInfo descriptor_pool_create_info{};
    descriptor_pool_create_info.poolSizeCount = static_cast<uint32_t>(descriptor_pool_sizes.size());
    descriptor_pool_create_info.pPoolSizes = descriptor_pool_sizes.data();
    descriptor_pool_create_info.maxSets = swapchain_image_count;

    vk::Result const result =
      device->getLogicalDevice().createDescriptorPool(&descriptor_pool_create_info, nullptr, &raytracingDescriptorPool);
    ASSERT_VULKAN(static_cast<VkResult>(result), "Failed to create command pool!")
}

void Kataglyphis::VulkanRenderer::cleanUpSync()
{
    for (vk::Semaphore semaphore : render_finished_by_image) {
        if (semaphore) { device->getLogicalDevice().destroySemaphore(semaphore); }
    }
    render_finished_by_image.clear();

    for (uint32_t i = 0; i < image_available.size(); i++) {
        if (image_available[i]) { device->getLogicalDevice().destroySemaphore(image_available[i]); }
        if (in_flight_fences[i]) { device->getLogicalDevice().destroyFence(in_flight_fences[i]); }
    }
    image_available.clear();
    in_flight_fences.clear();
    images_in_flight_fences.clear();
}

void Kataglyphis::VulkanRenderer::create_object_description_buffer()
{
    std::vector<ObjectDescription> objectDescriptions = scene->getObjectDescriptions();

    if (!objectDescriptions.empty()) {
        vulkanBufferManager.createBufferAndUploadVectorOnDevice(device,
          graphics_command_pool,
          objectDescriptionBuffer,
          vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer,
          vk::MemoryPropertyFlagBits::eDeviceLocal,
          objectDescriptions);
    } else {
        // Create an empty buffer (1 byte) if no object descriptions are present to avoid validation error
        vulkanBufferManager.createBufferAndUploadVectorOnDevice(device,
          graphics_command_pool,
          objectDescriptionBuffer,
          vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer,
          vk::MemoryPropertyFlagBits::eDeviceLocal,
          std::vector<uint32_t>{0});
    }

    for (size_t i = 0; i < vulkanSwapChain.getNumberSwapChainImages(); i++) {
        vk::DescriptorBufferInfo object_descriptions_buffer_info{};
        object_descriptions_buffer_info.buffer = objectDescriptionBuffer.getBuffer();
        object_descriptions_buffer_info.offset = 0;
        object_descriptions_buffer_info.range = VK_WHOLE_SIZE;

        vk::WriteDescriptorSet descriptor_object_descriptions_writer{};
        descriptor_object_descriptions_writer.pNext = nullptr;
        descriptor_object_descriptions_writer.dstSet = sharedRenderDescriptorSet[i];
        descriptor_object_descriptions_writer.dstBinding = OBJECT_DESCRIPTION_BINDING;
        descriptor_object_descriptions_writer.dstArrayElement = 0;
        descriptor_object_descriptions_writer.descriptorCount = 1;
        descriptor_object_descriptions_writer.descriptorType = vk::DescriptorType::eStorageBuffer;
        descriptor_object_descriptions_writer.pImageInfo = nullptr;
        descriptor_object_descriptions_writer.pBufferInfo = &object_descriptions_buffer_info;
        descriptor_object_descriptions_writer.pTexelBufferView = nullptr;

        std::vector<vk::WriteDescriptorSet> write_descriptor_sets = { descriptor_object_descriptions_writer };

        device->getLogicalDevice().updateDescriptorSets(
          static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
    }
}

void Kataglyphis::VulkanRenderer::createRaytracingDescriptorSetLayouts()
{
    {
        std::array<vk::DescriptorSetLayoutBinding, 2> descriptor_set_layout_bindings{};

        descriptor_set_layout_bindings[0].binding = TLAS_BINDING;
        descriptor_set_layout_bindings[0].descriptorCount = 1;
        descriptor_set_layout_bindings[0].descriptorType = vk::DescriptorType::eAccelerationStructureKHR;
        descriptor_set_layout_bindings[0].pImmutableSamplers = nullptr;
        descriptor_set_layout_bindings[0].stageFlags = vk::ShaderStageFlagBits::eRaygenKHR
                                                       | vk::ShaderStageFlagBits::eClosestHitKHR
                                                       | vk::ShaderStageFlagBits::eCompute;

        descriptor_set_layout_bindings[1].binding = OUT_IMAGE_BINDING;
        descriptor_set_layout_bindings[1].descriptorCount = 1;
        descriptor_set_layout_bindings[1].descriptorType = vk::DescriptorType::eStorageImage;
        descriptor_set_layout_bindings[1].pImmutableSamplers = nullptr;
        descriptor_set_layout_bindings[1].stageFlags = vk::ShaderStageFlagBits::eRaygenKHR
                                                       | vk::ShaderStageFlagBits::eClosestHitKHR
                                                       | vk::ShaderStageFlagBits::eCompute;

        vk::DescriptorSetLayoutCreateInfo descriptor_set_layout_create_info{};
        descriptor_set_layout_create_info.bindingCount = static_cast<uint32_t>(descriptor_set_layout_bindings.size());
        descriptor_set_layout_create_info.pBindings = descriptor_set_layout_bindings.data();

        vk::Result const result = device->getLogicalDevice().createDescriptorSetLayout(
          &descriptor_set_layout_create_info, nullptr, &raytracingDescriptorSetLayout);
        ASSERT_VULKAN(static_cast<VkResult>(result), "Failed to create raytracing descriptor set layout!")
    }
}

void Kataglyphis::VulkanRenderer::createRaytracingDescriptorSets()
{
    raytracingDescriptorSet.resize(vulkanSwapChain.getNumberSwapChainImages());

    std::vector<vk::DescriptorSetLayout> set_layouts(
      vulkanSwapChain.getNumberSwapChainImages(), raytracingDescriptorSetLayout);

    vk::DescriptorSetAllocateInfo descriptor_set_allocate_info{};
    descriptor_set_allocate_info.descriptorPool = raytracingDescriptorPool;
    descriptor_set_allocate_info.descriptorSetCount = vulkanSwapChain.getNumberSwapChainImages();
    descriptor_set_allocate_info.pSetLayouts = set_layouts.data();

    vk::Result const result =
      device->getLogicalDevice().allocateDescriptorSets(&descriptor_set_allocate_info, raytracingDescriptorSet.data());
    ASSERT_VULKAN(static_cast<VkResult>(result), "Failed to allocate raytracing descriptor set!")
}

void Kataglyphis::VulkanRenderer::updateRaytracingDescriptorSets()
{
    vk::AccelerationStructureKHR &vulkanTLAS = asManager.getTLAS();
    if (!vulkanTLAS) {
        return;
    }

    for (size_t i = 0; i < vulkanSwapChain.getNumberSwapChainImages(); i++) {
        vk::WriteDescriptorSetAccelerationStructureKHR descriptor_set_acceleration_structure{};
        descriptor_set_acceleration_structure.pNext = nullptr;
        descriptor_set_acceleration_structure.accelerationStructureCount = 1;
        descriptor_set_acceleration_structure.pAccelerationStructures = &vulkanTLAS;

        vk::WriteDescriptorSet write_descriptor_set_acceleration_structure{};
        write_descriptor_set_acceleration_structure.pNext = &descriptor_set_acceleration_structure;
        write_descriptor_set_acceleration_structure.dstSet = raytracingDescriptorSet[i];
        write_descriptor_set_acceleration_structure.dstBinding = TLAS_BINDING;
        write_descriptor_set_acceleration_structure.dstArrayElement = 0;
        write_descriptor_set_acceleration_structure.descriptorCount = 1;
        write_descriptor_set_acceleration_structure.descriptorType = vk::DescriptorType::eAccelerationStructureKHR;
        write_descriptor_set_acceleration_structure.pImageInfo = nullptr;
        write_descriptor_set_acceleration_structure.pBufferInfo = nullptr;
        write_descriptor_set_acceleration_structure.pTexelBufferView = nullptr;

        vk::DescriptorImageInfo image_info{};
    Kataglyphis::VulkanRendererInternals::FrontendShared::GUIRendererSharedVars const &guiRendererSharedVars =
      gui->getGuiRendererSharedVars();
        Texture &renderResult = guiRendererSharedVars.rasterizationMode == Kataglyphis::VulkanRendererInternals::FrontendShared::RasterizationMode::Forward ? rasterizer.getOffscreenTexture(static_cast<uint32_t>(i)) : deferredRasterizer.getOffscreenTexture(static_cast<uint32_t>(i));
        image_info.imageView = renderResult.getImageView();
        image_info.imageLayout = vk::ImageLayout::eGeneral;

        vk::WriteDescriptorSet descriptor_image_writer{};
        descriptor_image_writer.pNext = nullptr;
        descriptor_image_writer.dstSet = raytracingDescriptorSet[i];
        descriptor_image_writer.dstBinding = OUT_IMAGE_BINDING;
        descriptor_image_writer.dstArrayElement = 0;
        descriptor_image_writer.descriptorCount = 1;
        descriptor_image_writer.descriptorType = vk::DescriptorType::eStorageImage;
        descriptor_image_writer.pImageInfo = &image_info;
        descriptor_image_writer.pBufferInfo = nullptr;
        descriptor_image_writer.pTexelBufferView = nullptr;

        std::vector<vk::WriteDescriptorSet> write_descriptor_sets = { write_descriptor_set_acceleration_structure,
            descriptor_image_writer };

        device->getLogicalDevice().updateDescriptorSets(
          static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
    }
}

void Kataglyphis::VulkanRenderer::createSharedRenderDescriptorSetLayouts()
{
    const bool raytracing_available = device->supportsHardwareAcceleratedRRT();

    vk::ShaderStageFlags global_ubo_stages = vk::ShaderStageFlagBits::eVertex;
    vk::ShaderStageFlags scene_ubo_stages = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
    vk::ShaderStageFlags object_description_stages =
      vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
    vk::ShaderStageFlags sampler_stages = vk::ShaderStageFlagBits::eFragment;
    vk::ShaderStageFlags textures_stages = vk::ShaderStageFlagBits::eFragment;

    if (raytracing_available) {
        global_ubo_stages |= vk::ShaderStageFlagBits::eRaygenKHR | vk::ShaderStageFlagBits::eCompute;
        scene_ubo_stages |= vk::ShaderStageFlagBits::eRaygenKHR | vk::ShaderStageFlagBits::eClosestHitKHR
                            | vk::ShaderStageFlagBits::eCompute;
        object_description_stages |= vk::ShaderStageFlagBits::eClosestHitKHR | vk::ShaderStageFlagBits::eCompute;
        sampler_stages |= vk::ShaderStageFlagBits::eClosestHitKHR | vk::ShaderStageFlagBits::eCompute;
        textures_stages |= vk::ShaderStageFlagBits::eClosestHitKHR | vk::ShaderStageFlagBits::eCompute;
    }

    std::array<vk::DescriptorSetLayoutBinding, 5> descriptor_set_layout_bindings{};
    descriptor_set_layout_bindings[0].binding = globalUBO_BINDING;
    descriptor_set_layout_bindings[0].descriptorType = vk::DescriptorType::eUniformBuffer;
    descriptor_set_layout_bindings[0].descriptorCount = 1;
    descriptor_set_layout_bindings[0].stageFlags = global_ubo_stages;
    descriptor_set_layout_bindings[0].pImmutableSamplers = nullptr;

    descriptor_set_layout_bindings[1].binding = sceneUBO_BINDING;
    descriptor_set_layout_bindings[1].descriptorType = vk::DescriptorType::eUniformBuffer;
    descriptor_set_layout_bindings[1].descriptorCount = 1;
    descriptor_set_layout_bindings[1].stageFlags = scene_ubo_stages;
    descriptor_set_layout_bindings[1].pImmutableSamplers = nullptr;

    descriptor_set_layout_bindings[2].binding = OBJECT_DESCRIPTION_BINDING;
    descriptor_set_layout_bindings[2].descriptorCount = 1;
    descriptor_set_layout_bindings[2].descriptorType = vk::DescriptorType::eStorageBuffer;
    descriptor_set_layout_bindings[2].pImmutableSamplers = nullptr;
    descriptor_set_layout_bindings[2].stageFlags = object_description_stages;

    descriptor_set_layout_bindings[3].binding = TEXTURES_BINDING;
    descriptor_set_layout_bindings[3].descriptorType = vk::DescriptorType::eSampledImage;
    descriptor_set_layout_bindings[3].descriptorCount = MAX_TEXTURE_COUNT;
    descriptor_set_layout_bindings[3].stageFlags = textures_stages;
    descriptor_set_layout_bindings[3].pImmutableSamplers = nullptr;

    descriptor_set_layout_bindings[4].binding = SAMPLER_BINDING;
    descriptor_set_layout_bindings[4].descriptorType = vk::DescriptorType::eSampler;
    descriptor_set_layout_bindings[4].descriptorCount = MAX_TEXTURE_COUNT;
    descriptor_set_layout_bindings[4].stageFlags = sampler_stages;
    descriptor_set_layout_bindings[4].pImmutableSamplers = nullptr;

    vk::DescriptorSetLayoutCreateInfo layout_create_info{};
    layout_create_info.bindingCount = static_cast<uint32_t>(descriptor_set_layout_bindings.size());
    layout_create_info.pBindings = descriptor_set_layout_bindings.data();

    auto result = device->getLogicalDevice().createDescriptorSetLayout(layout_create_info);
    if (result.result != vk::Result::eSuccess) {
        spdlog::error("Failed to create shared render descriptor set layout!");
        return;
    }
    sharedRenderDescriptorSetLayout = result.value;
}

void Kataglyphis::VulkanRenderer::create_command_pool()
{
    Kataglyphis::VulkanRendererInternals::QueueFamilyIndices const queue_family_indices = device->getQueueFamilies();

    {
        vk::CommandPoolCreateInfo pool_info{};
        pool_info.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
        pool_info.queueFamilyIndex = static_cast<uint32_t>(queue_family_indices.graphics_family);

        vk::Result const result =
          device->getLogicalDevice().createCommandPool(&pool_info, nullptr, &graphics_command_pool);
        if (result != vk::Result::eSuccess) {
            spdlog::error("Failed to create graphics command pool! Error: {}", static_cast<int>(result));
            std::abort();
        }
    }

    {
        vk::CommandPoolCreateInfo pool_info{};
        pool_info.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
        pool_info.queueFamilyIndex = static_cast<uint32_t>(queue_family_indices.compute_family);

        vk::Result const result =
          device->getLogicalDevice().createCommandPool(&pool_info, nullptr, &compute_command_pool);
        if (result != vk::Result::eSuccess) {
            spdlog::error("Failed to create compute command pool! Error: {}", static_cast<int>(result));
            std::abort();
        }
    }
}

void Kataglyphis::VulkanRenderer::cleanUpCommandPools()
{
    if (graphics_command_pool) {
        device->getLogicalDevice().destroyCommandPool(graphics_command_pool);
        graphics_command_pool = nullptr;
    }
    if (compute_command_pool) {
        device->getLogicalDevice().destroyCommandPool(compute_command_pool);
        compute_command_pool = nullptr;
    }
}

void Kataglyphis::VulkanRenderer::create_command_buffers()
{
    if (!command_buffers.empty()) {
        device->getLogicalDevice().freeCommandBuffers(graphics_command_pool, command_buffers);
    }
    command_buffers.resize(vulkanSwapChain.getNumberSwapChainImages());

    vk::CommandBufferAllocateInfo command_buffer_alloc_info{};
    command_buffer_alloc_info.commandPool = graphics_command_pool;
    command_buffer_alloc_info.level = vk::CommandBufferLevel::ePrimary;

    command_buffer_alloc_info.commandBufferCount = static_cast<uint32_t>(command_buffers.size());

    vk::Result const result =
      device->getLogicalDevice().allocateCommandBuffers(&command_buffer_alloc_info, command_buffers.data());
    ASSERT_VULKAN(static_cast<VkResult>(result), "Failed to allocate command buffers!")
}

void Kataglyphis::VulkanRenderer::createSynchronization()
{
    frame_sync_count = std::min<uint32_t>(
      static_cast<uint32_t>(Kataglyphis::MAX_FRAME_DRAWS), vulkanSwapChain.getNumberSwapChainImages());

    if (!image_available.empty()) {
        for (uint32_t i = 0; i < image_available.size(); i++) {
            if (in_flight_fences[i]) { device->getLogicalDevice().destroyFence(in_flight_fences[i]); }
        }
        for (vk::Semaphore semaphore : image_available) {
            if (semaphore) { device->getLogicalDevice().destroySemaphore(semaphore); }
        }
        for (vk::Semaphore semaphore : render_finished_by_image) {
            if (semaphore) { device->getLogicalDevice().destroySemaphore(semaphore); }
        }
    }

    image_available.resize(frame_sync_count);
    render_finished_by_image.resize(vulkanSwapChain.getNumberSwapChainImages());
    in_flight_fences.resize(frame_sync_count);
    images_in_flight_fences.resize(vulkanSwapChain.getNumberSwapChainImages());

    vk::SemaphoreCreateInfo semaphore_create_info{};

    vk::FenceCreateInfo fence_create_info{};
    fence_create_info.flags = vk::FenceCreateFlagBits::eSignaled;

    for (uint32_t i = 0; i < frame_sync_count; i++) {
        auto image_available_result_value = device->getLogicalDevice().createSemaphore(semaphore_create_info);
        auto image_available_result = image_available_result_value.result;
        auto image_available_handle = image_available_result_value.value;
        auto in_flight_fence_result_value = device->getLogicalDevice().createFence(fence_create_info);
        auto in_flight_fence_result = in_flight_fence_result_value.result;
        auto in_flight_fence_handle = in_flight_fence_result_value.value;

        if (image_available_result != vk::Result::eSuccess || in_flight_fence_result != vk::Result::eSuccess
            || !image_available_handle || !in_flight_fence_handle) {
            spdlog::error(
              fmt::format("Failed to create synchronization objects for frame {} (imageAvailable={}, fence={}).",
                i,
                static_cast<int>(image_available_result),
                static_cast<int>(in_flight_fence_result)));
            frame_sync_count = 0;
            return;
        }

        image_available[i] = image_available_handle;
        in_flight_fences[i] = in_flight_fence_handle;
    }

    for (uint32_t image = 0; image < vulkanSwapChain.getNumberSwapChainImages(); ++image) {
        auto render_finished_result_value = device->getLogicalDevice().createSemaphore(semaphore_create_info);
        auto render_finished_result = render_finished_result_value.result;
        auto render_finished_handle = render_finished_result_value.value;

        if (render_finished_result != vk::Result::eSuccess || !render_finished_handle) {
            spdlog::error(fmt::format("Failed to create render-finished semaphore for swapchain image {} ({}).",
              image,
              static_cast<int>(render_finished_result)));
            frame_sync_count = 0;
            return;
        }

        render_finished_by_image[image] = render_finished_handle;
    }

    for (uint32_t image = 0; image < vulkanSwapChain.getNumberSwapChainImages(); ++image) {
        images_in_flight_fences[image] = nullptr;
    }

    current_frame = 0;
}

void Kataglyphis::VulkanRenderer::create_uniform_buffers()
{
    const uint32_t imageCount = vulkanSwapChain.getNumberSwapChainImages();
    globalUBOBuffer.resize(imageCount);
    globalUBOMapped.resize(imageCount);
    sceneUBOBuffer.resize(imageCount);
    sceneUBOMapped.resize(imageCount);

    for (size_t i = 0; i < imageCount; i++) {
        globalUBOBuffer[i].create(device,
          sizeof(VulkanRendererInternals::GlobalUBO),
          vk::BufferUsageFlagBits::eUniformBuffer,
          vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);

        globalUBOMapped[i] = device->getLogicalDevice().mapMemory(globalUBOBuffer[i].getBufferMemory(), 0, sizeof(VulkanRendererInternals::GlobalUBO)).value;

        sceneUBOBuffer[i].create(device,
          sizeof(VulkanRendererInternals::SceneUBO),
          vk::BufferUsageFlagBits::eUniformBuffer,
          vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);

        sceneUBOMapped[i] = device->getLogicalDevice().mapMemory(sceneUBOBuffer[i].getBufferMemory(), 0, sizeof(VulkanRendererInternals::SceneUBO)).value;

        // Initial upload
        update_uniform_buffers(static_cast<uint32_t>(i));
    }
}

void Kataglyphis::VulkanRenderer::createDescriptorPoolSharedRenderStages()
{
    vk::DescriptorPoolSize vp_pool_size{};
    vp_pool_size.type = vk::DescriptorType::eUniformBuffer;
    vp_pool_size.descriptorCount = static_cast<uint32_t>(globalUBOBuffer.size());

    vk::DescriptorPoolSize directions_pool_size{};
    directions_pool_size.type = vk::DescriptorType::eUniformBuffer;
    directions_pool_size.descriptorCount = static_cast<uint32_t>(sceneUBOBuffer.size());

    vk::DescriptorPoolSize object_descriptions_pool_size{};
    object_descriptions_pool_size.type = vk::DescriptorType::eStorageBuffer;
    object_descriptions_pool_size.descriptorCount =
      static_cast<uint32_t>(sizeof(ObjectDescription) * Kataglyphis::MAX_OBJECTS);

    vk::DescriptorPoolSize sampler_pool_size{};
    sampler_pool_size.type = vk::DescriptorType::eSampler;
    sampler_pool_size.descriptorCount = MAX_TEXTURE_COUNT * vulkanSwapChain.getNumberSwapChainImages();

    vk::DescriptorPoolSize sampled_image_pool_size{};
    sampled_image_pool_size.type = vk::DescriptorType::eSampledImage;
    sampled_image_pool_size.descriptorCount = MAX_TEXTURE_COUNT * vulkanSwapChain.getNumberSwapChainImages();

    std::vector<vk::DescriptorPoolSize> descriptor_pool_sizes = {
        vp_pool_size, directions_pool_size, object_descriptions_pool_size, sampler_pool_size, sampled_image_pool_size
    };

    vk::DescriptorPoolCreateInfo pool_create_info{};
    pool_create_info.maxSets = vulkanSwapChain.getNumberSwapChainImages();
    pool_create_info.poolSizeCount = static_cast<uint32_t>(descriptor_pool_sizes.size());
    pool_create_info.pPoolSizes = descriptor_pool_sizes.data();

    vk::Result const result =
      device->getLogicalDevice().createDescriptorPool(&pool_create_info, nullptr, &descriptorPoolSharedRenderStages);
    ASSERT_VULKAN(static_cast<VkResult>(result), "Failed to create a descriptor pool!")
}

void Kataglyphis::VulkanRenderer::createSharedRenderDescriptorSet()
{
    sharedRenderDescriptorSet.resize(vulkanSwapChain.getNumberSwapChainImages());

    std::vector<vk::DescriptorSetLayout> set_layouts(
      vulkanSwapChain.getNumberSwapChainImages(), sharedRenderDescriptorSetLayout);

    vk::DescriptorSetAllocateInfo set_alloc_info{};
    set_alloc_info.descriptorPool = descriptorPoolSharedRenderStages;
    set_alloc_info.descriptorSetCount = vulkanSwapChain.getNumberSwapChainImages();
    set_alloc_info.pSetLayouts = set_layouts.data();

    auto result = device->getLogicalDevice().allocateDescriptorSets(set_alloc_info);
    if (result.result != vk::Result::eSuccess) {
        spdlog::error("Failed to allocate shared render descriptor sets!");
        sharedRenderDescriptorSet.clear();
        return;
    }
    sharedRenderDescriptorSet = result.value;

    for (size_t i = 0; i < vulkanSwapChain.getNumberSwapChainImages(); i++) {
        vk::DescriptorBufferInfo globalUBO_buffer_info{};
        globalUBO_buffer_info.buffer = globalUBOBuffer[i].getBuffer();
        globalUBO_buffer_info.offset = 0;
        globalUBO_buffer_info.range = sizeof(globalUBO);

        vk::WriteDescriptorSet globalUBO_set_write{};
        globalUBO_set_write.dstSet = sharedRenderDescriptorSet[i];
        globalUBO_set_write.dstBinding = 0;
        globalUBO_set_write.dstArrayElement = 0;
        globalUBO_set_write.descriptorType = vk::DescriptorType::eUniformBuffer;
        globalUBO_set_write.descriptorCount = 1;
        globalUBO_set_write.pBufferInfo = &globalUBO_buffer_info;

        vk::DescriptorBufferInfo sceneUBO_buffer_info{};
        sceneUBO_buffer_info.buffer = sceneUBOBuffer[i].getBuffer();
        sceneUBO_buffer_info.offset = 0;
        sceneUBO_buffer_info.range = sizeof(sceneUBO);

        vk::WriteDescriptorSet sceneUBO_set_write{};
        sceneUBO_set_write.dstSet = sharedRenderDescriptorSet[i];
        sceneUBO_set_write.dstBinding = 1;
        sceneUBO_set_write.dstArrayElement = 0;
        sceneUBO_set_write.descriptorType = vk::DescriptorType::eUniformBuffer;
        sceneUBO_set_write.descriptorCount = 1;
        sceneUBO_set_write.pBufferInfo = &sceneUBO_buffer_info;

        std::vector<vk::WriteDescriptorSet> write_descriptor_sets = { globalUBO_set_write, sceneUBO_set_write };

        device->getLogicalDevice().updateDescriptorSets(
          static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
    }
}

void Kataglyphis::VulkanRenderer::updateTexturesInSharedRenderDescriptorSet()
{
    if (sharedRenderDescriptorSet.empty()) {
        return;
    }

    if (scene->getModelCount() == 0) {
        return;
    }

    std::vector<Texture> &modelTextures = scene->getTextures(0);
    const uint32_t scene_texture_count = scene->getTextureCount(0);
    if (scene_texture_count == 0) {
        return;
    }

    const uint32_t texture_count_for_descriptors = std::min<uint32_t>(scene_texture_count, MAX_TEXTURE_COUNT);
    std::vector<vk::Sampler> &modelTextureSampler = scene->getTextureSampler(0);

    std::vector<vk::DescriptorImageInfo> image_info_textures(MAX_TEXTURE_COUNT);
    std::vector<vk::DescriptorImageInfo> image_info_texture_sampler(MAX_TEXTURE_COUNT);

    for (uint32_t i = 0; i < MAX_TEXTURE_COUNT; i++) {
        const uint32_t texture_index = (i < texture_count_for_descriptors) ? i : 0;

        image_info_textures[i].imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
        image_info_textures[i].imageView = modelTextures[texture_index].getImageView();

        image_info_texture_sampler[i].imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
        image_info_texture_sampler[i].sampler = modelTextureSampler[texture_index];
    }

    for (uint32_t i = 0; i < vulkanSwapChain.getNumberSwapChainImages(); i++) {
        vk::WriteDescriptorSet descriptor_write{};
        descriptor_write.dstSet = sharedRenderDescriptorSet[i];
        descriptor_write.dstBinding = TEXTURES_BINDING;
        descriptor_write.descriptorType = vk::DescriptorType::eSampledImage;
        descriptor_write.descriptorCount = MAX_TEXTURE_COUNT;
        descriptor_write.pImageInfo = image_info_textures.data();

        vk::WriteDescriptorSet descriptor_sampler_write{};
        descriptor_sampler_write.dstSet = sharedRenderDescriptorSet[i];
        descriptor_sampler_write.dstBinding = SAMPLER_BINDING;
        descriptor_sampler_write.descriptorType = vk::DescriptorType::eSampler;
        descriptor_sampler_write.descriptorCount = MAX_TEXTURE_COUNT;
        descriptor_sampler_write.pImageInfo = image_info_texture_sampler.data();

        std::array<vk::WriteDescriptorSet, 2> write_descriptor_sets = { descriptor_write, descriptor_sampler_write };
        device->getLogicalDevice().updateDescriptorSets(write_descriptor_sets, nullptr);
    }
}
void Kataglyphis::VulkanRenderer::create_gbuffer_descriptor_layout()
{
    std::array<vk::DescriptorSetLayoutBinding, 5> layout_bindings{};
    for(uint32_t i = 0; i < 5; i++) {
        layout_bindings[i].binding = i;
        layout_bindings[i].descriptorType = vk::DescriptorType::eInputAttachment;
        layout_bindings[i].descriptorCount = 1;
        layout_bindings[i].stageFlags = vk::ShaderStageFlagBits::eFragment;
        layout_bindings[i].pImmutableSamplers = nullptr;
    }

    vk::DescriptorSetLayoutCreateInfo layout_create_info{};
    layout_create_info.bindingCount = static_cast<uint32_t>(layout_bindings.size());
    layout_create_info.pBindings = layout_bindings.data();

    auto result = device->getLogicalDevice().createDescriptorSetLayout(layout_create_info);
    if (result.result != vk::Result::eSuccess) {
        spdlog::error("Failed to create gbuffer descriptor set layout!");
        return;
    }
    gbuffer_descriptor_set_layout = result.value;

    vk::DescriptorPoolSize pool_size{};
    pool_size.type = vk::DescriptorType::eInputAttachment;
    pool_size.descriptorCount = static_cast<uint32_t>(vulkanSwapChain.getNumberSwapChainImages()) * 5;

    vk::DescriptorPoolCreateInfo pool_info{};
    pool_info.poolSizeCount = 1;
    pool_info.pPoolSizes = &pool_size;
    pool_info.maxSets = static_cast<uint32_t>(vulkanSwapChain.getNumberSwapChainImages());

    auto pool_result = device->getLogicalDevice().createDescriptorPool(pool_info);
    if (pool_result.result != vk::Result::eSuccess) {
        spdlog::error("Failed to create gbuffer descriptor pool!");
        return;
    }
    gbuffer_descriptor_pool = pool_result.value;

    std::vector<vk::DescriptorSetLayout> layouts(vulkanSwapChain.getNumberSwapChainImages(), gbuffer_descriptor_set_layout);
    vk::DescriptorSetAllocateInfo alloc_info{};
    alloc_info.descriptorPool = gbuffer_descriptor_pool;
    alloc_info.descriptorSetCount = static_cast<uint32_t>(vulkanSwapChain.getNumberSwapChainImages());
    alloc_info.pSetLayouts = layouts.data();

    auto alloc_result = device->getLogicalDevice().allocateDescriptorSets(alloc_info);
    if (alloc_result.result != vk::Result::eSuccess) {
        spdlog::error("Failed to allocate gbuffer descriptor sets!");
        return;
    }
    gbuffer_descriptor_set = alloc_result.value;
}

void Kataglyphis::VulkanRenderer::updateGBufferDescriptorSets()
{
    for (size_t i = 0; i < vulkanSwapChain.getNumberSwapChainImages(); i++) {
        std::array<vk::DescriptorImageInfo, 5> imageInfos;

        imageInfos[0].imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
        imageInfos[0].imageView = deferredRasterizer.getGBufferPosition(static_cast<uint32_t>(i));

        imageInfos[1].imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
        imageInfos[1].imageView = deferredRasterizer.getGBufferNormal(static_cast<uint32_t>(i));

        imageInfos[2].imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
        imageInfos[2].imageView = deferredRasterizer.getGBufferAlbedo(static_cast<uint32_t>(i));

        imageInfos[3].imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
        imageInfos[3].imageView = deferredRasterizer.getGBufferMaterial(static_cast<uint32_t>(i));

        imageInfos[4].imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
        imageInfos[4].imageView = deferredRasterizer.getDepthBufferImageView();

        std::array<vk::WriteDescriptorSet, 5> descriptorWrites;;
        for(uint32_t j = 0; j < 5; j++) {
            descriptorWrites[j].dstSet = gbuffer_descriptor_set[i];
            descriptorWrites[j].dstBinding = j;
            descriptorWrites[j].dstArrayElement = 0;
            descriptorWrites[j].descriptorType = vk::DescriptorType::eInputAttachment;
            descriptorWrites[j].descriptorCount = 1;
            descriptorWrites[j].pImageInfo = &imageInfos[j];
        }

        device->getLogicalDevice().updateDescriptorSets(descriptorWrites, nullptr);
    }
}