From 81337dad225672575e81b1d71ab659a1ce396a27 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Wed, 6 Aug 2025 17:24:18 -0500 Subject: [PATCH 01/24] add a more general logger --- CMakeLists.txt | 5 + build-shaders.bat | 8 +- src/imgui/Imgui.cpp | 210 +++++++++++++++++++++---------- src/imgui/Imgui.hpp | 4 +- src/logging/ImGuiConsoleSink.cpp | 31 +++++ src/logging/ImGuiConsoleSink.hpp | 26 ++++ src/logging/Logger.hpp | 126 +++++++++++++++++++ src/logging/SpdlogSink.cpp | 36 ++++++ src/logging/SpdlogSink.hpp | 19 +++ src/vulkan/ShadowMapping.cpp | 3 +- src/vulkan/VulkanContext.cpp | 4 +- src/vulkan/VulkanRenderer.cpp | 18 ++- src/vulkan/VulkanRenderer.hpp | 2 +- 13 files changed, 412 insertions(+), 80 deletions(-) create mode 100644 src/logging/ImGuiConsoleSink.cpp create mode 100644 src/logging/ImGuiConsoleSink.hpp create mode 100644 src/logging/Logger.hpp create mode 100644 src/logging/SpdlogSink.cpp create mode 100644 src/logging/SpdlogSink.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5869e80..b494105 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,11 @@ add_library(ReactorLib STATIC src/core/ModelIO.cpp src/vulkan/ShadowMapping.hpp src/vulkan/ShadowMapping.cpp + src/logging/Logger.hpp + src/logging/SpdlogSink.cpp + src/logging/SpdlogSink.hpp + src/logging/ImGuiConsoleSink.cpp + src/logging/ImGuiConsoleSink.hpp ) target_compile_definitions(ReactorLib PUBLIC GLM_ENABLE_EXPERIMENTAL) diff --git a/build-shaders.bat b/build-shaders.bat index c18d078..23a5754 100644 --- a/build-shaders.bat +++ b/build-shaders.bat @@ -1,5 +1,5 @@ -glslc --target-env=vulkan1.3 -o resources/shaders/triangle.vert.spv shaders/triangle.vert -glslc --target-env=vulkan1.3 -o resources/shaders/triangle.frag.spv shaders/triangle.frag +glslc -g --target-env=vulkan1.3 -o resources/shaders/triangle.vert.spv shaders/triangle.vert +glslc -g --target-env=vulkan1.3 -o resources/shaders/triangle.frag.spv shaders/triangle.frag -glslc --target-env=vulkan1.3 -o resources/shaders/composite.vert.spv shaders/composite.vert -glslc --target-env=vulkan1.3 -o resources/shaders/composite.frag.spv shaders/composite.frag \ No newline at end of file +glslc -g --target-env=vulkan1.3 -o resources/shaders/composite.vert.spv shaders/composite.vert +glslc -g --target-env=vulkan1.3 -o resources/shaders/composite.frag.spv shaders/composite.frag \ No newline at end of file diff --git a/src/imgui/Imgui.cpp b/src/imgui/Imgui.cpp index 5049f63..e14518a 100644 --- a/src/imgui/Imgui.cpp +++ b/src/imgui/Imgui.cpp @@ -6,38 +6,43 @@ #define IMGUI_IMPL_VULKAN_NO_PROTOTYPES #include -#include #include #include +#include -namespace reactor { +namespace reactor +{ -Imgui::Imgui(VulkanContext &vulkanContext, Window &window, EventManager &eventManager) : -m_device(vulkanContext.device()), m_eventManager(eventManager) { +Imgui::Imgui(VulkanContext& vulkanContext, + Window& window, + EventManager& eventManager, + std::shared_ptr consoleSink) + : m_device(vulkanContext.device()), m_eventManager(eventManager), m_consoleSink(std::move(consoleSink)) +{ IMGUI_CHECKVERSION(); ImGui::CreateContext(); - ImGuiIO &io = ImGui::GetIO(); + ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; ImGui::StyleColorsDark(); // --- Initialize descriptor pool for ImGui --- - VkDescriptorPoolSize pool_sizes[] = {{VK_DESCRIPTOR_TYPE_SAMPLER, 1000}, - {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000}, - {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000}, - {VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000}, - {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000}, - {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000}, - {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000}, - {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000}, - {VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000}}; - VkDescriptorPoolCreateInfo pool_info = {}; - pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; - pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes); - pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); - pool_info.pPoolSizes = pool_sizes; + VkDescriptorPoolSize pool_sizes[] = {{VK_DESCRIPTOR_TYPE_SAMPLER, 1000}, + {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000}, + {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000}, + {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000}, + {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000}, + {VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000}, + {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000}, + {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000}, + {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000}, + {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000}, + {VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000}}; + VkDescriptorPoolCreateInfo pool_info = {}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes); + pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); + pool_info.pPoolSizes = pool_sizes; // create the pool m_descriptorPool = vulkanContext.device().createDescriptorPool(pool_info); @@ -48,36 +53,38 @@ m_device(vulkanContext.device()), m_eventManager(eventManager) { auto colorAttachmentFormat = vk::Format::eB8G8R8A8Srgb; vk::PipelineRenderingCreateInfo renderingInfo{}; - renderingInfo.colorAttachmentCount = 1; + renderingInfo.colorAttachmentCount = 1; renderingInfo.pColorAttachmentFormats = &colorAttachmentFormat; - renderingInfo.viewMask = 0; - renderingInfo.depthAttachmentFormat = vk::Format::eUndefined; + renderingInfo.viewMask = 0; + renderingInfo.depthAttachmentFormat = vk::Format::eUndefined; renderingInfo.stencilAttachmentFormat = vk::Format::eUndefined; ImGui_ImplVulkan_InitInfo initInfo{}; - initInfo.Instance = vulkanContext.instance(); - initInfo.PhysicalDevice = vulkanContext.physicalDevice(); - initInfo.Device = vulkanContext.device(); - initInfo.QueueFamily = vulkanContext.queueFamilies().graphicsFamily.value(); - initInfo.Queue = vulkanContext.graphicsQueue(); - initInfo.PipelineCache = nullptr; - initInfo.DescriptorPool = m_descriptorPool; - initInfo.MinImageCount = 3; - initInfo.ImageCount = 3; - initInfo.UseDynamicRendering = true; + initInfo.Instance = vulkanContext.instance(); + initInfo.PhysicalDevice = vulkanContext.physicalDevice(); + initInfo.Device = vulkanContext.device(); + initInfo.QueueFamily = vulkanContext.queueFamilies().graphicsFamily.value(); + initInfo.Queue = vulkanContext.graphicsQueue(); + initInfo.PipelineCache = nullptr; + initInfo.DescriptorPool = m_descriptorPool; + initInfo.MinImageCount = 3; + initInfo.ImageCount = 3; + initInfo.UseDynamicRendering = true; initInfo.PipelineRenderingCreateInfo = renderingInfo; ImGui_ImplVulkan_Init(&initInfo); } -Imgui::~Imgui() { +Imgui::~Imgui() +{ ImGui_ImplGlfw_Shutdown(); ImGui_ImplVulkan_Shutdown(); m_device.destroyDescriptorPool(m_descriptorPool); } -void Imgui::createFrame() { +void Imgui::createFrame() +{ ImGui_ImplVulkan_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); @@ -88,24 +95,26 @@ void Imgui::createFrame() { ShowConsole(); } -void Imgui::drawFrame(vk::CommandBuffer commandBuffer) { +void Imgui::drawFrame(vk::CommandBuffer commandBuffer) +{ ImGui::Render(); ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commandBuffer); } -void Imgui::ShowDockspace() { +void Imgui::ShowDockspace() +{ static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_PassthruCentralNode; - ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; - const ImGuiViewport *viewport = ImGui::GetMainViewport(); + ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; + const ImGuiViewport* viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->Pos); ImGui::SetNextWindowSize(viewport->Size); ImGui::SetNextWindowViewport(viewport->ID); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; + window_flags |= + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; ImGui::Begin("DockSpace Window", nullptr, window_flags); @@ -116,7 +125,8 @@ void Imgui::ShowDockspace() { ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); static bool first_time = true; - if (first_time) { + if (first_time) + { SetupInitialDockLayout(dockspace_id); first_time = false; } @@ -124,32 +134,31 @@ void Imgui::ShowDockspace() { ImGui::End(); } -void Imgui::ShowSceneView() { +void Imgui::ShowSceneView() +{ ImGui::Begin("Scene View"); - if (m_sceneImguiId) { + if (m_sceneImguiId) + { const ImVec2 size = ImGui::GetContentRegionAvail(); if (size.x > 0.0f && size.y > 0.0f) { - const auto id = - reinterpret_cast(static_cast(m_sceneImguiId)); + const auto id = reinterpret_cast(static_cast(m_sceneImguiId)); - ImGui::Image( - id, - size, - ImVec2(0, 1), - ImVec2(1, 0)); + ImGui::Image(id, size, ImVec2(0, 1), ImVec2(1, 0)); ImVec2 image_pos = ImGui::GetItemRectMin(); ImGui::SetCursorScreenPos(image_pos); ImGui::InvisibleButton("scene_viewport", size); - if (ImGui::IsItemHovered()) { + if (ImGui::IsItemHovered()) + { ImGuiIO& io = ImGui::GetIO(); // Post MouseMoved if position changed - if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) { + if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) + { Event e{}; e.type = EventType::MouseMoved; e.mouseMove.x = static_cast(io.MousePos.x); @@ -158,8 +167,10 @@ void Imgui::ShowSceneView() { } // Post button presses/releases for left (0), right (1), middle (2) - for (int btn = 0; btn < 3; ++btn) { - if (ImGui::IsMouseClicked(btn)) { + for (int btn = 0; btn < 3; ++btn) + { + if (ImGui::IsMouseClicked(btn)) + { Event e{}; e.type = EventType::MouseButtonPressed; e.mouseButton.button = btn; @@ -167,7 +178,8 @@ void Imgui::ShowSceneView() { e.mouseButton.y = static_cast(io.MousePos.y); m_eventManager.post(e); } - if (ImGui::IsMouseReleased(btn)) { + if (ImGui::IsMouseReleased(btn)) + { Event e{}; e.type = EventType::MouseButtonReleased; e.mouseButton.button = btn; @@ -176,17 +188,19 @@ void Imgui::ShowSceneView() { m_eventManager.post(e); } } - } } - } else { + } + else + { ImGui::Text("No scene image"); } ImGui::End(); } -void Imgui::ShowInspector() { +void Imgui::ShowInspector() +{ ImGui::Begin("Inspector"); ImGui::SliderFloat("Exposure", &m_exposure, 0.0, 2.0); ImGui::SliderFloat("Contrast", &m_contrast, 0.0, 2.0); @@ -196,13 +210,74 @@ void Imgui::ShowInspector() { ImGui::End(); } -void Imgui::ShowConsole() { +void Imgui::ShowConsole() +{ ImGui::Begin("Console"); - ImGui::Text("Console"); + + // Add a "Clear" button to empty the console + if (ImGui::Button("Clear")) + { + if (m_consoleSink) + { + m_consoleSink->clearMessages(); + } + } + + ImGui::SameLine(); + + ImGui::Separator(); + + ImGui::BeginChild("ScrollingRegion", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); + + if (m_consoleSink) + { + // Safely access and draw messages using the thread-safe accessor + m_consoleSink->accessMessages([](const std::deque& messages) { + for (const auto& msg : messages) + { + ImVec4 color; + switch (msg.level) + { + case LogLevel::Trace: + color = ImVec4(0.6f, 0.6f, 0.6f, 1.0f); + break; // Grey + case LogLevel::Info: + color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + break; // White + case LogLevel::Warn: + color = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); + break; // Yellow + case LogLevel::Error: + color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); + break; // Red + case LogLevel::Critical: + color = ImVec4(1.0f, 0.2f, 0.2f, 1.0f); + break; // Bright Red + default: + color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + break; + } + + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(msg.message.c_str()); + ImGui::PopStyleColor(); + + } + }); + + // Auto-scroll to the bottom if the user hasn't scrolled up + if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) + { + ImGui::SetScrollHereY(1.0f); + } + } + + ImGui::EndChild(); ImGui::End(); } -void Imgui::SetupInitialDockLayout(ImGuiID dockspace_id) { +void Imgui::SetupInitialDockLayout(ImGuiID dockspace_id) +{ ImGui::DockBuilderRemoveNode(dockspace_id); // Clear existing layout ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace); ImGui::DockBuilderSetNodeSize(dockspace_id, ImGui::GetMainViewport()->Size); @@ -216,20 +291,19 @@ void Imgui::SetupInitialDockLayout(ImGuiID dockspace_id) { ImGui::DockBuilderDockWindow("Scene View", dock_main); ImGui::DockBuilderDockWindow("Inspector", dock_right); ImGui::DockBuilderDockWindow("Console", dock_bottom); - ImGui::DockBuilderDockWindow("Composite", dock_main); // or move to its own panel + ImGui::DockBuilderDockWindow("Composite", dock_main); // or move to its own panel ImGui::DockBuilderFinish(dockspace_id); } - -vk::DescriptorSet Imgui::createDescriptorSet(vk::ImageView imageView, vk::Sampler sampler) { +vk::DescriptorSet Imgui::createDescriptorSet(vk::ImageView imageView, vk::Sampler sampler) +{ // if (m_sceneImguiId) { // ImGui_ImplVulkan_RemoveTexture(m_sceneImguiId); // } return ImGui_ImplVulkan_AddTexture(sampler, imageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - } } // namespace reactor \ No newline at end of file diff --git a/src/imgui/Imgui.hpp b/src/imgui/Imgui.hpp index fa19de6..ec5c028 100644 --- a/src/imgui/Imgui.hpp +++ b/src/imgui/Imgui.hpp @@ -6,6 +6,7 @@ #define IMGUI_HPP #include "../core/Window.hpp" #include "../vulkan/VulkanContext.hpp" +#include "../logging/ImGuiConsoleSink.hpp" #include @@ -13,7 +14,7 @@ namespace reactor { class Imgui { public: - Imgui(VulkanContext& vulkanContext, Window& window, EventManager& eventManager); + Imgui(VulkanContext& vulkanContext, Window& window, EventManager& eventManager, std::shared_ptr consoleSink); ~Imgui(); void createFrame(); @@ -29,6 +30,7 @@ class Imgui { private: + std::shared_ptr m_consoleSink; vk::Device m_device; EventManager& m_eventManager; vk::DescriptorPool m_descriptorPool; diff --git a/src/logging/ImGuiConsoleSink.cpp b/src/logging/ImGuiConsoleSink.cpp new file mode 100644 index 0000000..15e9fda --- /dev/null +++ b/src/logging/ImGuiConsoleSink.cpp @@ -0,0 +1,31 @@ +#include "ImGuiConsoleSink.hpp" + +namespace reactor +{ + +ImGuiConsoleSink::ImGuiConsoleSink(size_t maxMessages) : m_maxMessages(maxMessages) {} + +void ImGuiConsoleSink::log(const LogMessage& msg) +{ + std::lock_guard lock(m_mutex); + m_messages.push_back(msg); + // Trim the queue if it exceeds the maximum size + if (m_messages.size() > m_maxMessages) + { + m_messages.pop_front(); + } +} + +void ImGuiConsoleSink::accessMessages(const std::function&)>& accessor) const +{ + std::lock_guard lock(m_mutex); + accessor(m_messages); +} + +void ImGuiConsoleSink::clearMessages() +{ + std::lock_guard lock(m_mutex); + m_messages.clear(); +} + +} // namespace reactor \ No newline at end of file diff --git a/src/logging/ImGuiConsoleSink.hpp b/src/logging/ImGuiConsoleSink.hpp new file mode 100644 index 0000000..6184a38 --- /dev/null +++ b/src/logging/ImGuiConsoleSink.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "Logger.hpp" + +namespace reactor +{ + +class ImGuiConsoleSink : public ILogSink +{ +public: + explicit ImGuiConsoleSink(size_t maxMessages = 2000); + void log(const LogMessage& msg) override; + + // Allows the UI thread to safely iterate over the collected messages + void accessMessages(const std::function&)>& accessor) const; + + // Clears all messages from the console + void clearMessages(); + +private: + std::deque m_messages; + size_t m_maxMessages; + mutable std::mutex m_mutex; // `mutable` allows locking in const methods +}; + +} // namespace reactor \ No newline at end of file diff --git a/src/logging/Logger.hpp b/src/logging/Logger.hpp new file mode 100644 index 0000000..d1b2b9a --- /dev/null +++ b/src/logging/Logger.hpp @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +// Use the fmt library for powerful and safe string formatting +#include + +namespace reactor +{ + +// Defines the severity of a log message +enum class LogLevel +{ + Trace, + Info, + Warn, + Error, + Critical +}; + +// Represents a single log entry +struct LogMessage +{ + std::string message; + LogLevel level; +}; + +// Abstract interface for a "sink" that receives log messages. +class ILogSink +{ + public: + virtual ~ILogSink() = default; + virtual void log(const LogMessage& msg) = 0; +}; + +// The main Logger class, implemented as a singleton for global access. +class Logger +{ + public: + // Make the singleton non-copyable and non-movable + Logger(const Logger&) = delete; + Logger& operator=(const Logger&) = delete; + Logger(Logger&&) = delete; + Logger& operator=(Logger&&) = delete; + + // Access the single instance of the logger + static Logger& getInstance() + { + static Logger instance; + return instance; + } + + // Attach a new sink to the logger + void addSink(std::shared_ptr sink) + { + std::lock_guard lock(m_mutex); + m_sinks.push_back(std::move(sink)); + } + + // Template functions for logging with various arguments + template + void trace(fmt::format_string format, Args&&... args) + { + log(LogLevel::Trace, fmt::format(format, std::forward(args)...)); + } + + template + void info(fmt::format_string format, Args&&... args) + { + log(LogLevel::Info, fmt::format(format, std::forward(args)...)); + } + + template + void warn(fmt::format_string format, Args&&... args) + { + log(LogLevel::Warn, fmt::format(format, std::forward(args)...)); + } + + template + void error(fmt::format_string format, Args&&... args) + { + log(LogLevel::Error, fmt::format(format, std::forward(args)...)); + } + + template + void critical(fmt::format_string format, Args&&... args) + { + log(LogLevel::Critical, fmt::format(format, std::forward(args)...)); + } + + private: + // Private constructor for singleton pattern + Logger() = default; + ~Logger() = default; + + // The core log function that distributes messages to sinks + void log(LogLevel level, const std::string& msg) + { + std::lock_guard lock(m_mutex); + LogMessage logMsg{msg, level}; + for (const auto& sink : m_sinks) + { + if (sink) + { + sink->log(logMsg); + } + } + } + + std::vector> m_sinks; + std::mutex m_mutex; +}; + +} // namespace reactor + +// Helper macros to make logging calls cleaner and less verbose +#define LOG_TRACE(...) reactor::Logger::getInstance().trace(__VA_ARGS__) +#define LOG_INFO(...) reactor::Logger::getInstance().info(__VA_ARGS__) +#define LOG_WARN(...) reactor::Logger::getInstance().warn(__VA_ARGS__) +#define LOG_ERROR(...) reactor::Logger::getInstance().error(__VA_ARGS__) +#define LOG_CRITICAL(...) reactor::Logger::getInstance().critical(__VA_ARGS__) \ No newline at end of file diff --git a/src/logging/SpdlogSink.cpp b/src/logging/SpdlogSink.cpp new file mode 100644 index 0000000..f45afca --- /dev/null +++ b/src/logging/SpdlogSink.cpp @@ -0,0 +1,36 @@ +#include "SpdlogSink.hpp" +#include + +namespace reactor +{ + +SpdlogSink::SpdlogSink() +{ + // Initialize a thread-safe color logger that outputs to the console + m_spdlogLogger = spdlog::stdout_color_mt("console"); +} + +void SpdlogSink::log(const LogMessage& msg) +{ + // Map our LogLevel to the corresponding spdlog function + switch (msg.level) + { + case LogLevel::Trace: + m_spdlogLogger->trace(msg.message); + break; + case LogLevel::Info: + m_spdlogLogger->info(msg.message); + break; + case LogLevel::Warn: + m_spdlogLogger->warn(msg.message); + break; + case LogLevel::Error: + m_spdlogLogger->error(msg.message); + break; + case LogLevel::Critical: + m_spdlogLogger->critical(msg.message); + break; + } +} + +} // namespace reactor \ No newline at end of file diff --git a/src/logging/SpdlogSink.hpp b/src/logging/SpdlogSink.hpp new file mode 100644 index 0000000..146e9ea --- /dev/null +++ b/src/logging/SpdlogSink.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "Logger.hpp" +#include + +namespace reactor +{ + +class SpdlogSink : public ILogSink +{ +public: + SpdlogSink(); + void log(const LogMessage& msg) override; + +private: + std::shared_ptr m_spdlogLogger; +}; + +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/ShadowMapping.cpp b/src/vulkan/ShadowMapping.cpp index ccf41b8..a4ff3e8 100644 --- a/src/vulkan/ShadowMapping.cpp +++ b/src/vulkan/ShadowMapping.cpp @@ -2,6 +2,7 @@ #include "VulkanRenderer.hpp" #include "../core/Uniforms.hpp" +#include "../logging/Logger.hpp" namespace reactor { @@ -87,7 +88,7 @@ void ShadowMapping::createResources() void ShadowMapping::createPipeline() { - spdlog::info("Creating shadow mapping pipeline"); + LOG_INFO("Creating shadow mapping pipeline"); auto device = m_renderer.device(); diff --git a/src/vulkan/VulkanContext.cpp b/src/vulkan/VulkanContext.cpp index 2c0a863..fba76ba 100644 --- a/src/vulkan/VulkanContext.cpp +++ b/src/vulkan/VulkanContext.cpp @@ -2,7 +2,7 @@ #include -#include +#include "../logging/Logger.hpp" namespace { void printVulkanVersion() { @@ -11,7 +11,7 @@ void printVulkanVersion() { uint32_t major = VK_VERSION_MAJOR(vulkanApiVersion); uint32_t minor = VK_VERSION_MINOR(vulkanApiVersion); uint32_t patch = VK_VERSION_PATCH(vulkanApiVersion); - spdlog::info("Vulkan API version: {}.{}.{}", major, minor, patch); + LOG_INFO("Vulkan API version: {}.{}.{}", major, minor, patch); } else { spdlog::error("Failed to enumerate Vulkan API version"); } diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index dcd1521..1416038 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -5,6 +5,9 @@ #include "../core/Window.hpp" #include "ImageUtils.hpp" #include "VulkanUtils.hpp" +#include "../logging/SpdlogSink.hpp" +#include "../logging/ImGuiConsoleSink.hpp" +#include "../logging/Logger.hpp" #include #include @@ -14,6 +17,15 @@ namespace reactor VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Camera& camera) : m_config(config), m_window(window), m_camera(camera) { + + auto imguiConsoleSink = std::make_shared(); + auto spdlogSing = std::make_shared(); + + Logger::getInstance().addSink(imguiConsoleSink); + Logger::getInstance().addSink(spdlogSing); + + LOG_INFO("Created the logger"); + createCoreVulkanObjects(); createSwapchainAndFrameManager(); @@ -25,7 +37,7 @@ VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Cam createDescriptorPool(); createPipelineAndDescriptors(); - setupUI(); + setupUI(imguiConsoleSink); createMSAAImage(); createResolveImages(); createSceneViewImages(); @@ -151,9 +163,9 @@ void VulkanRenderer::handleSwapchainResizing() } } -void VulkanRenderer::setupUI() +void VulkanRenderer::setupUI(std::shared_ptr consoleSink) { - m_imgui = std::make_unique(*m_context, m_window, m_window.getEventManager()); + m_imgui = std::make_unique(*m_context, m_window, m_window.getEventManager(), consoleSink); } VulkanRenderer::~VulkanRenderer() diff --git a/src/vulkan/VulkanRenderer.hpp b/src/vulkan/VulkanRenderer.hpp index e297c4a..80a9a46 100644 --- a/src/vulkan/VulkanRenderer.hpp +++ b/src/vulkan/VulkanRenderer.hpp @@ -94,7 +94,7 @@ class VulkanRenderer void createCoreVulkanObjects(); void createSwapchainAndFrameManager(); void createPipelineAndDescriptors(); - void setupUI(); + void setupUI(std::shared_ptr consoleSink); void createMSAAImage(); void createResolveImages(); void createSceneViewImages(); From 86cc4e2e757f9f9829fc85567ce16b3562c749cf Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Thu, 7 Aug 2025 22:44:32 -0500 Subject: [PATCH 02/24] switched to Slang! --- CMakeLists.txt | 1 + build-shaders.bat | 5 +- shaders/triangle.slang | 157 ++++++++++++++++++++++++++++++++++ src/core/Application.cpp | 4 +- src/pch.hpp | 1 + src/tools/ModelBuilder.cpp | 4 +- src/vulkan/DebugUtils.hpp | 48 +++++++++++ src/vulkan/VulkanContext.cpp | 9 ++ src/vulkan/VulkanContext.hpp | 2 + src/vulkan/VulkanRenderer.cpp | 114 ++++++++++++++---------- 10 files changed, 296 insertions(+), 49 deletions(-) create mode 100644 shaders/triangle.slang create mode 100644 src/vulkan/DebugUtils.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b494105..f3f04b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,7 @@ add_library(ReactorLib STATIC src/logging/SpdlogSink.hpp src/logging/ImGuiConsoleSink.cpp src/logging/ImGuiConsoleSink.hpp + src/vulkan/DebugUtils.hpp ) target_compile_definitions(ReactorLib PUBLIC GLM_ENABLE_EXPERIMENTAL) diff --git a/build-shaders.bat b/build-shaders.bat index 23a5754..8b0427d 100644 --- a/build-shaders.bat +++ b/build-shaders.bat @@ -2,4 +2,7 @@ glslc -g --target-env=vulkan1.3 -o resources/shaders/triangle.vert.spv shaders/t glslc -g --target-env=vulkan1.3 -o resources/shaders/triangle.frag.spv shaders/triangle.frag glslc -g --target-env=vulkan1.3 -o resources/shaders/composite.vert.spv shaders/composite.vert -glslc -g --target-env=vulkan1.3 -o resources/shaders/composite.frag.spv shaders/composite.frag \ No newline at end of file +glslc -g --target-env=vulkan1.3 -o resources/shaders/composite.frag.spv shaders/composite.frag + +slangc -g shaders/triangle.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/triangle-slang.vert.spv +slangc -g shaders/triangle.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/triangle-slang.frag.spv \ No newline at end of file diff --git a/shaders/triangle.slang b/shaders/triangle.slang new file mode 100644 index 0000000..d2228b2 --- /dev/null +++ b/shaders/triangle.slang @@ -0,0 +1,157 @@ +// Combined Slang Shader Module +// triangle.slang + +// ========================================================================= +// 1. Data Structures & Resource Bindings +// ========================================================================= + +/** + * Defines the input attributes for a single vertex. + * These correspond to the `layout(location = ...)` inputs in the GLSL + * vertex shader. Semantics like `POSITION` and `NORMAL` are used instead + * of raw location indices. + */ +struct VertexInput +{ + float3 position : POSITION; // Corresponds to GLSL: layout(location = 0) in vec3 inPosition; + float3 normal : NORMAL; // Corresponds to GLSL: layout(location = 1) in vec3 inNormal; + float3 color : COLOR; // Corresponds to GLSL: layout(location = 2) in vec3 inColor; + float2 texCoord : TEXCOORD0; // Corresponds to GLSL: layout(location = 3) in vec2 inTexCoord; +}; + +/** + * Defines the data passed from the vertex shader to the fragment shader. + * This replaces the individual `out` variables in the vertex shader and + * `in` variables in the fragment shader. `SV_Position` is a system-value + * semantic required for the final clip-space position. + */ +struct Varyings +{ + float4 position : SV_Position; // The final position for the rasterizer. + float3 worldPos : WORLD_POS; // Corresponds to GLSL: outWorldPos + float3 normal : NORMAL; // Corresponds to GLSL: outNormal + float4 lightSpacePos : LIGHT_SPACE_POS; // Corresponds to GLSL: outLightSpacePos +}; + +/** + * Per-scene constants, equivalent to the `SceneUBO` in GLSL. + * The `register(b0)` attribute maps this buffer to binding 0. + */ +[[vk::binding(0, 0)]] +cbuffer SceneUBO : register(b0) +{ + matrix view; + matrix projection; + matrix lightSpaceMatrix; +}; + +/** + * Per-light constants, equivalent to the `LightUBO` in GLSL. + * The `register(b1)` attribute maps this buffer to binding 1. + */ +[[vk::binding(1, 0)]] +cbuffer LightUBO : register(b1) +{ + float4 lightDirection; + float4 lightColor; + float lightIntensity; +}; + +/** + * Push constants for per-draw call data, like the model matrix. + */ +[push_constant] +cbuffer PushConstants +{ + matrix model; +}; + +/** + * In GLSL, `sampler2DShadow` is a combined type. In Slang (and HLSL), + * the texture and its sampler state are typically defined separately for + * greater flexibility. We bind them to the same slot index `2` but on + * different resource types (`t` for texture, `s` for sampler). + */ +[[vk::binding(2, 0)]] +Texture2D shadowMap : register(t2); + +[[vk::binding(3, 0)]] +SamplerComparisonState shadowSampler : register(s2); + + +// ========================================================================= +// 2. Shadow Calculation +// ========================================================================= + +/** + * Calculates the shadow contribution. This logic is identical to the + * GLSL `calculateShadow` function, but it now takes the light-space + * position as a parameter instead of reading it from a global input. + */ +float calculateShadow(Varyings input) +{ + // Perform perspective divide + float3 projCoords = input.lightSpacePos.xyz / input.lightSpacePos.w; + + // Convert from [-1, 1] to [0, 1] texture coordinates + projCoords.xy = projCoords.xy * 0.5 + 0.5; + + // Sample the shadow map using a comparison sampler. + // The `SampleCmp` function performs the depth test (projCoords.z) + // against the value in the shadow map at projCoords.xy. + // This is the Slang equivalent of `texture(shadowMap, projCoords)`. + return shadowMap.SampleCmp(shadowSampler, projCoords.xy, projCoords.z); +} + + +// ========================================================================= +// 3. Vertex Shader Stage +// ========================================================================= + +[shader("vertex")] +Varyings vertexMain(VertexInput input) +{ + Varyings output; + + // Transform position and normal to world space + float4 worldPos = mul(model, float4(input.position, 1.0)); + output.worldPos = worldPos.xyz; + output.normal = normalize(mul((float3x3)model, input.normal)); + + // Calculate the final clip-space position + output.position = mul(projection, mul(view, worldPos)); + + // Transform world position to light's view space for shadowing + output.lightSpacePos = mul(lightSpaceMatrix, worldPos); + + return output; +} + + +// ========================================================================= +// 4. Fragment Shader Stage +// ========================================================================= + +[shader("fragment")] +float4 fragmentMain(Varyings input) : SV_Target +{ + float3 objectColor = float3(0.8, 0.8, 0.8); + + // Ambient light component + float3 ambient = objectColor * 0.1; + + // Calculate the diffuse (directional) light component + float3 normal = normalize(input.normal); + float3 lightDir = normalize(-lightDirection.xyz); + float diffuseFactor = max(dot(normal, lightDir), 0.0); + float3 diffuse = objectColor * lightColor.rgb * lightIntensity * diffuseFactor; + + // Get the shadow factor (1.0 for lit, < 1.0 for shadowed) + float shadow = calculateShadow(input); + + // Combine components: ambient light is always present, but diffuse + // light is affected by the shadow factor. + float3 finalColor = ambient + (diffuse * shadow); + + return float4(finalColor, 1.0); +} \ No newline at end of file diff --git a/src/core/Application.cpp b/src/core/Application.cpp index 1850352..02ba8b5 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -25,8 +25,8 @@ void Application::initialize() { .windowWidth = 1280, .windowHeight = 720, .windowTitle = "Reactor", - .vertShaderPath = "../resources/shaders/triangle.vert.spv", - .fragShaderPath = "../resources/shaders/triangle.frag.spv", + .vertShaderPath = "../resources/shaders/triangle-slang.vert.spv", + .fragShaderPath = "../resources/shaders/triangle-slang.frag.spv", .compositeVertShaderPath = "../resources/shaders/composite.vert.spv", .compositeFragShaderPath = "../resources/shaders/composite.frag.spv" }; diff --git a/src/pch.hpp b/src/pch.hpp index c8dbb98..559eb8a 100644 --- a/src/pch.hpp +++ b/src/pch.hpp @@ -12,6 +12,7 @@ #include #include +#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 #include #include #include diff --git a/src/tools/ModelBuilder.cpp b/src/tools/ModelBuilder.cpp index ad2ae8a..d48559b 100644 --- a/src/tools/ModelBuilder.cpp +++ b/src/tools/ModelBuilder.cpp @@ -5,8 +5,8 @@ int main() { // Define input and output paths - const std::string inputModel = "../workspace/monkey.obj"; - const std::string outputModel = "./monkey.mesh"; + const std::string inputModel = "../workspace/thingy.obj"; + const std::string outputModel = "../resources/models/thingy.mesh"; // Run the import and export process if(reactor::importAndExport(inputModel, outputModel)) { diff --git a/src/vulkan/DebugUtils.hpp b/src/vulkan/DebugUtils.hpp new file mode 100644 index 0000000..eda1101 --- /dev/null +++ b/src/vulkan/DebugUtils.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +#ifndef NDEBUG + const bool g_enableDebugMarkers = true; +#else +const bool g_enableDebugMarkers = false; +#endif + +namespace reactor::Debug +{ + +// Sets the name of any Vulkan object +inline void setObjectName(vk::Device device, uint64_t object, vk::ObjectType objectType, const std::string& name) { + if (!g_enableDebugMarkers) return; + + vk::DebugUtilsObjectNameInfoEXT nameInfo{}; + nameInfo.objectType = objectType; + nameInfo.objectHandle = object; + nameInfo.pObjectName = name.c_str(); + + // No need for dynamic dispatch here, as device functions are loaded with the device + device.setDebugUtilsObjectNameEXT(nameInfo); +} + +// Begins a labeled region in a command buffer +inline void beginLabel(vk::CommandBuffer cmd, const std::string& label, const std::array& color = {1.0f, 1.0f, 1.0f, 1.0f}) { + if (!g_enableDebugMarkers) return; + + vk::DebugUtilsLabelEXT labelInfo{}; + labelInfo.pLabelName = label.c_str(); + labelInfo.color[0] = color[0]; + labelInfo.color[1] = color[1]; + labelInfo.color[2] = color[2]; + labelInfo.color[3] = color[3]; + + cmd.beginDebugUtilsLabelEXT(labelInfo); +} + +// Ends a labeled region in a command buffer +inline void endLabel(vk::CommandBuffer cmd) { + if (!g_enableDebugMarkers) return; + cmd.endDebugUtilsLabelEXT(); +} + +} // namespace reactor::Debug \ No newline at end of file diff --git a/src/vulkan/VulkanContext.cpp b/src/vulkan/VulkanContext.cpp index fba76ba..24376c1 100644 --- a/src/vulkan/VulkanContext.cpp +++ b/src/vulkan/VulkanContext.cpp @@ -4,6 +4,8 @@ #include "../logging/Logger.hpp" +VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE + namespace { void printVulkanVersion() { uint32_t vulkanApiVersion = 0; @@ -36,6 +38,9 @@ VulkanContext::~VulkanContext() { } void VulkanContext::createInstance() { + + VULKAN_HPP_DEFAULT_DISPATCHER.init( ); + constexpr vk::ApplicationInfo appInfo { "Reactor App", 1, @@ -56,6 +61,8 @@ void VulkanContext::createInstance() { extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); #endif + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + vk::InstanceCreateInfo createInfo {}; createInfo.pApplicationInfo = &appInfo; createInfo.setPEnabledExtensionNames(extensions); @@ -68,6 +75,7 @@ void VulkanContext::createInstance() { m_instance = vk::createInstance(createInfo); spdlog::info("Vulkan instance created"); printVulkanVersion(); + VULKAN_HPP_DEFAULT_DISPATCHER.init( m_instance ); } catch (const vk::SystemError& err) { std::cerr << "Failed to create Vulkan instance: " << err.what() << std::endl; @@ -194,6 +202,7 @@ void VulkanContext::createLogicalDevice() { m_presentQueue = m_device.getQueue(indices.presentFamily.value(), 0); m_queueFamilies = indices; // Store the found queue families + m_dldi = vk::detail::DispatchLoaderDynamic(m_instance, vkGetInstanceProcAddr, m_device, vkGetDeviceProcAddr); spdlog::info("Logical device created"); } catch (const vk::SystemError& err) { std::cerr << "Failed to create logical device: " << err.what() << std::endl; diff --git a/src/vulkan/VulkanContext.hpp b/src/vulkan/VulkanContext.hpp index 693cca2..16d08f2 100644 --- a/src/vulkan/VulkanContext.hpp +++ b/src/vulkan/VulkanContext.hpp @@ -33,6 +33,7 @@ class VulkanContext { [[nodiscard]] vk::Queue graphicsQueue() const { return m_graphicsQueue; } [[nodiscard]] vk::Queue presentQueue() const { return m_presentQueue; } [[nodiscard]] QueueFamilyIndices queueFamilies() const { return m_queueFamilies; } + [[nodiscard]] const vk::detail::DispatchLoaderDynamic& dldi() const { return m_dldi; } private: // Private helper methods to keep the constructor clean @@ -49,6 +50,7 @@ class VulkanContext { vk::SurfaceKHR m_surface; vk::PhysicalDevice m_physicalDevice; vk::Device m_device; + vk::detail::DispatchLoaderDynamic m_dldi; // Queues are retrieved from the logical device vk::Queue m_graphicsQueue; diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 1416038..49ad348 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -3,11 +3,12 @@ #include "../core/ModelIO.hpp" #include "../core/Uniforms.hpp" #include "../core/Window.hpp" -#include "ImageUtils.hpp" -#include "VulkanUtils.hpp" -#include "../logging/SpdlogSink.hpp" #include "../logging/ImGuiConsoleSink.hpp" #include "../logging/Logger.hpp" +#include "../logging/SpdlogSink.hpp" +#include "DebugUtils.hpp" +#include "ImageUtils.hpp" +#include "VulkanUtils.hpp" #include #include @@ -93,23 +94,35 @@ void VulkanRenderer::createSwapchainAndFrameManager() void VulkanRenderer::createDescriptorPool() { std::vector poolSizes = {{vk::DescriptorType::eUniformBuffer, 32}, - {vk::DescriptorType::eCombinedImageSampler, 32}}; + {vk::DescriptorType::eCombinedImageSampler, 32}, + {vk::DescriptorType::eSampledImage, 32}, + {vk::DescriptorType::eSampler, 32} + }; vk::DescriptorPoolCreateInfo poolInfo(vk::DescriptorPoolCreateFlags(), 128, poolSizes.size(), poolSizes.data()); m_descriptorPool = m_context->device().createDescriptorPool(poolInfo); } - void VulkanRenderer::createPipelineAndDescriptors() { const std::string vertShaderPath = m_config.vertShaderPath; const std::string fragShaderPath = m_config.fragShaderPath; const std::vector bindings = { + // Binding 0: Scene UBO (Vertex Shader) vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex), + + // Binding 1: Light UBO (Fragment Shader) vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment), - vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), + + // Binding 2: Shadow Map Texture (Fragment Shader) + vk::DescriptorSetLayoutBinding( + 2, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), + + // Binding 3: Shadow Map Sampler + vk::DescriptorSetLayoutBinding( + 3, vk::DescriptorType::eSampler, 1, vk::ShaderStageFlagBits::eFragment), }; m_descriptorSet = std::make_unique(m_context->device(), m_descriptorPool, 2, bindings); const std::vector setLayouts = {m_descriptorSet->getLayout()}; @@ -126,6 +139,11 @@ void VulkanRenderer::createPipelineAndDescriptors() .addPushContantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)) .build(); + Debug::setObjectName(m_context->device(), + (uint64_t)(VkPipeline)m_pipeline->get(), + vk::ObjectType::ePipeline, + "Main Geometry Pipeline"); + const std::vector compositeBindings = { vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), @@ -134,7 +152,8 @@ void VulkanRenderer::createPipelineAndDescriptors() 2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), }; - m_compositeDescriptorSet = std::make_unique(m_context->device(), m_descriptorPool, 2, compositeBindings); + m_compositeDescriptorSet = + std::make_unique(m_context->device(), m_descriptorPool, 2, compositeBindings); std::vector compositeSetLayouts = {m_compositeDescriptorSet->getLayout()}; m_compositePipeline = Pipeline::Builder(m_context->device()) @@ -336,18 +355,13 @@ void VulkanRenderer::drawFrame() glm::vec3 lightTarget = glm::vec3(0.0f); float lightDistance = 20.0f; glm::vec3 lightPosition = lightTarget - glm::vec3(m_light.lightDirection) * lightDistance; - glm::mat4 lightView = glm::lookAt( - lightPosition, // Position of the light in world space - lightTarget, // The point the light is looking at (scene origin) - glm::vec3(0.0f, 1.0f, 0.0f) // Up vector + glm::mat4 lightView = glm::lookAt(lightPosition, // Position of the light in world space + lightTarget, // The point the light is looking at (scene origin) + glm::vec3(0.0f, 1.0f, 0.0f) // Up vector ); glm::mat4 clipCorrection = { - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f,-1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 0.5f, 0.0f, - 0.0f, 0.0f, 0.5f, 1.0f - }; + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 1.0f}; glm::mat4 lightSpaceMatrix = clipCorrection * lightProjection * lightView; @@ -366,10 +380,13 @@ void VulkanRenderer::drawFrame() vk::DescriptorBufferInfo sceneBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); vk::DescriptorBufferInfo lightBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); - vk::DescriptorImageInfo shadowMapImageInfo = {}; - shadowMapImageInfo.sampler = m_shadowMapping->shadowMapSampler(); - shadowMapImageInfo.imageView = m_shadowMapping->shadowMapView(); - shadowMapImageInfo.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal; + vk::DescriptorImageInfo shadowMapTextureInfo = {}; + // shadowMapImageInfo.sampler = m_shadowMapping->shadowMapSampler(); + shadowMapTextureInfo.imageView = m_shadowMapping->shadowMapView(); + shadowMapTextureInfo.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal; + + vk::DescriptorImageInfo shadowMapSamplerInfo = {}; + shadowMapSamplerInfo.sampler = m_shadowMapping->shadowMapSampler(); // Create write for Scene UBO at binding 0 vk::WriteDescriptorSet sceneWrite{}; @@ -387,17 +404,25 @@ void VulkanRenderer::drawFrame() lightWrite.descriptorCount = 1; lightWrite.pBufferInfo = &lightBufferInfo; - vk::WriteDescriptorSet shadowMapWrite{}; - shadowMapWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); - shadowMapWrite.dstBinding = 2; // Target binding 2 - shadowMapWrite.descriptorType = vk::DescriptorType::eCombinedImageSampler; - shadowMapWrite.descriptorCount = 1; - shadowMapWrite.pImageInfo = &shadowMapImageInfo; + vk::WriteDescriptorSet shadowMapTextureWrite{}; + shadowMapTextureWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); + shadowMapTextureWrite.dstBinding = 2; // Target binding 2 + shadowMapTextureWrite.descriptorType = vk::DescriptorType::eSampledImage; + shadowMapTextureWrite.descriptorCount = 1; + shadowMapTextureWrite.pImageInfo = &shadowMapTextureInfo; + + vk::WriteDescriptorSet shadowMapSamplerWrite{}; + shadowMapSamplerWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); + shadowMapSamplerWrite.dstBinding = 3; // Target binding 3 + shadowMapSamplerWrite.descriptorType = vk::DescriptorType::eSampler; + shadowMapSamplerWrite.descriptorCount = 1; + shadowMapSamplerWrite.pImageInfo = &shadowMapSamplerInfo; - m_descriptorSet->updateSet({sceneWrite, lightWrite, shadowMapWrite}); + m_descriptorSet->updateSet({sceneWrite, lightWrite, shadowMapTextureWrite, shadowMapSamplerWrite}); beginCommandBuffer(cmd); + Debug::beginLabel(cmd, "Depth Prepass", {0.8f, 0.4f, 0.2f, 1.0}); // get depth image view for this frame vk::ImageView depthView = m_depthViews[frameIdx]; @@ -416,24 +441,25 @@ void VulkanRenderer::drawFrame() cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_depthPipeline->get()); drawGeometry(cmd); endDynamicRendering(cmd); + Debug::endLabel(cmd); - - - //m_shadowMapping->setLightMatrix(lightMVP, frameIdx); - auto drawFunc = [this](vk::CommandBuffer cmd) { - this->drawGeometry(cmd); - }; + Debug::beginLabel(cmd, "Shadow Pass", {0.8f, 0.4f, 0.2f, 1.0}); + auto drawFunc = [this](vk::CommandBuffer cmd) { this->drawGeometry(cmd); }; m_shadowMapping->recordShadowPass(cmd, frameIdx, drawFunc); m_imageStateTracker.transition(cmd, - m_shadowMapping->shadowMapImage(), - vk::ImageLayout::eDepthStencilReadOnlyOptimal, - vk::PipelineStageFlagBits::eLateFragmentTests, - vk::PipelineStageFlagBits::eFragmentShader, - vk::AccessFlagBits::eDepthStencilAttachmentWrite, - vk::AccessFlagBits::eShaderRead, - vk::ImageAspectFlagBits::eDepth); + m_shadowMapping->shadowMapImage(), + vk::ImageLayout::eDepthStencilReadOnlyOptimal, + vk::PipelineStageFlagBits::eLateFragmentTests, + vk::PipelineStageFlagBits::eFragmentShader, + vk::AccessFlagBits::eDepthStencilAttachmentWrite, + vk::AccessFlagBits::eShaderRead, + vk::ImageAspectFlagBits::eDepth); + + Debug::endLabel(cmd); + + Debug::beginLabel(cmd, "Main Pass", {0.8f, 0.4f, 0.2f, 1.0}); // --- 1. Geometry Pass --- // Transition the MSAA image so we can render the main scene into it. @@ -518,6 +544,8 @@ void VulkanRenderer::drawFrame() vk::AccessFlagBits::eDepthStencilAttachmentRead, vk::ImageAspectFlagBits::eDepth); + Debug::endLabel(cmd); + beginDynamicRendering(cmd, m_sceneViewViews[frameIdx], nullptr, extent, true); utils::setupViewportAndScissor(cmd, extent); @@ -705,8 +733,6 @@ void VulkanRenderer::createSampler() void VulkanRenderer::createDescriptorSets() { - - const auto framesInFlight = m_frameManager->getFramesInFlightCount(); m_sceneViewImageDescriptorSets.resize(framesInFlight); for (int i = 0; i < framesInFlight; ++i) @@ -768,9 +794,9 @@ void VulkanRenderer::initScene() auto planeVerts = generatePlaneVertices(10, 50.0f); auto planeInds = generatePlaneIndices(10); auto planeMesh = std::make_shared(*m_allocator, planeVerts, planeInds); - m_objects.push_back({planeMesh, glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -0.5f, 0.0f))}); + m_objects.push_back({planeMesh, glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -0.0f, 0.0f))}); - auto meshDataVec = loadModelFromBinary("monkey.mesh"); + auto meshDataVec = loadModelFromBinary("../resources/models/thingy.mesh"); if (!meshDataVec.empty()) { From 5db3c266209f26ed9c61f6e08217a03326f5f1d3 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Fri, 8 Aug 2025 13:14:15 -0500 Subject: [PATCH 03/24] switch composite to slang --- build-shaders.bat | 5 +- shaders/composite.slang | 107 ++++++++++++++++++++++++++++++++++ src/core/Application.cpp | 4 +- src/vulkan/VulkanRenderer.cpp | 80 ++++++++++++------------- 4 files changed, 153 insertions(+), 43 deletions(-) create mode 100644 shaders/composite.slang diff --git a/build-shaders.bat b/build-shaders.bat index 8b0427d..a6c5e87 100644 --- a/build-shaders.bat +++ b/build-shaders.bat @@ -5,4 +5,7 @@ glslc -g --target-env=vulkan1.3 -o resources/shaders/composite.vert.spv shaders/ glslc -g --target-env=vulkan1.3 -o resources/shaders/composite.frag.spv shaders/composite.frag slangc -g shaders/triangle.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/triangle-slang.vert.spv -slangc -g shaders/triangle.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/triangle-slang.frag.spv \ No newline at end of file +slangc -g shaders/triangle.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/triangle-slang.frag.spv + +slangc -g shaders/composite.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/composite-slang.vert.spv +slangc -g shaders/composite.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/composite-slang.frag.spv \ No newline at end of file diff --git a/shaders/composite.slang b/shaders/composite.slang new file mode 100644 index 0000000..3988e2a --- /dev/null +++ b/shaders/composite.slang @@ -0,0 +1,107 @@ +struct Varyings +{ + float4 position : SV_Position; // System value for clip space position + float2 uv : TEXCOORD0; // The UV coordinates for sampling +}; + +/** + * The C++ code in `VulkanRenderer.cpp` creates a multisampled depth + * image with 4 samples. We explicitly define that here. + */ +static const int MSAA_SAMPLES = 4; + +[[vk::binding(0, 0)]] +Texture2D uInputImage : register(t0); + +[[vk::binding(3, 0)]] +SamplerState g_sampler : register(s3); + +[[vk::binding(2, 0)]] +Texture2DMS uDepthImage : register(t2); + +[[vk::binding(1, 0)]] +cbuffer CompositeParams : register(b1) +{ + float uExposure; + float uContrast; + float uSaturation; + float uVignetteIntensity; + float uVignetteFalloff; + float uFogDensity; +}; + +float linearizeDepth(float depth) +{ + const float Z_NEAR = 0.1; + const float Z_FAR = 100.0; + float z_n = 2.0 * depth - 1.0; + return (2.0 * Z_NEAR * Z_FAR) / (Z_FAR + Z_NEAR - z_n * (Z_FAR - Z_NEAR)); +} + +float3 tonemapACES(float3 x) +{ + const float a = 2.51; + const float b = 0.03; + const float c = 2.43; + const float d = 0.59; + const float e = 0.14; + return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0); +} + +[shader("vertex")] +Varyings vertexMain(uint vertexID : SV_VertexID) +{ + Varyings output; + + // Defines the triangle vertices to cover the screen[cite: 13]. + const float2 pos[3] = { + float2(-1.0, -1.0), + float2( 3.0, -1.0), + float2(-1.0, 3.0) + }; + // Defines the UV coordinates for the triangle[cite: 14]. + const float2 uv[3] = { + float2(0.0, 0.0), + float2(2.0, 0.0), + float2(0.0, 2.0) + }; + + output.uv = uv[vertexID]; + output.position = float4(pos[vertexID], 0.0, 1.0); + + return output; +} + +[shader("fragment")] +float4 fragmentMain(Varyings input) : SV_Target +{ + // Get the integer coordinates for the current fragment. + int2 texelCoord = int2(input.position.xy); + + // To read from a Texture2DMS, use the `.Load()` method. + // It takes the coordinate and the sample index (we'll just use the first). + // This replaces `texelFetch(uDepthImage, texelCoord, 0)`. + float depth = uDepthImage.Load(texelCoord, 0).r; + + // --- Fog Calculation --- + float3 fogColor = float3(0.5, 0.6, 0.7); + float viewDistance = linearizeDepth(depth); + float fogFactor = 1.0 - exp(-viewDistance * uFogDensity); + + // --- Color Processing --- + // To sample a standard texture, use the `.Sample()` method. + // This replaces `texture(uInputImage, fragUV)`. + float3 hdrColor = uInputImage.Sample(g_sampler, input.uv).rgb; + + // Apply exposure, tonemapping, and color grading[cite: 35, 36, 37, 38, 39, 40]. + float3 exposedColor = hdrColor * uExposure; + float3 ldrColor = tonemapACES(exposedColor); + float3 finalColor = ldrColor; + + finalColor = pow(finalColor, float3(uContrast)); + float3 grayscale = float3(dot(finalColor, float3(0.299, 0.587, 0.114))); + finalColor = lerp(grayscale, finalColor, uSaturation); + finalColor = lerp(finalColor, fogColor, fogFactor); + + return float4(finalColor, 1.0); +} \ No newline at end of file diff --git a/src/core/Application.cpp b/src/core/Application.cpp index 02ba8b5..1ab9ff2 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -27,8 +27,8 @@ void Application::initialize() { .windowTitle = "Reactor", .vertShaderPath = "../resources/shaders/triangle-slang.vert.spv", .fragShaderPath = "../resources/shaders/triangle-slang.frag.spv", - .compositeVertShaderPath = "../resources/shaders/composite.vert.spv", - .compositeFragShaderPath = "../resources/shaders/composite.frag.spv" + .compositeVertShaderPath = "../resources/shaders/composite-slang.vert.spv", + .compositeFragShaderPath = "../resources/shaders/composite-slang.frag.spv" }; m_renderer = std::make_unique(config, *m_window, *m_camera); diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 49ad348..eb1f40a 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -95,9 +95,8 @@ void VulkanRenderer::createDescriptorPool() { std::vector poolSizes = {{vk::DescriptorType::eUniformBuffer, 32}, {vk::DescriptorType::eCombinedImageSampler, 32}, - {vk::DescriptorType::eSampledImage, 32}, - {vk::DescriptorType::eSampler, 32} - }; + {vk::DescriptorType::eSampledImage, 32}, + {vk::DescriptorType::eSampler, 32}}; vk::DescriptorPoolCreateInfo poolInfo(vk::DescriptorPoolCreateFlags(), 128, poolSizes.size(), poolSizes.data()); @@ -117,12 +116,10 @@ void VulkanRenderer::createPipelineAndDescriptors() vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment), // Binding 2: Shadow Map Texture (Fragment Shader) - vk::DescriptorSetLayoutBinding( - 2, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), + vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), // Binding 3: Shadow Map Sampler - vk::DescriptorSetLayoutBinding( - 3, vk::DescriptorType::eSampler, 1, vk::ShaderStageFlagBits::eFragment), + vk::DescriptorSetLayoutBinding(3, vk::DescriptorType::eSampler, 1, vk::ShaderStageFlagBits::eFragment), }; m_descriptorSet = std::make_unique(m_context->device(), m_descriptorPool, 2, bindings); const std::vector setLayouts = {m_descriptorSet->getLayout()}; @@ -145,11 +142,17 @@ void VulkanRenderer::createPipelineAndDescriptors() "Main Geometry Pipeline"); const std::vector compositeBindings = { + // binding 0: uInputImage (Texture2D) vk::DescriptorSetLayoutBinding( - 0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), + 0, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), + // binding 1: CompositeParams (UBO) vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment), + // binding 2: uDepthImage (Texture2DMS) vk::DescriptorSetLayoutBinding( - 2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), + 2, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), + // binding 3: g_sampler (SamplerState) + vk::DescriptorSetLayoutBinding(3, vk::DescriptorType::eSampler, 1, vk::ShaderStageFlagBits::eFragment), + }; m_compositeDescriptorSet = @@ -380,7 +383,7 @@ void VulkanRenderer::drawFrame() vk::DescriptorBufferInfo sceneBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); vk::DescriptorBufferInfo lightBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); - vk::DescriptorImageInfo shadowMapTextureInfo = {}; + vk::DescriptorImageInfo shadowMapTextureInfo = {}; // shadowMapImageInfo.sampler = m_shadowMapping->shadowMapSampler(); shadowMapTextureInfo.imageView = m_shadowMapping->shadowMapView(); shadowMapTextureInfo.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal; @@ -551,40 +554,37 @@ void VulkanRenderer::drawFrame() vk::DescriptorBufferInfo compositeBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); - vk::DescriptorImageInfo imageInfo = {}; - imageInfo.imageView = m_resolveViews[frameIdx]; - imageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - imageInfo.sampler = m_sampler->get(); + vk::DescriptorImageInfo resolveImageInfo = {}; + resolveImageInfo.imageView = m_resolveViews[frameIdx]; + resolveImageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; vk::DescriptorImageInfo depthImageInfo = {}; depthImageInfo.imageView = depthView; depthImageInfo.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal; - depthImageInfo.sampler = m_sampler->get(); - - std::vector writes = {vk::WriteDescriptorSet{ - m_compositeDescriptorSet->getCurrentSet(frameIdx), - 0, - 0, - 1, - vk::DescriptorType::eCombinedImageSampler, - &imageInfo, - nullptr, - }, - vk::WriteDescriptorSet{m_compositeDescriptorSet->getCurrentSet(frameIdx), - 1, - 0, - 1, - vk::DescriptorType::eUniformBuffer, - nullptr, - &compositeBufferInfo, - nullptr}, - vk::WriteDescriptorSet{m_compositeDescriptorSet->getCurrentSet(frameIdx), - 2, - 0, - 1, - vk::DescriptorType::eCombinedImageSampler, - &depthImageInfo, - nullptr}}; + + // Info for binding 3: g_sampler (the sampler to be used with uInputImage) + vk::DescriptorImageInfo samplerInfo = {}; + samplerInfo.sampler = m_sampler->get(); // This is the generic sampler you created + + std::vector writes; + writes.reserve(4); + + // Write for binding 0 + writes.emplace_back(m_compositeDescriptorSet->getCurrentSet(frameIdx), 0, 0, 1, + vk::DescriptorType::eSampledImage, &resolveImageInfo); + + // Write for binding 1 + writes.emplace_back(m_compositeDescriptorSet->getCurrentSet(frameIdx), 1, 0, 1, + vk::DescriptorType::eUniformBuffer, nullptr, &compositeBufferInfo); + + // Write for binding 2 + writes.emplace_back(m_compositeDescriptorSet->getCurrentSet(frameIdx), 2, 0, 1, + vk::DescriptorType::eSampledImage, &depthImageInfo); + + // Write for binding 3 (This is the crucial fix for the validation error) + writes.emplace_back(m_compositeDescriptorSet->getCurrentSet(frameIdx), 3, 0, 1, + vk::DescriptorType::eSampler, &samplerInfo); + m_compositeDescriptorSet->updateSet(writes); cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_compositePipeline->get()); From 3e312636c1e8e7b639c3333a19bc09e916a2de35 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Fri, 8 Aug 2025 16:02:46 -0500 Subject: [PATCH 04/24] fixed validation errors --- build-shaders.bat | 4 ++-- shaders/composite.slang | 4 ++-- src/vulkan/VulkanContext.cpp | 6 +++++ src/vulkan/VulkanRenderer.cpp | 43 ++++++++++++++++++++++++++++++++++- src/vulkan/VulkanRenderer.hpp | 4 ++++ 5 files changed, 56 insertions(+), 5 deletions(-) diff --git a/build-shaders.bat b/build-shaders.bat index a6c5e87..16ee418 100644 --- a/build-shaders.bat +++ b/build-shaders.bat @@ -7,5 +7,5 @@ glslc -g --target-env=vulkan1.3 -o resources/shaders/composite.frag.spv shaders/ slangc -g shaders/triangle.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/triangle-slang.vert.spv slangc -g shaders/triangle.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/triangle-slang.frag.spv -slangc -g shaders/composite.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/composite-slang.vert.spv -slangc -g shaders/composite.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/composite-slang.frag.spv \ No newline at end of file +slangc -g -O1 shaders/composite.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/composite-slang.vert.spv +slangc -g -O1 shaders/composite.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/composite-slang.frag.spv \ No newline at end of file diff --git a/shaders/composite.slang b/shaders/composite.slang index 3988e2a..0db3a9e 100644 --- a/shaders/composite.slang +++ b/shaders/composite.slang @@ -17,7 +17,7 @@ Texture2D uInputImage : register(t0); SamplerState g_sampler : register(s3); [[vk::binding(2, 0)]] -Texture2DMS uDepthImage : register(t2); +Texture2D uDepthImage : register(t2); [[vk::binding(1, 0)]] cbuffer CompositeParams : register(b1) @@ -81,7 +81,7 @@ float4 fragmentMain(Varyings input) : SV_Target // To read from a Texture2DMS, use the `.Load()` method. // It takes the coordinate and the sample index (we'll just use the first). // This replaces `texelFetch(uDepthImage, texelCoord, 0)`. - float depth = uDepthImage.Load(texelCoord, 0).r; + float depth = uDepthImage.Sample(g_sampler, input.uv).r; // --- Fog Calculation --- float3 fogColor = float3(0.5, 0.6, 0.7); diff --git a/src/vulkan/VulkanContext.cpp b/src/vulkan/VulkanContext.cpp index 24376c1..e953c3a 100644 --- a/src/vulkan/VulkanContext.cpp +++ b/src/vulkan/VulkanContext.cpp @@ -175,6 +175,12 @@ void VulkanContext::createLogicalDevice() { vk::PhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures{}; dynamicRenderingFeatures.dynamicRendering = VK_TRUE; + // create struct for Vulkan 1.1 features + vk::PhysicalDeviceVulkan11Features vulkan11Features{}; + vulkan11Features.shaderDrawParameters = VK_TRUE; + + dynamicRenderingFeatures.pNext = &vulkan11Features; + std::vector deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, // VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index eb1f40a..dde27dc 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -201,6 +201,7 @@ VulkanRenderer::~VulkanRenderer() m_context->device().destroyImageView(m_resolveViews[i]); m_context->device().destroyImageView(m_sceneViewViews[i]); m_context->device().destroyImageView(m_depthViews[i]); + m_context->device().destroyImageView(m_depthResolveViews[i]); } m_context->device().destroyDescriptorPool(m_descriptorPool); @@ -281,6 +282,9 @@ void VulkanRenderer::beginDynamicRendering(vk::CommandBuffer cmd, colorAttachment.loadOp = clearColor ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad; colorAttachment.storeOp = vk::AttachmentStoreOp::eStore; colorAttachment.clearValue = clearColorValue; + // colorAttachment.resolveImageView = m_resolveViews[m_frameManager->getCurrentFrameIndex()]; + // colorAttachment.resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal; + // colorAttachment.resolveMode = vk::ResolveModeFlagBits::eAverage; renderingInfo.colorAttachmentCount = 1; renderingInfo.pColorAttachments = &colorAttachment; } @@ -297,6 +301,16 @@ void VulkanRenderer::beginDynamicRendering(vk::CommandBuffer cmd, depthAttachment.loadOp = clearDepth ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad; depthAttachment.storeOp = vk::AttachmentStoreOp::eStore; depthAttachment.clearValue = depthClearValue; + + if (m_depthResolveViews[m_frameManager->getCurrentFrameIndex()]) + { + depthAttachment.resolveImageView = m_depthResolveViews[m_frameManager->getCurrentFrameIndex()]; + depthAttachment.resolveImageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; + depthAttachment.resolveMode = vk::ResolveModeFlagBits::eAverage; + // conservative + //depthAttachment.resolveMode = vk::ResolveModeFlagBits::eMin; + } + renderingInfo.pDepthAttachment = &depthAttachment; } else @@ -547,6 +561,15 @@ void VulkanRenderer::drawFrame() vk::AccessFlagBits::eDepthStencilAttachmentRead, vk::ImageAspectFlagBits::eDepth); + m_imageStateTracker.transition(cmd, + m_depthResolveImages[frameIdx]->get(), + vk::ImageLayout::eDepthStencilReadOnlyOptimal, + vk::PipelineStageFlagBits::eLateFragmentTests, + vk::PipelineStageFlagBits::eFragmentShader, + vk::AccessFlagBits::eDepthStencilAttachmentWrite, + vk::AccessFlagBits::eShaderRead, + vk::ImageAspectFlagBits::eDepth); + Debug::endLabel(cmd); beginDynamicRendering(cmd, m_sceneViewViews[frameIdx], nullptr, extent, true); @@ -559,7 +582,7 @@ void VulkanRenderer::drawFrame() resolveImageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; vk::DescriptorImageInfo depthImageInfo = {}; - depthImageInfo.imageView = depthView; + depthImageInfo.imageView = m_depthResolveViews[frameIdx]; depthImageInfo.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal; // Info for binding 3: g_sampler (the sampler to be used with uInputImage) @@ -771,6 +794,24 @@ void VulkanRenderer::createDepthImages() m_imageStateTracker.recordState(m_depthImages[i]->get(), vk::ImageLayout::eUndefined); } + + // ...existing code creating m_depthImages (MSAA)... + m_depthResolveImages.resize(framesInFlight); + m_depthResolveViews.resize(framesInFlight); + + utils::ImageBuilder resolveBuilder(m_context->device(), *m_allocator, m_swapchain->getExtent()); + for (size_t i = 0; i < framesInFlight; ++i) + { + auto built = resolveBuilder + .setFormat(format) + .setUsage(vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled) + .setSamples(vk::SampleCountFlagBits::e1) + .setAspectMask(vk::ImageAspectFlagBits::eDepth) + .build(); + m_depthResolveImages[i] = std::move(built.image); + m_depthResolveViews[i] = built.view; + m_imageStateTracker.recordState(m_depthResolveImages[i]->get(), vk::ImageLayout::eUndefined); + } } void VulkanRenderer::createDepthPipelineAndDescriptorSets() { diff --git a/src/vulkan/VulkanRenderer.hpp b/src/vulkan/VulkanRenderer.hpp index 80a9a46..556c8fc 100644 --- a/src/vulkan/VulkanRenderer.hpp +++ b/src/vulkan/VulkanRenderer.hpp @@ -86,6 +86,10 @@ class VulkanRenderer std::vector m_depthViews; std::vector m_depthImageDescriptorSets; + // storage for resolved depth + std::vector> m_depthResolveImages; + std::vector m_depthResolveViews; + DirectionalLightUBO m_light; std::vector m_objects; From 214f4d41fd192be2e0805e7165946e974ba1d4a0 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Fri, 8 Aug 2025 16:21:58 -0500 Subject: [PATCH 05/24] remove old shaders --- build-shaders.bat | 14 +++----- shaders/composite.frag | 78 ------------------------------------------ shaders/composite.vert | 20 ----------- shaders/triangle.frag | 55 ----------------------------- shaders/triangle.vert | 29 ---------------- 5 files changed, 4 insertions(+), 192 deletions(-) delete mode 100644 shaders/composite.frag delete mode 100644 shaders/composite.vert delete mode 100644 shaders/triangle.frag delete mode 100644 shaders/triangle.vert diff --git a/build-shaders.bat b/build-shaders.bat index 16ee418..82a22f7 100644 --- a/build-shaders.bat +++ b/build-shaders.bat @@ -1,11 +1,5 @@ -glslc -g --target-env=vulkan1.3 -o resources/shaders/triangle.vert.spv shaders/triangle.vert -glslc -g --target-env=vulkan1.3 -o resources/shaders/triangle.frag.spv shaders/triangle.frag +slangc -g -O0 shaders/triangle.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/triangle-slang.vert.spv +slangc -g -O0 shaders/triangle.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/triangle-slang.frag.spv -glslc -g --target-env=vulkan1.3 -o resources/shaders/composite.vert.spv shaders/composite.vert -glslc -g --target-env=vulkan1.3 -o resources/shaders/composite.frag.spv shaders/composite.frag - -slangc -g shaders/triangle.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/triangle-slang.vert.spv -slangc -g shaders/triangle.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/triangle-slang.frag.spv - -slangc -g -O1 shaders/composite.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/composite-slang.vert.spv -slangc -g -O1 shaders/composite.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/composite-slang.frag.spv \ No newline at end of file +slangc -g -O0 shaders/composite.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/composite-slang.vert.spv +slangc -g -O0 shaders/composite.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/composite-slang.frag.spv \ No newline at end of file diff --git a/shaders/composite.frag b/shaders/composite.frag deleted file mode 100644 index aee3a8a..0000000 --- a/shaders/composite.frag +++ /dev/null @@ -1,78 +0,0 @@ -#version 450 - -layout(location = 0) in vec2 fragUV; -layout(location = 0) out vec4 outColor; - -layout(binding = 0) uniform sampler2D uInputImage; - -// Binding 2: Multisampled depth texture from geometry pass -layout(binding = 2) uniform sampler2DMS uDepthImage; - -layout(set = 0, binding = 1) uniform CompositeParams { - float uExposure;// Default: 1.0 - float uContrast;// Default: 1.0 - float uSaturation;// Default: 1.0 - float uVignetteIntensity;// Default: 0.5 - float uVignetteFalloff;// Default 0.5 - float uFogDensity; -}; - -// add camera projection uniforms to the UBO or pass them in another way -const float Z_NEAR = 0.1; -const float Z_FAR = 100.0; - -float linearizeDepth(float depth) { - float z_n = 2.0 * depth - 1.0; - return (2.0 * Z_NEAR * Z_FAR) / (Z_FAR + Z_NEAR - z_n * (Z_FAR - Z_NEAR)); -} - -// ACES Filmic Tone Mapping Curve -vec3 tonemapACES(vec3 x) { - const float a = 2.51; - const float b = 0.03; - const float c = 2.43; - const float d = 0.59; - const float e = 0.14; - return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0); -} - -void main() { - - ivec2 texelCoord = ivec2(gl_FragCoord.xy); - - float depth = texelFetch(uDepthImage, texelCoord, 0).r; - - // --- Fog Calculation --- - vec3 fogColor = vec3(0.5, 0.6, 0.7);// A cool, hazy blue - float fogStart = 0.5; - float fogEnd = 120.0; - float viewDistance = linearizeDepth(depth); - // Calculate fog amount (0.0 = no fog, 1.0 = full fog) - float fogFactor = 1.0 - exp(-viewDistance * uFogDensity); - // float fogFactor = uFogDensity; - // ---------------------- - - // Sample the HDR input image - vec3 hdrColor = texture(uInputImage, fragUV).rgb; - - // Apply exposure - vec3 exposedColor = hdrColor * uExposure; - - // Tonemap (Reinhard) - vec3 ldrColor = tonemapACES(exposedColor); - - // Apply Color Grading - vec3 finalColor = ldrColor; - - // Contrast - finalColor = pow(finalColor, vec3(uContrast)); - - // Saturation - vec3 grayscale = vec3(dot(finalColor, vec3(0.299, 0.587, 0.114))); - finalColor = mix(grayscale, finalColor, uSaturation); - - // Mix the final scene color with the fog color - finalColor = mix(finalColor, fogColor, fogFactor); - - outColor = vec4(finalColor, 1.0); -} \ No newline at end of file diff --git a/shaders/composite.vert b/shaders/composite.vert deleted file mode 100644 index c9ff5e5..0000000 --- a/shaders/composite.vert +++ /dev/null @@ -1,20 +0,0 @@ -#version 450 - -layout(location = 0) out vec2 fragUV; - -void main() { - // Fullscreen triangle using gl_VertexIndex (0, 1, 2) - const vec2 pos[3] = vec2[]( - vec2(-1.0, -1.0), // bottom-left - vec2( 3.0, -1.0), // bottom-right far (for covering entire screen) - vec2(-1.0, 3.0) // top-left far (for covering entire screen) - ); - const vec2 uv[3] = vec2[]( - vec2(0.0, 0.0), - vec2(2.0, 0.0), - vec2(0.0, 2.0) - ); - - fragUV = uv[gl_VertexIndex]; - gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0); -} \ No newline at end of file diff --git a/shaders/triangle.frag b/shaders/triangle.frag deleted file mode 100644 index 20b5af0..0000000 --- a/shaders/triangle.frag +++ /dev/null @@ -1,55 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 inWorldPos; -layout(location = 1) in vec3 inNormal; -layout(location = 2) in vec4 inLightSpacePos; - -layout(binding = 1) uniform LightUBO { - vec4 lightDirection; - vec4 lightColor; - float lightIntensity; -} ubo; - -layout(binding = 2) uniform sampler2DShadow shadowMap; - -// Final output color -layout(location = 0) out vec4 outColor; - -float calculateShadow() -{ - // Perform perspective divide - vec3 projCoords = inLightSpacePos.xyz / inLightSpacePos.w; - - // Convert from [-1, 1] to [0, 1] texture coordinates - projCoords.xy = projCoords.xy * 0.5 + 0.5; - - // Get the closest depth from the shadow map (from light's perspective) - // The 'z' coordinate of projCoords is the current fragment's depth from the light - // texture() for sampler2DShadow performs the depth comparison automatically - float shadow = texture(shadowMap, projCoords); - - return shadow; -} - -void main() { - vec3 objectColor = vec3(0.8, 0.8, 0.8); - - vec3 ambient = objectColor * 0.1; - - // 2. Calculate the diffuse (directional) light component. - vec3 normal = normalize(inNormal); - vec3 lightDir = normalize(-ubo.lightDirection.xyz); - float diffuseFactor = max(dot(normal, lightDir), 0.0); - vec3 diffuse = objectColor * ubo.lightColor.rgb * ubo.lightIntensity * diffuseFactor; - - // 3. Get the shadow factor (1.0 for lit, 0.0 for shadowed). - float shadow = calculateShadow(); - - // 4. Combine the components for the final color. - // The final color is the ambient light, plus the direct (diffuse) light, - // which IS blocked by shadows. - vec3 finalColor = ambient + (diffuse * shadow); - - outColor = vec4(finalColor, 1.0); - -} \ No newline at end of file diff --git a/shaders/triangle.vert b/shaders/triangle.vert deleted file mode 100644 index c020edc..0000000 --- a/shaders/triangle.vert +++ /dev/null @@ -1,29 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inNormal; -layout(location = 2) in vec3 inColor; -layout(location = 3) in vec2 inTexCoord; - -layout(location = 0) out vec3 outWorldPos; -layout(location = 1) out vec3 outNormal; -layout(location = 2) out vec4 outLightSpacePos; - -layout(binding = 0) uniform SceneUBO { - mat4 view; - mat4 projection; - mat4 lightSpaceMatrix; -} ubo; - -layout(push_constant) uniform PushConstants { - mat4 model; -} push; - -void main() { - vec4 worldPos = push.model * vec4(inPosition, 1.0); - gl_Position = ubo.projection * ubo.view * worldPos; - - outWorldPos = worldPos.xyz; - outNormal = normalize(mat3(push.model) * inNormal); - outLightSpacePos = ubo.lightSpaceMatrix * worldPos; -} \ No newline at end of file From 5819dfd67200941e82ffc3e98c7bd37bf6bcb94e Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Fri, 8 Aug 2025 16:33:32 -0500 Subject: [PATCH 06/24] add better labels for stages --- src/vulkan/VulkanRenderer.cpp | 50 +++++++++++++++-------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index dde27dc..44077f8 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -439,7 +439,11 @@ void VulkanRenderer::drawFrame() beginCommandBuffer(cmd); - Debug::beginLabel(cmd, "Depth Prepass", {0.8f, 0.4f, 0.2f, 1.0}); + // Main frame label (Gray) + Debug::beginLabel(cmd, "Render Frame", {0.5f, 0.5f, 0.5f, 1.0f}); + + // 1. Depth Pre-pass (Light Blue) + Debug::beginLabel(cmd, "Depth Pre-pass", {0.2f, 0.6f, 1.0f, 1.0f}); // get depth image view for this frame vk::ImageView depthView = m_depthViews[frameIdx]; @@ -458,9 +462,10 @@ void VulkanRenderer::drawFrame() cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_depthPipeline->get()); drawGeometry(cmd); endDynamicRendering(cmd); - Debug::endLabel(cmd); + Debug::endLabel(cmd); // End Depth Pre-pass - Debug::beginLabel(cmd, "Shadow Pass", {0.8f, 0.4f, 0.2f, 1.0}); + // 2. Shadow Pass (Dark Gray) + Debug::beginLabel(cmd, "Shadow Pass", {0.3f, 0.3f, 0.3f, 1.0f}); auto drawFunc = [this](vk::CommandBuffer cmd) { this->drawGeometry(cmd); }; m_shadowMapping->recordShadowPass(cmd, frameIdx, drawFunc); @@ -473,14 +478,10 @@ void VulkanRenderer::drawFrame() vk::AccessFlagBits::eDepthStencilAttachmentWrite, vk::AccessFlagBits::eShaderRead, vk::ImageAspectFlagBits::eDepth); - Debug::endLabel(cmd); - Debug::beginLabel(cmd, "Main Pass", {0.8f, 0.4f, 0.2f, 1.0}); - - // --- 1. Geometry Pass --- - // Transition the MSAA image so we can render the main scene into it. - // Its layout was likely UNDEFINED (on first use) or TRANSFER_SRC (from previous frame's resolve). + // 3. Main Geometry/Shading Pass (Red) + Debug::beginLabel(cmd, "Geometry Pass", {1.0f, 0.3f, 0.3f, 1.0f}); m_imageStateTracker.transition(cmd, msaaImage, vk::ImageLayout::eColorAttachmentOptimal, @@ -496,12 +497,10 @@ void VulkanRenderer::drawFrame() cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_pipeline->get()); drawGeometry(cmd); endDynamicRendering(cmd); + Debug::endLabel(cmd); // End Geometry Pass - // --- 2. MSAA Resolve --- - // Resolve the multi-sampled image into a standard image for post-processing. - // vkCmdResolveImage requires the source to be TRANSFER_SRC and destination to be TRANSFER_DST. - - // Transition MSAA image from COLOR_ATTACHMENT to TRANSFER_SRC_OPTIMAL to be read by the resolve command. + // 4. MSAA Resolve (Purple) + Debug::beginLabel(cmd, "MSAA Resolve", {0.7f, 0.4f, 1.0f, 1.0f}); m_imageStateTracker.transition(cmd, msaaImage, vk::ImageLayout::eTransferSrcOptimal, @@ -520,11 +519,10 @@ void VulkanRenderer::drawFrame() vk::AccessFlagBits::eTransferWrite); utils::resolveMSAAImageTo(cmd, msaaImage, resolveImage, width, height); + Debug::endLabel(cmd); // End MSAA Resolve - // --- 3. Composite Pass --- - // This pass reads from the resolved image and writes to the swapchain image. - - // Transition the Resolve image from TRANSFER_DST to SHADER_READ_ONLY so it can be used as a texture. + // 5. Composite & Post-Processing Pass (Green) + Debug::beginLabel(cmd, "Composite Pass", {0.2f, 0.8f, 0.2f, 1.0f}); m_imageStateTracker.transition(cmd, resolveImage, vk::ImageLayout::eShaderReadOnlyOptimal, @@ -570,8 +568,6 @@ void VulkanRenderer::drawFrame() vk::AccessFlagBits::eShaderRead, vk::ImageAspectFlagBits::eDepth); - Debug::endLabel(cmd); - beginDynamicRendering(cmd, m_sceneViewViews[frameIdx], nullptr, extent, true); utils::setupViewportAndScissor(cmd, extent); @@ -618,8 +614,10 @@ void VulkanRenderer::drawFrame() nullptr); cmd.draw(3, 1, 0, 0); endDynamicRendering(cmd); + Debug::endLabel(cmd); // End Composite Pass - // -- Prepare sceneView for ImGui + // 6. UI Pass (Yellow) + Debug::beginLabel(cmd, "UI Pass", {1.0f, 0.9f, 0.3f, 1.0f}); m_imageStateTracker.transition(cmd, sceneViewImage, vk::ImageLayout::eShaderReadOnlyOptimal, @@ -637,17 +635,12 @@ void VulkanRenderer::drawFrame() {}, vk::AccessFlagBits::eColorAttachmentWrite); - // --- 4. UI Pass --- - // The UI is rendered on top of the composited scene. - // The swapchain image is already in COLOR_ATTACHMENT_OPTIMAL, so no transition is needed. beginDynamicRendering(cmd, m_swapchain->getImageViews()[imageIndex], nullptr, extent, false); - m_imgui->setSceneDescriptorSet(m_sceneViewImageDescriptorSets[frameIdx]); renderUI(cmd); endDynamicRendering(cmd); + Debug::endLabel(cmd); // End UI Pass - // --- 5. Prepare for Presentation --- - // Transition the swapchain image from COLOR_ATTACHMENT to PRESENT_SRC_KHR for the presentation engine. m_imageStateTracker.transition(cmd, swapchainImage, vk::ImageLayout::ePresentSrcKHR, @@ -656,8 +649,9 @@ void VulkanRenderer::drawFrame() vk::AccessFlagBits::eColorAttachmentWrite, {}); - endCommandBuffer(cmd); + Debug::endLabel(cmd); // End Render Frame + endCommandBuffer(cmd); submitAndPresent(imageIndex); } From 8c3312d4fd71349ee667d59801e5550b28c4108b Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Fri, 8 Aug 2025 19:37:47 -0500 Subject: [PATCH 07/24] Resolve MSAA during geometry render --- src/vulkan/VulkanRenderer.cpp | 128 ++++++++++++++++------------------ src/vulkan/VulkanRenderer.hpp | 8 ++- src/vulkan/VulkanUtils.hpp | 43 ++++-------- 3 files changed, 80 insertions(+), 99 deletions(-) diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 44077f8..b0aeafb 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -143,13 +143,11 @@ void VulkanRenderer::createPipelineAndDescriptors() const std::vector compositeBindings = { // binding 0: uInputImage (Texture2D) - vk::DescriptorSetLayoutBinding( - 0, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), // binding 1: CompositeParams (UBO) vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment), // binding 2: uDepthImage (Texture2DMS) - vk::DescriptorSetLayoutBinding( - 2, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), + vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), // binding 3: g_sampler (SamplerState) vk::DescriptorSetLayoutBinding(3, vk::DescriptorType::eSampler, 1, vk::ShaderStageFlagBits::eFragment), @@ -259,10 +257,11 @@ void VulkanRenderer::submitAndPresent(uint32_t imageIndex) void VulkanRenderer::beginDynamicRendering(vk::CommandBuffer cmd, vk::ImageView colorImageView, + vk::ImageView resolveImageView, vk::ImageView depthImageView, vk::Extent2D extent, - bool clearColor = true, - bool clearDepth = false) + bool clearColor, + bool clearDepth) { vk::RenderingAttachmentInfo colorAttachment{}; vk::RenderingAttachmentInfo depthAttachment{}; @@ -282,9 +281,12 @@ void VulkanRenderer::beginDynamicRendering(vk::CommandBuffer cmd, colorAttachment.loadOp = clearColor ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad; colorAttachment.storeOp = vk::AttachmentStoreOp::eStore; colorAttachment.clearValue = clearColorValue; - // colorAttachment.resolveImageView = m_resolveViews[m_frameManager->getCurrentFrameIndex()]; - // colorAttachment.resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal; - // colorAttachment.resolveMode = vk::ResolveModeFlagBits::eAverage; + if (resolveImageView) + { + colorAttachment.resolveImageView = resolveImageView; + colorAttachment.resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal; + colorAttachment.resolveMode = vk::ResolveModeFlagBits::eAverage; + } renderingInfo.colorAttachmentCount = 1; renderingInfo.pColorAttachments = &colorAttachment; } @@ -308,7 +310,7 @@ void VulkanRenderer::beginDynamicRendering(vk::CommandBuffer cmd, depthAttachment.resolveImageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; depthAttachment.resolveMode = vk::ResolveModeFlagBits::eAverage; // conservative - //depthAttachment.resolveMode = vk::ResolveModeFlagBits::eMin; + // depthAttachment.resolveMode = vk::ResolveModeFlagBits::eMin; } renderingInfo.pDepthAttachment = &depthAttachment; @@ -346,6 +348,7 @@ void VulkanRenderer::drawFrame() const vk::Image msaaImage = m_msaaImages[frameIdx]->get(); const vk::ImageView msaaView = m_msaaColorViews[frameIdx]; const vk::Image resolveImage = m_resolveImages[frameIdx]->get(); + const vk::ImageView resolveView = m_resolveViews[frameIdx]; const vk::Image sceneViewImage = m_sceneViewImages[frameIdx]->get(); SceneUBO sceneData{}; @@ -456,7 +459,7 @@ void VulkanRenderer::drawFrame() vk::AccessFlagBits::eDepthStencilAttachmentWrite, vk::ImageAspectFlagBits::eDepth); - beginDynamicRendering(cmd, nullptr, depthView, extent, false, true); + beginDynamicRendering(cmd, nullptr, nullptr, depthView, extent, false, true); utils::setupViewportAndScissor(cmd, extent); bindDescriptorSets(cmd); cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_depthPipeline->get()); @@ -490,8 +493,15 @@ void VulkanRenderer::drawFrame() {}, // Source Access vk::AccessFlagBits::eColorAttachmentWrite // Destination Access ); + m_imageStateTracker.transition(cmd, + resolveImage, + vk::ImageLayout::eColorAttachmentOptimal, + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + {}, + vk::AccessFlagBits::eColorAttachmentWrite); - beginDynamicRendering(cmd, msaaView, depthView, extent, true, false); + beginDynamicRendering(cmd, msaaView, resolveView, depthView, extent, true, false); utils::setupViewportAndScissor(cmd, extent); bindDescriptorSets(cmd); cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_pipeline->get()); @@ -499,36 +509,14 @@ void VulkanRenderer::drawFrame() endDynamicRendering(cmd); Debug::endLabel(cmd); // End Geometry Pass - // 4. MSAA Resolve (Purple) - Debug::beginLabel(cmd, "MSAA Resolve", {0.7f, 0.4f, 1.0f, 1.0f}); - m_imageStateTracker.transition(cmd, - msaaImage, - vk::ImageLayout::eTransferSrcOptimal, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eTransfer, - vk::AccessFlagBits::eColorAttachmentWrite, - vk::AccessFlagBits::eTransferRead); - - // Transition the Resolve image to TRANSFER_DST_OPTIMAL to be written to by the resolve command. - m_imageStateTracker.transition(cmd, - resolveImage, - vk::ImageLayout::eTransferDstOptimal, - vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eTransfer, - {}, - vk::AccessFlagBits::eTransferWrite); - - utils::resolveMSAAImageTo(cmd, msaaImage, resolveImage, width, height); - Debug::endLabel(cmd); // End MSAA Resolve - - // 5. Composite & Post-Processing Pass (Green) + // 4. Composite & Post-Processing Pass (Green) Debug::beginLabel(cmd, "Composite Pass", {0.2f, 0.8f, 0.2f, 1.0f}); m_imageStateTracker.transition(cmd, resolveImage, vk::ImageLayout::eShaderReadOnlyOptimal, - vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader, - vk::AccessFlagBits::eTransferWrite, + vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eShaderRead); // Transition the Swapchain image to COLOR_ATTACHMENT_OPTIMAL so we can render the composite result to it. @@ -560,20 +548,20 @@ void VulkanRenderer::drawFrame() vk::ImageAspectFlagBits::eDepth); m_imageStateTracker.transition(cmd, - m_depthResolveImages[frameIdx]->get(), - vk::ImageLayout::eDepthStencilReadOnlyOptimal, - vk::PipelineStageFlagBits::eLateFragmentTests, - vk::PipelineStageFlagBits::eFragmentShader, - vk::AccessFlagBits::eDepthStencilAttachmentWrite, - vk::AccessFlagBits::eShaderRead, - vk::ImageAspectFlagBits::eDepth); - - beginDynamicRendering(cmd, m_sceneViewViews[frameIdx], nullptr, extent, true); + m_depthResolveImages[frameIdx]->get(), + vk::ImageLayout::eDepthStencilReadOnlyOptimal, + vk::PipelineStageFlagBits::eLateFragmentTests, + vk::PipelineStageFlagBits::eFragmentShader, + vk::AccessFlagBits::eDepthStencilAttachmentWrite, + vk::AccessFlagBits::eShaderRead, + vk::ImageAspectFlagBits::eDepth); + + beginDynamicRendering(cmd, m_sceneViewViews[frameIdx], nullptr, nullptr, extent, true); utils::setupViewportAndScissor(cmd, extent); vk::DescriptorBufferInfo compositeBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); - vk::DescriptorImageInfo resolveImageInfo = {}; + vk::DescriptorImageInfo resolveImageInfo = {}; resolveImageInfo.imageView = m_resolveViews[frameIdx]; resolveImageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; @@ -589,20 +577,29 @@ void VulkanRenderer::drawFrame() writes.reserve(4); // Write for binding 0 - writes.emplace_back(m_compositeDescriptorSet->getCurrentSet(frameIdx), 0, 0, 1, - vk::DescriptorType::eSampledImage, &resolveImageInfo); + writes.emplace_back(m_compositeDescriptorSet->getCurrentSet(frameIdx), + 0, + 0, + 1, + vk::DescriptorType::eSampledImage, + &resolveImageInfo); // Write for binding 1 - writes.emplace_back(m_compositeDescriptorSet->getCurrentSet(frameIdx), 1, 0, 1, - vk::DescriptorType::eUniformBuffer, nullptr, &compositeBufferInfo); + writes.emplace_back(m_compositeDescriptorSet->getCurrentSet(frameIdx), + 1, + 0, + 1, + vk::DescriptorType::eUniformBuffer, + nullptr, + &compositeBufferInfo); // Write for binding 2 - writes.emplace_back(m_compositeDescriptorSet->getCurrentSet(frameIdx), 2, 0, 1, - vk::DescriptorType::eSampledImage, &depthImageInfo); + writes.emplace_back( + m_compositeDescriptorSet->getCurrentSet(frameIdx), 2, 0, 1, vk::DescriptorType::eSampledImage, &depthImageInfo); // Write for binding 3 (This is the crucial fix for the validation error) - writes.emplace_back(m_compositeDescriptorSet->getCurrentSet(frameIdx), 3, 0, 1, - vk::DescriptorType::eSampler, &samplerInfo); + writes.emplace_back( + m_compositeDescriptorSet->getCurrentSet(frameIdx), 3, 0, 1, vk::DescriptorType::eSampler, &samplerInfo); m_compositeDescriptorSet->updateSet(writes); @@ -616,7 +613,7 @@ void VulkanRenderer::drawFrame() endDynamicRendering(cmd); Debug::endLabel(cmd); // End Composite Pass - // 6. UI Pass (Yellow) + // 5. UI Pass (Yellow) Debug::beginLabel(cmd, "UI Pass", {1.0f, 0.9f, 0.3f, 1.0f}); m_imageStateTracker.transition(cmd, sceneViewImage, @@ -635,7 +632,7 @@ void VulkanRenderer::drawFrame() {}, vk::AccessFlagBits::eColorAttachmentWrite); - beginDynamicRendering(cmd, m_swapchain->getImageViews()[imageIndex], nullptr, extent, false); + beginDynamicRendering(cmd, m_swapchain->getImageViews()[imageIndex], nullptr, nullptr, extent, false); m_imgui->setSceneDescriptorSet(m_sceneViewImageDescriptorSets[frameIdx]); renderUI(cmd); endDynamicRendering(cmd); @@ -658,8 +655,7 @@ void VulkanRenderer::drawFrame() void VulkanRenderer::createMSAAImage() { vk::Format format = vk::Format::eR16G16B16A16Sfloat; - vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment - | vk::ImageUsageFlagBits::eTransferSrc; + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment; size_t framesInFlight = m_frameManager->getFramesInFlightCount(); utils::ImageBuilder builder(m_context->device(), *m_allocator, m_swapchain->getExtent()); @@ -679,8 +675,7 @@ void VulkanRenderer::createMSAAImage() void VulkanRenderer::createResolveImages() { vk::Format format = vk::Format::eR16G16B16A16Sfloat; - vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled - | vk::ImageUsageFlagBits::eTransferDst; + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled; size_t framesInFlight = m_frameManager->getFramesInFlightCount(); utils::ImageBuilder builder(m_context->device(), *m_allocator, m_swapchain->getExtent()); @@ -796,12 +791,11 @@ void VulkanRenderer::createDepthImages() utils::ImageBuilder resolveBuilder(m_context->device(), *m_allocator, m_swapchain->getExtent()); for (size_t i = 0; i < framesInFlight; ++i) { - auto built = resolveBuilder - .setFormat(format) - .setUsage(vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled) - .setSamples(vk::SampleCountFlagBits::e1) - .setAspectMask(vk::ImageAspectFlagBits::eDepth) - .build(); + auto built = resolveBuilder.setFormat(format) + .setUsage(vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled) + .setSamples(vk::SampleCountFlagBits::e1) + .setAspectMask(vk::ImageAspectFlagBits::eDepth) + .build(); m_depthResolveImages[i] = std::move(built.image); m_depthResolveViews[i] = built.view; m_imageStateTracker.recordState(m_depthResolveImages[i]->get(), vk::ImageLayout::eUndefined); diff --git a/src/vulkan/VulkanRenderer.hpp b/src/vulkan/VulkanRenderer.hpp index 556c8fc..b80eb25 100644 --- a/src/vulkan/VulkanRenderer.hpp +++ b/src/vulkan/VulkanRenderer.hpp @@ -111,7 +111,13 @@ class VulkanRenderer void handleSwapchainResizing(); void beginCommandBuffer(vk::CommandBuffer cmd); - void beginDynamicRendering(vk::CommandBuffer cmd, vk::ImageView colorImageView, vk::ImageView depthImageView, vk::Extent2D extent, bool clearColor, bool clearDepth); + void beginDynamicRendering(vk::CommandBuffer cmd, + vk::ImageView colorImageView, + vk::ImageView resolveImageView, + vk::ImageView depthImageView, + vk::Extent2D extent, + bool clearColor = true, + bool clearDepth = false); void bindDescriptorSets(vk::CommandBuffer cmd); void drawGeometry(vk::CommandBuffer cmd); void renderUI(vk::CommandBuffer cmd) const; diff --git a/src/vulkan/VulkanUtils.hpp b/src/vulkan/VulkanUtils.hpp index 1e50cca..5620105 100644 --- a/src/vulkan/VulkanUtils.hpp +++ b/src/vulkan/VulkanUtils.hpp @@ -2,37 +2,16 @@ #include -namespace reactor::utils { +namespace reactor::utils +{ - - -inline void resolveMSAAImageTo(vk::CommandBuffer cmd, vk::Image msaaImage, vk::Image resolveImage, - uint32_t width, uint32_t height) { - - - - vk::ImageResolve resolveRegion{}; - resolveRegion.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; - resolveRegion.srcSubresource.mipLevel = 0; - resolveRegion.srcSubresource.baseArrayLayer = 0; - resolveRegion.srcSubresource.layerCount = 1; - resolveRegion.srcOffset = vk::Offset3D{0, 0, 0}; - resolveRegion.dstSubresource = resolveRegion.srcSubresource; - resolveRegion.dstOffset = vk::Offset3D{0, 0, 0}; - resolveRegion.extent = vk::Extent3D{width, height, 1}; - - cmd.resolveImage(msaaImage, // srcImage - vk::ImageLayout::eTransferSrcOptimal, - resolveImage, // dstImage - vk::ImageLayout::eTransferDstOptimal, 1, &resolveRegion); -} - -inline void setupViewportAndScissor(vk::CommandBuffer cmd, vk::Extent2D extent) { +inline void setupViewportAndScissor(vk::CommandBuffer cmd, vk::Extent2D extent) +{ vk::Viewport viewport{}; - viewport.x = 0.0f; - viewport.y = 0.0f; - viewport.width = static_cast(extent.width); - viewport.height = static_cast(extent.height); + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = static_cast(extent.width); + viewport.height = static_cast(extent.height); viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; cmd.setViewport(0, viewport); @@ -41,8 +20,10 @@ inline void setupViewportAndScissor(vk::CommandBuffer cmd, vk::Extent2D extent) cmd.setScissor(0, scissor); } -inline vk::SampleCountFlagBits mapSampleCountFlag(uint32_t sampleCount) { - switch (sampleCount) { +inline vk::SampleCountFlagBits mapSampleCountFlag(uint32_t sampleCount) +{ + switch (sampleCount) + { case 1: return vk::SampleCountFlagBits::e1; case 2: From 28830a684b3f504926b8904feccdc3c4d8e61a37 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Wed, 13 Aug 2025 19:12:43 -0500 Subject: [PATCH 08/24] prep for Linux --- .clang-format | 76 +- .clang-tidy | 64 +- .github/workflows/build.yml | 96 +- .gitignore | 16 +- .gitmodules | 6 +- CMakeLists.txt | 200 ++-- CMakePresets.json | 169 +-- LICENSE | 42 +- README.md | 56 +- build-shaders.bat | 8 +- shaders/composite.slang | 212 ++-- shaders/triangle.slang | 312 +++--- src/core/Application.cpp | 88 +- src/core/Application.hpp | 80 +- src/core/Camera.cpp | 286 ++--- src/core/Camera.hpp | 126 +-- src/core/EventManager.cpp | 54 +- src/core/EventManager.hpp | 92 +- src/core/ModelIO.cpp | 326 +++--- src/core/ModelIO.hpp | 34 +- src/core/OrbitController.cpp | 196 ++-- src/core/OrbitController.hpp | 86 +- src/core/Uniforms.hpp | 50 +- src/core/Window.cpp | 232 ++--- src/core/Window.hpp | 140 +-- src/core/main.cpp | 32 +- src/imgui/Imgui.cpp | 616 +++++------ src/imgui/Imgui.hpp | 110 +- src/logging/ImGuiConsoleSink.cpp | 60 +- src/logging/ImGuiConsoleSink.hpp | 50 +- src/logging/Logger.hpp | 250 ++--- src/logging/SpdlogSink.cpp | 70 +- src/logging/SpdlogSink.hpp | 36 +- src/pch.hpp | 56 +- src/tools/ModelBuilder.cpp | 38 +- src/vulkan/Allocator.cpp | 182 ++-- src/vulkan/Allocator.hpp | 110 +- src/vulkan/Buffer.cpp | 112 +- src/vulkan/Buffer.hpp | 76 +- src/vulkan/DebugUtils.hpp | 94 +- src/vulkan/DescriptorSet.cpp | 110 +- src/vulkan/DescriptorSet.hpp | 84 +- src/vulkan/FrameManager.cpp | 276 ++--- src/vulkan/FrameManager.hpp | 104 +- src/vulkan/Image.cpp | 136 +-- src/vulkan/Image.hpp | 64 +- src/vulkan/ImageStateTracker.cpp | 134 +-- src/vulkan/ImageStateTracker.h | 62 +- src/vulkan/ImageUtils.cpp | 120 +-- src/vulkan/ImageUtils.hpp | 70 +- src/vulkan/Mesh.cpp | 202 ++-- src/vulkan/Mesh.hpp | 92 +- src/vulkan/MeshGenerators.cpp | 228 ++-- src/vulkan/MeshGenerators.hpp | 22 +- src/vulkan/Pipeline.cpp | 500 ++++----- src/vulkan/Pipeline.hpp | 158 +-- src/vulkan/Sampler.cpp | 38 +- src/vulkan/Sampler.hpp | 48 +- src/vulkan/ShaderModule.cpp | 48 +- src/vulkan/ShaderModule.hpp | 66 +- src/vulkan/ShadowMapping.cpp | 456 ++++---- src/vulkan/ShadowMapping.hpp | 114 +- src/vulkan/Swapchain.cpp | 384 +++---- src/vulkan/Swapchain.hpp | 88 +- src/vulkan/UniformManager.cpp | 54 +- src/vulkan/UniformManager.hpp | 122 +-- src/vulkan/Vertex.hpp | 100 +- src/vulkan/VulkanContext.cpp | 438 ++++---- src/vulkan/VulkanContext.hpp | 126 +-- src/vulkan/VulkanRenderer.cpp | 1677 +++++++++++++++--------------- src/vulkan/VulkanRenderer.hpp | 258 ++--- src/vulkan/VulkanUtils.hpp | 78 +- src/vulkan/vk_mem_alloc.cpp | 4 +- vcpkg | 2 +- vcpkg.json | 40 +- 75 files changed, 5735 insertions(+), 5707 deletions(-) diff --git a/.clang-format b/.clang-format index b4615d0..a4c3eab 100644 --- a/.clang-format +++ b/.clang-format @@ -1,39 +1,39 @@ ---- -Language: Cpp -BasedOnStyle: LLVM -IndentWidth: 4 -TabWidth: 4 -UseTab: Never -ColumnLimit: 120 # No forced wrapping; useful for long VulkanHpp calls -AccessModifierOffset: -4 -AlignAfterOpenBracket: Align -AlignConsecutiveAssignments: false -AlignConsecutiveDeclarations: false -AllowShortBlocksOnASingleLine: false -AllowShortFunctionsOnASingleLine: None -AllowShortIfStatementsOnASingleLine: Never -AllowShortLoopsOnASingleLine: false -AlwaysBreakTemplateDeclarations: Yes -BinPackArguments: false -BinPackParameters: false -BreakBeforeBinaryOperators: NonAssignment -BreakBeforeBraces: Custom -BraceWrapping: - AfterClass: true - AfterControlStatement: true - AfterEnum: true - AfterFunction: true - AfterNamespace: true - AfterStruct: true - AfterUnion: true - BeforeCatch: true - BeforeElse: true - IndentBraces: false - SplitEmptyFunction: false - SplitEmptyRecord: false -Cpp11BracedListStyle: true -DerivePointerAlignment: false -NamespaceIndentation: None -PointerAlignment: Left -SortIncludes: true +--- +Language: Cpp +BasedOnStyle: LLVM +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +ColumnLimit: 120 # No forced wrapping; useful for long VulkanHpp calls +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false +Cpp11BracedListStyle: true +DerivePointerAlignment: false +NamespaceIndentation: None +PointerAlignment: Left +SortIncludes: true IncludeBlocks: Preserve \ No newline at end of file diff --git a/.clang-tidy b/.clang-tidy index 752f2f5..122f1d0 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,32 +1,32 @@ -Checks: > - clang-analyzer-*, - bugprone-*, - performance-*, - hicpp-*, - modernize-*, - cppcoreguidelines-*, - readability-*, - - # disable overly noisy checks - -clang-analyzer-alpha.*, - -cppcoreguidelines-pro-type-union-access, - -cppcoreguidelines-pro-type-reinterpret-cast, - -cppcoreguidelines-pro-bounds-pointer-arithmetic, - -cppcoreguidelines-avoid-magic-numbers, - -misc-unused-using-decls, - -misc-unused-parameters, - -hicpp-vararg, - -modernize-use-trailing-return-type, - -readability-const-parameter, - -readability-const-variable - -WarningsAsErrors: '' -HeaderFilterRegex: '^(include|src)/' -FormatStyle: file - -CheckOptions: - - { key: modernize-use-nullptr.NullMacros, value: 'NULL' } - - { key: readability-identifier-naming.VariableCase, value: camelBack } - - { key: readability-identifier-naming.ClassCase, value: CamelCase } - - { key: readability-identifier-naming.FunctionCase, value: camelBack } - - { key: readability-function-size.LineThreshold, value: '200' } +Checks: > + clang-analyzer-*, + bugprone-*, + performance-*, + hicpp-*, + modernize-*, + cppcoreguidelines-*, + readability-*, + + # disable overly noisy checks + -clang-analyzer-alpha.*, + -cppcoreguidelines-pro-type-union-access, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-avoid-magic-numbers, + -misc-unused-using-decls, + -misc-unused-parameters, + -hicpp-vararg, + -modernize-use-trailing-return-type, + -readability-const-parameter, + -readability-const-variable + +WarningsAsErrors: '' +HeaderFilterRegex: '^(include|src)/' +FormatStyle: file + +CheckOptions: + - { key: modernize-use-nullptr.NullMacros, value: 'NULL' } + - { key: readability-identifier-naming.VariableCase, value: camelBack } + - { key: readability-identifier-naming.ClassCase, value: CamelCase } + - { key: readability-identifier-naming.FunctionCase, value: camelBack } + - { key: readability-function-size.LineThreshold, value: '200' } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f2764b5..f2ab8f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,48 +1,48 @@ -name: CMake Build - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - submodules: true - - - name: Cache vcpkg dependencies - uses: actions/cache@v4 - with: - # The path to cache is the entire vcpkg directory - path: ${{ github.workspace }}/vcpkg - # The key is specific to the OS and the contents of your vcpkg manifest - key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json') }} - # A fallback key to restore the latest cache for the current OS - restore-keys: | - vcpkg-${{ runner.os }}- - - - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - libvulkan-dev \ - libxinerama-dev \ - libxcursor-dev \ - xorg-dev \ - libglu1-mesa-dev \ - pkg-config \ - clang-tidy - - - name: Bootstrap vcpkg - run: ./vcpkg/bootstrap-vcpkg.sh - - - name: Configure CMake with compile commands - run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake - - - name: Build - run: cmake --build build --config Release +name: CMake Build + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + submodules: true + + - name: Cache vcpkg dependencies + uses: actions/cache@v4 + with: + # The path to cache is the entire vcpkg directory + path: ${{ github.workspace }}/vcpkg + # The key is specific to the OS and the contents of your vcpkg manifest + key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json') }} + # A fallback key to restore the latest cache for the current OS + restore-keys: | + vcpkg-${{ runner.os }}- + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libvulkan-dev \ + libxinerama-dev \ + libxcursor-dev \ + xorg-dev \ + libglu1-mesa-dev \ + pkg-config \ + clang-tidy + + - name: Bootstrap vcpkg + run: ./vcpkg/bootstrap-vcpkg.sh + + - name: Configure CMake with compile commands + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake + + - name: Build + run: cmake --build build --config Release diff --git a/.gitignore b/.gitignore index 2f2af59..2992609 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ -build/ -cmake-build-debug/ -vcpkg_installed/ -.idea -resources/ -*.fbx -workspace/ \ No newline at end of file +build/ +cmake-build-debug/ +vcpkg_installed/ +.idea +resources/ +*.fbx +workspace/ +*~ +vcpkg/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index a0a57f3..5f3ed45 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "vcpkg"] - path = vcpkg - url = https://github.com/microsoft/vcpkg.git +[submodule "vcpkg"] + path = vcpkg + url = https://github.com/microsoft/vcpkg.git diff --git a/CMakeLists.txt b/CMakeLists.txt index f3f04b4..3bfaa96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,100 +1,100 @@ -cmake_minimum_required(VERSION 3.28) -project(Reactor) - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -find_package(Vulkan REQUIRED) -find_package(VulkanHeaders CONFIG) -find_package(glfw3 CONFIG REQUIRED) -find_package(spdlog CONFIG REQUIRED) -find_package(VulkanMemoryAllocator CONFIG REQUIRED) -find_package(glm CONFIG REQUIRED) -find_package(imgui CONFIG REQUIRED) -find_package(assimp CONFIG REQUIRED) - -add_library(ReactorLib STATIC - src/vulkan/VulkanContext.cpp - src/vulkan/VulkanContext.hpp - src/pch.hpp - src/vulkan/VulkanRenderer.hpp - src/vulkan/VulkanRenderer.cpp - src/core/Window.cpp - src/core/Window.hpp - src/vulkan/Swapchain.hpp - src/vulkan/FrameManager.hpp - src/vulkan/Swapchain.cpp - src/vulkan/FrameManager.cpp - src/vulkan/Pipeline.cpp - src/vulkan/Pipeline.hpp - src/vulkan/DescriptorSet.cpp - src/vulkan/DescriptorSet.hpp - src/vulkan/Allocator.cpp - src/vulkan/Allocator.hpp - src/vulkan/vk_mem_alloc.cpp - src/vulkan/Buffer.cpp - src/vulkan/Buffer.hpp - src/imgui/Imgui.cpp - src/imgui/Imgui.hpp - src/vulkan/VulkanUtils.hpp - src/vulkan/Image.cpp - src/vulkan/Image.hpp - src/vulkan/Sampler.cpp - src/vulkan/Sampler.hpp - src/vulkan/ImageStateTracker.cpp - src/vulkan/ImageStateTracker.h - src/core/Uniforms.hpp - src/vulkan/UniformManager.cpp - src/vulkan/UniformManager.hpp - src/vulkan/ShaderModule.cpp - src/vulkan/ShaderModule.hpp - src/core/EventManager.hpp - src/core/EventManager.cpp - src/core/Camera.cpp - src/core/Camera.hpp - src/core/OrbitController.cpp - src/core/OrbitController.hpp - src/core/Application.cpp - src/core/Application.hpp - src/vulkan/Vertex.hpp - src/vulkan/ImageUtils.hpp - src/vulkan/ImageUtils.cpp - src/vulkan/Mesh.cpp - src/vulkan/Mesh.hpp - src/vulkan/MeshGenerators.hpp - src/vulkan/MeshGenerators.cpp - src/core/ModelIO.hpp - src/core/ModelIO.cpp - src/vulkan/ShadowMapping.hpp - src/vulkan/ShadowMapping.cpp - src/logging/Logger.hpp - src/logging/SpdlogSink.cpp - src/logging/SpdlogSink.hpp - src/logging/ImGuiConsoleSink.cpp - src/logging/ImGuiConsoleSink.hpp - src/vulkan/DebugUtils.hpp -) - -target_compile_definitions(ReactorLib PUBLIC GLM_ENABLE_EXPERIMENTAL) -target_compile_definitions(ReactorLib PUBLIC GLM_FORCE_DEPTH_ZERO_TO_ONE) - -target_link_libraries(ReactorLib PUBLIC - Vulkan::Vulkan - Vulkan::Headers - glfw - fmt::fmt - spdlog::spdlog - GPUOpen::VulkanMemoryAllocator - glm::glm - imgui::imgui - assimp::assimp -) - -target_precompile_headers(ReactorLib PRIVATE src/pch.hpp) - -add_executable(Editor src/core/main.cpp) -target_link_libraries(Editor PRIVATE ReactorLib) - -add_executable(BuildModel src/tools/ModelBuilder.cpp) -target_link_libraries(BuildModel PRIVATE ReactorLib) +cmake_minimum_required(VERSION 3.28) +project(Reactor) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +find_package(Vulkan REQUIRED) +find_package(VulkanHeaders CONFIG) +find_package(glfw3 CONFIG REQUIRED) +find_package(spdlog CONFIG REQUIRED) +find_package(VulkanMemoryAllocator CONFIG REQUIRED) +find_package(glm CONFIG REQUIRED) +find_package(imgui CONFIG REQUIRED) +find_package(assimp CONFIG REQUIRED) + +add_library(ReactorLib STATIC + src/vulkan/VulkanContext.cpp + src/vulkan/VulkanContext.hpp + src/pch.hpp + src/vulkan/VulkanRenderer.hpp + src/vulkan/VulkanRenderer.cpp + src/core/Window.cpp + src/core/Window.hpp + src/vulkan/Swapchain.hpp + src/vulkan/FrameManager.hpp + src/vulkan/Swapchain.cpp + src/vulkan/FrameManager.cpp + src/vulkan/Pipeline.cpp + src/vulkan/Pipeline.hpp + src/vulkan/DescriptorSet.cpp + src/vulkan/DescriptorSet.hpp + src/vulkan/Allocator.cpp + src/vulkan/Allocator.hpp + src/vulkan/vk_mem_alloc.cpp + src/vulkan/Buffer.cpp + src/vulkan/Buffer.hpp + src/imgui/Imgui.cpp + src/imgui/Imgui.hpp + src/vulkan/VulkanUtils.hpp + src/vulkan/Image.cpp + src/vulkan/Image.hpp + src/vulkan/Sampler.cpp + src/vulkan/Sampler.hpp + src/vulkan/ImageStateTracker.cpp + src/vulkan/ImageStateTracker.h + src/core/Uniforms.hpp + src/vulkan/UniformManager.cpp + src/vulkan/UniformManager.hpp + src/vulkan/ShaderModule.cpp + src/vulkan/ShaderModule.hpp + src/core/EventManager.hpp + src/core/EventManager.cpp + src/core/Camera.cpp + src/core/Camera.hpp + src/core/OrbitController.cpp + src/core/OrbitController.hpp + src/core/Application.cpp + src/core/Application.hpp + src/vulkan/Vertex.hpp + src/vulkan/ImageUtils.hpp + src/vulkan/ImageUtils.cpp + src/vulkan/Mesh.cpp + src/vulkan/Mesh.hpp + src/vulkan/MeshGenerators.hpp + src/vulkan/MeshGenerators.cpp + src/core/ModelIO.hpp + src/core/ModelIO.cpp + src/vulkan/ShadowMapping.hpp + src/vulkan/ShadowMapping.cpp + src/logging/Logger.hpp + src/logging/SpdlogSink.cpp + src/logging/SpdlogSink.hpp + src/logging/ImGuiConsoleSink.cpp + src/logging/ImGuiConsoleSink.hpp + src/vulkan/DebugUtils.hpp +) + +target_compile_definitions(ReactorLib PUBLIC GLM_ENABLE_EXPERIMENTAL) +target_compile_definitions(ReactorLib PUBLIC GLM_FORCE_DEPTH_ZERO_TO_ONE) + +target_link_libraries(ReactorLib PUBLIC + Vulkan::Vulkan + Vulkan::Headers + glfw + fmt::fmt + spdlog::spdlog + GPUOpen::VulkanMemoryAllocator + glm::glm + imgui::imgui + assimp::assimp +) + +target_precompile_headers(ReactorLib PRIVATE src/pch.hpp) + +add_executable(Editor src/core/main.cpp) +target_link_libraries(Editor PRIVATE ReactorLib) + +add_executable(BuildModel src/tools/ModelBuilder.cpp) +target_link_libraries(BuildModel PRIVATE ReactorLib) diff --git a/CMakePresets.json b/CMakePresets.json index 97325ba..72c5861 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,74 +1,97 @@ -{ - "version": 6, - "configurePresets": [ - { - "name": "win32-gcc-x64-mingw", - "displayName": "Win32 GCC x64 MinGW", - "generator": "Ninja", - "binaryDir": "${sourceDir}/build/win32-gcc-x64-mingw", - "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "vcpkg/scripts/buildsystems/vcpkg.cmake", - "VCPKG_TARGET_TRIPLET": "x64-mingw-dynamic", - "CMAKE_MAKE_PROGRAM": "C:/msys64/ucrt64/bin/ninja", - "CMAKE_CXX_FLAGS": "-Wall -Wextra -Wpedantic -Werror", - "CMAKE_BUILD_TYPE": "Debug" - }, - "environment": { - "VCPKG_DEFAULT_TRIPLET": "x64-mingw-dynamic", - "VCPKG_DEFAULT_HOST_TRIPLET": "x64-mingw-dynamic" - } - }, - { - "name": "macos-clang-x64", - "displayName": "macOS Clang x64", - "generator": "Ninja", - "binaryDir": "${sourceDir}/build/macos-clang-x64", - "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "vcpkg/scripts/buildsystems/vcpkg.cmake", - "VCPKG_TARGET_TRIPLET": "x64-osx", - "CMAKE_CXX_FLAGS": "-Wall -Wextra -Wpedantic -Werror", - "CMAKE_BUILD_TYPE": "Debug" - } - }, - { - "name": "win32-msvc-vs2022", - "displayName": "Windows MSVC Visual Studio 2022", - "generator": "Ninja", - "binaryDir": "${sourceDir}/build/win32-msvc-vs2022", - "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "vcpkg/scripts/buildsystems/vcpkg.cmake", - "VCPKG_TARGET_TRIPLET": "x64-windows", - "CMAKE_CXX_FLAGS": "/W4 /WX", - "CMAKE_BUILD_TYPE": "Debug" - }, - "architecture": { - "value": "x64", - "strategy": "external" - } - } - ], - "buildPresets": [ - { - "name": "win32-gcc-x64-mingw-debug", - "displayName": "Win32 GCC x64 MinGW", - "configurePreset": "win32-gcc-x64-mingw", - "description": "Build for Win32 GCC x64 MinGW", - "configuration": "Debug" - }, - { - "name": "macos-clang-x64-debug", - "displayName": "macOS Clang x64 Debug", - "configurePreset": "macos-clang-x64", - "description": "Build for macOS Clang x64", - "configuration": "Debug" - }, - { - "name": "win32-msvc-vs2022-debug", - "displayName": "Windows MSVC VS2022 Debug", - "configurePreset": "win32-msvc-vs2022", - "description": "Build using MSVC on Windows via Visual Studio 2022", - "configuration": "Debug" - } - - ] +{ + "version": 6, + "configurePresets": [ + { + "name": "win32-gcc-x64-mingw", + "displayName": "Win32 GCC x64 MinGW", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/win32-gcc-x64-mingw", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "vcpkg/scripts/buildsystems/vcpkg.cmake", + "VCPKG_TARGET_TRIPLET": "x64-mingw-dynamic", + "CMAKE_MAKE_PROGRAM": "C:/msys64/ucrt64/bin/ninja", + "CMAKE_CXX_FLAGS": "-Wall -Wextra -Wpedantic -Werror", + "CMAKE_BUILD_TYPE": "Debug" + }, + "environment": { + "VCPKG_DEFAULT_TRIPLET": "x64-mingw-dynamic", + "VCPKG_DEFAULT_HOST_TRIPLET": "x64-mingw-dynamic" + } + }, + { + "name": "macos-clang-x64", + "displayName": "macOS Clang x64", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/macos-clang-x64", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "vcpkg/scripts/buildsystems/vcpkg.cmake", + "VCPKG_TARGET_TRIPLET": "x64-osx", + "CMAKE_CXX_FLAGS": "-Wall -Wextra -Wpedantic -Werror", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "win32-msvc-vs2022", + "displayName": "Windows MSVC Visual Studio 2022", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/win32-msvc-vs2022", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "vcpkg/scripts/buildsystems/vcpkg.cmake", + "VCPKG_TARGET_TRIPLET": "x64-windows", + "CMAKE_CXX_FLAGS": "/W4 /WX", + "CMAKE_BUILD_TYPE": "Debug" + }, + "architecture": { + "value": "x64", + "strategy": "external" + } + }, + { + "name": "linux-gcc-x64", + "displayName": "Linux GCC x64", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/linux-gcc-x64", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "vcpkg/scripts/buildsystems/vcpkg.cmake", + "VCPKG_TARGET_TRIPLET": "x64-linux", + "CMAKE_CXX_FLAGS": "-Wall -Wextra -Wpedantic -Werror", + "CMAKE_BUILD_TYPE": "Debug" + }, + "environment": { + "VCPKG_DEFAULT_TRIPLET": "x64-linux", + "VCPKG_DEFAULT_HOST_TRIPLET": "x64-linux" + } + } + ], + "buildPresets": [ + { + "name": "win32-gcc-x64-mingw-debug", + "displayName": "Win32 GCC x64 MinGW", + "configurePreset": "win32-gcc-x64-mingw", + "description": "Build for Win32 GCC x64 MinGW", + "configuration": "Debug" + }, + { + "name": "macos-clang-x64-debug", + "displayName": "macOS Clang x64 Debug", + "configurePreset": "macos-clang-x64", + "description": "Build for macOS Clang x64", + "configuration": "Debug" + }, + { + "name": "win32-msvc-vs2022-debug", + "displayName": "Windows MSVC VS2022 Debug", + "configurePreset": "win32-msvc-vs2022", + "description": "Build using MSVC on Windows via Visual Studio 2022", + "configuration": "Debug" + }, + { + "name": "linux-gcc-x64-debug", + "displayName": "Linux GCC x64 Debug", + "configurePreset": "linux-gcc-x64", + "description": "Build for Linux GCC x64", + "configuration": "Debug" + } + + ] } \ No newline at end of file diff --git a/LICENSE b/LICENSE index e35d6ae..841b093 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2025 Robert F. Dickerson - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. +MIT License + +Copyright (c) 2025 Robert F. Dickerson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/README.md b/README.md index 0fdaa1b..1f9610b 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,28 @@ -# Reactor - -A modern C++ Vulkan renderer built using Vulkan-Hpp, designed with extensibility and clarity in mind. This renderer leverages dynamic rendering, multi-sample anti-aliasing (MSAA), HDR rendering, and a modular render loop architecture to simplify development of real-time graphics applications. - -## ✨ Features -- Vulkan-Hpp: Modern C++ bindings for Vulkan for type safety and cleaner API. -- Dynamic Rendering: No need for complex render pass setups. -- MSAA + Resolve: High-quality anti-aliasing using MSAA images resolved into HDR buffers. -- HDR Rendering: Full 16-bit float rendering pipeline using R16G16B16A16_SFLOAT. -- ImGui Integration: Built-in user interface rendering support. -- Robust Frame Management: Supports multiple frames in flight. -- Descriptor Management: Automatic uniform buffer updates and descriptor set handling. -- Swapchain Resilience: Handles window resize events and swapchain recreation cleanly. - -## 🚀 Getting Started - -**Prerequisites** -- Vulkan SDK (1.3+) -- C++20 compiler (e.g., clang++ or g++) -- CMake (3.18+) -- glfw for windowing -- glm for math -- Vulkan Memory Allocator (VMA) for efficient GPU memory management -- ImGui for UI - -### 📜 License - -MIT License. See LICENSE for details. +# Reactor + +A modern C++ Vulkan renderer built using Vulkan-Hpp, designed with extensibility and clarity in mind. This renderer leverages dynamic rendering, multi-sample anti-aliasing (MSAA), HDR rendering, and a modular render loop architecture to simplify development of real-time graphics applications. + +## ✨ Features +- Vulkan-Hpp: Modern C++ bindings for Vulkan for type safety and cleaner API. +- Dynamic Rendering: No need for complex render pass setups. +- MSAA + Resolve: High-quality anti-aliasing using MSAA images resolved into HDR buffers. +- HDR Rendering: Full 16-bit float rendering pipeline using R16G16B16A16_SFLOAT. +- ImGui Integration: Built-in user interface rendering support. +- Robust Frame Management: Supports multiple frames in flight. +- Descriptor Management: Automatic uniform buffer updates and descriptor set handling. +- Swapchain Resilience: Handles window resize events and swapchain recreation cleanly. + +## 🚀 Getting Started + +**Prerequisites** +- Vulkan SDK (1.3+) +- C++20 compiler (e.g., clang++ or g++) +- CMake (3.18+) +- glfw for windowing +- glm for math +- Vulkan Memory Allocator (VMA) for efficient GPU memory management +- ImGui for UI + +### 📜 License + +MIT License. See LICENSE for details. diff --git a/build-shaders.bat b/build-shaders.bat index 82a22f7..e14fcd3 100644 --- a/build-shaders.bat +++ b/build-shaders.bat @@ -1,5 +1,5 @@ -slangc -g -O0 shaders/triangle.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/triangle-slang.vert.spv -slangc -g -O0 shaders/triangle.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/triangle-slang.frag.spv - -slangc -g -O0 shaders/composite.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/composite-slang.vert.spv +slangc -g -O0 shaders/triangle.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/triangle-slang.vert.spv +slangc -g -O0 shaders/triangle.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/triangle-slang.frag.spv + +slangc -g -O0 shaders/composite.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/composite-slang.vert.spv slangc -g -O0 shaders/composite.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/composite-slang.frag.spv \ No newline at end of file diff --git a/shaders/composite.slang b/shaders/composite.slang index 0db3a9e..eec1236 100644 --- a/shaders/composite.slang +++ b/shaders/composite.slang @@ -1,107 +1,107 @@ -struct Varyings -{ - float4 position : SV_Position; // System value for clip space position - float2 uv : TEXCOORD0; // The UV coordinates for sampling -}; - -/** - * The C++ code in `VulkanRenderer.cpp` creates a multisampled depth - * image with 4 samples. We explicitly define that here. - */ -static const int MSAA_SAMPLES = 4; - -[[vk::binding(0, 0)]] -Texture2D uInputImage : register(t0); - -[[vk::binding(3, 0)]] -SamplerState g_sampler : register(s3); - -[[vk::binding(2, 0)]] -Texture2D uDepthImage : register(t2); - -[[vk::binding(1, 0)]] -cbuffer CompositeParams : register(b1) -{ - float uExposure; - float uContrast; - float uSaturation; - float uVignetteIntensity; - float uVignetteFalloff; - float uFogDensity; -}; - -float linearizeDepth(float depth) -{ - const float Z_NEAR = 0.1; - const float Z_FAR = 100.0; - float z_n = 2.0 * depth - 1.0; - return (2.0 * Z_NEAR * Z_FAR) / (Z_FAR + Z_NEAR - z_n * (Z_FAR - Z_NEAR)); -} - -float3 tonemapACES(float3 x) -{ - const float a = 2.51; - const float b = 0.03; - const float c = 2.43; - const float d = 0.59; - const float e = 0.14; - return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0); -} - -[shader("vertex")] -Varyings vertexMain(uint vertexID : SV_VertexID) -{ - Varyings output; - - // Defines the triangle vertices to cover the screen[cite: 13]. - const float2 pos[3] = { - float2(-1.0, -1.0), - float2( 3.0, -1.0), - float2(-1.0, 3.0) - }; - // Defines the UV coordinates for the triangle[cite: 14]. - const float2 uv[3] = { - float2(0.0, 0.0), - float2(2.0, 0.0), - float2(0.0, 2.0) - }; - - output.uv = uv[vertexID]; - output.position = float4(pos[vertexID], 0.0, 1.0); - - return output; -} - -[shader("fragment")] -float4 fragmentMain(Varyings input) : SV_Target -{ - // Get the integer coordinates for the current fragment. - int2 texelCoord = int2(input.position.xy); - - // To read from a Texture2DMS, use the `.Load()` method. - // It takes the coordinate and the sample index (we'll just use the first). - // This replaces `texelFetch(uDepthImage, texelCoord, 0)`. - float depth = uDepthImage.Sample(g_sampler, input.uv).r; - - // --- Fog Calculation --- - float3 fogColor = float3(0.5, 0.6, 0.7); - float viewDistance = linearizeDepth(depth); - float fogFactor = 1.0 - exp(-viewDistance * uFogDensity); - - // --- Color Processing --- - // To sample a standard texture, use the `.Sample()` method. - // This replaces `texture(uInputImage, fragUV)`. - float3 hdrColor = uInputImage.Sample(g_sampler, input.uv).rgb; - - // Apply exposure, tonemapping, and color grading[cite: 35, 36, 37, 38, 39, 40]. - float3 exposedColor = hdrColor * uExposure; - float3 ldrColor = tonemapACES(exposedColor); - float3 finalColor = ldrColor; - - finalColor = pow(finalColor, float3(uContrast)); - float3 grayscale = float3(dot(finalColor, float3(0.299, 0.587, 0.114))); - finalColor = lerp(grayscale, finalColor, uSaturation); - finalColor = lerp(finalColor, fogColor, fogFactor); - - return float4(finalColor, 1.0); +struct Varyings +{ + float4 position : SV_Position; // System value for clip space position + float2 uv : TEXCOORD0; // The UV coordinates for sampling +}; + +/** + * The C++ code in `VulkanRenderer.cpp` creates a multisampled depth + * image with 4 samples. We explicitly define that here. + */ +static const int MSAA_SAMPLES = 4; + +[[vk::binding(0, 0)]] +Texture2D uInputImage : register(t0); + +[[vk::binding(3, 0)]] +SamplerState g_sampler : register(s3); + +[[vk::binding(2, 0)]] +Texture2D uDepthImage : register(t2); + +[[vk::binding(1, 0)]] +cbuffer CompositeParams : register(b1) +{ + float uExposure; + float uContrast; + float uSaturation; + float uVignetteIntensity; + float uVignetteFalloff; + float uFogDensity; +}; + +float linearizeDepth(float depth) +{ + const float Z_NEAR = 0.1; + const float Z_FAR = 100.0; + float z_n = 2.0 * depth - 1.0; + return (2.0 * Z_NEAR * Z_FAR) / (Z_FAR + Z_NEAR - z_n * (Z_FAR - Z_NEAR)); +} + +float3 tonemapACES(float3 x) +{ + const float a = 2.51; + const float b = 0.03; + const float c = 2.43; + const float d = 0.59; + const float e = 0.14; + return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0); +} + +[shader("vertex")] +Varyings vertexMain(uint vertexID : SV_VertexID) +{ + Varyings output; + + // Defines the triangle vertices to cover the screen[cite: 13]. + const float2 pos[3] = { + float2(-1.0, -1.0), + float2( 3.0, -1.0), + float2(-1.0, 3.0) + }; + // Defines the UV coordinates for the triangle[cite: 14]. + const float2 uv[3] = { + float2(0.0, 0.0), + float2(2.0, 0.0), + float2(0.0, 2.0) + }; + + output.uv = uv[vertexID]; + output.position = float4(pos[vertexID], 0.0, 1.0); + + return output; +} + +[shader("fragment")] +float4 fragmentMain(Varyings input) : SV_Target +{ + // Get the integer coordinates for the current fragment. + int2 texelCoord = int2(input.position.xy); + + // To read from a Texture2DMS, use the `.Load()` method. + // It takes the coordinate and the sample index (we'll just use the first). + // This replaces `texelFetch(uDepthImage, texelCoord, 0)`. + float depth = uDepthImage.Sample(g_sampler, input.uv).r; + + // --- Fog Calculation --- + float3 fogColor = float3(0.5, 0.6, 0.7); + float viewDistance = linearizeDepth(depth); + float fogFactor = 1.0 - exp(-viewDistance * uFogDensity); + + // --- Color Processing --- + // To sample a standard texture, use the `.Sample()` method. + // This replaces `texture(uInputImage, fragUV)`. + float3 hdrColor = uInputImage.Sample(g_sampler, input.uv).rgb; + + // Apply exposure, tonemapping, and color grading[cite: 35, 36, 37, 38, 39, 40]. + float3 exposedColor = hdrColor * uExposure; + float3 ldrColor = tonemapACES(exposedColor); + float3 finalColor = ldrColor; + + finalColor = pow(finalColor, float3(uContrast)); + float3 grayscale = float3(dot(finalColor, float3(0.299, 0.587, 0.114))); + finalColor = lerp(grayscale, finalColor, uSaturation); + finalColor = lerp(finalColor, fogColor, fogFactor); + + return float4(finalColor, 1.0); } \ No newline at end of file diff --git a/shaders/triangle.slang b/shaders/triangle.slang index d2228b2..f1515f0 100644 --- a/shaders/triangle.slang +++ b/shaders/triangle.slang @@ -1,157 +1,157 @@ -// Combined Slang Shader Module -// triangle.slang - -// ========================================================================= -// 1. Data Structures & Resource Bindings -// ========================================================================= - -/** - * Defines the input attributes for a single vertex. - * These correspond to the `layout(location = ...)` inputs in the GLSL - * vertex shader. Semantics like `POSITION` and `NORMAL` are used instead - * of raw location indices. - */ -struct VertexInput -{ - float3 position : POSITION; // Corresponds to GLSL: layout(location = 0) in vec3 inPosition; - float3 normal : NORMAL; // Corresponds to GLSL: layout(location = 1) in vec3 inNormal; - float3 color : COLOR; // Corresponds to GLSL: layout(location = 2) in vec3 inColor; - float2 texCoord : TEXCOORD0; // Corresponds to GLSL: layout(location = 3) in vec2 inTexCoord; -}; - -/** - * Defines the data passed from the vertex shader to the fragment shader. - * This replaces the individual `out` variables in the vertex shader and - * `in` variables in the fragment shader. `SV_Position` is a system-value - * semantic required for the final clip-space position. - */ -struct Varyings -{ - float4 position : SV_Position; // The final position for the rasterizer. - float3 worldPos : WORLD_POS; // Corresponds to GLSL: outWorldPos - float3 normal : NORMAL; // Corresponds to GLSL: outNormal - float4 lightSpacePos : LIGHT_SPACE_POS; // Corresponds to GLSL: outLightSpacePos -}; - -/** - * Per-scene constants, equivalent to the `SceneUBO` in GLSL. - * The `register(b0)` attribute maps this buffer to binding 0. - */ -[[vk::binding(0, 0)]] -cbuffer SceneUBO : register(b0) -{ - matrix view; - matrix projection; - matrix lightSpaceMatrix; -}; - -/** - * Per-light constants, equivalent to the `LightUBO` in GLSL. - * The `register(b1)` attribute maps this buffer to binding 1. - */ -[[vk::binding(1, 0)]] -cbuffer LightUBO : register(b1) -{ - float4 lightDirection; - float4 lightColor; - float lightIntensity; -}; - -/** - * Push constants for per-draw call data, like the model matrix. - */ -[push_constant] -cbuffer PushConstants -{ - matrix model; -}; - -/** - * In GLSL, `sampler2DShadow` is a combined type. In Slang (and HLSL), - * the texture and its sampler state are typically defined separately for - * greater flexibility. We bind them to the same slot index `2` but on - * different resource types (`t` for texture, `s` for sampler). - */ -[[vk::binding(2, 0)]] -Texture2D shadowMap : register(t2); - -[[vk::binding(3, 0)]] -SamplerComparisonState shadowSampler : register(s2); - - -// ========================================================================= -// 2. Shadow Calculation -// ========================================================================= - -/** - * Calculates the shadow contribution. This logic is identical to the - * GLSL `calculateShadow` function, but it now takes the light-space - * position as a parameter instead of reading it from a global input. - */ -float calculateShadow(Varyings input) -{ - // Perform perspective divide - float3 projCoords = input.lightSpacePos.xyz / input.lightSpacePos.w; - - // Convert from [-1, 1] to [0, 1] texture coordinates - projCoords.xy = projCoords.xy * 0.5 + 0.5; - - // Sample the shadow map using a comparison sampler. - // The `SampleCmp` function performs the depth test (projCoords.z) - // against the value in the shadow map at projCoords.xy. - // This is the Slang equivalent of `texture(shadowMap, projCoords)`. - return shadowMap.SampleCmp(shadowSampler, projCoords.xy, projCoords.z); -} - - -// ========================================================================= -// 3. Vertex Shader Stage -// ========================================================================= - -[shader("vertex")] -Varyings vertexMain(VertexInput input) -{ - Varyings output; - - // Transform position and normal to world space - float4 worldPos = mul(model, float4(input.position, 1.0)); - output.worldPos = worldPos.xyz; - output.normal = normalize(mul((float3x3)model, input.normal)); - - // Calculate the final clip-space position - output.position = mul(projection, mul(view, worldPos)); - - // Transform world position to light's view space for shadowing - output.lightSpacePos = mul(lightSpaceMatrix, worldPos); - - return output; -} - - -// ========================================================================= -// 4. Fragment Shader Stage -// ========================================================================= - -[shader("fragment")] -float4 fragmentMain(Varyings input) : SV_Target -{ - float3 objectColor = float3(0.8, 0.8, 0.8); - - // Ambient light component - float3 ambient = objectColor * 0.1; - - // Calculate the diffuse (directional) light component - float3 normal = normalize(input.normal); - float3 lightDir = normalize(-lightDirection.xyz); - float diffuseFactor = max(dot(normal, lightDir), 0.0); - float3 diffuse = objectColor * lightColor.rgb * lightIntensity * diffuseFactor; - - // Get the shadow factor (1.0 for lit, < 1.0 for shadowed) - float shadow = calculateShadow(input); - - // Combine components: ambient light is always present, but diffuse - // light is affected by the shadow factor. - float3 finalColor = ambient + (diffuse * shadow); - - return float4(finalColor, 1.0); +// Combined Slang Shader Module +// triangle.slang + +// ========================================================================= +// 1. Data Structures & Resource Bindings +// ========================================================================= + +/** + * Defines the input attributes for a single vertex. + * These correspond to the `layout(location = ...)` inputs in the GLSL + * vertex shader. Semantics like `POSITION` and `NORMAL` are used instead + * of raw location indices. + */ +struct VertexInput +{ + float3 position : POSITION; // Corresponds to GLSL: layout(location = 0) in vec3 inPosition; + float3 normal : NORMAL; // Corresponds to GLSL: layout(location = 1) in vec3 inNormal; + float3 color : COLOR; // Corresponds to GLSL: layout(location = 2) in vec3 inColor; + float2 texCoord : TEXCOORD0; // Corresponds to GLSL: layout(location = 3) in vec2 inTexCoord; +}; + +/** + * Defines the data passed from the vertex shader to the fragment shader. + * This replaces the individual `out` variables in the vertex shader and + * `in` variables in the fragment shader. `SV_Position` is a system-value + * semantic required for the final clip-space position. + */ +struct Varyings +{ + float4 position : SV_Position; // The final position for the rasterizer. + float3 worldPos : WORLD_POS; // Corresponds to GLSL: outWorldPos + float3 normal : NORMAL; // Corresponds to GLSL: outNormal + float4 lightSpacePos : LIGHT_SPACE_POS; // Corresponds to GLSL: outLightSpacePos +}; + +/** + * Per-scene constants, equivalent to the `SceneUBO` in GLSL. + * The `register(b0)` attribute maps this buffer to binding 0. + */ +[[vk::binding(0, 0)]] +cbuffer SceneUBO : register(b0) +{ + matrix view; + matrix projection; + matrix lightSpaceMatrix; +}; + +/** + * Per-light constants, equivalent to the `LightUBO` in GLSL. + * The `register(b1)` attribute maps this buffer to binding 1. + */ +[[vk::binding(1, 0)]] +cbuffer LightUBO : register(b1) +{ + float4 lightDirection; + float4 lightColor; + float lightIntensity; +}; + +/** + * Push constants for per-draw call data, like the model matrix. + */ +[push_constant] +cbuffer PushConstants +{ + matrix model; +}; + +/** + * In GLSL, `sampler2DShadow` is a combined type. In Slang (and HLSL), + * the texture and its sampler state are typically defined separately for + * greater flexibility. We bind them to the same slot index `2` but on + * different resource types (`t` for texture, `s` for sampler). + */ +[[vk::binding(2, 0)]] +Texture2D shadowMap : register(t2); + +[[vk::binding(3, 0)]] +SamplerComparisonState shadowSampler : register(s2); + + +// ========================================================================= +// 2. Shadow Calculation +// ========================================================================= + +/** + * Calculates the shadow contribution. This logic is identical to the + * GLSL `calculateShadow` function, but it now takes the light-space + * position as a parameter instead of reading it from a global input. + */ +float calculateShadow(Varyings input) +{ + // Perform perspective divide + float3 projCoords = input.lightSpacePos.xyz / input.lightSpacePos.w; + + // Convert from [-1, 1] to [0, 1] texture coordinates + projCoords.xy = projCoords.xy * 0.5 + 0.5; + + // Sample the shadow map using a comparison sampler. + // The `SampleCmp` function performs the depth test (projCoords.z) + // against the value in the shadow map at projCoords.xy. + // This is the Slang equivalent of `texture(shadowMap, projCoords)`. + return shadowMap.SampleCmp(shadowSampler, projCoords.xy, projCoords.z); +} + + +// ========================================================================= +// 3. Vertex Shader Stage +// ========================================================================= + +[shader("vertex")] +Varyings vertexMain(VertexInput input) +{ + Varyings output; + + // Transform position and normal to world space + float4 worldPos = mul(model, float4(input.position, 1.0)); + output.worldPos = worldPos.xyz; + output.normal = normalize(mul((float3x3)model, input.normal)); + + // Calculate the final clip-space position + output.position = mul(projection, mul(view, worldPos)); + + // Transform world position to light's view space for shadowing + output.lightSpacePos = mul(lightSpaceMatrix, worldPos); + + return output; +} + + +// ========================================================================= +// 4. Fragment Shader Stage +// ========================================================================= + +[shader("fragment")] +float4 fragmentMain(Varyings input) : SV_Target +{ + float3 objectColor = float3(0.8, 0.8, 0.8); + + // Ambient light component + float3 ambient = objectColor * 0.1; + + // Calculate the diffuse (directional) light component + float3 normal = normalize(input.normal); + float3 lightDir = normalize(-lightDirection.xyz); + float diffuseFactor = max(dot(normal, lightDir), 0.0); + float3 diffuse = objectColor * lightColor.rgb * lightIntensity * diffuseFactor; + + // Get the shadow factor (1.0 for lit, < 1.0 for shadowed) + float shadow = calculateShadow(input); + + // Combine components: ambient light is always present, but diffuse + // light is affected by the shadow factor. + float3 finalColor = ambient + (diffuse * shadow); + + return float4(finalColor, 1.0); } \ No newline at end of file diff --git a/src/core/Application.cpp b/src/core/Application.cpp index 1ab9ff2..350eeaf 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -1,45 +1,45 @@ -#include "Application.hpp" - -namespace reactor { - - Application::Application() { - initialize(); - } - -Application::~Application() = default; - -void Application::initialize() { - m_eventManager = std::make_unique(); - - m_window = std::make_unique(1280, 720, "Reactor", *m_eventManager); - m_camera = std::make_unique(); - m_orbitController = std::make_unique(*m_camera); - m_orbitController->setSimCityView(20.0f, 45.0f); - - // subscribe controller to input events. - m_eventManager->subscribe(EventType::MouseMoved, m_orbitController.get()); - m_eventManager->subscribe(EventType::MouseButtonPressed, m_orbitController.get()); - m_eventManager->subscribe(EventType::MouseButtonReleased, m_orbitController.get()); - - RendererConfig config{ - .windowWidth = 1280, - .windowHeight = 720, - .windowTitle = "Reactor", - .vertShaderPath = "../resources/shaders/triangle-slang.vert.spv", - .fragShaderPath = "../resources/shaders/triangle-slang.frag.spv", - .compositeVertShaderPath = "../resources/shaders/composite-slang.vert.spv", - .compositeFragShaderPath = "../resources/shaders/composite-slang.frag.spv" - }; - - m_renderer = std::make_unique(config, *m_window, *m_camera); -} - -void Application::run() const { - while (!m_window->shouldClose()) { - Window::pollEvents(); - m_renderer->drawFrame(); - } -} - - +#include "Application.hpp" + +namespace reactor { + + Application::Application() { + initialize(); + } + +Application::~Application() = default; + +void Application::initialize() { + m_eventManager = std::make_unique(); + + m_window = std::make_unique(1280, 720, "Reactor", *m_eventManager); + m_camera = std::make_unique(); + m_orbitController = std::make_unique(*m_camera); + m_orbitController->setSimCityView(20.0f, 45.0f); + + // subscribe controller to input events. + m_eventManager->subscribe(EventType::MouseMoved, m_orbitController.get()); + m_eventManager->subscribe(EventType::MouseButtonPressed, m_orbitController.get()); + m_eventManager->subscribe(EventType::MouseButtonReleased, m_orbitController.get()); + + RendererConfig config{ + .windowWidth = 1280, + .windowHeight = 720, + .windowTitle = "Reactor", + .vertShaderPath = "../resources/shaders/triangle-slang.vert.spv", + .fragShaderPath = "../resources/shaders/triangle-slang.frag.spv", + .compositeVertShaderPath = "../resources/shaders/composite-slang.vert.spv", + .compositeFragShaderPath = "../resources/shaders/composite-slang.frag.spv" + }; + + m_renderer = std::make_unique(config, *m_window, *m_camera); +} + +void Application::run() const { + while (!m_window->shouldClose()) { + Window::pollEvents(); + m_renderer->drawFrame(); + } +} + + } // reactor \ No newline at end of file diff --git a/src/core/Application.hpp b/src/core/Application.hpp index d9b9344..f540fcf 100644 --- a/src/core/Application.hpp +++ b/src/core/Application.hpp @@ -1,40 +1,40 @@ -#pragma once - -#include "Camera.hpp" -#include "EventManager.hpp" -#include "OrbitController.hpp" -#include "Window.hpp" -#include "../vulkan/VulkanRenderer.hpp" -#include - -namespace reactor { - -class Application { -public: - Application(); - ~Application(); - - // Prevent copying - Application(const Application&) = delete; - Application& operator=(const Application&) = delete; - - // Allow moving if needed - Application(Application&&) = default; - Application& operator=(Application&&) = default; - - // Runs the main application loop. Returns program exit code. - void run() const; - -private: - void initialize(); - - std::unique_ptr m_eventManager; - std::unique_ptr m_window; - std::unique_ptr m_camera; - std::unique_ptr m_orbitController; - std::unique_ptr m_renderer; - -}; - -} // namespace reactor - +#pragma once + +#include "Camera.hpp" +#include "EventManager.hpp" +#include "OrbitController.hpp" +#include "Window.hpp" +#include "../vulkan/VulkanRenderer.hpp" +#include + +namespace reactor { + +class Application { +public: + Application(); + ~Application(); + + // Prevent copying + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + + // Allow moving if needed + Application(Application&&) = default; + Application& operator=(Application&&) = default; + + // Runs the main application loop. Returns program exit code. + void run() const; + +private: + void initialize(); + + std::unique_ptr m_eventManager; + std::unique_ptr m_window; + std::unique_ptr m_camera; + std::unique_ptr m_orbitController; + std::unique_ptr m_renderer; + +}; + +} // namespace reactor + diff --git a/src/core/Camera.cpp b/src/core/Camera.cpp index 594f38a..8c47636 100644 --- a/src/core/Camera.cpp +++ b/src/core/Camera.cpp @@ -1,144 +1,144 @@ -#include "Camera.hpp" - -#include -#include -#include -#include -#include - -namespace reactor { - -Camera::Camera(): m_view(glm::mat4(1.0f)), m_projection(glm::mat4(1.0f)) { - updateProjection(); - updateView(); -} - -void Camera::setProjectionType(ProjectionType type) { - if (m_type != type) { - m_type = type; - m_projDirty = true; - } -} - -void Camera::setPerspective(float fov, float aspect, float near, float far) { - if (fov <= 0.0f || fov >= 180.0f || aspect <= 0.0f || near <= 0.0f || far <= 0.0f) { - throw std::invalid_argument("Invalid camera parameters"); - } - m_fov = fov; - m_aspect = aspect; - m_near = near; - m_far = far; - setProjectionType(ProjectionType::Perspective); - m_projDirty = true; -} - -void Camera::setPosition(const glm::vec3& position) { - m_position = position; - m_viewDirty = true; -} - -void Camera::setTarget(const glm::vec3& target) { - glm::vec3 direction = glm::normalize(target - m_position); - m_orientation = glm::quatLookAt(direction, m_up); - m_viewDirty = true; -} - -void Camera::setUp(const glm::vec3& up) { - m_up = glm::normalize(up); - m_viewDirty = true; -} - -void Camera::lookAt(const glm::vec3& eye, const glm::vec3& target, const glm::vec3& up) { - m_position = eye; - setTarget(target); - m_up = normalize(up); - m_viewDirty = true; -} - -void Camera::move(const glm::vec3& delta) { - m_position += delta; - m_viewDirty = true; -} - -void Camera::moveRelative(const glm::vec3 &delta) { - m_position += getRight() * delta.x + getUp() * delta.y + getForward() * delta.z; - m_viewDirty = true; -} - - -void Camera::rotate(float yaw, float pitch, float roll) { - glm::quat qYaw = glm::angleAxis(glm::radians(yaw), glm::vec3(0.0f, 1.0f, 0.0f)); - glm::quat qPitch = glm::angleAxis(glm::radians(pitch), getRight()); - glm::quat qRoll = glm::angleAxis(glm::radians(roll), getForward()); - m_orientation = glm::normalize(qYaw * m_orientation * qPitch * qRoll); - m_up = getUp(); - m_viewDirty = true; -} - -void Camera::dolly(float distance) { - glm::vec3 direction = getForward(); - m_position += direction * distance; - m_viewDirty = true; -} - -// --- Matrix getters --- - -const glm::mat4& Camera::getViewMatrix() const { - if (m_viewDirty) { - const_cast(this)->updateView(); - } - return m_view; -} - -const glm::mat4& Camera::getProjectionMatrix() const { - if (m_projDirty) { - const_cast(this)->updateProjection(); - } - return m_projection; -} - -float Camera::getFOV() const { - return m_fov; -} - -glm::vec3 Camera::getPosition() const { - return m_position; -} - -glm::vec3 Camera::getTarget() const { - return m_position + getForward(); -} - -glm::vec3 Camera::getForward() const { - return glm::vec3(m_orientation * glm::vec3(0.0f, 0.0f, -1.0f)); -} - -glm::vec3 Camera::getRight() const { - return glm::vec3(m_orientation * glm::vec3(1.0f, 0.0f, 0.0f)); -} - -glm::vec3 Camera::getUp() const { - return glm::normalize(m_orientation * glm::vec3(0.0f, 1.0f, 0.0f)); -} - -float Camera::getDistanceToTarget() const { - return 1.0; -} - -void Camera::updateView() { - glm::mat4 rotation = glm::toMat4(glm::conjugate(m_orientation)); - glm::mat4 translation = glm::translate(glm::mat4(1.0f), -m_position); - m_view = rotation * translation; - m_viewDirty = false; -} - -void Camera::updateProjection() { - if (m_type == ProjectionType::Perspective) { - m_projection = glm::perspective(glm::radians(m_fov), m_aspect, m_near, m_far); - } else { - m_projection = glm::ortho(m_left, m_right, m_bottom, m_top, m_near, m_far); - } - m_projDirty = false; -} - +#include "Camera.hpp" + +#include +#include +#include +#include +#include + +namespace reactor { + +Camera::Camera(): m_view(glm::mat4(1.0f)), m_projection(glm::mat4(1.0f)) { + updateProjection(); + updateView(); +} + +void Camera::setProjectionType(ProjectionType type) { + if (m_type != type) { + m_type = type; + m_projDirty = true; + } +} + +void Camera::setPerspective(float fov, float aspect, float near, float far) { + if (fov <= 0.0f || fov >= 180.0f || aspect <= 0.0f || near <= 0.0f || far <= 0.0f) { + throw std::invalid_argument("Invalid camera parameters"); + } + m_fov = fov; + m_aspect = aspect; + m_near = near; + m_far = far; + setProjectionType(ProjectionType::Perspective); + m_projDirty = true; +} + +void Camera::setPosition(const glm::vec3& position) { + m_position = position; + m_viewDirty = true; +} + +void Camera::setTarget(const glm::vec3& target) { + glm::vec3 direction = glm::normalize(target - m_position); + m_orientation = glm::quatLookAt(direction, m_up); + m_viewDirty = true; +} + +void Camera::setUp(const glm::vec3& up) { + m_up = glm::normalize(up); + m_viewDirty = true; +} + +void Camera::lookAt(const glm::vec3& eye, const glm::vec3& target, const glm::vec3& up) { + m_position = eye; + setTarget(target); + m_up = normalize(up); + m_viewDirty = true; +} + +void Camera::move(const glm::vec3& delta) { + m_position += delta; + m_viewDirty = true; +} + +void Camera::moveRelative(const glm::vec3 &delta) { + m_position += getRight() * delta.x + getUp() * delta.y + getForward() * delta.z; + m_viewDirty = true; +} + + +void Camera::rotate(float yaw, float pitch, float roll) { + glm::quat qYaw = glm::angleAxis(glm::radians(yaw), glm::vec3(0.0f, 1.0f, 0.0f)); + glm::quat qPitch = glm::angleAxis(glm::radians(pitch), getRight()); + glm::quat qRoll = glm::angleAxis(glm::radians(roll), getForward()); + m_orientation = glm::normalize(qYaw * m_orientation * qPitch * qRoll); + m_up = getUp(); + m_viewDirty = true; +} + +void Camera::dolly(float distance) { + glm::vec3 direction = getForward(); + m_position += direction * distance; + m_viewDirty = true; +} + +// --- Matrix getters --- + +const glm::mat4& Camera::getViewMatrix() const { + if (m_viewDirty) { + const_cast(this)->updateView(); + } + return m_view; +} + +const glm::mat4& Camera::getProjectionMatrix() const { + if (m_projDirty) { + const_cast(this)->updateProjection(); + } + return m_projection; +} + +float Camera::getFOV() const { + return m_fov; +} + +glm::vec3 Camera::getPosition() const { + return m_position; +} + +glm::vec3 Camera::getTarget() const { + return m_position + getForward(); +} + +glm::vec3 Camera::getForward() const { + return glm::vec3(m_orientation * glm::vec3(0.0f, 0.0f, -1.0f)); +} + +glm::vec3 Camera::getRight() const { + return glm::vec3(m_orientation * glm::vec3(1.0f, 0.0f, 0.0f)); +} + +glm::vec3 Camera::getUp() const { + return glm::normalize(m_orientation * glm::vec3(0.0f, 1.0f, 0.0f)); +} + +float Camera::getDistanceToTarget() const { + return 1.0; +} + +void Camera::updateView() { + glm::mat4 rotation = glm::toMat4(glm::conjugate(m_orientation)); + glm::mat4 translation = glm::translate(glm::mat4(1.0f), -m_position); + m_view = rotation * translation; + m_viewDirty = false; +} + +void Camera::updateProjection() { + if (m_type == ProjectionType::Perspective) { + m_projection = glm::perspective(glm::radians(m_fov), m_aspect, m_near, m_far); + } else { + m_projection = glm::ortho(m_left, m_right, m_bottom, m_top, m_near, m_far); + } + m_projDirty = false; +} + } // reactor \ No newline at end of file diff --git a/src/core/Camera.hpp b/src/core/Camera.hpp index d3f8104..7eba477 100644 --- a/src/core/Camera.hpp +++ b/src/core/Camera.hpp @@ -1,63 +1,63 @@ -#pragma once - -#include -#include -#include - -namespace reactor { - -enum class ProjectionType { - Perspective, - Orthographic, -}; - -class Camera { -public: - Camera(); - - void setProjectionType(ProjectionType type); - void setPerspective(float fov, float aspect, float near, float far); - - void setPosition(const glm::vec3& position); - void setTarget(const glm::vec3& target); - void setUp(const glm::vec3& up); - - void lookAt(const glm::vec3& eye, const glm::vec3& target, const glm::vec3& up); - void move(const glm::vec3& delta); - void moveRelative(const glm::vec3& delta); - void rotate(float yaw, float pitch, float roll); - void dolly(float distance); - - // getters - const glm::mat4& getViewMatrix() const; - const glm::mat4& getProjectionMatrix() const; - float getFOV() const; - glm::vec3 getPosition() const; - glm::vec3 getTarget() const; - glm::vec3 getForward() const; - glm::vec3 getRight() const; - glm::vec3 getUp() const; - float getDistanceToTarget() const; - -private: - void updateView(); - void updateProjection(); - - ProjectionType m_type{ProjectionType::Perspective}; - glm::mat4 m_view{1.0f}; - glm::mat4 m_projection{1.0f}; - - float m_fov{45.0f}, m_aspect{16.0f / 9.0f}, m_near{0.1f}, m_far{100.0f}; - float m_left, m_right, m_bottom, m_top; - - glm::vec3 m_position{0.0f, 0.0f, 5.0f}; - glm::quat m_orientation{1.0f, 0.0f, 0.0f, 0.0f}; - glm::vec3 m_up{0.0f, 1.0f, 0.0f}; - - mutable bool m_viewDirty{true}; - mutable bool m_projDirty{true}; -}; - -} // reactor - - +#pragma once + +#include +#include +#include + +namespace reactor { + +enum class ProjectionType { + Perspective, + Orthographic, +}; + +class Camera { +public: + Camera(); + + void setProjectionType(ProjectionType type); + void setPerspective(float fov, float aspect, float near, float far); + + void setPosition(const glm::vec3& position); + void setTarget(const glm::vec3& target); + void setUp(const glm::vec3& up); + + void lookAt(const glm::vec3& eye, const glm::vec3& target, const glm::vec3& up); + void move(const glm::vec3& delta); + void moveRelative(const glm::vec3& delta); + void rotate(float yaw, float pitch, float roll); + void dolly(float distance); + + // getters + const glm::mat4& getViewMatrix() const; + const glm::mat4& getProjectionMatrix() const; + float getFOV() const; + glm::vec3 getPosition() const; + glm::vec3 getTarget() const; + glm::vec3 getForward() const; + glm::vec3 getRight() const; + glm::vec3 getUp() const; + float getDistanceToTarget() const; + +private: + void updateView(); + void updateProjection(); + + ProjectionType m_type{ProjectionType::Perspective}; + glm::mat4 m_view{1.0f}; + glm::mat4 m_projection{1.0f}; + + float m_fov{45.0f}, m_aspect{16.0f / 9.0f}, m_near{0.1f}, m_far{100.0f}; + float m_left, m_right, m_bottom, m_top; + + glm::vec3 m_position{0.0f, 0.0f, 5.0f}; + glm::quat m_orientation{1.0f, 0.0f, 0.0f, 0.0f}; + glm::vec3 m_up{0.0f, 1.0f, 0.0f}; + + mutable bool m_viewDirty{true}; + mutable bool m_projDirty{true}; +}; + +} // reactor + + diff --git a/src/core/EventManager.cpp b/src/core/EventManager.cpp index 7bbb80d..0330a2e 100644 --- a/src/core/EventManager.cpp +++ b/src/core/EventManager.cpp @@ -1,28 +1,28 @@ -#include "EventManager.hpp" - -namespace reactor { - -void EventManager::subscribe(EventType type, IEventListener *listener) { - m_listeners[type].push_back(listener); -} - -void EventManager::unsubscribe(EventType type, IEventListener *listener) { - auto& listeners = m_listeners[type]; - std::erase(listeners, listener); -} - -void EventManager::post(const Event &event) const { - if (!m_listeners.contains(event.type)) { - // no listeners, so nothing to do. - return; - } - - for (auto listener : m_listeners.at(event.type)) { - listener->onEvent(event); - } - -} - - - +#include "EventManager.hpp" + +namespace reactor { + +void EventManager::subscribe(EventType type, IEventListener *listener) { + m_listeners[type].push_back(listener); +} + +void EventManager::unsubscribe(EventType type, IEventListener *listener) { + auto& listeners = m_listeners[type]; + std::erase(listeners, listener); +} + +void EventManager::post(const Event &event) const { + if (!m_listeners.contains(event.type)) { + // no listeners, so nothing to do. + return; + } + + for (auto listener : m_listeners.at(event.type)) { + listener->onEvent(event); + } + +} + + + } \ No newline at end of file diff --git a/src/core/EventManager.hpp b/src/core/EventManager.hpp index e4a48e5..0ba724b 100644 --- a/src/core/EventManager.hpp +++ b/src/core/EventManager.hpp @@ -1,47 +1,47 @@ -#pragma once -#include -#include - -namespace reactor { - -enum class EventType { - MouseMoved, - MouseButtonPressed, - MouseButtonReleased, - KeyPressed, - KeyReleased, -}; - -struct Event { - EventType type; - union { - struct { - double x, y; - } mouseMove; - struct { - int button; - double x, y; - } mouseButton; - struct { - int key; - } keyboard; - }; -}; - -class IEventListener { -public: - virtual ~IEventListener() = default; - virtual void onEvent(const Event& event) = 0; -}; - -class EventManager { -public: - void subscribe(EventType type, IEventListener* listener); - void unsubscribe(EventType type, IEventListener* listener); - void post(const Event& event) const; - -private: - std::unordered_map> m_listeners; -}; - +#pragma once +#include +#include + +namespace reactor { + +enum class EventType { + MouseMoved, + MouseButtonPressed, + MouseButtonReleased, + KeyPressed, + KeyReleased, +}; + +struct Event { + EventType type; + union { + struct { + double x, y; + } mouseMove; + struct { + int button; + double x, y; + } mouseButton; + struct { + int key; + } keyboard; + }; +}; + +class IEventListener { +public: + virtual ~IEventListener() = default; + virtual void onEvent(const Event& event) = 0; +}; + +class EventManager { +public: + void subscribe(EventType type, IEventListener* listener); + void unsubscribe(EventType type, IEventListener* listener); + void post(const Event& event) const; + +private: + std::unordered_map> m_listeners; +}; + } \ No newline at end of file diff --git a/src/core/ModelIO.cpp b/src/core/ModelIO.cpp index ea9ab52..7ad296a 100644 --- a/src/core/ModelIO.cpp +++ b/src/core/ModelIO.cpp @@ -1,163 +1,163 @@ -// -// Created by rfdic on 7/22/2025. -// -#include "ModelIO.hpp" - -#include - -#include -#include -#include - -namespace reactor -{ -bool processAndExportScene(const aiScene*, const std::string&); - - -// Processes the assimp scene and writes it to our custom binary format. -bool processAndExportScene(const aiScene* scene, const std::string& outputPath) -{ - // Open the output file in binary mode. - std::ofstream outFile(outputPath, std::ios::binary); - if (!outFile.is_open()) { - spdlog::error("Failed to open output file for writing: {}", outputPath); - return false; - } - - // --- Write File Header --- - const char magic[8] = "R_MESH"; - uint32_t version = 1; - uint32_t meshCount = scene->mNumMeshes; - - outFile.write(magic, sizeof(magic)); - outFile.write(reinterpret_cast(&version), sizeof(version)); - outFile.write(reinterpret_cast(&meshCount), sizeof(meshCount)); - - spdlog::info("Exporting {} meshes to {}", meshCount, outputPath); - - // --- Write Each Mesh's Data --- - for (unsigned int i = 0; i < scene->mNumMeshes; ++i) { - aiMesh* pMesh = scene->mMeshes[i]; - - std::vector vertices; - std::vector indices; - - // Extract vertex data - vertices.reserve(pMesh->mNumVertices); - for (unsigned int v = 0; v < pMesh->mNumVertices; ++v) { - reactor::Vertex vertex{}; - vertex.pos = {pMesh->mVertices[v].x, pMesh->mVertices[v].y, pMesh->mVertices[v].z}; - - if (pMesh->HasNormals()) { - vertex.normal = {pMesh->mNormals[v].x, pMesh->mNormals[v].y, pMesh->mNormals[v].z}; - } - - // Set a default color, or extract from mesh if available - vertex.color = {1.0f, 1.0f, 1.0f}; - if (pMesh->HasVertexColors(0)) { - vertex.color = {pMesh->mColors[0][v].r, pMesh->mColors[0][v].g, pMesh->mColors[0][v].b}; - } - - if (pMesh->HasTextureCoords(0)) { - vertex.texCoord = {pMesh->mTextureCoords[0][v].x, pMesh->mTextureCoords[0][v].y}; - } - vertices.push_back(vertex); - } - - // Extract index data - indices.reserve(pMesh->mNumFaces * 3); - for (unsigned int f = 0; f < pMesh->mNumFaces; ++f) { - aiFace face = pMesh->mFaces[f]; - // We assume the mesh is triangulated (aiProcess_Triangulate) - for (unsigned int j = 0; j < face.mNumIndices; ++j) { - indices.push_back(face.mIndices[j]); - } - } - - // --- Write Mesh Chunk to File --- - uint64_t vertexCount = vertices.size(); - uint64_t indexCount = indices.size(); - - outFile.write(reinterpret_cast(&vertexCount), sizeof(vertexCount)); - outFile.write(reinterpret_cast(&indexCount), sizeof(indexCount)); - outFile.write(reinterpret_cast(vertices.data()), vertexCount * sizeof(reactor::Vertex)); - outFile.write(reinterpret_cast(indices.data()), indexCount * sizeof(uint32_t)); - - spdlog::info(" - Mesh {}: {} vertices, {} indices", i, vertexCount, indexCount); - } - - outFile.close(); - spdlog::info("Successfully exported model to {}", outputPath); - return true; -} - -// Example of how you would load the data back in your engine. -std::vector loadModelFromBinary(const std::string& path) { - std::vector allMeshes; - - std::ifstream inFile(path, std::ios::binary); - if (!inFile.is_open()) { - spdlog::error("Failed to open model file for reading: {}", path); - return allMeshes; - } - - // --- Read and Validate Header --- - char magic[8]; - uint32_t version; - uint32_t meshCount; - - inFile.read(magic, sizeof(magic)); - inFile.read(reinterpret_cast(&version), sizeof(version)); - inFile.read(reinterpret_cast(&meshCount), sizeof(meshCount)); - - if (std::string(magic) != "R_MESH" || version != 1) { - spdlog::error("Invalid model file or version mismatch: {}", path); - return allMeshes; - } - - allMeshes.resize(meshCount); - spdlog::info("Loading {} meshes from {}", meshCount, path); - - // --- Read Each Mesh's Data --- - for (uint32_t i = 0; i < meshCount; ++i) { - uint64_t vertexCount; - uint64_t indexCount; - - inFile.read(reinterpret_cast(&vertexCount), sizeof(vertexCount)); - inFile.read(reinterpret_cast(&indexCount), sizeof(indexCount)); - - allMeshes[i].vertices.resize(vertexCount); - allMeshes[i].indices.resize(indexCount); - - inFile.read(reinterpret_cast(allMeshes[i].vertices.data()), vertexCount * sizeof(reactor::Vertex)); - inFile.read(reinterpret_cast(allMeshes[i].indices.data()), indexCount * sizeof(uint32_t)); - - spdlog::info(" - Mesh {}: {} vertices, {} indices", i, vertexCount, indexCount); - } - - inFile.close(); - return allMeshes; -} - - -bool importAndExport(const std::string& importPath, const std::string& exportPath) { - Assimp::Importer importer; - - // Use aiProcess_FlipUVs since Vulkan's coordinate system is different from OpenGL's - const aiScene* scene = importer.ReadFile(importPath, - aiProcess_CalcTangentSpace | - aiProcess_Triangulate | - aiProcess_JoinIdenticalVertices | - aiProcess_SortByPType | - aiProcess_FlipUVs); - - if (nullptr == scene) { - spdlog::error("Assimp import error: {}", importer.GetErrorString()); - return false; - } - - processAndExportScene(scene, exportPath); - - return true; -} -} +// +// Created by rfdic on 7/22/2025. +// +#include "ModelIO.hpp" + +#include + +#include +#include +#include + +namespace reactor +{ +bool processAndExportScene(const aiScene*, const std::string&); + + +// Processes the assimp scene and writes it to our custom binary format. +bool processAndExportScene(const aiScene* scene, const std::string& outputPath) +{ + // Open the output file in binary mode. + std::ofstream outFile(outputPath, std::ios::binary); + if (!outFile.is_open()) { + spdlog::error("Failed to open output file for writing: {}", outputPath); + return false; + } + + // --- Write File Header --- + const char magic[8] = "R_MESH"; + uint32_t version = 1; + uint32_t meshCount = scene->mNumMeshes; + + outFile.write(magic, sizeof(magic)); + outFile.write(reinterpret_cast(&version), sizeof(version)); + outFile.write(reinterpret_cast(&meshCount), sizeof(meshCount)); + + spdlog::info("Exporting {} meshes to {}", meshCount, outputPath); + + // --- Write Each Mesh's Data --- + for (unsigned int i = 0; i < scene->mNumMeshes; ++i) { + aiMesh* pMesh = scene->mMeshes[i]; + + std::vector vertices; + std::vector indices; + + // Extract vertex data + vertices.reserve(pMesh->mNumVertices); + for (unsigned int v = 0; v < pMesh->mNumVertices; ++v) { + reactor::Vertex vertex{}; + vertex.pos = {pMesh->mVertices[v].x, pMesh->mVertices[v].y, pMesh->mVertices[v].z}; + + if (pMesh->HasNormals()) { + vertex.normal = {pMesh->mNormals[v].x, pMesh->mNormals[v].y, pMesh->mNormals[v].z}; + } + + // Set a default color, or extract from mesh if available + vertex.color = {1.0f, 1.0f, 1.0f}; + if (pMesh->HasVertexColors(0)) { + vertex.color = {pMesh->mColors[0][v].r, pMesh->mColors[0][v].g, pMesh->mColors[0][v].b}; + } + + if (pMesh->HasTextureCoords(0)) { + vertex.texCoord = {pMesh->mTextureCoords[0][v].x, pMesh->mTextureCoords[0][v].y}; + } + vertices.push_back(vertex); + } + + // Extract index data + indices.reserve(pMesh->mNumFaces * 3); + for (unsigned int f = 0; f < pMesh->mNumFaces; ++f) { + aiFace face = pMesh->mFaces[f]; + // We assume the mesh is triangulated (aiProcess_Triangulate) + for (unsigned int j = 0; j < face.mNumIndices; ++j) { + indices.push_back(face.mIndices[j]); + } + } + + // --- Write Mesh Chunk to File --- + uint64_t vertexCount = vertices.size(); + uint64_t indexCount = indices.size(); + + outFile.write(reinterpret_cast(&vertexCount), sizeof(vertexCount)); + outFile.write(reinterpret_cast(&indexCount), sizeof(indexCount)); + outFile.write(reinterpret_cast(vertices.data()), vertexCount * sizeof(reactor::Vertex)); + outFile.write(reinterpret_cast(indices.data()), indexCount * sizeof(uint32_t)); + + spdlog::info(" - Mesh {}: {} vertices, {} indices", i, vertexCount, indexCount); + } + + outFile.close(); + spdlog::info("Successfully exported model to {}", outputPath); + return true; +} + +// Example of how you would load the data back in your engine. +std::vector loadModelFromBinary(const std::string& path) { + std::vector allMeshes; + + std::ifstream inFile(path, std::ios::binary); + if (!inFile.is_open()) { + spdlog::error("Failed to open model file for reading: {}", path); + return allMeshes; + } + + // --- Read and Validate Header --- + char magic[8]; + uint32_t version; + uint32_t meshCount; + + inFile.read(magic, sizeof(magic)); + inFile.read(reinterpret_cast(&version), sizeof(version)); + inFile.read(reinterpret_cast(&meshCount), sizeof(meshCount)); + + if (std::string(magic) != "R_MESH" || version != 1) { + spdlog::error("Invalid model file or version mismatch: {}", path); + return allMeshes; + } + + allMeshes.resize(meshCount); + spdlog::info("Loading {} meshes from {}", meshCount, path); + + // --- Read Each Mesh's Data --- + for (uint32_t i = 0; i < meshCount; ++i) { + uint64_t vertexCount; + uint64_t indexCount; + + inFile.read(reinterpret_cast(&vertexCount), sizeof(vertexCount)); + inFile.read(reinterpret_cast(&indexCount), sizeof(indexCount)); + + allMeshes[i].vertices.resize(vertexCount); + allMeshes[i].indices.resize(indexCount); + + inFile.read(reinterpret_cast(allMeshes[i].vertices.data()), vertexCount * sizeof(reactor::Vertex)); + inFile.read(reinterpret_cast(allMeshes[i].indices.data()), indexCount * sizeof(uint32_t)); + + spdlog::info(" - Mesh {}: {} vertices, {} indices", i, vertexCount, indexCount); + } + + inFile.close(); + return allMeshes; +} + + +bool importAndExport(const std::string& importPath, const std::string& exportPath) { + Assimp::Importer importer; + + // Use aiProcess_FlipUVs since Vulkan's coordinate system is different from OpenGL's + const aiScene* scene = importer.ReadFile(importPath, + aiProcess_CalcTangentSpace | + aiProcess_Triangulate | + aiProcess_JoinIdenticalVertices | + aiProcess_SortByPType | + aiProcess_FlipUVs); + + if (nullptr == scene) { + spdlog::error("Assimp import error: {}", importer.GetErrorString()); + return false; + } + + processAndExportScene(scene, exportPath); + + return true; +} +} diff --git a/src/core/ModelIO.hpp b/src/core/ModelIO.hpp index 8452cc1..3744363 100644 --- a/src/core/ModelIO.hpp +++ b/src/core/ModelIO.hpp @@ -1,18 +1,18 @@ -#pragma once - -#include "../vulkan/Vertex.hpp" - -namespace reactor -{ - -// A structure to hold mesh data, which is what we'll load back in the engine. -struct MeshData -{ - std::vector vertices; - std::vector indices; -}; - -std::vector loadModelFromBinary(const std::string&); -bool importAndExport(const std::string&, const std::string&); - +#pragma once + +#include "../vulkan/Vertex.hpp" + +namespace reactor +{ + +// A structure to hold mesh data, which is what we'll load back in the engine. +struct MeshData +{ + std::vector vertices; + std::vector indices; +}; + +std::vector loadModelFromBinary(const std::string&); +bool importAndExport(const std::string&, const std::string&); + } // namespace reactor \ No newline at end of file diff --git a/src/core/OrbitController.cpp b/src/core/OrbitController.cpp index c20cc06..8dc5ae2 100644 --- a/src/core/OrbitController.cpp +++ b/src/core/OrbitController.cpp @@ -1,99 +1,99 @@ -// -// Created by rfdic on 7/7/2025. -// - -#include "OrbitController.hpp" - -#include - -namespace reactor { - - OrbitController::OrbitController(Camera &camera) : m_camera(camera) {} - -void OrbitController::setSimCityView(float initialDistance, float initialElevationDegrees) { - m_distance = initialDistance; - m_elevation = glm::radians(initialElevationDegrees); - updateCamera(); - } - - -void OrbitController::onEvent(const Event &event) { - switch (event.type) { - case EventType::MouseMoved: - if (m_rotating) { - double dx = event.mouseMove.x - m_lastX; - double dy = event.mouseMove.y - m_lastY; - m_azimuth += static_cast(dx) * ROTATE_SPEED; - m_elevation += static_cast(dy) * ROTATE_SPEED; - m_elevation = glm::clamp(m_elevation, MIN_ELEVATION, MAX_ELEVATION); - updateCamera(); - m_lastX = event.mouseMove.x; - m_lastY = event.mouseMove.y; - } - if (m_panning) { - double dx = event.mouseMove.x - m_lastPanX; - double dy = event.mouseMove.y - m_lastPanY; - updatePan(static_cast(dx), static_cast(dy)); - m_lastPanX = event.mouseMove.x; - m_lastPanY = event.mouseMove.y; - } - break; - case EventType::MouseButtonPressed: - if (event.mouseButton.button == 0) { - m_rotating = true; - m_lastX = event.mouseButton.x; - m_lastY = event.mouseButton.y; - } else if (event.mouseButton.button == 1) { - m_panning = true; - m_lastPanX = event.mouseButton.x; - m_lastPanY = event.mouseButton.y; - } - break; - case EventType::MouseButtonReleased: - if (event.mouseButton.button == 0) { - m_rotating = false; - } else if (event.mouseButton.button == 1) { - m_panning = false; - } - break; - case EventType::KeyPressed: - break; - } - } - -void OrbitController::updateCamera() { - const float x = m_distance * cosf(m_elevation) * sinf(m_azimuth); - const float y = m_distance * sinf(m_elevation); - const float z = m_distance * cosf(m_elevation) * cosf(m_azimuth); - - const glm::vec3 position = m_target + glm::vec3(x, y, z); - const glm::vec3 up(0.0f, 1.0f, 0.0f); - - m_camera.lookAt(position, m_target, up); - } - -void OrbitController::updatePan(float dx, float dy) { - const float fovScale = std::tan(glm::radians(m_camera.getFOV() / 2.0f)); - const float speed = (m_distance * fovScale * PAN_SPEED); - - // get camera facing direction - const glm::vec3 position = m_camera.getPosition(); - const glm::vec3 facingDir = glm::normalize(position - m_target); - - // horizontal right - const glm::vec3 up(0.0f, 1.0f, 0.0f); - const glm::vec3 right = glm::normalize(glm::cross(facingDir, up)); - - // horizontal forward - const glm::vec3 groundForward = glm::normalize(glm::vec3(facingDir.x, 0.0f, facingDir.z)); - - const glm::vec3 delta = (right * -dx * speed) + (groundForward * -dy * speed); - - m_camera.move(delta); - m_target += delta; - updateCamera(); - - } - - +// +// Created by rfdic on 7/7/2025. +// + +#include "OrbitController.hpp" + +#include + +namespace reactor { + + OrbitController::OrbitController(Camera &camera) : m_camera(camera) {} + +void OrbitController::setSimCityView(float initialDistance, float initialElevationDegrees) { + m_distance = initialDistance; + m_elevation = glm::radians(initialElevationDegrees); + updateCamera(); + } + + +void OrbitController::onEvent(const Event &event) { + switch (event.type) { + case EventType::MouseMoved: + if (m_rotating) { + double dx = event.mouseMove.x - m_lastX; + double dy = event.mouseMove.y - m_lastY; + m_azimuth += static_cast(dx) * ROTATE_SPEED; + m_elevation += static_cast(dy) * ROTATE_SPEED; + m_elevation = glm::clamp(m_elevation, MIN_ELEVATION, MAX_ELEVATION); + updateCamera(); + m_lastX = event.mouseMove.x; + m_lastY = event.mouseMove.y; + } + if (m_panning) { + double dx = event.mouseMove.x - m_lastPanX; + double dy = event.mouseMove.y - m_lastPanY; + updatePan(static_cast(dx), static_cast(dy)); + m_lastPanX = event.mouseMove.x; + m_lastPanY = event.mouseMove.y; + } + break; + case EventType::MouseButtonPressed: + if (event.mouseButton.button == 0) { + m_rotating = true; + m_lastX = event.mouseButton.x; + m_lastY = event.mouseButton.y; + } else if (event.mouseButton.button == 1) { + m_panning = true; + m_lastPanX = event.mouseButton.x; + m_lastPanY = event.mouseButton.y; + } + break; + case EventType::MouseButtonReleased: + if (event.mouseButton.button == 0) { + m_rotating = false; + } else if (event.mouseButton.button == 1) { + m_panning = false; + } + break; + case EventType::KeyPressed: + break; + } + } + +void OrbitController::updateCamera() { + const float x = m_distance * cosf(m_elevation) * sinf(m_azimuth); + const float y = m_distance * sinf(m_elevation); + const float z = m_distance * cosf(m_elevation) * cosf(m_azimuth); + + const glm::vec3 position = m_target + glm::vec3(x, y, z); + const glm::vec3 up(0.0f, 1.0f, 0.0f); + + m_camera.lookAt(position, m_target, up); + } + +void OrbitController::updatePan(float dx, float dy) { + const float fovScale = std::tan(glm::radians(m_camera.getFOV() / 2.0f)); + const float speed = (m_distance * fovScale * PAN_SPEED); + + // get camera facing direction + const glm::vec3 position = m_camera.getPosition(); + const glm::vec3 facingDir = glm::normalize(position - m_target); + + // horizontal right + const glm::vec3 up(0.0f, 1.0f, 0.0f); + const glm::vec3 right = glm::normalize(glm::cross(facingDir, up)); + + // horizontal forward + const glm::vec3 groundForward = glm::normalize(glm::vec3(facingDir.x, 0.0f, facingDir.z)); + + const glm::vec3 delta = (right * -dx * speed) + (groundForward * -dy * speed); + + m_camera.move(delta); + m_target += delta; + updateCamera(); + + } + + } // reactor \ No newline at end of file diff --git a/src/core/OrbitController.hpp b/src/core/OrbitController.hpp index cba1967..e258614 100644 --- a/src/core/OrbitController.hpp +++ b/src/core/OrbitController.hpp @@ -1,43 +1,43 @@ -#pragma once - -#include "EventManager.hpp" -#include "Camera.hpp" - -namespace reactor { - -class OrbitController final : public IEventListener { -public: - explicit OrbitController(Camera& camera); - - void onEvent(const Event& event) override; - - void setSimCityView(float initialDistance = 20.f, float initialElevationDegrees = 45.0f); - -private: - Camera& m_camera; - glm::vec3 m_target{0.0f}; - float m_distance = 5.0f; - float m_azimuth = 0.0f; - float m_elevation = 0.0f; - - bool m_rotating = false; - bool m_panning = false; - double m_lastX = 0.0; - double m_lastY = 0.0; - double m_lastPanX = 0.0; - double m_lastPanY = 0.0; - - void updateCamera(); - void updatePan(float dx, float dy); - - static constexpr float ROTATE_SPEED = 0.01f; - static constexpr float PAN_SPEED = 0.0005f; - static constexpr float ZOOM_SPEED = 0.5f; - static constexpr float MIN_DISTANCE = 0.1f; - static constexpr float MAX_DISTANCE = 50.0f; - static constexpr float MIN_ELEVATION = glm::radians(20.0f); - static constexpr float MAX_ELEVATION = glm::radians(85.0f); -}; - -} // reactor - +#pragma once + +#include "EventManager.hpp" +#include "Camera.hpp" + +namespace reactor { + +class OrbitController final : public IEventListener { +public: + explicit OrbitController(Camera& camera); + + void onEvent(const Event& event) override; + + void setSimCityView(float initialDistance = 20.f, float initialElevationDegrees = 45.0f); + +private: + Camera& m_camera; + glm::vec3 m_target{0.0f}; + float m_distance = 5.0f; + float m_azimuth = 0.0f; + float m_elevation = 0.0f; + + bool m_rotating = false; + bool m_panning = false; + double m_lastX = 0.0; + double m_lastY = 0.0; + double m_lastPanX = 0.0; + double m_lastPanY = 0.0; + + void updateCamera(); + void updatePan(float dx, float dy); + + static constexpr float ROTATE_SPEED = 0.01f; + static constexpr float PAN_SPEED = 0.0005f; + static constexpr float ZOOM_SPEED = 0.5f; + static constexpr float MIN_DISTANCE = 0.1f; + static constexpr float MAX_DISTANCE = 50.0f; + static constexpr float MIN_ELEVATION = glm::radians(20.0f); + static constexpr float MAX_ELEVATION = glm::radians(85.0f); +}; + +} // reactor + diff --git a/src/core/Uniforms.hpp b/src/core/Uniforms.hpp index d11d467..781beac 100644 --- a/src/core/Uniforms.hpp +++ b/src/core/Uniforms.hpp @@ -1,26 +1,26 @@ -#pragma once -#include - -struct SceneUBO { - glm::mat4 view; - glm::mat4 projection; - glm::mat4 lightSpaceMatrix; -}; - -struct DirectionalLightUBO -{ - glm::vec4 lightDirection = glm::vec4(-0.5f, 1.0f, -0.5f, 0.0f); - glm::vec4 lightColor = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); - float lightIntensity = 1.0f; - // add 12-bytes of padding - float pad[3]; -}; - -struct CompositeUBO { - float uExposure = 1.0f; - float uContrast = 1.0f; - float uSaturation = 1.0f; - float uVignetteIntensity = 0.5f; - float uVignetteFalloff = 0.5f; - float uFogDensity = 0.001f; +#pragma once +#include + +struct SceneUBO { + glm::mat4 view; + glm::mat4 projection; + glm::mat4 lightSpaceMatrix; +}; + +struct DirectionalLightUBO +{ + glm::vec4 lightDirection = glm::vec4(-0.5f, 1.0f, -0.5f, 0.0f); + glm::vec4 lightColor = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); + float lightIntensity = 1.0f; + // add 12-bytes of padding + float pad[3]; +}; + +struct CompositeUBO { + float uExposure = 1.0f; + float uContrast = 1.0f; + float uSaturation = 1.0f; + float uVignetteIntensity = 0.5f; + float uVignetteFalloff = 0.5f; + float uFogDensity = 0.001f; }; \ No newline at end of file diff --git a/src/core/Window.cpp b/src/core/Window.cpp index 380e653..65c4ba4 100644 --- a/src/core/Window.cpp +++ b/src/core/Window.cpp @@ -1,116 +1,116 @@ -#include "Window.hpp" -#include - -namespace reactor { - // The constructor initializes GLFW and creates the window. - Window::Window(int width, int height, std::string title, EventManager& eventManager) : -m_width(width), m_height(height), m_title(title), m_eventManager(eventManager) { - // Initialize the GLFW library. - if (!glfwInit()) { - throw std::runtime_error("Failed to initialize GLFW!"); - } - - // Tell GLFW not to create an OpenGL context, as we are using Vulkan. - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - // Create the window. - m_window = glfwCreateWindow(width, height, m_title.c_str(), nullptr, nullptr); - if (!m_window) { - glfwTerminate(); - throw std::runtime_error("Failed to create GLFW window!"); - } - - glfwSetWindowUserPointer(m_window, this); - glfwSetFramebufferSizeCallback(m_window, framebufferResizeCallback); - - glfwSetKeyCallback(m_window, keyCallback); - // glfwSetCursorPosCallback(m_window, cursorPosCallback); - // glfwSetMouseButtonCallback(m_window, mouseButtonCallback); - } - - // The destructor cleans up GLFW resources. - Window::~Window() { - if (m_window) { - glfwDestroyWindow(m_window); - } - glfwTerminate(); - } - - // Returns the window's framebuffer dimensions as a vk::Extent2D. - vk::Extent2D Window::getFramebufferSize() const { - int width, height; - glfwGetFramebufferSize(m_window, &width, &height); - return vk::Extent2D{ - static_cast(width), - static_cast(height) - }; - } - - // This function handles the creation of the Vulkan surface. - // It's part of the Window class because the surface is intrinsically linked to the window. - vk::SurfaceKHR Window::createVulkanSurface(vk::Instance instance) { - VkSurfaceKHR surface_c; - // glfwCreateWindowSurface abstracts the platform-specific details of surface creation. - if (glfwCreateWindowSurface(instance, m_window, nullptr, &surface_c) != VK_SUCCESS) { - throw std::runtime_error("Failed to create window surface!"); - } - // The C-style handle is implicitly convertible to the C++ Vulkan-Hpp handle. - return surface_c; - } - - - // This is the static callback that GLFW invokes when the window is resized. - void Window::framebufferResizeCallback(GLFWwindow *window, int width, int height) { - // Retrieve the pointer to our Window class instance. - auto *windowInstance = static_cast(glfwGetWindowUserPointer(window)); - if (windowInstance) { - // Set the flag indicating that a resize has occurred. - windowInstance->m_framebufferResized = true; - } - } - void Window::keyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) { - auto* windowInstance = static_cast(glfwGetWindowUserPointer(window)); - if (!windowInstance) return; - - Event event{}; - if (action == GLFW_PRESS) { - event.type = EventType::KeyPressed; - event.keyboard.key = key; - windowInstance->m_eventManager.post(event); - } else if (action == GLFW_RELEASE) { - event.type = EventType::KeyReleased; - event.keyboard.key = key; - windowInstance->m_eventManager.post(event); - } - - } - - void Window::mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) { - auto* windowInstance = static_cast(glfwGetWindowUserPointer(window)); - if (!windowInstance) return; - - Event event{}; - if (action == GLFW_PRESS) { - event.type = EventType::MouseButtonPressed; - event.mouseButton.button = button; - windowInstance->m_eventManager.post(event); - } else if (action == GLFW_RELEASE) { - event.type = EventType::MouseButtonReleased; - event.mouseButton.button = button; - } - - windowInstance->m_eventManager.post(event); - } - - void Window::cursorPosCallback(GLFWwindow *window, double xpos, double ypos) { - auto* windowInstance = static_cast(glfwGetWindowUserPointer(window)); - if (!windowInstance) return; - - Event event{}; - event.type = EventType::MouseMoved; - event.mouseMove.x = xpos; - event.mouseMove.y = ypos; - windowInstance->m_eventManager.post(event); - - } - } // namespace reactor +#include "Window.hpp" +#include + +namespace reactor { + // The constructor initializes GLFW and creates the window. + Window::Window(int width, int height, std::string title, EventManager& eventManager) : +m_width(width), m_height(height), m_title(title), m_eventManager(eventManager) { + // Initialize the GLFW library. + if (!glfwInit()) { + throw std::runtime_error("Failed to initialize GLFW!"); + } + + // Tell GLFW not to create an OpenGL context, as we are using Vulkan. + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + // Create the window. + m_window = glfwCreateWindow(width, height, m_title.c_str(), nullptr, nullptr); + if (!m_window) { + glfwTerminate(); + throw std::runtime_error("Failed to create GLFW window!"); + } + + glfwSetWindowUserPointer(m_window, this); + glfwSetFramebufferSizeCallback(m_window, framebufferResizeCallback); + + glfwSetKeyCallback(m_window, keyCallback); + // glfwSetCursorPosCallback(m_window, cursorPosCallback); + // glfwSetMouseButtonCallback(m_window, mouseButtonCallback); + } + + // The destructor cleans up GLFW resources. + Window::~Window() { + if (m_window) { + glfwDestroyWindow(m_window); + } + glfwTerminate(); + } + + // Returns the window's framebuffer dimensions as a vk::Extent2D. + vk::Extent2D Window::getFramebufferSize() const { + int width, height; + glfwGetFramebufferSize(m_window, &width, &height); + return vk::Extent2D{ + static_cast(width), + static_cast(height) + }; + } + + // This function handles the creation of the Vulkan surface. + // It's part of the Window class because the surface is intrinsically linked to the window. + vk::SurfaceKHR Window::createVulkanSurface(vk::Instance instance) { + VkSurfaceKHR surface_c; + // glfwCreateWindowSurface abstracts the platform-specific details of surface creation. + if (glfwCreateWindowSurface(instance, m_window, nullptr, &surface_c) != VK_SUCCESS) { + throw std::runtime_error("Failed to create window surface!"); + } + // The C-style handle is implicitly convertible to the C++ Vulkan-Hpp handle. + return surface_c; + } + + + // This is the static callback that GLFW invokes when the window is resized. + void Window::framebufferResizeCallback(GLFWwindow *window, int width, int height) { + // Retrieve the pointer to our Window class instance. + auto *windowInstance = static_cast(glfwGetWindowUserPointer(window)); + if (windowInstance) { + // Set the flag indicating that a resize has occurred. + windowInstance->m_framebufferResized = true; + } + } + void Window::keyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) { + auto* windowInstance = static_cast(glfwGetWindowUserPointer(window)); + if (!windowInstance) return; + + Event event{}; + if (action == GLFW_PRESS) { + event.type = EventType::KeyPressed; + event.keyboard.key = key; + windowInstance->m_eventManager.post(event); + } else if (action == GLFW_RELEASE) { + event.type = EventType::KeyReleased; + event.keyboard.key = key; + windowInstance->m_eventManager.post(event); + } + + } + + void Window::mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) { + auto* windowInstance = static_cast(glfwGetWindowUserPointer(window)); + if (!windowInstance) return; + + Event event{}; + if (action == GLFW_PRESS) { + event.type = EventType::MouseButtonPressed; + event.mouseButton.button = button; + windowInstance->m_eventManager.post(event); + } else if (action == GLFW_RELEASE) { + event.type = EventType::MouseButtonReleased; + event.mouseButton.button = button; + } + + windowInstance->m_eventManager.post(event); + } + + void Window::cursorPosCallback(GLFWwindow *window, double xpos, double ypos) { + auto* windowInstance = static_cast(glfwGetWindowUserPointer(window)); + if (!windowInstance) return; + + Event event{}; + event.type = EventType::MouseMoved; + event.mouseMove.x = xpos; + event.mouseMove.y = ypos; + windowInstance->m_eventManager.post(event); + + } + } // namespace reactor diff --git a/src/core/Window.hpp b/src/core/Window.hpp index 3a1300b..648cbb0 100644 --- a/src/core/Window.hpp +++ b/src/core/Window.hpp @@ -1,70 +1,70 @@ -// -// Created by rfdic on 6/9/2025. -// - -#ifndef WINDOW_HPP -#define WINDOW_HPP - -#include -#include -#include "EventManager.hpp" - -namespace reactor { - - - class Window { - public: - Window(int width, int height, std::string title, EventManager& eventManager); - ~Window(); - - // Prevent copying and moving to ensure a single owner for the window resource. - Window(const Window&) = delete; - Window& operator=(const Window&) = delete; - Window(Window&&) = delete; - Window& operator=(Window&&) = delete; - - // Checks if the user has requested the window to close (e.g., by clicking the 'X' button). - bool shouldClose() const { return glfwWindowShouldClose(m_window); } - - // Processes all pending events in the window's event queue. - static void pollEvents() { glfwPollEvents(); } - - // Puts the current thread to sleep until at least one event is available. - // Useful for preventing the app from spinning at 100% CPU when minimized. - static void waitEvents() { glfwWaitEvents(); } - - // Gets the underlying native window handle. - GLFWwindow* getNativeWindow() const { return m_window; } - - // Gets the current size of the framebuffer in pixels. - // This can be different from the window size on high-DPI displays. - vk::Extent2D getFramebufferSize() const; - - // Creates a Vulkan surface (the connection between Vulkan and the window system). - vk::SurfaceKHR createVulkanSurface(vk::Instance instance); - - EventManager& getEventManager() const { return m_eventManager; } - - // -- Resize Handling -- - bool wasResized() const { return m_framebufferResized; } - void resetResizedFlag() { m_framebufferResized = false; } - - - private: - // This is the static callback function that GLFW will call on resize events. - static void framebufferResizeCallback(GLFWwindow* window, int width, int height); - static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); - static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods); - static void cursorPosCallback(GLFWwindow* window, double xpos, double ypos); - - GLFWwindow* m_window = nullptr; - EventManager& m_eventManager; - int m_width; - int m_height; - std::string m_title; - bool m_framebufferResized = false; - }; - -} - -#endif //WINDOW_HPP +// +// Created by rfdic on 6/9/2025. +// + +#ifndef WINDOW_HPP +#define WINDOW_HPP + +#include +#include +#include "EventManager.hpp" + +namespace reactor { + + + class Window { + public: + Window(int width, int height, std::string title, EventManager& eventManager); + ~Window(); + + // Prevent copying and moving to ensure a single owner for the window resource. + Window(const Window&) = delete; + Window& operator=(const Window&) = delete; + Window(Window&&) = delete; + Window& operator=(Window&&) = delete; + + // Checks if the user has requested the window to close (e.g., by clicking the 'X' button). + bool shouldClose() const { return glfwWindowShouldClose(m_window); } + + // Processes all pending events in the window's event queue. + static void pollEvents() { glfwPollEvents(); } + + // Puts the current thread to sleep until at least one event is available. + // Useful for preventing the app from spinning at 100% CPU when minimized. + static void waitEvents() { glfwWaitEvents(); } + + // Gets the underlying native window handle. + GLFWwindow* getNativeWindow() const { return m_window; } + + // Gets the current size of the framebuffer in pixels. + // This can be different from the window size on high-DPI displays. + vk::Extent2D getFramebufferSize() const; + + // Creates a Vulkan surface (the connection between Vulkan and the window system). + vk::SurfaceKHR createVulkanSurface(vk::Instance instance); + + EventManager& getEventManager() const { return m_eventManager; } + + // -- Resize Handling -- + bool wasResized() const { return m_framebufferResized; } + void resetResizedFlag() { m_framebufferResized = false; } + + + private: + // This is the static callback function that GLFW will call on resize events. + static void framebufferResizeCallback(GLFWwindow* window, int width, int height); + static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); + static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods); + static void cursorPosCallback(GLFWwindow* window, double xpos, double ypos); + + GLFWwindow* m_window = nullptr; + EventManager& m_eventManager; + int m_width; + int m_height; + std::string m_title; + bool m_framebufferResized = false; + }; + +} + +#endif //WINDOW_HPP diff --git a/src/core/main.cpp b/src/core/main.cpp index 7d97e6d..35a7088 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -1,16 +1,16 @@ - -#include "../vulkan/VulkanContext.hpp" -#include "../vulkan/VulkanRenderer.hpp" -#include "Application.hpp" -#include "Window.hpp" - -int main() { - - try { - reactor::Application app; - app.run(); - } catch (const std::exception& e) { - return EXIT_FAILURE; - } - return EXIT_SUCCESS; -} + +#include "../vulkan/VulkanContext.hpp" +#include "../vulkan/VulkanRenderer.hpp" +#include "Application.hpp" +#include "Window.hpp" + +int main() { + + try { + reactor::Application app; + app.run(); + } catch (const std::exception& e) { + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/src/imgui/Imgui.cpp b/src/imgui/Imgui.cpp index e14518a..147ed7f 100644 --- a/src/imgui/Imgui.cpp +++ b/src/imgui/Imgui.cpp @@ -1,309 +1,309 @@ -// -// Created by rfdic on 6/16/2025. -// - -#include "Imgui.hpp" - -#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES -#include -#include -#include -#include - -namespace reactor -{ - -Imgui::Imgui(VulkanContext& vulkanContext, - Window& window, - EventManager& eventManager, - std::shared_ptr consoleSink) - : m_device(vulkanContext.device()), m_eventManager(eventManager), m_consoleSink(std::move(consoleSink)) -{ - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - ImGui::StyleColorsDark(); - - // --- Initialize descriptor pool for ImGui --- - VkDescriptorPoolSize pool_sizes[] = {{VK_DESCRIPTOR_TYPE_SAMPLER, 1000}, - {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000}, - {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000}, - {VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000}, - {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000}, - {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000}, - {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000}, - {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000}, - {VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000}}; - VkDescriptorPoolCreateInfo pool_info = {}; - pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; - pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes); - pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); - pool_info.pPoolSizes = pool_sizes; - - // create the pool - m_descriptorPool = vulkanContext.device().createDescriptorPool(pool_info); - - // GLFW backend init - ImGui_ImplGlfw_InitForVulkan(window.getNativeWindow(), true); - - auto colorAttachmentFormat = vk::Format::eB8G8R8A8Srgb; - - vk::PipelineRenderingCreateInfo renderingInfo{}; - renderingInfo.colorAttachmentCount = 1; - renderingInfo.pColorAttachmentFormats = &colorAttachmentFormat; - renderingInfo.viewMask = 0; - renderingInfo.depthAttachmentFormat = vk::Format::eUndefined; - renderingInfo.stencilAttachmentFormat = vk::Format::eUndefined; - - ImGui_ImplVulkan_InitInfo initInfo{}; - initInfo.Instance = vulkanContext.instance(); - initInfo.PhysicalDevice = vulkanContext.physicalDevice(); - initInfo.Device = vulkanContext.device(); - initInfo.QueueFamily = vulkanContext.queueFamilies().graphicsFamily.value(); - initInfo.Queue = vulkanContext.graphicsQueue(); - initInfo.PipelineCache = nullptr; - initInfo.DescriptorPool = m_descriptorPool; - initInfo.MinImageCount = 3; - initInfo.ImageCount = 3; - initInfo.UseDynamicRendering = true; - initInfo.PipelineRenderingCreateInfo = renderingInfo; - - ImGui_ImplVulkan_Init(&initInfo); -} - -Imgui::~Imgui() -{ - - ImGui_ImplGlfw_Shutdown(); - ImGui_ImplVulkan_Shutdown(); - m_device.destroyDescriptorPool(m_descriptorPool); -} - -void Imgui::createFrame() -{ - ImGui_ImplVulkan_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - ShowDockspace(); - ShowSceneView(); - ShowInspector(); - ShowConsole(); -} - -void Imgui::drawFrame(vk::CommandBuffer commandBuffer) -{ - ImGui::Render(); - ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commandBuffer); -} - -void Imgui::ShowDockspace() -{ - static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_PassthruCentralNode; - - ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; - const ImGuiViewport* viewport = ImGui::GetMainViewport(); - - ImGui::SetNextWindowPos(viewport->Pos); - ImGui::SetNextWindowSize(viewport->Size); - ImGui::SetNextWindowViewport(viewport->ID); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - window_flags |= - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; - window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; - - ImGui::Begin("DockSpace Window", nullptr, window_flags); - ImGui::PopStyleVar(2); - - // Create dockspace ID - ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); - ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); - - static bool first_time = true; - if (first_time) - { - SetupInitialDockLayout(dockspace_id); - first_time = false; - } - - ImGui::End(); -} - -void Imgui::ShowSceneView() -{ - ImGui::Begin("Scene View"); - - if (m_sceneImguiId) - { - - const ImVec2 size = ImGui::GetContentRegionAvail(); - - if (size.x > 0.0f && size.y > 0.0f) - { - const auto id = reinterpret_cast(static_cast(m_sceneImguiId)); - - ImGui::Image(id, size, ImVec2(0, 1), ImVec2(1, 0)); - - ImVec2 image_pos = ImGui::GetItemRectMin(); - ImGui::SetCursorScreenPos(image_pos); - ImGui::InvisibleButton("scene_viewport", size); - - if (ImGui::IsItemHovered()) - { - ImGuiIO& io = ImGui::GetIO(); - // Post MouseMoved if position changed - if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) - { - Event e{}; - e.type = EventType::MouseMoved; - e.mouseMove.x = static_cast(io.MousePos.x); - e.mouseMove.y = static_cast(io.MousePos.y); - m_eventManager.post(e); - } - - // Post button presses/releases for left (0), right (1), middle (2) - for (int btn = 0; btn < 3; ++btn) - { - if (ImGui::IsMouseClicked(btn)) - { - Event e{}; - e.type = EventType::MouseButtonPressed; - e.mouseButton.button = btn; - e.mouseButton.x = static_cast(io.MousePos.x); - e.mouseButton.y = static_cast(io.MousePos.y); - m_eventManager.post(e); - } - if (ImGui::IsMouseReleased(btn)) - { - Event e{}; - e.type = EventType::MouseButtonReleased; - e.mouseButton.button = btn; - e.mouseButton.x = static_cast(io.MousePos.x); - e.mouseButton.y = static_cast(io.MousePos.y); - m_eventManager.post(e); - } - } - } - } - } - else - { - ImGui::Text("No scene image"); - } - - ImGui::End(); -} - -void Imgui::ShowInspector() -{ - ImGui::Begin("Inspector"); - ImGui::SliderFloat("Exposure", &m_exposure, 0.0, 2.0); - ImGui::SliderFloat("Contrast", &m_contrast, 0.0, 2.0); - ImGui::SliderFloat("Saturation", &m_saturation, 0.0, 2.0); - - ImGui::SliderFloat("Fog Density", &m_fogDensity, 0.0, 0.5); - ImGui::End(); -} - -void Imgui::ShowConsole() -{ - ImGui::Begin("Console"); - - // Add a "Clear" button to empty the console - if (ImGui::Button("Clear")) - { - if (m_consoleSink) - { - m_consoleSink->clearMessages(); - } - } - - ImGui::SameLine(); - - ImGui::Separator(); - - ImGui::BeginChild("ScrollingRegion", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); - - if (m_consoleSink) - { - // Safely access and draw messages using the thread-safe accessor - m_consoleSink->accessMessages([](const std::deque& messages) { - for (const auto& msg : messages) - { - ImVec4 color; - switch (msg.level) - { - case LogLevel::Trace: - color = ImVec4(0.6f, 0.6f, 0.6f, 1.0f); - break; // Grey - case LogLevel::Info: - color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); - break; // White - case LogLevel::Warn: - color = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); - break; // Yellow - case LogLevel::Error: - color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); - break; // Red - case LogLevel::Critical: - color = ImVec4(1.0f, 0.2f, 0.2f, 1.0f); - break; // Bright Red - default: - color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); - break; - } - - ImGui::PushStyleColor(ImGuiCol_Text, color); - ImGui::TextUnformatted(msg.message.c_str()); - ImGui::PopStyleColor(); - - } - }); - - // Auto-scroll to the bottom if the user hasn't scrolled up - if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) - { - ImGui::SetScrollHereY(1.0f); - } - } - - ImGui::EndChild(); - ImGui::End(); -} - -void Imgui::SetupInitialDockLayout(ImGuiID dockspace_id) -{ - ImGui::DockBuilderRemoveNode(dockspace_id); // Clear existing layout - ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace); - ImGui::DockBuilderSetNodeSize(dockspace_id, ImGui::GetMainViewport()->Size); - - // Split main dockspace into left (main), right (inspector), and bottom (console) - ImGuiID dock_main = dockspace_id; - ImGuiID dock_right = ImGui::DockBuilderSplitNode(dock_main, ImGuiDir_Right, 0.25f, nullptr, &dock_main); - ImGuiID dock_bottom = ImGui::DockBuilderSplitNode(dock_main, ImGuiDir_Down, 0.25f, nullptr, &dock_main); - - // Assign windows to dock nodes - ImGui::DockBuilderDockWindow("Scene View", dock_main); - ImGui::DockBuilderDockWindow("Inspector", dock_right); - ImGui::DockBuilderDockWindow("Console", dock_bottom); - ImGui::DockBuilderDockWindow("Composite", dock_main); // or move to its own panel - - ImGui::DockBuilderFinish(dockspace_id); -} - -vk::DescriptorSet Imgui::createDescriptorSet(vk::ImageView imageView, vk::Sampler sampler) -{ - - // if (m_sceneImguiId) { - // ImGui_ImplVulkan_RemoveTexture(m_sceneImguiId); - // } - - return ImGui_ImplVulkan_AddTexture(sampler, imageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); -} - +// +// Created by rfdic on 6/16/2025. +// + +#include "Imgui.hpp" + +#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES +#include +#include +#include +#include + +namespace reactor +{ + +Imgui::Imgui(VulkanContext& vulkanContext, + Window& window, + EventManager& eventManager, + std::shared_ptr consoleSink) + : m_device(vulkanContext.device()), m_eventManager(eventManager), m_consoleSink(std::move(consoleSink)) +{ + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + ImGui::StyleColorsDark(); + + // --- Initialize descriptor pool for ImGui --- + VkDescriptorPoolSize pool_sizes[] = {{VK_DESCRIPTOR_TYPE_SAMPLER, 1000}, + {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000}, + {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000}, + {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000}, + {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000}, + {VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000}, + {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000}, + {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000}, + {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000}, + {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000}, + {VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000}}; + VkDescriptorPoolCreateInfo pool_info = {}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes); + pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); + pool_info.pPoolSizes = pool_sizes; + + // create the pool + m_descriptorPool = vulkanContext.device().createDescriptorPool(pool_info); + + // GLFW backend init + ImGui_ImplGlfw_InitForVulkan(window.getNativeWindow(), true); + + auto colorAttachmentFormat = vk::Format::eB8G8R8A8Srgb; + + vk::PipelineRenderingCreateInfo renderingInfo{}; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachmentFormats = &colorAttachmentFormat; + renderingInfo.viewMask = 0; + renderingInfo.depthAttachmentFormat = vk::Format::eUndefined; + renderingInfo.stencilAttachmentFormat = vk::Format::eUndefined; + + ImGui_ImplVulkan_InitInfo initInfo{}; + initInfo.Instance = vulkanContext.instance(); + initInfo.PhysicalDevice = vulkanContext.physicalDevice(); + initInfo.Device = vulkanContext.device(); + initInfo.QueueFamily = vulkanContext.queueFamilies().graphicsFamily.value(); + initInfo.Queue = vulkanContext.graphicsQueue(); + initInfo.PipelineCache = nullptr; + initInfo.DescriptorPool = m_descriptorPool; + initInfo.MinImageCount = 3; + initInfo.ImageCount = 3; + initInfo.UseDynamicRendering = true; + initInfo.PipelineRenderingCreateInfo = renderingInfo; + + ImGui_ImplVulkan_Init(&initInfo); +} + +Imgui::~Imgui() +{ + + ImGui_ImplGlfw_Shutdown(); + ImGui_ImplVulkan_Shutdown(); + m_device.destroyDescriptorPool(m_descriptorPool); +} + +void Imgui::createFrame() +{ + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + ShowDockspace(); + ShowSceneView(); + ShowInspector(); + ShowConsole(); +} + +void Imgui::drawFrame(vk::CommandBuffer commandBuffer) +{ + ImGui::Render(); + ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commandBuffer); +} + +void Imgui::ShowDockspace() +{ + static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_PassthruCentralNode; + + ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + + ImGui::SetNextWindowPos(viewport->Pos); + ImGui::SetNextWindowSize(viewport->Size); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + window_flags |= + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; + window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + + ImGui::Begin("DockSpace Window", nullptr, window_flags); + ImGui::PopStyleVar(2); + + // Create dockspace ID + ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); + ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); + + static bool first_time = true; + if (first_time) + { + SetupInitialDockLayout(dockspace_id); + first_time = false; + } + + ImGui::End(); +} + +void Imgui::ShowSceneView() +{ + ImGui::Begin("Scene View"); + + if (m_sceneImguiId) + { + + const ImVec2 size = ImGui::GetContentRegionAvail(); + + if (size.x > 0.0f && size.y > 0.0f) + { + const auto id = reinterpret_cast(static_cast(m_sceneImguiId)); + + ImGui::Image(id, size, ImVec2(0, 1), ImVec2(1, 0)); + + ImVec2 image_pos = ImGui::GetItemRectMin(); + ImGui::SetCursorScreenPos(image_pos); + ImGui::InvisibleButton("scene_viewport", size); + + if (ImGui::IsItemHovered()) + { + ImGuiIO& io = ImGui::GetIO(); + // Post MouseMoved if position changed + if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) + { + Event e{}; + e.type = EventType::MouseMoved; + e.mouseMove.x = static_cast(io.MousePos.x); + e.mouseMove.y = static_cast(io.MousePos.y); + m_eventManager.post(e); + } + + // Post button presses/releases for left (0), right (1), middle (2) + for (int btn = 0; btn < 3; ++btn) + { + if (ImGui::IsMouseClicked(btn)) + { + Event e{}; + e.type = EventType::MouseButtonPressed; + e.mouseButton.button = btn; + e.mouseButton.x = static_cast(io.MousePos.x); + e.mouseButton.y = static_cast(io.MousePos.y); + m_eventManager.post(e); + } + if (ImGui::IsMouseReleased(btn)) + { + Event e{}; + e.type = EventType::MouseButtonReleased; + e.mouseButton.button = btn; + e.mouseButton.x = static_cast(io.MousePos.x); + e.mouseButton.y = static_cast(io.MousePos.y); + m_eventManager.post(e); + } + } + } + } + } + else + { + ImGui::Text("No scene image"); + } + + ImGui::End(); +} + +void Imgui::ShowInspector() +{ + ImGui::Begin("Inspector"); + ImGui::SliderFloat("Exposure", &m_exposure, 0.0, 2.0); + ImGui::SliderFloat("Contrast", &m_contrast, 0.0, 2.0); + ImGui::SliderFloat("Saturation", &m_saturation, 0.0, 2.0); + + ImGui::SliderFloat("Fog Density", &m_fogDensity, 0.0, 0.5); + ImGui::End(); +} + +void Imgui::ShowConsole() +{ + ImGui::Begin("Console"); + + // Add a "Clear" button to empty the console + if (ImGui::Button("Clear")) + { + if (m_consoleSink) + { + m_consoleSink->clearMessages(); + } + } + + ImGui::SameLine(); + + ImGui::Separator(); + + ImGui::BeginChild("ScrollingRegion", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); + + if (m_consoleSink) + { + // Safely access and draw messages using the thread-safe accessor + m_consoleSink->accessMessages([](const std::deque& messages) { + for (const auto& msg : messages) + { + ImVec4 color; + switch (msg.level) + { + case LogLevel::Trace: + color = ImVec4(0.6f, 0.6f, 0.6f, 1.0f); + break; // Grey + case LogLevel::Info: + color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + break; // White + case LogLevel::Warn: + color = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); + break; // Yellow + case LogLevel::Error: + color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); + break; // Red + case LogLevel::Critical: + color = ImVec4(1.0f, 0.2f, 0.2f, 1.0f); + break; // Bright Red + default: + color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + break; + } + + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(msg.message.c_str()); + ImGui::PopStyleColor(); + + } + }); + + // Auto-scroll to the bottom if the user hasn't scrolled up + if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) + { + ImGui::SetScrollHereY(1.0f); + } + } + + ImGui::EndChild(); + ImGui::End(); +} + +void Imgui::SetupInitialDockLayout(ImGuiID dockspace_id) +{ + ImGui::DockBuilderRemoveNode(dockspace_id); // Clear existing layout + ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace); + ImGui::DockBuilderSetNodeSize(dockspace_id, ImGui::GetMainViewport()->Size); + + // Split main dockspace into left (main), right (inspector), and bottom (console) + ImGuiID dock_main = dockspace_id; + ImGuiID dock_right = ImGui::DockBuilderSplitNode(dock_main, ImGuiDir_Right, 0.25f, nullptr, &dock_main); + ImGuiID dock_bottom = ImGui::DockBuilderSplitNode(dock_main, ImGuiDir_Down, 0.25f, nullptr, &dock_main); + + // Assign windows to dock nodes + ImGui::DockBuilderDockWindow("Scene View", dock_main); + ImGui::DockBuilderDockWindow("Inspector", dock_right); + ImGui::DockBuilderDockWindow("Console", dock_bottom); + ImGui::DockBuilderDockWindow("Composite", dock_main); // or move to its own panel + + ImGui::DockBuilderFinish(dockspace_id); +} + +vk::DescriptorSet Imgui::createDescriptorSet(vk::ImageView imageView, vk::Sampler sampler) +{ + + // if (m_sceneImguiId) { + // ImGui_ImplVulkan_RemoveTexture(m_sceneImguiId); + // } + + return ImGui_ImplVulkan_AddTexture(sampler, imageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); +} + } // namespace reactor \ No newline at end of file diff --git a/src/imgui/Imgui.hpp b/src/imgui/Imgui.hpp index ec5c028..0fa5bc3 100644 --- a/src/imgui/Imgui.hpp +++ b/src/imgui/Imgui.hpp @@ -1,55 +1,55 @@ -// -// Created by rfdic on 6/16/2025. -// - -#ifndef IMGUI_HPP -#define IMGUI_HPP -#include "../core/Window.hpp" -#include "../vulkan/VulkanContext.hpp" -#include "../logging/ImGuiConsoleSink.hpp" - -#include - -namespace reactor { - -class Imgui { -public: - Imgui(VulkanContext& vulkanContext, Window& window, EventManager& eventManager, std::shared_ptr consoleSink); - ~Imgui(); - - void createFrame(); - void drawFrame(vk::CommandBuffer commandBuffer); - - [[nodiscard]] float getExposure() const { return m_exposure; } - [[nodiscard]] float getContrast() const { return m_contrast; } - [[nodiscard]] float getSaturation() const { return m_saturation; } - [[nodiscard]] float getFogDensity() const { return m_fogDensity; } - - static vk::DescriptorSet createDescriptorSet(vk::ImageView imageView, vk::Sampler sampler); - void setSceneDescriptorSet(const vk::DescriptorSet descriptorSet) { m_sceneImguiId = descriptorSet; }; - -private: - - std::shared_ptr m_consoleSink; - vk::Device m_device; - EventManager& m_eventManager; - vk::DescriptorPool m_descriptorPool; - - vk::DescriptorSet m_sceneImguiId; - - float m_exposure = 1.0f; - float m_contrast = 1.0f; - float m_saturation = 1.0f; - float m_fogDensity = 0.001f; - - void ShowDockspace(); - void ShowSceneView(); - void ShowInspector(); - void ShowConsole(); - void SetupInitialDockLayout(ImGuiID dockspace_id); - -}; - -} // reactor - -#endif //IMGUI_HPP +// +// Created by rfdic on 6/16/2025. +// + +#ifndef IMGUI_HPP +#define IMGUI_HPP +#include "../core/Window.hpp" +#include "../vulkan/VulkanContext.hpp" +#include "../logging/ImGuiConsoleSink.hpp" + +#include + +namespace reactor { + +class Imgui { +public: + Imgui(VulkanContext& vulkanContext, Window& window, EventManager& eventManager, std::shared_ptr consoleSink); + ~Imgui(); + + void createFrame(); + void drawFrame(vk::CommandBuffer commandBuffer); + + [[nodiscard]] float getExposure() const { return m_exposure; } + [[nodiscard]] float getContrast() const { return m_contrast; } + [[nodiscard]] float getSaturation() const { return m_saturation; } + [[nodiscard]] float getFogDensity() const { return m_fogDensity; } + + static vk::DescriptorSet createDescriptorSet(vk::ImageView imageView, vk::Sampler sampler); + void setSceneDescriptorSet(const vk::DescriptorSet descriptorSet) { m_sceneImguiId = descriptorSet; }; + +private: + + std::shared_ptr m_consoleSink; + vk::Device m_device; + EventManager& m_eventManager; + vk::DescriptorPool m_descriptorPool; + + vk::DescriptorSet m_sceneImguiId; + + float m_exposure = 1.0f; + float m_contrast = 1.0f; + float m_saturation = 1.0f; + float m_fogDensity = 0.001f; + + void ShowDockspace(); + void ShowSceneView(); + void ShowInspector(); + void ShowConsole(); + void SetupInitialDockLayout(ImGuiID dockspace_id); + +}; + +} // reactor + +#endif //IMGUI_HPP diff --git a/src/logging/ImGuiConsoleSink.cpp b/src/logging/ImGuiConsoleSink.cpp index 15e9fda..aa7fed4 100644 --- a/src/logging/ImGuiConsoleSink.cpp +++ b/src/logging/ImGuiConsoleSink.cpp @@ -1,31 +1,31 @@ -#include "ImGuiConsoleSink.hpp" - -namespace reactor -{ - -ImGuiConsoleSink::ImGuiConsoleSink(size_t maxMessages) : m_maxMessages(maxMessages) {} - -void ImGuiConsoleSink::log(const LogMessage& msg) -{ - std::lock_guard lock(m_mutex); - m_messages.push_back(msg); - // Trim the queue if it exceeds the maximum size - if (m_messages.size() > m_maxMessages) - { - m_messages.pop_front(); - } -} - -void ImGuiConsoleSink::accessMessages(const std::function&)>& accessor) const -{ - std::lock_guard lock(m_mutex); - accessor(m_messages); -} - -void ImGuiConsoleSink::clearMessages() -{ - std::lock_guard lock(m_mutex); - m_messages.clear(); -} - +#include "ImGuiConsoleSink.hpp" + +namespace reactor +{ + +ImGuiConsoleSink::ImGuiConsoleSink(size_t maxMessages) : m_maxMessages(maxMessages) {} + +void ImGuiConsoleSink::log(const LogMessage& msg) +{ + std::lock_guard lock(m_mutex); + m_messages.push_back(msg); + // Trim the queue if it exceeds the maximum size + if (m_messages.size() > m_maxMessages) + { + m_messages.pop_front(); + } +} + +void ImGuiConsoleSink::accessMessages(const std::function&)>& accessor) const +{ + std::lock_guard lock(m_mutex); + accessor(m_messages); +} + +void ImGuiConsoleSink::clearMessages() +{ + std::lock_guard lock(m_mutex); + m_messages.clear(); +} + } // namespace reactor \ No newline at end of file diff --git a/src/logging/ImGuiConsoleSink.hpp b/src/logging/ImGuiConsoleSink.hpp index 6184a38..01e9869 100644 --- a/src/logging/ImGuiConsoleSink.hpp +++ b/src/logging/ImGuiConsoleSink.hpp @@ -1,26 +1,26 @@ -#pragma once - -#include "Logger.hpp" - -namespace reactor -{ - -class ImGuiConsoleSink : public ILogSink -{ -public: - explicit ImGuiConsoleSink(size_t maxMessages = 2000); - void log(const LogMessage& msg) override; - - // Allows the UI thread to safely iterate over the collected messages - void accessMessages(const std::function&)>& accessor) const; - - // Clears all messages from the console - void clearMessages(); - -private: - std::deque m_messages; - size_t m_maxMessages; - mutable std::mutex m_mutex; // `mutable` allows locking in const methods -}; - +#pragma once + +#include "Logger.hpp" + +namespace reactor +{ + +class ImGuiConsoleSink : public ILogSink +{ +public: + explicit ImGuiConsoleSink(size_t maxMessages = 2000); + void log(const LogMessage& msg) override; + + // Allows the UI thread to safely iterate over the collected messages + void accessMessages(const std::function&)>& accessor) const; + + // Clears all messages from the console + void clearMessages(); + +private: + std::deque m_messages; + size_t m_maxMessages; + mutable std::mutex m_mutex; // `mutable` allows locking in const methods +}; + } // namespace reactor \ No newline at end of file diff --git a/src/logging/Logger.hpp b/src/logging/Logger.hpp index d1b2b9a..d26111c 100644 --- a/src/logging/Logger.hpp +++ b/src/logging/Logger.hpp @@ -1,126 +1,126 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -// Use the fmt library for powerful and safe string formatting -#include - -namespace reactor -{ - -// Defines the severity of a log message -enum class LogLevel -{ - Trace, - Info, - Warn, - Error, - Critical -}; - -// Represents a single log entry -struct LogMessage -{ - std::string message; - LogLevel level; -}; - -// Abstract interface for a "sink" that receives log messages. -class ILogSink -{ - public: - virtual ~ILogSink() = default; - virtual void log(const LogMessage& msg) = 0; -}; - -// The main Logger class, implemented as a singleton for global access. -class Logger -{ - public: - // Make the singleton non-copyable and non-movable - Logger(const Logger&) = delete; - Logger& operator=(const Logger&) = delete; - Logger(Logger&&) = delete; - Logger& operator=(Logger&&) = delete; - - // Access the single instance of the logger - static Logger& getInstance() - { - static Logger instance; - return instance; - } - - // Attach a new sink to the logger - void addSink(std::shared_ptr sink) - { - std::lock_guard lock(m_mutex); - m_sinks.push_back(std::move(sink)); - } - - // Template functions for logging with various arguments - template - void trace(fmt::format_string format, Args&&... args) - { - log(LogLevel::Trace, fmt::format(format, std::forward(args)...)); - } - - template - void info(fmt::format_string format, Args&&... args) - { - log(LogLevel::Info, fmt::format(format, std::forward(args)...)); - } - - template - void warn(fmt::format_string format, Args&&... args) - { - log(LogLevel::Warn, fmt::format(format, std::forward(args)...)); - } - - template - void error(fmt::format_string format, Args&&... args) - { - log(LogLevel::Error, fmt::format(format, std::forward(args)...)); - } - - template - void critical(fmt::format_string format, Args&&... args) - { - log(LogLevel::Critical, fmt::format(format, std::forward(args)...)); - } - - private: - // Private constructor for singleton pattern - Logger() = default; - ~Logger() = default; - - // The core log function that distributes messages to sinks - void log(LogLevel level, const std::string& msg) - { - std::lock_guard lock(m_mutex); - LogMessage logMsg{msg, level}; - for (const auto& sink : m_sinks) - { - if (sink) - { - sink->log(logMsg); - } - } - } - - std::vector> m_sinks; - std::mutex m_mutex; -}; - -} // namespace reactor - -// Helper macros to make logging calls cleaner and less verbose -#define LOG_TRACE(...) reactor::Logger::getInstance().trace(__VA_ARGS__) -#define LOG_INFO(...) reactor::Logger::getInstance().info(__VA_ARGS__) -#define LOG_WARN(...) reactor::Logger::getInstance().warn(__VA_ARGS__) -#define LOG_ERROR(...) reactor::Logger::getInstance().error(__VA_ARGS__) +#pragma once + +#include +#include +#include +#include +#include +#include + +// Use the fmt library for powerful and safe string formatting +#include + +namespace reactor +{ + +// Defines the severity of a log message +enum class LogLevel +{ + Trace, + Info, + Warn, + Error, + Critical +}; + +// Represents a single log entry +struct LogMessage +{ + std::string message; + LogLevel level; +}; + +// Abstract interface for a "sink" that receives log messages. +class ILogSink +{ + public: + virtual ~ILogSink() = default; + virtual void log(const LogMessage& msg) = 0; +}; + +// The main Logger class, implemented as a singleton for global access. +class Logger +{ + public: + // Make the singleton non-copyable and non-movable + Logger(const Logger&) = delete; + Logger& operator=(const Logger&) = delete; + Logger(Logger&&) = delete; + Logger& operator=(Logger&&) = delete; + + // Access the single instance of the logger + static Logger& getInstance() + { + static Logger instance; + return instance; + } + + // Attach a new sink to the logger + void addSink(std::shared_ptr sink) + { + std::lock_guard lock(m_mutex); + m_sinks.push_back(std::move(sink)); + } + + // Template functions for logging with various arguments + template + void trace(fmt::format_string format, Args&&... args) + { + log(LogLevel::Trace, fmt::format(format, std::forward(args)...)); + } + + template + void info(fmt::format_string format, Args&&... args) + { + log(LogLevel::Info, fmt::format(format, std::forward(args)...)); + } + + template + void warn(fmt::format_string format, Args&&... args) + { + log(LogLevel::Warn, fmt::format(format, std::forward(args)...)); + } + + template + void error(fmt::format_string format, Args&&... args) + { + log(LogLevel::Error, fmt::format(format, std::forward(args)...)); + } + + template + void critical(fmt::format_string format, Args&&... args) + { + log(LogLevel::Critical, fmt::format(format, std::forward(args)...)); + } + + private: + // Private constructor for singleton pattern + Logger() = default; + ~Logger() = default; + + // The core log function that distributes messages to sinks + void log(LogLevel level, const std::string& msg) + { + std::lock_guard lock(m_mutex); + LogMessage logMsg{msg, level}; + for (const auto& sink : m_sinks) + { + if (sink) + { + sink->log(logMsg); + } + } + } + + std::vector> m_sinks; + std::mutex m_mutex; +}; + +} // namespace reactor + +// Helper macros to make logging calls cleaner and less verbose +#define LOG_TRACE(...) reactor::Logger::getInstance().trace(__VA_ARGS__) +#define LOG_INFO(...) reactor::Logger::getInstance().info(__VA_ARGS__) +#define LOG_WARN(...) reactor::Logger::getInstance().warn(__VA_ARGS__) +#define LOG_ERROR(...) reactor::Logger::getInstance().error(__VA_ARGS__) #define LOG_CRITICAL(...) reactor::Logger::getInstance().critical(__VA_ARGS__) \ No newline at end of file diff --git a/src/logging/SpdlogSink.cpp b/src/logging/SpdlogSink.cpp index f45afca..a53f588 100644 --- a/src/logging/SpdlogSink.cpp +++ b/src/logging/SpdlogSink.cpp @@ -1,36 +1,36 @@ -#include "SpdlogSink.hpp" -#include - -namespace reactor -{ - -SpdlogSink::SpdlogSink() -{ - // Initialize a thread-safe color logger that outputs to the console - m_spdlogLogger = spdlog::stdout_color_mt("console"); -} - -void SpdlogSink::log(const LogMessage& msg) -{ - // Map our LogLevel to the corresponding spdlog function - switch (msg.level) - { - case LogLevel::Trace: - m_spdlogLogger->trace(msg.message); - break; - case LogLevel::Info: - m_spdlogLogger->info(msg.message); - break; - case LogLevel::Warn: - m_spdlogLogger->warn(msg.message); - break; - case LogLevel::Error: - m_spdlogLogger->error(msg.message); - break; - case LogLevel::Critical: - m_spdlogLogger->critical(msg.message); - break; - } -} - +#include "SpdlogSink.hpp" +#include + +namespace reactor +{ + +SpdlogSink::SpdlogSink() +{ + // Initialize a thread-safe color logger that outputs to the console + m_spdlogLogger = spdlog::stdout_color_mt("console"); +} + +void SpdlogSink::log(const LogMessage& msg) +{ + // Map our LogLevel to the corresponding spdlog function + switch (msg.level) + { + case LogLevel::Trace: + m_spdlogLogger->trace(msg.message); + break; + case LogLevel::Info: + m_spdlogLogger->info(msg.message); + break; + case LogLevel::Warn: + m_spdlogLogger->warn(msg.message); + break; + case LogLevel::Error: + m_spdlogLogger->error(msg.message); + break; + case LogLevel::Critical: + m_spdlogLogger->critical(msg.message); + break; + } +} + } // namespace reactor \ No newline at end of file diff --git a/src/logging/SpdlogSink.hpp b/src/logging/SpdlogSink.hpp index 146e9ea..2040d4c 100644 --- a/src/logging/SpdlogSink.hpp +++ b/src/logging/SpdlogSink.hpp @@ -1,19 +1,19 @@ -#pragma once - -#include "Logger.hpp" -#include - -namespace reactor -{ - -class SpdlogSink : public ILogSink -{ -public: - SpdlogSink(); - void log(const LogMessage& msg) override; - -private: - std::shared_ptr m_spdlogLogger; -}; - +#pragma once + +#include "Logger.hpp" +#include + +namespace reactor +{ + +class SpdlogSink : public ILogSink +{ +public: + SpdlogSink(); + void log(const LogMessage& msg) override; + +private: + std::shared_ptr m_spdlogLogger; +}; + } // namespace reactor \ No newline at end of file diff --git a/src/pch.hpp b/src/pch.hpp index 559eb8a..e0eae14 100644 --- a/src/pch.hpp +++ b/src/pch.hpp @@ -1,28 +1,28 @@ -#pragma once - -// Common standard library includes -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 -#include -#include -#include - -// VMA allocator -#include - -#include -#include -#include - -#include - +#pragma once + +// Common standard library includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 +#include +#include +#include + +// VMA allocator +#include + +#include +#include +#include + +#include + diff --git a/src/tools/ModelBuilder.cpp b/src/tools/ModelBuilder.cpp index d48559b..218ba6e 100644 --- a/src/tools/ModelBuilder.cpp +++ b/src/tools/ModelBuilder.cpp @@ -1,19 +1,19 @@ -#include "../core/ModelIO.hpp" - -#include - -int main() -{ - // Define input and output paths - const std::string inputModel = "../workspace/thingy.obj"; - const std::string outputModel = "../resources/models/thingy.mesh"; - - // Run the import and export process - if(reactor::importAndExport(inputModel, outputModel)) { - // Optional: Test the loader to verify the export - spdlog::info("--- Verifying export by loading file back ---"); - reactor::loadModelFromBinary(outputModel); - } - - return 0; -} +#include "../core/ModelIO.hpp" + +#include + +int main() +{ + // Define input and output paths + const std::string inputModel = "../workspace/thingy.obj"; + const std::string outputModel = "../resources/models/thingy.mesh"; + + // Run the import and export process + if(reactor::importAndExport(inputModel, outputModel)) { + // Optional: Test the loader to verify the export + spdlog::info("--- Verifying export by loading file back ---"); + reactor::loadModelFromBinary(outputModel); + } + + return 0; +} diff --git a/src/vulkan/Allocator.cpp b/src/vulkan/Allocator.cpp index cbe2667..17cb987 100644 --- a/src/vulkan/Allocator.cpp +++ b/src/vulkan/Allocator.cpp @@ -1,92 +1,92 @@ -// -// Created by rfdic on 6/13/2025. -// - -#include "Allocator.hpp" - -#include - -#include "Buffer.hpp" - -namespace reactor -{ - -Allocator::Allocator(vk::PhysicalDevice physicalDevice, vk::Device device, vk::Instance instance, vk::Queue graphicsQueue, uint32_t graphicsQueueFamilyIndex) - : m_device(device), m_graphicsQueue(graphicsQueue), m_graphicQueueFamilyIndex(graphicsQueueFamilyIndex) -{ - VmaAllocatorCreateInfo allocatorInfo = {}; - allocatorInfo.physicalDevice = physicalDevice; - allocatorInfo.device = device; - allocatorInfo.instance = instance; - - vmaCreateAllocator(&allocatorInfo, &m_allocator); - - spdlog::info("Allocator created"); -} - -Allocator::~Allocator() -{ - if (m_allocator) - vmaDestroyAllocator(m_allocator); -} - -std::unique_ptr Allocator::createBufferWithData(const void* data, vk::DeviceSize size, vk::BufferUsageFlags usage, const std::string& name) -{ - // Create CPU-visible staging buffer - Buffer stagingBuffer( - *this, - size, - vk::BufferUsageFlagBits::eTransferSrc, - VMA_MEMORY_USAGE_CPU_ONLY, - name + " Staging"); - - // Map and copy data to staging buffer - void* mappedData; - vmaMapMemory(m_allocator, stagingBuffer.allocation(), &mappedData); - memcpy(mappedData, data, size); - vmaUnmapMemory(m_allocator, stagingBuffer.allocation()); - - // Create GPU-local destination buffer - // Add the transfer destination usage flag - usage |= vk::BufferUsageFlagBits::eTransferDst; - auto destBuffer = std::make_unique( - *this, - size, - usage, - VMA_MEMORY_USAGE_GPU_ONLY, - name); - - // Perform the copy - immediateSubmit([&](vk::CommandBuffer cmd) { - vk::BufferCopy copyRegion(0, 0, size); - cmd.copyBuffer(stagingBuffer.getHandle(), destBuffer->getHandle(), 1, ©Region); - }); - - return destBuffer; -} - -void Allocator::immediateSubmit( - std::function&& function) -{ - // Create a temporary command pool and buffer - vk::CommandPoolCreateInfo poolInfo(vk::CommandPoolCreateFlagBits::eTransient, m_graphicQueueFamilyIndex); - vk::CommandPool cmdPool = m_device.createCommandPool(poolInfo); - - vk::CommandBufferAllocateInfo allocInfo(cmdPool, vk::CommandBufferLevel::ePrimary, 1); - vk::CommandBuffer cmd = m_device.allocateCommandBuffers(allocInfo)[0]; - - // Record and submit commands - cmd.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); - function(cmd); // Execute the provided command recording lambda - cmd.end(); - - vk::SubmitInfo submitInfo(0, nullptr, nullptr, 1, &cmd); - m_graphicsQueue.submit(submitInfo, nullptr); - m_graphicsQueue.waitIdle(); // Wait for the operation to complete - - // Clean up temporary resources - m_device.freeCommandBuffers(cmdPool, cmd); - m_device.destroyCommandPool(cmdPool); -} - +// +// Created by rfdic on 6/13/2025. +// + +#include "Allocator.hpp" + +#include + +#include "Buffer.hpp" + +namespace reactor +{ + +Allocator::Allocator(vk::PhysicalDevice physicalDevice, vk::Device device, vk::Instance instance, vk::Queue graphicsQueue, uint32_t graphicsQueueFamilyIndex) + : m_device(device), m_graphicsQueue(graphicsQueue), m_graphicQueueFamilyIndex(graphicsQueueFamilyIndex) +{ + VmaAllocatorCreateInfo allocatorInfo = {}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + allocatorInfo.instance = instance; + + vmaCreateAllocator(&allocatorInfo, &m_allocator); + + spdlog::info("Allocator created"); +} + +Allocator::~Allocator() +{ + if (m_allocator) + vmaDestroyAllocator(m_allocator); +} + +std::unique_ptr Allocator::createBufferWithData(const void* data, vk::DeviceSize size, vk::BufferUsageFlags usage, const std::string& name) +{ + // Create CPU-visible staging buffer + Buffer stagingBuffer( + *this, + size, + vk::BufferUsageFlagBits::eTransferSrc, + VMA_MEMORY_USAGE_CPU_ONLY, + name + " Staging"); + + // Map and copy data to staging buffer + void* mappedData; + vmaMapMemory(m_allocator, stagingBuffer.allocation(), &mappedData); + memcpy(mappedData, data, size); + vmaUnmapMemory(m_allocator, stagingBuffer.allocation()); + + // Create GPU-local destination buffer + // Add the transfer destination usage flag + usage |= vk::BufferUsageFlagBits::eTransferDst; + auto destBuffer = std::make_unique( + *this, + size, + usage, + VMA_MEMORY_USAGE_GPU_ONLY, + name); + + // Perform the copy + immediateSubmit([&](vk::CommandBuffer cmd) { + vk::BufferCopy copyRegion(0, 0, size); + cmd.copyBuffer(stagingBuffer.getHandle(), destBuffer->getHandle(), 1, ©Region); + }); + + return destBuffer; +} + +void Allocator::immediateSubmit( + std::function&& function) +{ + // Create a temporary command pool and buffer + vk::CommandPoolCreateInfo poolInfo(vk::CommandPoolCreateFlagBits::eTransient, m_graphicQueueFamilyIndex); + vk::CommandPool cmdPool = m_device.createCommandPool(poolInfo); + + vk::CommandBufferAllocateInfo allocInfo(cmdPool, vk::CommandBufferLevel::ePrimary, 1); + vk::CommandBuffer cmd = m_device.allocateCommandBuffers(allocInfo)[0]; + + // Record and submit commands + cmd.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + function(cmd); // Execute the provided command recording lambda + cmd.end(); + + vk::SubmitInfo submitInfo(0, nullptr, nullptr, 1, &cmd); + m_graphicsQueue.submit(submitInfo, nullptr); + m_graphicsQueue.waitIdle(); // Wait for the operation to complete + + // Clean up temporary resources + m_device.freeCommandBuffers(cmdPool, cmd); + m_device.destroyCommandPool(cmdPool); +} + } // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Allocator.hpp b/src/vulkan/Allocator.hpp index 248e432..447a8ce 100644 --- a/src/vulkan/Allocator.hpp +++ b/src/vulkan/Allocator.hpp @@ -1,55 +1,55 @@ -#pragma once - -#include -#include -#include - -namespace reactor -{ -class Buffer; - -class Allocator -{ -public: - Allocator(vk::PhysicalDevice physicalDevice, vk::Device device, vk::Instance instance, vk::Queue graphicsQueue, uint32_t graphicsQueueFamilyIndex); - ~Allocator(); - - VmaAllocator getAllocator() const - { - return m_allocator; - } - vk::Device getDevice() const - { - return m_device; - } - vk::Queue getGraphicsQueue() const - { - return m_graphicsQueue; - } - uint32_t getGraphicsQueueFamilyIndex() const - { - return m_graphicQueueFamilyIndex; - } - - // New factory method - std::unique_ptr createBufferWithData( - const void* data, - vk::DeviceSize size, - vk::BufferUsageFlags usage, - const std::string& name = ""); - - // Non-copyable - Allocator(const Allocator&) = delete; - Allocator& operator=(const Allocator&) = delete; - -private: - - void immediateSubmit(std::function&& function); - - VmaAllocator m_allocator = nullptr; - vk::Device m_device; - vk::Queue m_graphicsQueue; - uint32_t m_graphicQueueFamilyIndex; -}; - -} // namespace reactor +#pragma once + +#include +#include +#include + +namespace reactor +{ +class Buffer; + +class Allocator +{ +public: + Allocator(vk::PhysicalDevice physicalDevice, vk::Device device, vk::Instance instance, vk::Queue graphicsQueue, uint32_t graphicsQueueFamilyIndex); + ~Allocator(); + + VmaAllocator getAllocator() const + { + return m_allocator; + } + vk::Device getDevice() const + { + return m_device; + } + vk::Queue getGraphicsQueue() const + { + return m_graphicsQueue; + } + uint32_t getGraphicsQueueFamilyIndex() const + { + return m_graphicQueueFamilyIndex; + } + + // New factory method + std::unique_ptr createBufferWithData( + const void* data, + vk::DeviceSize size, + vk::BufferUsageFlags usage, + const std::string& name = ""); + + // Non-copyable + Allocator(const Allocator&) = delete; + Allocator& operator=(const Allocator&) = delete; + +private: + + void immediateSubmit(std::function&& function); + + VmaAllocator m_allocator = nullptr; + vk::Device m_device; + vk::Queue m_graphicsQueue; + uint32_t m_graphicQueueFamilyIndex; +}; + +} // namespace reactor diff --git a/src/vulkan/Buffer.cpp b/src/vulkan/Buffer.cpp index beb45af..9004217 100644 --- a/src/vulkan/Buffer.cpp +++ b/src/vulkan/Buffer.cpp @@ -1,57 +1,57 @@ -// -// Created by rfdic on 6/14/2025. -// - -#include "Buffer.hpp" - -#include - -namespace reactor { - - Buffer::Buffer(Allocator &allocator, vk::DeviceSize size, vk::BufferUsageFlags usage, VmaMemoryUsage memoryUsage, std::string name) - : m_allocator(allocator), m_size(size), m_name(name) - { - vk::BufferCreateInfo bufferInfo = {}; - bufferInfo.size = size; - bufferInfo.usage = usage; - bufferInfo.sharingMode = vk::SharingMode::eExclusive; - - VmaAllocationCreateInfo allocInfo = {}; - allocInfo.usage = memoryUsage; - - const VkResult result = vmaCreateBuffer( - m_allocator.getAllocator(), - reinterpret_cast(&bufferInfo), - &allocInfo, - reinterpret_cast(&m_buffer), - &m_allocation, - nullptr); - - if (result != VK_SUCCESS) { - throw std::runtime_error("failed to create buffer!"); - } - } - - Buffer::~Buffer() { - if (m_buffer && m_allocation) { - vmaDestroyBuffer(m_allocator.getAllocator(), m_buffer, m_allocation); - - spdlog::info("Buffer {} destroyed", m_name.c_str()); - - m_buffer = nullptr; - m_allocation = nullptr; - } - } - -void* Buffer::map() { - void* data; - vmaMapMemory(m_allocator.getAllocator(), m_allocation, &data); - return data; -} - -void Buffer::unmap() { - vmaUnmapMemory(m_allocator.getAllocator(), m_allocation); -} - - +// +// Created by rfdic on 6/14/2025. +// + +#include "Buffer.hpp" + +#include + +namespace reactor { + + Buffer::Buffer(Allocator &allocator, vk::DeviceSize size, vk::BufferUsageFlags usage, VmaMemoryUsage memoryUsage, std::string name) + : m_allocator(allocator), m_size(size), m_name(name) + { + vk::BufferCreateInfo bufferInfo = {}; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = vk::SharingMode::eExclusive; + + VmaAllocationCreateInfo allocInfo = {}; + allocInfo.usage = memoryUsage; + + const VkResult result = vmaCreateBuffer( + m_allocator.getAllocator(), + reinterpret_cast(&bufferInfo), + &allocInfo, + reinterpret_cast(&m_buffer), + &m_allocation, + nullptr); + + if (result != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + } + + Buffer::~Buffer() { + if (m_buffer && m_allocation) { + vmaDestroyBuffer(m_allocator.getAllocator(), m_buffer, m_allocation); + + spdlog::info("Buffer {} destroyed", m_name.c_str()); + + m_buffer = nullptr; + m_allocation = nullptr; + } + } + +void* Buffer::map() { + void* data; + vmaMapMemory(m_allocator.getAllocator(), m_allocation, &data); + return data; +} + +void Buffer::unmap() { + vmaUnmapMemory(m_allocator.getAllocator(), m_allocation); +} + + } // reactor \ No newline at end of file diff --git a/src/vulkan/Buffer.hpp b/src/vulkan/Buffer.hpp index f9db8b4..db56c67 100644 --- a/src/vulkan/Buffer.hpp +++ b/src/vulkan/Buffer.hpp @@ -1,38 +1,38 @@ -#pragma once - -#include "Allocator.hpp" -#include -#include - -namespace reactor { - -class Buffer { -public: - Buffer(Allocator& allocator, vk::DeviceSize size, vk::BufferUsageFlags usage, VmaMemoryUsage memoryUsage, std::string name=""); - - ~Buffer(); - - [[nodiscard]] const std::string& getName() const { return m_name; } - - // Non-copyable - Buffer(const Buffer&) = delete; - Buffer& operator=(const Buffer&) = delete; - - [[nodiscard]] vk::Buffer getHandle() const { return m_buffer; } - [[nodiscard]] VmaAllocation allocation() const { return m_allocation; } - [[nodiscard]] vk::DeviceSize size() const { return m_size; } - - void* map(); - void unmap(); - -private: - Allocator& m_allocator; - vk::Buffer m_buffer = VK_NULL_HANDLE; - VmaAllocation m_allocation = VK_NULL_HANDLE; - vk::DeviceSize m_size = 0; - - std::string m_name; -}; - -} // reactor - +#pragma once + +#include "Allocator.hpp" +#include +#include + +namespace reactor { + +class Buffer { +public: + Buffer(Allocator& allocator, vk::DeviceSize size, vk::BufferUsageFlags usage, VmaMemoryUsage memoryUsage, std::string name=""); + + ~Buffer(); + + [[nodiscard]] const std::string& getName() const { return m_name; } + + // Non-copyable + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + + [[nodiscard]] vk::Buffer getHandle() const { return m_buffer; } + [[nodiscard]] VmaAllocation allocation() const { return m_allocation; } + [[nodiscard]] vk::DeviceSize size() const { return m_size; } + + void* map(); + void unmap(); + +private: + Allocator& m_allocator; + vk::Buffer m_buffer = VK_NULL_HANDLE; + VmaAllocation m_allocation = VK_NULL_HANDLE; + vk::DeviceSize m_size = 0; + + std::string m_name; +}; + +} // reactor + diff --git a/src/vulkan/DebugUtils.hpp b/src/vulkan/DebugUtils.hpp index eda1101..aadc1e7 100644 --- a/src/vulkan/DebugUtils.hpp +++ b/src/vulkan/DebugUtils.hpp @@ -1,48 +1,48 @@ -#pragma once - -#include -#include - -#ifndef NDEBUG - const bool g_enableDebugMarkers = true; -#else -const bool g_enableDebugMarkers = false; -#endif - -namespace reactor::Debug -{ - -// Sets the name of any Vulkan object -inline void setObjectName(vk::Device device, uint64_t object, vk::ObjectType objectType, const std::string& name) { - if (!g_enableDebugMarkers) return; - - vk::DebugUtilsObjectNameInfoEXT nameInfo{}; - nameInfo.objectType = objectType; - nameInfo.objectHandle = object; - nameInfo.pObjectName = name.c_str(); - - // No need for dynamic dispatch here, as device functions are loaded with the device - device.setDebugUtilsObjectNameEXT(nameInfo); -} - -// Begins a labeled region in a command buffer -inline void beginLabel(vk::CommandBuffer cmd, const std::string& label, const std::array& color = {1.0f, 1.0f, 1.0f, 1.0f}) { - if (!g_enableDebugMarkers) return; - - vk::DebugUtilsLabelEXT labelInfo{}; - labelInfo.pLabelName = label.c_str(); - labelInfo.color[0] = color[0]; - labelInfo.color[1] = color[1]; - labelInfo.color[2] = color[2]; - labelInfo.color[3] = color[3]; - - cmd.beginDebugUtilsLabelEXT(labelInfo); -} - -// Ends a labeled region in a command buffer -inline void endLabel(vk::CommandBuffer cmd) { - if (!g_enableDebugMarkers) return; - cmd.endDebugUtilsLabelEXT(); -} - +#pragma once + +#include +#include + +#ifndef NDEBUG + const bool g_enableDebugMarkers = true; +#else +const bool g_enableDebugMarkers = false; +#endif + +namespace reactor::Debug +{ + +// Sets the name of any Vulkan object +inline void setObjectName(vk::Device device, uint64_t object, vk::ObjectType objectType, const std::string& name) { + if (!g_enableDebugMarkers) return; + + vk::DebugUtilsObjectNameInfoEXT nameInfo{}; + nameInfo.objectType = objectType; + nameInfo.objectHandle = object; + nameInfo.pObjectName = name.c_str(); + + // No need for dynamic dispatch here, as device functions are loaded with the device + device.setDebugUtilsObjectNameEXT(nameInfo); +} + +// Begins a labeled region in a command buffer +inline void beginLabel(vk::CommandBuffer cmd, const std::string& label, const std::array& color = {1.0f, 1.0f, 1.0f, 1.0f}) { + if (!g_enableDebugMarkers) return; + + vk::DebugUtilsLabelEXT labelInfo{}; + labelInfo.pLabelName = label.c_str(); + labelInfo.color[0] = color[0]; + labelInfo.color[1] = color[1]; + labelInfo.color[2] = color[2]; + labelInfo.color[3] = color[3]; + + cmd.beginDebugUtilsLabelEXT(labelInfo); +} + +// Ends a labeled region in a command buffer +inline void endLabel(vk::CommandBuffer cmd) { + if (!g_enableDebugMarkers) return; + cmd.endDebugUtilsLabelEXT(); +} + } // namespace reactor::Debug \ No newline at end of file diff --git a/src/vulkan/DescriptorSet.cpp b/src/vulkan/DescriptorSet.cpp index f57e2a9..cf3e5bd 100644 --- a/src/vulkan/DescriptorSet.cpp +++ b/src/vulkan/DescriptorSet.cpp @@ -1,56 +1,56 @@ -// -// Created by rfdic on 6/13/2025. -// - -#include "DescriptorSet.hpp" - -#include "Buffer.hpp" - -namespace reactor { - - DescriptorSet::DescriptorSet(vk::Device device, vk::DescriptorPool pool, size_t framesInFlight, const std::vector &bindings) - : m_device(device), m_pool(pool) - { - // create a descriptor set layout - vk::DescriptorSetLayoutCreateInfo layoutInfo({}, bindings); - m_layout = device.createDescriptorSetLayout(layoutInfo); - - std::vector poolSizes; - for (const auto& binding : bindings) { - poolSizes.push_back({binding.descriptorType, static_cast(framesInFlight)}); - } - - // allocate one set per frame - std::vector layouts(framesInFlight, m_layout); - vk::DescriptorSetAllocateInfo allocInfo(m_pool, layouts); - m_sets = device.allocateDescriptorSets(allocInfo); - - } - - DescriptorSet::~DescriptorSet() { - if (m_layout) m_device.destroyDescriptorSetLayout(m_layout); - } - - void DescriptorSet::updateUniformBuffer(size_t frame, const Buffer &buffer) { - vk::DescriptorBufferInfo bufferInfo; - bufferInfo.buffer = buffer.getHandle(); - bufferInfo.offset = 0; - bufferInfo.range = buffer.size(); - - vk::WriteDescriptorSet write; - write.dstSet = m_sets[frame]; - write.dstBinding = 0; - write.dstArrayElement = 0; - write.descriptorType = vk::DescriptorType::eUniformBuffer; - write.descriptorCount = 1; - write.pBufferInfo = &bufferInfo; - write.pImageInfo = nullptr; - write.pTexelBufferView = nullptr; - - m_device.updateDescriptorSets(1, &write, 0, nullptr); - } - -void DescriptorSet::updateSet(const std::vector &writes) { - m_device.updateDescriptorSets(static_cast(writes.size()), writes.data(), 0, nullptr); - } +// +// Created by rfdic on 6/13/2025. +// + +#include "DescriptorSet.hpp" + +#include "Buffer.hpp" + +namespace reactor { + + DescriptorSet::DescriptorSet(vk::Device device, vk::DescriptorPool pool, size_t framesInFlight, const std::vector &bindings) + : m_device(device), m_pool(pool) + { + // create a descriptor set layout + vk::DescriptorSetLayoutCreateInfo layoutInfo({}, bindings); + m_layout = device.createDescriptorSetLayout(layoutInfo); + + std::vector poolSizes; + for (const auto& binding : bindings) { + poolSizes.push_back({binding.descriptorType, static_cast(framesInFlight)}); + } + + // allocate one set per frame + std::vector layouts(framesInFlight, m_layout); + vk::DescriptorSetAllocateInfo allocInfo(m_pool, layouts); + m_sets = device.allocateDescriptorSets(allocInfo); + + } + + DescriptorSet::~DescriptorSet() { + if (m_layout) m_device.destroyDescriptorSetLayout(m_layout); + } + + void DescriptorSet::updateUniformBuffer(size_t frame, const Buffer &buffer) { + vk::DescriptorBufferInfo bufferInfo; + bufferInfo.buffer = buffer.getHandle(); + bufferInfo.offset = 0; + bufferInfo.range = buffer.size(); + + vk::WriteDescriptorSet write; + write.dstSet = m_sets[frame]; + write.dstBinding = 0; + write.dstArrayElement = 0; + write.descriptorType = vk::DescriptorType::eUniformBuffer; + write.descriptorCount = 1; + write.pBufferInfo = &bufferInfo; + write.pImageInfo = nullptr; + write.pTexelBufferView = nullptr; + + m_device.updateDescriptorSets(1, &write, 0, nullptr); + } + +void DescriptorSet::updateSet(const std::vector &writes) { + m_device.updateDescriptorSets(static_cast(writes.size()), writes.data(), 0, nullptr); + } } // reactor \ No newline at end of file diff --git a/src/vulkan/DescriptorSet.hpp b/src/vulkan/DescriptorSet.hpp index 3bd2ac8..cde7102 100644 --- a/src/vulkan/DescriptorSet.hpp +++ b/src/vulkan/DescriptorSet.hpp @@ -1,42 +1,42 @@ -// -// Created by rfdic on 6/13/2025. -// - -#ifndef DESCRIPTORSET_HPP -#define DESCRIPTORSET_HPP - -namespace reactor { - class Buffer; - - class DescriptorSet { -public: - DescriptorSet( - vk::Device device, - vk::DescriptorPool pool, - size_t framesInFlight, - const std::vector& bindings); - - ~DescriptorSet(); - - [[nodiscard]] vk::DescriptorSetLayout getLayout() const { return m_layout; } - [[nodiscard]] vk::DescriptorSet get(size_t frame) const { return m_sets[frame]; } - [[nodiscard]] size_t getFrameCount() const { return m_sets.size(); } - - [[nodiscard]] vk::DescriptorSet getCurrentSet(size_t currentFrame) const { return m_sets[currentFrame]; } - - // helper for updating sets - void updateSet(const std::vector& writes); - - void updateUniformBuffer(size_t frame, const Buffer& buffer); - -private: - vk::Device m_device; - vk::DescriptorPool m_pool; - vk::DescriptorSetLayout m_layout; - - std::vector m_sets; -}; - -} // reactor - -#endif //DESCRIPTORSET_HPP +// +// Created by rfdic on 6/13/2025. +// + +#ifndef DESCRIPTORSET_HPP +#define DESCRIPTORSET_HPP + +namespace reactor { + class Buffer; + + class DescriptorSet { +public: + DescriptorSet( + vk::Device device, + vk::DescriptorPool pool, + size_t framesInFlight, + const std::vector& bindings); + + ~DescriptorSet(); + + [[nodiscard]] vk::DescriptorSetLayout getLayout() const { return m_layout; } + [[nodiscard]] vk::DescriptorSet get(size_t frame) const { return m_sets[frame]; } + [[nodiscard]] size_t getFrameCount() const { return m_sets.size(); } + + [[nodiscard]] vk::DescriptorSet getCurrentSet(size_t currentFrame) const { return m_sets[currentFrame]; } + + // helper for updating sets + void updateSet(const std::vector& writes); + + void updateUniformBuffer(size_t frame, const Buffer& buffer); + +private: + vk::Device m_device; + vk::DescriptorPool m_pool; + vk::DescriptorSetLayout m_layout; + + std::vector m_sets; +}; + +} // reactor + +#endif //DESCRIPTORSET_HPP diff --git a/src/vulkan/FrameManager.cpp b/src/vulkan/FrameManager.cpp index d252dd4..94cc61f 100644 --- a/src/vulkan/FrameManager.cpp +++ b/src/vulkan/FrameManager.cpp @@ -1,138 +1,138 @@ -#include "FrameManager.hpp" - -#include -#include - -namespace reactor { - -FrameManager::FrameManager(vk::Device device, Allocator& allocator, uint32_t commandQueueFamilyIndex, size_t maxFramesInFlight, uint32_t swapchainImageCount) - : m_device(device), m_currentFrame(0), m_framesInFlightCount(maxFramesInFlight) -{ - spdlog::info("Creating FrameManager with {} frames in flight", maxFramesInFlight); - - vk::CommandPoolCreateInfo poolInfo{ - vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - commandQueueFamilyIndex - }; - m_commandPool = m_device.createCommandPool(poolInfo); - - // Allocate per-frame resources - m_frames.resize(maxFramesInFlight); - for (auto& frame : m_frames) { - vk::CommandBufferAllocateInfo allocInfo{ - m_commandPool, - vk::CommandBufferLevel::ePrimary, - 1 - }; - frame.commandBuffer = m_device.allocateCommandBuffers(allocInfo)[0]; - vk::FenceCreateInfo fenceInfo{vk::FenceCreateFlagBits::eSignaled}; - frame.inFlightFence = m_device.createFence(fenceInfo); - - // create a uniform buffer - frame.uniformBuffer = std::make_unique(allocator, 1024, vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_ONLY); - } - - vk::SemaphoreCreateInfo semaphoreInfo{}; - - m_imageAvailableSemaphores.resize(maxFramesInFlight); - for (size_t i = 0; i < maxFramesInFlight; i++) { - m_imageAvailableSemaphores[i] = m_device.createSemaphore(semaphoreInfo); - } - - m_renderFinishedSemaphores.resize(swapchainImageCount); - for (size_t i = 0; i < swapchainImageCount; i++) { - m_renderFinishedSemaphores[i] = m_device.createSemaphore(semaphoreInfo); - } - - m_imagesInFlight.resize(swapchainImageCount, VK_NULL_HANDLE); - -} - -FrameManager::~FrameManager() { - spdlog::info("Destroying FrameManager and cleaning up resources."); - - for (auto& semaphore : m_imageAvailableSemaphores) { - m_device.destroySemaphore(semaphore); - } - - for (auto& semaphore : m_renderFinishedSemaphores) { - m_device.destroySemaphore(semaphore); - } - - for (auto& frame : m_frames) { - if (frame.inFlightFence) { - m_device.destroyFence(frame.inFlightFence); - } - } - - if (m_commandPool) { - m_device.destroyCommandPool(m_commandPool); - } -} - -bool FrameManager::beginFrame(vk::SwapchainKHR swapchain, uint32_t& outImageIndex) { - vk::Result result = m_device.waitForFences(m_frames[m_currentFrame].inFlightFence, VK_TRUE, UINT64_MAX); - if (result != vk::Result::eSuccess) { - throw std::runtime_error("Failed to wait for fence!"); - } - - auto resultValue = m_device.acquireNextImageKHR( - swapchain, - UINT64_MAX, - m_imageAvailableSemaphores[m_currentFrame], - VK_NULL_HANDLE - ); - - if (resultValue.result == vk::Result::eErrorOutOfDateKHR) { - return false; - } - if (resultValue.result != vk::Result::eSuccess && resultValue.result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("Failed to acquire swapchain image!"); - } - - outImageIndex = resultValue.value; - - if (m_imagesInFlight[outImageIndex] != VK_NULL_HANDLE) { - m_device.waitForFences(m_imagesInFlight[outImageIndex], VK_TRUE, UINT64_MAX); - } - - // Associate the current frame's fence with the acquired swapchain image - m_imagesInFlight[outImageIndex] = m_frames[m_currentFrame].inFlightFence; - - m_device.resetFences(m_frames[m_currentFrame].inFlightFence); - - return true; -} - -void FrameManager::endFrame(vk::Queue graphicsQueue, vk::Queue presentQueue, vk::SwapchainKHR swapchain, uint32_t imageIndex) { - Frame& frame = m_frames[m_currentFrame]; - - vk::PipelineStageFlags waitStages = vk::PipelineStageFlagBits::eColorAttachmentOutput; - - vk::SubmitInfo submitInfo{ - 1, &m_imageAvailableSemaphores[m_currentFrame], - &waitStages, - 1, &frame.commandBuffer, - 1, &m_renderFinishedSemaphores[imageIndex] - }; - - graphicsQueue.submit(submitInfo, frame.inFlightFence); - - vk::PresentInfoKHR presentInfo{ - 1, &m_renderFinishedSemaphores[imageIndex], - 1, &swapchain, - &imageIndex - }; - - vk::Result result = presentQueue.presentKHR(presentInfo); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR) { - // Handle swapchain recreation here - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("Failed to present swapchain image!"); - } - - // Advance to next frame - m_currentFrame = (m_currentFrame + 1) % m_frames.size(); -} - -} // namespace reactor +#include "FrameManager.hpp" + +#include +#include + +namespace reactor { + +FrameManager::FrameManager(vk::Device device, Allocator& allocator, uint32_t commandQueueFamilyIndex, size_t maxFramesInFlight, uint32_t swapchainImageCount) + : m_device(device), m_currentFrame(0), m_framesInFlightCount(maxFramesInFlight) +{ + spdlog::info("Creating FrameManager with {} frames in flight", maxFramesInFlight); + + vk::CommandPoolCreateInfo poolInfo{ + vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + commandQueueFamilyIndex + }; + m_commandPool = m_device.createCommandPool(poolInfo); + + // Allocate per-frame resources + m_frames.resize(maxFramesInFlight); + for (auto& frame : m_frames) { + vk::CommandBufferAllocateInfo allocInfo{ + m_commandPool, + vk::CommandBufferLevel::ePrimary, + 1 + }; + frame.commandBuffer = m_device.allocateCommandBuffers(allocInfo)[0]; + vk::FenceCreateInfo fenceInfo{vk::FenceCreateFlagBits::eSignaled}; + frame.inFlightFence = m_device.createFence(fenceInfo); + + // create a uniform buffer + frame.uniformBuffer = std::make_unique(allocator, 1024, vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_ONLY); + } + + vk::SemaphoreCreateInfo semaphoreInfo{}; + + m_imageAvailableSemaphores.resize(maxFramesInFlight); + for (size_t i = 0; i < maxFramesInFlight; i++) { + m_imageAvailableSemaphores[i] = m_device.createSemaphore(semaphoreInfo); + } + + m_renderFinishedSemaphores.resize(swapchainImageCount); + for (size_t i = 0; i < swapchainImageCount; i++) { + m_renderFinishedSemaphores[i] = m_device.createSemaphore(semaphoreInfo); + } + + m_imagesInFlight.resize(swapchainImageCount, VK_NULL_HANDLE); + +} + +FrameManager::~FrameManager() { + spdlog::info("Destroying FrameManager and cleaning up resources."); + + for (auto& semaphore : m_imageAvailableSemaphores) { + m_device.destroySemaphore(semaphore); + } + + for (auto& semaphore : m_renderFinishedSemaphores) { + m_device.destroySemaphore(semaphore); + } + + for (auto& frame : m_frames) { + if (frame.inFlightFence) { + m_device.destroyFence(frame.inFlightFence); + } + } + + if (m_commandPool) { + m_device.destroyCommandPool(m_commandPool); + } +} + +bool FrameManager::beginFrame(vk::SwapchainKHR swapchain, uint32_t& outImageIndex) { + vk::Result result = m_device.waitForFences(m_frames[m_currentFrame].inFlightFence, VK_TRUE, UINT64_MAX); + if (result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to wait for fence!"); + } + + auto resultValue = m_device.acquireNextImageKHR( + swapchain, + UINT64_MAX, + m_imageAvailableSemaphores[m_currentFrame], + VK_NULL_HANDLE + ); + + if (resultValue.result == vk::Result::eErrorOutOfDateKHR) { + return false; + } + if (resultValue.result != vk::Result::eSuccess && resultValue.result != vk::Result::eSuboptimalKHR) { + throw std::runtime_error("Failed to acquire swapchain image!"); + } + + outImageIndex = resultValue.value; + + if (m_imagesInFlight[outImageIndex] != VK_NULL_HANDLE) { + m_device.waitForFences(m_imagesInFlight[outImageIndex], VK_TRUE, UINT64_MAX); + } + + // Associate the current frame's fence with the acquired swapchain image + m_imagesInFlight[outImageIndex] = m_frames[m_currentFrame].inFlightFence; + + m_device.resetFences(m_frames[m_currentFrame].inFlightFence); + + return true; +} + +void FrameManager::endFrame(vk::Queue graphicsQueue, vk::Queue presentQueue, vk::SwapchainKHR swapchain, uint32_t imageIndex) { + Frame& frame = m_frames[m_currentFrame]; + + vk::PipelineStageFlags waitStages = vk::PipelineStageFlagBits::eColorAttachmentOutput; + + vk::SubmitInfo submitInfo{ + 1, &m_imageAvailableSemaphores[m_currentFrame], + &waitStages, + 1, &frame.commandBuffer, + 1, &m_renderFinishedSemaphores[imageIndex] + }; + + graphicsQueue.submit(submitInfo, frame.inFlightFence); + + vk::PresentInfoKHR presentInfo{ + 1, &m_renderFinishedSemaphores[imageIndex], + 1, &swapchain, + &imageIndex + }; + + vk::Result result = presentQueue.presentKHR(presentInfo); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR) { + // Handle swapchain recreation here + } else if (result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to present swapchain image!"); + } + + // Advance to next frame + m_currentFrame = (m_currentFrame + 1) % m_frames.size(); +} + +} // namespace reactor diff --git a/src/vulkan/FrameManager.hpp b/src/vulkan/FrameManager.hpp index 02c540d..274a3dd 100644 --- a/src/vulkan/FrameManager.hpp +++ b/src/vulkan/FrameManager.hpp @@ -1,52 +1,52 @@ -// -// Created by rfdic on 6/9/2025. -// - -#ifndef FRAMEMANAGER_HPP -#define FRAMEMANAGER_HPP - -#include - -#include "Buffer.hpp" - -namespace reactor { - // Using the Frame struct from our previous discussion - struct Frame { - vk::CommandBuffer commandBuffer; - vk::Fence inFlightFence; - vk::DescriptorSet cameraDescriptorSet; - std::unique_ptr uniformBuffer; - }; - - class FrameManager { - public: - FrameManager( - vk::Device device, - Allocator& allocator, - uint32_t commandQueueFamilyIndex, - size_t maxFramesInFlight, - uint32_t swapchainImageCount); - - ~FrameManager(); - - // Methods to orchestrate the frame lifecycle - bool beginFrame(vk::SwapchainKHR swapchain, uint32_t& outImageIndex); // Handles wait/acquire - void endFrame(vk::Queue graphicsQueue, vk::Queue presentQueue, vk::SwapchainKHR swapchain, uint32_t imageIndex); // Handles submit/present - - Frame& getCurrentFrame() { return m_frames[m_currentFrame]; } - size_t getFrameIndex() const { return m_currentFrame; } - size_t getFramesInFlightCount() const { return m_framesInFlightCount; } - size_t getCurrentFrameIndex() const { return m_currentFrame; } - - private: - vk::Device m_device; - vk::CommandPool m_commandPool; - std::vector m_frames; - std::vector m_imageAvailableSemaphores; - std::vector m_renderFinishedSemaphores; - std::vector m_imagesInFlight; - size_t m_currentFrame; - size_t m_framesInFlightCount; - }; -} -#endif //FRAMEMANAGER_HPP +// +// Created by rfdic on 6/9/2025. +// + +#ifndef FRAMEMANAGER_HPP +#define FRAMEMANAGER_HPP + +#include + +#include "Buffer.hpp" + +namespace reactor { + // Using the Frame struct from our previous discussion + struct Frame { + vk::CommandBuffer commandBuffer; + vk::Fence inFlightFence; + vk::DescriptorSet cameraDescriptorSet; + std::unique_ptr uniformBuffer; + }; + + class FrameManager { + public: + FrameManager( + vk::Device device, + Allocator& allocator, + uint32_t commandQueueFamilyIndex, + size_t maxFramesInFlight, + uint32_t swapchainImageCount); + + ~FrameManager(); + + // Methods to orchestrate the frame lifecycle + bool beginFrame(vk::SwapchainKHR swapchain, uint32_t& outImageIndex); // Handles wait/acquire + void endFrame(vk::Queue graphicsQueue, vk::Queue presentQueue, vk::SwapchainKHR swapchain, uint32_t imageIndex); // Handles submit/present + + Frame& getCurrentFrame() { return m_frames[m_currentFrame]; } + size_t getFrameIndex() const { return m_currentFrame; } + size_t getFramesInFlightCount() const { return m_framesInFlightCount; } + size_t getCurrentFrameIndex() const { return m_currentFrame; } + + private: + vk::Device m_device; + vk::CommandPool m_commandPool; + std::vector m_frames; + std::vector m_imageAvailableSemaphores; + std::vector m_renderFinishedSemaphores; + std::vector m_imagesInFlight; + size_t m_currentFrame; + size_t m_framesInFlightCount; + }; +} +#endif //FRAMEMANAGER_HPP diff --git a/src/vulkan/Image.cpp b/src/vulkan/Image.cpp index 2cf5e30..d9302d0 100644 --- a/src/vulkan/Image.cpp +++ b/src/vulkan/Image.cpp @@ -1,69 +1,69 @@ -// -// Created by rfdic on 6/17/2025. -// - -#include "Image.hpp" - -namespace reactor { - - Image::Image(Allocator &allocator, const vk::ImageCreateInfo &imageInfo, VmaMemoryUsage memoryUsage) - : m_allocator(allocator) { - VmaAllocationCreateInfo allocInfo = {}; - allocInfo.usage = memoryUsage; - - const VkResult result = vmaCreateImage( - m_allocator.getAllocator(), - reinterpret_cast(&imageInfo), - &allocInfo, - reinterpret_cast(&m_image), - &m_allocation, - nullptr); - - if (result != VK_SUCCESS) { - throw std::runtime_error("Failed to create image!"); - } - } - - Image::~Image() { - cleanup(); - } - - Image::Image(Image&& other) noexcept - : m_allocator(other.m_allocator), // Initialize reference in move constructor - m_image(other.m_image), - m_allocation(other.m_allocation) - { - other.m_image = VK_NULL_HANDLE; - other.m_allocation = VK_NULL_HANDLE; - } - - Image& Image::operator=(Image&& other) noexcept { - if (this != &other) { - // First, clean up the resources currently held by *this* object. - cleanup(); - - // Transfer ownership of resources from 'other' to 'this'. - m_image = other.m_image; - m_allocation = other.m_allocation; - - // Invalidate 'other' to prevent it from cleaning up the moved resources. - other.m_image = VK_NULL_HANDLE; - other.m_allocation = VK_NULL_HANDLE; - - // **DO NOT** attempt to assign to m_allocator. - // m_allocator is a reference and must remain bound to its original Allocator instance. - // It was already initialized in the constructor and cannot be changed. - } - return *this; - } - - void Image::cleanup() { - if (m_image && m_allocation) { - vmaDestroyImage(m_allocator.getAllocator(), m_image, m_allocation); - spdlog::info("Image destroyed"); - m_image = VK_NULL_HANDLE; - m_allocation = VK_NULL_HANDLE; - } - } - +// +// Created by rfdic on 6/17/2025. +// + +#include "Image.hpp" + +namespace reactor { + + Image::Image(Allocator &allocator, const vk::ImageCreateInfo &imageInfo, VmaMemoryUsage memoryUsage) + : m_allocator(allocator) { + VmaAllocationCreateInfo allocInfo = {}; + allocInfo.usage = memoryUsage; + + const VkResult result = vmaCreateImage( + m_allocator.getAllocator(), + reinterpret_cast(&imageInfo), + &allocInfo, + reinterpret_cast(&m_image), + &m_allocation, + nullptr); + + if (result != VK_SUCCESS) { + throw std::runtime_error("Failed to create image!"); + } + } + + Image::~Image() { + cleanup(); + } + + Image::Image(Image&& other) noexcept + : m_allocator(other.m_allocator), // Initialize reference in move constructor + m_image(other.m_image), + m_allocation(other.m_allocation) + { + other.m_image = VK_NULL_HANDLE; + other.m_allocation = VK_NULL_HANDLE; + } + + Image& Image::operator=(Image&& other) noexcept { + if (this != &other) { + // First, clean up the resources currently held by *this* object. + cleanup(); + + // Transfer ownership of resources from 'other' to 'this'. + m_image = other.m_image; + m_allocation = other.m_allocation; + + // Invalidate 'other' to prevent it from cleaning up the moved resources. + other.m_image = VK_NULL_HANDLE; + other.m_allocation = VK_NULL_HANDLE; + + // **DO NOT** attempt to assign to m_allocator. + // m_allocator is a reference and must remain bound to its original Allocator instance. + // It was already initialized in the constructor and cannot be changed. + } + return *this; + } + + void Image::cleanup() { + if (m_image && m_allocation) { + vmaDestroyImage(m_allocator.getAllocator(), m_image, m_allocation); + spdlog::info("Image destroyed"); + m_image = VK_NULL_HANDLE; + m_allocation = VK_NULL_HANDLE; + } + } + } // reactor \ No newline at end of file diff --git a/src/vulkan/Image.hpp b/src/vulkan/Image.hpp index e2ef554..6908acd 100644 --- a/src/vulkan/Image.hpp +++ b/src/vulkan/Image.hpp @@ -1,33 +1,33 @@ -#pragma once - -#include "Allocator.hpp" -#include -#include - -namespace reactor { - - class Image { - public: - Image(Allocator& allocator, - const vk::ImageCreateInfo& imageInfo, - VmaMemoryUsage memoryUsage); - - ~Image(); - - // Prevent copying and assignment - Image(const Image&) = delete; - Image& operator=(const Image&) = delete; - // Allow moving - Image(Image&& other) noexcept; - Image& operator=(Image&& other) noexcept; - - vk::Image get() const { return m_image; } - - private: - Allocator& m_allocator; - vk::Image m_image = VK_NULL_HANDLE; - VmaAllocation m_allocation = VK_NULL_HANDLE; - - void cleanup(); - }; +#pragma once + +#include "Allocator.hpp" +#include +#include + +namespace reactor { + + class Image { + public: + Image(Allocator& allocator, + const vk::ImageCreateInfo& imageInfo, + VmaMemoryUsage memoryUsage); + + ~Image(); + + // Prevent copying and assignment + Image(const Image&) = delete; + Image& operator=(const Image&) = delete; + // Allow moving + Image(Image&& other) noexcept; + Image& operator=(Image&& other) noexcept; + + vk::Image get() const { return m_image; } + + private: + Allocator& m_allocator; + vk::Image m_image = VK_NULL_HANDLE; + VmaAllocation m_allocation = VK_NULL_HANDLE; + + void cleanup(); + }; } \ No newline at end of file diff --git a/src/vulkan/ImageStateTracker.cpp b/src/vulkan/ImageStateTracker.cpp index 196efff..b8261c4 100644 --- a/src/vulkan/ImageStateTracker.cpp +++ b/src/vulkan/ImageStateTracker.cpp @@ -1,68 +1,68 @@ -// -// Created by rfdic on 6/29/2025. -// - -#include "ImageStateTracker.h" -#include - -namespace reactor { - -void ImageStateTracker::recordState(vk::Image image, vk::ImageLayout initialState) { - m_imageStates[image] = initialState; -} - -vk::ImageLayout ImageStateTracker::getCurrentLayout(vk::Image image) const { - auto it = m_imageStates.find(image); - if (it == m_imageStates.end()) { - // Or you could default to eUndefined and record it, depending on desired strictness - throw std::runtime_error("Attempted to get layout of an untracked image."); - } - return it->second; -} - -void ImageStateTracker::transition( - vk::CommandBuffer cmd, - vk::Image image, - vk::ImageLayout newLayout, - vk::PipelineStageFlags srcStage, - vk::PipelineStageFlags dstStage, - vk::AccessFlags srcAccess, - vk::AccessFlags dstAccess, - vk::ImageAspectFlags aspectFlags -) { - const vk::ImageLayout oldLayout = getCurrentLayout(image); - - // If the layout is already correct, we don't need a barrier. - if (oldLayout == newLayout) { - return; - } - - vk::ImageMemoryBarrier barrier{}; - barrier.oldLayout = oldLayout; - barrier.newLayout = newLayout; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = image; - barrier.subresourceRange.aspectMask = aspectFlags; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = 1; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - barrier.srcAccessMask = srcAccess; - barrier.dstAccessMask = dstAccess; - - cmd.pipelineBarrier( - srcStage, // Stage where operations occurred before the barrier - dstStage, // Stage where operations will wait for the barrier - {}, // Dependency flags - nullptr, // Memory barriers - nullptr, // Buffer memory barriers - barrier // Image memory barriers - ); - - // IMPORTANT: Update the internal state to the new layout. - m_imageStates[image] = newLayout; -} - - +// +// Created by rfdic on 6/29/2025. +// + +#include "ImageStateTracker.h" +#include + +namespace reactor { + +void ImageStateTracker::recordState(vk::Image image, vk::ImageLayout initialState) { + m_imageStates[image] = initialState; +} + +vk::ImageLayout ImageStateTracker::getCurrentLayout(vk::Image image) const { + auto it = m_imageStates.find(image); + if (it == m_imageStates.end()) { + // Or you could default to eUndefined and record it, depending on desired strictness + throw std::runtime_error("Attempted to get layout of an untracked image."); + } + return it->second; +} + +void ImageStateTracker::transition( + vk::CommandBuffer cmd, + vk::Image image, + vk::ImageLayout newLayout, + vk::PipelineStageFlags srcStage, + vk::PipelineStageFlags dstStage, + vk::AccessFlags srcAccess, + vk::AccessFlags dstAccess, + vk::ImageAspectFlags aspectFlags +) { + const vk::ImageLayout oldLayout = getCurrentLayout(image); + + // If the layout is already correct, we don't need a barrier. + if (oldLayout == newLayout) { + return; + } + + vk::ImageMemoryBarrier barrier{}; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = aspectFlags; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.srcAccessMask = srcAccess; + barrier.dstAccessMask = dstAccess; + + cmd.pipelineBarrier( + srcStage, // Stage where operations occurred before the barrier + dstStage, // Stage where operations will wait for the barrier + {}, // Dependency flags + nullptr, // Memory barriers + nullptr, // Buffer memory barriers + barrier // Image memory barriers + ); + + // IMPORTANT: Update the internal state to the new layout. + m_imageStates[image] = newLayout; +} + + } // reactor \ No newline at end of file diff --git a/src/vulkan/ImageStateTracker.h b/src/vulkan/ImageStateTracker.h index 9977bcd..0fffd9f 100644 --- a/src/vulkan/ImageStateTracker.h +++ b/src/vulkan/ImageStateTracker.h @@ -1,31 +1,31 @@ -#pragma once - -#include -#include - -namespace reactor { - -class ImageStateTracker { -public: - ImageStateTracker() = default; - - void recordState(vk::Image image, vk::ImageLayout initialState = vk::ImageLayout::eUndefined); - - void transition( - vk::CommandBuffer cmd, - vk::Image image, - vk::ImageLayout newLayout, - vk::PipelineStageFlags srcStage, - vk::PipelineStageFlags dstStage, - vk::AccessFlags srcAccess, - vk::AccessFlags dstAccess, - vk::ImageAspectFlags aspectFlags = vk::ImageAspectFlagBits::eColor); - - vk::ImageLayout getCurrentLayout(vk::Image image) const; - -private: - std::map m_imageStates; -}; - -} // namespace reactor - +#pragma once + +#include +#include + +namespace reactor { + +class ImageStateTracker { +public: + ImageStateTracker() = default; + + void recordState(vk::Image image, vk::ImageLayout initialState = vk::ImageLayout::eUndefined); + + void transition( + vk::CommandBuffer cmd, + vk::Image image, + vk::ImageLayout newLayout, + vk::PipelineStageFlags srcStage, + vk::PipelineStageFlags dstStage, + vk::AccessFlags srcAccess, + vk::AccessFlags dstAccess, + vk::ImageAspectFlags aspectFlags = vk::ImageAspectFlagBits::eColor); + + vk::ImageLayout getCurrentLayout(vk::Image image) const; + +private: + std::map m_imageStates; +}; + +} // namespace reactor + diff --git a/src/vulkan/ImageUtils.cpp b/src/vulkan/ImageUtils.cpp index 3014158..6acade6 100644 --- a/src/vulkan/ImageUtils.cpp +++ b/src/vulkan/ImageUtils.cpp @@ -1,61 +1,61 @@ -#include "ImageUtils.hpp" - -namespace reactor::utils -{ - -ImageBuilder::ImageBuilder(const vk::Device& device, Allocator& allocator, const vk::Extent2D& defaultExtent) - : m_device(device), m_allocator(allocator), m_extent(defaultExtent) -{ - m_imageInfo.imageType = vk::ImageType::e2D; - m_imageInfo.extent = vk::Extent3D{m_extent.width, m_extent.height, 1}; - m_imageInfo.mipLevels = 1; - m_imageInfo.arrayLayers = 1; - m_imageInfo.tiling = vk::ImageTiling::eOptimal; - m_imageInfo.initialLayout = vk::ImageLayout::eUndefined; - m_imageInfo.sharingMode = vk::SharingMode::eExclusive; - m_imageInfo.samples = vk::SampleCountFlagBits::e1; - - m_viewInfo.viewType = vk::ImageViewType::e2D; - m_viewInfo.subresourceRange.baseMipLevel = 0; - m_viewInfo.subresourceRange.levelCount = 1; - m_viewInfo.subresourceRange.baseArrayLayer = 0; - m_viewInfo.subresourceRange.layerCount = 1; - m_viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; -} - -ImageBuilder& ImageBuilder::setFormat(vk::Format format) -{ - m_imageInfo.format = format; - m_viewInfo.format = format; - return *this; -} - -ImageBuilder& ImageBuilder::setUsage(vk::ImageUsageFlags usage) -{ - m_imageInfo.usage = usage; - return *this; -} - -ImageBuilder& ImageBuilder::setSamples(vk::SampleCountFlagBits samples) -{ - m_imageInfo.samples = samples; - return *this; -} - -ImageBuilder& ImageBuilder::setAspectMask(vk::ImageAspectFlags aspect) -{ - m_viewInfo.subresourceRange.aspectMask = aspect; - return *this; -} - -ImageBuilder::BuiltImage ImageBuilder::build() -{ - auto image = std::make_unique(m_allocator, m_imageInfo, VMA_MEMORY_USAGE_GPU_ONLY); - - m_viewInfo.image = image->get(); - vk::ImageView view = m_device.createImageView(m_viewInfo); - - return {.image = std::move(image), .view = view}; -} - +#include "ImageUtils.hpp" + +namespace reactor::utils +{ + +ImageBuilder::ImageBuilder(const vk::Device& device, Allocator& allocator, const vk::Extent2D& defaultExtent) + : m_device(device), m_allocator(allocator), m_extent(defaultExtent) +{ + m_imageInfo.imageType = vk::ImageType::e2D; + m_imageInfo.extent = vk::Extent3D{m_extent.width, m_extent.height, 1}; + m_imageInfo.mipLevels = 1; + m_imageInfo.arrayLayers = 1; + m_imageInfo.tiling = vk::ImageTiling::eOptimal; + m_imageInfo.initialLayout = vk::ImageLayout::eUndefined; + m_imageInfo.sharingMode = vk::SharingMode::eExclusive; + m_imageInfo.samples = vk::SampleCountFlagBits::e1; + + m_viewInfo.viewType = vk::ImageViewType::e2D; + m_viewInfo.subresourceRange.baseMipLevel = 0; + m_viewInfo.subresourceRange.levelCount = 1; + m_viewInfo.subresourceRange.baseArrayLayer = 0; + m_viewInfo.subresourceRange.layerCount = 1; + m_viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; +} + +ImageBuilder& ImageBuilder::setFormat(vk::Format format) +{ + m_imageInfo.format = format; + m_viewInfo.format = format; + return *this; +} + +ImageBuilder& ImageBuilder::setUsage(vk::ImageUsageFlags usage) +{ + m_imageInfo.usage = usage; + return *this; +} + +ImageBuilder& ImageBuilder::setSamples(vk::SampleCountFlagBits samples) +{ + m_imageInfo.samples = samples; + return *this; +} + +ImageBuilder& ImageBuilder::setAspectMask(vk::ImageAspectFlags aspect) +{ + m_viewInfo.subresourceRange.aspectMask = aspect; + return *this; +} + +ImageBuilder::BuiltImage ImageBuilder::build() +{ + auto image = std::make_unique(m_allocator, m_imageInfo, VMA_MEMORY_USAGE_GPU_ONLY); + + m_viewInfo.image = image->get(); + vk::ImageView view = m_device.createImageView(m_viewInfo); + + return {.image = std::move(image), .view = view}; +} + } // namespace reactor::utils \ No newline at end of file diff --git a/src/vulkan/ImageUtils.hpp b/src/vulkan/ImageUtils.hpp index 9206306..9b8cef6 100644 --- a/src/vulkan/ImageUtils.hpp +++ b/src/vulkan/ImageUtils.hpp @@ -1,36 +1,36 @@ -#pragma once - -#include -#include - -#include "Image.hpp" - -namespace reactor::utils -{ -class ImageBuilder -{ -public: - ImageBuilder(const vk::Device& device, Allocator& allocator, const vk::Extent2D& defaultExtent); - - ImageBuilder& setFormat(vk::Format); - ImageBuilder& setUsage(vk::ImageUsageFlags usage); - ImageBuilder& setSamples(vk::SampleCountFlagBits samples); - ImageBuilder& setAspectMask(vk::ImageAspectFlags aspect); - - struct BuiltImage - { - std::unique_ptr image; - vk::ImageView view; - }; - - BuiltImage build(); - -private: - const vk::Device& m_device; - Allocator& m_allocator; - vk::Extent2D m_extent; - - vk::ImageCreateInfo m_imageInfo{}; - vk::ImageViewCreateInfo m_viewInfo{}; -}; +#pragma once + +#include +#include + +#include "Image.hpp" + +namespace reactor::utils +{ +class ImageBuilder +{ +public: + ImageBuilder(const vk::Device& device, Allocator& allocator, const vk::Extent2D& defaultExtent); + + ImageBuilder& setFormat(vk::Format); + ImageBuilder& setUsage(vk::ImageUsageFlags usage); + ImageBuilder& setSamples(vk::SampleCountFlagBits samples); + ImageBuilder& setAspectMask(vk::ImageAspectFlags aspect); + + struct BuiltImage + { + std::unique_ptr image; + vk::ImageView view; + }; + + BuiltImage build(); + +private: + const vk::Device& m_device; + Allocator& m_allocator; + vk::Extent2D m_extent; + + vk::ImageCreateInfo m_imageInfo{}; + vk::ImageViewCreateInfo m_viewInfo{}; +}; } // namespace reactor::utils \ No newline at end of file diff --git a/src/vulkan/Mesh.cpp b/src/vulkan/Mesh.cpp index ee1b3e3..7288358 100644 --- a/src/vulkan/Mesh.cpp +++ b/src/vulkan/Mesh.cpp @@ -1,102 +1,102 @@ -#include "Mesh.hpp" -#include // For memcpy - -#include "Allocator.hpp" - -namespace reactor { - -// Forward declaration of a helper function to perform the copy. -// This keeps the constructor clean. -// Helper function for one-time command submission. -// This could be placed in a utility file or within the Allocator class. -void immediateSubmit( - Allocator& allocator, - std::function&& function) -{ - vk::Device device = allocator.getDevice(); - vk::Queue graphicsQueue = allocator.getGraphicsQueue(); - uint32_t queueFamilyIndex = allocator.getGraphicsQueueFamilyIndex(); - - // Create a temporary command pool and buffer - vk::CommandPoolCreateInfo poolInfo(vk::CommandPoolCreateFlagBits::eTransient, queueFamilyIndex); - vk::CommandPool cmdPool = device.createCommandPool(poolInfo); - - vk::CommandBufferAllocateInfo allocInfo(cmdPool, vk::CommandBufferLevel::ePrimary, 1); - vk::CommandBuffer cmd = device.allocateCommandBuffers(allocInfo)[0]; - - // Record and submit commands - vk::CommandBufferBeginInfo beginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit); - cmd.begin(beginInfo); - - function(cmd); // Execute the provided command recording function - - cmd.end(); - - vk::SubmitInfo submitInfo(0, nullptr, nullptr, 1, &cmd); - graphicsQueue.submit(submitInfo, nullptr); - graphicsQueue.waitIdle(); // Wait for the operation to complete - - // Clean up temporary resources - device.freeCommandBuffers(cmdPool, cmd); - device.destroyCommandPool(cmdPool); -} - -Mesh::Mesh(Allocator& allocator, const std::vector& vertices, const std::vector& indices) { - vk::DeviceSize vertexSize = vertices.size() * sizeof(Vertex); - vk::DeviceSize indexSize = indices.size() * sizeof(uint32_t); - m_indexCount = static_cast(indices.size()); - - // Staging buffers (CPU-visible) - Buffer stagingVertex(allocator, vertexSize, vk::BufferUsageFlagBits::eTransferSrc, VMA_MEMORY_USAGE_CPU_TO_GPU, "Staging Vertex"); - void* data; - vmaMapMemory(allocator.getAllocator(), stagingVertex.allocation(), &data); - memcpy(data, vertices.data(), static_cast(vertexSize)); - vmaUnmapMemory(allocator.getAllocator(), stagingVertex.allocation()); - - Buffer stagingIndex(allocator, indexSize, vk::BufferUsageFlagBits::eTransferSrc, VMA_MEMORY_USAGE_CPU_TO_GPU, "Staging Index"); - vmaMapMemory(allocator.getAllocator(), stagingIndex.allocation(), &data); - memcpy(data, indices.data(), static_cast(indexSize)); - vmaUnmapMemory(allocator.getAllocator(), stagingIndex.allocation()); - - // GPU buffers (device-local) - m_vertexBuffer = std::make_unique(allocator, vertexSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, VMA_MEMORY_USAGE_GPU_ONLY, "Vertex Buffer"); - m_indexBuffer = std::make_unique(allocator, indexSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, VMA_MEMORY_USAGE_GPU_ONLY, "Index Buffer"); - - - // One-time transfer from staging to device-local buffers - immediateSubmit(allocator, [&](vk::CommandBuffer cmd) { - vk::BufferCopy vertexCopyRegion{}; - vertexCopyRegion.srcOffset = 0; - vertexCopyRegion.dstOffset = 0; - vertexCopyRegion.size = vertexSize; - cmd.copyBuffer(stagingVertex.getHandle(), m_vertexBuffer->getHandle(), 1, &vertexCopyRegion); - - vk::BufferCopy indexCopyRegion{}; - indexCopyRegion.srcOffset = 0; - indexCopyRegion.dstOffset = 0; - indexCopyRegion.size = indexSize; - cmd.copyBuffer(stagingIndex.getHandle(), m_indexBuffer->getHandle(), 1, &indexCopyRegion); - }); - - -} - - -Mesh::Mesh(Mesh&& other) noexcept - : m_vertexBuffer(std::move(other.m_vertexBuffer)), - m_indexBuffer(std::move(other.m_indexBuffer)), - m_indexCount(other.m_indexCount) { - other.m_indexCount = 0; -} - -Mesh& Mesh::operator=(Mesh&& other) noexcept { - if (this != &other) { - m_vertexBuffer = std::move(other.m_vertexBuffer); - m_indexBuffer = std::move(other.m_indexBuffer); - m_indexCount = other.m_indexCount; - other.m_indexCount = 0; - } - return *this; -} - +#include "Mesh.hpp" +#include // For memcpy + +#include "Allocator.hpp" + +namespace reactor { + +// Forward declaration of a helper function to perform the copy. +// This keeps the constructor clean. +// Helper function for one-time command submission. +// This could be placed in a utility file or within the Allocator class. +void immediateSubmit( + Allocator& allocator, + std::function&& function) +{ + vk::Device device = allocator.getDevice(); + vk::Queue graphicsQueue = allocator.getGraphicsQueue(); + uint32_t queueFamilyIndex = allocator.getGraphicsQueueFamilyIndex(); + + // Create a temporary command pool and buffer + vk::CommandPoolCreateInfo poolInfo(vk::CommandPoolCreateFlagBits::eTransient, queueFamilyIndex); + vk::CommandPool cmdPool = device.createCommandPool(poolInfo); + + vk::CommandBufferAllocateInfo allocInfo(cmdPool, vk::CommandBufferLevel::ePrimary, 1); + vk::CommandBuffer cmd = device.allocateCommandBuffers(allocInfo)[0]; + + // Record and submit commands + vk::CommandBufferBeginInfo beginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit); + cmd.begin(beginInfo); + + function(cmd); // Execute the provided command recording function + + cmd.end(); + + vk::SubmitInfo submitInfo(0, nullptr, nullptr, 1, &cmd); + graphicsQueue.submit(submitInfo, nullptr); + graphicsQueue.waitIdle(); // Wait for the operation to complete + + // Clean up temporary resources + device.freeCommandBuffers(cmdPool, cmd); + device.destroyCommandPool(cmdPool); +} + +Mesh::Mesh(Allocator& allocator, const std::vector& vertices, const std::vector& indices) { + vk::DeviceSize vertexSize = vertices.size() * sizeof(Vertex); + vk::DeviceSize indexSize = indices.size() * sizeof(uint32_t); + m_indexCount = static_cast(indices.size()); + + // Staging buffers (CPU-visible) + Buffer stagingVertex(allocator, vertexSize, vk::BufferUsageFlagBits::eTransferSrc, VMA_MEMORY_USAGE_CPU_TO_GPU, "Staging Vertex"); + void* data; + vmaMapMemory(allocator.getAllocator(), stagingVertex.allocation(), &data); + memcpy(data, vertices.data(), static_cast(vertexSize)); + vmaUnmapMemory(allocator.getAllocator(), stagingVertex.allocation()); + + Buffer stagingIndex(allocator, indexSize, vk::BufferUsageFlagBits::eTransferSrc, VMA_MEMORY_USAGE_CPU_TO_GPU, "Staging Index"); + vmaMapMemory(allocator.getAllocator(), stagingIndex.allocation(), &data); + memcpy(data, indices.data(), static_cast(indexSize)); + vmaUnmapMemory(allocator.getAllocator(), stagingIndex.allocation()); + + // GPU buffers (device-local) + m_vertexBuffer = std::make_unique(allocator, vertexSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, VMA_MEMORY_USAGE_GPU_ONLY, "Vertex Buffer"); + m_indexBuffer = std::make_unique(allocator, indexSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, VMA_MEMORY_USAGE_GPU_ONLY, "Index Buffer"); + + + // One-time transfer from staging to device-local buffers + immediateSubmit(allocator, [&](vk::CommandBuffer cmd) { + vk::BufferCopy vertexCopyRegion{}; + vertexCopyRegion.srcOffset = 0; + vertexCopyRegion.dstOffset = 0; + vertexCopyRegion.size = vertexSize; + cmd.copyBuffer(stagingVertex.getHandle(), m_vertexBuffer->getHandle(), 1, &vertexCopyRegion); + + vk::BufferCopy indexCopyRegion{}; + indexCopyRegion.srcOffset = 0; + indexCopyRegion.dstOffset = 0; + indexCopyRegion.size = indexSize; + cmd.copyBuffer(stagingIndex.getHandle(), m_indexBuffer->getHandle(), 1, &indexCopyRegion); + }); + + +} + + +Mesh::Mesh(Mesh&& other) noexcept + : m_vertexBuffer(std::move(other.m_vertexBuffer)), + m_indexBuffer(std::move(other.m_indexBuffer)), + m_indexCount(other.m_indexCount) { + other.m_indexCount = 0; +} + +Mesh& Mesh::operator=(Mesh&& other) noexcept { + if (this != &other) { + m_vertexBuffer = std::move(other.m_vertexBuffer); + m_indexBuffer = std::move(other.m_indexBuffer); + m_indexCount = other.m_indexCount; + other.m_indexCount = 0; + } + return *this; +} + } // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Mesh.hpp b/src/vulkan/Mesh.hpp index 66041e3..869f38a 100644 --- a/src/vulkan/Mesh.hpp +++ b/src/vulkan/Mesh.hpp @@ -1,46 +1,46 @@ -#pragma once - -#include "Buffer.hpp" -#include "Vertex.hpp" -#include - -#include - -namespace reactor -{ - -class Mesh -{ -public: - Mesh(Allocator& allocator, const std::vector& vertices, const std::vector& indices); - - // Movable but not copyable (due to Buffer) - Mesh(Mesh&& other) noexcept; - Mesh& operator=(Mesh&& other) noexcept; - Mesh(const Mesh&) = delete; - Mesh& operator=(const Mesh&) = delete; - - ~Mesh() = default; - - vk::Buffer getVertexBuffer() const - { - return m_vertexBuffer->getHandle(); - } - vk::Buffer getIndexBuffer() const - { - return m_indexBuffer->getHandle(); - } - uint32_t getIndexCount() const - { - return m_indexCount; - } - -private: - std::unique_ptr m_vertexBuffer; - std::unique_ptr m_indexBuffer; - uint32_t m_indexCount = 0; - - void createBuffer(Allocator& allocator, const void* data, vk::DeviceSize size, vk::BufferUsageFlags usage, std::unique_ptr& buffer, const std::string& name); -}; - -} // namespace reactor +#pragma once + +#include "Buffer.hpp" +#include "Vertex.hpp" +#include + +#include + +namespace reactor +{ + +class Mesh +{ +public: + Mesh(Allocator& allocator, const std::vector& vertices, const std::vector& indices); + + // Movable but not copyable (due to Buffer) + Mesh(Mesh&& other) noexcept; + Mesh& operator=(Mesh&& other) noexcept; + Mesh(const Mesh&) = delete; + Mesh& operator=(const Mesh&) = delete; + + ~Mesh() = default; + + vk::Buffer getVertexBuffer() const + { + return m_vertexBuffer->getHandle(); + } + vk::Buffer getIndexBuffer() const + { + return m_indexBuffer->getHandle(); + } + uint32_t getIndexCount() const + { + return m_indexCount; + } + +private: + std::unique_ptr m_vertexBuffer; + std::unique_ptr m_indexBuffer; + uint32_t m_indexCount = 0; + + void createBuffer(Allocator& allocator, const void* data, vk::DeviceSize size, vk::BufferUsageFlags usage, std::unique_ptr& buffer, const std::string& name); +}; + +} // namespace reactor diff --git a/src/vulkan/MeshGenerators.cpp b/src/vulkan/MeshGenerators.cpp index 01cfa9b..fbffd77 100644 --- a/src/vulkan/MeshGenerators.cpp +++ b/src/vulkan/MeshGenerators.cpp @@ -1,114 +1,114 @@ -#include "MeshGenerators.hpp" -#include "Vertex.hpp" - -namespace reactor -{ -std::vector generatePlaneVertices(int subdivisions, float size) -{ - std::vector vertices; - int rows = subdivisions + 1; - vertices.reserve(rows * rows); - for (int z = 0; z < rows; ++z) - { - for (int x = 0; x < rows; ++x) - { - float u = static_cast(x) / subdivisions; - float v = static_cast(z) / subdivisions; - Vertex vert{}; - vert.pos = glm::vec3((u - 0.5f) * size, 0.0f, (v - 0.5f) * size); - vert.normal = glm::vec3(0.0f, 1.0f, 0.0f); - vert.color = glm::vec3(0.2f, 0.8f, 0.2f); // Greenish ground - vert.texCoord = glm::vec2(u, v); - vertices.push_back(vert); - } - } - return vertices; -} - -std::vector generatePlaneIndices(int subdivisions) -{ - std::vector indices; - int rows = subdivisions + 1; - for (int z = 0; z < subdivisions; ++z) - { - for (int x = 0; x < subdivisions; ++x) - { - uint32_t bottomLeft = z * rows + x; - uint32_t bottomRight = bottomLeft + 1; - uint32_t topLeft = bottomLeft + rows; - uint32_t topRight = topLeft + 1; - indices.insert(indices.end(), {bottomLeft, topLeft, bottomRight, bottomRight, topLeft, topRight}); - } - } - return indices; -} - -std::vector generateUnitCubeVertices() -{ - // A cube has 6 faces, and each face has 4 vertices. - // To have sharp edges with distinct normals for each face, - // we need 24 vertices in total, not 8. - // The vertex order for each face is: - // Bottom-Left, Bottom-Right, Top-Right, Top-Left - return { - // Back face (-Z) - {{-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, - {{0.5f, -0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, - {{-0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, - - // Front face (+Z) - {{-0.5f, -0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, - {{0.5f, -0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, - {{-0.5f, 0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, - - // Left face (-X) - {{-0.5f, -0.5f, 0.5f}, {-1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, - {{-0.5f, -0.5f, -0.5f}, {-1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, - {{-0.5f, 0.5f, -0.5f}, {-1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, - {{-0.5f, 0.5f, 0.5f}, {-1.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, - - // Right face (+X) - {{0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, - {{0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f, 0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, - {{0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, - - // Bottom face (-Y) - {{-0.5f, -0.5f, -0.5f}, {0.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, - {{0.5f, -0.5f, -0.5f}, {0.0f, -1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, - {{0.5f, -0.5f, 0.5f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, - {{-0.5f, -0.5f, 0.5f}, {0.0f, -1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, - - // Top face (+Y) - {{-0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, - {{0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, - {{-0.5f, 0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, - }; -} - -std::vector generateUnitCubeIndices() -{ - // For each face, we define two triangles. - // The indices are arranged to have a CLOCKWISE winding order - // when viewed from outside the cube. This is necessary for - // back-face culling to work correctly. - // For a quad with vertices 0,1,2,3 (BL, BR, TR, TL), - // the triangles are (0, 1, 2) and (0, 2, 3). - return { - // Back - 0, 1, 2, 0, 2, 3, - // Front - 4, 5, 6, 4, 6, 7, - // Left - 8, 9, 10, 8, 10, 11, - // Right - 12, 13, 14, 12, 14, 15, - // Bottom - 16, 17, 18, 16, 18, 19, - // Top - 20, 21, 22, 20, 22, 23}; -} -} // namespace reactor +#include "MeshGenerators.hpp" +#include "Vertex.hpp" + +namespace reactor +{ +std::vector generatePlaneVertices(int subdivisions, float size) +{ + std::vector vertices; + int rows = subdivisions + 1; + vertices.reserve(rows * rows); + for (int z = 0; z < rows; ++z) + { + for (int x = 0; x < rows; ++x) + { + float u = static_cast(x) / subdivisions; + float v = static_cast(z) / subdivisions; + Vertex vert{}; + vert.pos = glm::vec3((u - 0.5f) * size, 0.0f, (v - 0.5f) * size); + vert.normal = glm::vec3(0.0f, 1.0f, 0.0f); + vert.color = glm::vec3(0.2f, 0.8f, 0.2f); // Greenish ground + vert.texCoord = glm::vec2(u, v); + vertices.push_back(vert); + } + } + return vertices; +} + +std::vector generatePlaneIndices(int subdivisions) +{ + std::vector indices; + int rows = subdivisions + 1; + for (int z = 0; z < subdivisions; ++z) + { + for (int x = 0; x < subdivisions; ++x) + { + uint32_t bottomLeft = z * rows + x; + uint32_t bottomRight = bottomLeft + 1; + uint32_t topLeft = bottomLeft + rows; + uint32_t topRight = topLeft + 1; + indices.insert(indices.end(), {bottomLeft, topLeft, bottomRight, bottomRight, topLeft, topRight}); + } + } + return indices; +} + +std::vector generateUnitCubeVertices() +{ + // A cube has 6 faces, and each face has 4 vertices. + // To have sharp edges with distinct normals for each face, + // we need 24 vertices in total, not 8. + // The vertex order for each face is: + // Bottom-Left, Bottom-Right, Top-Right, Top-Left + return { + // Back face (-Z) + {{-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, + + // Front face (+Z) + {{-0.5f, -0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, + + // Left face (-X) + {{-0.5f, -0.5f, 0.5f}, {-1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{-0.5f, -0.5f, -0.5f}, {-1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{-0.5f, 0.5f, -0.5f}, {-1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.5f}, {-1.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, + + // Right face (+X) + {{0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, 0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, + + // Bottom face (-Y) + {{-0.5f, -0.5f, -0.5f}, {0.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, -0.5f}, {0.0f, -1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, -0.5f, 0.5f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, -0.5f, 0.5f}, {0.0f, -1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, + + // Top face (+Y) + {{-0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, + }; +} + +std::vector generateUnitCubeIndices() +{ + // For each face, we define two triangles. + // The indices are arranged to have a CLOCKWISE winding order + // when viewed from outside the cube. This is necessary for + // back-face culling to work correctly. + // For a quad with vertices 0,1,2,3 (BL, BR, TR, TL), + // the triangles are (0, 1, 2) and (0, 2, 3). + return { + // Back + 0, 1, 2, 0, 2, 3, + // Front + 4, 5, 6, 4, 6, 7, + // Left + 8, 9, 10, 8, 10, 11, + // Right + 12, 13, 14, 12, 14, 15, + // Bottom + 16, 17, 18, 16, 18, 19, + // Top + 20, 21, 22, 20, 22, 23}; +} +} // namespace reactor diff --git a/src/vulkan/MeshGenerators.hpp b/src/vulkan/MeshGenerators.hpp index d807b48..415b727 100644 --- a/src/vulkan/MeshGenerators.hpp +++ b/src/vulkan/MeshGenerators.hpp @@ -1,12 +1,12 @@ -#pragma once - -#include "Vertex.hpp" - -namespace reactor -{ -std::vector generatePlaneVertices(int subdivisions, float size); -std::vector generatePlaneIndices(int subdivisions); -std::vector generateUnitCubeVertices(); -std::vector generateUnitCubeIndices(); - +#pragma once + +#include "Vertex.hpp" + +namespace reactor +{ +std::vector generatePlaneVertices(int subdivisions, float size); +std::vector generatePlaneIndices(int subdivisions); +std::vector generateUnitCubeVertices(); +std::vector generateUnitCubeIndices(); + } // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Pipeline.cpp b/src/vulkan/Pipeline.cpp index 1743582..42955d0 100644 --- a/src/vulkan/Pipeline.cpp +++ b/src/vulkan/Pipeline.cpp @@ -1,251 +1,251 @@ -#include "Pipeline.hpp" -#include -#include - -#include "ShaderModule.hpp" -#include "VulkanUtils.hpp" - -namespace reactor -{ - - static std::vector readFile(const std::string& filename) - { - std::ifstream file(filename, std::ios::ate | std::ios::binary); - if (!file.is_open()) - throw std::runtime_error("Failed to open shader file: " + filename); - size_t fileSize = (size_t)file.tellg(); - std::vector buffer(fileSize); - file.seekg(0); - file.read(buffer.data(), fileSize); - return buffer; - } - - Pipeline::Builder::Builder(vk::Device device) - : m_device(device) - {} - - Pipeline::Builder& Pipeline::Builder::setVertexShader(const std::string& shaderPath) - { - m_vertShaderPath = shaderPath; - return *this; - } - - Pipeline::Builder& Pipeline::Builder::setFragmentShader(const std::string& shaderPath) - { - m_fragShaderPath = shaderPath; - return *this; - } - - Pipeline::Builder& Pipeline::Builder::setColorAttachment(vk::Format format) - { - m_colorAttachmentFormat = format; - return *this; - } - - Pipeline::Builder& Pipeline::Builder::setDepthAttachment(vk::Format format, bool depthWriteEnable) - { - m_depthAttachmentFormat = format; - m_depthWriteEnable = depthWriteEnable; - return *this; - } - - Pipeline::Builder& - Pipeline::Builder::setDescriptorSetLayouts(const std::vector& layouts) - { - m_setLayouts = layouts; - return *this; - } - Pipeline::Builder& Pipeline::Builder::setVertexInputFromVertex() - { - m_bindings.push_back(Vertex::getBindingDescription()); - auto attrs = Vertex::getAttributeDescriptions(); - m_attributes.insert(m_attributes.end(), attrs.begin(), attrs.end()); - return *this; - } - - Pipeline::Builder& Pipeline::Builder::setMultisample(uint32_t samples) - { - m_samples = samples; - return *this; - } - - Pipeline::Builder& Pipeline::Builder::setCullMode(vk::CullModeFlags cullMode) - { - m_cullMode = cullMode; - return *this; - } - - Pipeline::Builder& Pipeline::Builder::setFrontFace(vk::FrontFace frontFace) - { - m_frontFace = frontFace; - return *this; - } - - Pipeline::Builder& Pipeline::Builder::addPushContantRange(vk::ShaderStageFlags stages, uint32_t offset, uint32_t size) - { - m_pushRanges.push_back({stages, offset, size}); - return *this; - } - - Pipeline::Builder& Pipeline::Builder::enableDepthBias(bool enable) - { - m_depthBiasEnable = enable; - return *this; - } - - - std::unique_ptr Pipeline::Builder::build() const - { - // 1. Shader Stages - auto vertShaderCode = readFile(m_vertShaderPath); - auto vertShaderModule = ShaderModule(m_device, vertShaderCode); - vk::PipelineShaderStageCreateInfo vertStageInfo({}, vk::ShaderStageFlagBits::eVertex, vertShaderModule.getHandle(), "main"); - - std::vector shaderStages = {vertStageInfo}; - std::unique_ptr fragShaderModule; - if (!m_fragShaderPath.empty()) - { - auto fragShaderCode = readFile(m_fragShaderPath); - fragShaderModule = std::make_unique(m_device, fragShaderCode); - vk::PipelineShaderStageCreateInfo fragStageInfo({}, vk::ShaderStageFlagBits::eFragment, fragShaderModule->getHandle(), "main"); - shaderStages.push_back(fragStageInfo); - } - - // 2. Vertex Input - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{}; - vertexInputInfo.vertexBindingDescriptionCount = static_cast(m_bindings.size()); - vertexInputInfo.pVertexBindingDescriptions = m_bindings.data(); - vertexInputInfo.vertexAttributeDescriptionCount = static_cast(m_attributes.size()); - vertexInputInfo.pVertexAttributeDescriptions = m_attributes.data(); - - // 3. Input Assembly - vk::PipelineInputAssemblyStateCreateInfo inputAssembly({}, vk::PrimitiveTopology::eTriangleList, VK_FALSE); - - // 4. Viewport and Scissor (Dynamic) - vk::PipelineViewportStateCreateInfo viewportState({}, 1, nullptr, 1, nullptr); - - // 5. Rasterizer - vk::PipelineRasterizationStateCreateInfo rasterizer( - {}, VK_FALSE, VK_FALSE, vk::PolygonMode::eFill, m_cullMode, m_frontFace, m_depthBiasEnable? VK_TRUE : VK_FALSE, 0.0f, 0.0f, 0.0f, 1.0f); - - // 6. Multisampling - vk::PipelineMultisampleStateCreateInfo multisampling({}, utils::mapSampleCountFlag(m_samples), VK_FALSE); - - // 7. Depth/Stencil - vk::PipelineDepthStencilStateCreateInfo depthStencil{}; - if (m_depthAttachmentFormat != vk::Format::eUndefined) - { - depthStencil.depthTestEnable = VK_TRUE; - depthStencil.depthWriteEnable = m_depthWriteEnable ? VK_TRUE : VK_FALSE; - depthStencil.depthCompareOp = vk::CompareOp::eLessOrEqual; - } - - // 8. Color Blending - vk::PipelineColorBlendAttachmentState colorBlendAttachment{}; - colorBlendAttachment.colorWriteMask = - vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = VK_FALSE; - - vk::PipelineColorBlendStateCreateInfo colorBlending{}; - colorBlending.logicOpEnable = VK_FALSE; - colorBlending.attachmentCount = (m_colorAttachmentFormat != vk::Format::eUndefined) ? 1 : 0; - colorBlending.pAttachments = - (m_colorAttachmentFormat != vk::Format::eUndefined) ? &colorBlendAttachment : nullptr; - - // 9. Dynamic State - std::vector dynamicStates = {vk::DynamicState::eViewport, - vk::DynamicState::eScissor}; - // conditionally add eDepthBias to the list of dynamic states - if (m_depthBiasEnable) - { - dynamicStates.push_back(vk::DynamicState::eDepthBias); - } - - vk::PipelineDynamicStateCreateInfo dynamicState({}, dynamicStates); - - // 10. Pipeline Layout - vk::PipelineLayoutCreateInfo pipelineLayoutInfo({}, m_setLayouts); - pipelineLayoutInfo.pushConstantRangeCount = static_cast(m_pushRanges.size()); - pipelineLayoutInfo.pPushConstantRanges = m_pushRanges.data(); - vk::PipelineLayout pipelineLayout = m_device.createPipelineLayout(pipelineLayoutInfo); - - // 11. Dynamic Rendering Info - vk::PipelineRenderingCreateInfo renderingInfo{}; - renderingInfo.viewMask = 0; - renderingInfo.colorAttachmentCount = - (m_colorAttachmentFormat != vk::Format::eUndefined) ? 1 : 0; - renderingInfo.pColorAttachmentFormats = &m_colorAttachmentFormat; - renderingInfo.depthAttachmentFormat = m_depthAttachmentFormat; - - // 12. Graphics Pipeline Create Info - vk::GraphicsPipelineCreateInfo pipelineInfo{}; - pipelineInfo.pNext = &renderingInfo; - pipelineInfo.stageCount = static_cast(shaderStages.size()); - pipelineInfo.pStages = shaderStages.data(); - pipelineInfo.pVertexInputState = &vertexInputInfo; - pipelineInfo.pInputAssemblyState = &inputAssembly; - pipelineInfo.pViewportState = &viewportState; - pipelineInfo.pRasterizationState = &rasterizer; - pipelineInfo.pMultisampleState = &multisampling; - pipelineInfo.pDepthStencilState = - (m_depthAttachmentFormat != vk::Format::eUndefined) ? &depthStencil : nullptr; - pipelineInfo.pColorBlendState = &colorBlending; - pipelineInfo.pDynamicState = &dynamicState; - pipelineInfo.layout = pipelineLayout; - - auto result = m_device.createGraphicsPipeline({}, pipelineInfo); - if (result.result != vk::Result::eSuccess) - { - throw std::runtime_error("Failed to create graphics pipeline!"); - } - - // Using a private constructor to pass ownership of the created handles - return std::unique_ptr(new Pipeline(m_device, pipelineLayout, result.value)); - } - - // --- Pipeline Implementation --- - - Pipeline::Pipeline(vk::Device device, vk::PipelineLayout layout, vk::Pipeline pipeline) - : m_device(device), m_pipelineLayout(layout), m_pipeline(pipeline) - {} - - Pipeline::~Pipeline() - { - if (m_pipeline) - { - m_device.destroyPipeline(m_pipeline); - } - if (m_pipelineLayout) - { - m_device.destroyPipelineLayout(m_pipelineLayout); - } - } - - Pipeline::Pipeline(Pipeline&& other) noexcept - : m_device(other.m_device), m_pipelineLayout(other.m_pipelineLayout), - m_pipeline(other.m_pipeline) - { - other.m_pipelineLayout = nullptr; - other.m_pipeline = nullptr; - } - - Pipeline& Pipeline::operator=(Pipeline&& other) noexcept - { - if (this != &other) - { - if (m_pipeline) - m_device.destroyPipeline(m_pipeline); - if (m_pipelineLayout) - m_device.destroyPipelineLayout(m_pipelineLayout); - - m_device = other.m_device; - m_pipelineLayout = other.m_pipelineLayout; - m_pipeline = other.m_pipeline; - - other.m_pipelineLayout = nullptr; - other.m_pipeline = nullptr; - } - return *this; - } - +#include "Pipeline.hpp" +#include +#include + +#include "ShaderModule.hpp" +#include "VulkanUtils.hpp" + +namespace reactor +{ + + static std::vector readFile(const std::string& filename) + { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + if (!file.is_open()) + throw std::runtime_error("Failed to open shader file: " + filename); + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + file.seekg(0); + file.read(buffer.data(), fileSize); + return buffer; + } + + Pipeline::Builder::Builder(vk::Device device) + : m_device(device) + {} + + Pipeline::Builder& Pipeline::Builder::setVertexShader(const std::string& shaderPath) + { + m_vertShaderPath = shaderPath; + return *this; + } + + Pipeline::Builder& Pipeline::Builder::setFragmentShader(const std::string& shaderPath) + { + m_fragShaderPath = shaderPath; + return *this; + } + + Pipeline::Builder& Pipeline::Builder::setColorAttachment(vk::Format format) + { + m_colorAttachmentFormat = format; + return *this; + } + + Pipeline::Builder& Pipeline::Builder::setDepthAttachment(vk::Format format, bool depthWriteEnable) + { + m_depthAttachmentFormat = format; + m_depthWriteEnable = depthWriteEnable; + return *this; + } + + Pipeline::Builder& + Pipeline::Builder::setDescriptorSetLayouts(const std::vector& layouts) + { + m_setLayouts = layouts; + return *this; + } + Pipeline::Builder& Pipeline::Builder::setVertexInputFromVertex() + { + m_bindings.push_back(Vertex::getBindingDescription()); + auto attrs = Vertex::getAttributeDescriptions(); + m_attributes.insert(m_attributes.end(), attrs.begin(), attrs.end()); + return *this; + } + + Pipeline::Builder& Pipeline::Builder::setMultisample(uint32_t samples) + { + m_samples = samples; + return *this; + } + + Pipeline::Builder& Pipeline::Builder::setCullMode(vk::CullModeFlags cullMode) + { + m_cullMode = cullMode; + return *this; + } + + Pipeline::Builder& Pipeline::Builder::setFrontFace(vk::FrontFace frontFace) + { + m_frontFace = frontFace; + return *this; + } + + Pipeline::Builder& Pipeline::Builder::addPushContantRange(vk::ShaderStageFlags stages, uint32_t offset, uint32_t size) + { + m_pushRanges.push_back({stages, offset, size}); + return *this; + } + + Pipeline::Builder& Pipeline::Builder::enableDepthBias(bool enable) + { + m_depthBiasEnable = enable; + return *this; + } + + + std::unique_ptr Pipeline::Builder::build() const + { + // 1. Shader Stages + auto vertShaderCode = readFile(m_vertShaderPath); + auto vertShaderModule = ShaderModule(m_device, vertShaderCode); + vk::PipelineShaderStageCreateInfo vertStageInfo({}, vk::ShaderStageFlagBits::eVertex, vertShaderModule.getHandle(), "main"); + + std::vector shaderStages = {vertStageInfo}; + std::unique_ptr fragShaderModule; + if (!m_fragShaderPath.empty()) + { + auto fragShaderCode = readFile(m_fragShaderPath); + fragShaderModule = std::make_unique(m_device, fragShaderCode); + vk::PipelineShaderStageCreateInfo fragStageInfo({}, vk::ShaderStageFlagBits::eFragment, fragShaderModule->getHandle(), "main"); + shaderStages.push_back(fragStageInfo); + } + + // 2. Vertex Input + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.vertexBindingDescriptionCount = static_cast(m_bindings.size()); + vertexInputInfo.pVertexBindingDescriptions = m_bindings.data(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(m_attributes.size()); + vertexInputInfo.pVertexAttributeDescriptions = m_attributes.data(); + + // 3. Input Assembly + vk::PipelineInputAssemblyStateCreateInfo inputAssembly({}, vk::PrimitiveTopology::eTriangleList, VK_FALSE); + + // 4. Viewport and Scissor (Dynamic) + vk::PipelineViewportStateCreateInfo viewportState({}, 1, nullptr, 1, nullptr); + + // 5. Rasterizer + vk::PipelineRasterizationStateCreateInfo rasterizer( + {}, VK_FALSE, VK_FALSE, vk::PolygonMode::eFill, m_cullMode, m_frontFace, m_depthBiasEnable? VK_TRUE : VK_FALSE, 0.0f, 0.0f, 0.0f, 1.0f); + + // 6. Multisampling + vk::PipelineMultisampleStateCreateInfo multisampling({}, utils::mapSampleCountFlag(m_samples), VK_FALSE); + + // 7. Depth/Stencil + vk::PipelineDepthStencilStateCreateInfo depthStencil{}; + if (m_depthAttachmentFormat != vk::Format::eUndefined) + { + depthStencil.depthTestEnable = VK_TRUE; + depthStencil.depthWriteEnable = m_depthWriteEnable ? VK_TRUE : VK_FALSE; + depthStencil.depthCompareOp = vk::CompareOp::eLessOrEqual; + } + + // 8. Color Blending + vk::PipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = + vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = VK_FALSE; + + vk::PipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.attachmentCount = (m_colorAttachmentFormat != vk::Format::eUndefined) ? 1 : 0; + colorBlending.pAttachments = + (m_colorAttachmentFormat != vk::Format::eUndefined) ? &colorBlendAttachment : nullptr; + + // 9. Dynamic State + std::vector dynamicStates = {vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + // conditionally add eDepthBias to the list of dynamic states + if (m_depthBiasEnable) + { + dynamicStates.push_back(vk::DynamicState::eDepthBias); + } + + vk::PipelineDynamicStateCreateInfo dynamicState({}, dynamicStates); + + // 10. Pipeline Layout + vk::PipelineLayoutCreateInfo pipelineLayoutInfo({}, m_setLayouts); + pipelineLayoutInfo.pushConstantRangeCount = static_cast(m_pushRanges.size()); + pipelineLayoutInfo.pPushConstantRanges = m_pushRanges.data(); + vk::PipelineLayout pipelineLayout = m_device.createPipelineLayout(pipelineLayoutInfo); + + // 11. Dynamic Rendering Info + vk::PipelineRenderingCreateInfo renderingInfo{}; + renderingInfo.viewMask = 0; + renderingInfo.colorAttachmentCount = + (m_colorAttachmentFormat != vk::Format::eUndefined) ? 1 : 0; + renderingInfo.pColorAttachmentFormats = &m_colorAttachmentFormat; + renderingInfo.depthAttachmentFormat = m_depthAttachmentFormat; + + // 12. Graphics Pipeline Create Info + vk::GraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.pNext = &renderingInfo; + pipelineInfo.stageCount = static_cast(shaderStages.size()); + pipelineInfo.pStages = shaderStages.data(); + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = + (m_depthAttachmentFormat != vk::Format::eUndefined) ? &depthStencil : nullptr; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + + auto result = m_device.createGraphicsPipeline({}, pipelineInfo); + if (result.result != vk::Result::eSuccess) + { + throw std::runtime_error("Failed to create graphics pipeline!"); + } + + // Using a private constructor to pass ownership of the created handles + return std::unique_ptr(new Pipeline(m_device, pipelineLayout, result.value)); + } + + // --- Pipeline Implementation --- + + Pipeline::Pipeline(vk::Device device, vk::PipelineLayout layout, vk::Pipeline pipeline) + : m_device(device), m_pipelineLayout(layout), m_pipeline(pipeline) + {} + + Pipeline::~Pipeline() + { + if (m_pipeline) + { + m_device.destroyPipeline(m_pipeline); + } + if (m_pipelineLayout) + { + m_device.destroyPipelineLayout(m_pipelineLayout); + } + } + + Pipeline::Pipeline(Pipeline&& other) noexcept + : m_device(other.m_device), m_pipelineLayout(other.m_pipelineLayout), + m_pipeline(other.m_pipeline) + { + other.m_pipelineLayout = nullptr; + other.m_pipeline = nullptr; + } + + Pipeline& Pipeline::operator=(Pipeline&& other) noexcept + { + if (this != &other) + { + if (m_pipeline) + m_device.destroyPipeline(m_pipeline); + if (m_pipelineLayout) + m_device.destroyPipelineLayout(m_pipelineLayout); + + m_device = other.m_device; + m_pipelineLayout = other.m_pipelineLayout; + m_pipeline = other.m_pipeline; + + other.m_pipelineLayout = nullptr; + other.m_pipeline = nullptr; + } + return *this; + } + } // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Pipeline.hpp b/src/vulkan/Pipeline.hpp index bbbf3c9..ca3c615 100644 --- a/src/vulkan/Pipeline.hpp +++ b/src/vulkan/Pipeline.hpp @@ -1,80 +1,80 @@ -#pragma once - -#include -#include -#include - -#include "Vertex.hpp" - -namespace reactor -{ - - class Pipeline - { - public: - class Builder - { - public: - Builder(vk::Device device); - - Builder& setVertexShader(const std::string& shaderPath); - Builder& setFragmentShader(const std::string& shaderPath); - Builder& setColorAttachment(vk::Format format); - Builder& setDepthAttachment(vk::Format format, bool depthWriteEnable = true); - Builder& setDescriptorSetLayouts(const std::vector& layouts); - Builder& setVertexInputFromVertex(); - Builder& setMultisample(uint32_t samples); - Builder& setCullMode(vk::CullModeFlags cullMode); - Builder& setFrontFace(vk::FrontFace frontFace); - Builder& addPushContantRange(vk::ShaderStageFlags stages, uint32_t offset, uint32_t size); - Builder& enableDepthBias(bool enable=true); - - [[nodiscard]] std::unique_ptr build() const; - - private: - vk::Device m_device; - std::string m_vertShaderPath; - std::string m_fragShaderPath; - vk::Format m_colorAttachmentFormat = vk::Format::eUndefined; - vk::Format m_depthAttachmentFormat = vk::Format::eUndefined; - bool m_depthWriteEnable = true; - std::vector m_setLayouts; - uint32_t m_samples = 1; - vk::CullModeFlags m_cullMode = vk::CullModeFlagBits::eBack; - vk::FrontFace m_frontFace = vk::FrontFace::eCounterClockwise; - bool m_depthBiasEnable = false; - - std::vector m_bindings; - std::vector m_attributes; - std::vector m_pushRanges; - }; - - ~Pipeline(); - - [[nodiscard]] vk::Pipeline get() const - { - return m_pipeline; - } - [[nodiscard]] vk::PipelineLayout getLayout() const - { - return m_pipelineLayout; - } - - // Delete copy operations - Pipeline(const Pipeline&) = delete; - Pipeline& operator=(const Pipeline&) = delete; - - // Move operations - Pipeline(Pipeline&& other) noexcept; - Pipeline& operator=(Pipeline&& other) noexcept; - - private: - friend class Builder; - Pipeline(vk::Device device, vk::PipelineLayout pipelineLayout, vk::Pipeline pipeline); - - vk::Device m_device; - vk::PipelineLayout m_pipelineLayout; - vk::Pipeline m_pipeline; - }; - +#pragma once + +#include +#include +#include + +#include "Vertex.hpp" + +namespace reactor +{ + + class Pipeline + { + public: + class Builder + { + public: + Builder(vk::Device device); + + Builder& setVertexShader(const std::string& shaderPath); + Builder& setFragmentShader(const std::string& shaderPath); + Builder& setColorAttachment(vk::Format format); + Builder& setDepthAttachment(vk::Format format, bool depthWriteEnable = true); + Builder& setDescriptorSetLayouts(const std::vector& layouts); + Builder& setVertexInputFromVertex(); + Builder& setMultisample(uint32_t samples); + Builder& setCullMode(vk::CullModeFlags cullMode); + Builder& setFrontFace(vk::FrontFace frontFace); + Builder& addPushContantRange(vk::ShaderStageFlags stages, uint32_t offset, uint32_t size); + Builder& enableDepthBias(bool enable=true); + + [[nodiscard]] std::unique_ptr build() const; + + private: + vk::Device m_device; + std::string m_vertShaderPath; + std::string m_fragShaderPath; + vk::Format m_colorAttachmentFormat = vk::Format::eUndefined; + vk::Format m_depthAttachmentFormat = vk::Format::eUndefined; + bool m_depthWriteEnable = true; + std::vector m_setLayouts; + uint32_t m_samples = 1; + vk::CullModeFlags m_cullMode = vk::CullModeFlagBits::eBack; + vk::FrontFace m_frontFace = vk::FrontFace::eCounterClockwise; + bool m_depthBiasEnable = false; + + std::vector m_bindings; + std::vector m_attributes; + std::vector m_pushRanges; + }; + + ~Pipeline(); + + [[nodiscard]] vk::Pipeline get() const + { + return m_pipeline; + } + [[nodiscard]] vk::PipelineLayout getLayout() const + { + return m_pipelineLayout; + } + + // Delete copy operations + Pipeline(const Pipeline&) = delete; + Pipeline& operator=(const Pipeline&) = delete; + + // Move operations + Pipeline(Pipeline&& other) noexcept; + Pipeline& operator=(Pipeline&& other) noexcept; + + private: + friend class Builder; + Pipeline(vk::Device device, vk::PipelineLayout pipelineLayout, vk::Pipeline pipeline); + + vk::Device m_device; + vk::PipelineLayout m_pipelineLayout; + vk::Pipeline m_pipeline; + }; + } // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Sampler.cpp b/src/vulkan/Sampler.cpp index f3052d8..b982008 100644 --- a/src/vulkan/Sampler.cpp +++ b/src/vulkan/Sampler.cpp @@ -1,20 +1,20 @@ -#include "Sampler.hpp" - -namespace reactor -{ - -Sampler::Sampler(vk::Device device, const vk::SamplerCreateInfo& createInfo) - : m_device(device) -{ - m_sampler = m_device.createSampler(createInfo); -} - -Sampler::~Sampler() -{ - if (m_sampler) - { - m_device.destroySampler(m_sampler, nullptr); - } -} - +#include "Sampler.hpp" + +namespace reactor +{ + +Sampler::Sampler(vk::Device device, const vk::SamplerCreateInfo& createInfo) + : m_device(device) +{ + m_sampler = m_device.createSampler(createInfo); +} + +Sampler::~Sampler() +{ + if (m_sampler) + { + m_device.destroySampler(m_sampler, nullptr); + } +} + } // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Sampler.hpp b/src/vulkan/Sampler.hpp index bbd4915..7e6d00a 100644 --- a/src/vulkan/Sampler.hpp +++ b/src/vulkan/Sampler.hpp @@ -1,24 +1,24 @@ -#pragma once - -#include - -namespace reactor -{ - -class Sampler -{ -public: - Sampler(vk::Device device, const vk::SamplerCreateInfo& createInfo); - ~Sampler(); - - [[nodiscard]] vk::Sampler get() const - { - return m_sampler; - } - -private: - vk::Device m_device; - vk::Sampler m_sampler; -}; - -} // namespace reactor +#pragma once + +#include + +namespace reactor +{ + +class Sampler +{ +public: + Sampler(vk::Device device, const vk::SamplerCreateInfo& createInfo); + ~Sampler(); + + [[nodiscard]] vk::Sampler get() const + { + return m_sampler; + } + +private: + vk::Device m_device; + vk::Sampler m_sampler; +}; + +} // namespace reactor diff --git a/src/vulkan/ShaderModule.cpp b/src/vulkan/ShaderModule.cpp index 2e15dd9..c5227ef 100644 --- a/src/vulkan/ShaderModule.cpp +++ b/src/vulkan/ShaderModule.cpp @@ -1,25 +1,25 @@ -// -// Created by rfdic on 7/2/2025. -// - -#include "ShaderModule.hpp" - -namespace reactor -{ - -ShaderModule::ShaderModule(vk::Device device, const std::vector& code) - : m_device(device) -{ - vk::ShaderModuleCreateInfo createInfo({}, code.size(), reinterpret_cast(code.data())); - m_handle = m_device.createShaderModule(createInfo); -} - -ShaderModule::~ShaderModule() -{ - if (m_handle) - { - m_device.destroyShaderModule(m_handle); - } -} - +// +// Created by rfdic on 7/2/2025. +// + +#include "ShaderModule.hpp" + +namespace reactor +{ + +ShaderModule::ShaderModule(vk::Device device, const std::vector& code) + : m_device(device) +{ + vk::ShaderModuleCreateInfo createInfo({}, code.size(), reinterpret_cast(code.data())); + m_handle = m_device.createShaderModule(createInfo); +} + +ShaderModule::~ShaderModule() +{ + if (m_handle) + { + m_device.destroyShaderModule(m_handle); + } +} + } // namespace reactor \ No newline at end of file diff --git a/src/vulkan/ShaderModule.hpp b/src/vulkan/ShaderModule.hpp index 579de6f..dfc1d53 100644 --- a/src/vulkan/ShaderModule.hpp +++ b/src/vulkan/ShaderModule.hpp @@ -1,33 +1,33 @@ -#pragma once -#include - -namespace reactor -{ - -class ShaderModule -{ -public: - ShaderModule(vk::Device device, const std::vector& code); - ~ShaderModule(); - - [[nodiscard]] vk::ShaderModule getHandle() const - { - return m_handle; - }; - - // disallow copy, allow move - ShaderModule(const ShaderModule&) = delete; - ShaderModule& operator=(const ShaderModule&) = delete; - ShaderModule(ShaderModule&& other) noexcept - : m_handle(other.m_handle) - { - other.m_handle = nullptr; - } - ShaderModule& operator=(ShaderModule&&) = delete; - -private: - vk::ShaderModule m_handle; - vk::Device m_device; -}; - -} // namespace reactor +#pragma once +#include + +namespace reactor +{ + +class ShaderModule +{ +public: + ShaderModule(vk::Device device, const std::vector& code); + ~ShaderModule(); + + [[nodiscard]] vk::ShaderModule getHandle() const + { + return m_handle; + }; + + // disallow copy, allow move + ShaderModule(const ShaderModule&) = delete; + ShaderModule& operator=(const ShaderModule&) = delete; + ShaderModule(ShaderModule&& other) noexcept + : m_handle(other.m_handle) + { + other.m_handle = nullptr; + } + ShaderModule& operator=(ShaderModule&&) = delete; + +private: + vk::ShaderModule m_handle; + vk::Device m_device; +}; + +} // namespace reactor diff --git a/src/vulkan/ShadowMapping.cpp b/src/vulkan/ShadowMapping.cpp index a4ff3e8..508e48e 100644 --- a/src/vulkan/ShadowMapping.cpp +++ b/src/vulkan/ShadowMapping.cpp @@ -1,229 +1,229 @@ -#include "ShadowMapping.hpp" - -#include "VulkanRenderer.hpp" -#include "../core/Uniforms.hpp" -#include "../logging/Logger.hpp" - -namespace reactor -{ - -ShadowMapping::ShadowMapping(VulkanRenderer& renderer, uint32_t resolution) - : m_renderer(renderer), m_resolution(resolution), m_shadowMapView(VK_NULL_HANDLE), - m_shadowMapSampler(VK_NULL_HANDLE) -{ - createResources(); - createDescriptors(); - createPipeline(); -} - -ShadowMapping::~ShadowMapping() -{ - // if you own any Vulkan handles directly, clean them up here - - auto device = m_renderer.device(); - device.destroyImageView(m_shadowMapView); - device.destroySampler(m_shadowMapSampler); -} - -void ShadowMapping::createResources() -{ - auto device = m_renderer.device(); - auto& allocator = m_renderer.allocator(); - - vk::ImageCreateInfo imageInfo = {}; - imageInfo.imageType = vk::ImageType::e2D; - imageInfo.format = vk::Format::eD32Sfloat; - imageInfo.extent = vk::Extent3D{m_resolution, m_resolution, 1}; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.samples = vk::SampleCountFlagBits::e1; - imageInfo.tiling = vk::ImageTiling::eOptimal; - imageInfo.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled; - imageInfo.sharingMode = vk::SharingMode::eExclusive; - imageInfo.initialLayout = vk::ImageLayout::eUndefined; - - VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_GPU_ONLY; - - m_shadowMap = std::make_unique(allocator, imageInfo, memoryUsage); - - // 2. Create image view - vk::ImageViewCreateInfo viewInfo{}; - viewInfo.image = m_shadowMap->get(); - viewInfo.viewType = vk::ImageViewType::e2D; - viewInfo.format = vk::Format::eD32Sfloat; - viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eDepth; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - m_shadowMapView = device.createImageView(viewInfo); - - // 3. Create sampler - vk::SamplerCreateInfo samplerInfo{}; - samplerInfo.magFilter = vk::Filter::eLinear; - samplerInfo.minFilter = vk::Filter::eLinear; - samplerInfo.mipmapMode = vk::SamplerMipmapMode::eNearest; - samplerInfo.addressModeU = vk::SamplerAddressMode::eClampToBorder; - samplerInfo.addressModeV = vk::SamplerAddressMode::eClampToBorder; - samplerInfo.addressModeW = vk::SamplerAddressMode::eClampToBorder; - samplerInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite; - samplerInfo.compareEnable = VK_TRUE; - samplerInfo.compareOp = vk::CompareOp::eLessOrEqual; - - m_shadowMapSampler = device.createSampler(samplerInfo); - - const size_t frameCount = 2; - m_mvpBuffer.clear(); - m_mvpBuffer.reserve(frameCount); - for (size_t i = 0; i < frameCount; ++i) - { - m_mvpBuffer.push_back(std::make_unique(m_renderer.allocator(), - sizeof(SceneUBO), - vk::BufferUsageFlagBits::eUniformBuffer, - VMA_MEMORY_USAGE_CPU_TO_GPU, - "MVP Buffer")); - } -} - -void ShadowMapping::createPipeline() -{ - LOG_INFO("Creating shadow mapping pipeline"); - - auto device = m_renderer.device(); - - std::vector setLayouts; - if (m_descriptors) - { - setLayouts.push_back(m_descriptors->getLayout()); - } - - Pipeline::Builder builder(device); - - builder - .setVertexShader("../resources/shaders/triangle.vert.spv") - // No fragment shader, we only want depth output - .setVertexInputFromVertex() - .setDepthAttachment(vk::Format::eD32Sfloat, true) // depth test and write enabled - .enableDepthBias() - .setDescriptorSetLayouts(setLayouts) - .setMultisample(1) - .setCullMode(vk::CullModeFlagBits::eFront) - .setFrontFace(vk::FrontFace::eClockwise) // Match main geometry pipeline - .addPushContantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)); - - m_depthPassPipeline = builder.build(); -} - -void ShadowMapping::createDescriptors() -{ - spdlog::info("Creating shadow mapping descriptors"); - vk::Device device = m_renderer.device(); - size_t framesInFlight = 2; - - // Descriptor set bindings: UBO for light's MVP - std::vector bindings = { - {0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex} - // Add more if you want—for example for a sampler or shadow map - }; - - // Use your DescriptorSet abstraction to handle layout & Vulkan allocation - m_descriptors = std::make_unique(device, m_renderer.descriptorPool(), framesInFlight, bindings); - - // Now update each descriptor set with the corresponding per-frame UBO - for (size_t i = 0; i < framesInFlight; ++i) - { - vk::DescriptorBufferInfo uboInfo{}; - uboInfo.buffer = m_mvpBuffer[i]->getHandle(); - uboInfo.offset = 0; - uboInfo.range = sizeof(SceneUBO); - - vk::WriteDescriptorSet writes{}; - writes.dstSet = m_descriptors->get(i); - writes.dstBinding = 0; - writes.dstArrayElement = 0; - writes.descriptorType = vk::DescriptorType::eUniformBuffer; - writes.descriptorCount = 1; - writes.pBufferInfo = &uboInfo; - - m_descriptors->updateSet({writes}); - } -} - -void ShadowMapping::recordShadowPass(vk::CommandBuffer cmd, - size_t frameIndex, - const std::function& drawCallback) -{ - vk::ClearValue clearDepth; - clearDepth.depthStencil = vk::ClearDepthStencilValue{1.0f, 0}; - - vk::RenderingAttachmentInfo depthAttachment{}; - depthAttachment.imageView = m_shadowMapView; - depthAttachment.imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; - depthAttachment.loadOp = vk::AttachmentLoadOp::eClear; - depthAttachment.storeOp = vk::AttachmentStoreOp::eStore; - depthAttachment.clearValue = clearDepth; - - vk::RenderingInfo renderingInfo{}; - renderingInfo.renderArea = vk::Rect2D({0, 0}, {m_resolution, m_resolution}); - renderingInfo.layerCount = 1; - renderingInfo.pDepthAttachment = &depthAttachment; - - cmd.setDepthBias(depthBiasConstant, 0.0f, depthBiasSlope); - - cmd.beginRendering(&renderingInfo); - - // set viewport/scissor - const vk::Viewport viewport = {0.0f, 0.0f, static_cast(m_resolution), static_cast(m_resolution), 0.0f, 1.0f}; - const vk::Rect2D scissor = {vk::Offset2D{0, 0}, vk::Extent2D{m_resolution, m_resolution}}; - cmd.setViewport(0, viewport); - cmd.setScissor(0, scissor); - - cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_depthPassPipeline->get()); - - auto descriptor = m_descriptors->get(frameIndex); - - // bind descriptor set (with light MVP) - cmd.bindDescriptorSets( - vk::PipelineBindPoint::eGraphics, m_depthPassPipeline->getLayout(), 0, 1, &descriptor, 0, nullptr); - - // draw the shadow casters - drawCallback(cmd); - - cmd.endRendering(); -} - -vk::ImageView ShadowMapping::shadowMapView() const -{ - return m_shadowMapView; -} - -vk::Sampler ShadowMapping::shadowMapSampler() const -{ - return m_shadowMapSampler; -} - -vk::DescriptorSet ShadowMapping::shadowMapDescriptorSet(size_t frameIndex) const -{ - return m_descriptors->get(frameIndex); -} - -vk::Image ShadowMapping::shadowMapImage() const -{ - return m_shadowMap->get(); -} - -void ShadowMapping::setLightMatrix(const glm::mat4& lightSpaceMatrix, size_t frameIndex) -{ - SceneUBO ubo{}; - ubo.view = glm::mat4(1.0f); - ubo.projection = lightSpaceMatrix; - ubo.lightSpaceMatrix = glm::mat4(1.0f); - - // map buffer, copy matrix - void* data = m_mvpBuffer[frameIndex]->map(); - memcpy(data, &ubo, sizeof(SceneUBO)); - m_mvpBuffer[frameIndex]->unmap(); -} - +#include "ShadowMapping.hpp" + +#include "VulkanRenderer.hpp" +#include "../core/Uniforms.hpp" +#include "../logging/Logger.hpp" + +namespace reactor +{ + +ShadowMapping::ShadowMapping(VulkanRenderer& renderer, uint32_t resolution) + : m_renderer(renderer), m_resolution(resolution), m_shadowMapView(VK_NULL_HANDLE), + m_shadowMapSampler(VK_NULL_HANDLE) +{ + createResources(); + createDescriptors(); + createPipeline(); +} + +ShadowMapping::~ShadowMapping() +{ + // if you own any Vulkan handles directly, clean them up here + + auto device = m_renderer.device(); + device.destroyImageView(m_shadowMapView); + device.destroySampler(m_shadowMapSampler); +} + +void ShadowMapping::createResources() +{ + auto device = m_renderer.device(); + auto& allocator = m_renderer.allocator(); + + vk::ImageCreateInfo imageInfo = {}; + imageInfo.imageType = vk::ImageType::e2D; + imageInfo.format = vk::Format::eD32Sfloat; + imageInfo.extent = vk::Extent3D{m_resolution, m_resolution, 1}; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.samples = vk::SampleCountFlagBits::e1; + imageInfo.tiling = vk::ImageTiling::eOptimal; + imageInfo.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled; + imageInfo.sharingMode = vk::SharingMode::eExclusive; + imageInfo.initialLayout = vk::ImageLayout::eUndefined; + + VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_GPU_ONLY; + + m_shadowMap = std::make_unique(allocator, imageInfo, memoryUsage); + + // 2. Create image view + vk::ImageViewCreateInfo viewInfo{}; + viewInfo.image = m_shadowMap->get(); + viewInfo.viewType = vk::ImageViewType::e2D; + viewInfo.format = vk::Format::eD32Sfloat; + viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eDepth; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + m_shadowMapView = device.createImageView(viewInfo); + + // 3. Create sampler + vk::SamplerCreateInfo samplerInfo{}; + samplerInfo.magFilter = vk::Filter::eLinear; + samplerInfo.minFilter = vk::Filter::eLinear; + samplerInfo.mipmapMode = vk::SamplerMipmapMode::eNearest; + samplerInfo.addressModeU = vk::SamplerAddressMode::eClampToBorder; + samplerInfo.addressModeV = vk::SamplerAddressMode::eClampToBorder; + samplerInfo.addressModeW = vk::SamplerAddressMode::eClampToBorder; + samplerInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite; + samplerInfo.compareEnable = VK_TRUE; + samplerInfo.compareOp = vk::CompareOp::eLessOrEqual; + + m_shadowMapSampler = device.createSampler(samplerInfo); + + const size_t frameCount = 2; + m_mvpBuffer.clear(); + m_mvpBuffer.reserve(frameCount); + for (size_t i = 0; i < frameCount; ++i) + { + m_mvpBuffer.push_back(std::make_unique(m_renderer.allocator(), + sizeof(SceneUBO), + vk::BufferUsageFlagBits::eUniformBuffer, + VMA_MEMORY_USAGE_CPU_TO_GPU, + "MVP Buffer")); + } +} + +void ShadowMapping::createPipeline() +{ + LOG_INFO("Creating shadow mapping pipeline"); + + auto device = m_renderer.device(); + + std::vector setLayouts; + if (m_descriptors) + { + setLayouts.push_back(m_descriptors->getLayout()); + } + + Pipeline::Builder builder(device); + + builder + .setVertexShader("../resources/shaders/triangle.vert.spv") + // No fragment shader, we only want depth output + .setVertexInputFromVertex() + .setDepthAttachment(vk::Format::eD32Sfloat, true) // depth test and write enabled + .enableDepthBias() + .setDescriptorSetLayouts(setLayouts) + .setMultisample(1) + .setCullMode(vk::CullModeFlagBits::eFront) + .setFrontFace(vk::FrontFace::eClockwise) // Match main geometry pipeline + .addPushContantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)); + + m_depthPassPipeline = builder.build(); +} + +void ShadowMapping::createDescriptors() +{ + spdlog::info("Creating shadow mapping descriptors"); + vk::Device device = m_renderer.device(); + size_t framesInFlight = 2; + + // Descriptor set bindings: UBO for light's MVP + std::vector bindings = { + {0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex} + // Add more if you want—for example for a sampler or shadow map + }; + + // Use your DescriptorSet abstraction to handle layout & Vulkan allocation + m_descriptors = std::make_unique(device, m_renderer.descriptorPool(), framesInFlight, bindings); + + // Now update each descriptor set with the corresponding per-frame UBO + for (size_t i = 0; i < framesInFlight; ++i) + { + vk::DescriptorBufferInfo uboInfo{}; + uboInfo.buffer = m_mvpBuffer[i]->getHandle(); + uboInfo.offset = 0; + uboInfo.range = sizeof(SceneUBO); + + vk::WriteDescriptorSet writes{}; + writes.dstSet = m_descriptors->get(i); + writes.dstBinding = 0; + writes.dstArrayElement = 0; + writes.descriptorType = vk::DescriptorType::eUniformBuffer; + writes.descriptorCount = 1; + writes.pBufferInfo = &uboInfo; + + m_descriptors->updateSet({writes}); + } +} + +void ShadowMapping::recordShadowPass(vk::CommandBuffer cmd, + size_t frameIndex, + const std::function& drawCallback) +{ + vk::ClearValue clearDepth; + clearDepth.depthStencil = vk::ClearDepthStencilValue{1.0f, 0}; + + vk::RenderingAttachmentInfo depthAttachment{}; + depthAttachment.imageView = m_shadowMapView; + depthAttachment.imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; + depthAttachment.loadOp = vk::AttachmentLoadOp::eClear; + depthAttachment.storeOp = vk::AttachmentStoreOp::eStore; + depthAttachment.clearValue = clearDepth; + + vk::RenderingInfo renderingInfo{}; + renderingInfo.renderArea = vk::Rect2D({0, 0}, {m_resolution, m_resolution}); + renderingInfo.layerCount = 1; + renderingInfo.pDepthAttachment = &depthAttachment; + + cmd.setDepthBias(depthBiasConstant, 0.0f, depthBiasSlope); + + cmd.beginRendering(&renderingInfo); + + // set viewport/scissor + const vk::Viewport viewport = {0.0f, 0.0f, static_cast(m_resolution), static_cast(m_resolution), 0.0f, 1.0f}; + const vk::Rect2D scissor = {vk::Offset2D{0, 0}, vk::Extent2D{m_resolution, m_resolution}}; + cmd.setViewport(0, viewport); + cmd.setScissor(0, scissor); + + cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_depthPassPipeline->get()); + + auto descriptor = m_descriptors->get(frameIndex); + + // bind descriptor set (with light MVP) + cmd.bindDescriptorSets( + vk::PipelineBindPoint::eGraphics, m_depthPassPipeline->getLayout(), 0, 1, &descriptor, 0, nullptr); + + // draw the shadow casters + drawCallback(cmd); + + cmd.endRendering(); +} + +vk::ImageView ShadowMapping::shadowMapView() const +{ + return m_shadowMapView; +} + +vk::Sampler ShadowMapping::shadowMapSampler() const +{ + return m_shadowMapSampler; +} + +vk::DescriptorSet ShadowMapping::shadowMapDescriptorSet(size_t frameIndex) const +{ + return m_descriptors->get(frameIndex); +} + +vk::Image ShadowMapping::shadowMapImage() const +{ + return m_shadowMap->get(); +} + +void ShadowMapping::setLightMatrix(const glm::mat4& lightSpaceMatrix, size_t frameIndex) +{ + SceneUBO ubo{}; + ubo.view = glm::mat4(1.0f); + ubo.projection = lightSpaceMatrix; + ubo.lightSpaceMatrix = glm::mat4(1.0f); + + // map buffer, copy matrix + void* data = m_mvpBuffer[frameIndex]->map(); + memcpy(data, &ubo, sizeof(SceneUBO)); + m_mvpBuffer[frameIndex]->unmap(); +} + } // namespace reactor \ No newline at end of file diff --git a/src/vulkan/ShadowMapping.hpp b/src/vulkan/ShadowMapping.hpp index fe03e4d..394ab4b 100644 --- a/src/vulkan/ShadowMapping.hpp +++ b/src/vulkan/ShadowMapping.hpp @@ -1,58 +1,58 @@ -#pragma once - -#include "Buffer.hpp" -#include "DescriptorSet.hpp" -#include "Image.hpp" -#include "Pipeline.hpp" -#include -#include - -namespace reactor -{ - -class VulkanRenderer; - -class ShadowMapping -{ -public: - explicit ShadowMapping(VulkanRenderer& renderer, uint32_t resolution = 2048); - ~ShadowMapping(); - - void recordShadowPass(vk::CommandBuffer cmd, size_t frameIndex, const std::function& drawCallback); - - vk::ImageView shadowMapView() const; - vk::Sampler shadowMapSampler() const; - vk::DescriptorSet shadowMapDescriptorSet(size_t frameIndex) const; - vk::Image shadowMapImage() const; - - void setLightMatrix(const glm::mat4& lightMVP, size_t frameIndex); - - uint32_t resolution() const - { - return m_resolution; - } - -private: - void createResources(); - void createPipeline(); - void createDescriptors(); - - const float depthBiasConstant = 1.25f; - const float depthBiasSlope = 1.75f; - - VulkanRenderer& m_renderer; - uint32_t m_resolution; - - // depth image, view, and sampler for shadow map - std::unique_ptr m_shadowMap; - vk::ImageView m_shadowMapView; - vk::Sampler m_shadowMapSampler; - - std::vector> m_mvpBuffer; - - std::unique_ptr m_descriptors; - - // Pipeline for depth pass - std::unique_ptr m_depthPassPipeline; -}; +#pragma once + +#include "Buffer.hpp" +#include "DescriptorSet.hpp" +#include "Image.hpp" +#include "Pipeline.hpp" +#include +#include + +namespace reactor +{ + +class VulkanRenderer; + +class ShadowMapping +{ +public: + explicit ShadowMapping(VulkanRenderer& renderer, uint32_t resolution = 2048); + ~ShadowMapping(); + + void recordShadowPass(vk::CommandBuffer cmd, size_t frameIndex, const std::function& drawCallback); + + vk::ImageView shadowMapView() const; + vk::Sampler shadowMapSampler() const; + vk::DescriptorSet shadowMapDescriptorSet(size_t frameIndex) const; + vk::Image shadowMapImage() const; + + void setLightMatrix(const glm::mat4& lightMVP, size_t frameIndex); + + uint32_t resolution() const + { + return m_resolution; + } + +private: + void createResources(); + void createPipeline(); + void createDescriptors(); + + const float depthBiasConstant = 1.25f; + const float depthBiasSlope = 1.75f; + + VulkanRenderer& m_renderer; + uint32_t m_resolution; + + // depth image, view, and sampler for shadow map + std::unique_ptr m_shadowMap; + vk::ImageView m_shadowMapView; + vk::Sampler m_shadowMapSampler; + + std::vector> m_mvpBuffer; + + std::unique_ptr m_descriptors; + + // Pipeline for depth pass + std::unique_ptr m_depthPassPipeline; +}; } // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Swapchain.cpp b/src/vulkan/Swapchain.cpp index 4360c3e..6906676 100644 --- a/src/vulkan/Swapchain.cpp +++ b/src/vulkan/Swapchain.cpp @@ -1,192 +1,192 @@ -#include "Swapchain.hpp" - -namespace reactor { - - // Helper structures and selection for formats/present modes - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - for (const auto& format : availableFormats) { - if (format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { - return format; - } - } - return availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - for (const auto& presentMode : availablePresentModes) { - if (presentMode == vk::PresentModeKHR::eMailbox) { - return presentMode; - } - } - return vk::PresentModeKHR::eFifo; // guaranteed to be available - } - - static vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities, const Window& window) { - if (capabilities.currentExtent.width != UINT32_MAX) { - return capabilities.currentExtent; - } else { - vk::Extent2D actualExtent = window.getFramebufferSize(); - actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); - actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - - return actualExtent; - } - } - - Swapchain::Swapchain(vk::Device device, vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface, const Window& window) - : m_device(device), m_physicalDevice(physicalDevice), m_surface(surface), m_window(window) { - - // Query surface capabilities - auto capabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); - auto formats = physicalDevice.getSurfaceFormatsKHR(surface); - auto presentModes = physicalDevice.getSurfacePresentModesKHR(surface); - - vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(formats); - vk::PresentModeKHR presentMode = chooseSwapPresentMode(presentModes); - vk::Extent2D extent = chooseSwapExtent(capabilities, window); - - uint32_t imageCount = capabilities.minImageCount + 1; - if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount) { - imageCount = capabilities.maxImageCount; - } - - vk::SwapchainCreateInfoKHR createInfo = {}; - createInfo.surface = surface; - createInfo.minImageCount = imageCount; - createInfo.imageFormat = surfaceFormat.format; - createInfo.imageColorSpace = surfaceFormat.colorSpace; - createInfo.imageExtent = extent; - createInfo.imageArrayLayers = 1; - createInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst; - - // Assume graphics and present queue families are the same for simplicity - createInfo.imageSharingMode = vk::SharingMode::eExclusive; - - createInfo.preTransform = capabilities.currentTransform; - createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; - createInfo.presentMode = presentMode; - createInfo.clipped = VK_TRUE; - createInfo.oldSwapchain = VK_NULL_HANDLE; - - m_swapchain = m_device.createSwapchainKHR(createInfo); - m_images = m_device.getSwapchainImagesKHR(m_swapchain); - m_format = surfaceFormat.format; - m_extent = extent; - - // Create image views - m_imageViews.reserve(m_images.size()); - for (auto image : m_images) { - vk::ImageViewCreateInfo viewInfo = {}; - viewInfo.image = image; - viewInfo.viewType = vk::ImageViewType::e2D; - viewInfo.format = m_format; - viewInfo.components = { - vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, - vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity - }; - viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - m_imageViews.push_back(m_device.createImageView(viewInfo)); - } - - spdlog::info("Created swapchain: format = {}, extent = {}x{}, images = {}, present mode = {}", - static_cast(surfaceFormat.format), - extent.width, extent.height, - m_images.size(), - static_cast(presentMode) - ); - - } - - Swapchain::~Swapchain() { - cleanup(); - } - - void Swapchain::cleanup() { - spdlog::info("Cleaning up swapchain resources"); - - for (auto view : m_imageViews) { - m_device.destroyImageView(view); - } - if (m_swapchain) { - m_device.destroySwapchainKHR(m_swapchain); - m_swapchain = nullptr; - } - m_imageViews.clear(); - m_images.clear(); - } - - void Swapchain::recreate() { - spdlog::info("Recreating swapchain..."); - - cleanup(); - - // Query and (re-)create swapchain just like constructor - auto capabilities = m_physicalDevice.getSurfaceCapabilitiesKHR(m_surface); - auto formats = m_physicalDevice.getSurfaceFormatsKHR(m_surface); - auto presentModes = m_physicalDevice.getSurfacePresentModesKHR(m_surface); - - vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(formats); - vk::PresentModeKHR presentMode = chooseSwapPresentMode(presentModes); - vk::Extent2D extent = chooseSwapExtent(capabilities, m_window); - - uint32_t imageCount = capabilities.minImageCount + 1; - if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount) { - imageCount = capabilities.maxImageCount; - } - - vk::SwapchainCreateInfoKHR createInfo = {}; - createInfo.surface = m_surface; - createInfo.minImageCount = imageCount; - createInfo.imageFormat = surfaceFormat.format; - createInfo.imageColorSpace = surfaceFormat.colorSpace; - createInfo.imageExtent = extent; - createInfo.imageArrayLayers = 1; - createInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment; - createInfo.imageSharingMode = vk::SharingMode::eExclusive; - createInfo.preTransform = capabilities.currentTransform; - createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; - createInfo.presentMode = presentMode; - createInfo.clipped = VK_TRUE; - createInfo.oldSwapchain = VK_NULL_HANDLE; - - m_swapchain = m_device.createSwapchainKHR(createInfo); - m_images = m_device.getSwapchainImagesKHR(m_swapchain); - m_format = surfaceFormat.format; - m_extent = extent; - - m_imageViews.reserve(m_images.size()); - for (auto image : m_images) { - vk::ImageViewCreateInfo viewInfo = {}; - viewInfo.image = image; - viewInfo.viewType = vk::ImageViewType::e2D; - viewInfo.format = m_format; - viewInfo.components = { - vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, - vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity - }; - viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - m_imageViews.push_back(m_device.createImageView(viewInfo)); - } - - spdlog::info("Swapchain recreated: format = {}, extent = {}x{}, images = {}, present mode = {}", - static_cast(m_format), - m_extent.width, m_extent.height, - m_images.size(), - static_cast(chooseSwapPresentMode( - m_physicalDevice.getSurfacePresentModesKHR(m_surface))) - ); - - } - -} +#include "Swapchain.hpp" + +namespace reactor { + + // Helper structures and selection for formats/present modes + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& format : availableFormats) { + if (format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { + return format; + } + } + return availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& presentMode : availablePresentModes) { + if (presentMode == vk::PresentModeKHR::eMailbox) { + return presentMode; + } + } + return vk::PresentModeKHR::eFifo; // guaranteed to be available + } + + static vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities, const Window& window) { + if (capabilities.currentExtent.width != UINT32_MAX) { + return capabilities.currentExtent; + } else { + vk::Extent2D actualExtent = window.getFramebufferSize(); + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + Swapchain::Swapchain(vk::Device device, vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface, const Window& window) + : m_device(device), m_physicalDevice(physicalDevice), m_surface(surface), m_window(window) { + + // Query surface capabilities + auto capabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + auto formats = physicalDevice.getSurfaceFormatsKHR(surface); + auto presentModes = physicalDevice.getSurfacePresentModesKHR(surface); + + vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(formats); + vk::PresentModeKHR presentMode = chooseSwapPresentMode(presentModes); + vk::Extent2D extent = chooseSwapExtent(capabilities, window); + + uint32_t imageCount = capabilities.minImageCount + 1; + if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount) { + imageCount = capabilities.maxImageCount; + } + + vk::SwapchainCreateInfoKHR createInfo = {}; + createInfo.surface = surface; + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst; + + // Assume graphics and present queue families are the same for simplicity + createInfo.imageSharingMode = vk::SharingMode::eExclusive; + + createInfo.preTransform = capabilities.currentTransform; + createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + createInfo.oldSwapchain = VK_NULL_HANDLE; + + m_swapchain = m_device.createSwapchainKHR(createInfo); + m_images = m_device.getSwapchainImagesKHR(m_swapchain); + m_format = surfaceFormat.format; + m_extent = extent; + + // Create image views + m_imageViews.reserve(m_images.size()); + for (auto image : m_images) { + vk::ImageViewCreateInfo viewInfo = {}; + viewInfo.image = image; + viewInfo.viewType = vk::ImageViewType::e2D; + viewInfo.format = m_format; + viewInfo.components = { + vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, + vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity + }; + viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + m_imageViews.push_back(m_device.createImageView(viewInfo)); + } + + spdlog::info("Created swapchain: format = {}, extent = {}x{}, images = {}, present mode = {}", + static_cast(surfaceFormat.format), + extent.width, extent.height, + m_images.size(), + static_cast(presentMode) + ); + + } + + Swapchain::~Swapchain() { + cleanup(); + } + + void Swapchain::cleanup() { + spdlog::info("Cleaning up swapchain resources"); + + for (auto view : m_imageViews) { + m_device.destroyImageView(view); + } + if (m_swapchain) { + m_device.destroySwapchainKHR(m_swapchain); + m_swapchain = nullptr; + } + m_imageViews.clear(); + m_images.clear(); + } + + void Swapchain::recreate() { + spdlog::info("Recreating swapchain..."); + + cleanup(); + + // Query and (re-)create swapchain just like constructor + auto capabilities = m_physicalDevice.getSurfaceCapabilitiesKHR(m_surface); + auto formats = m_physicalDevice.getSurfaceFormatsKHR(m_surface); + auto presentModes = m_physicalDevice.getSurfacePresentModesKHR(m_surface); + + vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(formats); + vk::PresentModeKHR presentMode = chooseSwapPresentMode(presentModes); + vk::Extent2D extent = chooseSwapExtent(capabilities, m_window); + + uint32_t imageCount = capabilities.minImageCount + 1; + if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount) { + imageCount = capabilities.maxImageCount; + } + + vk::SwapchainCreateInfoKHR createInfo = {}; + createInfo.surface = m_surface; + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment; + createInfo.imageSharingMode = vk::SharingMode::eExclusive; + createInfo.preTransform = capabilities.currentTransform; + createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + createInfo.oldSwapchain = VK_NULL_HANDLE; + + m_swapchain = m_device.createSwapchainKHR(createInfo); + m_images = m_device.getSwapchainImagesKHR(m_swapchain); + m_format = surfaceFormat.format; + m_extent = extent; + + m_imageViews.reserve(m_images.size()); + for (auto image : m_images) { + vk::ImageViewCreateInfo viewInfo = {}; + viewInfo.image = image; + viewInfo.viewType = vk::ImageViewType::e2D; + viewInfo.format = m_format; + viewInfo.components = { + vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, + vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity + }; + viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + m_imageViews.push_back(m_device.createImageView(viewInfo)); + } + + spdlog::info("Swapchain recreated: format = {}, extent = {}x{}, images = {}, present mode = {}", + static_cast(m_format), + m_extent.width, m_extent.height, + m_images.size(), + static_cast(chooseSwapPresentMode( + m_physicalDevice.getSurfacePresentModesKHR(m_surface))) + ); + + } + +} diff --git a/src/vulkan/Swapchain.hpp b/src/vulkan/Swapchain.hpp index 2f34678..737e0f2 100644 --- a/src/vulkan/Swapchain.hpp +++ b/src/vulkan/Swapchain.hpp @@ -1,44 +1,44 @@ -// -// Created by rfdic on 6/9/2025. -// - -#ifndef SWAPCHAIN_HPP -#define SWAPCHAIN_HPP - -#include -#include - -#include "../core/Window.hpp" - -namespace reactor { - class Swapchain { - public: - Swapchain(vk::Device device, vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface, const Window& window); - ~Swapchain(); - - // Public interface - [[nodiscard]] vk::SwapchainKHR get() const { return m_swapchain; } - [[nodiscard]] vk::Format getFormat() const { return m_format; } - [[nodiscard]] vk::Extent2D getExtent() const { return m_extent; } - [[nodiscard]] const std::vector& getImageViews() const { return m_imageViews; } - [[nodiscard]] const std::vector& getImages() const { return m_images; } - - void recreate(); - - private: - void cleanup(); - - vk::Device m_device; // Store a copy for cleanup - vk::SwapchainKHR m_swapchain; - std::vector m_images; - std::vector m_imageViews; - vk::Format m_format; - vk::Extent2D m_extent; - - vk::PhysicalDevice m_physicalDevice; - vk::SurfaceKHR m_surface; - const Window& m_window; // non-owning - }; -} - -#endif //SWAPCHAIN_HPP +// +// Created by rfdic on 6/9/2025. +// + +#ifndef SWAPCHAIN_HPP +#define SWAPCHAIN_HPP + +#include +#include + +#include "../core/Window.hpp" + +namespace reactor { + class Swapchain { + public: + Swapchain(vk::Device device, vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface, const Window& window); + ~Swapchain(); + + // Public interface + [[nodiscard]] vk::SwapchainKHR get() const { return m_swapchain; } + [[nodiscard]] vk::Format getFormat() const { return m_format; } + [[nodiscard]] vk::Extent2D getExtent() const { return m_extent; } + [[nodiscard]] const std::vector& getImageViews() const { return m_imageViews; } + [[nodiscard]] const std::vector& getImages() const { return m_images; } + + void recreate(); + + private: + void cleanup(); + + vk::Device m_device; // Store a copy for cleanup + vk::SwapchainKHR m_swapchain; + std::vector m_images; + std::vector m_imageViews; + vk::Format m_format; + vk::Extent2D m_extent; + + vk::PhysicalDevice m_physicalDevice; + vk::SurfaceKHR m_surface; + const Window& m_window; // non-owning + }; +} + +#endif //SWAPCHAIN_HPP diff --git a/src/vulkan/UniformManager.cpp b/src/vulkan/UniformManager.cpp index 4fcb196..b39c000 100644 --- a/src/vulkan/UniformManager.cpp +++ b/src/vulkan/UniformManager.cpp @@ -1,26 +1,28 @@ -// -// Created by rfdic on 6/29/2025. -// - -#include "UniformManager.hpp" - -namespace reactor { - - UniformManager::UniformManager(Allocator &allocator, size_t framesInFlight) - : m_allocator(allocator), m_framesInFlight(framesInFlight) { } - -std::vector> - UniformManager::createFrameSpecificBuffers(vk::DeviceSize size) { - std::vector> buffers; - for (size_t i = 0; i < m_framesInFlight; i++) { - buffers.push_back(std::make_unique( - m_allocator, - size, - vk::BufferUsageFlagBits::eUniformBuffer, - VMA_MEMORY_USAGE_CPU_TO_GPU, "Uniform buffer")); - } - return buffers; - } - - -} // reactor \ No newline at end of file +// +// Created by rfdic on 6/29/2025. +// + +#include "UniformManager.hpp" + +namespace reactor +{ + +UniformManager::UniformManager(Allocator& allocator, size_t framesInFlight) + : m_allocator(allocator), m_framesInFlightCount(framesInFlight) +{} + +std::vector> UniformManager::createFrameSpecificBuffers(vk::DeviceSize size) +{ + std::vector> buffers; + buffers.reserve(m_framesInFlightCount); + + for (size_t frame = 0; frame < m_framesInFlightCount; ++frame) + { + buffers.emplace_back(std::make_unique( + m_allocator, size, vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU, "Uniform buffer")); + } + + return buffers; +} + +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/UniformManager.hpp b/src/vulkan/UniformManager.hpp index 06fbcaa..644b24a 100644 --- a/src/vulkan/UniformManager.hpp +++ b/src/vulkan/UniformManager.hpp @@ -1,61 +1,61 @@ -#pragma once -#include "Allocator.hpp" -#include "Buffer.hpp" - -#include - -namespace reactor { - -class UniformManager { -public: - UniformManager(Allocator& allocator, size_t framesInFlight); - ~UniformManager() = default; - - // Register a new type of UBO. This creates the buffers - template - void registerUBO(const std::string& name) { - m_uniformBuffers[name] = createFrameSpecificBuffers(sizeof(T)); - m_uboTypeMap[std::type_index(typeid(T))] = name; - } - - // Update the buffer for a specific UBO type on the current frame. - template - void update(size_t frameIndex, const T& data) { - const auto& name = m_uboTypeMap.at(std::type_index(typeid(T))); - auto& buffer = m_uniformBuffers.at(name)[frameIndex]; - - void* mappedData = nullptr; - vmaMapMemory(m_allocator.getAllocator(), buffer->allocation(), &mappedData); - memcpy(mappedData, &data, sizeof(T)); - vmaUnmapMemory(m_allocator.getAllocator(), buffer->allocation()); - } - - // Get the descriptor info need to update a descriptor set. - template - [[nodiscard]] vk::DescriptorBufferInfo getDescriptorInfo(size_t frameIndex) const { - const auto& name = m_uboTypeMap.at(std::type_index(typeid(T))); - const auto& buffer = m_uniformBuffers.at(name)[frameIndex]; - - vk::DescriptorBufferInfo bufferInfo{}; - bufferInfo.buffer = buffer->getHandle(); - bufferInfo.offset = 0; - bufferInfo.range = buffer->size(); - return bufferInfo; - } - -private: - std::vector> createFrameSpecificBuffers(vk::DeviceSize size); - - Allocator& m_allocator; - size_t m_framesInFlight; - - // maps a string name to a vector of buffers (one for each frame of flight) - std::unordered_map>> m_uniformBuffers; - - // maps a C++ type to its registered string name for easy lookup - std::unordered_map m_uboTypeMap; - -}; - -} // reactor - +#pragma once +#include "Allocator.hpp" +#include "Buffer.hpp" + +#include + +namespace reactor { + +class UniformManager { +public: + UniformManager(Allocator& allocator, size_t framesInFlight); + ~UniformManager() = default; + + // Register a new type of UBO. This creates the buffers + template + void registerUBO(const std::string& name) { + m_uniformBuffers[name] = createFrameSpecificBuffers(sizeof(T)); + m_uboTypeMap[std::type_index(typeid(T))] = name; + } + + // Update the buffer for a specific UBO type on the current frame. + template + void update(size_t frameIndex, const T& data) { + const auto& name = m_uboTypeMap.at(std::type_index(typeid(T))); + auto& buffer = m_uniformBuffers.at(name)[frameIndex]; + + void* mappedData = nullptr; + vmaMapMemory(m_allocator.getAllocator(), buffer->allocation(), &mappedData); + memcpy(mappedData, &data, sizeof(T)); + vmaUnmapMemory(m_allocator.getAllocator(), buffer->allocation()); + } + + // Get the descriptor info need to update a descriptor set. + template + [[nodiscard]] vk::DescriptorBufferInfo getDescriptorInfo(size_t frameIndex) const { + const auto& name = m_uboTypeMap.at(std::type_index(typeid(T))); + const auto& buffer = m_uniformBuffers.at(name)[frameIndex]; + + vk::DescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = buffer->getHandle(); + bufferInfo.offset = 0; + bufferInfo.range = buffer->size(); + return bufferInfo; + } + +private: + std::vector> createFrameSpecificBuffers(vk::DeviceSize size); + + Allocator& m_allocator; + size_t m_framesInFlightCount; + + // maps a string name to a vector of buffers (one for each frame of flight) + std::unordered_map>> m_uniformBuffers; + + // maps a C++ type to its registered string name for easy lookup + std::unordered_map m_uboTypeMap; + +}; + +} // reactor + diff --git a/src/vulkan/Vertex.hpp b/src/vulkan/Vertex.hpp index e7309d3..773b535 100644 --- a/src/vulkan/Vertex.hpp +++ b/src/vulkan/Vertex.hpp @@ -1,51 +1,51 @@ -#pragma once - -#include -#include - -#include - -namespace reactor -{ - -struct Vertex -{ - glm::vec3 pos; - glm::vec3 normal; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() - { - return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; - } - - static std::array getAttributeDescriptions() - { - std::array attributeDescriptions{}; - - attributeDescriptions[0].binding = 0; - attributeDescriptions[0].location = 0; - attributeDescriptions[0].format = vk::Format::eR32G32B32Sfloat; - attributeDescriptions[0].offset = offsetof(Vertex, pos); - - attributeDescriptions[1].binding = 0; - attributeDescriptions[1].location = 1; - attributeDescriptions[1].format = vk::Format::eR32G32B32Sfloat; - attributeDescriptions[1].offset = offsetof(Vertex, normal); - - attributeDescriptions[2].binding = 0; - attributeDescriptions[2].location = 2; - attributeDescriptions[2].format = vk::Format::eR32G32B32Sfloat; - attributeDescriptions[2].offset = offsetof(Vertex, color); - - attributeDescriptions[3].binding = 0; - attributeDescriptions[3].location = 3; - attributeDescriptions[3].format = vk::Format::eR32G32Sfloat; - attributeDescriptions[3].offset = offsetof(Vertex, texCoord); - - return attributeDescriptions; - } -}; - +#pragma once + +#include +#include + +#include + +namespace reactor +{ + +struct Vertex +{ + glm::vec3 pos; + glm::vec3 normal; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = vk::Format::eR32G32B32Sfloat; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = vk::Format::eR32G32B32Sfloat; + attributeDescriptions[1].offset = offsetof(Vertex, normal); + + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = vk::Format::eR32G32B32Sfloat; + attributeDescriptions[2].offset = offsetof(Vertex, color); + + attributeDescriptions[3].binding = 0; + attributeDescriptions[3].location = 3; + attributeDescriptions[3].format = vk::Format::eR32G32Sfloat; + attributeDescriptions[3].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } +}; + } // namespace reactor \ No newline at end of file diff --git a/src/vulkan/VulkanContext.cpp b/src/vulkan/VulkanContext.cpp index e953c3a..d859862 100644 --- a/src/vulkan/VulkanContext.cpp +++ b/src/vulkan/VulkanContext.cpp @@ -1,220 +1,220 @@ -#include "VulkanContext.hpp" - -#include - -#include "../logging/Logger.hpp" - -VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE - -namespace { -void printVulkanVersion() { - uint32_t vulkanApiVersion = 0; - if (vk::enumerateInstanceVersion(&vulkanApiVersion) == vk::Result::eSuccess) { - uint32_t major = VK_VERSION_MAJOR(vulkanApiVersion); - uint32_t minor = VK_VERSION_MINOR(vulkanApiVersion); - uint32_t patch = VK_VERSION_PATCH(vulkanApiVersion); - LOG_INFO("Vulkan API version: {}.{}.{}", major, minor, patch); - } else { - spdlog::error("Failed to enumerate Vulkan API version"); - } -} -} - -namespace reactor { - -VulkanContext::VulkanContext(GLFWwindow* window) { - spdlog::info("Creating Vulkan context"); - createInstance(); - createSurface(window); - pickPhysicalDevice(); - createLogicalDevice(); - -} - -VulkanContext::~VulkanContext() { - m_device.destroy(); - m_instance.destroySurfaceKHR(m_surface); - m_instance.destroy(); -} - -void VulkanContext::createInstance() { - - VULKAN_HPP_DEFAULT_DISPATCHER.init( ); - - constexpr vk::ApplicationInfo appInfo { - "Reactor App", - 1, - "No Engine", - 1, - VK_API_VERSION_1_3 - }; - - uint32_t glfwExtensionCount = 0; - const char** glfwExtensions; - glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - - // add the GLFW extensions - - #ifdef __APPLE__ - extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); - #endif - - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - - vk::InstanceCreateInfo createInfo {}; - createInfo.pApplicationInfo = &appInfo; - createInfo.setPEnabledExtensionNames(extensions); - - #ifdef __APPLE__ - createInfo.flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR; - #endif - - try { - m_instance = vk::createInstance(createInfo); - spdlog::info("Vulkan instance created"); - printVulkanVersion(); - VULKAN_HPP_DEFAULT_DISPATCHER.init( m_instance ); - - } catch (const vk::SystemError& err) { - std::cerr << "Failed to create Vulkan instance: " << err.what() << std::endl; - throw; - } -} - -void VulkanContext::createSurface(GLFWwindow *window) { - if (glfwCreateWindowSurface(m_instance, window, nullptr, reinterpret_cast(&m_surface) -) != VK_SUCCESS) { - throw std::runtime_error("Failed to create window surface!"); - } - -} - - void VulkanContext::pickPhysicalDevice() { - uint32_t deviceCount = 0; - if (m_instance.enumeratePhysicalDevices(&deviceCount, nullptr) != vk::Result::eSuccess || deviceCount == 0) { - throw std::runtime_error("Failed to find GPUs with Vulkan support!"); - } - - std::vector devices(deviceCount); - auto result = m_instance.enumeratePhysicalDevices(&deviceCount, devices.data()); - if (result != vk::Result::eSuccess) { - throw std::runtime_error("Failed to enumerate GPUs with Vulkan support!"); - } - - for (const auto& device : devices) { - if (isDeviceSuitable(device)) { - m_physicalDevice = device; - vk::PhysicalDeviceProperties properties = m_physicalDevice.getProperties(); - spdlog::info("Selected physical device: {}", static_cast(properties.deviceName)); - - return; - } - } - - throw std::runtime_error("Failed to find a suitable GPU!"); -} - - bool VulkanContext::isDeviceSuitable(vk::PhysicalDevice device) { - const QueueFamilyIndices indices = findQueueFamilies(device); - - // Basic check for device suitability: does it have a graphics and present queue? - // You'll likely want to add more checks here, e.g., device extensions, swap chain support. - return indices.isComplete(); -} - -QueueFamilyIndices VulkanContext::findQueueFamilies(vk::PhysicalDevice device) const { - QueueFamilyIndices indices; - - const std::vector queueFamilies = device.getQueueFamilyProperties(); - - int i = 0; - for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueFlags & vk::QueueFlagBits::eGraphics) { - indices.graphicsFamily = i; - } - - vk::Bool32 presentSupport = vk::False; - if (const auto result = device.getSurfaceSupportKHR(i, m_surface, &presentSupport); - result != vk::Result::eSuccess) { - throw std::runtime_error("Failed to get surface support!"); - } - - if (presentSupport) { - indices.presentFamily = i; - } - - if (indices.isComplete()) { - break; - } - - i++; - } - - return indices; -} - -void VulkanContext::createLogicalDevice() { - QueueFamilyIndices indices = findQueueFamilies(m_physicalDevice); - - std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; - - float queuePriority = 1.0f; - for (uint32_t queueFamily : uniqueQueueFamilies) { - vk::DeviceQueueCreateInfo queueCreateInfo{}; - queueCreateInfo.queueFamilyIndex = queueFamily; - queueCreateInfo.queueCount = 1; - queueCreateInfo.pQueuePriorities = &queuePriority; - queueCreateInfos.push_back(queueCreateInfo); - } - - vk::PhysicalDeviceFeatures deviceFeatures{}; // No special features for now - - vk::PhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures{}; - dynamicRenderingFeatures.dynamicRendering = VK_TRUE; - - // create struct for Vulkan 1.1 features - vk::PhysicalDeviceVulkan11Features vulkan11Features{}; - vulkan11Features.shaderDrawParameters = VK_TRUE; - - dynamicRenderingFeatures.pNext = &vulkan11Features; - - std::vector deviceExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME, - // VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME - }; - - vk::DeviceCreateInfo createInfo{}; - createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); - createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.pEnabledFeatures = &deviceFeatures; - - createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); - createInfo.ppEnabledExtensionNames = deviceExtensions.data(); - createInfo.pNext = &dynamicRenderingFeatures; - - #ifdef __APPLE__ - // Required for MoltenVK - std::vector deviceExtensions = { "VK_KHR_portability_subset" }; - createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); - createInfo.ppEnabledExtensionNames = deviceExtensions.data(); - #endif - - try { - m_device = m_physicalDevice.createDevice(createInfo); - m_graphicsQueue = m_device.getQueue(indices.graphicsFamily.value(), 0); - m_presentQueue = m_device.getQueue(indices.presentFamily.value(), 0); - m_queueFamilies = indices; // Store the found queue families - - m_dldi = vk::detail::DispatchLoaderDynamic(m_instance, vkGetInstanceProcAddr, m_device, vkGetDeviceProcAddr); - spdlog::info("Logical device created"); - } catch (const vk::SystemError& err) { - std::cerr << "Failed to create logical device: " << err.what() << std::endl; - throw; - } -} - - +#include "VulkanContext.hpp" + +#include + +#include "../logging/Logger.hpp" + +VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE + +namespace { +void printVulkanVersion() { + uint32_t vulkanApiVersion = 0; + if (vk::enumerateInstanceVersion(&vulkanApiVersion) == vk::Result::eSuccess) { + uint32_t major = VK_VERSION_MAJOR(vulkanApiVersion); + uint32_t minor = VK_VERSION_MINOR(vulkanApiVersion); + uint32_t patch = VK_VERSION_PATCH(vulkanApiVersion); + LOG_INFO("Vulkan API version: {}.{}.{}", major, minor, patch); + } else { + spdlog::error("Failed to enumerate Vulkan API version"); + } +} +} + +namespace reactor { + +VulkanContext::VulkanContext(GLFWwindow* window) { + spdlog::info("Creating Vulkan context"); + createInstance(); + createSurface(window); + pickPhysicalDevice(); + createLogicalDevice(); + +} + +VulkanContext::~VulkanContext() { + m_device.destroy(); + m_instance.destroySurfaceKHR(m_surface); + m_instance.destroy(); +} + +void VulkanContext::createInstance() { + + VULKAN_HPP_DEFAULT_DISPATCHER.init( ); + + constexpr vk::ApplicationInfo appInfo { + "Reactor App", + 1, + "No Engine", + 1, + VK_API_VERSION_1_3 + }; + + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + // add the GLFW extensions + + #ifdef __APPLE__ + extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + #endif + + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + vk::InstanceCreateInfo createInfo {}; + createInfo.pApplicationInfo = &appInfo; + createInfo.setPEnabledExtensionNames(extensions); + + #ifdef __APPLE__ + createInfo.flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR; + #endif + + try { + m_instance = vk::createInstance(createInfo); + spdlog::info("Vulkan instance created"); + printVulkanVersion(); + VULKAN_HPP_DEFAULT_DISPATCHER.init( m_instance ); + + } catch (const vk::SystemError& err) { + std::cerr << "Failed to create Vulkan instance: " << err.what() << std::endl; + throw; + } +} + +void VulkanContext::createSurface(GLFWwindow *window) { + if (glfwCreateWindowSurface(m_instance, window, nullptr, reinterpret_cast(&m_surface) +) != VK_SUCCESS) { + throw std::runtime_error("Failed to create window surface!"); + } + +} + + void VulkanContext::pickPhysicalDevice() { + uint32_t deviceCount = 0; + if (m_instance.enumeratePhysicalDevices(&deviceCount, nullptr) != vk::Result::eSuccess || deviceCount == 0) { + throw std::runtime_error("Failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + auto result = m_instance.enumeratePhysicalDevices(&deviceCount, devices.data()); + if (result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to enumerate GPUs with Vulkan support!"); + } + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + m_physicalDevice = device; + vk::PhysicalDeviceProperties properties = m_physicalDevice.getProperties(); + spdlog::info("Selected physical device: {}", static_cast(properties.deviceName)); + + return; + } + } + + throw std::runtime_error("Failed to find a suitable GPU!"); +} + + bool VulkanContext::isDeviceSuitable(vk::PhysicalDevice device) { + const QueueFamilyIndices indices = findQueueFamilies(device); + + // Basic check for device suitability: does it have a graphics and present queue? + // You'll likely want to add more checks here, e.g., device extensions, swap chain support. + return indices.isComplete(); +} + +QueueFamilyIndices VulkanContext::findQueueFamilies(vk::PhysicalDevice device) const { + QueueFamilyIndices indices; + + const std::vector queueFamilies = device.getQueueFamilyProperties(); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & vk::QueueFlagBits::eGraphics) { + indices.graphicsFamily = i; + } + + vk::Bool32 presentSupport = vk::False; + if (const auto result = device.getSurfaceSupportKHR(i, m_surface, &presentSupport); + result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to get surface support!"); + } + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; +} + +void VulkanContext::createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(m_physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + vk::DeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + vk::PhysicalDeviceFeatures deviceFeatures{}; // No special features for now + + vk::PhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures{}; + dynamicRenderingFeatures.dynamicRendering = VK_TRUE; + + // create struct for Vulkan 1.1 features + vk::PhysicalDeviceVulkan11Features vulkan11Features{}; + vulkan11Features.shaderDrawParameters = VK_TRUE; + + dynamicRenderingFeatures.pNext = &vulkan11Features; + + std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + // VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME + }; + + vk::DeviceCreateInfo createInfo{}; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + createInfo.pNext = &dynamicRenderingFeatures; + + #ifdef __APPLE__ + // Required for MoltenVK + std::vector deviceExtensions = { "VK_KHR_portability_subset" }; + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + #endif + + try { + m_device = m_physicalDevice.createDevice(createInfo); + m_graphicsQueue = m_device.getQueue(indices.graphicsFamily.value(), 0); + m_presentQueue = m_device.getQueue(indices.presentFamily.value(), 0); + m_queueFamilies = indices; // Store the found queue families + + m_dldi = vk::detail::DispatchLoaderDynamic(m_instance, vkGetInstanceProcAddr, m_device, vkGetDeviceProcAddr); + spdlog::info("Logical device created"); + } catch (const vk::SystemError& err) { + std::cerr << "Failed to create logical device: " << err.what() << std::endl; + throw; + } +} + + } // reactor \ No newline at end of file diff --git a/src/vulkan/VulkanContext.hpp b/src/vulkan/VulkanContext.hpp index 16d08f2..9cb18f8 100644 --- a/src/vulkan/VulkanContext.hpp +++ b/src/vulkan/VulkanContext.hpp @@ -1,63 +1,63 @@ -#pragma once - -#include -#include -#include - -namespace reactor { - -struct QueueFamilyIndices { - std::optional graphicsFamily; - std::optional presentFamily; - - [[nodiscard]] bool isComplete() const { - return graphicsFamily.has_value() && presentFamily.has_value(); - } -}; - -class VulkanContext { -public: - explicit VulkanContext(GLFWwindow* window); - - ~VulkanContext(); - - VulkanContext(const VulkanContext&) = delete; - VulkanContext& operator=(const VulkanContext&) = delete; - VulkanContext(VulkanContext&&) = delete; - VulkanContext& operator=(VulkanContext&&) = delete; - - [[nodiscard]] vk::Instance instance() const { return m_instance; } - [[nodiscard]] vk::PhysicalDevice physicalDevice() const { return m_physicalDevice; } - [[nodiscard]] vk::Device device() const { return m_device; } - [[nodiscard]] vk::SurfaceKHR surface() const { return m_surface; } - [[nodiscard]] vk::Queue graphicsQueue() const { return m_graphicsQueue; } - [[nodiscard]] vk::Queue presentQueue() const { return m_presentQueue; } - [[nodiscard]] QueueFamilyIndices queueFamilies() const { return m_queueFamilies; } - [[nodiscard]] const vk::detail::DispatchLoaderDynamic& dldi() const { return m_dldi; } - -private: - // Private helper methods to keep the constructor clean - void createInstance(); - void createSurface(GLFWwindow* window); - void pickPhysicalDevice(); - void createLogicalDevice(); - - bool isDeviceSuitable(vk::PhysicalDevice device); - [[nodiscard]] QueueFamilyIndices findQueueFamilies(vk::PhysicalDevice device) const; - - // Member variables - these are owned by the context - vk::Instance m_instance; - vk::SurfaceKHR m_surface; - vk::PhysicalDevice m_physicalDevice; - vk::Device m_device; - vk::detail::DispatchLoaderDynamic m_dldi; - - // Queues are retrieved from the logical device - vk::Queue m_graphicsQueue; - vk::Queue m_presentQueue; - QueueFamilyIndices m_queueFamilies; - -}; - -} // reactor - +#pragma once + +#include +#include +#include + +namespace reactor { + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + [[nodiscard]] bool isComplete() const { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +class VulkanContext { +public: + explicit VulkanContext(GLFWwindow* window); + + ~VulkanContext(); + + VulkanContext(const VulkanContext&) = delete; + VulkanContext& operator=(const VulkanContext&) = delete; + VulkanContext(VulkanContext&&) = delete; + VulkanContext& operator=(VulkanContext&&) = delete; + + [[nodiscard]] vk::Instance instance() const { return m_instance; } + [[nodiscard]] vk::PhysicalDevice physicalDevice() const { return m_physicalDevice; } + [[nodiscard]] vk::Device device() const { return m_device; } + [[nodiscard]] vk::SurfaceKHR surface() const { return m_surface; } + [[nodiscard]] vk::Queue graphicsQueue() const { return m_graphicsQueue; } + [[nodiscard]] vk::Queue presentQueue() const { return m_presentQueue; } + [[nodiscard]] QueueFamilyIndices queueFamilies() const { return m_queueFamilies; } + [[nodiscard]] const vk::detail::DispatchLoaderDynamic& dldi() const { return m_dldi; } + +private: + // Private helper methods to keep the constructor clean + void createInstance(); + void createSurface(GLFWwindow* window); + void pickPhysicalDevice(); + void createLogicalDevice(); + + bool isDeviceSuitable(vk::PhysicalDevice device); + [[nodiscard]] QueueFamilyIndices findQueueFamilies(vk::PhysicalDevice device) const; + + // Member variables - these are owned by the context + vk::Instance m_instance; + vk::SurfaceKHR m_surface; + vk::PhysicalDevice m_physicalDevice; + vk::Device m_device; + vk::detail::DispatchLoaderDynamic m_dldi; + + // Queues are retrieved from the logical device + vk::Queue m_graphicsQueue; + vk::Queue m_presentQueue; + QueueFamilyIndices m_queueFamilies; + +}; + +} // reactor + diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index b0aeafb..11457a3 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -1,838 +1,839 @@ -#include "VulkanRenderer.hpp" - -#include "../core/ModelIO.hpp" -#include "../core/Uniforms.hpp" -#include "../core/Window.hpp" -#include "../logging/ImGuiConsoleSink.hpp" -#include "../logging/Logger.hpp" -#include "../logging/SpdlogSink.hpp" -#include "DebugUtils.hpp" -#include "ImageUtils.hpp" -#include "VulkanUtils.hpp" - -#include -#include - -namespace reactor -{ -VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Camera& camera) - : m_config(config), m_window(window), m_camera(camera) -{ - - auto imguiConsoleSink = std::make_shared(); - auto spdlogSing = std::make_shared(); - - Logger::getInstance().addSink(imguiConsoleSink); - Logger::getInstance().addSink(spdlogSing); - - LOG_INFO("Created the logger"); - - createCoreVulkanObjects(); - createSwapchainAndFrameManager(); - - // Setup the Uniform Manager - m_uniformManager = std::make_unique(*m_allocator, m_frameManager->getFramesInFlightCount()); - m_uniformManager->registerUBO("scene"); - m_uniformManager->registerUBO("composite"); - m_uniformManager->registerUBO("lighting"); - - createDescriptorPool(); - createPipelineAndDescriptors(); - setupUI(imguiConsoleSink); - createMSAAImage(); - createResolveImages(); - createSceneViewImages(); - createDepthImages(); - createSampler(); - createDescriptorSets(); - createDepthPipelineAndDescriptorSets(); - initScene(); - - m_shadowMapping = std::make_unique(*this); - m_imageStateTracker.recordState(m_shadowMapping->shadowMapImage(), vk::ImageLayout::eUndefined); -} - -Allocator& VulkanRenderer::allocator() -{ - return *m_allocator; -} - -vk::Device VulkanRenderer::device() const -{ - return m_context->device(); -} - -vk::DescriptorPool VulkanRenderer::descriptorPool() const -{ - return m_descriptorPool; -} - -void VulkanRenderer::createCoreVulkanObjects() -{ - m_context = std::make_unique(m_window.getNativeWindow()); - m_allocator = std::make_unique(m_context->physicalDevice(), - m_context->device(), - m_context->instance(), - m_context->graphicsQueue(), - m_context->queueFamilies().graphicsFamily.value()); -} - -void VulkanRenderer::createSwapchainAndFrameManager() -{ - m_swapchain = - std::make_unique(m_context->device(), m_context->physicalDevice(), m_context->surface(), m_window); - - uint32_t swapchainImageCount = m_swapchain->getImageViews().size(); - m_frameManager = std::make_unique(m_context->device(), *m_allocator, 0, 2, swapchainImageCount); - - for (const auto& image : m_swapchain->getImages()) - { - m_imageStateTracker.recordState(image, vk::ImageLayout::eUndefined); - } -} - -void VulkanRenderer::createDescriptorPool() -{ - std::vector poolSizes = {{vk::DescriptorType::eUniformBuffer, 32}, - {vk::DescriptorType::eCombinedImageSampler, 32}, - {vk::DescriptorType::eSampledImage, 32}, - {vk::DescriptorType::eSampler, 32}}; - - vk::DescriptorPoolCreateInfo poolInfo(vk::DescriptorPoolCreateFlags(), 128, poolSizes.size(), poolSizes.data()); - - m_descriptorPool = m_context->device().createDescriptorPool(poolInfo); -} - -void VulkanRenderer::createPipelineAndDescriptors() -{ - const std::string vertShaderPath = m_config.vertShaderPath; - const std::string fragShaderPath = m_config.fragShaderPath; - - const std::vector bindings = { - // Binding 0: Scene UBO (Vertex Shader) - vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex), - - // Binding 1: Light UBO (Fragment Shader) - vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment), - - // Binding 2: Shadow Map Texture (Fragment Shader) - vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), - - // Binding 3: Shadow Map Sampler - vk::DescriptorSetLayoutBinding(3, vk::DescriptorType::eSampler, 1, vk::ShaderStageFlagBits::eFragment), - }; - m_descriptorSet = std::make_unique(m_context->device(), m_descriptorPool, 2, bindings); - const std::vector setLayouts = {m_descriptorSet->getLayout()}; - - m_pipeline = Pipeline::Builder(m_context->device()) - .setVertexShader(m_config.vertShaderPath) - .setFragmentShader(m_config.fragShaderPath) - .setVertexInputFromVertex() - .setColorAttachment(vk::Format::eR16G16B16A16Sfloat) - .setDepthAttachment(vk::Format::eD32Sfloat, true) // depth test and write - .setDescriptorSetLayouts(setLayouts) - .setMultisample(4) - .setFrontFace(vk::FrontFace::eClockwise) // Assuming standard winding order for cubes - .addPushContantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)) - .build(); - - Debug::setObjectName(m_context->device(), - (uint64_t)(VkPipeline)m_pipeline->get(), - vk::ObjectType::ePipeline, - "Main Geometry Pipeline"); - - const std::vector compositeBindings = { - // binding 0: uInputImage (Texture2D) - vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), - // binding 1: CompositeParams (UBO) - vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment), - // binding 2: uDepthImage (Texture2DMS) - vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), - // binding 3: g_sampler (SamplerState) - vk::DescriptorSetLayoutBinding(3, vk::DescriptorType::eSampler, 1, vk::ShaderStageFlagBits::eFragment), - - }; - - m_compositeDescriptorSet = - std::make_unique(m_context->device(), m_descriptorPool, 2, compositeBindings); - std::vector compositeSetLayouts = {m_compositeDescriptorSet->getLayout()}; - - m_compositePipeline = Pipeline::Builder(m_context->device()) - .setVertexShader(m_config.compositeVertShaderPath) - .setFragmentShader(m_config.compositeFragShaderPath) - .setColorAttachment(m_swapchain->getFormat()) - .setDescriptorSetLayouts(compositeSetLayouts) - .setMultisample(1) // No MSAA for composite pass - .setFrontFace(vk::FrontFace::eClockwise) // Assuming standard winding order for cubes - .build(); -} - -void VulkanRenderer::handleSwapchainResizing() -{ - if (m_window.wasResized()) - { - vk::Extent2D size = m_window.getFramebufferSize(); - while (size.width == 0 || size.height == 0) - { - Window::waitEvents(); - size = m_window.getFramebufferSize(); - } - m_context->device().waitIdle(); - m_swapchain->recreate(); - m_window.resetResizedFlag(); - } -} - -void VulkanRenderer::setupUI(std::shared_ptr consoleSink) -{ - m_imgui = std::make_unique(*m_context, m_window, m_window.getEventManager(), consoleSink); -} - -VulkanRenderer::~VulkanRenderer() -{ - - m_context->device().waitIdle(); - - for (auto i = 0; i < m_frameManager->getFramesInFlightCount(); ++i) - { - m_context->device().destroyImageView(m_msaaColorViews[i]); - m_context->device().destroyImageView(m_resolveViews[i]); - m_context->device().destroyImageView(m_sceneViewViews[i]); - m_context->device().destroyImageView(m_depthViews[i]); - m_context->device().destroyImageView(m_depthResolveViews[i]); - } - - m_context->device().destroyDescriptorPool(m_descriptorPool); -} - -void VulkanRenderer::beginCommandBuffer(vk::CommandBuffer cmd) -{ - cmd.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); -} - -void VulkanRenderer::bindDescriptorSets(vk::CommandBuffer cmd) -{ - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - m_pipeline->getLayout(), - 0, - m_descriptorSet->getCurrentSet(m_frameManager->getFrameIndex()), - nullptr); -} - -void VulkanRenderer::drawGeometry(vk::CommandBuffer cmd) -{ - for (const auto& obj : m_objects) - { - cmd.pushConstants( - m_pipeline->getLayout(), vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4), &obj.transform[0][0]); - - vk::Buffer vbs[] = {obj.mesh->getVertexBuffer()}; - vk::DeviceSize offsets[] = {0}; - cmd.bindVertexBuffers(0, 1, vbs, offsets); - cmd.bindIndexBuffer(obj.mesh->getIndexBuffer(), 0, vk::IndexType::eUint32); - cmd.drawIndexed(obj.mesh->getIndexCount(), 1, 0, 0, 0); - } -} - -void VulkanRenderer::renderUI(const vk::CommandBuffer cmd) const -{ - m_imgui->createFrame(); - m_imgui->drawFrame(cmd); -} - -void VulkanRenderer::endDynamicRendering(vk::CommandBuffer cmd) -{ - cmd.endRendering(); -} - -void VulkanRenderer::endCommandBuffer(vk::CommandBuffer cmd) -{ - cmd.end(); -} - -void VulkanRenderer::submitAndPresent(uint32_t imageIndex) -{ - m_frameManager->endFrame(m_context->graphicsQueue(), m_context->presentQueue(), m_swapchain->get(), imageIndex); -} - -void VulkanRenderer::beginDynamicRendering(vk::CommandBuffer cmd, - vk::ImageView colorImageView, - vk::ImageView resolveImageView, - vk::ImageView depthImageView, - vk::Extent2D extent, - bool clearColor, - bool clearDepth) -{ - vk::RenderingAttachmentInfo colorAttachment{}; - vk::RenderingAttachmentInfo depthAttachment{}; - vk::RenderingInfo renderingInfo{}; - renderingInfo.renderArea.offset = vk::Offset2D{0, 0}; - renderingInfo.renderArea.extent = extent; - renderingInfo.layerCount = 1; - - constexpr vk::ClearValue clearColorValue = vk::ClearColorValue(std::array{0.0f, 0.0f, 0.0f, 1.0f}); - vk::ClearValue depthClearValue{}; - depthClearValue.depthStencil = vk::ClearDepthStencilValue(1.0f, 0); - - if (colorImageView) - { - colorAttachment.imageView = colorImageView; - colorAttachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal; - colorAttachment.loadOp = clearColor ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad; - colorAttachment.storeOp = vk::AttachmentStoreOp::eStore; - colorAttachment.clearValue = clearColorValue; - if (resolveImageView) - { - colorAttachment.resolveImageView = resolveImageView; - colorAttachment.resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal; - colorAttachment.resolveMode = vk::ResolveModeFlagBits::eAverage; - } - renderingInfo.colorAttachmentCount = 1; - renderingInfo.pColorAttachments = &colorAttachment; - } - else - { - renderingInfo.colorAttachmentCount = 0; - renderingInfo.pColorAttachments = nullptr; - } - - if (depthImageView) - { - depthAttachment.imageView = depthImageView; - depthAttachment.imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; - depthAttachment.loadOp = clearDepth ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad; - depthAttachment.storeOp = vk::AttachmentStoreOp::eStore; - depthAttachment.clearValue = depthClearValue; - - if (m_depthResolveViews[m_frameManager->getCurrentFrameIndex()]) - { - depthAttachment.resolveImageView = m_depthResolveViews[m_frameManager->getCurrentFrameIndex()]; - depthAttachment.resolveImageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; - depthAttachment.resolveMode = vk::ResolveModeFlagBits::eAverage; - // conservative - // depthAttachment.resolveMode = vk::ResolveModeFlagBits::eMin; - } - - renderingInfo.pDepthAttachment = &depthAttachment; - } - else - { - renderingInfo.pDepthAttachment = nullptr; - } - - cmd.beginRendering(renderingInfo); -} - -void VulkanRenderer::drawFrame() -{ - handleSwapchainResizing(); - - uint32_t imageIndex; - if (!m_frameManager->beginFrame(m_swapchain->get(), imageIndex)) - { - return; // Swapchain out-of-date - } - - static auto startTime = std::chrono::high_resolution_clock::now(); - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - const auto& currentFrame = m_frameManager->getCurrentFrame(); - const uint32_t frameIdx = m_frameManager->getCurrentFrameIndex(); - const vk::CommandBuffer cmd = currentFrame.commandBuffer; - const vk::Image swapchainImage = m_swapchain->getImages()[imageIndex]; - const vk::Extent2D extent = m_swapchain->getExtent(); - - const auto width = m_swapchain->getExtent().width; - const auto height = m_swapchain->getExtent().height; - const vk::Image msaaImage = m_msaaImages[frameIdx]->get(); - const vk::ImageView msaaView = m_msaaColorViews[frameIdx]; - const vk::Image resolveImage = m_resolveImages[frameIdx]->get(); - const vk::ImageView resolveView = m_resolveViews[frameIdx]; - const vk::Image sceneViewImage = m_sceneViewImages[frameIdx]->get(); - - SceneUBO sceneData{}; - const auto aspect = static_cast(width) / static_cast(height); - sceneData.view = glm::mat4(1.0); - sceneData.projection = glm::perspective(glm::radians(45.0F), aspect, 0.1F, 100.0F); - m_uniformManager->update(frameIdx, sceneData); - - CompositeUBO compositeData; - compositeData.uExposure = m_imgui->getExposure(); - compositeData.uContrast = m_imgui->getContrast(); - compositeData.uSaturation = m_imgui->getSaturation(); - compositeData.uFogDensity = m_imgui->getFogDensity(); - m_uniformManager->update(frameIdx, compositeData); - - m_light.lightDirection = glm::vec4(sin(time), -0.5f, cos(time), 0.0f); - m_light.lightDirection = glm::normalize(m_light.lightDirection); - - const float orthoSize = 10.0f; - const float nearPlane = 0.1f; - const float farPlane = 100.0f; - glm::mat4 lightProjection = glm::ortho(-orthoSize, orthoSize, -orthoSize, orthoSize, nearPlane, farPlane); - - glm::vec3 lightTarget = glm::vec3(0.0f); - float lightDistance = 20.0f; - glm::vec3 lightPosition = lightTarget - glm::vec3(m_light.lightDirection) * lightDistance; - glm::mat4 lightView = glm::lookAt(lightPosition, // Position of the light in world space - lightTarget, // The point the light is looking at (scene origin) - glm::vec3(0.0f, 1.0f, 0.0f) // Up vector - ); - - glm::mat4 clipCorrection = { - 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 1.0f}; - - glm::mat4 lightSpaceMatrix = clipCorrection * lightProjection * lightView; - - // 4. Set the matrix for the shadow mapping pass. - m_shadowMapping->setLightMatrix(lightSpaceMatrix, frameIdx); - - SceneUBO ubo{}; - ubo.view = m_camera.getViewMatrix(); - ubo.projection = m_camera.getProjectionMatrix(); - ubo.lightSpaceMatrix = lightSpaceMatrix; - - m_uniformManager->update(frameIdx, m_light); - m_uniformManager->update(frameIdx, ubo); - - // Get descriptor info for both UBOs - vk::DescriptorBufferInfo sceneBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); - vk::DescriptorBufferInfo lightBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); - - vk::DescriptorImageInfo shadowMapTextureInfo = {}; - // shadowMapImageInfo.sampler = m_shadowMapping->shadowMapSampler(); - shadowMapTextureInfo.imageView = m_shadowMapping->shadowMapView(); - shadowMapTextureInfo.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal; - - vk::DescriptorImageInfo shadowMapSamplerInfo = {}; - shadowMapSamplerInfo.sampler = m_shadowMapping->shadowMapSampler(); - - // Create write for Scene UBO at binding 0 - vk::WriteDescriptorSet sceneWrite{}; - sceneWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); - sceneWrite.dstBinding = 0; - sceneWrite.descriptorType = vk::DescriptorType::eUniformBuffer; - sceneWrite.descriptorCount = 1; - sceneWrite.pBufferInfo = &sceneBufferInfo; - - // Create write for Light UBO at binding 1 - vk::WriteDescriptorSet lightWrite{}; - lightWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); - lightWrite.dstBinding = 1; // Target binding 1 - lightWrite.descriptorType = vk::DescriptorType::eUniformBuffer; - lightWrite.descriptorCount = 1; - lightWrite.pBufferInfo = &lightBufferInfo; - - vk::WriteDescriptorSet shadowMapTextureWrite{}; - shadowMapTextureWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); - shadowMapTextureWrite.dstBinding = 2; // Target binding 2 - shadowMapTextureWrite.descriptorType = vk::DescriptorType::eSampledImage; - shadowMapTextureWrite.descriptorCount = 1; - shadowMapTextureWrite.pImageInfo = &shadowMapTextureInfo; - - vk::WriteDescriptorSet shadowMapSamplerWrite{}; - shadowMapSamplerWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); - shadowMapSamplerWrite.dstBinding = 3; // Target binding 3 - shadowMapSamplerWrite.descriptorType = vk::DescriptorType::eSampler; - shadowMapSamplerWrite.descriptorCount = 1; - shadowMapSamplerWrite.pImageInfo = &shadowMapSamplerInfo; - - m_descriptorSet->updateSet({sceneWrite, lightWrite, shadowMapTextureWrite, shadowMapSamplerWrite}); - - beginCommandBuffer(cmd); - - // Main frame label (Gray) - Debug::beginLabel(cmd, "Render Frame", {0.5f, 0.5f, 0.5f, 1.0f}); - - // 1. Depth Pre-pass (Light Blue) - Debug::beginLabel(cmd, "Depth Pre-pass", {0.2f, 0.6f, 1.0f, 1.0f}); - // get depth image view for this frame - vk::ImageView depthView = m_depthViews[frameIdx]; - - m_imageStateTracker.transition(cmd, - m_depthImages[frameIdx]->get(), - vk::ImageLayout::eDepthAttachmentOptimal, - vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eEarlyFragmentTests, - vk::AccessFlagBits::eNone, - vk::AccessFlagBits::eDepthStencilAttachmentWrite, - vk::ImageAspectFlagBits::eDepth); - - beginDynamicRendering(cmd, nullptr, nullptr, depthView, extent, false, true); - utils::setupViewportAndScissor(cmd, extent); - bindDescriptorSets(cmd); - cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_depthPipeline->get()); - drawGeometry(cmd); - endDynamicRendering(cmd); - Debug::endLabel(cmd); // End Depth Pre-pass - - // 2. Shadow Pass (Dark Gray) - Debug::beginLabel(cmd, "Shadow Pass", {0.3f, 0.3f, 0.3f, 1.0f}); - auto drawFunc = [this](vk::CommandBuffer cmd) { this->drawGeometry(cmd); }; - - m_shadowMapping->recordShadowPass(cmd, frameIdx, drawFunc); - - m_imageStateTracker.transition(cmd, - m_shadowMapping->shadowMapImage(), - vk::ImageLayout::eDepthStencilReadOnlyOptimal, - vk::PipelineStageFlagBits::eLateFragmentTests, - vk::PipelineStageFlagBits::eFragmentShader, - vk::AccessFlagBits::eDepthStencilAttachmentWrite, - vk::AccessFlagBits::eShaderRead, - vk::ImageAspectFlagBits::eDepth); - Debug::endLabel(cmd); - - // 3. Main Geometry/Shading Pass (Red) - Debug::beginLabel(cmd, "Geometry Pass", {1.0f, 0.3f, 0.3f, 1.0f}); - m_imageStateTracker.transition(cmd, - msaaImage, - vk::ImageLayout::eColorAttachmentOptimal, - vk::PipelineStageFlagBits::eTopOfPipe, // Source Stage - vk::PipelineStageFlagBits::eColorAttachmentOutput, // Destination Stage - {}, // Source Access - vk::AccessFlagBits::eColorAttachmentWrite // Destination Access - ); - m_imageStateTracker.transition(cmd, - resolveImage, - vk::ImageLayout::eColorAttachmentOptimal, - vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - {}, - vk::AccessFlagBits::eColorAttachmentWrite); - - beginDynamicRendering(cmd, msaaView, resolveView, depthView, extent, true, false); - utils::setupViewportAndScissor(cmd, extent); - bindDescriptorSets(cmd); - cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_pipeline->get()); - drawGeometry(cmd); - endDynamicRendering(cmd); - Debug::endLabel(cmd); // End Geometry Pass - - // 4. Composite & Post-Processing Pass (Green) - Debug::beginLabel(cmd, "Composite Pass", {0.2f, 0.8f, 0.2f, 1.0f}); - m_imageStateTracker.transition(cmd, - resolveImage, - vk::ImageLayout::eShaderReadOnlyOptimal, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eFragmentShader, - vk::AccessFlagBits::eColorAttachmentWrite, - vk::AccessFlagBits::eShaderRead); - - // Transition the Swapchain image to COLOR_ATTACHMENT_OPTIMAL so we can render the composite result to it. - m_imageStateTracker.transition(cmd, - swapchainImage, - vk::ImageLayout::eColorAttachmentOptimal, - vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - {}, - vk::AccessFlagBits::eColorAttachmentWrite); - - m_imageStateTracker.transition( - cmd, - sceneViewImage, - vk::ImageLayout::eColorAttachmentOptimal, - vk::PipelineStageFlagBits::eFragmentShader, // Source stage (previous shader read use) - vk::PipelineStageFlagBits::eColorAttachmentOutput, // Destination stage - vk::AccessFlagBits::eShaderRead, // Source access - vk::AccessFlagBits::eColorAttachmentWrite // Destination access - ); - - m_imageStateTracker.transition(cmd, - m_depthImages[frameIdx]->get(), - vk::ImageLayout::eDepthStencilReadOnlyOptimal, - vk::PipelineStageFlagBits::eFragmentShader, - vk::PipelineStageFlagBits::eEarlyFragmentTests, - vk::AccessFlagBits::eShaderRead, - vk::AccessFlagBits::eDepthStencilAttachmentRead, - vk::ImageAspectFlagBits::eDepth); - - m_imageStateTracker.transition(cmd, - m_depthResolveImages[frameIdx]->get(), - vk::ImageLayout::eDepthStencilReadOnlyOptimal, - vk::PipelineStageFlagBits::eLateFragmentTests, - vk::PipelineStageFlagBits::eFragmentShader, - vk::AccessFlagBits::eDepthStencilAttachmentWrite, - vk::AccessFlagBits::eShaderRead, - vk::ImageAspectFlagBits::eDepth); - - beginDynamicRendering(cmd, m_sceneViewViews[frameIdx], nullptr, nullptr, extent, true); - utils::setupViewportAndScissor(cmd, extent); - - vk::DescriptorBufferInfo compositeBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); - - vk::DescriptorImageInfo resolveImageInfo = {}; - resolveImageInfo.imageView = m_resolveViews[frameIdx]; - resolveImageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - - vk::DescriptorImageInfo depthImageInfo = {}; - depthImageInfo.imageView = m_depthResolveViews[frameIdx]; - depthImageInfo.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal; - - // Info for binding 3: g_sampler (the sampler to be used with uInputImage) - vk::DescriptorImageInfo samplerInfo = {}; - samplerInfo.sampler = m_sampler->get(); // This is the generic sampler you created - - std::vector writes; - writes.reserve(4); - - // Write for binding 0 - writes.emplace_back(m_compositeDescriptorSet->getCurrentSet(frameIdx), - 0, - 0, - 1, - vk::DescriptorType::eSampledImage, - &resolveImageInfo); - - // Write for binding 1 - writes.emplace_back(m_compositeDescriptorSet->getCurrentSet(frameIdx), - 1, - 0, - 1, - vk::DescriptorType::eUniformBuffer, - nullptr, - &compositeBufferInfo); - - // Write for binding 2 - writes.emplace_back( - m_compositeDescriptorSet->getCurrentSet(frameIdx), 2, 0, 1, vk::DescriptorType::eSampledImage, &depthImageInfo); - - // Write for binding 3 (This is the crucial fix for the validation error) - writes.emplace_back( - m_compositeDescriptorSet->getCurrentSet(frameIdx), 3, 0, 1, vk::DescriptorType::eSampler, &samplerInfo); - - m_compositeDescriptorSet->updateSet(writes); - - cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_compositePipeline->get()); - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - m_compositePipeline->getLayout(), - 0, - m_compositeDescriptorSet->getCurrentSet(m_frameManager->getFrameIndex()), - nullptr); - cmd.draw(3, 1, 0, 0); - endDynamicRendering(cmd); - Debug::endLabel(cmd); // End Composite Pass - - // 5. UI Pass (Yellow) - Debug::beginLabel(cmd, "UI Pass", {1.0f, 0.9f, 0.3f, 1.0f}); - m_imageStateTracker.transition(cmd, - sceneViewImage, - vk::ImageLayout::eShaderReadOnlyOptimal, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eFragmentShader, - vk::AccessFlagBits::eColorAttachmentWrite, - vk::AccessFlagBits::eShaderRead); - - // UI pass - m_imageStateTracker.transition(cmd, - swapchainImage, - vk::ImageLayout::eColorAttachmentOptimal, - vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - {}, - vk::AccessFlagBits::eColorAttachmentWrite); - - beginDynamicRendering(cmd, m_swapchain->getImageViews()[imageIndex], nullptr, nullptr, extent, false); - m_imgui->setSceneDescriptorSet(m_sceneViewImageDescriptorSets[frameIdx]); - renderUI(cmd); - endDynamicRendering(cmd); - Debug::endLabel(cmd); // End UI Pass - - m_imageStateTracker.transition(cmd, - swapchainImage, - vk::ImageLayout::ePresentSrcKHR, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eBottomOfPipe, - vk::AccessFlagBits::eColorAttachmentWrite, - {}); - - Debug::endLabel(cmd); // End Render Frame - - endCommandBuffer(cmd); - submitAndPresent(imageIndex); -} - -void VulkanRenderer::createMSAAImage() -{ - vk::Format format = vk::Format::eR16G16B16A16Sfloat; - vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment; - size_t framesInFlight = m_frameManager->getFramesInFlightCount(); - - utils::ImageBuilder builder(m_context->device(), *m_allocator, m_swapchain->getExtent()); - m_msaaImages.resize(framesInFlight); - m_msaaColorViews.resize(framesInFlight); - - for (size_t i = 0; i < framesInFlight; ++i) - { - auto built = builder.setFormat(format).setUsage(usage).setSamples(vk::SampleCountFlagBits::e4).build(); - m_msaaImages[i] = std::move(built.image); - m_msaaColorViews[i] = built.view; - - m_imageStateTracker.recordState(m_msaaImages[i]->get(), vk::ImageLayout::eUndefined); - } -} - -void VulkanRenderer::createResolveImages() -{ - vk::Format format = vk::Format::eR16G16B16A16Sfloat; - vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled; - size_t framesInFlight = m_frameManager->getFramesInFlightCount(); - - utils::ImageBuilder builder(m_context->device(), *m_allocator, m_swapchain->getExtent()); - m_resolveImages.resize(framesInFlight); - m_resolveViews.resize(framesInFlight); - - for (size_t i = 0; i < framesInFlight; ++i) - { - auto built = builder.setFormat(format).setUsage(usage).build(); // Defaults to e1 samples - - m_resolveImages[i] = std::move(built.image); - m_resolveViews[i] = built.view; - - m_imageStateTracker.recordState(m_resolveImages[i]->get(), vk::ImageLayout::eUndefined); - } -} -void VulkanRenderer::createSceneViewImages() -{ - vk::Format format = m_swapchain->getFormat(); - vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled - | vk::ImageUsageFlagBits::eTransferSrc; - size_t framesInFlight = m_frameManager->getFramesInFlightCount(); - - // Destroy old resources if recreating - for (size_t i = 0; i < m_sceneViewViews.size(); ++i) - { - m_context->device().destroyImageView(m_sceneViewViews[i]); - } - m_sceneViewImages.clear(); - m_sceneViewViews.clear(); - - utils::ImageBuilder builder(m_context->device(), *m_allocator, m_swapchain->getExtent()); - m_sceneViewImages.resize(framesInFlight); - m_sceneViewViews.resize(framesInFlight); - - for (size_t i = 0; i < framesInFlight; ++i) - { - auto built = builder.setFormat(format).setUsage(usage).build(); // Defaults to e1 samples and color aspect - - m_sceneViewImages[i] = std::move(built.image); - m_sceneViewViews[i] = built.view; - - m_imageStateTracker.recordState(m_sceneViewImages[i]->get(), vk::ImageLayout::eUndefined); - } -} - -void VulkanRenderer::createSampler() -{ - vk::SamplerCreateInfo samplerInfo{}; - samplerInfo.magFilter = vk::Filter::eLinear; - samplerInfo.minFilter = vk::Filter::eLinear; - samplerInfo.mipmapMode = vk::SamplerMipmapMode::eLinear; - samplerInfo.addressModeU = vk::SamplerAddressMode::eClampToEdge; - samplerInfo.addressModeV = vk::SamplerAddressMode::eClampToEdge; - samplerInfo.addressModeW = vk::SamplerAddressMode::eClampToEdge; - samplerInfo.mipLodBias = 0.0f; - samplerInfo.anisotropyEnable = VK_FALSE; - samplerInfo.maxAnisotropy = 1.0f; - samplerInfo.compareEnable = VK_FALSE; - samplerInfo.compareOp = vk::CompareOp::eAlways; - samplerInfo.minLod = 0.0f; - samplerInfo.maxLod = 0.0f; - samplerInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite; - - m_sampler = std::make_unique(m_context->device(), samplerInfo); -} -void VulkanRenderer::createDescriptorSets() -{ - - const auto framesInFlight = m_frameManager->getFramesInFlightCount(); - m_sceneViewImageDescriptorSets.resize(framesInFlight); - for (int i = 0; i < framesInFlight; ++i) - { - m_sceneViewImageDescriptorSets[i] = m_imgui->createDescriptorSet(m_sceneViewViews[i], m_sampler->get()); - } -} -void VulkanRenderer::createDepthImages() -{ - vk::Format format = vk::Format::eD32Sfloat; - vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled; - size_t framesInFlight = m_frameManager->getFramesInFlightCount(); - - // Destroy old resources if recreating - for (size_t i = 0; i < m_depthViews.size(); ++i) - { - m_context->device().destroyImageView(m_depthViews[i]); - } - m_depthImages.clear(); - m_depthViews.clear(); - - utils::ImageBuilder builder(m_context->device(), *m_allocator, m_swapchain->getExtent()); - m_depthImages.resize(framesInFlight); - m_depthViews.resize(framesInFlight); - - for (size_t i = 0; i < framesInFlight; ++i) - { - auto built = builder.setFormat(format) - .setUsage(usage) - .setSamples(vk::SampleCountFlagBits::e4) - .setAspectMask(vk::ImageAspectFlagBits::eDepth) - .build(); - - m_depthImages[i] = std::move(built.image); - m_depthViews[i] = built.view; - - m_imageStateTracker.recordState(m_depthImages[i]->get(), vk::ImageLayout::eUndefined); - } - - // ...existing code creating m_depthImages (MSAA)... - m_depthResolveImages.resize(framesInFlight); - m_depthResolveViews.resize(framesInFlight); - - utils::ImageBuilder resolveBuilder(m_context->device(), *m_allocator, m_swapchain->getExtent()); - for (size_t i = 0; i < framesInFlight; ++i) - { - auto built = resolveBuilder.setFormat(format) - .setUsage(vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled) - .setSamples(vk::SampleCountFlagBits::e1) - .setAspectMask(vk::ImageAspectFlagBits::eDepth) - .build(); - m_depthResolveImages[i] = std::move(built.image); - m_depthResolveViews[i] = built.view; - m_imageStateTracker.recordState(m_depthResolveImages[i]->get(), vk::ImageLayout::eUndefined); - } -} -void VulkanRenderer::createDepthPipelineAndDescriptorSets() -{ - - std::vector setLayouts = {m_descriptorSet->getLayout()}; - - m_depthPipeline = Pipeline::Builder(m_context->device()) - .setVertexShader(m_config.vertShaderPath) - // No fragment shader, we only want depth output - .setVertexInputFromVertex() - .setDepthAttachment(vk::Format::eD32Sfloat, true) // depth test and write enabled - .setDescriptorSetLayouts(setLayouts) - .setMultisample(4) - .setFrontFace(vk::FrontFace::eClockwise) // Match main geometry pipeline - .addPushContantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)) - .build(); -} - -void VulkanRenderer::initScene() -{ - auto planeVerts = generatePlaneVertices(10, 50.0f); - auto planeInds = generatePlaneIndices(10); - auto planeMesh = std::make_shared(*m_allocator, planeVerts, planeInds); - m_objects.push_back({planeMesh, glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -0.0f, 0.0f))}); - - auto meshDataVec = loadModelFromBinary("../resources/models/thingy.mesh"); - - if (!meshDataVec.empty()) - { - const auto& meshData = meshDataVec[0]; // Use the first mesh for monkey - auto monkeyMesh = std::make_shared(*m_allocator, meshData.vertices, meshData.indices); - m_objects.push_back(RenderObject{monkeyMesh}); - } -} - -} // namespace reactor +#include "VulkanRenderer.hpp" + +#include "../core/ModelIO.hpp" +#include "../core/Uniforms.hpp" +#include "../core/Window.hpp" +#include "../logging/ImGuiConsoleSink.hpp" +#include "../logging/Logger.hpp" +#include "../logging/SpdlogSink.hpp" +#include "DebugUtils.hpp" +#include "ImageUtils.hpp" +#include "VulkanUtils.hpp" + +#include +#include + +namespace reactor +{ +VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Camera& camera) + : m_config(config), m_window(window), m_camera(camera) +{ + + auto imguiConsoleSink = std::make_shared(); + auto spdlogSing = std::make_shared(); + + Logger::getInstance().addSink(imguiConsoleSink); + Logger::getInstance().addSink(spdlogSing); + + LOG_INFO("Created the logger"); + + createCoreVulkanObjects(); + createSwapchainAndFrameManager(); + + // Setup the Uniform Manager + m_uniformManager = std::make_unique(*m_allocator, m_frameManager->getFramesInFlightCount()); + m_uniformManager->registerUBO("scene"); + m_uniformManager->registerUBO("composite"); + m_uniformManager->registerUBO("lighting"); + + createDescriptorPool(); + createPipelineAndDescriptors(); + setupUI(imguiConsoleSink); + createMSAAImage(); + createResolveImages(); + createSceneViewImages(); + createDepthImages(); + createSampler(); + createDescriptorSets(); + createDepthPipelineAndDescriptorSets(); + initScene(); + + m_shadowMapping = std::make_unique(*this); + m_imageStateTracker.recordState(m_shadowMapping->shadowMapImage(), vk::ImageLayout::eUndefined); +} + +Allocator& VulkanRenderer::allocator() +{ + return *m_allocator; +} + +vk::Device VulkanRenderer::device() const +{ + return m_context->device(); +} + +vk::DescriptorPool VulkanRenderer::descriptorPool() const +{ + return m_descriptorPool; +} + +void VulkanRenderer::createCoreVulkanObjects() +{ + m_context = std::make_unique(m_window.getNativeWindow()); + m_allocator = std::make_unique(m_context->physicalDevice(), + m_context->device(), + m_context->instance(), + m_context->graphicsQueue(), + m_context->queueFamilies().graphicsFamily.value()); +} + +void VulkanRenderer::createSwapchainAndFrameManager() +{ + m_swapchain = + std::make_unique(m_context->device(), m_context->physicalDevice(), m_context->surface(), m_window); + + uint32_t swapchainImageCount = m_swapchain->getImageViews().size(); + m_frameManager = std::make_unique(m_context->device(), *m_allocator, 0, 2, swapchainImageCount); + + for (const auto& image : m_swapchain->getImages()) + { + m_imageStateTracker.recordState(image, vk::ImageLayout::eUndefined); + } +} + +void VulkanRenderer::createDescriptorPool() +{ + std::vector poolSizes = {{vk::DescriptorType::eUniformBuffer, 32}, + {vk::DescriptorType::eCombinedImageSampler, 32}, + {vk::DescriptorType::eSampledImage, 32}, + {vk::DescriptorType::eSampler, 32}}; + + vk::DescriptorPoolCreateInfo poolInfo(vk::DescriptorPoolCreateFlags(), 128, poolSizes.size(), poolSizes.data()); + + m_descriptorPool = m_context->device().createDescriptorPool(poolInfo); +} + +void VulkanRenderer::createPipelineAndDescriptors() +{ + const std::string vertShaderPath = m_config.vertShaderPath; + const std::string fragShaderPath = m_config.fragShaderPath; + + const std::vector bindings = { + // Binding 0: Scene UBO (Vertex Shader) + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex), + + // Binding 1: Light UBO (Fragment Shader) + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment), + + // Binding 2: Shadow Map Texture (Fragment Shader) + vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), + + // Binding 3: Shadow Map Sampler + vk::DescriptorSetLayoutBinding(3, vk::DescriptorType::eSampler, 1, vk::ShaderStageFlagBits::eFragment), + }; + m_descriptorSet = std::make_unique(m_context->device(), m_descriptorPool, 2, bindings); + const std::vector setLayouts = {m_descriptorSet->getLayout()}; + + m_pipeline = Pipeline::Builder(m_context->device()) + .setVertexShader(m_config.vertShaderPath) + .setFragmentShader(m_config.fragShaderPath) + .setVertexInputFromVertex() + .setColorAttachment(vk::Format::eR16G16B16A16Sfloat) + .setDepthAttachment(vk::Format::eD32Sfloat, true) // depth test and write + .setDescriptorSetLayouts(setLayouts) + .setMultisample(4) + .setFrontFace(vk::FrontFace::eClockwise) // Assuming standard winding order for cubes + .addPushContantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)) + .build(); + + Debug::setObjectName(m_context->device(), + (uint64_t)(VkPipeline)m_pipeline->get(), + vk::ObjectType::ePipeline, + "Main Geometry Pipeline"); + + const std::vector compositeBindings = { + // binding 0: uInputImage (Texture2D) + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), + // binding 1: CompositeParams (UBO) + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment), + // binding 2: uDepthImage (Texture2DMS) + vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), + // binding 3: g_sampler (SamplerState) + vk::DescriptorSetLayoutBinding(3, vk::DescriptorType::eSampler, 1, vk::ShaderStageFlagBits::eFragment), + + }; + + m_compositeDescriptorSet = + std::make_unique(m_context->device(), m_descriptorPool, 2, compositeBindings); + std::vector compositeSetLayouts = {m_compositeDescriptorSet->getLayout()}; + + m_compositePipeline = Pipeline::Builder(m_context->device()) + .setVertexShader(m_config.compositeVertShaderPath) + .setFragmentShader(m_config.compositeFragShaderPath) + .setColorAttachment(m_swapchain->getFormat()) + .setDescriptorSetLayouts(compositeSetLayouts) + .setMultisample(1) // No MSAA for composite pass + .setFrontFace(vk::FrontFace::eClockwise) // Assuming standard winding order for cubes + .build(); +} + +void VulkanRenderer::handleSwapchainResizing() +{ + if (m_window.wasResized()) + { + vk::Extent2D size = m_window.getFramebufferSize(); + while (size.width == 0 || size.height == 0) + { + Window::waitEvents(); + size = m_window.getFramebufferSize(); + } + m_context->device().waitIdle(); + m_swapchain->recreate(); + m_window.resetResizedFlag(); + } +} + +void VulkanRenderer::setupUI(std::shared_ptr consoleSink) +{ + m_imgui = std::make_unique(*m_context, m_window, m_window.getEventManager(), consoleSink); +} + +VulkanRenderer::~VulkanRenderer() +{ + + m_context->device().waitIdle(); + + for (auto i = 0; i < m_frameManager->getFramesInFlightCount(); ++i) + { + m_context->device().destroyImageView(m_msaaColorViews[i]); + m_context->device().destroyImageView(m_resolveViews[i]); + m_context->device().destroyImageView(m_sceneViewViews[i]); + m_context->device().destroyImageView(m_depthViews[i]); + m_context->device().destroyImageView(m_depthResolveViews[i]); + } + + m_context->device().destroyDescriptorPool(m_descriptorPool); +} + +void VulkanRenderer::beginCommandBuffer(vk::CommandBuffer cmd) +{ + cmd.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); +} + +void VulkanRenderer::bindDescriptorSets(vk::CommandBuffer cmd) +{ + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + m_pipeline->getLayout(), + 0, + m_descriptorSet->getCurrentSet(m_frameManager->getFrameIndex()), + nullptr); +} + +void VulkanRenderer::drawGeometry(vk::CommandBuffer cmd) +{ + for (const auto& obj : m_objects) + { + cmd.pushConstants( + m_pipeline->getLayout(), vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4), &obj.transform[0][0]); + + vk::Buffer vbs[] = {obj.mesh->getVertexBuffer()}; + vk::DeviceSize offsets[] = {0}; + cmd.bindVertexBuffers(0, 1, vbs, offsets); + cmd.bindIndexBuffer(obj.mesh->getIndexBuffer(), 0, vk::IndexType::eUint32); + cmd.drawIndexed(obj.mesh->getIndexCount(), 1, 0, 0, 0); + } +} + +void VulkanRenderer::renderUI(const vk::CommandBuffer cmd) const +{ + m_imgui->createFrame(); + m_imgui->drawFrame(cmd); +} + +void VulkanRenderer::endDynamicRendering(vk::CommandBuffer cmd) +{ + cmd.endRendering(); +} + +void VulkanRenderer::endCommandBuffer(vk::CommandBuffer cmd) +{ + cmd.end(); +} + +void VulkanRenderer::submitAndPresent(uint32_t imageIndex) +{ + m_frameManager->endFrame(m_context->graphicsQueue(), m_context->presentQueue(), m_swapchain->get(), imageIndex); +} + +void VulkanRenderer::beginDynamicRendering(vk::CommandBuffer cmd, + vk::ImageView colorImageView, + vk::ImageView resolveImageView, + vk::ImageView depthImageView, + vk::Extent2D extent, + bool clearColor, + bool clearDepth) +{ + vk::RenderingAttachmentInfo colorAttachment{}; + vk::RenderingAttachmentInfo depthAttachment{}; + vk::RenderingInfo renderingInfo{}; + renderingInfo.renderArea.offset = vk::Offset2D{0, 0}; + renderingInfo.renderArea.extent = extent; + renderingInfo.layerCount = 1; + + constexpr vk::ClearValue clearColorValue = vk::ClearColorValue(std::array{0.0f, 0.0f, 0.0f, 1.0f}); + vk::ClearValue depthClearValue{}; + depthClearValue.depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + + if (colorImageView) + { + colorAttachment.imageView = colorImageView; + colorAttachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal; + colorAttachment.loadOp = clearColor ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad; + colorAttachment.storeOp = vk::AttachmentStoreOp::eStore; + colorAttachment.clearValue = clearColorValue; + if (resolveImageView) + { + colorAttachment.resolveImageView = resolveImageView; + colorAttachment.resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal; + colorAttachment.resolveMode = vk::ResolveModeFlagBits::eAverage; + } + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + } + else + { + renderingInfo.colorAttachmentCount = 0; + renderingInfo.pColorAttachments = nullptr; + } + + if (depthImageView) + { + depthAttachment.imageView = depthImageView; + depthAttachment.imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; + depthAttachment.loadOp = clearDepth ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad; + depthAttachment.storeOp = vk::AttachmentStoreOp::eStore; + depthAttachment.clearValue = depthClearValue; + + if (m_depthResolveViews[m_frameManager->getCurrentFrameIndex()]) + { + depthAttachment.resolveImageView = m_depthResolveViews[m_frameManager->getCurrentFrameIndex()]; + depthAttachment.resolveImageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; + depthAttachment.resolveMode = vk::ResolveModeFlagBits::eAverage; + // conservative + // depthAttachment.resolveMode = vk::ResolveModeFlagBits::eMin; + } + + renderingInfo.pDepthAttachment = &depthAttachment; + } + else + { + renderingInfo.pDepthAttachment = nullptr; + } + + cmd.beginRendering(renderingInfo); +} + +void VulkanRenderer::drawFrame() +{ + handleSwapchainResizing(); + + uint32_t imageIndex; + if (!m_frameManager->beginFrame(m_swapchain->get(), imageIndex)) + { + return; // Swapchain out-of-date + } + + static auto startTime = std::chrono::high_resolution_clock::now(); + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + const auto& currentFrame = m_frameManager->getCurrentFrame(); + const uint32_t frameIdx = m_frameManager->getCurrentFrameIndex(); + const vk::CommandBuffer cmd = currentFrame.commandBuffer; + const vk::Image swapchainImage = m_swapchain->getImages()[imageIndex]; + const vk::Extent2D extent = m_swapchain->getExtent(); + + const auto width = m_swapchain->getExtent().width; + const auto height = m_swapchain->getExtent().height; + const vk::Image msaaImage = m_msaaImages[frameIdx]->get(); + const vk::ImageView msaaView = m_msaaColorViews[frameIdx]; + const vk::Image resolveImage = m_resolveImages[frameIdx]->get(); + const vk::ImageView resolveView = m_resolveViews[frameIdx]; + const vk::Image sceneViewImage = m_sceneViewImages[frameIdx]->get(); + + SceneUBO sceneData{}; + const auto aspect = static_cast(width) / static_cast(height); + sceneData.view = glm::mat4(1.0); + sceneData.projection = glm::perspective(glm::radians(45.0F), aspect, 0.1F, 100.0F); + m_uniformManager->update(frameIdx, sceneData); + + CompositeUBO compositeData; + compositeData.uExposure = m_imgui->getExposure(); + compositeData.uContrast = m_imgui->getContrast(); + compositeData.uSaturation = m_imgui->getSaturation(); + compositeData.uFogDensity = m_imgui->getFogDensity(); + m_uniformManager->update(frameIdx, compositeData); + + m_light.lightDirection = glm::vec4(sin(time), -0.5f, cos(time), 0.0f); + m_light.lightDirection = glm::normalize(m_light.lightDirection); + + const float orthoSize = 10.0f; + const float nearPlane = 0.1f; + const float farPlane = 100.0f; + glm::mat4 lightProjection = glm::ortho(-orthoSize, orthoSize, -orthoSize, orthoSize, nearPlane, farPlane); + + glm::vec3 lightTarget = glm::vec3(0.0f); + float lightDistance = 20.0f; + glm::vec3 lightPosition = lightTarget - glm::vec3(m_light.lightDirection) * lightDistance; + glm::mat4 lightView = glm::lookAt(lightPosition, // Position of the light in world space + lightTarget, // The point the light is looking at (scene origin) + glm::vec3(0.0f, 1.0f, 0.0f) // Up vector + ); + + glm::mat4 clipCorrection = { + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 1.0f}; + + glm::mat4 lightSpaceMatrix = clipCorrection * lightProjection * lightView; + + // 4. Set the matrix for the shadow mapping pass. + m_shadowMapping->setLightMatrix(lightSpaceMatrix, frameIdx); + + SceneUBO ubo{}; + ubo.view = m_camera.getViewMatrix(); + ubo.projection = m_camera.getProjectionMatrix(); + ubo.lightSpaceMatrix = lightSpaceMatrix; + + m_uniformManager->update(frameIdx, m_light); + m_uniformManager->update(frameIdx, ubo); + + // Get descriptor info for both UBOs + vk::DescriptorBufferInfo sceneBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); + vk::DescriptorBufferInfo lightBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); + + vk::DescriptorImageInfo shadowMapTextureInfo = {}; + // shadowMapImageInfo.sampler = m_shadowMapping->shadowMapSampler(); + shadowMapTextureInfo.imageView = m_shadowMapping->shadowMapView(); + shadowMapTextureInfo.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal; + + vk::DescriptorImageInfo shadowMapSamplerInfo = {}; + shadowMapSamplerInfo.sampler = m_shadowMapping->shadowMapSampler(); + + // Create write for Scene UBO at binding 0 + vk::WriteDescriptorSet sceneWrite{}; + sceneWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); + sceneWrite.dstBinding = 0; + sceneWrite.descriptorType = vk::DescriptorType::eUniformBuffer; + sceneWrite.descriptorCount = 1; + sceneWrite.pBufferInfo = &sceneBufferInfo; + + // Create write for Light UBO at binding 1 + vk::WriteDescriptorSet lightWrite{}; + lightWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); + lightWrite.dstBinding = 1; // Target binding 1 + lightWrite.descriptorType = vk::DescriptorType::eUniformBuffer; + lightWrite.descriptorCount = 1; + lightWrite.pBufferInfo = &lightBufferInfo; + + vk::WriteDescriptorSet shadowMapTextureWrite{}; + shadowMapTextureWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); + shadowMapTextureWrite.dstBinding = 2; // Target binding 2 + shadowMapTextureWrite.descriptorType = vk::DescriptorType::eSampledImage; + shadowMapTextureWrite.descriptorCount = 1; + shadowMapTextureWrite.pImageInfo = &shadowMapTextureInfo; + + vk::WriteDescriptorSet shadowMapSamplerWrite{}; + shadowMapSamplerWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); + shadowMapSamplerWrite.dstBinding = 3; // Target binding 3 + shadowMapSamplerWrite.descriptorType = vk::DescriptorType::eSampler; + shadowMapSamplerWrite.descriptorCount = 1; + shadowMapSamplerWrite.pImageInfo = &shadowMapSamplerInfo; + + m_descriptorSet->updateSet({sceneWrite, lightWrite, shadowMapTextureWrite, shadowMapSamplerWrite}); + + beginCommandBuffer(cmd); + + // Main frame label (Gray) + Debug::beginLabel(cmd, "Render Frame", {0.5f, 0.5f, 0.5f, 1.0f}); + + // 1. Depth Pre-pass (Light Blue) + Debug::beginLabel(cmd, "Depth Pre-pass", {0.2f, 0.6f, 1.0f, 1.0f}); + // get depth image view for this frame + vk::ImageView depthView = m_depthViews[frameIdx]; + + m_imageStateTracker.transition(cmd, + m_depthImages[frameIdx]->get(), + vk::ImageLayout::eDepthAttachmentOptimal, + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eEarlyFragmentTests, + vk::AccessFlagBits::eNone, + vk::AccessFlagBits::eDepthStencilAttachmentWrite, + vk::ImageAspectFlagBits::eDepth); + + beginDynamicRendering(cmd, nullptr, nullptr, depthView, extent, false, true); + utils::setupViewportAndScissor(cmd, extent); + bindDescriptorSets(cmd); + cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_depthPipeline->get()); + drawGeometry(cmd); + endDynamicRendering(cmd); + Debug::endLabel(cmd); // End Depth Pre-pass + + // 2. Shadow Pass (Dark Gray) + Debug::beginLabel(cmd, "Shadow Pass", {0.3f, 0.3f, 0.3f, 1.0f}); + auto drawFunc = [this](vk::CommandBuffer cmd) { this->drawGeometry(cmd); }; + + m_shadowMapping->recordShadowPass(cmd, frameIdx, drawFunc); + + m_imageStateTracker.transition(cmd, + m_shadowMapping->shadowMapImage(), + vk::ImageLayout::eDepthStencilReadOnlyOptimal, + vk::PipelineStageFlagBits::eLateFragmentTests, + vk::PipelineStageFlagBits::eFragmentShader, + vk::AccessFlagBits::eDepthStencilAttachmentWrite, + vk::AccessFlagBits::eShaderRead, + vk::ImageAspectFlagBits::eDepth); + Debug::endLabel(cmd); + + // 3. Main Geometry/Shading Pass (Red) + Debug::beginLabel(cmd, "Geometry Pass", {1.0f, 0.3f, 0.3f, 1.0f}); + m_imageStateTracker.transition(cmd, + msaaImage, + vk::ImageLayout::eColorAttachmentOptimal, + vk::PipelineStageFlagBits::eTopOfPipe, // Source Stage + vk::PipelineStageFlagBits::eColorAttachmentOutput, // Destination Stage + {}, // Source Access + vk::AccessFlagBits::eColorAttachmentWrite // Destination Access + ); + m_imageStateTracker.transition(cmd, + resolveImage, + vk::ImageLayout::eColorAttachmentOptimal, + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + {}, + vk::AccessFlagBits::eColorAttachmentWrite); + + beginDynamicRendering(cmd, msaaView, resolveView, depthView, extent, true, false); + utils::setupViewportAndScissor(cmd, extent); + bindDescriptorSets(cmd); + cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_pipeline->get()); + drawGeometry(cmd); + endDynamicRendering(cmd); + Debug::endLabel(cmd); // End Geometry Pass + + // 4. Composite & Post-Processing Pass (Green) + Debug::beginLabel(cmd, "Composite Pass", {0.2f, 0.8f, 0.2f, 1.0f}); + m_imageStateTracker.transition(cmd, + resolveImage, + vk::ImageLayout::eShaderReadOnlyOptimal, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eFragmentShader, + vk::AccessFlagBits::eColorAttachmentWrite, + vk::AccessFlagBits::eShaderRead); + + // Transition the Swapchain image to COLOR_ATTACHMENT_OPTIMAL so we can render the composite result to it. + m_imageStateTracker.transition(cmd, + swapchainImage, + vk::ImageLayout::eColorAttachmentOptimal, + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + {}, + vk::AccessFlagBits::eColorAttachmentWrite); + + m_imageStateTracker.transition( + cmd, + sceneViewImage, + vk::ImageLayout::eColorAttachmentOptimal, + vk::PipelineStageFlagBits::eFragmentShader, // Source stage (previous shader read use) + vk::PipelineStageFlagBits::eColorAttachmentOutput, // Destination stage + vk::AccessFlagBits::eShaderRead, // Source access + vk::AccessFlagBits::eColorAttachmentWrite // Destination access + ); + + m_imageStateTracker.transition(cmd, + m_depthImages[frameIdx]->get(), + vk::ImageLayout::eDepthStencilReadOnlyOptimal, + vk::PipelineStageFlagBits::eFragmentShader, + vk::PipelineStageFlagBits::eEarlyFragmentTests, + vk::AccessFlagBits::eShaderRead, + vk::AccessFlagBits::eDepthStencilAttachmentRead, + vk::ImageAspectFlagBits::eDepth); + + m_imageStateTracker.transition(cmd, + m_depthResolveImages[frameIdx]->get(), + vk::ImageLayout::eDepthStencilReadOnlyOptimal, + vk::PipelineStageFlagBits::eLateFragmentTests, + vk::PipelineStageFlagBits::eFragmentShader, + vk::AccessFlagBits::eDepthStencilAttachmentWrite, + vk::AccessFlagBits::eShaderRead, + vk::ImageAspectFlagBits::eDepth); + + beginDynamicRendering(cmd, m_sceneViewViews[frameIdx], nullptr, nullptr, extent, true); + utils::setupViewportAndScissor(cmd, extent); + + vk::DescriptorBufferInfo compositeBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); + + vk::DescriptorImageInfo resolveImageInfo = {}; + resolveImageInfo.imageView = m_resolveViews[frameIdx]; + resolveImageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + + vk::DescriptorImageInfo depthImageInfo = {}; + depthImageInfo.imageView = m_depthResolveViews[frameIdx]; + depthImageInfo.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal; + + // Info for binding 3: g_sampler (the sampler to be used with uInputImage) + vk::DescriptorImageInfo samplerInfo = {}; + samplerInfo.sampler = m_sampler->get(); // This is the generic sampler you created + + std::vector writes; + writes.reserve(4); + + // Write for binding 0 + writes.emplace_back(m_compositeDescriptorSet->getCurrentSet(frameIdx), + 0, + 0, + 1, + vk::DescriptorType::eSampledImage, + &resolveImageInfo); + + // Write for binding 1 + writes.emplace_back(m_compositeDescriptorSet->getCurrentSet(frameIdx), + 1, + 0, + 1, + vk::DescriptorType::eUniformBuffer, + nullptr, + &compositeBufferInfo); + + // Write for binding 2 + writes.emplace_back( + m_compositeDescriptorSet->getCurrentSet(frameIdx), 2, 0, 1, vk::DescriptorType::eSampledImage, &depthImageInfo); + + // Write for binding 3 (This is the crucial fix for the validation error) + writes.emplace_back( + m_compositeDescriptorSet->getCurrentSet(frameIdx), 3, 0, 1, vk::DescriptorType::eSampler, &samplerInfo); + + m_compositeDescriptorSet->updateSet(writes); + + cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_compositePipeline->get()); + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + m_compositePipeline->getLayout(), + 0, + m_compositeDescriptorSet->getCurrentSet(m_frameManager->getFrameIndex()), + nullptr); + cmd.draw(3, 1, 0, 0); + endDynamicRendering(cmd); + Debug::endLabel(cmd); // End Composite Pass + + // 5. UI Pass (Yellow) + Debug::beginLabel(cmd, "UI Pass", {1.0f, 0.9f, 0.3f, 1.0f}); + m_imageStateTracker.transition(cmd, + sceneViewImage, + vk::ImageLayout::eShaderReadOnlyOptimal, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eFragmentShader, + vk::AccessFlagBits::eColorAttachmentWrite, + vk::AccessFlagBits::eShaderRead); + + // UI pass + m_imageStateTracker.transition(cmd, + swapchainImage, + vk::ImageLayout::eColorAttachmentOptimal, + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + {}, + vk::AccessFlagBits::eColorAttachmentWrite); + + beginDynamicRendering(cmd, m_swapchain->getImageViews()[imageIndex], nullptr, nullptr, extent, false); + m_imgui->setSceneDescriptorSet(m_sceneViewImageDescriptorSets[frameIdx]); + renderUI(cmd); + endDynamicRendering(cmd); + Debug::endLabel(cmd); // End UI Pass + + m_imageStateTracker.transition(cmd, + swapchainImage, + vk::ImageLayout::ePresentSrcKHR, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eBottomOfPipe, + vk::AccessFlagBits::eColorAttachmentWrite, + {}); + + Debug::endLabel(cmd); // End Render Frame + + endCommandBuffer(cmd); + submitAndPresent(imageIndex); +} + +void VulkanRenderer::createMSAAImage() +{ + vk::Format format = vk::Format::eR16G16B16A16Sfloat; + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment; + size_t framesInFlight = m_frameManager->getFramesInFlightCount(); + + utils::ImageBuilder builder(m_context->device(), *m_allocator, m_swapchain->getExtent()); + m_msaaImages.resize(framesInFlight); + m_msaaColorViews.resize(framesInFlight); + + for (size_t i = 0; i < framesInFlight; ++i) + { + auto built = builder.setFormat(format).setUsage(usage).setSamples(vk::SampleCountFlagBits::e4).build(); + m_msaaImages[i] = std::move(built.image); + m_msaaColorViews[i] = built.view; + + m_imageStateTracker.recordState(m_msaaImages[i]->get(), vk::ImageLayout::eUndefined); + } +} + +void VulkanRenderer::createResolveImages() +{ + vk::Format format = vk::Format::eR16G16B16A16Sfloat; + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled; + size_t framesInFlight = m_frameManager->getFramesInFlightCount(); + + utils::ImageBuilder builder(m_context->device(), *m_allocator, m_swapchain->getExtent()); + m_resolveImages.resize(framesInFlight); + m_resolveViews.resize(framesInFlight); + + for (size_t i = 0; i < framesInFlight; ++i) + { + auto built = builder.setFormat(format).setUsage(usage).build(); // Defaults to e1 samples + + m_resolveImages[i] = std::move(built.image); + m_resolveViews[i] = built.view; + + m_imageStateTracker.recordState(m_resolveImages[i]->get(), vk::ImageLayout::eUndefined); + } +} +void VulkanRenderer::createSceneViewImages() +{ + vk::Format format = m_swapchain->getFormat(); + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled + | vk::ImageUsageFlagBits::eTransferSrc; + size_t framesInFlight = m_frameManager->getFramesInFlightCount(); + + // Destroy old resources if recreating + for (size_t i = 0; i < m_sceneViewViews.size(); ++i) + { + m_context->device().destroyImageView(m_sceneViewViews[i]); + } + m_sceneViewImages.clear(); + m_sceneViewViews.clear(); + + utils::ImageBuilder builder(m_context->device(), *m_allocator, m_swapchain->getExtent()); + m_sceneViewImages.resize(framesInFlight); + m_sceneViewViews.resize(framesInFlight); + + for (size_t i = 0; i < framesInFlight; ++i) + { + auto built = builder.setFormat(format).setUsage(usage).build(); // Defaults to e1 samples and color aspect + + m_sceneViewImages[i] = std::move(built.image); + m_sceneViewViews[i] = built.view; + + m_imageStateTracker.recordState(m_sceneViewImages[i]->get(), vk::ImageLayout::eUndefined); + } +} + +void VulkanRenderer::createSampler() +{ + vk::SamplerCreateInfo samplerInfo{}; + samplerInfo.magFilter = vk::Filter::eLinear; + samplerInfo.minFilter = vk::Filter::eLinear; + samplerInfo.mipmapMode = vk::SamplerMipmapMode::eLinear; + samplerInfo.addressModeU = vk::SamplerAddressMode::eClampToEdge; + samplerInfo.addressModeV = vk::SamplerAddressMode::eClampToEdge; + samplerInfo.addressModeW = vk::SamplerAddressMode::eClampToEdge; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.anisotropyEnable = VK_FALSE; + samplerInfo.maxAnisotropy = 1.0f; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = vk::CompareOp::eAlways; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 0.0f; + samplerInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite; + + m_sampler = std::make_unique(m_context->device(), samplerInfo); +} +void VulkanRenderer::createDescriptorSets() +{ + + const auto framesInFlight = m_frameManager->getFramesInFlightCount(); + m_sceneViewImageDescriptorSets.resize(framesInFlight); + for (int i = 0; i < framesInFlight; ++i) + { + m_sceneViewImageDescriptorSets[i] = m_imgui->createDescriptorSet(m_sceneViewViews[i], m_sampler->get()); + } +} +void VulkanRenderer::createDepthImages() +{ + vk::Format format = vk::Format::eD32Sfloat; + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled; + size_t framesInFlight = m_frameManager->getFramesInFlightCount(); + + // Destroy old resources if recreating + for (size_t i = 0; i < m_depthViews.size(); ++i) + { + m_context->device().destroyImageView(m_depthViews[i]); + } + m_depthImages.clear(); + m_depthViews.clear(); + + utils::ImageBuilder builder(m_context->device(), *m_allocator, m_swapchain->getExtent()); + m_depthImages.resize(framesInFlight); + m_depthViews.resize(framesInFlight); + + for (size_t i = 0; i < framesInFlight; ++i) + { + auto built = builder.setFormat(format) + .setUsage(usage) + .setSamples(vk::SampleCountFlagBits::e4) + .setAspectMask(vk::ImageAspectFlagBits::eDepth) + .build(); + + m_depthImages[i] = std::move(built.image); + m_depthViews[i] = built.view; + + m_imageStateTracker.recordState(m_depthImages[i]->get(), vk::ImageLayout::eUndefined); + } + + // ...existing code creating m_depthImages (MSAA)... + m_depthResolveImages.resize(framesInFlight); + m_depthResolveViews.resize(framesInFlight); + + utils::ImageBuilder resolveBuilder(m_context->device(), *m_allocator, m_swapchain->getExtent()); + for (size_t i = 0; i < framesInFlight; ++i) + { + auto built = resolveBuilder.setFormat(format) + .setUsage(vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled) + .setSamples(vk::SampleCountFlagBits::e1) + .setAspectMask(vk::ImageAspectFlagBits::eDepth) + .build(); + m_depthResolveImages[i] = std::move(built.image); + m_depthResolveViews[i] = built.view; + m_imageStateTracker.recordState(m_depthResolveImages[i]->get(), vk::ImageLayout::eUndefined); + } +} +void VulkanRenderer::createDepthPipelineAndDescriptorSets() +{ + + std::vector setLayouts = {m_descriptorSet->getLayout()}; + + m_depthPipeline = Pipeline::Builder(m_context->device()) + .setVertexShader(m_config.vertShaderPath) + // No fragment shader, we only want depth output + .setVertexInputFromVertex() + .setDepthAttachment(vk::Format::eD32Sfloat, true) // depth test and write enabled + .setDescriptorSetLayouts(setLayouts) + .setMultisample(4) + .setFrontFace(vk::FrontFace::eClockwise) // Match main geometry pipeline + .addPushContantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)) + .build(); + +} + +void VulkanRenderer::initScene() +{ + auto planeVerts = generatePlaneVertices(10, 50.0f); + auto planeInds = generatePlaneIndices(10); + auto planeMesh = std::make_shared(*m_allocator, planeVerts, planeInds); + m_objects.push_back({planeMesh, glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -0.0f, 0.0f))}); + + auto meshDataVec = loadModelFromBinary("../resources/models/thingy.mesh"); + + if (!meshDataVec.empty()) + { + const auto& meshData = meshDataVec[0]; // Use the first mesh for monkey + auto monkeyMesh = std::make_shared(*m_allocator, meshData.vertices, meshData.indices); + m_objects.push_back(RenderObject{monkeyMesh}); + } +} + +} // namespace reactor diff --git a/src/vulkan/VulkanRenderer.hpp b/src/vulkan/VulkanRenderer.hpp index b80eb25..a3e7953 100644 --- a/src/vulkan/VulkanRenderer.hpp +++ b/src/vulkan/VulkanRenderer.hpp @@ -1,129 +1,129 @@ -#pragma once - -#include - -#include "../core/Camera.hpp" -#include "../core/Uniforms.hpp" -#include "../core/Window.hpp" -#include "../imgui/Imgui.hpp" -#include "Allocator.hpp" -#include "DescriptorSet.hpp" -#include "FrameManager.hpp" -#include "Image.hpp" -#include "ImageStateTracker.h" -#include "Mesh.hpp" -#include "MeshGenerators.hpp" -#include "Pipeline.hpp" -#include "Sampler.hpp" -#include "ShadowMapping.hpp" -#include "Swapchain.hpp" -#include "UniformManager.hpp" -#include "VulkanContext.hpp" - -namespace reactor -{ - -struct RendererConfig -{ - uint32_t windowWidth; - uint32_t windowHeight; - std::string windowTitle; - std::string vertShaderPath; - std::string fragShaderPath; - std::string compositeVertShaderPath; - std::string compositeFragShaderPath; -}; - -struct RenderObject -{ - std::shared_ptr mesh; - glm::mat4 transform = glm::mat4(1.0f); -}; - -class VulkanRenderer -{ -public: - VulkanRenderer(const RendererConfig& config, Window& window, Camera& camera); - ~VulkanRenderer(); - - void drawFrame(); - - vk::Device device() const; - Allocator& allocator(); - vk::DescriptorPool descriptorPool() const; - -private: - const RendererConfig& m_config; - Window& m_window; - Camera& m_camera; - - std::unique_ptr m_context; - std::unique_ptr m_swapchain; - std::unique_ptr m_allocator; - std::unique_ptr m_frameManager; - std::unique_ptr m_descriptorSet; - std::unique_ptr m_pipeline; - std::unique_ptr m_compositePipeline; - std::unique_ptr m_depthPipeline; - std::unique_ptr m_compositeDescriptorSet; - std::unique_ptr m_sampler; - std::unique_ptr m_uniformManager; - std::unique_ptr m_imgui; - std::unique_ptr m_shadowMapping; - - ImageStateTracker m_imageStateTracker; - - std::vector> m_msaaImages; - std::vector m_msaaColorViews; - - std::vector> m_resolveImages; - std::vector m_resolveViews; - std::vector> m_sceneViewImages; - std::vector m_sceneViewViews; - std::vector m_sceneViewImageDescriptorSets; - - std::vector> m_depthImages; - std::vector m_depthViews; - std::vector m_depthImageDescriptorSets; - - // storage for resolved depth - std::vector> m_depthResolveImages; - std::vector m_depthResolveViews; - - DirectionalLightUBO m_light; - std::vector m_objects; - - vk::DescriptorPool m_descriptorPool; - - void createCoreVulkanObjects(); - void createSwapchainAndFrameManager(); - void createPipelineAndDescriptors(); - void setupUI(std::shared_ptr consoleSink); - void createMSAAImage(); - void createResolveImages(); - void createSceneViewImages(); - void createSampler(); - void createDescriptorSets(); - void createDepthImages(); - void createDepthPipelineAndDescriptorSets(); - void initScene(); - void createDescriptorPool(); - - void handleSwapchainResizing(); - void beginCommandBuffer(vk::CommandBuffer cmd); - void beginDynamicRendering(vk::CommandBuffer cmd, - vk::ImageView colorImageView, - vk::ImageView resolveImageView, - vk::ImageView depthImageView, - vk::Extent2D extent, - bool clearColor = true, - bool clearDepth = false); - void bindDescriptorSets(vk::CommandBuffer cmd); - void drawGeometry(vk::CommandBuffer cmd); - void renderUI(vk::CommandBuffer cmd) const; - static void endDynamicRendering(vk::CommandBuffer cmd); - static void endCommandBuffer(vk::CommandBuffer cmd); - void submitAndPresent(uint32_t imageIndex); -}; - -} // namespace reactor +#pragma once + +#include + +#include "../core/Camera.hpp" +#include "../core/Uniforms.hpp" +#include "../core/Window.hpp" +#include "../imgui/Imgui.hpp" +#include "Allocator.hpp" +#include "DescriptorSet.hpp" +#include "FrameManager.hpp" +#include "Image.hpp" +#include "ImageStateTracker.h" +#include "Mesh.hpp" +#include "MeshGenerators.hpp" +#include "Pipeline.hpp" +#include "Sampler.hpp" +#include "ShadowMapping.hpp" +#include "Swapchain.hpp" +#include "UniformManager.hpp" +#include "VulkanContext.hpp" + +namespace reactor +{ + +struct RendererConfig +{ + uint32_t windowWidth; + uint32_t windowHeight; + std::string windowTitle; + std::string vertShaderPath; + std::string fragShaderPath; + std::string compositeVertShaderPath; + std::string compositeFragShaderPath; +}; + +struct RenderObject +{ + std::shared_ptr mesh; + glm::mat4 transform = glm::mat4(1.0f); +}; + +class VulkanRenderer +{ +public: + VulkanRenderer(const RendererConfig& config, Window& window, Camera& camera); + ~VulkanRenderer(); + + void drawFrame(); + + vk::Device device() const; + Allocator& allocator(); + vk::DescriptorPool descriptorPool() const; + +private: + const RendererConfig& m_config; + Window& m_window; + Camera& m_camera; + + std::unique_ptr m_context; + std::unique_ptr m_swapchain; + std::unique_ptr m_allocator; + std::unique_ptr m_frameManager; + std::unique_ptr m_descriptorSet; + std::unique_ptr m_pipeline; + std::unique_ptr m_compositePipeline; + std::unique_ptr m_depthPipeline; + std::unique_ptr m_compositeDescriptorSet; + std::unique_ptr m_sampler; + std::unique_ptr m_uniformManager; + std::unique_ptr m_imgui; + std::unique_ptr m_shadowMapping; + + ImageStateTracker m_imageStateTracker; + + std::vector> m_msaaImages; + std::vector m_msaaColorViews; + + std::vector> m_resolveImages; + std::vector m_resolveViews; + std::vector> m_sceneViewImages; + std::vector m_sceneViewViews; + std::vector m_sceneViewImageDescriptorSets; + + std::vector> m_depthImages; + std::vector m_depthViews; + std::vector m_depthImageDescriptorSets; + + // storage for resolved depth + std::vector> m_depthResolveImages; + std::vector m_depthResolveViews; + + DirectionalLightUBO m_light; + std::vector m_objects; + + vk::DescriptorPool m_descriptorPool; + + void createCoreVulkanObjects(); + void createSwapchainAndFrameManager(); + void createPipelineAndDescriptors(); + void setupUI(std::shared_ptr consoleSink); + void createMSAAImage(); + void createResolveImages(); + void createSceneViewImages(); + void createSampler(); + void createDescriptorSets(); + void createDepthImages(); + void createDepthPipelineAndDescriptorSets(); + void initScene(); + void createDescriptorPool(); + + void handleSwapchainResizing(); + void beginCommandBuffer(vk::CommandBuffer cmd); + void beginDynamicRendering(vk::CommandBuffer cmd, + vk::ImageView colorImageView, + vk::ImageView resolveImageView, + vk::ImageView depthImageView, + vk::Extent2D extent, + bool clearColor = true, + bool clearDepth = false); + void bindDescriptorSets(vk::CommandBuffer cmd); + void drawGeometry(vk::CommandBuffer cmd); + void renderUI(vk::CommandBuffer cmd) const; + static void endDynamicRendering(vk::CommandBuffer cmd); + static void endCommandBuffer(vk::CommandBuffer cmd); + void submitAndPresent(uint32_t imageIndex); +}; + +} // namespace reactor diff --git a/src/vulkan/VulkanUtils.hpp b/src/vulkan/VulkanUtils.hpp index 5620105..3021732 100644 --- a/src/vulkan/VulkanUtils.hpp +++ b/src/vulkan/VulkanUtils.hpp @@ -1,40 +1,40 @@ -#pragma once - -#include - -namespace reactor::utils -{ - -inline void setupViewportAndScissor(vk::CommandBuffer cmd, vk::Extent2D extent) -{ - vk::Viewport viewport{}; - viewport.x = 0.0f; - viewport.y = 0.0f; - viewport.width = static_cast(extent.width); - viewport.height = static_cast(extent.height); - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - cmd.setViewport(0, viewport); - - const vk::Rect2D scissor{{0, 0}, extent}; - cmd.setScissor(0, scissor); -} - -inline vk::SampleCountFlagBits mapSampleCountFlag(uint32_t sampleCount) -{ - switch (sampleCount) - { - case 1: - return vk::SampleCountFlagBits::e1; - case 2: - return vk::SampleCountFlagBits::e2; - case 4: - return vk::SampleCountFlagBits::e4; - case 8: - return vk::SampleCountFlagBits::e8; - default: - return vk::SampleCountFlagBits::e1; - } -} - +#pragma once + +#include + +namespace reactor::utils +{ + +inline void setupViewportAndScissor(vk::CommandBuffer cmd, vk::Extent2D extent) +{ + vk::Viewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = static_cast(extent.width); + viewport.height = static_cast(extent.height); + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + cmd.setViewport(0, viewport); + + const vk::Rect2D scissor{{0, 0}, extent}; + cmd.setScissor(0, scissor); +} + +inline vk::SampleCountFlagBits mapSampleCountFlag(uint32_t sampleCount) +{ + switch (sampleCount) + { + case 1: + return vk::SampleCountFlagBits::e1; + case 2: + return vk::SampleCountFlagBits::e2; + case 4: + return vk::SampleCountFlagBits::e4; + case 8: + return vk::SampleCountFlagBits::e8; + default: + return vk::SampleCountFlagBits::e1; + } +} + } // namespace reactor::utils \ No newline at end of file diff --git a/src/vulkan/vk_mem_alloc.cpp b/src/vulkan/vk_mem_alloc.cpp index a2023d3..ac249df 100644 --- a/src/vulkan/vk_mem_alloc.cpp +++ b/src/vulkan/vk_mem_alloc.cpp @@ -1,2 +1,2 @@ -#define VMA_IMPLEMENTATION -#include "vk_mem_alloc.h" +#define VMA_IMPLEMENTATION +#include "vk_mem_alloc.h" diff --git a/vcpkg b/vcpkg index 984f923..44a1e4e 160000 --- a/vcpkg +++ b/vcpkg @@ -1 +1 @@ -Subproject commit 984f9232b2fe0eb94f5e9f161d6c632c581fff0c +Subproject commit 44a1e4eca5211434b5fefcc25a69bba246c3f861 diff --git a/vcpkg.json b/vcpkg.json index 9690106..ffe8ea9 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,21 +1,21 @@ -{ - "name" : "reactor", - "version-string" : "1.0.0", - "dependencies" : [ - "fmt", - "spdlog", - "vulkan-headers", - "vulkan-memory-allocator", - "glfw3", - "glm", - { - "name": "imgui", - "features": [ - "glfw-binding", - "vulkan-binding", - "docking-experimental" - ] - }, - "assimp" - ] +{ + "name" : "reactor", + "version-string" : "1.0.0", + "dependencies" : [ + "fmt", + "spdlog", + "vulkan-headers", + "vulkan-memory-allocator", + "glfw3", + "glm", + { + "name": "imgui", + "features": [ + "glfw-binding", + "vulkan-binding", + "docking-experimental" + ] + }, + "assimp" + ] } \ No newline at end of file From 88639788ee5c68d7f5557101d561996614d5a0b9 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Thu, 14 Aug 2025 12:47:55 -0500 Subject: [PATCH 09/24] fixed issues with HiDPI rendering --- CMakeLists.txt | 6 ++++++ src/imgui/Imgui.cpp | 18 ++++++++++++++++++ src/vulkan/Allocator.cpp | 6 ++++++ src/vulkan/ImageUtils.cpp | 8 +++++++- src/vulkan/VulkanContext.cpp | 4 +++- src/vulkan/VulkanContext.hpp | 5 ++--- src/vulkan/vk_mem_alloc.cpp | 1 + 7 files changed, 43 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3bfaa96..d6095c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,12 @@ add_library(ReactorLib STATIC src/vulkan/DebugUtils.hpp ) +target_compile_definitions(ReactorLib PRIVATE + VK_NO_PROTOTYPES + VMA_STATIC_VULKAN_FUNCTIONS=0 + VMA_DYNAMIC_VULKAN_FUNCTIONS=1 +) +#add_compile_definitions(VK_NO_PROTOTYPES) target_compile_definitions(ReactorLib PUBLIC GLM_ENABLE_EXPERIMENTAL) target_compile_definitions(ReactorLib PUBLIC GLM_FORCE_DEPTH_ZERO_TO_ONE) diff --git a/src/imgui/Imgui.cpp b/src/imgui/Imgui.cpp index 147ed7f..786a770 100644 --- a/src/imgui/Imgui.cpp +++ b/src/imgui/Imgui.cpp @@ -23,6 +23,24 @@ Imgui::Imgui(VulkanContext& vulkanContext, ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + + io.Fonts->AddFontFromFileTTF("../resources/fonts/Roboto-Medium.ttf", 16.0f); + + float scaleX, scaleY; + GLFWwindow* nativeWindow = window.getNativeWindow(); + glfwGetWindowContentScale(nativeWindow, &scaleX, &scaleY); + float hidpiScale = scaleX; + spdlog::info("Hidpi scale: {}", hidpiScale); + hidpiScale = 1.5; + + if (hidpiScale > 1.0f) + { + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(hidpiScale); + io.Fonts->Clear(); + io.Fonts->AddFontFromFileTTF("../resources/fonts/Roboto-Medium.ttf", 16.0f * hidpiScale); + } + ImGui::StyleColorsDark(); // --- Initialize descriptor pool for ImGui --- diff --git a/src/vulkan/Allocator.cpp b/src/vulkan/Allocator.cpp index 17cb987..f8c7291 100644 --- a/src/vulkan/Allocator.cpp +++ b/src/vulkan/Allocator.cpp @@ -19,6 +19,12 @@ Allocator::Allocator(vk::PhysicalDevice physicalDevice, vk::Device device, vk::I allocatorInfo.device = device; allocatorInfo.instance = instance; + // Tell VMA how to load Vulkan entry points (needed with VK_NO_PROTOTYPES) + VmaVulkanFunctions funcs{}; + funcs.vkGetInstanceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr; + funcs.vkGetDeviceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetDeviceProcAddr; + allocatorInfo.pVulkanFunctions = &funcs; + vmaCreateAllocator(&allocatorInfo, &m_allocator); spdlog::info("Allocator created"); diff --git a/src/vulkan/ImageUtils.cpp b/src/vulkan/ImageUtils.cpp index 6acade6..be5cbe3 100644 --- a/src/vulkan/ImageUtils.cpp +++ b/src/vulkan/ImageUtils.cpp @@ -4,7 +4,7 @@ namespace reactor::utils { ImageBuilder::ImageBuilder(const vk::Device& device, Allocator& allocator, const vk::Extent2D& defaultExtent) - : m_device(device), m_allocator(allocator), m_extent(defaultExtent) + : m_device(device), m_allocator(allocator), m_extent(defaultExtent), m_imageInfo{}, m_viewInfo{} { m_imageInfo.imageType = vk::ImageType::e2D; m_imageInfo.extent = vk::Extent3D{m_extent.width, m_extent.height, 1}; @@ -15,12 +15,16 @@ ImageBuilder::ImageBuilder(const vk::Device& device, Allocator& allocator, const m_imageInfo.sharingMode = vk::SharingMode::eExclusive; m_imageInfo.samples = vk::SampleCountFlagBits::e1; + m_viewInfo.viewType = vk::ImageViewType::e2D; m_viewInfo.subresourceRange.baseMipLevel = 0; m_viewInfo.subresourceRange.levelCount = 1; m_viewInfo.subresourceRange.baseArrayLayer = 0; m_viewInfo.subresourceRange.layerCount = 1; m_viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + m_viewInfo.components = { vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, + vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity }; + } ImageBuilder& ImageBuilder::setFormat(vk::Format format) @@ -52,6 +56,8 @@ ImageBuilder::BuiltImage ImageBuilder::build() { auto image = std::make_unique(m_allocator, m_imageInfo, VMA_MEMORY_USAGE_GPU_ONLY); + assert(VULKAN_HPP_DEFAULT_DISPATCHER.vkCreateImageView && "Device fp not loaded"); + m_viewInfo.image = image->get(); vk::ImageView view = m_device.createImageView(m_viewInfo); diff --git a/src/vulkan/VulkanContext.cpp b/src/vulkan/VulkanContext.cpp index d859862..a8a6269 100644 --- a/src/vulkan/VulkanContext.cpp +++ b/src/vulkan/VulkanContext.cpp @@ -208,7 +208,9 @@ void VulkanContext::createLogicalDevice() { m_presentQueue = m_device.getQueue(indices.presentFamily.value(), 0); m_queueFamilies = indices; // Store the found queue families - m_dldi = vk::detail::DispatchLoaderDynamic(m_instance, vkGetInstanceProcAddr, m_device, vkGetDeviceProcAddr); + // do dynamic loading on that device + VULKAN_HPP_DEFAULT_DISPATCHER.init(m_device); + spdlog::info("Logical device created"); } catch (const vk::SystemError& err) { std::cerr << "Failed to create logical device: " << err.what() << std::endl; diff --git a/src/vulkan/VulkanContext.hpp b/src/vulkan/VulkanContext.hpp index 9cb18f8..7c1124a 100644 --- a/src/vulkan/VulkanContext.hpp +++ b/src/vulkan/VulkanContext.hpp @@ -28,12 +28,12 @@ class VulkanContext { [[nodiscard]] vk::Instance instance() const { return m_instance; } [[nodiscard]] vk::PhysicalDevice physicalDevice() const { return m_physicalDevice; } - [[nodiscard]] vk::Device device() const { return m_device; } + [[nodiscard]] const vk::Device& device() const { return m_device; } [[nodiscard]] vk::SurfaceKHR surface() const { return m_surface; } [[nodiscard]] vk::Queue graphicsQueue() const { return m_graphicsQueue; } [[nodiscard]] vk::Queue presentQueue() const { return m_presentQueue; } [[nodiscard]] QueueFamilyIndices queueFamilies() const { return m_queueFamilies; } - [[nodiscard]] const vk::detail::DispatchLoaderDynamic& dldi() const { return m_dldi; } + private: // Private helper methods to keep the constructor clean @@ -50,7 +50,6 @@ class VulkanContext { vk::SurfaceKHR m_surface; vk::PhysicalDevice m_physicalDevice; vk::Device m_device; - vk::detail::DispatchLoaderDynamic m_dldi; // Queues are retrieved from the logical device vk::Queue m_graphicsQueue; diff --git a/src/vulkan/vk_mem_alloc.cpp b/src/vulkan/vk_mem_alloc.cpp index ac249df..b3dede6 100644 --- a/src/vulkan/vk_mem_alloc.cpp +++ b/src/vulkan/vk_mem_alloc.cpp @@ -1,2 +1,3 @@ + #define VMA_IMPLEMENTATION #include "vk_mem_alloc.h" From 6ed0ce243b4edf2fb5023c4ebcb5d0dda638ca26 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Thu, 14 Aug 2025 17:24:52 -0500 Subject: [PATCH 10/24] make resolution bigger --- src/core/Application.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/Application.cpp b/src/core/Application.cpp index 350eeaf..37097c3 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -2,6 +2,9 @@ namespace reactor { +const int WINDOW_WIDTH = 1920; +const int WINDOW_HEIGHT = 1080; + Application::Application() { initialize(); } @@ -11,7 +14,7 @@ Application::~Application() = default; void Application::initialize() { m_eventManager = std::make_unique(); - m_window = std::make_unique(1280, 720, "Reactor", *m_eventManager); + m_window = std::make_unique(WINDOW_WIDTH, WINDOW_HEIGHT, "Reactor", *m_eventManager); m_camera = std::make_unique(); m_orbitController = std::make_unique(*m_camera); m_orbitController->setSimCityView(20.0f, 45.0f); @@ -22,8 +25,8 @@ void Application::initialize() { m_eventManager->subscribe(EventType::MouseButtonReleased, m_orbitController.get()); RendererConfig config{ - .windowWidth = 1280, - .windowHeight = 720, + .windowWidth = WINDOW_WIDTH, + .windowHeight = WINDOW_HEIGHT, .windowTitle = "Reactor", .vertShaderPath = "../resources/shaders/triangle-slang.vert.spv", .fragShaderPath = "../resources/shaders/triangle-slang.frag.spv", From 8ff98faf72805ec695078476388d2b3b3e162175 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Thu, 14 Aug 2025 17:25:08 -0500 Subject: [PATCH 11/24] add script to build the shaders --- build-shaders.sh | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 build-shaders.sh diff --git a/build-shaders.sh b/build-shaders.sh new file mode 100644 index 0000000..5bb8c06 --- /dev/null +++ b/build-shaders.sh @@ -0,0 +1,9 @@ +slangc -g -O0 shaders/triangle.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/triangle-slang.vert.spv + +slangc -g -O0 shaders/triangle.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/triangle-slang.frag.spv + +slangc -g -O0 shaders/composite.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/composite-slang.vert.spv + +slangc -g -O0 shaders/composite.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/composite-slang.frag.spv + +echo "Shaders compiled" \ No newline at end of file From 223c6772fc3e13c3bb3da0e3e29f48bdb5dcc2c9 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Thu, 14 Aug 2025 17:25:34 -0500 Subject: [PATCH 12/24] small change --- src/imgui/Imgui.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/imgui/Imgui.cpp b/src/imgui/Imgui.cpp index 786a770..a8cd822 100644 --- a/src/imgui/Imgui.cpp +++ b/src/imgui/Imgui.cpp @@ -4,7 +4,6 @@ #include "Imgui.hpp" -#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES #include #include #include From daade0c963344604ede3e15089868931624f4eb1 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Thu, 14 Aug 2025 21:26:38 -0500 Subject: [PATCH 13/24] fixed warnings --- CMakeLists.txt | 5 +++++ src/core/OrbitController.cpp | 2 ++ src/core/Window.cpp | 10 ++++++++++ src/core/Window.hpp | 2 +- src/imgui/Imgui.hpp | 4 +++- src/vulkan/FrameManager.cpp | 6 +++++- src/vulkan/VulkanRenderer.cpp | 4 ++-- 7 files changed, 28 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d6095c9..add66ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,11 @@ add_library(ReactorLib STATIC src/vulkan/DebugUtils.hpp ) +target_compile_options(ReactorLib PRIVATE + $<$:-Wall -Wextra -Wpedantic -Werror> + $<$:/Wall /WX> +) + target_compile_definitions(ReactorLib PRIVATE VK_NO_PROTOTYPES VMA_STATIC_VULKAN_FUNCTIONS=0 diff --git a/src/core/OrbitController.cpp b/src/core/OrbitController.cpp index 8dc5ae2..56ade2f 100644 --- a/src/core/OrbitController.cpp +++ b/src/core/OrbitController.cpp @@ -58,6 +58,8 @@ void OrbitController::onEvent(const Event &event) { break; case EventType::KeyPressed: break; + case EventType::KeyReleased: + break; } } diff --git a/src/core/Window.cpp b/src/core/Window.cpp index 65c4ba4..b58fdaf 100644 --- a/src/core/Window.cpp +++ b/src/core/Window.cpp @@ -1,4 +1,7 @@ #include "Window.hpp" + +#include "../logging/Logger.hpp" + #include namespace reactor { @@ -67,11 +70,16 @@ m_width(width), m_height(height), m_title(title), m_eventManager(eventManager) { // Set the flag indicating that a resize has occurred. windowInstance->m_framebufferResized = true; } + + LOG_INFO("Resized to {} {}", width, height); } + void Window::keyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) { auto* windowInstance = static_cast(glfwGetWindowUserPointer(window)); if (!windowInstance) return; + LOG_INFO("Key {} pressed, {} scancode, {} mods", key, scancode, mods); + Event event{}; if (action == GLFW_PRESS) { event.type = EventType::KeyPressed; @@ -89,6 +97,8 @@ m_width(width), m_height(height), m_title(title), m_eventManager(eventManager) { auto* windowInstance = static_cast(glfwGetWindowUserPointer(window)); if (!windowInstance) return; + LOG_INFO("Mouse button {} pressed, {} mods", button, mods); + Event event{}; if (action == GLFW_PRESS) { event.type = EventType::MouseButtonPressed; diff --git a/src/core/Window.hpp b/src/core/Window.hpp index 648cbb0..cbcbd08 100644 --- a/src/core/Window.hpp +++ b/src/core/Window.hpp @@ -58,11 +58,11 @@ namespace reactor { static void cursorPosCallback(GLFWwindow* window, double xpos, double ypos); GLFWwindow* m_window = nullptr; - EventManager& m_eventManager; int m_width; int m_height; std::string m_title; bool m_framebufferResized = false; + EventManager& m_eventManager; }; } diff --git a/src/imgui/Imgui.hpp b/src/imgui/Imgui.hpp index 0fa5bc3..c935352 100644 --- a/src/imgui/Imgui.hpp +++ b/src/imgui/Imgui.hpp @@ -30,9 +30,11 @@ class Imgui { private: - std::shared_ptr m_consoleSink; + vk::Device m_device; EventManager& m_eventManager; + std::shared_ptr m_consoleSink; + vk::DescriptorPool m_descriptorPool; vk::DescriptorSet m_sceneImguiId; diff --git a/src/vulkan/FrameManager.cpp b/src/vulkan/FrameManager.cpp index 94cc61f..7151a86 100644 --- a/src/vulkan/FrameManager.cpp +++ b/src/vulkan/FrameManager.cpp @@ -93,7 +93,11 @@ bool FrameManager::beginFrame(vk::SwapchainKHR swapchain, uint32_t& outImageInde outImageIndex = resultValue.value; if (m_imagesInFlight[outImageIndex] != VK_NULL_HANDLE) { - m_device.waitForFences(m_imagesInFlight[outImageIndex], VK_TRUE, UINT64_MAX); + auto ret = m_device.waitForFences(m_imagesInFlight[outImageIndex], VK_TRUE, UINT64_MAX); + if (ret != vk::Result::eSuccess) + { + throw std::runtime_error("Failed to wait for fence!"); + } } // Associate the current frame's fence with the acquired swapchain image diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 11457a3..2029bcb 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -193,7 +193,7 @@ VulkanRenderer::~VulkanRenderer() m_context->device().waitIdle(); - for (auto i = 0; i < m_frameManager->getFramesInFlightCount(); ++i) + for (size_t i = 0; i < m_frameManager->getFramesInFlightCount(); ++i) { m_context->device().destroyImageView(m_msaaColorViews[i]); m_context->device().destroyImageView(m_resolveViews[i]); @@ -747,7 +747,7 @@ void VulkanRenderer::createDescriptorSets() const auto framesInFlight = m_frameManager->getFramesInFlightCount(); m_sceneViewImageDescriptorSets.resize(framesInFlight); - for (int i = 0; i < framesInFlight; ++i) + for (size_t i = 0; i < framesInFlight; ++i) { m_sceneViewImageDescriptorSets[i] = m_imgui->createDescriptorSet(m_sceneViewViews[i], m_sampler->get()); } From c3a5efa582282dd1b2878db4cb8d967f0ef57639 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Sat, 16 Aug 2025 09:07:58 -0500 Subject: [PATCH 14/24] clang tidy stuff --- .clang-format | 39 ------------------------- .clang-tidy | 32 --------------------- src/core/Camera.hpp | 2 +- src/vulkan/Pipeline.cpp | 2 +- src/vulkan/Pipeline.hpp | 4 +-- src/vulkan/ShadowMapping.cpp | 11 ++++--- src/vulkan/ShadowMapping.hpp | 14 ++++----- src/vulkan/VulkanRenderer.cpp | 54 ++++++++++++++++------------------- src/vulkan/VulkanRenderer.hpp | 16 +++++------ 9 files changed, 48 insertions(+), 126 deletions(-) delete mode 100644 .clang-format delete mode 100644 .clang-tidy diff --git a/.clang-format b/.clang-format deleted file mode 100644 index a4c3eab..0000000 --- a/.clang-format +++ /dev/null @@ -1,39 +0,0 @@ ---- -Language: Cpp -BasedOnStyle: LLVM -IndentWidth: 4 -TabWidth: 4 -UseTab: Never -ColumnLimit: 120 # No forced wrapping; useful for long VulkanHpp calls -AccessModifierOffset: -4 -AlignAfterOpenBracket: Align -AlignConsecutiveAssignments: false -AlignConsecutiveDeclarations: false -AllowShortBlocksOnASingleLine: false -AllowShortFunctionsOnASingleLine: None -AllowShortIfStatementsOnASingleLine: Never -AllowShortLoopsOnASingleLine: false -AlwaysBreakTemplateDeclarations: Yes -BinPackArguments: false -BinPackParameters: false -BreakBeforeBinaryOperators: NonAssignment -BreakBeforeBraces: Custom -BraceWrapping: - AfterClass: true - AfterControlStatement: true - AfterEnum: true - AfterFunction: true - AfterNamespace: true - AfterStruct: true - AfterUnion: true - BeforeCatch: true - BeforeElse: true - IndentBraces: false - SplitEmptyFunction: false - SplitEmptyRecord: false -Cpp11BracedListStyle: true -DerivePointerAlignment: false -NamespaceIndentation: None -PointerAlignment: Left -SortIncludes: true -IncludeBlocks: Preserve \ No newline at end of file diff --git a/.clang-tidy b/.clang-tidy deleted file mode 100644 index 122f1d0..0000000 --- a/.clang-tidy +++ /dev/null @@ -1,32 +0,0 @@ -Checks: > - clang-analyzer-*, - bugprone-*, - performance-*, - hicpp-*, - modernize-*, - cppcoreguidelines-*, - readability-*, - - # disable overly noisy checks - -clang-analyzer-alpha.*, - -cppcoreguidelines-pro-type-union-access, - -cppcoreguidelines-pro-type-reinterpret-cast, - -cppcoreguidelines-pro-bounds-pointer-arithmetic, - -cppcoreguidelines-avoid-magic-numbers, - -misc-unused-using-decls, - -misc-unused-parameters, - -hicpp-vararg, - -modernize-use-trailing-return-type, - -readability-const-parameter, - -readability-const-variable - -WarningsAsErrors: '' -HeaderFilterRegex: '^(include|src)/' -FormatStyle: file - -CheckOptions: - - { key: modernize-use-nullptr.NullMacros, value: 'NULL' } - - { key: readability-identifier-naming.VariableCase, value: camelBack } - - { key: readability-identifier-naming.ClassCase, value: CamelCase } - - { key: readability-identifier-naming.FunctionCase, value: camelBack } - - { key: readability-function-size.LineThreshold, value: '200' } diff --git a/src/core/Camera.hpp b/src/core/Camera.hpp index 7eba477..ea8fa60 100644 --- a/src/core/Camera.hpp +++ b/src/core/Camera.hpp @@ -47,7 +47,7 @@ class Camera { glm::mat4 m_view{1.0f}; glm::mat4 m_projection{1.0f}; - float m_fov{45.0f}, m_aspect{16.0f / 9.0f}, m_near{0.1f}, m_far{100.0f}; + float m_fov{45.0f}, m_aspect{16.0f / 9.0f}, m_near{0.1f}, m_far{50.0f}; float m_left, m_right, m_bottom, m_top; glm::vec3 m_position{0.0f, 0.0f, 5.0f}; diff --git a/src/vulkan/Pipeline.cpp b/src/vulkan/Pipeline.cpp index 42955d0..c968f4b 100644 --- a/src/vulkan/Pipeline.cpp +++ b/src/vulkan/Pipeline.cpp @@ -81,7 +81,7 @@ namespace reactor return *this; } - Pipeline::Builder& Pipeline::Builder::addPushContantRange(vk::ShaderStageFlags stages, uint32_t offset, uint32_t size) + Pipeline::Builder& Pipeline::Builder::addPushConstantRange(vk::ShaderStageFlags stages, uint32_t offset, uint32_t size) { m_pushRanges.push_back({stages, offset, size}); return *this; diff --git a/src/vulkan/Pipeline.hpp b/src/vulkan/Pipeline.hpp index ca3c615..021253e 100644 --- a/src/vulkan/Pipeline.hpp +++ b/src/vulkan/Pipeline.hpp @@ -15,7 +15,7 @@ namespace reactor class Builder { public: - Builder(vk::Device device); + explicit Builder(vk::Device device); Builder& setVertexShader(const std::string& shaderPath); Builder& setFragmentShader(const std::string& shaderPath); @@ -26,7 +26,7 @@ namespace reactor Builder& setMultisample(uint32_t samples); Builder& setCullMode(vk::CullModeFlags cullMode); Builder& setFrontFace(vk::FrontFace frontFace); - Builder& addPushContantRange(vk::ShaderStageFlags stages, uint32_t offset, uint32_t size); + Builder& addPushConstantRange(vk::ShaderStageFlags stages, uint32_t offset, uint32_t size); Builder& enableDepthBias(bool enable=true); [[nodiscard]] std::unique_ptr build() const; diff --git a/src/vulkan/ShadowMapping.cpp b/src/vulkan/ShadowMapping.cpp index 508e48e..c1ba8ef 100644 --- a/src/vulkan/ShadowMapping.cpp +++ b/src/vulkan/ShadowMapping.cpp @@ -46,7 +46,7 @@ void ShadowMapping::createResources() m_shadowMap = std::make_unique(allocator, imageInfo, memoryUsage); - // 2. Create image view + // 2. Create the image view vk::ImageViewCreateInfo viewInfo{}; viewInfo.image = m_shadowMap->get(); viewInfo.viewType = vk::ImageViewType::e2D; @@ -73,7 +73,8 @@ void ShadowMapping::createResources() m_shadowMapSampler = device.createSampler(samplerInfo); - const size_t frameCount = 2; + // TODO: check swapchain for the frame count + constexpr size_t frameCount = 2; m_mvpBuffer.clear(); m_mvpBuffer.reserve(frameCount); for (size_t i = 0; i < frameCount; ++i) @@ -110,7 +111,7 @@ void ShadowMapping::createPipeline() .setMultisample(1) .setCullMode(vk::CullModeFlagBits::eFront) .setFrontFace(vk::FrontFace::eClockwise) // Match main geometry pipeline - .addPushContantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)); + .addPushConstantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)); m_depthPassPipeline = builder.build(); } @@ -124,7 +125,6 @@ void ShadowMapping::createDescriptors() // Descriptor set bindings: UBO for light's MVP std::vector bindings = { {0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex} - // Add more if you want—for example for a sampler or shadow map }; // Use your DescriptorSet abstraction to handle layout & Vulkan allocation @@ -213,8 +213,7 @@ vk::Image ShadowMapping::shadowMapImage() const return m_shadowMap->get(); } -void ShadowMapping::setLightMatrix(const glm::mat4& lightSpaceMatrix, size_t frameIndex) -{ +void ShadowMapping::setLightMatrix(const glm::mat4& lightSpaceMatrix, size_t frameIndex) const { SceneUBO ubo{}; ubo.view = glm::mat4(1.0f); ubo.projection = lightSpaceMatrix; diff --git a/src/vulkan/ShadowMapping.hpp b/src/vulkan/ShadowMapping.hpp index 394ab4b..24429a5 100644 --- a/src/vulkan/ShadowMapping.hpp +++ b/src/vulkan/ShadowMapping.hpp @@ -20,14 +20,14 @@ class ShadowMapping void recordShadowPass(vk::CommandBuffer cmd, size_t frameIndex, const std::function& drawCallback); - vk::ImageView shadowMapView() const; - vk::Sampler shadowMapSampler() const; - vk::DescriptorSet shadowMapDescriptorSet(size_t frameIndex) const; - vk::Image shadowMapImage() const; + [[nodiscard]] vk::ImageView shadowMapView() const; + [[nodiscard]] vk::Sampler shadowMapSampler() const; + [[nodiscard]] vk::DescriptorSet shadowMapDescriptorSet(size_t frameIndex) const; + [[nodiscard]] vk::Image shadowMapImage() const; - void setLightMatrix(const glm::mat4& lightMVP, size_t frameIndex); + void setLightMatrix(const glm::mat4& lightMVP, size_t frameIndex) const; - uint32_t resolution() const + [[nodiscard]] uint32_t resolution() const { return m_resolution; } @@ -43,7 +43,7 @@ class ShadowMapping VulkanRenderer& m_renderer; uint32_t m_resolution; - // depth image, view, and sampler for shadow map + // depth image, view, and sampler for shadowmap std::unique_ptr m_shadowMap; vk::ImageView m_shadowMapView; vk::Sampler m_shadowMapSampler; diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 2029bcb..ecee3b5 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -30,7 +30,7 @@ VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Cam createCoreVulkanObjects(); createSwapchainAndFrameManager(); - // Setup the Uniform Manager + // Set up the Uniform Manager m_uniformManager = std::make_unique(*m_allocator, m_frameManager->getFramesInFlightCount()); m_uniformManager->registerUBO("scene"); m_uniformManager->registerUBO("composite"); @@ -52,8 +52,7 @@ VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Cam m_imageStateTracker.recordState(m_shadowMapping->shadowMapImage(), vk::ImageLayout::eUndefined); } -Allocator& VulkanRenderer::allocator() -{ +Allocator& VulkanRenderer::allocator() const { return *m_allocator; } @@ -133,11 +132,11 @@ void VulkanRenderer::createPipelineAndDescriptors() .setDescriptorSetLayouts(setLayouts) .setMultisample(4) .setFrontFace(vk::FrontFace::eClockwise) // Assuming standard winding order for cubes - .addPushContantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)) + .addPushConstantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)) .build(); Debug::setObjectName(m_context->device(), - (uint64_t)(VkPipeline)m_pipeline->get(), + reinterpret_cast(static_cast(m_pipeline->get())), vk::ObjectType::ePipeline, "Main Geometry Pipeline"); @@ -167,8 +166,7 @@ void VulkanRenderer::createPipelineAndDescriptors() .build(); } -void VulkanRenderer::handleSwapchainResizing() -{ +void VulkanRenderer::handleSwapchainResizing() const { if (m_window.wasResized()) { vk::Extent2D size = m_window.getFramebufferSize(); @@ -183,7 +181,7 @@ void VulkanRenderer::handleSwapchainResizing() } } -void VulkanRenderer::setupUI(std::shared_ptr consoleSink) +void VulkanRenderer::setupUI(const std::shared_ptr& consoleSink) { m_imgui = std::make_unique(*m_context, m_window, m_window.getEventManager(), consoleSink); } @@ -210,8 +208,7 @@ void VulkanRenderer::beginCommandBuffer(vk::CommandBuffer cmd) cmd.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); } -void VulkanRenderer::bindDescriptorSets(vk::CommandBuffer cmd) -{ +void VulkanRenderer::bindDescriptorSets(vk::CommandBuffer cmd) const { cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_pipeline->getLayout(), 0, @@ -219,8 +216,7 @@ void VulkanRenderer::bindDescriptorSets(vk::CommandBuffer cmd) nullptr); } -void VulkanRenderer::drawGeometry(vk::CommandBuffer cmd) -{ +void VulkanRenderer::drawGeometry(vk::CommandBuffer cmd) const { for (const auto& obj : m_objects) { cmd.pushConstants( @@ -250,8 +246,7 @@ void VulkanRenderer::endCommandBuffer(vk::CommandBuffer cmd) cmd.end(); } -void VulkanRenderer::submitAndPresent(uint32_t imageIndex) -{ +void VulkanRenderer::submitAndPresent(uint32_t imageIndex) const { m_frameManager->endFrame(m_context->graphicsQueue(), m_context->presentQueue(), m_swapchain->get(), imageIndex); } @@ -261,8 +256,7 @@ void VulkanRenderer::beginDynamicRendering(vk::CommandBuffer cmd, vk::ImageView depthImageView, vk::Extent2D extent, bool clearColor, - bool clearDepth) -{ + bool clearDepth) const { vk::RenderingAttachmentInfo colorAttachment{}; vk::RenderingAttachmentInfo depthAttachment{}; vk::RenderingInfo renderingInfo{}; @@ -364,15 +358,15 @@ void VulkanRenderer::drawFrame() compositeData.uFogDensity = m_imgui->getFogDensity(); m_uniformManager->update(frameIdx, compositeData); - m_light.lightDirection = glm::vec4(sin(time), -0.5f, cos(time), 0.0f); + m_light.lightDirection = glm::vec4(glm::sin(time), -0.5f, glm::cos(time), 0.0f); m_light.lightDirection = glm::normalize(m_light.lightDirection); - const float orthoSize = 10.0f; - const float nearPlane = 0.1f; - const float farPlane = 100.0f; + constexpr float orthoSize = 10.0f; + constexpr float nearPlane = 0.1f; + constexpr float farPlane = 100.0f; glm::mat4 lightProjection = glm::ortho(-orthoSize, orthoSize, -orthoSize, orthoSize, nearPlane, farPlane); - glm::vec3 lightTarget = glm::vec3(0.0f); + auto lightTarget = glm::vec3(0.0f); float lightDistance = 20.0f; glm::vec3 lightPosition = lightTarget - glm::vec3(m_light.lightDirection) * lightDistance; glm::mat4 lightView = glm::lookAt(lightPosition, // Position of the light in world space @@ -700,9 +694,9 @@ void VulkanRenderer::createSceneViewImages() size_t framesInFlight = m_frameManager->getFramesInFlightCount(); // Destroy old resources if recreating - for (size_t i = 0; i < m_sceneViewViews.size(); ++i) + for (const auto m_sceneViewView : m_sceneViewViews) { - m_context->device().destroyImageView(m_sceneViewViews[i]); + m_context->device().destroyImageView(m_sceneViewView); } m_sceneViewImages.clear(); m_sceneViewViews.clear(); @@ -749,7 +743,7 @@ void VulkanRenderer::createDescriptorSets() m_sceneViewImageDescriptorSets.resize(framesInFlight); for (size_t i = 0; i < framesInFlight; ++i) { - m_sceneViewImageDescriptorSets[i] = m_imgui->createDescriptorSet(m_sceneViewViews[i], m_sampler->get()); + m_sceneViewImageDescriptorSets[i] = Imgui::createDescriptorSet(m_sceneViewViews[i], m_sampler->get()); } } void VulkanRenderer::createDepthImages() @@ -759,9 +753,9 @@ void VulkanRenderer::createDepthImages() size_t framesInFlight = m_frameManager->getFramesInFlightCount(); // Destroy old resources if recreating - for (size_t i = 0; i < m_depthViews.size(); ++i) + for (auto m_depthView : m_depthViews) { - m_context->device().destroyImageView(m_depthViews[i]); + m_context->device().destroyImageView(m_depthView); } m_depthImages.clear(); m_depthViews.clear(); @@ -814,16 +808,16 @@ void VulkanRenderer::createDepthPipelineAndDescriptorSets() .setDescriptorSetLayouts(setLayouts) .setMultisample(4) .setFrontFace(vk::FrontFace::eClockwise) // Match main geometry pipeline - .addPushContantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)) + .addPushConstantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)) .build(); } void VulkanRenderer::initScene() { - auto planeVerts = generatePlaneVertices(10, 50.0f); - auto planeInds = generatePlaneIndices(10); - auto planeMesh = std::make_shared(*m_allocator, planeVerts, planeInds); + auto planeVertices = generatePlaneVertices(10, 50.0f); + auto planeIndices = generatePlaneIndices(10); + const auto planeMesh = std::make_shared(*m_allocator, planeVertices, planeIndices); m_objects.push_back({planeMesh, glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -0.0f, 0.0f))}); auto meshDataVec = loadModelFromBinary("../resources/models/thingy.mesh"); diff --git a/src/vulkan/VulkanRenderer.hpp b/src/vulkan/VulkanRenderer.hpp index a3e7953..5db34f1 100644 --- a/src/vulkan/VulkanRenderer.hpp +++ b/src/vulkan/VulkanRenderer.hpp @@ -49,7 +49,7 @@ class VulkanRenderer void drawFrame(); vk::Device device() const; - Allocator& allocator(); + Allocator& allocator() const; vk::DescriptorPool descriptorPool() const; private: @@ -98,7 +98,7 @@ class VulkanRenderer void createCoreVulkanObjects(); void createSwapchainAndFrameManager(); void createPipelineAndDescriptors(); - void setupUI(std::shared_ptr consoleSink); + void setupUI(const std::shared_ptr& consoleSink); void createMSAAImage(); void createResolveImages(); void createSceneViewImages(); @@ -109,21 +109,21 @@ class VulkanRenderer void initScene(); void createDescriptorPool(); - void handleSwapchainResizing(); - void beginCommandBuffer(vk::CommandBuffer cmd); + void handleSwapchainResizing() const; + static void beginCommandBuffer(vk::CommandBuffer cmd); void beginDynamicRendering(vk::CommandBuffer cmd, vk::ImageView colorImageView, vk::ImageView resolveImageView, vk::ImageView depthImageView, vk::Extent2D extent, bool clearColor = true, - bool clearDepth = false); - void bindDescriptorSets(vk::CommandBuffer cmd); - void drawGeometry(vk::CommandBuffer cmd); + bool clearDepth = false) const; + void bindDescriptorSets(vk::CommandBuffer cmd) const; + void drawGeometry(vk::CommandBuffer cmd) const; void renderUI(vk::CommandBuffer cmd) const; static void endDynamicRendering(vk::CommandBuffer cmd); static void endCommandBuffer(vk::CommandBuffer cmd); - void submitAndPresent(uint32_t imageIndex); + void submitAndPresent(uint32_t imageIndex) const; }; } // namespace reactor From 60cbcd4414e0ae1294e2155f30a30d08e909983e Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Sat, 16 Aug 2025 09:08:23 -0500 Subject: [PATCH 15/24] clang tidy stuff --- src/vulkan/ShadowMapping.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vulkan/ShadowMapping.cpp b/src/vulkan/ShadowMapping.cpp index c1ba8ef..7253d41 100644 --- a/src/vulkan/ShadowMapping.cpp +++ b/src/vulkan/ShadowMapping.cpp @@ -73,7 +73,6 @@ void ShadowMapping::createResources() m_shadowMapSampler = device.createSampler(samplerInfo); - // TODO: check swapchain for the frame count constexpr size_t frameCount = 2; m_mvpBuffer.clear(); m_mvpBuffer.reserve(frameCount); From 288557ebdfad88a16c7c29eb6524a22a287f38e8 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Mon, 18 Aug 2025 08:56:17 -0500 Subject: [PATCH 16/24] some google format stuff --- .clang-format | 61 ++++++++++++++++ CMakeLists.txt | 8 +-- src/vulkan/Allocator.cpp | 8 +-- src/vulkan/Buffer.cpp | 57 --------------- src/vulkan/Buffer.hpp | 38 ---------- src/vulkan/DescriptorSet.cpp | 4 +- src/vulkan/FrameManager.hpp | 2 +- src/vulkan/Mesh.cpp | 12 ++-- src/vulkan/Mesh.hpp | 6 +- src/vulkan/Pipeline.cpp | 4 +- src/vulkan/ShadowMapping.cpp | 6 +- src/vulkan/ShadowMapping.hpp | 2 +- src/vulkan/UniformManager.hpp | 8 +-- src/vulkan/VulkanContext.cpp | 6 +- src/vulkan/VulkanContext.hpp | 2 +- src/vulkan/buffer.cc | 53 ++++++++++++++ src/vulkan/buffer.h | 49 +++++++++++++ .../{ShaderModule.cpp => shader_module.cc} | 48 ++++++------- .../{ShaderModule.hpp => shader_module.h} | 70 ++++++++++--------- 19 files changed, 258 insertions(+), 186 deletions(-) create mode 100644 .clang-format delete mode 100644 src/vulkan/Buffer.cpp delete mode 100644 src/vulkan/Buffer.hpp create mode 100644 src/vulkan/buffer.cc create mode 100644 src/vulkan/buffer.h rename src/vulkan/{ShaderModule.cpp => shader_module.cc} (89%) rename src/vulkan/{ShaderModule.hpp => shader_module.h} (90%) diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a42cafc --- /dev/null +++ b/.clang-format @@ -0,0 +1,61 @@ +# Start from Google C++ Style +BasedOnStyle: Google + +# General formatting +IndentWidth: 2 +TabWidth: 2 +UseTab: Never +ColumnLimit: 80 + +# Braces and indentation +BreakBeforeBraces: Attach +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false + +# Spaces +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: true + +# Pointer and reference alignment +DerivePointerAlignment: false +PointerAlignment: Left +ReferenceAlignment: Left + +# Includes +IncludeBlocks: Regroup +SortIncludes: true +IncludeCategories: + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*>' + Priority: 2 + - Regex: '.*' + Priority: 3 + +# Namespace formatting +NamespaceIndentation: None +IndentNamespace: None + +# Comments +CommentPragmas: '^ IWYU pragma:' + +# C++ specifics +Standard: Cpp17 +AccessModifierOffset: -1 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 2 + +# Alignments +AlignAfterOpenBracket: Align +AlignOperands: true +AlignTrailingComments: true + +# Others +BinPackArguments: true +BinPackParameters: true +Cpp11BracedListStyle: true +ReflowComments: true \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index add66ea..05ed6fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,8 +33,8 @@ add_library(ReactorLib STATIC src/vulkan/Allocator.cpp src/vulkan/Allocator.hpp src/vulkan/vk_mem_alloc.cpp - src/vulkan/Buffer.cpp - src/vulkan/Buffer.hpp + src/vulkan/buffer.cc + src/vulkan/buffer.h src/imgui/Imgui.cpp src/imgui/Imgui.hpp src/vulkan/VulkanUtils.hpp @@ -47,8 +47,8 @@ add_library(ReactorLib STATIC src/core/Uniforms.hpp src/vulkan/UniformManager.cpp src/vulkan/UniformManager.hpp - src/vulkan/ShaderModule.cpp - src/vulkan/ShaderModule.hpp + src/vulkan/shader_module.cc + src/vulkan/shader_module.h src/core/EventManager.hpp src/core/EventManager.cpp src/core/Camera.cpp diff --git a/src/vulkan/Allocator.cpp b/src/vulkan/Allocator.cpp index f8c7291..804d590 100644 --- a/src/vulkan/Allocator.cpp +++ b/src/vulkan/Allocator.cpp @@ -6,7 +6,7 @@ #include -#include "Buffer.hpp" +#include "buffer.h" namespace reactor { @@ -48,9 +48,9 @@ std::unique_ptr Allocator::createBufferWithData(const void* data, vk::De // Map and copy data to staging buffer void* mappedData; - vmaMapMemory(m_allocator, stagingBuffer.allocation(), &mappedData); + vmaMapMemory(m_allocator, stagingBuffer.GetAllocation(), &mappedData); memcpy(mappedData, data, size); - vmaUnmapMemory(m_allocator, stagingBuffer.allocation()); + vmaUnmapMemory(m_allocator, stagingBuffer.GetAllocation()); // Create GPU-local destination buffer // Add the transfer destination usage flag @@ -65,7 +65,7 @@ std::unique_ptr Allocator::createBufferWithData(const void* data, vk::De // Perform the copy immediateSubmit([&](vk::CommandBuffer cmd) { vk::BufferCopy copyRegion(0, 0, size); - cmd.copyBuffer(stagingBuffer.getHandle(), destBuffer->getHandle(), 1, ©Region); + cmd.copyBuffer(stagingBuffer.GetHandle(), destBuffer->GetHandle(), 1, ©Region); }); return destBuffer; diff --git a/src/vulkan/Buffer.cpp b/src/vulkan/Buffer.cpp deleted file mode 100644 index 9004217..0000000 --- a/src/vulkan/Buffer.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// -// Created by rfdic on 6/14/2025. -// - -#include "Buffer.hpp" - -#include - -namespace reactor { - - Buffer::Buffer(Allocator &allocator, vk::DeviceSize size, vk::BufferUsageFlags usage, VmaMemoryUsage memoryUsage, std::string name) - : m_allocator(allocator), m_size(size), m_name(name) - { - vk::BufferCreateInfo bufferInfo = {}; - bufferInfo.size = size; - bufferInfo.usage = usage; - bufferInfo.sharingMode = vk::SharingMode::eExclusive; - - VmaAllocationCreateInfo allocInfo = {}; - allocInfo.usage = memoryUsage; - - const VkResult result = vmaCreateBuffer( - m_allocator.getAllocator(), - reinterpret_cast(&bufferInfo), - &allocInfo, - reinterpret_cast(&m_buffer), - &m_allocation, - nullptr); - - if (result != VK_SUCCESS) { - throw std::runtime_error("failed to create buffer!"); - } - } - - Buffer::~Buffer() { - if (m_buffer && m_allocation) { - vmaDestroyBuffer(m_allocator.getAllocator(), m_buffer, m_allocation); - - spdlog::info("Buffer {} destroyed", m_name.c_str()); - - m_buffer = nullptr; - m_allocation = nullptr; - } - } - -void* Buffer::map() { - void* data; - vmaMapMemory(m_allocator.getAllocator(), m_allocation, &data); - return data; -} - -void Buffer::unmap() { - vmaUnmapMemory(m_allocator.getAllocator(), m_allocation); -} - - -} // reactor \ No newline at end of file diff --git a/src/vulkan/Buffer.hpp b/src/vulkan/Buffer.hpp deleted file mode 100644 index db56c67..0000000 --- a/src/vulkan/Buffer.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "Allocator.hpp" -#include -#include - -namespace reactor { - -class Buffer { -public: - Buffer(Allocator& allocator, vk::DeviceSize size, vk::BufferUsageFlags usage, VmaMemoryUsage memoryUsage, std::string name=""); - - ~Buffer(); - - [[nodiscard]] const std::string& getName() const { return m_name; } - - // Non-copyable - Buffer(const Buffer&) = delete; - Buffer& operator=(const Buffer&) = delete; - - [[nodiscard]] vk::Buffer getHandle() const { return m_buffer; } - [[nodiscard]] VmaAllocation allocation() const { return m_allocation; } - [[nodiscard]] vk::DeviceSize size() const { return m_size; } - - void* map(); - void unmap(); - -private: - Allocator& m_allocator; - vk::Buffer m_buffer = VK_NULL_HANDLE; - VmaAllocation m_allocation = VK_NULL_HANDLE; - vk::DeviceSize m_size = 0; - - std::string m_name; -}; - -} // reactor - diff --git a/src/vulkan/DescriptorSet.cpp b/src/vulkan/DescriptorSet.cpp index cf3e5bd..e454639 100644 --- a/src/vulkan/DescriptorSet.cpp +++ b/src/vulkan/DescriptorSet.cpp @@ -4,7 +4,7 @@ #include "DescriptorSet.hpp" -#include "Buffer.hpp" +#include "buffer.h" namespace reactor { @@ -33,7 +33,7 @@ namespace reactor { void DescriptorSet::updateUniformBuffer(size_t frame, const Buffer &buffer) { vk::DescriptorBufferInfo bufferInfo; - bufferInfo.buffer = buffer.getHandle(); + bufferInfo.buffer = buffer.GetHandle(); bufferInfo.offset = 0; bufferInfo.range = buffer.size(); diff --git a/src/vulkan/FrameManager.hpp b/src/vulkan/FrameManager.hpp index 274a3dd..1087efe 100644 --- a/src/vulkan/FrameManager.hpp +++ b/src/vulkan/FrameManager.hpp @@ -7,7 +7,7 @@ #include -#include "Buffer.hpp" +#include "buffer.h" namespace reactor { // Using the Frame struct from our previous discussion diff --git a/src/vulkan/Mesh.cpp b/src/vulkan/Mesh.cpp index 7288358..d568f6a 100644 --- a/src/vulkan/Mesh.cpp +++ b/src/vulkan/Mesh.cpp @@ -49,14 +49,14 @@ Mesh::Mesh(Allocator& allocator, const std::vector& vertices, const std: // Staging buffers (CPU-visible) Buffer stagingVertex(allocator, vertexSize, vk::BufferUsageFlagBits::eTransferSrc, VMA_MEMORY_USAGE_CPU_TO_GPU, "Staging Vertex"); void* data; - vmaMapMemory(allocator.getAllocator(), stagingVertex.allocation(), &data); + vmaMapMemory(allocator.getAllocator(), stagingVertex.GetAllocation(), &data); memcpy(data, vertices.data(), static_cast(vertexSize)); - vmaUnmapMemory(allocator.getAllocator(), stagingVertex.allocation()); + vmaUnmapMemory(allocator.getAllocator(), stagingVertex.GetAllocation()); Buffer stagingIndex(allocator, indexSize, vk::BufferUsageFlagBits::eTransferSrc, VMA_MEMORY_USAGE_CPU_TO_GPU, "Staging Index"); - vmaMapMemory(allocator.getAllocator(), stagingIndex.allocation(), &data); + vmaMapMemory(allocator.getAllocator(), stagingIndex.GetAllocation(), &data); memcpy(data, indices.data(), static_cast(indexSize)); - vmaUnmapMemory(allocator.getAllocator(), stagingIndex.allocation()); + vmaUnmapMemory(allocator.getAllocator(), stagingIndex.GetAllocation()); // GPU buffers (device-local) m_vertexBuffer = std::make_unique(allocator, vertexSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, VMA_MEMORY_USAGE_GPU_ONLY, "Vertex Buffer"); @@ -69,13 +69,13 @@ Mesh::Mesh(Allocator& allocator, const std::vector& vertices, const std: vertexCopyRegion.srcOffset = 0; vertexCopyRegion.dstOffset = 0; vertexCopyRegion.size = vertexSize; - cmd.copyBuffer(stagingVertex.getHandle(), m_vertexBuffer->getHandle(), 1, &vertexCopyRegion); + cmd.copyBuffer(stagingVertex.GetHandle(), m_vertexBuffer->GetHandle(), 1, &vertexCopyRegion); vk::BufferCopy indexCopyRegion{}; indexCopyRegion.srcOffset = 0; indexCopyRegion.dstOffset = 0; indexCopyRegion.size = indexSize; - cmd.copyBuffer(stagingIndex.getHandle(), m_indexBuffer->getHandle(), 1, &indexCopyRegion); + cmd.copyBuffer(stagingIndex.GetHandle(), m_indexBuffer->GetHandle(), 1, &indexCopyRegion); }); diff --git a/src/vulkan/Mesh.hpp b/src/vulkan/Mesh.hpp index 869f38a..c481094 100644 --- a/src/vulkan/Mesh.hpp +++ b/src/vulkan/Mesh.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Buffer.hpp" +#include "buffer.h" #include "Vertex.hpp" #include @@ -24,11 +24,11 @@ class Mesh vk::Buffer getVertexBuffer() const { - return m_vertexBuffer->getHandle(); + return m_vertexBuffer->GetHandle(); } vk::Buffer getIndexBuffer() const { - return m_indexBuffer->getHandle(); + return m_indexBuffer->GetHandle(); } uint32_t getIndexCount() const { diff --git a/src/vulkan/Pipeline.cpp b/src/vulkan/Pipeline.cpp index c968f4b..d6755ca 100644 --- a/src/vulkan/Pipeline.cpp +++ b/src/vulkan/Pipeline.cpp @@ -2,7 +2,7 @@ #include #include -#include "ShaderModule.hpp" +#include "shader_module.h" #include "VulkanUtils.hpp" namespace reactor @@ -13,7 +13,7 @@ namespace reactor std::ifstream file(filename, std::ios::ate | std::ios::binary); if (!file.is_open()) throw std::runtime_error("Failed to open shader file: " + filename); - size_t fileSize = (size_t)file.tellg(); + size_t fileSize = static_cast(file.tellg()); std::vector buffer(fileSize); file.seekg(0); file.read(buffer.data(), fileSize); diff --git a/src/vulkan/ShadowMapping.cpp b/src/vulkan/ShadowMapping.cpp index 7253d41..04217a4 100644 --- a/src/vulkan/ShadowMapping.cpp +++ b/src/vulkan/ShadowMapping.cpp @@ -133,7 +133,7 @@ void ShadowMapping::createDescriptors() for (size_t i = 0; i < framesInFlight; ++i) { vk::DescriptorBufferInfo uboInfo{}; - uboInfo.buffer = m_mvpBuffer[i]->getHandle(); + uboInfo.buffer = m_mvpBuffer[i]->GetHandle(); uboInfo.offset = 0; uboInfo.range = sizeof(SceneUBO); @@ -219,9 +219,9 @@ void ShadowMapping::setLightMatrix(const glm::mat4& lightSpaceMatrix, size_t fra ubo.lightSpaceMatrix = glm::mat4(1.0f); // map buffer, copy matrix - void* data = m_mvpBuffer[frameIndex]->map(); + void* data = m_mvpBuffer[frameIndex]->Map(); memcpy(data, &ubo, sizeof(SceneUBO)); - m_mvpBuffer[frameIndex]->unmap(); + m_mvpBuffer[frameIndex]->Unmap(); } } // namespace reactor \ No newline at end of file diff --git a/src/vulkan/ShadowMapping.hpp b/src/vulkan/ShadowMapping.hpp index 24429a5..06dcf8e 100644 --- a/src/vulkan/ShadowMapping.hpp +++ b/src/vulkan/ShadowMapping.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Buffer.hpp" +#include "buffer.h" #include "DescriptorSet.hpp" #include "Image.hpp" #include "Pipeline.hpp" diff --git a/src/vulkan/UniformManager.hpp b/src/vulkan/UniformManager.hpp index 644b24a..6266d7c 100644 --- a/src/vulkan/UniformManager.hpp +++ b/src/vulkan/UniformManager.hpp @@ -1,6 +1,6 @@ #pragma once #include "Allocator.hpp" -#include "Buffer.hpp" +#include "buffer.h" #include @@ -25,9 +25,9 @@ class UniformManager { auto& buffer = m_uniformBuffers.at(name)[frameIndex]; void* mappedData = nullptr; - vmaMapMemory(m_allocator.getAllocator(), buffer->allocation(), &mappedData); + vmaMapMemory(m_allocator.getAllocator(), buffer->GetAllocation(), &mappedData); memcpy(mappedData, &data, sizeof(T)); - vmaUnmapMemory(m_allocator.getAllocator(), buffer->allocation()); + vmaUnmapMemory(m_allocator.getAllocator(), buffer->GetAllocation()); } // Get the descriptor info need to update a descriptor set. @@ -37,7 +37,7 @@ class UniformManager { const auto& buffer = m_uniformBuffers.at(name)[frameIndex]; vk::DescriptorBufferInfo bufferInfo{}; - bufferInfo.buffer = buffer->getHandle(); + bufferInfo.buffer = buffer->GetHandle(); bufferInfo.offset = 0; bufferInfo.range = buffer->size(); return bufferInfo; diff --git a/src/vulkan/VulkanContext.cpp b/src/vulkan/VulkanContext.cpp index a8a6269..0890bc9 100644 --- a/src/vulkan/VulkanContext.cpp +++ b/src/vulkan/VulkanContext.cpp @@ -50,8 +50,8 @@ void VulkanContext::createInstance() { }; uint32_t glfwExtensionCount = 0; - const char** glfwExtensions; - glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + const char **glfwExtensions = + glfwGetRequiredInstanceExtensions(&glfwExtensionCount); std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); @@ -116,7 +116,7 @@ void VulkanContext::createSurface(GLFWwindow *window) { throw std::runtime_error("Failed to find a suitable GPU!"); } - bool VulkanContext::isDeviceSuitable(vk::PhysicalDevice device) { + bool VulkanContext::isDeviceSuitable(vk::PhysicalDevice device) const { const QueueFamilyIndices indices = findQueueFamilies(device); // Basic check for device suitability: does it have a graphics and present queue? diff --git a/src/vulkan/VulkanContext.hpp b/src/vulkan/VulkanContext.hpp index 7c1124a..d7b54f2 100644 --- a/src/vulkan/VulkanContext.hpp +++ b/src/vulkan/VulkanContext.hpp @@ -42,7 +42,7 @@ class VulkanContext { void pickPhysicalDevice(); void createLogicalDevice(); - bool isDeviceSuitable(vk::PhysicalDevice device); + [[nodiscard]] bool isDeviceSuitable(vk::PhysicalDevice device) const; [[nodiscard]] QueueFamilyIndices findQueueFamilies(vk::PhysicalDevice device) const; // Member variables - these are owned by the context diff --git a/src/vulkan/buffer.cc b/src/vulkan/buffer.cc new file mode 100644 index 0000000..f66fd02 --- /dev/null +++ b/src/vulkan/buffer.cc @@ -0,0 +1,53 @@ +#include "buffer.h" + +#include + +namespace reactor { +Buffer::Buffer(Allocator& allocator, vk::DeviceSize size, + vk::BufferUsageFlags usage, VmaMemoryUsage memoryUsage, + std::string name) + : m_allocator(allocator), + m_size(size), + m_name(name) { + vk::BufferCreateInfo bufferInfo = {}; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = vk::SharingMode::eExclusive; + + VmaAllocationCreateInfo allocInfo = {}; + allocInfo.usage = memoryUsage; + + const VkResult result = vmaCreateBuffer( + m_allocator.getAllocator(), + reinterpret_cast(&bufferInfo), + &allocInfo, + reinterpret_cast(&m_buffer), + &m_allocation, + nullptr); + + if (result != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } +} + +Buffer::~Buffer() { + if (m_buffer && m_allocation) { + vmaDestroyBuffer(m_allocator.getAllocator(), m_buffer, m_allocation); + + spdlog::info("Buffer {} destroyed", m_name.c_str()); + + m_buffer = nullptr; + m_allocation = nullptr; + } +} + +void* Buffer::Map() { + void* data; + vmaMapMemory(m_allocator.getAllocator(), m_allocation, &data); + return data; +} + +void Buffer::Unmap() { + vmaUnmapMemory(m_allocator.getAllocator(), m_allocation); +} +} // reactor \ No newline at end of file diff --git a/src/vulkan/buffer.h b/src/vulkan/buffer.h new file mode 100644 index 0000000..a513115 --- /dev/null +++ b/src/vulkan/buffer.h @@ -0,0 +1,49 @@ +#ifndef BUFFER_H +#define BUFFER_H + +#include "Allocator.hpp" +#include +#include + +namespace reactor { +class Buffer { +public: + Buffer(Allocator& allocator, vk::DeviceSize size, vk::BufferUsageFlags usage, + VmaMemoryUsage memoryUsage, std::string name = ""); + + ~Buffer(); + + [[nodiscard]] const std::string& getName() const { + return m_name; + } + + // Non-copyable + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + + [[nodiscard]] vk::Buffer GetHandle() const { + return m_buffer; + } + + [[nodiscard]] VmaAllocation GetAllocation() const { + return m_allocation; + } + + [[nodiscard]] vk::DeviceSize size() const { + return m_size; + } + + void* Map(); + void Unmap(); + +private: + Allocator& m_allocator; + vk::Buffer m_buffer = VK_NULL_HANDLE; + VmaAllocation m_allocation = VK_NULL_HANDLE; + vk::DeviceSize m_size = 0; + + std::string m_name; +}; +} // reactor + +#endif // BUFFER_H \ No newline at end of file diff --git a/src/vulkan/ShaderModule.cpp b/src/vulkan/shader_module.cc similarity index 89% rename from src/vulkan/ShaderModule.cpp rename to src/vulkan/shader_module.cc index c5227ef..699878c 100644 --- a/src/vulkan/ShaderModule.cpp +++ b/src/vulkan/shader_module.cc @@ -1,25 +1,25 @@ -// -// Created by rfdic on 7/2/2025. -// - -#include "ShaderModule.hpp" - -namespace reactor -{ - -ShaderModule::ShaderModule(vk::Device device, const std::vector& code) - : m_device(device) -{ - vk::ShaderModuleCreateInfo createInfo({}, code.size(), reinterpret_cast(code.data())); - m_handle = m_device.createShaderModule(createInfo); -} - -ShaderModule::~ShaderModule() -{ - if (m_handle) - { - m_device.destroyShaderModule(m_handle); - } -} - +// +// Created by rfdic on 7/2/2025. +// + +#include "shader_module.h" + +namespace reactor +{ + +ShaderModule::ShaderModule(vk::Device device, const std::vector& code) + : m_device(device) +{ + vk::ShaderModuleCreateInfo createInfo({}, code.size(), reinterpret_cast(code.data())); + m_handle = m_device.createShaderModule(createInfo); +} + +ShaderModule::~ShaderModule() +{ + if (m_handle) + { + m_device.destroyShaderModule(m_handle); + } +} + } // namespace reactor \ No newline at end of file diff --git a/src/vulkan/ShaderModule.hpp b/src/vulkan/shader_module.h similarity index 90% rename from src/vulkan/ShaderModule.hpp rename to src/vulkan/shader_module.h index dfc1d53..c4e5ce2 100644 --- a/src/vulkan/ShaderModule.hpp +++ b/src/vulkan/shader_module.h @@ -1,33 +1,37 @@ -#pragma once -#include - -namespace reactor -{ - -class ShaderModule -{ -public: - ShaderModule(vk::Device device, const std::vector& code); - ~ShaderModule(); - - [[nodiscard]] vk::ShaderModule getHandle() const - { - return m_handle; - }; - - // disallow copy, allow move - ShaderModule(const ShaderModule&) = delete; - ShaderModule& operator=(const ShaderModule&) = delete; - ShaderModule(ShaderModule&& other) noexcept - : m_handle(other.m_handle) - { - other.m_handle = nullptr; - } - ShaderModule& operator=(ShaderModule&&) = delete; - -private: - vk::ShaderModule m_handle; - vk::Device m_device; -}; - -} // namespace reactor +#ifndef SHADER_MODULE_H +#define SHADER_MODULE_H + +#include + +namespace reactor +{ + +class ShaderModule +{ +public: + ShaderModule(vk::Device device, const std::vector& code); + ~ShaderModule(); + + [[nodiscard]] vk::ShaderModule getHandle() const + { + return m_handle; + }; + + // disallow copy, allow move + ShaderModule(const ShaderModule&) = delete; + ShaderModule& operator=(const ShaderModule&) = delete; + ShaderModule(ShaderModule&& other) noexcept + : m_handle(other.m_handle) + { + other.m_handle = nullptr; + } + ShaderModule& operator=(ShaderModule&&) = delete; + +private: + vk::ShaderModule m_handle; + vk::Device m_device; +}; + +} // namespace reactor + +#endif // SHADER_MODULE_H \ No newline at end of file From 4b050a8e41292d945023e1649c29fd0271ae3fdc Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Mon, 18 Aug 2025 15:19:40 -0500 Subject: [PATCH 17/24] add soft shadows --- build-shaders.sh | 2 + shaders/shadows.slang | 98 +++++++++++++++++++++++++++++++++++ shaders/triangle.slang | 71 +++++-------------------- src/core/Uniforms.hpp | 40 ++++++++------ src/vulkan/ShadowMapping.cpp | 2 +- src/vulkan/VulkanRenderer.cpp | 46 ++++++++++++++-- 6 files changed, 181 insertions(+), 78 deletions(-) create mode 100644 shaders/shadows.slang diff --git a/build-shaders.sh b/build-shaders.sh index 5bb8c06..5faf8bb 100644 --- a/build-shaders.sh +++ b/build-shaders.sh @@ -1,3 +1,5 @@ +#slangc -g -O0 shaders/shadows.slang -module Shadows -o shadows.slang-module + slangc -g -O0 shaders/triangle.slang -target spirv -profile vs_6_0 -entry vertexMain -o resources/shaders/triangle-slang.vert.spv slangc -g -O0 shaders/triangle.slang -target spirv -profile ps_6_0 -entry fragmentMain -o resources/shaders/triangle-slang.frag.spv diff --git a/shaders/shadows.slang b/shaders/shadows.slang new file mode 100644 index 0000000..4d293c5 --- /dev/null +++ b/shaders/shadows.slang @@ -0,0 +1,98 @@ +module shadows; + +[[vk::binding(5, 0)]] +cbuffer ShadowUBO : register(b2) +{ + float lightRadiusUV; + int blockerSearchSamples; + int pcfSamples; + float depthBias; +} + + +public struct GeometryVaryings +{ + public float4 position : SV_Position; + public float3 worldPos : WORLD_POS; + public float3 normal : NORMAL; + public float4 lightSpacePos : LIGHT_SPACE_POS; +}; + +static const int kPoissonCount = 32; +static const float2 kPoisson[kPoissonCount] = { + float2(-0.613392, 0.617481), float2( 0.170019, -0.040254), + float2(-0.299417, 0.791925), float2( 0.645680, 0.493210), + float2(-0.651784, -0.634790), float2( 0.421003, 0.027070), + float2(-0.817194, -0.271096), float2(-0.705374, 0.170919), + float2( 0.977050, -0.108615), float2( 0.063326, 0.142369), + float2( 0.203528, 0.214331), float2(-0.667531, 0.326090), + float2(-0.098422, -0.295755), float2(-0.885922, 0.215369), + float2( 0.566637, 0.605213), float2( 0.039766, -0.396100), + float2( 0.751946, 0.453352), float2( 0.078707, -0.715323), + float2(-0.075838, -0.529344), float2( 0.724479, -0.580798), + float2( 0.222999, -0.215125), float2(-0.467574, -0.405438), + float2(-0.248268, -0.814753), float2( 0.354411, -0.887570), + float2( 0.175817, 0.382366), float2( 0.487472, -0.063082), + float2(-0.084078, 0.898312), float2( 0.488876, -0.783441), + float2( 0.470016, 0.217933), float2(-0.696890, -0.549791), + float2(-0.149693, 0.605762), float2( 0.034211, 0.979980) +}; + + +// float findAverageBlockerDepth( +// Texture2D shadowMap, +// SamplerState shadowSamplerLinear, +// float2 baseUV, +// float zReceiver) +// { +// float avgBlockerDepth = 0.0; +// float numBlockers = 0.0; +// for (int i = 0; i < blockerSearchSamples; ++i) +// { +// float2 offset = kPoisson[i] * lightRadiusUV; +// float shadowMapDepth = shadowMap.Sample(shadowSamplerLinear, baseUV + offset).r; + +// if (shadowMapDepth < zReceiver) +// { +// avgBlockerDepth += shadowMapDepth; +// numBlockers++; +// } +// } + +// return numBlockers > 0.0 ? avgBlockerDepth / numBlockers : 0.0; +// } + + +float pcfFilter( + Texture2D shadowMap, + SamplerComparisonState shadowSampler, + float2 baseUV, + float zReceiver, + float filterRadiusUV) +{ + float shadow = 0.0; + for (int i = 0; i < pcfSamples; ++i) + { + float2 offset = kPoisson[i] * filterRadiusUV; + shadow += shadowMap.SampleCmp(shadowSampler, baseUV + offset, zReceiver); + } + return shadow / float(pcfSamples); +} + + +public float calculateShadow( + GeometryVaryings input, + Texture2D shadowMap, + SamplerComparisonState shadowSampler, + SamplerState shadowSamplerLinear) +{ + float3 projCoords = input.lightSpacePos.xyz / input.lightSpacePos.w; + float2 uv = projCoords.xy * 0.5 + 0.5; + float zReceiver = projCoords.z - depthBias; + + if (uv.x <= 0.0 || uv.x >= 1.0 || uv.y <= 0.0 || uv.y >= 1.0) + return 1.0; + + return pcfFilter(shadowMap, shadowSampler, uv, zReceiver, lightRadiusUV); + +} \ No newline at end of file diff --git a/shaders/triangle.slang b/shaders/triangle.slang index f1515f0..fd14c02 100644 --- a/shaders/triangle.slang +++ b/shaders/triangle.slang @@ -1,5 +1,4 @@ -// Combined Slang Shader Module -// triangle.slang +import shadows; // ========================================================================= // 1. Data Structures & Resource Bindings @@ -7,10 +6,7 @@ /** * Defines the input attributes for a single vertex. - * These correspond to the `layout(location = ...)` inputs in the GLSL - * vertex shader. Semantics like `POSITION` and `NORMAL` are used instead - * of raw location indices. - */ +*/ struct VertexInput { float3 position : POSITION; // Corresponds to GLSL: layout(location = 0) in vec3 inPosition; @@ -20,22 +16,7 @@ struct VertexInput }; /** - * Defines the data passed from the vertex shader to the fragment shader. - * This replaces the individual `out` variables in the vertex shader and - * `in` variables in the fragment shader. `SV_Position` is a system-value - * semantic required for the final clip-space position. - */ -struct Varyings -{ - float4 position : SV_Position; // The final position for the rasterizer. - float3 worldPos : WORLD_POS; // Corresponds to GLSL: outWorldPos - float3 normal : NORMAL; // Corresponds to GLSL: outNormal - float4 lightSpacePos : LIGHT_SPACE_POS; // Corresponds to GLSL: outLightSpacePos -}; - -/** - * Per-scene constants, equivalent to the `SceneUBO` in GLSL. - * The `register(b0)` attribute maps this buffer to binding 0. + * Per-scene constants, equivalent to the `SceneUBO` in GLSL. */ [[vk::binding(0, 0)]] cbuffer SceneUBO : register(b0) @@ -46,8 +27,7 @@ cbuffer SceneUBO : register(b0) }; /** - * Per-light constants, equivalent to the `LightUBO` in GLSL. - * The `register(b1)` attribute maps this buffer to binding 1. + * Per-light constants. */ [[vk::binding(1, 0)]] cbuffer LightUBO : register(b1) @@ -58,7 +38,7 @@ cbuffer LightUBO : register(b1) }; /** - * Push constants for per-draw call data, like the model matrix. + * Push constants for the model matrix. */ [push_constant] cbuffer PushConstants @@ -67,10 +47,7 @@ cbuffer PushConstants }; /** - * In GLSL, `sampler2DShadow` is a combined type. In Slang (and HLSL), - * the texture and its sampler state are typically defined separately for - * greater flexibility. We bind them to the same slot index `2` but on - * different resource types (`t` for texture, `s` for sampler). + * Shadow map texture and samplers. */ [[vk::binding(2, 0)]] Texture2D shadowMap : register(t2); @@ -78,42 +55,18 @@ Texture2D shadowMap : register(t2); [[vk::binding(3, 0)]] SamplerComparisonState shadowSampler : register(s2); - -// ========================================================================= -// 2. Shadow Calculation -// ========================================================================= - -/** - * Calculates the shadow contribution. This logic is identical to the - * GLSL `calculateShadow` function, but it now takes the light-space - * position as a parameter instead of reading it from a global input. - */ -float calculateShadow(Varyings input) -{ - // Perform perspective divide - float3 projCoords = input.lightSpacePos.xyz / input.lightSpacePos.w; - - // Convert from [-1, 1] to [0, 1] texture coordinates - projCoords.xy = projCoords.xy * 0.5 + 0.5; - - // Sample the shadow map using a comparison sampler. - // The `SampleCmp` function performs the depth test (projCoords.z) - // against the value in the shadow map at projCoords.xy. - // This is the Slang equivalent of `texture(shadowMap, projCoords)`. - return shadowMap.SampleCmp(shadowSampler, projCoords.xy, projCoords.z); -} - +[[vk::binding(4, 0)]] +SamplerState shadowSamplerLinear : register(s3); // ========================================================================= // 3. Vertex Shader Stage // ========================================================================= [shader("vertex")] -Varyings vertexMain(VertexInput input) +GeometryVaryings vertexMain(VertexInput input) { - Varyings output; + GeometryVaryings output; - // Transform position and normal to world space float4 worldPos = mul(model, float4(input.position, 1.0)); output.worldPos = worldPos.xyz; output.normal = normalize(mul((float3x3)model, input.normal)); @@ -133,7 +86,7 @@ Varyings vertexMain(VertexInput input) // ========================================================================= [shader("fragment")] -float4 fragmentMain(Varyings input) : SV_Target +float4 fragmentMain(GeometryVaryings input) : SV_Target { float3 objectColor = float3(0.8, 0.8, 0.8); @@ -147,7 +100,7 @@ float4 fragmentMain(Varyings input) : SV_Target float3 diffuse = objectColor * lightColor.rgb * lightIntensity * diffuseFactor; // Get the shadow factor (1.0 for lit, < 1.0 for shadowed) - float shadow = calculateShadow(input); + float shadow = calculateShadow(input, shadowMap, shadowSampler, shadowSamplerLinear); // Combine components: ambient light is always present, but diffuse // light is affected by the shadow factor. diff --git a/src/core/Uniforms.hpp b/src/core/Uniforms.hpp index 781beac..691e566 100644 --- a/src/core/Uniforms.hpp +++ b/src/core/Uniforms.hpp @@ -1,26 +1,36 @@ #pragma once #include +namespace reactor { struct SceneUBO { - glm::mat4 view; - glm::mat4 projection; - glm::mat4 lightSpaceMatrix; + glm::mat4 view; + glm::mat4 projection; + glm::mat4 lightSpaceMatrix; }; struct DirectionalLightUBO { - glm::vec4 lightDirection = glm::vec4(-0.5f, 1.0f, -0.5f, 0.0f); - glm::vec4 lightColor = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); - float lightIntensity = 1.0f; - // add 12-bytes of padding - float pad[3]; + glm::vec4 lightDirection = glm::vec4(-0.5f, 1.0f, -0.5f, 0.0f); + glm::vec4 lightColor = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); + float lightIntensity = 1.0f; + // add 12-bytes of padding + float pad[3]; }; struct CompositeUBO { - float uExposure = 1.0f; - float uContrast = 1.0f; - float uSaturation = 1.0f; - float uVignetteIntensity = 0.5f; - float uVignetteFalloff = 0.5f; - float uFogDensity = 0.001f; -}; \ No newline at end of file + float uExposure = 1.0f; + float uContrast = 1.0f; + float uSaturation = 1.0f; + float uVignetteIntensity = 0.5f; + float uVignetteFalloff = 0.5f; + float uFogDensity = 0.001f; +}; + +struct ShadowUBO +{ + float lightRadiusUV; + int blockerSearchSamples; + int pcfSamples; + float depthBias; +}; +} \ No newline at end of file diff --git a/src/vulkan/ShadowMapping.cpp b/src/vulkan/ShadowMapping.cpp index 04217a4..d828bd8 100644 --- a/src/vulkan/ShadowMapping.cpp +++ b/src/vulkan/ShadowMapping.cpp @@ -101,7 +101,7 @@ void ShadowMapping::createPipeline() Pipeline::Builder builder(device); builder - .setVertexShader("../resources/shaders/triangle.vert.spv") + .setVertexShader("../resources/shaders/triangle-slang.vert.spv") // No fragment shader, we only want depth output .setVertexInputFromVertex() .setDepthAttachment(vk::Format::eD32Sfloat, true) // depth test and write enabled diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index ecee3b5..860443c 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -35,6 +35,7 @@ VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Cam m_uniformManager->registerUBO("scene"); m_uniformManager->registerUBO("composite"); m_uniformManager->registerUBO("lighting"); + m_uniformManager->registerUBO("shadow"); createDescriptorPool(); createPipelineAndDescriptors(); @@ -117,8 +118,15 @@ void VulkanRenderer::createPipelineAndDescriptors() // Binding 2: Shadow Map Texture (Fragment Shader) vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment), - // Binding 3: Shadow Map Sampler + // Binding 3: Shadow Map Sampler (Comparison) vk::DescriptorSetLayoutBinding(3, vk::DescriptorType::eSampler, 1, vk::ShaderStageFlagBits::eFragment), + + // Binding 4: Shadow Map Sampler (Linear) + vk::DescriptorSetLayoutBinding(4, vk::DescriptorType::eSampler, 1, vk::ShaderStageFlagBits::eFragment), + + // Binding 5: Shadow UBO (Fragment Shader) + vk::DescriptorSetLayoutBinding(5, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment), + }; m_descriptorSet = std::make_unique(m_context->device(), m_descriptorPool, 2, bindings); const std::vector setLayouts = {m_descriptorSet->getLayout()}; @@ -390,18 +398,29 @@ void VulkanRenderer::drawFrame() m_uniformManager->update(frameIdx, m_light); m_uniformManager->update(frameIdx, ubo); + // Populate the ShadowUBO with data + ShadowUBO shadowUboData{}; + shadowUboData.lightRadiusUV = 0.005f; + shadowUboData.blockerSearchSamples = 16; + shadowUboData.pcfSamples = 16; + shadowUboData.depthBias = 0.005f; + m_uniformManager->update(frameIdx, shadowUboData); + // Get descriptor info for both UBOs vk::DescriptorBufferInfo sceneBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); vk::DescriptorBufferInfo lightBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); + vk::DescriptorBufferInfo shadowBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); vk::DescriptorImageInfo shadowMapTextureInfo = {}; - // shadowMapImageInfo.sampler = m_shadowMapping->shadowMapSampler(); shadowMapTextureInfo.imageView = m_shadowMapping->shadowMapView(); shadowMapTextureInfo.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal; vk::DescriptorImageInfo shadowMapSamplerInfo = {}; shadowMapSamplerInfo.sampler = m_shadowMapping->shadowMapSampler(); + vk::DescriptorImageInfo shadowMapLinearSamplerInfo = {}; + shadowMapLinearSamplerInfo.sampler = m_sampler->get(); + // Create write for Scene UBO at binding 0 vk::WriteDescriptorSet sceneWrite{}; sceneWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); @@ -432,7 +451,28 @@ void VulkanRenderer::drawFrame() shadowMapSamplerWrite.descriptorCount = 1; shadowMapSamplerWrite.pImageInfo = &shadowMapSamplerInfo; - m_descriptorSet->updateSet({sceneWrite, lightWrite, shadowMapTextureWrite, shadowMapSamplerWrite}); + vk::WriteDescriptorSet shadowMapLinearSamplerWrite{}; + shadowMapLinearSamplerWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); + shadowMapLinearSamplerWrite.dstBinding = 4; // Target binding 4 + shadowMapLinearSamplerWrite.descriptorType = vk::DescriptorType::eSampler; + shadowMapLinearSamplerWrite.descriptorCount = 1; + shadowMapLinearSamplerWrite.pImageInfo = &shadowMapLinearSamplerInfo; + + vk::WriteDescriptorSet shadowUboWrite{}; + shadowUboWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); + shadowUboWrite.dstBinding = 5; // Target binding 5 + shadowUboWrite.descriptorType = vk::DescriptorType::eUniformBuffer; + shadowUboWrite.descriptorCount = 1; + shadowUboWrite.pBufferInfo = &shadowBufferInfo; + + m_descriptorSet->updateSet({ + sceneWrite, + lightWrite, + shadowMapTextureWrite, + shadowMapSamplerWrite, + shadowMapLinearSamplerWrite, + shadowUboWrite + }); beginCommandBuffer(cmd); From 3132681458b34da2df05d095fd5c09c46b829262 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Mon, 18 Aug 2025 15:25:07 -0500 Subject: [PATCH 18/24] clean up the shader --- shaders/triangle.slang | 82 ++++++++++++++---------------------------- 1 file changed, 27 insertions(+), 55 deletions(-) diff --git a/shaders/triangle.slang b/shaders/triangle.slang index fd14c02..9cc9c79 100644 --- a/shaders/triangle.slang +++ b/shaders/triangle.slang @@ -1,62 +1,52 @@ import shadows; -// ========================================================================= -// 1. Data Structures & Resource Bindings -// ========================================================================= +// +// Resource Bindings & Data Structures +// -/** - * Defines the input attributes for a single vertex. -*/ struct VertexInput { - float3 position : POSITION; // Corresponds to GLSL: layout(location = 0) in vec3 inPosition; - float3 normal : NORMAL; // Corresponds to GLSL: layout(location = 1) in vec3 inNormal; - float3 color : COLOR; // Corresponds to GLSL: layout(location = 2) in vec3 inColor; - float2 texCoord : TEXCOORD0; // Corresponds to GLSL: layout(location = 3) in vec2 inTexCoord; + float3 position : POSITION; + float3 normal : NORMAL; + float3 color : COLOR; + float2 texCoord : TEXCOORD0; }; -/** - * Per-scene constants, equivalent to the `SceneUBO` in GLSL. - */ -[[vk::binding(0, 0)]] -cbuffer SceneUBO : register(b0) +[[vk::binding(0, 0)]] cbuffer SceneUBO : register(b0) { matrix view; matrix projection; matrix lightSpaceMatrix; }; -/** - * Per-light constants. - */ -[[vk::binding(1, 0)]] -cbuffer LightUBO : register(b1) +[[vk::binding(1, 0)]] cbuffer LightUBO : register(b1) { float4 lightDirection; float4 lightColor; float lightIntensity; }; -/** - * Push constants for the model matrix. - */ -[push_constant] -cbuffer PushConstants +[push_constant] cbuffer PushConstants { matrix model; }; -/** - * Shadow map texture and samplers. - */ -[[vk::binding(2, 0)]] -Texture2D shadowMap : register(t2); +[[vk::binding(2, 0)]] Texture2D shadowMap : register(t2); +[[vk::binding(3, 0)]] SamplerComparisonState shadowSampler : register(s2); +[[vk::binding(4, 0)]] SamplerState shadowSamplerLinear : register(s3); -[[vk::binding(3, 0)]] -SamplerComparisonState shadowSampler : register(s2); +// Helper functions +float3 computeDiffuse(float3 normal, float3 objectColor) +{ + float3 lightDir = normalize(-lightDirection.xyz); + float diffuseFactor = max(dot(normal, lightDir), 0.0); + return objectColor * lightColor.rgb * lightIntensity * diffuseFactor; +} -[[vk::binding(4, 0)]] -SamplerState shadowSamplerLinear : register(s3); +float3 computeAmbient(float3 objectColor) +{ + return objectColor * 0.1; +} // ========================================================================= // 3. Vertex Shader Stage @@ -66,44 +56,26 @@ SamplerState shadowSamplerLinear : register(s3); GeometryVaryings vertexMain(VertexInput input) { GeometryVaryings output; - float4 worldPos = mul(model, float4(input.position, 1.0)); output.worldPos = worldPos.xyz; output.normal = normalize(mul((float3x3)model, input.normal)); - - // Calculate the final clip-space position output.position = mul(projection, mul(view, worldPos)); - - // Transform world position to light's view space for shadowing output.lightSpacePos = mul(lightSpaceMatrix, worldPos); - return output; } - -// ========================================================================= -// 4. Fragment Shader Stage -// ========================================================================= +// Fragment Shader [shader("fragment")] float4 fragmentMain(GeometryVaryings input) : SV_Target { float3 objectColor = float3(0.8, 0.8, 0.8); - // Ambient light component - float3 ambient = objectColor * 0.1; - - // Calculate the diffuse (directional) light component - float3 normal = normalize(input.normal); - float3 lightDir = normalize(-lightDirection.xyz); - float diffuseFactor = max(dot(normal, lightDir), 0.0); - float3 diffuse = objectColor * lightColor.rgb * lightIntensity * diffuseFactor; + float3 ambient = computeAmbient(objectColor); + float3 diffuse = computeDiffuse(input.normal, objectColor); - // Get the shadow factor (1.0 for lit, < 1.0 for shadowed) float shadow = calculateShadow(input, shadowMap, shadowSampler, shadowSamplerLinear); - // Combine components: ambient light is always present, but diffuse - // light is affected by the shadow factor. float3 finalColor = ambient + (diffuse * shadow); return float4(finalColor, 1.0); From 26369f3c29c6f24adbbd0cfe02ecba8048804fb9 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Mon, 18 Aug 2025 16:09:44 -0500 Subject: [PATCH 19/24] tweak shadow bias to improve shadow --- shaders/triangle.slang | 24 ++++++++++++++++++++++-- src/vulkan/VulkanRenderer.cpp | 2 +- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/shaders/triangle.slang b/shaders/triangle.slang index 9cc9c79..38e891e 100644 --- a/shaders/triangle.slang +++ b/shaders/triangle.slang @@ -35,6 +35,22 @@ struct VertexInput [[vk::binding(3, 0)]] SamplerComparisonState shadowSampler : register(s2); [[vk::binding(4, 0)]] SamplerState shadowSamplerLinear : register(s3); +public interface IBRDF +{ + float3 evaluate(float3 normal, float3 lightDir, float3 viewDir, float3 color); +} + +public struct LambertBRDF : IBRDF +{ + float3 evaluate(float3 normal, float3 lightDir, float3 viewDir, float3 color) + { + float NdotL = max(dot(normal, lightDir), 0.0); + return color * NdotL; + } +}; + +float3 getLightDir() { return normalize(-lightDirection.xyz); } + // Helper functions float3 computeDiffuse(float3 normal, float3 objectColor) { @@ -70,9 +86,13 @@ GeometryVaryings vertexMain(VertexInput input) float4 fragmentMain(GeometryVaryings input) : SV_Target { float3 objectColor = float3(0.8, 0.8, 0.8); - float3 ambient = computeAmbient(objectColor); - float3 diffuse = computeDiffuse(input.normal, objectColor); + + LambertBRDF brdf; + float3 lightDir = getLightDir(); + float3 viewDir = float3(0,0,1); + + float3 diffuse = brdf.evaluate(input.normal, lightDir, viewDir, objectColor) * lightColor.rgb * lightIntensity; float shadow = calculateShadow(input, shadowMap, shadowSampler, shadowSamplerLinear); diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 860443c..3e7a05b 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -403,7 +403,7 @@ void VulkanRenderer::drawFrame() shadowUboData.lightRadiusUV = 0.005f; shadowUboData.blockerSearchSamples = 16; shadowUboData.pcfSamples = 16; - shadowUboData.depthBias = 0.005f; + shadowUboData.depthBias = 0.0008f; m_uniformManager->update(frameIdx, shadowUboData); // Get descriptor info for both UBOs From 4dea4a8fd48c06e70025d7ae9804b8d45b61bc8a Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Tue, 19 Aug 2025 13:00:25 -0500 Subject: [PATCH 20/24] remove unused register in HLSL --- shaders/triangle.slang | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/shaders/triangle.slang b/shaders/triangle.slang index 38e891e..35db6ae 100644 --- a/shaders/triangle.slang +++ b/shaders/triangle.slang @@ -12,14 +12,14 @@ struct VertexInput float2 texCoord : TEXCOORD0; }; -[[vk::binding(0, 0)]] cbuffer SceneUBO : register(b0) +[[vk::binding(0, 0)]] cbuffer SceneUBO { - matrix view; - matrix projection; - matrix lightSpaceMatrix; + float4x4 view; + float4x4 projection; + float4x4 lightSpaceMatrix; }; -[[vk::binding(1, 0)]] cbuffer LightUBO : register(b1) +[[vk::binding(1, 0)]] cbuffer LightUBO { float4 lightDirection; float4 lightColor; @@ -28,12 +28,12 @@ struct VertexInput [push_constant] cbuffer PushConstants { - matrix model; + float4x4 model; }; -[[vk::binding(2, 0)]] Texture2D shadowMap : register(t2); -[[vk::binding(3, 0)]] SamplerComparisonState shadowSampler : register(s2); -[[vk::binding(4, 0)]] SamplerState shadowSamplerLinear : register(s3); +[[vk::binding(2, 0)]] Texture2D shadowMap; +[[vk::binding(3, 0)]] SamplerComparisonState shadowSampler; +[[vk::binding(4, 0)]] SamplerState shadowSamplerLinear; public interface IBRDF { From 0117d926b80cc357b71ff8517a77fb20127abcd5 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Tue, 19 Aug 2025 15:03:56 -0500 Subject: [PATCH 21/24] objects have colors now --- shaders/triangle.slang | 3 ++- src/core/Uniforms.hpp | 13 +++++++++---- src/vulkan/ShadowMapping.cpp | 2 +- src/vulkan/VulkanRenderer.cpp | 18 +++++++++++++----- src/vulkan/VulkanRenderer.hpp | 1 + 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/shaders/triangle.slang b/shaders/triangle.slang index 35db6ae..a3bf780 100644 --- a/shaders/triangle.slang +++ b/shaders/triangle.slang @@ -29,6 +29,7 @@ struct VertexInput [push_constant] cbuffer PushConstants { float4x4 model; + float4 objectColor; }; [[vk::binding(2, 0)]] Texture2D shadowMap; @@ -85,7 +86,7 @@ GeometryVaryings vertexMain(VertexInput input) [shader("fragment")] float4 fragmentMain(GeometryVaryings input) : SV_Target { - float3 objectColor = float3(0.8, 0.8, 0.8); + float3 objectColor = objectColor.rgb; float3 ambient = computeAmbient(objectColor); LambertBRDF brdf; diff --git a/src/core/Uniforms.hpp b/src/core/Uniforms.hpp index 691e566..7d38353 100644 --- a/src/core/Uniforms.hpp +++ b/src/core/Uniforms.hpp @@ -2,13 +2,13 @@ #include namespace reactor { -struct SceneUBO { +struct alignas(16) SceneUBO { glm::mat4 view; glm::mat4 projection; glm::mat4 lightSpaceMatrix; }; -struct DirectionalLightUBO +struct alignas(16) DirectionalLightUBO { glm::vec4 lightDirection = glm::vec4(-0.5f, 1.0f, -0.5f, 0.0f); glm::vec4 lightColor = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); @@ -17,7 +17,7 @@ struct DirectionalLightUBO float pad[3]; }; -struct CompositeUBO { +struct alignas(16) CompositeUBO { float uExposure = 1.0f; float uContrast = 1.0f; float uSaturation = 1.0f; @@ -26,11 +26,16 @@ struct CompositeUBO { float uFogDensity = 0.001f; }; -struct ShadowUBO +struct alignas(16) ShadowUBO { float lightRadiusUV; int blockerSearchSamples; int pcfSamples; float depthBias; }; + +struct alignas(16) ModelPushConstant { + glm::mat4 model; + glm::vec4 color; +}; } \ No newline at end of file diff --git a/src/vulkan/ShadowMapping.cpp b/src/vulkan/ShadowMapping.cpp index d828bd8..8f9584c 100644 --- a/src/vulkan/ShadowMapping.cpp +++ b/src/vulkan/ShadowMapping.cpp @@ -110,7 +110,7 @@ void ShadowMapping::createPipeline() .setMultisample(1) .setCullMode(vk::CullModeFlagBits::eFront) .setFrontFace(vk::FrontFace::eClockwise) // Match main geometry pipeline - .addPushConstantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)); + .addPushConstantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(ModelPushConstant)); m_depthPassPipeline = builder.build(); } diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 3e7a05b..90fd143 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -15,6 +15,8 @@ namespace reactor { + + VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Camera& camera) : m_config(config), m_window(window), m_camera(camera) { @@ -140,7 +142,7 @@ void VulkanRenderer::createPipelineAndDescriptors() .setDescriptorSetLayouts(setLayouts) .setMultisample(4) .setFrontFace(vk::FrontFace::eClockwise) // Assuming standard winding order for cubes - .addPushConstantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)) + .addPushConstantRange(vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0, sizeof(ModelPushConstant)) .build(); Debug::setObjectName(m_context->device(), @@ -227,8 +229,14 @@ void VulkanRenderer::bindDescriptorSets(vk::CommandBuffer cmd) const { void VulkanRenderer::drawGeometry(vk::CommandBuffer cmd) const { for (const auto& obj : m_objects) { + + ModelPushConstant pushConstant{}; + pushConstant.model = obj.transform; + pushConstant.color = obj.color; + + auto stages = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment; cmd.pushConstants( - m_pipeline->getLayout(), vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4), &obj.transform[0][0]); + m_pipeline->getLayout(), stages, 0, sizeof(ModelPushConstant), &pushConstant); vk::Buffer vbs[] = {obj.mesh->getVertexBuffer()}; vk::DeviceSize offsets[] = {0}; @@ -848,7 +856,7 @@ void VulkanRenderer::createDepthPipelineAndDescriptorSets() .setDescriptorSetLayouts(setLayouts) .setMultisample(4) .setFrontFace(vk::FrontFace::eClockwise) // Match main geometry pipeline - .addPushConstantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)) + .addPushConstantRange(vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0, sizeof(ModelPushConstant)) .build(); } @@ -858,7 +866,7 @@ void VulkanRenderer::initScene() auto planeVertices = generatePlaneVertices(10, 50.0f); auto planeIndices = generatePlaneIndices(10); const auto planeMesh = std::make_shared(*m_allocator, planeVertices, planeIndices); - m_objects.push_back({planeMesh, glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -0.0f, 0.0f))}); + m_objects.push_back({planeMesh, glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -0.0f, 0.0f)), glm::vec4(0.8, 0.8, 0.8, 1.0)}); auto meshDataVec = loadModelFromBinary("../resources/models/thingy.mesh"); @@ -866,7 +874,7 @@ void VulkanRenderer::initScene() { const auto& meshData = meshDataVec[0]; // Use the first mesh for monkey auto monkeyMesh = std::make_shared(*m_allocator, meshData.vertices, meshData.indices); - m_objects.push_back(RenderObject{monkeyMesh}); + m_objects.push_back(RenderObject{monkeyMesh, glm::mat4(1.0), glm::vec4(0.4, 0.6, 0.75, 1.0)}); } } diff --git a/src/vulkan/VulkanRenderer.hpp b/src/vulkan/VulkanRenderer.hpp index 5db34f1..5690dc1 100644 --- a/src/vulkan/VulkanRenderer.hpp +++ b/src/vulkan/VulkanRenderer.hpp @@ -38,6 +38,7 @@ struct RenderObject { std::shared_ptr mesh; glm::mat4 transform = glm::mat4(1.0f); + glm::vec4 color = glm::vec4(1.0f); }; class VulkanRenderer From c75f4d5cd99d8e8781127b76938fe1d1234d8836 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Tue, 19 Aug 2025 21:14:53 -0500 Subject: [PATCH 22/24] add PI to BRDF --- shaders/triangle.slang | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shaders/triangle.slang b/shaders/triangle.slang index a3bf780..ac4058a 100644 --- a/shaders/triangle.slang +++ b/shaders/triangle.slang @@ -36,6 +36,8 @@ struct VertexInput [[vk::binding(3, 0)]] SamplerComparisonState shadowSampler; [[vk::binding(4, 0)]] SamplerState shadowSamplerLinear; +static const float PI = 3.14159265; + public interface IBRDF { float3 evaluate(float3 normal, float3 lightDir, float3 viewDir, float3 color); @@ -43,10 +45,10 @@ public interface IBRDF public struct LambertBRDF : IBRDF { - float3 evaluate(float3 normal, float3 lightDir, float3 viewDir, float3 color) + public float3 evaluate(float3 normal, float3 lightDir, float3 viewDir, float3 color) { float NdotL = max(dot(normal, lightDir), 0.0); - return color * NdotL; + return color * NdotL / PI; } }; From 7dc967263a700c9bd725ed950287d64cb74d122c Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Wed, 20 Aug 2025 18:26:57 -0500 Subject: [PATCH 23/24] added forward definitions to clean up the header --- src/vulkan/VulkanRenderer.cpp | 10 ++ src/vulkan/VulkanRenderer.hpp | 221 +++++++++++++++++----------------- 2 files changed, 118 insertions(+), 113 deletions(-) diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 90fd143..41560ef 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -9,6 +9,16 @@ #include "DebugUtils.hpp" #include "ImageUtils.hpp" #include "VulkanUtils.hpp" +#include "FrameManager.hpp" +#include "UniformManager.hpp" +#include "Sampler.hpp" +#include "ShadowMapping.hpp" +#include "Swapchain.hpp" +#include "Mesh.hpp" +#include "MeshGenerators.hpp" +#include "../core/Camera.hpp" +#include "VulkanContext.hpp" +#include "../imgui/Imgui.hpp" #include #include diff --git a/src/vulkan/VulkanRenderer.hpp b/src/vulkan/VulkanRenderer.hpp index 5690dc1..1938aee 100644 --- a/src/vulkan/VulkanRenderer.hpp +++ b/src/vulkan/VulkanRenderer.hpp @@ -2,129 +2,124 @@ #include -#include "../core/Camera.hpp" #include "../core/Uniforms.hpp" -#include "../core/Window.hpp" -#include "../imgui/Imgui.hpp" -#include "Allocator.hpp" -#include "DescriptorSet.hpp" -#include "FrameManager.hpp" -#include "Image.hpp" #include "ImageStateTracker.h" -#include "Mesh.hpp" -#include "MeshGenerators.hpp" -#include "Pipeline.hpp" -#include "Sampler.hpp" -#include "ShadowMapping.hpp" -#include "Swapchain.hpp" -#include "UniformManager.hpp" -#include "VulkanContext.hpp" - -namespace reactor -{ - -struct RendererConfig -{ - uint32_t windowWidth; - uint32_t windowHeight; - std::string windowTitle; - std::string vertShaderPath; - std::string fragShaderPath; - std::string compositeVertShaderPath; - std::string compositeFragShaderPath; + +namespace reactor { +// Forward definitions +class Allocator; +class DescriptorSet; +class FrameManager; +class Sampler; +class UniformManager; +class ShadowMapping; +class Swapchain; +class Image; +class Mesh; +class Pipeline; +class Window; +class Camera; +class Imgui; +class VulkanContext; + +struct RendererConfig { + uint32_t windowWidth; + uint32_t windowHeight; + std::string windowTitle; + std::string vertShaderPath; + std::string fragShaderPath; + std::string compositeVertShaderPath; + std::string compositeFragShaderPath; }; -struct RenderObject -{ - std::shared_ptr mesh; - glm::mat4 transform = glm::mat4(1.0f); +struct RenderObject { + std::shared_ptr mesh; + glm::mat4 transform = glm::mat4(1.0f); glm::vec4 color = glm::vec4(1.0f); }; -class VulkanRenderer -{ +class VulkanRenderer { public: - VulkanRenderer(const RendererConfig& config, Window& window, Camera& camera); - ~VulkanRenderer(); + VulkanRenderer(const RendererConfig& config, Window& window, Camera& camera); + ~VulkanRenderer(); - void drawFrame(); + void drawFrame(); - vk::Device device() const; - Allocator& allocator() const; - vk::DescriptorPool descriptorPool() const; + [[nodiscard]] vk::Device device() const; + [[nodiscard]] Allocator& allocator() const; + [[nodiscard]] vk::DescriptorPool descriptorPool() const; private: - const RendererConfig& m_config; - Window& m_window; - Camera& m_camera; - - std::unique_ptr m_context; - std::unique_ptr m_swapchain; - std::unique_ptr m_allocator; - std::unique_ptr m_frameManager; - std::unique_ptr m_descriptorSet; - std::unique_ptr m_pipeline; - std::unique_ptr m_compositePipeline; - std::unique_ptr m_depthPipeline; - std::unique_ptr m_compositeDescriptorSet; - std::unique_ptr m_sampler; - std::unique_ptr m_uniformManager; - std::unique_ptr m_imgui; - std::unique_ptr m_shadowMapping; - - ImageStateTracker m_imageStateTracker; - - std::vector> m_msaaImages; - std::vector m_msaaColorViews; - - std::vector> m_resolveImages; - std::vector m_resolveViews; - std::vector> m_sceneViewImages; - std::vector m_sceneViewViews; - std::vector m_sceneViewImageDescriptorSets; - - std::vector> m_depthImages; - std::vector m_depthViews; - std::vector m_depthImageDescriptorSets; - - // storage for resolved depth - std::vector> m_depthResolveImages; - std::vector m_depthResolveViews; - - DirectionalLightUBO m_light; - std::vector m_objects; - - vk::DescriptorPool m_descriptorPool; - - void createCoreVulkanObjects(); - void createSwapchainAndFrameManager(); - void createPipelineAndDescriptors(); - void setupUI(const std::shared_ptr& consoleSink); - void createMSAAImage(); - void createResolveImages(); - void createSceneViewImages(); - void createSampler(); - void createDescriptorSets(); - void createDepthImages(); - void createDepthPipelineAndDescriptorSets(); - void initScene(); - void createDescriptorPool(); - - void handleSwapchainResizing() const; - static void beginCommandBuffer(vk::CommandBuffer cmd); - void beginDynamicRendering(vk::CommandBuffer cmd, - vk::ImageView colorImageView, - vk::ImageView resolveImageView, - vk::ImageView depthImageView, - vk::Extent2D extent, - bool clearColor = true, - bool clearDepth = false) const; - void bindDescriptorSets(vk::CommandBuffer cmd) const; - void drawGeometry(vk::CommandBuffer cmd) const; - void renderUI(vk::CommandBuffer cmd) const; - static void endDynamicRendering(vk::CommandBuffer cmd); - static void endCommandBuffer(vk::CommandBuffer cmd); - void submitAndPresent(uint32_t imageIndex) const; + const RendererConfig& m_config; + Window& m_window; + Camera& m_camera; + + std::unique_ptr m_context; + std::unique_ptr m_swapchain; + std::unique_ptr m_allocator; + std::unique_ptr m_frameManager; + std::unique_ptr m_descriptorSet; + std::unique_ptr m_pipeline; + std::unique_ptr m_compositePipeline; + std::unique_ptr m_depthPipeline; + std::unique_ptr m_compositeDescriptorSet; + std::unique_ptr m_sampler; + std::unique_ptr m_uniformManager; + std::unique_ptr m_imgui; + std::unique_ptr m_shadowMapping; + + ImageStateTracker m_imageStateTracker; + + std::vector> m_msaaImages; + std::vector m_msaaColorViews; + + std::vector> m_resolveImages; + std::vector m_resolveViews; + std::vector> m_sceneViewImages; + std::vector m_sceneViewViews; + std::vector m_sceneViewImageDescriptorSets; + + std::vector> m_depthImages; + std::vector m_depthViews; + std::vector m_depthImageDescriptorSets; + + // storage for resolved depth + std::vector> m_depthResolveImages; + std::vector m_depthResolveViews; + + DirectionalLightUBO m_light; + std::vector m_objects; + + vk::DescriptorPool m_descriptorPool; + + void createCoreVulkanObjects(); + void createSwapchainAndFrameManager(); + void createPipelineAndDescriptors(); + void setupUI(const std::shared_ptr& consoleSink); + void createMSAAImage(); + void createResolveImages(); + void createSceneViewImages(); + void createSampler(); + void createDescriptorSets(); + void createDepthImages(); + void createDepthPipelineAndDescriptorSets(); + void initScene(); + void createDescriptorPool(); + + void handleSwapchainResizing() const; + static void beginCommandBuffer(vk::CommandBuffer cmd); + void beginDynamicRendering(vk::CommandBuffer cmd, + vk::ImageView colorImageView, + vk::ImageView resolveImageView, + vk::ImageView depthImageView, + vk::Extent2D extent, + bool clearColor = true, + bool clearDepth = false) const; + void bindDescriptorSets(vk::CommandBuffer cmd) const; + void drawGeometry(vk::CommandBuffer cmd) const; + void renderUI(vk::CommandBuffer cmd) const; + static void endDynamicRendering(vk::CommandBuffer cmd); + static void endCommandBuffer(vk::CommandBuffer cmd); + void submitAndPresent(uint32_t imageIndex) const; }; - -} // namespace reactor +} // namespace reactor \ No newline at end of file From 63ac417a82fb6da3fc7e0b9f93c5d7b114788cc3 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Fri, 29 Aug 2025 17:12:05 -0500 Subject: [PATCH 24/24] allocator clean up --- src/vulkan/Allocator.hpp | 91 +++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/src/vulkan/Allocator.hpp b/src/vulkan/Allocator.hpp index 447a8ce..177c3d9 100644 --- a/src/vulkan/Allocator.hpp +++ b/src/vulkan/Allocator.hpp @@ -2,54 +2,61 @@ #include #include + #include +#include -namespace reactor -{ +namespace reactor { class Buffer; -class Allocator -{ +/// @brief Manages Vulkan memory allocations and associated resources. +/// Non-copyable, responsible for buffer creation and allocation. +class Allocator { public: - Allocator(vk::PhysicalDevice physicalDevice, vk::Device device, vk::Instance instance, vk::Queue graphicsQueue, uint32_t graphicsQueueFamilyIndex); - ~Allocator(); - - VmaAllocator getAllocator() const - { - return m_allocator; - } - vk::Device getDevice() const - { - return m_device; - } - vk::Queue getGraphicsQueue() const - { - return m_graphicsQueue; - } - uint32_t getGraphicsQueueFamilyIndex() const - { - return m_graphicQueueFamilyIndex; - } - - // New factory method - std::unique_ptr createBufferWithData( - const void* data, - vk::DeviceSize size, - vk::BufferUsageFlags usage, - const std::string& name = ""); - - // Non-copyable - Allocator(const Allocator&) = delete; - Allocator& operator=(const Allocator&) = delete; + // @brief Constructs an Allocator for Vulkan resource management + Allocator(vk::PhysicalDevice physicalDevice, + vk::Device device, + vk::Instance instance, + vk::Queue graphicsQueue, + uint32_t graphicsQueueFamilyIndex); + + ~Allocator(); + + Allocator(const Allocator&) = delete; + Allocator& operator=(const Allocator&) = delete; + Allocator(Allocator&&) = delete; + Allocator& operator=(Allocator&&) = delete; + + [[nodiscard]] VmaAllocator getAllocator() const { + return m_allocator; + } + + [[nodiscard]] vk::Device getDevice() const { + return m_device; + } + + /// Return the graphics queue used for immediate submissions. + [[nodiscard]] vk::Queue getGraphicsQueue() const { + return m_graphicsQueue; + } + + [[nodiscard]] uint32_t getGraphicsQueueFamilyIndex() const { + return m_graphicQueueFamilyIndex; + } + + /// @brief Creates a buffer with given data and usage. + std::unique_ptr createBufferWithData( + const void* data, + vk::DeviceSize size, + vk::BufferUsageFlags usage, + const std::string& name = ""); private: + void immediateSubmit(std::function&& function); - void immediateSubmit(std::function&& function); - - VmaAllocator m_allocator = nullptr; - vk::Device m_device; - vk::Queue m_graphicsQueue; - uint32_t m_graphicQueueFamilyIndex; + VmaAllocator m_allocator = nullptr; + vk::Device m_device{}; + vk::Queue m_graphicsQueue{}; + uint32_t m_graphicQueueFamilyIndex{}; }; - -} // namespace reactor +} // namespace reactor \ No newline at end of file