
.. _program_listing_file_Src_GraphicsEngineVulkan_renderer_VulkanRenderer.cpp:

Program Listing for File VulkanRenderer.cpp
===========================================

|exhale_lsh| :ref:`Return to documentation for file <file_Src_GraphicsEngineVulkan_renderer_VulkanRenderer.cpp>` (``Src/GraphicsEngineVulkan/renderer/VulkanRenderer.cpp``)

.. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS

.. code-block:: 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);
       }
   }
