From 3ee4a2905ff3f0f88712428ac36edfb51f028b5a Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Sat, 12 Jul 2025 21:18:12 -0500 Subject: [PATCH 01/26] use pipeline builder pattern --- src/vulkan/Pipeline.cpp | 231 ++++++++++++++++++---------------- src/vulkan/Pipeline.hpp | 66 ++++++---- src/vulkan/VulkanRenderer.cpp | 58 +++++---- 3 files changed, 202 insertions(+), 153 deletions(-) diff --git a/src/vulkan/Pipeline.cpp b/src/vulkan/Pipeline.cpp index b244c19..67460ba 100644 --- a/src/vulkan/Pipeline.cpp +++ b/src/vulkan/Pipeline.cpp @@ -7,7 +7,7 @@ namespace reactor { -std::vector Pipeline::readFile(const std::string &filename) const { +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); @@ -18,72 +18,95 @@ std::vector Pipeline::readFile(const std::string &filename) const { return buffer; } -Pipeline::Pipeline(vk::Device device, vk::Format colorAttachmentFormat, - const std::string &vertShaderPath, const std::string &fragShaderPath, - const std::vector &setLayouts, uint32_t samples, - vk::Format depthAttachmentFormat, bool depthWriteEnable) - : m_device(device) { - // 1. Read shader code from files - auto vertShaderCode = readFile(vertShaderPath); - auto vertShaderModule = ShaderModule(m_device, vertShaderCode); +Pipeline::Builder::Builder(vk::Device device) : m_device(device) {} - vk::PipelineShaderStageCreateInfo vertStageInfo{}; - vertStageInfo.stage = vk::ShaderStageFlagBits::eVertex; - vertStageInfo.module = vertShaderModule.getHandle(); - vertStageInfo.pName = "main"; +Pipeline::Builder &Pipeline::Builder::setVertexShader(const std::string &shaderPath) { + m_vertShaderPath = shaderPath; + return *this; +} - std::vector shaderStages = {vertStageInfo}; +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::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; +} - std::unique_ptr fragShaderModule; - if (!fragShaderPath.empty()) { - auto fragShaderCode = readFile(fragShaderPath); - fragShaderModule = std::make_unique(m_device, fragShaderCode); +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"); - vk::PipelineShaderStageCreateInfo fragStageInfo{}; - fragStageInfo.stage = vk::ShaderStageFlagBits::eFragment; - fragStageInfo.module = fragShaderModule->getHandle(); - fragStageInfo.pName = "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); } - // 3. Vertex input (empty, for a basic triangle with no vertex buffer) + // 2. Vertex Input vk::PipelineVertexInputStateCreateInfo vertexInputInfo{}; - // 4. Input assembly - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{}; - inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; - inputAssembly.primitiveRestartEnable = VK_FALSE; - - // 5. Viewport and scissor (dynamic for flexibility) - vk::PipelineViewportStateCreateInfo viewportState{}; - viewportState.viewportCount = 1; - viewportState.scissorCount = 1; - - // 6. Rasterizer - vk::PipelineRasterizationStateCreateInfo rasterizer{}; - rasterizer.depthClampEnable = VK_FALSE; - rasterizer.rasterizerDiscardEnable = VK_FALSE; - rasterizer.polygonMode = vk::PolygonMode::eFill; - rasterizer.lineWidth = 1.0f; - rasterizer.cullMode = vk::CullModeFlagBits::eBack; - rasterizer.frontFace = vk::FrontFace::eClockwise; - rasterizer.depthBiasEnable = VK_FALSE; - - // 7. Multisampling (disabled) - vk::PipelineMultisampleStateCreateInfo multisampling{}; - multisampling.sampleShadingEnable = VK_FALSE; - multisampling.rasterizationSamples = utils::mapSampleCountFlag(samples); + // 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, 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 (depthAttachmentFormat != vk::Format::eUndefined) { - depthStencil.depthTestEnable = VK_TRUE; - depthStencil.depthWriteEnable = depthWriteEnable ? VK_TRUE : VK_FALSE; - depthStencil.depthCompareOp = vk::CompareOp::eLessOrEqual; - depthStencil.depthBoundsTestEnable = VK_FALSE; - depthStencil.stencilTestEnable = VK_FALSE; + 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 + // 8. Color Blending vk::PipelineColorBlendAttachmentState colorBlendAttachment{}; colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | @@ -92,33 +115,30 @@ Pipeline::Pipeline(vk::Device device, vk::Format colorAttachmentFormat, vk::PipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.logicOpEnable = VK_FALSE; - colorBlending.attachmentCount = (colorAttachmentFormat != vk::Format::eUndefined) ? 1 : 0; - colorBlending.pAttachments = (colorAttachmentFormat != vk::Format::eUndefined) ? &colorBlendAttachment : nullptr; - - // 9. Pipeline layout - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{}; - pipelineLayoutInfo.setLayoutCount = static_cast(setLayouts.size()); - pipelineLayoutInfo.pSetLayouts = setLayouts.data(); + colorBlending.attachmentCount = (m_colorAttachmentFormat != vk::Format::eUndefined) ? 1 : 0; + colorBlending.pAttachments = + (m_colorAttachmentFormat != vk::Format::eUndefined) ? &colorBlendAttachment : nullptr; - m_pipelineLayout = m_device.createPipelineLayout(pipelineLayoutInfo); - - // 10. Dynamic state (viewport and scissor set in command buffer) + // 9. Dynamic State std::vector dynamicStates = {vk::DynamicState::eViewport, vk::DynamicState::eScissor}; - vk::PipelineDynamicStateCreateInfo dynamicState{}; - dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); - dynamicState.pDynamicStates = dynamicStates.data(); + vk::PipelineDynamicStateCreateInfo dynamicState({}, dynamicStates); + + // 10. Pipeline Layout + vk::PipelineLayoutCreateInfo pipelineLayoutInfo({}, m_setLayouts); + vk::PipelineLayout pipelineLayout = m_device.createPipelineLayout(pipelineLayoutInfo); - // 11. Dynamic rendering + // 11. Dynamic Rendering Info vk::PipelineRenderingCreateInfo renderingInfo{}; - renderingInfo.colorAttachmentCount = (colorAttachmentFormat != vk::Format::eUndefined) ? 1 : 0; - renderingInfo.pColorAttachmentFormats = (colorAttachmentFormat != vk::Format::eUndefined) ? &colorAttachmentFormat : nullptr; - renderingInfo.viewMask = 0; - renderingInfo.depthAttachmentFormat = depthAttachmentFormat; - renderingInfo.stencilAttachmentFormat = vk::Format::eUndefined; + renderingInfo.viewMask = 0; + renderingInfo.colorAttachmentCount = + (m_colorAttachmentFormat != vk::Format::eUndefined) ? 1 : 0; + renderingInfo.pColorAttachmentFormats = &m_colorAttachmentFormat; + renderingInfo.depthAttachmentFormat = m_depthAttachmentFormat; - // 11. Create graphics pipeline + // 12. Graphics Pipeline Create Info vk::GraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.pNext = &renderingInfo; pipelineInfo.stageCount = static_cast(shaderStages.size()); pipelineInfo.pStages = shaderStages.data(); pipelineInfo.pVertexInputState = &vertexInputInfo; @@ -126,64 +146,57 @@ Pipeline::Pipeline(vk::Device device, vk::Format colorAttachmentFormat, pipelineInfo.pViewportState = &viewportState; pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; - pipelineInfo.pColorBlendState = &colorBlending; - pipelineInfo.layout = m_pipelineLayout; - pipelineInfo.renderPass = VK_NULL_HANDLE; - pipelineInfo.subpass = 0; - pipelineInfo.pDynamicState = &dynamicState; - pipelineInfo.pNext = &renderingInfo; - - // assign depth stencil state to the pipeline info if specified - if (depthAttachmentFormat != vk::Format::eUndefined) { - pipelineInfo.pDepthStencilState = &depthStencil; - } - - auto pipelineResult = m_device.createGraphicsPipeline({}, pipelineInfo); - if (pipelineResult.result != vk::Result::eSuccess) { + 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!"); } - m_pipeline = pipelineResult.value; - + // 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) -{ +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; + other.m_pipeline = nullptr; } -Pipeline& Pipeline::operator=(Pipeline&& other) noexcept -{ +Pipeline &Pipeline::operator=(Pipeline &&other) noexcept { if (this != &other) { - // Destroy our own Vulkan objects first - if (m_pipeline) { + if (m_pipeline) m_device.destroyPipeline(m_pipeline); - } - if (m_pipelineLayout) { + if (m_pipelineLayout) m_device.destroyPipelineLayout(m_pipelineLayout); - } - m_device = other.m_device; + + m_device = other.m_device; m_pipelineLayout = other.m_pipelineLayout; - m_pipeline = other.m_pipeline; + m_pipeline = other.m_pipeline; other.m_pipelineLayout = nullptr; - other.m_pipeline = 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 900b270..018c1ef 100644 --- a/src/vulkan/Pipeline.hpp +++ b/src/vulkan/Pipeline.hpp @@ -1,38 +1,62 @@ #pragma once -#include #include #include +#include namespace reactor { - class Pipeline { - public: - Pipeline(vk::Device device, vk::Format colorAttachmentFormat, - const std::string& vertShaderPath, const std::string& fragShaderPath, - const std::vector& setLayouts, uint32_t samples, - vk::Format depthAttachmentFormat = vk::Format::eUndefined, bool depthWriteEnable = true); - ~Pipeline(); - - [[nodiscard]] vk::Pipeline get() const { return m_pipeline; } - [[nodiscard]] vk::PipelineLayout getLayout() const { return m_pipelineLayout; } +class Pipeline { + public: + class Builder { + public: + Builder(vk::Device device); - // Delete copy operations - Pipeline(const Pipeline&) = delete; - Pipeline& operator=(const Pipeline&) = delete; + 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 &setMultisample(uint32_t samples); + Builder &setCullMode(vk::CullModeFlags cullMode); + Builder &setFrontFace(vk::FrontFace frontFace); - // Move operations - Pipeline(Pipeline&& other) noexcept; - Pipeline& operator=(Pipeline&& other) noexcept; + [[nodiscard]] std::unique_ptr build() const; private: vk::Device m_device; - vk::PipelineLayout m_pipelineLayout; - vk::Pipeline m_pipeline; + 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; + }; - [[nodiscard]] std::vector readFile(const std::string& filename) const; + ~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/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 8c2dd80..eab6545 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -57,13 +57,18 @@ void VulkanRenderer::createPipelineAndDescriptors() { vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex), }; - m_descriptorSet = std::make_unique(m_context->device(), 2, bindings); - const std::vector setLayouts = {m_descriptorSet->getLayout()}; - m_pipeline = std::make_unique(m_context->device(), vk::Format::eR16G16B16A16Sfloat, - vertShaderPath, fragShaderPath, setLayouts, 4, vk::Format::eD32Sfloat, true); + m_pipeline = Pipeline::Builder(m_context->device()) + .setVertexShader(m_config.vertShaderPath) + .setFragmentShader(m_config.fragShaderPath) + .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 + .build(); const std::vector compositeBindings = { vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), @@ -73,14 +78,13 @@ void VulkanRenderer::createPipelineAndDescriptors() { m_compositeDescriptorSet = std::make_unique(m_context->device(), 2, compositeBindings); std::vector compositeSetLayouts = {m_compositeDescriptorSet->getLayout()}; - vk::Format swapchainFormat = m_swapchain->getFormat(); - - m_compositePipeline = std::make_unique( - m_context->device(), - swapchainFormat, - m_config.compositeVertShaderPath, - m_config.compositeFragShaderPath, - compositeSetLayouts, 1); + 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 + .build(); } void VulkanRenderer::handleSwapchainResizing() { @@ -311,7 +315,17 @@ void VulkanRenderer::drawFrame() { vk::AccessFlagBits::eColorAttachmentWrite ); - beginDynamicRendering(cmd, m_sceneViewViews[frameIdx], depthView, extent, true); + 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 +); + + beginDynamicRendering(cmd, m_sceneViewViews[frameIdx], nullptr, extent, true); utils::setupViewportAndScissor(cmd, extent); vk::DescriptorBufferInfo compositeBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); @@ -623,16 +637,14 @@ void VulkanRenderer::createDepthPipelineAndDescriptorSets() { std::vector setLayouts = { m_descriptorSet->getLayout()}; - m_depthPipeline = std::make_unique( - m_context->device(), - vk::Format::eUndefined, - depthVertexShaderPath, - emptyFragShaderPath, - setLayouts, - 4, - vk::Format::eD32Sfloat, - true - ); + m_depthPipeline = Pipeline::Builder(m_context->device()) + .setVertexShader(m_config.vertShaderPath) + // No fragment shader, we only want depth output + .setDepthAttachment(vk::Format::eD32Sfloat, true) // depth test and write enabled + .setDescriptorSetLayouts(setLayouts) + .setMultisample(4) + .setFrontFace(vk::FrontFace::eCounterClockwise) // Match main geometry pipeline + .build(); } From bc8565b99050630271d403bb5a1b6c888254ecfc Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Sat, 12 Jul 2025 21:23:21 -0500 Subject: [PATCH 02/26] allow for no color attachment for depth only --- src/vulkan/VulkanRenderer.cpp | 47 +++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index eab6545..dfc83fc 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -152,31 +152,40 @@ void VulkanRenderer::beginDynamicRendering( bool clearColor=true, bool clearDepth=false) { - constexpr vk::ClearValue clearColorValue = vk::ClearColorValue(std::array{0.0f, 0.0f, 0.0f, 1.0f}); vk::RenderingAttachmentInfo colorAttachment{}; - 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; + 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); - vk::RenderingAttachmentInfo depthAttachment{}; - 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 (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; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + } else { + renderingInfo.colorAttachmentCount = 0; + renderingInfo.pColorAttachments = nullptr; + } - vk::RenderingInfo renderingInfo{}; - renderingInfo.renderArea.offset = vk::Offset2D{0, 0}; - renderingInfo.renderArea.extent = extent; - renderingInfo.layerCount = 1; - renderingInfo.colorAttachmentCount = 1; - renderingInfo.pColorAttachments = &colorAttachment; - renderingInfo.pDepthAttachment = &depthAttachment; + 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; + renderingInfo.pDepthAttachment = &depthAttachment; + } else { + renderingInfo.pDepthAttachment = nullptr; + } cmd.beginRendering(renderingInfo); } From 3bda2b98f2cfc214cdf4d1735bb25c63edf27138 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Sun, 13 Jul 2025 08:29:46 -0500 Subject: [PATCH 03/26] fixed the composite not showing up --- src/vulkan/VulkanRenderer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index dfc83fc..df6524f 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -84,6 +84,7 @@ void VulkanRenderer::createPipelineAndDescriptors() { .setColorAttachment(m_swapchain->getFormat()) .setDescriptorSetLayouts(compositeSetLayouts) .setMultisample(1) // No MSAA for composite pass + .setFrontFace(vk::FrontFace::eClockwise) // Assuming standard winding order for cubes .build(); } @@ -652,7 +653,7 @@ void VulkanRenderer::createDepthPipelineAndDescriptorSets() { .setDepthAttachment(vk::Format::eD32Sfloat, true) // depth test and write enabled .setDescriptorSetLayouts(setLayouts) .setMultisample(4) - .setFrontFace(vk::FrontFace::eCounterClockwise) // Match main geometry pipeline + .setFrontFace(vk::FrontFace::eClockwise) // Match main geometry pipeline .build(); From 0801f44cc45bbd453703fef3dcb166a0175eaa11 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Sun, 13 Jul 2025 14:43:12 -0500 Subject: [PATCH 04/26] fix transitions --- shaders/composite.frag | 29 ++++++++++++++++++++++++++ src/vulkan/VulkanRenderer.cpp | 39 ++++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/shaders/composite.frag b/shaders/composite.frag index 8c03234..4e200db 100644 --- a/shaders/composite.frag +++ b/shaders/composite.frag @@ -5,6 +5,9 @@ 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 @@ -13,6 +16,15 @@ layout(set = 0, binding = 1) uniform CompositeParams { float uVignetteFalloff; // Default 0.5 }; +// 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; @@ -24,6 +36,20 @@ vec3 tonemapACES(vec3 x) { } 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 = 5.0; + float viewDistance = linearizeDepth(depth); + // Calculate fog amount (0.0 = no fog, 1.0 = full fog) + float fogFactor = smoothstep(fogStart, fogEnd, viewDistance); + // ---------------------- + // Sample the HDR input image vec3 hdrColor = texture(uInputImage, fragUV).rgb; @@ -43,5 +69,8 @@ void main() { 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/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index df6524f..eda8f4f 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -73,6 +73,7 @@ void VulkanRenderer::createPipelineAndDescriptors() { const std::vector compositeBindings = { vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment), + vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), }; m_compositeDescriptorSet = std::make_unique(m_context->device(), 2, compositeBindings); @@ -245,6 +246,17 @@ void VulkanRenderer::drawFrame() { // 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, depthView, extent, false, true); utils::setupViewportAndScissor(cmd, extent); bindDescriptorSets(cmd); @@ -333,7 +345,18 @@ void VulkanRenderer::drawFrame() { 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); + beginDynamicRendering(cmd, m_sceneViewViews[frameIdx], nullptr, extent, true); utils::setupViewportAndScissor(cmd, extent); @@ -345,6 +368,11 @@ void VulkanRenderer::drawFrame() { imageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; imageInfo.sampler = m_sampler->get(); + 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), @@ -364,6 +392,15 @@ void VulkanRenderer::drawFrame() { nullptr, &compositeBufferInfo, nullptr + }, + vk::WriteDescriptorSet{ + m_compositeDescriptorSet->getCurrentSet(frameIdx), + 2, + 0, + 1, + vk::DescriptorType::eCombinedImageSampler, + &depthImageInfo, + nullptr } }; m_compositeDescriptorSet->updateSet(writes); From 280f9d9a45a6f30649c8865a1e45cdd3cdbdc16c Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Mon, 14 Jul 2025 16:40:40 -0500 Subject: [PATCH 05/26] small fixes --- src/core/Application.cpp | 4 ++-- src/core/Application.hpp | 11 ++++++++++- src/core/Camera.cpp | 5 ----- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/core/Application.cpp b/src/core/Application.cpp index 8014ec1..7b397ea 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -33,9 +33,9 @@ void Application::initialize() { m_renderer = std::make_unique(config, *m_window, *m_camera); } -void Application::run() { +void Application::run() const { while (!m_window->shouldClose()) { - m_window->pollEvents(); + Window::pollEvents(); m_renderer->drawFrame(); } } diff --git a/src/core/Application.hpp b/src/core/Application.hpp index c1b672d..d9b9344 100644 --- a/src/core/Application.hpp +++ b/src/core/Application.hpp @@ -14,7 +14,16 @@ class Application { Application(); ~Application(); - void run(); + // 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(); diff --git a/src/core/Camera.cpp b/src/core/Camera.cpp index 05310a0..281fa42 100644 --- a/src/core/Camera.cpp +++ b/src/core/Camera.cpp @@ -1,11 +1,6 @@ -// -// Created by rfdic on 7/7/2025. -// - #include "Camera.hpp" #include -#include namespace reactor { From 151d1473ea4fde7a57e6921b5846809990f4cb63 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Mon, 14 Jul 2025 17:05:18 -0500 Subject: [PATCH 06/26] improve the camera --- src/core/Camera.cpp | 100 +++++++++++++++++++++++++++++++++- src/core/Camera.hpp | 43 ++++++++++++--- src/core/OrbitController.cpp | 2 +- src/vulkan/VulkanRenderer.cpp | 4 +- 4 files changed, 137 insertions(+), 12 deletions(-) diff --git a/src/core/Camera.cpp b/src/core/Camera.cpp index 281fa42..65119db 100644 --- a/src/core/Camera.cpp +++ b/src/core/Camera.cpp @@ -1,12 +1,108 @@ #include "Camera.hpp" -#include +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include + namespace reactor { Camera::Camera(): m_view(glm::mat4(1.0f)), m_projection(glm::mat4(1.0f)) { - m_projection = glm::perspective(glm::radians(45.0f), 16.0f / 9.0f, 0.1f, 100.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) { + 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) { + m_target = target; + m_viewDirty = true; +} + +void Camera::setUp(const glm::vec3& up) { + m_up = up; + m_viewDirty = true; +} + +void Camera::lookAt(const glm::vec3& eye, const glm::vec3& target, const glm::vec3& up) { + m_position = eye; + m_target = target; + m_up = up; + m_viewDirty = true; +} + +void Camera::move(const glm::vec3& delta) { + m_position += delta; + m_target += delta; + m_viewDirty = true; } +void Camera::rotate(float yaw, float pitch, float roll) { + // This offers a very simple orbital rotation around the target point for demonstration. + // For proper FPS/third-person/free camera, this should be replaced with a full quaternion-based approach. + + glm::vec3 dir = m_target - m_position; + glm::mat4 rot = glm::eulerAngleYXZ(glm::radians(yaw), glm::radians(pitch), glm::radians(roll)); + + dir = glm::vec3(rot * glm::vec4(dir, 0.0f)); + m_position = m_target - dir; + m_up = glm::vec3(rot * glm::vec4(m_up, 0.0f)); + 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; +} + +glm::vec3 Camera::getPosition() const { + return m_position; +} + + +void Camera::updateView() { + m_view = glm::lookAt(m_position, m_target, m_up); + 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 18ffb53..a187775 100644 --- a/src/core/Camera.hpp +++ b/src/core/Camera.hpp @@ -1,24 +1,53 @@ #pragma once -#include "EventManager.hpp" #include +#include namespace reactor { +enum class ProjectionType { + Perspective, + Orthographic, +}; + class Camera { public: - Camera(); - [[nodiscard]] glm::mat4 getView() const { return m_view; } - [[nodiscard]] glm::mat4 getProjection() const { return m_projection; } + void setProjectionType(ProjectionType type); + void setPerspective(float fov, float aspect, float near, float far); + void setOrthographic(float left, float right, float bottom, float top, float near, float far); + + void setPosition(const glm::vec3& position); + void setTarget(const glm::vec3& target); + void setUp(const glm::vec3& up); - void setView(const glm::mat4& view) { m_view = view; } + void lookAt(const glm::vec3& eye, const glm::vec3& target, const glm::vec3& up); + void move(const glm::vec3& delta); + void rotate(float yam, float pitch, float roll); + + // getters + const glm::mat4& getViewMatrix() const; + const glm::mat4& getProjectionMatrix() const; + glm::vec3 getPosition() const; private: - glm::mat4 m_view; - glm::mat4 m_projection; + 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::vec3 m_target{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/OrbitController.cpp b/src/core/OrbitController.cpp index e690a44..6178a45 100644 --- a/src/core/OrbitController.cpp +++ b/src/core/OrbitController.cpp @@ -53,7 +53,7 @@ void OrbitController::updateCamera() { constexpr glm::vec3 target(0.0f); constexpr glm::vec3 up(0.0f, 1.0f, 0.0f); - m_camera.setView(glm::lookAt(position, target, up)); + m_camera.lookAt(position, target, up); } diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index eda8f4f..0e0b9a0 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -226,8 +226,8 @@ void VulkanRenderer::drawFrame() { m_uniformManager->update(frameIdx, compositeData); SceneUBO ubo{}; - ubo.view = m_camera.getView(); - ubo.projection = m_camera.getProjection(); + ubo.view = m_camera.getViewMatrix(); + ubo.projection = m_camera.getProjectionMatrix(); m_uniformManager->update(frameIdx, ubo); From afa08882e549ba2ef3fe796651d5d13458031ff5 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Tue, 15 Jul 2025 12:01:52 -0500 Subject: [PATCH 07/26] massive improvements to the camera --- shaders/composite.frag | 2 +- src/core/Application.cpp | 1 + src/core/Camera.cpp | 63 +++++++++++++++++++++++++++--------- src/core/Camera.hpp | 13 ++++++-- src/core/OrbitController.cpp | 60 +++++++++++++++++++++++++++------- src/core/OrbitController.hpp | 10 ++++++ src/pch.hpp | 4 +++ 7 files changed, 123 insertions(+), 30 deletions(-) diff --git a/shaders/composite.frag b/shaders/composite.frag index 4e200db..c11c34b 100644 --- a/shaders/composite.frag +++ b/shaders/composite.frag @@ -70,7 +70,7 @@ void main() { finalColor = mix(grayscale, finalColor, uSaturation); // Mix the final scene color with the fog color - finalColor = mix(finalColor, fogColor, fogFactor); + //finalColor = mix(finalColor, fogColor, fogFactor); outColor = vec4(finalColor, 1.0); } \ No newline at end of file diff --git a/src/core/Application.cpp b/src/core/Application.cpp index 7b397ea..1850352 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -14,6 +14,7 @@ void Application::initialize() { 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()); diff --git a/src/core/Camera.cpp b/src/core/Camera.cpp index 65119db..64c238a 100644 --- a/src/core/Camera.cpp +++ b/src/core/Camera.cpp @@ -4,7 +4,8 @@ #include #include #include - +#include +#include namespace reactor { @@ -21,6 +22,9 @@ void Camera::setProjectionType(ProjectionType type) { } 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; @@ -35,38 +39,46 @@ void Camera::setPosition(const glm::vec3& position) { } void Camera::setTarget(const glm::vec3& target) { - m_target = 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 = 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; - m_target = target; - m_up = up; + setTarget(target); + m_up = normalize(up); m_viewDirty = true; } void Camera::move(const glm::vec3& delta) { m_position += delta; - m_target += delta; m_viewDirty = true; } -void Camera::rotate(float yaw, float pitch, float roll) { - // This offers a very simple orbital rotation around the target point for demonstration. - // For proper FPS/third-person/free camera, this should be replaced with a full quaternion-based approach. +void Camera::moveRelative(const glm::vec3 &delta) { + m_position += getRight() * delta.x + getUp() * delta.y + getForward() * delta.z; + m_viewDirty = true; +} - glm::vec3 dir = m_target - m_position; - glm::mat4 rot = glm::eulerAngleYXZ(glm::radians(yaw), glm::radians(pitch), glm::radians(roll)); - dir = glm::vec3(rot * glm::vec4(dir, 0.0f)); - m_position = m_target - dir; - m_up = glm::vec3(rot * glm::vec4(m_up, 0.0f)); +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; } @@ -90,9 +102,30 @@ 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() { - m_view = glm::lookAt(m_position, m_target, m_up); + 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; } diff --git a/src/core/Camera.hpp b/src/core/Camera.hpp index a187775..5bf09d5 100644 --- a/src/core/Camera.hpp +++ b/src/core/Camera.hpp @@ -2,6 +2,7 @@ #include #include +#include namespace reactor { @@ -16,7 +17,6 @@ class Camera { void setProjectionType(ProjectionType type); void setPerspective(float fov, float aspect, float near, float far); - void setOrthographic(float left, float right, float bottom, float top, float near, float far); void setPosition(const glm::vec3& position); void setTarget(const glm::vec3& target); @@ -24,12 +24,19 @@ class Camera { void lookAt(const glm::vec3& eye, const glm::vec3& target, const glm::vec3& up); void move(const glm::vec3& delta); - void rotate(float yam, float pitch, float roll); + 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; 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(); @@ -43,7 +50,7 @@ class Camera { float m_left, m_right, m_bottom, m_top; glm::vec3 m_position{0.0f, 0.0f, 5.0f}; - glm::vec3 m_target{0.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}; diff --git a/src/core/OrbitController.cpp b/src/core/OrbitController.cpp index 6178a45..b074be9 100644 --- a/src/core/OrbitController.cpp +++ b/src/core/OrbitController.cpp @@ -10,6 +10,13 @@ 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: @@ -18,30 +25,40 @@ void OrbitController::onEvent(const Event &event) { double dy = event.mouseMove.y - m_lastY; m_azimuth += static_cast(dx) * ROTATE_SPEED; m_elevation += static_cast(dy) * ROTATE_SPEED; - // clamp elevation - float max_elev = glm::radians(85.0f); - m_elevation = glm::clamp(m_elevation, -max_elev, max_elev); + 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; - case EventType::KeyReleased: - break; - - } + } } void OrbitController::updateCamera() { @@ -49,11 +66,32 @@ void OrbitController::updateCamera() { const float y = m_distance * sinf(m_elevation); const float z = m_distance * cosf(m_elevation) * cosf(m_azimuth); - const glm::vec3 position(x, y, z); - constexpr glm::vec3 target(0.0f); - constexpr glm::vec3 up(0.0f, 1.0f, 0.0f); + 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 speed = m_distance * 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(); - m_camera.lookAt(position, target, up); } diff --git a/src/core/OrbitController.hpp b/src/core/OrbitController.hpp index f7e7372..8a7961a 100644 --- a/src/core/OrbitController.hpp +++ b/src/core/OrbitController.hpp @@ -11,22 +11,32 @@ class OrbitController final : public IEventListener { 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.002f; 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/pch.hpp b/src/pch.hpp index e24f857..2ee3cc2 100644 --- a/src/pch.hpp +++ b/src/pch.hpp @@ -21,6 +21,10 @@ #include #include + +#define GLM_ENABLE_EXPERIMENTAL #include +#include +#include #endif //PCH_HPP From 3946fef36490af177f4a1f35de166ae847df3eac Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Tue, 15 Jul 2025 12:21:47 -0500 Subject: [PATCH 08/26] slow down pans --- src/core/Camera.cpp | 4 ++++ src/core/Camera.hpp | 1 + src/core/OrbitController.cpp | 3 ++- src/core/OrbitController.hpp | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/core/Camera.cpp b/src/core/Camera.cpp index 64c238a..7d91cd1 100644 --- a/src/core/Camera.cpp +++ b/src/core/Camera.cpp @@ -98,6 +98,10 @@ const glm::mat4& Camera::getProjectionMatrix() const { return m_projection; } +float Camera::getFOV() const { + return m_fov; +} + glm::vec3 Camera::getPosition() const { return m_position; } diff --git a/src/core/Camera.hpp b/src/core/Camera.hpp index 5bf09d5..d3f8104 100644 --- a/src/core/Camera.hpp +++ b/src/core/Camera.hpp @@ -31,6 +31,7 @@ class Camera { // 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; diff --git a/src/core/OrbitController.cpp b/src/core/OrbitController.cpp index b074be9..c20cc06 100644 --- a/src/core/OrbitController.cpp +++ b/src/core/OrbitController.cpp @@ -73,7 +73,8 @@ void OrbitController::updateCamera() { } void OrbitController::updatePan(float dx, float dy) { - const float speed = m_distance * PAN_SPEED; + 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(); diff --git a/src/core/OrbitController.hpp b/src/core/OrbitController.hpp index 8a7961a..cba1967 100644 --- a/src/core/OrbitController.hpp +++ b/src/core/OrbitController.hpp @@ -31,7 +31,7 @@ class OrbitController final : public IEventListener { void updatePan(float dx, float dy); static constexpr float ROTATE_SPEED = 0.01f; - static constexpr float PAN_SPEED = 0.002f; + 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; From ec4efe867ace0d8ccdcd3153b6f02f28cd1802b5 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Tue, 15 Jul 2025 13:53:52 -0500 Subject: [PATCH 09/26] events come from ImGui now --- src/core/Window.cpp | 4 ++-- src/core/Window.hpp | 2 ++ src/imgui/Imgui.cpp | 44 ++++++++++++++++++++++++++++++++--- src/imgui/Imgui.hpp | 3 ++- src/vulkan/VulkanRenderer.cpp | 2 +- 5 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/core/Window.cpp b/src/core/Window.cpp index a18121b..380e653 100644 --- a/src/core/Window.cpp +++ b/src/core/Window.cpp @@ -24,8 +24,8 @@ m_width(width), m_height(height), m_title(title), m_eventManager(eventManager) { glfwSetFramebufferSizeCallback(m_window, framebufferResizeCallback); glfwSetKeyCallback(m_window, keyCallback); - glfwSetCursorPosCallback(m_window, cursorPosCallback); - glfwSetMouseButtonCallback(m_window, mouseButtonCallback); + // glfwSetCursorPosCallback(m_window, cursorPosCallback); + // glfwSetMouseButtonCallback(m_window, mouseButtonCallback); } // The destructor cleans up GLFW resources. diff --git a/src/core/Window.hpp b/src/core/Window.hpp index 25cf268..3a1300b 100644 --- a/src/core/Window.hpp +++ b/src/core/Window.hpp @@ -43,6 +43,8 @@ namespace reactor { // 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; } diff --git a/src/imgui/Imgui.cpp b/src/imgui/Imgui.cpp index dd0580d..79df673 100644 --- a/src/imgui/Imgui.cpp +++ b/src/imgui/Imgui.cpp @@ -4,6 +4,7 @@ #include "Imgui.hpp" +#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES #include #include #include @@ -11,11 +12,11 @@ namespace reactor { -Imgui::Imgui(VulkanContext &vulkanContext, Window &window) : m_device(vulkanContext.device()) { +Imgui::Imgui(VulkanContext &vulkanContext, Window &window, EventManager &eventManager) : +m_device(vulkanContext.device()), m_eventManager(eventManager) { IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO &io = ImGui::GetIO(); - (void)io; io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; ImGui::StyleColorsDark(); @@ -128,7 +129,7 @@ void Imgui::ShowSceneView() { const ImVec2 size = ImGui::GetContentRegionAvail(); if (m_sceneImguiId) { - const ImTextureID id = + const auto id = reinterpret_cast(static_cast(m_sceneImguiId)); ImGui::Image( @@ -136,6 +137,43 @@ void Imgui::ShowSceneView() { 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"); } diff --git a/src/imgui/Imgui.hpp b/src/imgui/Imgui.hpp index c5cdae8..294fa02 100644 --- a/src/imgui/Imgui.hpp +++ b/src/imgui/Imgui.hpp @@ -13,7 +13,7 @@ namespace reactor { class Imgui { public: - Imgui(VulkanContext& vulkanContext, Window& window); + Imgui(VulkanContext& vulkanContext, Window& window, EventManager& eventManager); ~Imgui(); void createFrame(); @@ -29,6 +29,7 @@ class Imgui { private: vk::Device m_device; + EventManager& m_eventManager; vk::DescriptorPool m_descriptorPool; vk::DescriptorSet m_sceneImguiId; diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 0e0b9a0..6787f34 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -102,7 +102,7 @@ void VulkanRenderer::handleSwapchainResizing() { } } -void VulkanRenderer::setupUI() { m_imgui = std::make_unique(*m_context, m_window); } +void VulkanRenderer::setupUI() { m_imgui = std::make_unique(*m_context, m_window, m_window.getEventManager()); } VulkanRenderer::~VulkanRenderer() { From 0c3a180de795741698f0e64d66c10e25a0302a35 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Wed, 16 Jul 2025 18:23:57 -0500 Subject: [PATCH 10/26] formatting changes --- .clang-format | 52 ++-- CMakeLists.txt | 3 +- src/vulkan/Pipeline.cpp | 364 ++++++++++++++------------- src/vulkan/Pipeline.hpp | 100 ++++---- src/vulkan/Sampler.cpp | 27 +- src/vulkan/Sampler.hpp | 15 +- src/vulkan/ShaderModule.cpp | 28 ++- src/vulkan/ShaderModule.hpp | 24 +- src/vulkan/Vertex.hpp | 51 ++++ src/vulkan/VulkanRenderer.cpp | 449 ++++++++++++++++++---------------- 10 files changed, 626 insertions(+), 487 deletions(-) create mode 100644 src/vulkan/Vertex.hpp diff --git a/.clang-format b/.clang-format index 151c0f5..b04f019 100644 --- a/.clang-format +++ b/.clang-format @@ -1,23 +1,39 @@ -# .clang-format +--- +Language: Cpp BasedOnStyle: LLVM IndentWidth: 4 TabWidth: 4 UseTab: Never - -# Don’t indent contents of namespaces -NamespaceIndentation: None - -# Align consecutive assignments -AlignConsecutiveAssignments: true - -# Optionally align declarations too (if you like the visual symmetry) -AlignConsecutiveDeclarations: true - -# Keep parameters and arguments aligned (can be visually helpful) +ColumnLimit: 0 # No forced wrapping; useful for long VulkanHpp calls +AccessModifierOffset: -4 AlignAfterOpenBracket: Align - -# Optional: control brace placement if you like a specific style -BreakBeforeBraces: Attach - -# Optional: max line length -ColumnLimit: 100 +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/CMakeLists.txt b/CMakeLists.txt index 4a298cf..aaf6f08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,7 +55,8 @@ add_executable(Reactor src/core/main.cpp src/core/OrbitController.cpp src/core/OrbitController.hpp src/core/Application.cpp - src/core/Application.hpp) + src/core/Application.hpp + src/vulkan/Vertex.hpp) target_link_libraries(Reactor PRIVATE Vulkan::Vulkan Vulkan::Headers glfw fmt::fmt spdlog::spdlog GPUOpen::VulkanMemoryAllocator glm::glm) target_link_libraries(Reactor PRIVATE imgui::imgui) diff --git a/src/vulkan/Pipeline.cpp b/src/vulkan/Pipeline.cpp index 67460ba..8c19598 100644 --- a/src/vulkan/Pipeline.cpp +++ b/src/vulkan/Pipeline.cpp @@ -5,198 +5,222 @@ #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::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; -} - -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); +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; } - // 2. Vertex Input - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{}; + Pipeline::Builder::Builder(vk::Device device) + : m_device(device) + {} - // 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, VK_FALSE, 0.0f, - 0.0f, 0.0f, 1.0f); + Pipeline::Builder& Pipeline::Builder::setVertexShader(const std::string& shaderPath) + { + m_vertShaderPath = shaderPath; + return *this; + } - // 6. Multisampling - vk::PipelineMultisampleStateCreateInfo multisampling({}, utils::mapSampleCountFlag(m_samples), - VK_FALSE); + Pipeline::Builder& Pipeline::Builder::setFragmentShader(const std::string& shaderPath) + { + m_fragShaderPath = shaderPath; + return *this; + } - // 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; + Pipeline::Builder& Pipeline::Builder::setColorAttachment(vk::Format format) + { + m_colorAttachmentFormat = format; + return *this; } - // 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}; - vk::PipelineDynamicStateCreateInfo dynamicState({}, dynamicStates); - - // 10. Pipeline Layout - vk::PipelineLayoutCreateInfo pipelineLayoutInfo({}, m_setLayouts); - 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!"); + Pipeline::Builder& Pipeline::Builder::setDepthAttachment(vk::Format format, bool depthWriteEnable) + { + m_depthAttachmentFormat = format; + m_depthWriteEnable = depthWriteEnable; + return *this; } - // Using a private constructor to pass ownership of the created handles - return std::unique_ptr(new Pipeline(m_device, pipelineLayout, result.value)); -} + 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 Implementation --- + Pipeline::Builder& Pipeline::Builder::setMultisample(uint32_t samples) + { + m_samples = samples; + return *this; + } -Pipeline::Pipeline(vk::Device device, vk::PipelineLayout layout, vk::Pipeline pipeline) - : m_device(device), m_pipelineLayout(layout), m_pipeline(pipeline) {} + Pipeline::Builder& Pipeline::Builder::setCullMode(vk::CullModeFlags cullMode) + { + m_cullMode = cullMode; + return *this; + } -Pipeline::~Pipeline() { - if (m_pipeline) { - m_device.destroyPipeline(m_pipeline); + Pipeline::Builder& Pipeline::Builder::setFrontFace(vk::FrontFace frontFace) + { + m_frontFace = frontFace; + return *this; } - if (m_pipelineLayout) { - m_device.destroyPipelineLayout(m_pipelineLayout); + + 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{}; + + // 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, 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}; + vk::PipelineDynamicStateCreateInfo dynamicState({}, dynamicStates); + + // 10. Pipeline Layout + vk::PipelineLayoutCreateInfo pipelineLayoutInfo({}, m_setLayouts); + 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::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 Implementation --- -Pipeline &Pipeline::operator=(Pipeline &&other) noexcept { - if (this != &other) { + 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); + } + } - m_device = other.m_device; - m_pipelineLayout = other.m_pipelineLayout; - m_pipeline = other.m_pipeline; - + 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; + 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; } - return *this; -} } // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Pipeline.hpp b/src/vulkan/Pipeline.hpp index 018c1ef..df9893e 100644 --- a/src/vulkan/Pipeline.hpp +++ b/src/vulkan/Pipeline.hpp @@ -4,59 +4,73 @@ #include #include -namespace reactor { +#include "Vertex.hpp" -class Pipeline { - public: - class Builder { - public: - Builder(vk::Device device); +namespace reactor +{ - 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 &setMultisample(uint32_t samples); - Builder &setCullMode(vk::CullModeFlags cullMode); - Builder &setFrontFace(vk::FrontFace frontFace); + class Pipeline + { + public: + class Builder + { + public: + Builder(vk::Device device); - [[nodiscard]] std::unique_ptr build() const; + 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); - 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; - }; + [[nodiscard]] std::unique_ptr build() const; - ~Pipeline(); + 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; - [[nodiscard]] vk::Pipeline get() const { return m_pipeline; } - [[nodiscard]] vk::PipelineLayout getLayout() const { return m_pipelineLayout; } + std::vector m_bindings; + std::vector m_attributes; + }; - // Delete copy operations - Pipeline(const Pipeline &) = delete; - Pipeline &operator=(const Pipeline &) = delete; + ~Pipeline(); - // Move operations - Pipeline(Pipeline &&other) noexcept; - Pipeline &operator=(Pipeline &&other) noexcept; + [[nodiscard]] vk::Pipeline get() const + { + return m_pipeline; + } + [[nodiscard]] vk::PipelineLayout getLayout() const + { + return m_pipelineLayout; + } - private: - friend class Builder; - Pipeline(vk::Device device, vk::PipelineLayout pipelineLayout, vk::Pipeline pipeline); + // Delete copy operations + Pipeline(const Pipeline&) = delete; + Pipeline& operator=(const Pipeline&) = delete; - vk::Device m_device; - vk::PipelineLayout m_pipelineLayout; - vk::Pipeline m_pipeline; + // 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 3048180..f3052d8 100644 --- a/src/vulkan/Sampler.cpp +++ b/src/vulkan/Sampler.cpp @@ -1,17 +1,20 @@ #include "Sampler.hpp" -namespace reactor { +namespace reactor +{ - Sampler::Sampler(vk::Device device, const vk::SamplerCreateInfo &createInfo) -: m_device(device) { - m_sampler = m_device.createSampler(createInfo); - } +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); - } - } +Sampler::~Sampler() +{ + if (m_sampler) + { + m_device.destroySampler(m_sampler, nullptr); + } +} - -} // reactor \ No newline at end of file +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Sampler.hpp b/src/vulkan/Sampler.hpp index 8507bd3..bbd4915 100644 --- a/src/vulkan/Sampler.hpp +++ b/src/vulkan/Sampler.hpp @@ -2,20 +2,23 @@ #include -namespace reactor { +namespace reactor +{ -class Sampler { +class Sampler +{ public: Sampler(vk::Device device, const vk::SamplerCreateInfo& createInfo); ~Sampler(); - [[nodiscard]] vk::Sampler get() const { return m_sampler; } + [[nodiscard]] vk::Sampler get() const + { + return m_sampler; + } private: vk::Device m_device; vk::Sampler m_sampler; - }; -} // reactor - +} // namespace reactor diff --git a/src/vulkan/ShaderModule.cpp b/src/vulkan/ShaderModule.cpp index af71220..2e15dd9 100644 --- a/src/vulkan/ShaderModule.cpp +++ b/src/vulkan/ShaderModule.cpp @@ -4,18 +4,22 @@ #include "ShaderModule.hpp" -namespace reactor { +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(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); - } +ShaderModule::~ShaderModule() +{ + if (m_handle) + { + m_device.destroyShaderModule(m_handle); + } +} - - -} // reactor \ No newline at end of file +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/ShaderModule.hpp b/src/vulkan/ShaderModule.hpp index a8c9c98..579de6f 100644 --- a/src/vulkan/ShaderModule.hpp +++ b/src/vulkan/ShaderModule.hpp @@ -1,25 +1,33 @@ #pragma once #include -namespace reactor { +namespace reactor +{ -class ShaderModule { +class ShaderModule +{ public: ShaderModule(vk::Device device, const std::vector& code); ~ShaderModule(); - [[nodiscard]] vk::ShaderModule getHandle() const { return m_handle; }; + [[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(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 = {}; + vk::ShaderModule m_handle; + vk::Device m_device; }; -} // reactor - +} // namespace reactor diff --git a/src/vulkan/Vertex.hpp b/src/vulkan/Vertex.hpp new file mode 100644 index 0000000..e7309d3 --- /dev/null +++ b/src/vulkan/Vertex.hpp @@ -0,0 +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; + } +}; + +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 6787f34..365d505 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -7,9 +7,11 @@ #include #include -namespace reactor { +namespace reactor +{ VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Camera& camera) -: m_config(config), m_window(window), m_camera(camera) { + : m_config(config), m_window(window), m_camera(camera) +{ createCoreVulkanObjects(); createSwapchainAndFrameManager(); @@ -27,48 +29,47 @@ VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Cam createSampler(); createDescriptorSets(); createDepthPipelineAndDescriptorSets(); - } -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()); +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()); } -void VulkanRenderer::createSwapchainAndFrameManager() { - m_swapchain = std::make_unique(m_context->device(), m_context->physicalDevice(), - m_context->surface(), m_window); +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); + m_frameManager = std::make_unique(m_context->device(), *m_allocator, 0, 2, swapchainImageCount); - for (const auto& image : m_swapchain->getImages()) { + for (const auto& image : m_swapchain->getImages()) + { m_imageStateTracker.recordState(image, vk::ImageLayout::eUndefined); } } -void VulkanRenderer::createPipelineAndDescriptors() { +void VulkanRenderer::createPipelineAndDescriptors() +{ const std::string vertShaderPath = m_config.vertShaderPath; const std::string fragShaderPath = m_config.fragShaderPath; const std::vector bindings = { - vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, - vk::ShaderStageFlagBits::eVertex), + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex), }; m_descriptorSet = std::make_unique(m_context->device(), 2, bindings); const std::vector setLayouts = {m_descriptorSet->getLayout()}; m_pipeline = Pipeline::Builder(m_context->device()) - .setVertexShader(m_config.vertShaderPath) - .setFragmentShader(m_config.fragShaderPath) - .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 - .build(); + .setVertexShader(m_config.vertShaderPath) + .setFragmentShader(m_config.fragShaderPath) + .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 + .build(); const std::vector compositeBindings = { vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), @@ -80,19 +81,22 @@ void VulkanRenderer::createPipelineAndDescriptors() { 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(); + .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()) { +void VulkanRenderer::handleSwapchainResizing() +{ + if (m_window.wasResized()) + { vk::Extent2D size = m_window.getFramebufferSize(); - while (size.width == 0 || size.height == 0) { + while (size.width == 0 || size.height == 0) + { Window::waitEvents(); size = m_window.getFramebufferSize(); } @@ -102,13 +106,18 @@ void VulkanRenderer::handleSwapchainResizing() { } } -void VulkanRenderer::setupUI() { m_imgui = std::make_unique(*m_context, m_window, m_window.getEventManager()); } +void VulkanRenderer::setupUI() +{ + m_imgui = std::make_unique(*m_context, m_window, m_window.getEventManager()); +} -VulkanRenderer::~VulkanRenderer() { +VulkanRenderer::~VulkanRenderer() +{ m_context->device().waitIdle(); - for (auto i = 0; i < m_frameManager->getFramesInFlightCount(); ++i) { + 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]); @@ -116,34 +125,41 @@ VulkanRenderer::~VulkanRenderer() { } } -void VulkanRenderer::beginCommandBuffer(vk::CommandBuffer cmd) { +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::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) { - //cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_pipeline->get()); +void VulkanRenderer::drawGeometry(vk::CommandBuffer cmd) +{ + // cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_pipeline->get()); cmd.draw(36, 1, 0, 0); } -void VulkanRenderer::renderUI(const vk::CommandBuffer cmd) const { +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::endDynamicRendering(vk::CommandBuffer cmd) +{ + cmd.endRendering(); +} -void VulkanRenderer::endCommandBuffer(vk::CommandBuffer cmd) { cmd.end(); } +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::submitAndPresent(uint32_t imageIndex) +{ + m_frameManager->endFrame(m_context->graphicsQueue(), m_context->presentQueue(), m_swapchain->get(), imageIndex); } void VulkanRenderer::beginDynamicRendering( @@ -151,8 +167,8 @@ void VulkanRenderer::beginDynamicRendering( vk::ImageView colorImageView, vk::ImageView depthImageView, vk::Extent2D extent, - bool clearColor=true, - bool clearDepth=false) + bool clearColor = true, + bool clearDepth = false) { vk::RenderingAttachmentInfo colorAttachment{}; vk::RenderingAttachmentInfo depthAttachment{}; @@ -165,7 +181,8 @@ void VulkanRenderer::beginDynamicRendering( vk::ClearValue depthClearValue{}; depthClearValue.depthStencil = vk::ClearDepthStencilValue(1.0f, 0); - if (colorImageView) { + if (colorImageView) + { colorAttachment.imageView = colorImageView; colorAttachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal; colorAttachment.loadOp = clearColor ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad; @@ -173,45 +190,52 @@ void VulkanRenderer::beginDynamicRendering( colorAttachment.clearValue = clearColorValue; renderingInfo.colorAttachmentCount = 1; renderingInfo.pColorAttachments = &colorAttachment; - } else { + } + else + { renderingInfo.colorAttachmentCount = 0; renderingInfo.pColorAttachments = nullptr; } - if (depthImageView) { + 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; renderingInfo.pDepthAttachment = &depthAttachment; - } else { + } + else + { renderingInfo.pDepthAttachment = nullptr; } cmd.beginRendering(renderingInfo); } -void VulkanRenderer::drawFrame() { +void VulkanRenderer::drawFrame() +{ handleSwapchainResizing(); uint32_t imageIndex; - if (!m_frameManager->beginFrame(m_swapchain->get(), imageIndex)) { + if (!m_frameManager->beginFrame(m_swapchain->get(), imageIndex)) + { return; // Swapchain out-of-date } - const auto ¤tFrame = 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& 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::Image sceneViewImage = m_sceneViewImages[frameIdx]->get(); + 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::Image sceneViewImage = m_sceneViewImages[frameIdx]->get(); SceneUBO sceneData{}; const auto aspect = static_cast(width) / static_cast(height); @@ -254,8 +278,7 @@ void VulkanRenderer::drawFrame() { vk::PipelineStageFlagBits::eEarlyFragmentTests, vk::AccessFlagBits::eNone, vk::AccessFlagBits::eDepthStencilAttachmentWrite, - vk::ImageAspectFlagBits::eDepth - ); + vk::ImageAspectFlagBits::eDepth); beginDynamicRendering(cmd, nullptr, depthView, extent, false, true); utils::setupViewportAndScissor(cmd, extent); @@ -271,10 +294,10 @@ void VulkanRenderer::drawFrame() { cmd, msaaImage, vk::ImageLayout::eColorAttachmentOptimal, - vk::PipelineStageFlagBits::eTopOfPipe, // Source Stage + vk::PipelineStageFlagBits::eTopOfPipe, // Source Stage vk::PipelineStageFlagBits::eColorAttachmentOutput, // Destination Stage - {}, // Source Access - vk::AccessFlagBits::eColorAttachmentWrite // Destination Access + {}, // Source Access + vk::AccessFlagBits::eColorAttachmentWrite // Destination Access ); beginDynamicRendering(cmd, msaaView, depthView, extent, true, false); @@ -296,8 +319,7 @@ void VulkanRenderer::drawFrame() { vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eTransfer, vk::AccessFlagBits::eColorAttachmentWrite, - vk::AccessFlagBits::eTransferRead - ); + vk::AccessFlagBits::eTransferRead); // Transition the Resolve image to TRANSFER_DST_OPTIMAL to be written to by the resolve command. m_imageStateTracker.transition( @@ -307,8 +329,7 @@ void VulkanRenderer::drawFrame() { vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTransfer, {}, - vk::AccessFlagBits::eTransferWrite - ); + vk::AccessFlagBits::eTransferWrite); utils::resolveMSAAImageTo(cmd, msaaImage, resolveImage, width, height); @@ -323,8 +344,7 @@ void VulkanRenderer::drawFrame() { vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, vk::AccessFlagBits::eTransferWrite, - vk::AccessFlagBits::eShaderRead - ); + vk::AccessFlagBits::eShaderRead); // Transition the Swapchain image to COLOR_ATTACHMENT_OPTIMAL so we can render the composite result to it. m_imageStateTracker.transition( @@ -334,17 +354,16 @@ void VulkanRenderer::drawFrame() { vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eColorAttachmentOutput, {}, - vk::AccessFlagBits::eColorAttachmentWrite - ); + 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 + 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( @@ -357,7 +376,6 @@ void VulkanRenderer::drawFrame() { vk::AccessFlagBits::eDepthStencilAttachmentRead, vk::ImageAspectFlagBits::eDepth); - beginDynamicRendering(cmd, m_sceneViewViews[frameIdx], nullptr, extent, true); utils::setupViewportAndScissor(cmd, extent); @@ -391,8 +409,7 @@ void VulkanRenderer::drawFrame() { vk::DescriptorType::eUniformBuffer, nullptr, &compositeBufferInfo, - nullptr - }, + nullptr}, vk::WriteDescriptorSet{ m_compositeDescriptorSet->getCurrentSet(frameIdx), 2, @@ -400,9 +417,7 @@ void VulkanRenderer::drawFrame() { 1, vk::DescriptorType::eCombinedImageSampler, &depthImageInfo, - nullptr - } - }; + nullptr}}; m_compositeDescriptorSet->updateSet(writes); cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_compositePipeline->get()); @@ -418,8 +433,7 @@ void VulkanRenderer::drawFrame() { vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader, vk::AccessFlagBits::eColorAttachmentWrite, - vk::AccessFlagBits::eShaderRead - ); + vk::AccessFlagBits::eShaderRead); // UI pass m_imageStateTracker.transition( @@ -429,8 +443,7 @@ void VulkanRenderer::drawFrame() { vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eColorAttachmentOutput, {}, - vk::AccessFlagBits::eColorAttachmentWrite - ); + vk::AccessFlagBits::eColorAttachmentWrite); // --- 4. UI Pass --- // The UI is rendered on top of the composited scene. @@ -450,44 +463,43 @@ void VulkanRenderer::drawFrame() { vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eBottomOfPipe, vk::AccessFlagBits::eColorAttachmentWrite, - {} - ); + {}); endCommandBuffer(cmd); submitAndPresent(imageIndex); } -void VulkanRenderer::createMSAAImage() { +void VulkanRenderer::createMSAAImage() +{ vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e4; // set format to HDR capable space vk::Format format = vk::Format::eR16G16B16A16Sfloat; ; - constexpr vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | - vk::ImageUsageFlagBits::eInputAttachment | - vk::ImageUsageFlagBits::eTransferSrc; + constexpr vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment | vk::ImageUsageFlagBits::eTransferSrc; vk::ImageCreateInfo imageInfo{}; - imageInfo.imageType = vk::ImageType::e2D; - imageInfo.extent.width = m_swapchain->getExtent().width; + imageInfo.imageType = vk::ImageType::e2D; + imageInfo.extent.width = m_swapchain->getExtent().width; imageInfo.extent.height = m_swapchain->getExtent().height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = format; - imageInfo.tiling = vk::ImageTiling::eOptimal; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = vk::ImageTiling::eOptimal; imageInfo.initialLayout = vk::ImageLayout::eUndefined; - imageInfo.usage = usage; - imageInfo.samples = msaaSamples; - imageInfo.sharingMode = vk::SharingMode::eExclusive; + imageInfo.usage = usage; + imageInfo.samples = msaaSamples; + imageInfo.sharingMode = vk::SharingMode::eExclusive; size_t framesInFlight = m_frameManager->getFramesInFlightCount(); m_msaaImages.resize(framesInFlight); m_msaaColorViews.resize(framesInFlight); - for (size_t i = 0; i < framesInFlight; ++i) { + for (size_t i = 0; i < framesInFlight; ++i) + { // Create the Image object m_msaaImages[i] = std::make_unique(*m_allocator, imageInfo, VMA_MEMORY_USAGE_GPU_ONLY); @@ -495,41 +507,42 @@ void VulkanRenderer::createMSAAImage() { m_imageStateTracker.recordState(m_msaaImages[i]->get(), vk::ImageLayout::eUndefined); vk::ImageViewCreateInfo viewInfo{}; - viewInfo.image = m_msaaImages[i]->get(); // Get the VkImage from your new Image object + viewInfo.image = m_msaaImages[i]->get(); // Get the VkImage from your new Image object viewInfo.viewType = vk::ImageViewType::e2D; - viewInfo.format = format; - viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; + viewInfo.subresourceRange.layerCount = 1; m_msaaColorViews[i] = m_context->device().createImageView(viewInfo); } } -void VulkanRenderer::createResolveImages() { - vk::Format format = vk::Format::eR16G16B16A16Sfloat; // Match your swapchain/attachment format +void VulkanRenderer::createResolveImages() +{ + vk::Format format = vk::Format::eR16G16B16A16Sfloat; // Match your swapchain/attachment format vk::Extent2D extent = m_swapchain->getExtent(); size_t framesInFlight = m_frameManager->getFramesInFlightCount(); m_resolveImages.resize(framesInFlight); m_resolveViews.resize(framesInFlight); - for (size_t i = 0; i < framesInFlight; ++i) { + for (size_t i = 0; i < framesInFlight; ++i) + { vk::ImageCreateInfo imageInfo{}; - imageInfo.imageType = vk::ImageType::e2D; - imageInfo.extent.width = extent.width; + imageInfo.imageType = vk::ImageType::e2D; + imageInfo.extent.width = extent.width; imageInfo.extent.height = extent.height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = format; - imageInfo.tiling = vk::ImageTiling::eOptimal; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = vk::ImageTiling::eOptimal; imageInfo.initialLayout = vk::ImageLayout::eUndefined; - imageInfo.usage = vk::ImageUsageFlagBits::eColorAttachment | - vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst; - imageInfo.samples = vk::SampleCountFlagBits::e1; // Resolve is NOT multisampled! + imageInfo.usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst; + imageInfo.samples = vk::SampleCountFlagBits::e1; // Resolve is NOT multisampled! imageInfo.sharingMode = vk::SharingMode::eExclusive; // Create the Image (assume you have your own Image wrapper e.g. using VMA) @@ -539,26 +552,28 @@ void VulkanRenderer::createResolveImages() { m_imageStateTracker.recordState(m_resolveImages[i]->get(), vk::ImageLayout::eUndefined); vk::ImageViewCreateInfo viewInfo{}; - viewInfo.image = m_resolveImages[i]->get(); - viewInfo.viewType = vk::ImageViewType::e2D; - viewInfo.format = format; - viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; + viewInfo.image = m_resolveImages[i]->get(); + viewInfo.viewType = vk::ImageViewType::e2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; + viewInfo.subresourceRange.layerCount = 1; m_resolveViews[i] = m_context->device().createImageView(viewInfo); } } -void VulkanRenderer::createSceneViewImages() { - vk::Format format = m_swapchain->getFormat(); +void VulkanRenderer::createSceneViewImages() +{ + vk::Format format = m_swapchain->getFormat(); vk::Extent2D extent = m_swapchain->getExtent(); size_t framesInFlight = m_frameManager->getFramesInFlightCount(); // destroy old resources if recreating - for (size_t i = 0; i < m_sceneViewViews.size(); ++i) { + for (size_t i = 0; i < m_sceneViewViews.size(); ++i) + { m_context->device().destroyImageView(m_sceneViewViews[i]); } m_sceneViewImages.clear(); @@ -567,21 +582,20 @@ void VulkanRenderer::createSceneViewImages() { m_sceneViewImages.resize(framesInFlight); m_sceneViewViews.resize(framesInFlight); - for (size_t i = 0; i < framesInFlight; ++i) { + for (size_t i = 0; i < framesInFlight; ++i) + { vk::ImageCreateInfo imageInfo{}; - imageInfo.imageType = vk::ImageType::e2D; - imageInfo.extent.width = extent.width; + imageInfo.imageType = vk::ImageType::e2D; + imageInfo.extent.width = extent.width; imageInfo.extent.height = extent.height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = format; - imageInfo.tiling = vk::ImageTiling::eOptimal; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = vk::ImageTiling::eOptimal; imageInfo.initialLayout = vk::ImageLayout::eUndefined; - imageInfo.usage = vk::ImageUsageFlagBits::eColorAttachment | - vk::ImageUsageFlagBits::eSampled | - vk::ImageUsageFlagBits::eTransferSrc; - imageInfo.samples = vk::SampleCountFlagBits::e1; + imageInfo.usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferSrc; + imageInfo.samples = vk::SampleCountFlagBits::e1; imageInfo.sharingMode = vk::SharingMode::eExclusive; m_sceneViewImages[i] = std::make_unique(*m_allocator, imageInfo, VMA_MEMORY_USAGE_GPU_ONLY); @@ -589,53 +603,58 @@ void VulkanRenderer::createSceneViewImages() { m_imageStateTracker.recordState(m_sceneViewImages[i]->get(), vk::ImageLayout::eUndefined); vk::ImageViewCreateInfo viewInfo{}; - viewInfo.image = m_sceneViewImages[i]->get(); - viewInfo.viewType = vk::ImageViewType::e2D; - viewInfo.format = format; - viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; + viewInfo.image = m_sceneViewImages[i]->get(); + viewInfo.viewType = vk::ImageViewType::e2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; + viewInfo.subresourceRange.layerCount = 1; m_sceneViewViews[i] = m_context->device().createImageView(viewInfo); } } -void VulkanRenderer::createSampler() { +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.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; + 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() { +void VulkanRenderer::createDescriptorSets() +{ const auto framesInFlight = m_frameManager->getFramesInFlightCount(); m_sceneViewImageDescriptorSets.resize(framesInFlight); - for (int i = 0; i < framesInFlight; ++i) { + 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; // Common depth format - vk::Extent2D extent = m_swapchain->getExtent(); - size_t framesInFlight = m_frameManager->getFramesInFlightCount(); +void VulkanRenderer::createDepthImages() +{ + vk::Format format = vk::Format::eD32Sfloat; // Common depth format + vk::Extent2D extent = m_swapchain->getExtent(); + size_t framesInFlight = m_frameManager->getFramesInFlightCount(); - for (size_t i = 0; i < m_depthImages.size(); ++i) { + for (size_t i = 0; i < m_depthImages.size(); ++i) + { m_context->device().destroyImageView(m_depthViews[i]); } m_depthImages.clear(); @@ -643,20 +662,21 @@ void VulkanRenderer::createDepthImages() { m_depthImages.resize(framesInFlight); m_depthViews.resize(framesInFlight); - for (size_t i = 0; i < framesInFlight; ++i) { + for (size_t i = 0; i < framesInFlight; ++i) + { vk::ImageCreateInfo imageInfo{}; - imageInfo.imageType = vk::ImageType::e2D; - imageInfo.extent.width = extent.width; + imageInfo.imageType = vk::ImageType::e2D; + imageInfo.extent.width = extent.width; imageInfo.extent.height = extent.height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = format; - imageInfo.tiling = vk::ImageTiling::eOptimal; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = vk::ImageTiling::eOptimal; imageInfo.initialLayout = vk::ImageLayout::eUndefined; imageInfo.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled; - imageInfo.samples = vk::SampleCountFlagBits::e4; + imageInfo.samples = vk::SampleCountFlagBits::e4; imageInfo.sharingMode = vk::SharingMode::eExclusive; m_depthImages[i] = @@ -664,36 +684,31 @@ void VulkanRenderer::createDepthImages() { m_imageStateTracker.recordState(m_depthImages[i]->get(), vk::ImageLayout::eUndefined); vk::ImageViewCreateInfo viewInfo{}; - viewInfo.image = m_depthImages[i]->get(); - viewInfo.viewType = vk::ImageViewType::e2D; - viewInfo.format = format; - viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eDepth; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; + viewInfo.image = m_depthImages[i]->get(); + viewInfo.viewType = vk::ImageViewType::e2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eDepth; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; + viewInfo.subresourceRange.layerCount = 1; m_depthViews[i] = m_context->device().createImageView(viewInfo); } } -void VulkanRenderer::createDepthPipelineAndDescriptorSets() { - - const std::string depthVertexShaderPath = m_config.vertShaderPath; - const std::string emptyFragShaderPath = ""; // no fragment shader +void VulkanRenderer::createDepthPipelineAndDescriptorSets() +{ - std::vector setLayouts = { - m_descriptorSet->getLayout()}; + 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 - .setDepthAttachment(vk::Format::eD32Sfloat, true) // depth test and write enabled - .setDescriptorSetLayouts(setLayouts) - .setMultisample(4) - .setFrontFace(vk::FrontFace::eClockwise) // Match main geometry pipeline - .build(); - - + .setVertexShader(m_config.vertShaderPath) + // No fragment shader, we only want depth output + .setDepthAttachment(vk::Format::eD32Sfloat, true) // depth test and write enabled + .setDescriptorSetLayouts(setLayouts) + .setMultisample(4) + .setFrontFace(vk::FrontFace::eClockwise) // Match main geometry pipeline + .build(); } } // namespace reactor From 897587b32080c2559c87a025bda0b8c8fd825ed9 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Wed, 16 Jul 2025 23:05:25 -0500 Subject: [PATCH 11/26] add a builder pattern on the Images --- CMakeLists.txt | 4 ++- src/vulkan/ImageUtils.cpp | 61 +++++++++++++++++++++++++++++++++++ src/vulkan/ImageUtils.hpp | 36 +++++++++++++++++++++ src/vulkan/VulkanRenderer.cpp | 46 +++++++------------------- 4 files changed, 111 insertions(+), 36 deletions(-) create mode 100644 src/vulkan/ImageUtils.cpp create mode 100644 src/vulkan/ImageUtils.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index aaf6f08..15af8bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,9 @@ add_executable(Reactor src/core/main.cpp src/core/OrbitController.hpp src/core/Application.cpp src/core/Application.hpp - src/vulkan/Vertex.hpp) + src/vulkan/Vertex.hpp + src/vulkan/ImageUtils.hpp + src/vulkan/ImageUtils.cpp) target_link_libraries(Reactor PRIVATE Vulkan::Vulkan Vulkan::Headers glfw fmt::fmt spdlog::spdlog GPUOpen::VulkanMemoryAllocator glm::glm) target_link_libraries(Reactor PRIVATE imgui::imgui) diff --git a/src/vulkan/ImageUtils.cpp b/src/vulkan/ImageUtils.cpp new file mode 100644 index 0000000..3014158 --- /dev/null +++ b/src/vulkan/ImageUtils.cpp @@ -0,0 +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}; +} + +} // namespace reactor::utils \ No newline at end of file diff --git a/src/vulkan/ImageUtils.hpp b/src/vulkan/ImageUtils.hpp new file mode 100644 index 0000000..9206306 --- /dev/null +++ b/src/vulkan/ImageUtils.hpp @@ -0,0 +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{}; +}; +} // namespace reactor::utils \ No newline at end of file diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 365d505..0308c6d 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -2,6 +2,7 @@ #include "../core/Uniforms.hpp" #include "../core/Window.hpp" +#include "ImageUtils.hpp" #include "VulkanUtils.hpp" #include @@ -472,51 +473,26 @@ void VulkanRenderer::drawFrame() void VulkanRenderer::createMSAAImage() { - vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e4; - - // set format to HDR capable space vk::Format format = vk::Format::eR16G16B16A16Sfloat; - ; - constexpr vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment | vk::ImageUsageFlagBits::eTransferSrc; - - vk::ImageCreateInfo imageInfo{}; - imageInfo.imageType = vk::ImageType::e2D; - imageInfo.extent.width = m_swapchain->getExtent().width; - imageInfo.extent.height = m_swapchain->getExtent().height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = format; - imageInfo.tiling = vk::ImageTiling::eOptimal; - imageInfo.initialLayout = vk::ImageLayout::eUndefined; - imageInfo.usage = usage; - imageInfo.samples = msaaSamples; - imageInfo.sharingMode = vk::SharingMode::eExclusive; - + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment + | vk::ImageUsageFlagBits::eInputAttachment + | vk::ImageUsageFlagBits::eTransferSrc; 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) { - // Create the Image object - m_msaaImages[i] = - std::make_unique(*m_allocator, imageInfo, VMA_MEMORY_USAGE_GPU_ONLY); + 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); - - vk::ImageViewCreateInfo viewInfo{}; - viewInfo.image = m_msaaImages[i]->get(); // Get the VkImage from your new Image object - viewInfo.viewType = vk::ImageViewType::e2D; - viewInfo.format = format; - viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - m_msaaColorViews[i] = m_context->device().createImageView(viewInfo); } } From 67184ea9d2273f1e637d4b719c7e2e6af2b6cfe9 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Thu, 17 Jul 2025 08:21:38 -0500 Subject: [PATCH 12/26] more builder for images --- src/vulkan/VulkanRenderer.cpp | 141 +++++++++------------------------- 1 file changed, 38 insertions(+), 103 deletions(-) diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 0308c6d..345481a 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -498,97 +498,51 @@ void VulkanRenderer::createMSAAImage() void VulkanRenderer::createResolveImages() { - vk::Format format = vk::Format::eR16G16B16A16Sfloat; // Match your swapchain/attachment format - vk::Extent2D extent = m_swapchain->getExtent(); - + vk::Format format = vk::Format::eR16G16B16A16Sfloat; + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst; 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) - { - vk::ImageCreateInfo imageInfo{}; - imageInfo.imageType = vk::ImageType::e2D; - imageInfo.extent.width = extent.width; - imageInfo.extent.height = extent.height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = format; - imageInfo.tiling = vk::ImageTiling::eOptimal; - imageInfo.initialLayout = vk::ImageLayout::eUndefined; - imageInfo.usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst; - imageInfo.samples = vk::SampleCountFlagBits::e1; // Resolve is NOT multisampled! - imageInfo.sharingMode = vk::SharingMode::eExclusive; - - // Create the Image (assume you have your own Image wrapper e.g. using VMA) - m_resolveImages[i] = - std::make_unique(*m_allocator, imageInfo, VMA_MEMORY_USAGE_GPU_ONLY); + for (size_t i = 0; i < framesInFlight; ++i) { + auto built = builder.setFormat(format) + .setUsage(usage) + .build(); // Defaults to e1 samples - m_imageStateTracker.recordState(m_resolveImages[i]->get(), vk::ImageLayout::eUndefined); + m_resolveImages[i] = std::move(built.image); + m_resolveViews[i] = built.view; - vk::ImageViewCreateInfo viewInfo{}; - viewInfo.image = m_resolveImages[i]->get(); - viewInfo.viewType = vk::ImageViewType::e2D; - viewInfo.format = format; - viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - m_resolveViews[i] = m_context->device().createImageView(viewInfo); + m_imageStateTracker.recordState(m_resolveImages[i]->get(), vk::ImageLayout::eUndefined); } } void VulkanRenderer::createSceneViewImages() { vk::Format format = m_swapchain->getFormat(); - vk::Extent2D extent = m_swapchain->getExtent(); - + 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) - { + // 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) - { - vk::ImageCreateInfo imageInfo{}; - imageInfo.imageType = vk::ImageType::e2D; - imageInfo.extent.width = extent.width; - imageInfo.extent.height = extent.height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = format; - imageInfo.tiling = vk::ImageTiling::eOptimal; - imageInfo.initialLayout = vk::ImageLayout::eUndefined; - imageInfo.usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferSrc; - imageInfo.samples = vk::SampleCountFlagBits::e1; - imageInfo.sharingMode = vk::SharingMode::eExclusive; - - m_sceneViewImages[i] = std::make_unique(*m_allocator, imageInfo, VMA_MEMORY_USAGE_GPU_ONLY); + for (size_t i = 0; i < framesInFlight; ++i) { + auto built = builder.setFormat(format) + .setUsage(usage) + .build(); // Defaults to e1 samples and color aspect - m_imageStateTracker.recordState(m_sceneViewImages[i]->get(), vk::ImageLayout::eUndefined); + m_sceneViewImages[i] = std::move(built.image); + m_sceneViewViews[i] = built.view; - vk::ImageViewCreateInfo viewInfo{}; - viewInfo.image = m_sceneViewImages[i]->get(); - viewInfo.viewType = vk::ImageViewType::e2D; - viewInfo.format = format; - viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - m_sceneViewViews[i] = m_context->device().createImageView(viewInfo); + m_imageStateTracker.recordState(m_sceneViewImages[i]->get(), vk::ImageLayout::eUndefined); } } @@ -625,51 +579,32 @@ void VulkanRenderer::createDescriptorSets() } void VulkanRenderer::createDepthImages() { - vk::Format format = vk::Format::eD32Sfloat; // Common depth format - vk::Extent2D extent = m_swapchain->getExtent(); + vk::Format format = vk::Format::eD32Sfloat; + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled; size_t framesInFlight = m_frameManager->getFramesInFlightCount(); - for (size_t i = 0; i < m_depthImages.size(); ++i) - { + // 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) - { - vk::ImageCreateInfo imageInfo{}; - imageInfo.imageType = vk::ImageType::e2D; - imageInfo.extent.width = extent.width; - imageInfo.extent.height = extent.height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = format; - imageInfo.tiling = vk::ImageTiling::eOptimal; - imageInfo.initialLayout = vk::ImageLayout::eUndefined; - imageInfo.usage = - vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled; - imageInfo.samples = vk::SampleCountFlagBits::e4; - imageInfo.sharingMode = vk::SharingMode::eExclusive; - - m_depthImages[i] = - std::make_unique(*m_allocator, imageInfo, VMA_MEMORY_USAGE_GPU_ONLY); - m_imageStateTracker.recordState(m_depthImages[i]->get(), vk::ImageLayout::eUndefined); - vk::ImageViewCreateInfo viewInfo{}; - viewInfo.image = m_depthImages[i]->get(); - viewInfo.viewType = vk::ImageViewType::e2D; - viewInfo.format = format; - viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eDepth; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - m_depthViews[i] = m_context->device().createImageView(viewInfo); + 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); } } void VulkanRenderer::createDepthPipelineAndDescriptorSets() From 6fec8bdde421d0e11a13623ab0b31ddd1e37bbb3 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Mon, 21 Jul 2025 16:49:54 -0500 Subject: [PATCH 13/26] cubes appear! --- .clang-format | 2 +- CMakeLists.txt | 6 +- shaders/triangle.vert | 84 ++++++-------------------- src/imgui/Imgui.cpp | 83 +++++++++++++------------- src/vulkan/Allocator.cpp | 91 ++++++++++++++++++++++++----- src/vulkan/Allocator.hpp | 46 ++++++++++++--- src/vulkan/Buffer.cpp | 4 +- src/vulkan/Buffer.hpp | 2 +- src/vulkan/DescriptorSet.cpp | 2 +- src/vulkan/Image.cpp | 4 +- src/vulkan/Mesh.cpp | 102 ++++++++++++++++++++++++++++++++ src/vulkan/Mesh.hpp | 46 +++++++++++++++ src/vulkan/MeshGenerators.cpp | 107 ++++++++++++++++++++++++++++++++++ src/vulkan/MeshGenerators.hpp | 12 ++++ src/vulkan/Pipeline.cpp | 13 +++++ src/vulkan/Pipeline.hpp | 2 + src/vulkan/UniformManager.hpp | 6 +- src/vulkan/VulkanRenderer.cpp | 75 ++++++++++++++++++------ src/vulkan/VulkanRenderer.hpp | 88 ++++++++++++++++------------ 19 files changed, 580 insertions(+), 195 deletions(-) create mode 100644 src/vulkan/Mesh.cpp create mode 100644 src/vulkan/Mesh.hpp create mode 100644 src/vulkan/MeshGenerators.cpp create mode 100644 src/vulkan/MeshGenerators.hpp diff --git a/.clang-format b/.clang-format index b04f019..b4615d0 100644 --- a/.clang-format +++ b/.clang-format @@ -4,7 +4,7 @@ BasedOnStyle: LLVM IndentWidth: 4 TabWidth: 4 UseTab: Never -ColumnLimit: 0 # No forced wrapping; useful for long VulkanHpp calls +ColumnLimit: 120 # No forced wrapping; useful for long VulkanHpp calls AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false diff --git a/CMakeLists.txt b/CMakeLists.txt index 15af8bf..b18adbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,11 @@ add_executable(Reactor src/core/main.cpp src/core/Application.hpp src/vulkan/Vertex.hpp src/vulkan/ImageUtils.hpp - src/vulkan/ImageUtils.cpp) + src/vulkan/ImageUtils.cpp + src/vulkan/Mesh.cpp + src/vulkan/Mesh.hpp + src/vulkan/MeshGenerators.hpp + src/vulkan/MeshGenerators.cpp) target_link_libraries(Reactor PRIVATE Vulkan::Vulkan Vulkan::Headers glfw fmt::fmt spdlog::spdlog GPUOpen::VulkanMemoryAllocator glm::glm) target_link_libraries(Reactor PRIVATE imgui::imgui) diff --git a/shaders/triangle.vert b/shaders/triangle.vert index 120cd7a..e0c6722 100644 --- a/shaders/triangle.vert +++ b/shaders/triangle.vert @@ -1,74 +1,26 @@ #version 450 -layout(set = 0, binding = 0) uniform UBO { +layout(location = 0) in vec3 inPos; +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec3 inColor; +layout(location = 3) in vec2 inTexCoord; + +layout(binding = 0) uniform SceneUBO { mat4 view; mat4 projection; -}; - -layout(location = 0) out vec3 vColor; - -void main() { - // Hardcoded cube made of 12 triangles (36 vertices) - vec3 positions[36] = vec3[]( - // Front face - vec3(-0.5, -0.5, 0.5), - vec3( 0.5, -0.5, 0.5), - vec3( 0.5, 0.5, 0.5), - vec3(-0.5, -0.5, 0.5), - vec3( 0.5, 0.5, 0.5), - vec3(-0.5, 0.5, 0.5), - - // Back face - vec3(-0.5, -0.5, -0.5), - vec3( 0.5, 0.5, -0.5), - vec3( 0.5, -0.5, -0.5), - vec3(-0.5, -0.5, -0.5), - vec3(-0.5, 0.5, -0.5), - vec3( 0.5, 0.5, -0.5), +} ubo; - // Left face - vec3(-0.5, -0.5, -0.5), - vec3(-0.5, -0.5, 0.5), - vec3(-0.5, 0.5, 0.5), - vec3(-0.5, -0.5, -0.5), - vec3(-0.5, 0.5, 0.5), - vec3(-0.5, 0.5, -0.5), +layout(push_constant) uniform PushConstants { + mat4 model; +} push; - // Right face - vec3( 0.5, -0.5, -0.5), - vec3( 0.5, 0.5, 0.5), - vec3( 0.5, -0.5, 0.5), - vec3( 0.5, -0.5, -0.5), - vec3( 0.5, 0.5, -0.5), - vec3( 0.5, 0.5, 0.5), +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec3 fragNormal; +layout(location = 2) out vec2 fragTexCoord; - // Top face - vec3(-0.5, 0.5, -0.5), - vec3(-0.5, 0.5, 0.5), - vec3( 0.5, 0.5, 0.5), - vec3(-0.5, 0.5, -0.5), - vec3( 0.5, 0.5, 0.5), - vec3( 0.5, 0.5, -0.5), - - // Bottom face - vec3(-0.5, -0.5, -0.5), - vec3( 0.5, -0.5, 0.5), - vec3(-0.5, -0.5, 0.5), - vec3(-0.5, -0.5, -0.5), - vec3( 0.5, -0.5, -0.5), - vec3( 0.5, -0.5, 0.5) - ); - - vec3 colors[6] = vec3[]( - vec3(1.0, 0.0, 0.0), - vec3(0.0, 1.0, 0.0), - vec3(0.0, 0.0, 1.0), - vec3(1.0, 1.0, 0.0), - vec3(1.0, 0.0, 1.0), - vec3(0.0, 1.0, 1.0) - ); - - int face = gl_VertexIndex / 6; // Each face has 6 vertices - gl_Position = projection * view * vec4(positions[gl_VertexIndex], 1.0); - vColor = colors[face]; +void main() { + gl_Position = ubo.projection * ubo.view * push.model * vec4(inPos, 1.0); + fragNormal = mat3(transpose(inverse(push.model))) * inNormal; // Correct for non-uniform scales + fragColor = inColor; + fragTexCoord = inTexCoord; } \ No newline at end of file diff --git a/src/imgui/Imgui.cpp b/src/imgui/Imgui.cpp index 79df673..be34865 100644 --- a/src/imgui/Imgui.cpp +++ b/src/imgui/Imgui.cpp @@ -126,53 +126,58 @@ void Imgui::ShowDockspace() { void Imgui::ShowSceneView() { ImGui::Begin("Scene View"); - const ImVec2 size = ImGui::GetContentRegionAvail(); if (m_sceneImguiId) { - 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)) { + 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::MouseButtonPressed; - e.mouseButton.button = btn; - e.mouseButton.x = static_cast(io.MousePos.x); - e.mouseButton.y = static_cast(io.MousePos.y); + e.type = EventType::MouseMoved; + e.mouseMove.x = static_cast(io.MousePos.x); + e.mouseMove.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); + + // 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"); diff --git a/src/vulkan/Allocator.cpp b/src/vulkan/Allocator.cpp index 84187a8..cbe2667 100644 --- a/src/vulkan/Allocator.cpp +++ b/src/vulkan/Allocator.cpp @@ -6,22 +6,87 @@ #include -namespace reactor { +#include "Buffer.hpp" - Allocator::Allocator(vk::PhysicalDevice physicalDevice, vk::Device device, vk::Instance instance) { - VmaAllocatorCreateInfo allocatorInfo = {}; - allocatorInfo.physicalDevice = physicalDevice; - allocatorInfo.device = device; - allocatorInfo.instance = instance; +namespace reactor +{ - vmaCreateAllocator(&allocatorInfo, &m_allocator); +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; - spdlog::info("Allocator created"); - } + vmaCreateAllocator(&allocatorInfo, &m_allocator); - Allocator::~Allocator() { - if (m_allocator) vmaDestroyAllocator(m_allocator); - } + spdlog::info("Allocator created"); +} +Allocator::~Allocator() +{ + if (m_allocator) + vmaDestroyAllocator(m_allocator); +} -} // reactor \ No newline at end of file +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 3564913..9abc66e 100644 --- a/src/vulkan/Allocator.hpp +++ b/src/vulkan/Allocator.hpp @@ -1,26 +1,54 @@ #pragma once -#include #include +#include -namespace reactor { +namespace reactor +{ +class Buffer; -class Allocator { +class Allocator +{ public: - Allocator(vk::PhysicalDevice physicalDevice, vk::Device device, vk::Instance instance); + Allocator(vk::PhysicalDevice physicalDevice, vk::Device device, vk::Instance instance, vk::Queue graphicsQueue, uint32_t graphicsQueueFamilyIndex); ~Allocator(); - VmaAllocator get() const { return m_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: - VmaAllocator m_allocator = VK_NULL_HANDLE; -}; - -} // reactor + 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 04dbeec..d6bdccc 100644 --- a/src/vulkan/Buffer.cpp +++ b/src/vulkan/Buffer.cpp @@ -20,7 +20,7 @@ namespace reactor { allocInfo.usage = memoryUsage; const VkResult result = vmaCreateBuffer( - m_allocator.get(), + m_allocator.getAllocator(), reinterpret_cast(&bufferInfo), &allocInfo, reinterpret_cast(&m_buffer), @@ -34,7 +34,7 @@ namespace reactor { Buffer::~Buffer() { if (m_buffer && m_allocation) { - vmaDestroyBuffer(m_allocator.get(), m_buffer, m_allocation); + vmaDestroyBuffer(m_allocator.getAllocator(), m_buffer, m_allocation); spdlog::info("Buffer {} destroyed", m_name.c_str()); diff --git a/src/vulkan/Buffer.hpp b/src/vulkan/Buffer.hpp index 1a65670..a13bc77 100644 --- a/src/vulkan/Buffer.hpp +++ b/src/vulkan/Buffer.hpp @@ -18,7 +18,7 @@ class Buffer { Buffer(const Buffer&) = delete; Buffer& operator=(const Buffer&) = delete; - [[nodiscard]] vk::Buffer buffer() const { return m_buffer; } + [[nodiscard]] vk::Buffer getHandle() const { return m_buffer; } [[nodiscard]] VmaAllocation allocation() const { return m_allocation; } [[nodiscard]] vk::DeviceSize size() const { return m_size; } diff --git a/src/vulkan/DescriptorSet.cpp b/src/vulkan/DescriptorSet.cpp index d3830dc..c38fae8 100644 --- a/src/vulkan/DescriptorSet.cpp +++ b/src/vulkan/DescriptorSet.cpp @@ -41,7 +41,7 @@ namespace reactor { void DescriptorSet::updateUniformBuffer(size_t frame, const Buffer &buffer) { vk::DescriptorBufferInfo bufferInfo; - bufferInfo.buffer = buffer.buffer(); + bufferInfo.buffer = buffer.getHandle(); bufferInfo.offset = 0; bufferInfo.range = buffer.size(); diff --git a/src/vulkan/Image.cpp b/src/vulkan/Image.cpp index faef1e5..2cf5e30 100644 --- a/src/vulkan/Image.cpp +++ b/src/vulkan/Image.cpp @@ -12,7 +12,7 @@ namespace reactor { allocInfo.usage = memoryUsage; const VkResult result = vmaCreateImage( - m_allocator.get(), + m_allocator.getAllocator(), reinterpret_cast(&imageInfo), &allocInfo, reinterpret_cast(&m_image), @@ -59,7 +59,7 @@ namespace reactor { void Image::cleanup() { if (m_image && m_allocation) { - vmaDestroyImage(m_allocator.get(), 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; diff --git a/src/vulkan/Mesh.cpp b/src/vulkan/Mesh.cpp new file mode 100644 index 0000000..ee1b3e3 --- /dev/null +++ b/src/vulkan/Mesh.cpp @@ -0,0 +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; +} + +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Mesh.hpp b/src/vulkan/Mesh.hpp new file mode 100644 index 0000000..66041e3 --- /dev/null +++ b/src/vulkan/Mesh.hpp @@ -0,0 +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 diff --git a/src/vulkan/MeshGenerators.cpp b/src/vulkan/MeshGenerators.cpp new file mode 100644 index 0000000..53087c5 --- /dev/null +++ b/src/vulkan/MeshGenerators.cpp @@ -0,0 +1,107 @@ +#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() +{ + return { + // Front + {{-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}}, + // Back + {{-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {0.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 0.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}}; +} + +std::vector generateUnitCubeIndices() +{ + return { + // Front + 0, + 1, + 2, + 0, + 2, + 3, + // Back + 5, + 4, + 7, + 5, + 7, + 6, + // Left + 4, + 0, + 3, + 4, + 3, + 7, + // Right + 1, + 5, + 6, + 1, + 6, + 2, + // Top + 3, + 2, + 6, + 3, + 6, + 7, + // Bottom + 4, + 5, + 1, + 4, + 1, + 0}; +} +} // namespace reactor diff --git a/src/vulkan/MeshGenerators.hpp b/src/vulkan/MeshGenerators.hpp new file mode 100644 index 0000000..d807b48 --- /dev/null +++ b/src/vulkan/MeshGenerators.hpp @@ -0,0 +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(); + +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Pipeline.cpp b/src/vulkan/Pipeline.cpp index 8c19598..7520625 100644 --- a/src/vulkan/Pipeline.cpp +++ b/src/vulkan/Pipeline.cpp @@ -81,6 +81,13 @@ namespace reactor 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; + } + + std::unique_ptr Pipeline::Builder::build() const { // 1. Shader Stages @@ -100,6 +107,10 @@ namespace reactor // 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); @@ -142,6 +153,8 @@ namespace reactor // 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 diff --git a/src/vulkan/Pipeline.hpp b/src/vulkan/Pipeline.hpp index df9893e..8f9fef4 100644 --- a/src/vulkan/Pipeline.hpp +++ b/src/vulkan/Pipeline.hpp @@ -26,6 +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); [[nodiscard]] std::unique_ptr build() const; @@ -43,6 +44,7 @@ namespace reactor std::vector m_bindings; std::vector m_attributes; + std::vector m_pushRanges; }; ~Pipeline(); diff --git a/src/vulkan/UniformManager.hpp b/src/vulkan/UniformManager.hpp index 471cfdd..06fbcaa 100644 --- a/src/vulkan/UniformManager.hpp +++ b/src/vulkan/UniformManager.hpp @@ -25,9 +25,9 @@ class UniformManager { auto& buffer = m_uniformBuffers.at(name)[frameIndex]; void* mappedData = nullptr; - vmaMapMemory(m_allocator.get(), buffer->allocation(), &mappedData); + vmaMapMemory(m_allocator.getAllocator(), buffer->allocation(), &mappedData); memcpy(mappedData, &data, sizeof(T)); - vmaUnmapMemory(m_allocator.get(), buffer->allocation()); + vmaUnmapMemory(m_allocator.getAllocator(), buffer->allocation()); } // 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->buffer(); + bufferInfo.buffer = buffer->getHandle(); bufferInfo.offset = 0; bufferInfo.range = buffer->size(); return bufferInfo; diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 345481a..237e96b 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -30,12 +30,13 @@ VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Cam createSampler(); createDescriptorSets(); createDepthPipelineAndDescriptorSets(); + initScene(); } 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_allocator = std::make_unique(m_context->physicalDevice(), m_context->device(), m_context->instance(), m_context->graphicsQueue(), m_context->queueFamilies().graphicsFamily.value()); } void VulkanRenderer::createSwapchainAndFrameManager() @@ -65,11 +66,13 @@ void VulkanRenderer::createPipelineAndDescriptors() 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(); const std::vector compositeBindings = { @@ -138,8 +141,16 @@ void VulkanRenderer::bindDescriptorSets(vk::CommandBuffer cmd) void VulkanRenderer::drawGeometry(vk::CommandBuffer cmd) { - // cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_pipeline->get()); - cmd.draw(36, 1, 0, 0); + 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 @@ -486,9 +497,9 @@ void VulkanRenderer::createMSAAImage() for (size_t i = 0; i < framesInFlight; ++i) { auto built = builder.setFormat(format) - .setUsage(usage) - .setSamples(vk::SampleCountFlagBits::e4) - .build(); + .setUsage(usage) + .setSamples(vk::SampleCountFlagBits::e4) + .build(); m_msaaImages[i] = std::move(built.image); m_msaaColorViews[i] = built.view; @@ -506,10 +517,11 @@ void VulkanRenderer::createResolveImages() m_resolveImages.resize(framesInFlight); m_resolveViews.resize(framesInFlight); - for (size_t i = 0; i < framesInFlight; ++i) { + for (size_t i = 0; i < framesInFlight; ++i) + { auto built = builder.setFormat(format) - .setUsage(usage) - .build(); // Defaults to e1 samples + .setUsage(usage) + .build(); // Defaults to e1 samples m_resolveImages[i] = std::move(built.image); m_resolveViews[i] = built.view; @@ -524,7 +536,8 @@ 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 (size_t i = 0; i < m_sceneViewViews.size(); ++i) + { m_context->device().destroyImageView(m_sceneViewViews[i]); } m_sceneViewImages.clear(); @@ -534,10 +547,11 @@ void VulkanRenderer::createSceneViewImages() m_sceneViewImages.resize(framesInFlight); m_sceneViewViews.resize(framesInFlight); - for (size_t i = 0; i < framesInFlight; ++i) { + for (size_t i = 0; i < framesInFlight; ++i) + { auto built = builder.setFormat(format) - .setUsage(usage) - .build(); // Defaults to e1 samples and color aspect + .setUsage(usage) + .build(); // Defaults to e1 samples and color aspect m_sceneViewImages[i] = std::move(built.image); m_sceneViewViews[i] = built.view; @@ -584,7 +598,8 @@ 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 (size_t i = 0; i < m_depthViews.size(); ++i) + { m_context->device().destroyImageView(m_depthViews[i]); } m_depthImages.clear(); @@ -594,12 +609,13 @@ void VulkanRenderer::createDepthImages() m_depthImages.resize(framesInFlight); m_depthViews.resize(framesInFlight); - for (size_t i = 0; i < framesInFlight; ++i) { + for (size_t i = 0; i < framesInFlight; ++i) + { auto built = builder.setFormat(format) - .setUsage(usage) - .setSamples(vk::SampleCountFlagBits::e4) - .setAspectMask(vk::ImageAspectFlagBits::eDepth) - .build(); + .setUsage(usage) + .setSamples(vk::SampleCountFlagBits::e4) + .setAspectMask(vk::ImageAspectFlagBits::eDepth) + .build(); m_depthImages[i] = std::move(built.image); m_depthViews[i] = built.view; @@ -615,11 +631,32 @@ void VulkanRenderer::createDepthPipelineAndDescriptorSets() 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.5f, 0.0f))}); + + // Unit cube mesh (shared for buildings) + auto cubeVerts = generateUnitCubeVertices(); + auto cubeInds = generateUnitCubeIndices(); + auto cubeMesh = std::make_shared(*m_allocator, cubeVerts, cubeInds); + + // Several buildings (example: 3 cuboids with different positions and scales) + m_objects.push_back({cubeMesh, glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 2.0f, 0.0f)) * glm::scale(glm::mat4(1.0f), glm::vec3(4.0f, 4.0f, 4.0f))}); // Square building + m_objects.push_back({cubeMesh, glm::translate(glm::mat4(1.0f), glm::vec3(10.0f, 5.0f, 0.0f)) * glm::scale(glm::mat4(1.0f), glm::vec3(3.0f, 10.0f, 3.0f))}); // Tall thin + m_objects.push_back({cubeMesh, glm::translate(glm::mat4(1.0f), glm::vec3(-5.0f, 3.0f, 5.0f)) * glm::scale(glm::mat4(1.0f), glm::vec3(5.0f, 6.0f, 2.0f))}); // Wide short + // Add more as needed +} + } // namespace reactor diff --git a/src/vulkan/VulkanRenderer.hpp b/src/vulkan/VulkanRenderer.hpp index d415968..1af1078 100644 --- a/src/vulkan/VulkanRenderer.hpp +++ b/src/vulkan/VulkanRenderer.hpp @@ -15,12 +15,16 @@ #include "Swapchain.hpp" #include "UniformManager.hpp" #include "VulkanContext.hpp" +#include "Mesh.hpp" +#include "MeshGenerators.hpp" -namespace reactor { +namespace reactor +{ -struct RendererConfig { - uint32_t windowWidth; - uint32_t windowHeight; +struct RendererConfig +{ + uint32_t windowWidth; + uint32_t windowHeight; std::string windowTitle; std::string vertShaderPath; std::string fragShaderPath; @@ -28,45 +32,54 @@ struct RendererConfig { std::string compositeFragShaderPath; }; -class VulkanRenderer { - public: - VulkanRenderer(const RendererConfig &config, Window &window, Camera &camera); +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(); - 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; +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_imgui; ImageStateTracker m_imageStateTracker; std::vector> m_msaaImages; - std::vector m_msaaColorViews; + std::vector m_msaaColorViews; std::vector> m_resolveImages; - std::vector m_resolveViews; + std::vector m_resolveViews; std::vector> m_sceneViewImages; - std::vector m_sceneViewViews; - std::vector m_sceneViewImageDescriptorSets; + std::vector m_sceneViewViews; + std::vector m_sceneViewImageDescriptorSets; std::vector> m_depthImages; - std::vector m_depthViews; - std::vector m_depthImageDescriptorSets; + std::vector m_depthViews; + std::vector m_depthImageDescriptorSets; + + std::vector m_objects; void createCoreVulkanObjects(); void createSwapchainAndFrameManager(); @@ -79,18 +92,17 @@ class VulkanRenderer { void createDescriptorSets(); void createDepthImages(); void createDepthPipelineAndDescriptorSets(); + void initScene(); - 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 bindDescriptorSets(vk::CommandBuffer cmd); - void drawGeometry(vk::CommandBuffer cmd); - void renderUI(vk::CommandBuffer cmd) const; + 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 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); + void submitAndPresent(uint32_t imageIndex); }; } // namespace reactor From 330a8c61d97c4ef3e9384adfde59603b1183be9b Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Tue, 22 Jul 2025 16:54:14 -0500 Subject: [PATCH 14/26] add model export and loading --- .gitignore | 3 +- CMakeLists.txt | 7 +- shaders/triangle.frag | 12 ++- src/tools/ModelBuilder.cpp | 181 ++++++++++++++++++++++++++++++++++ src/vulkan/MeshGenerators.cpp | 95 +++++++++--------- vcpkg.json | 3 +- 6 files changed, 252 insertions(+), 49 deletions(-) create mode 100644 src/tools/ModelBuilder.cpp diff --git a/.gitignore b/.gitignore index 694a5e2..77e56a9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ build/ cmake-build-debug/ vcpkg_installed/ .idea -resources/ \ No newline at end of file +resources/ +*.fbx \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index b18adbe..c4bff10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ 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_executable(Reactor src/core/main.cpp src/vulkan/VulkanContext.cpp @@ -62,9 +63,13 @@ add_executable(Reactor src/core/main.cpp src/vulkan/Mesh.cpp src/vulkan/Mesh.hpp src/vulkan/MeshGenerators.hpp - src/vulkan/MeshGenerators.cpp) + src/vulkan/MeshGenerators.cpp +) target_link_libraries(Reactor PRIVATE Vulkan::Vulkan Vulkan::Headers glfw fmt::fmt spdlog::spdlog GPUOpen::VulkanMemoryAllocator glm::glm) target_link_libraries(Reactor PRIVATE imgui::imgui) target_precompile_headers(Reactor PRIVATE src/pch.hpp) + +add_executable(BuildModel src/tools/ModelBuilder.cpp) +target_link_libraries(BuildModel assimp::assimp fmt::fmt spdlog::spdlog) diff --git a/shaders/triangle.frag b/shaders/triangle.frag index e99cf82..7e89c28 100644 --- a/shaders/triangle.frag +++ b/shaders/triangle.frag @@ -1,8 +1,16 @@ #version 450 -layout(location = 0) in vec3 vColor; +layout(location = 1) in vec3 vNormal; + +// Final output color layout(location = 0) out vec4 outColor; void main() { - outColor = vec4(vColor, 1.0); + // Normalize the incoming normal vector to ensure unit vector. + vec3 normal = normalize(vNormal); + + // map normal components in (-1, 1) to (0, 1) + vec3 normalColor = (normal + 1.0) * 0.5; + + outColor = vec4(normalColor, 1.0); } \ No newline at end of file diff --git a/src/tools/ModelBuilder.cpp b/src/tools/ModelBuilder.cpp new file mode 100644 index 0000000..c185add --- /dev/null +++ b/src/tools/ModelBuilder.cpp @@ -0,0 +1,181 @@ +#include +#include +#include + +#include + +#include +#include +#include + +// Include the Vertex definition from your project +#include "../vulkan/Vertex.hpp" + +// A structure to hold mesh data, which is what we'll load back in the engine. +struct MeshData { + std::vector vertices; + std::vector indices; +}; + +// 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; +} + +int main() +{ + // Define input and output paths + const std::string inputModel = "../workspace/monkey.obj"; + const std::string outputModel = "./monkey.mesh"; + + // Run the import and export process + if(importAndExport(inputModel, outputModel)) { + // Optional: Test the loader to verify the export + spdlog::info("--- Verifying export by loading file back ---"); + loadModelFromBinary(outputModel); + } + + return 0; +} diff --git a/src/vulkan/MeshGenerators.cpp b/src/vulkan/MeshGenerators.cpp index 53087c5..01cfa9b 100644 --- a/src/vulkan/MeshGenerators.cpp +++ b/src/vulkan/MeshGenerators.cpp @@ -45,63 +45,70 @@ std::vector generatePlaneIndices(int subdivisions) 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 { - // Front + // 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}}, - // Back - {{-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}}, - {{0.5f, -0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {0.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 0.0f}, {1.0f, 1.0f}}, - {{-0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f, 1.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 { - // Front - 0, - 1, - 2, - 0, - 2, - 3, // Back - 5, - 4, - 7, - 5, - 7, - 6, + 0, 1, 2, 0, 2, 3, + // Front + 4, 5, 6, 4, 6, 7, // Left - 4, - 0, - 3, - 4, - 3, - 7, + 8, 9, 10, 8, 10, 11, // Right - 1, - 5, - 6, - 1, - 6, - 2, - // Top - 3, - 2, - 6, - 3, - 6, - 7, + 12, 13, 14, 12, 14, 15, // Bottom - 4, - 5, - 1, - 4, - 1, - 0}; + 16, 17, 18, 16, 18, 19, + // Top + 20, 21, 22, 20, 22, 23}; } } // namespace reactor diff --git a/vcpkg.json b/vcpkg.json index 43be7d4..9690106 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -15,6 +15,7 @@ "vulkan-binding", "docking-experimental" ] - } + }, + "assimp" ] } \ No newline at end of file From 44e87fd5a0cc858dd56d2f8f05b4387a13c616f2 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Tue, 22 Jul 2025 17:05:13 -0500 Subject: [PATCH 15/26] set up static library --- .gitignore | 3 ++- CMakeLists.txt | 25 ++++++++++++++++++++----- src/pch.hpp | 3 ++- src/vulkan/Allocator.hpp | 1 + 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 77e56a9..2f2af59 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ cmake-build-debug/ vcpkg_installed/ .idea resources/ -*.fbx \ No newline at end of file +*.fbx +workspace/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c4bff10..7e4366d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ find_package(glm CONFIG REQUIRED) find_package(imgui CONFIG REQUIRED) find_package(assimp CONFIG REQUIRED) -add_executable(Reactor src/core/main.cpp +add_library(ReactorLib STATIC src/vulkan/VulkanContext.cpp src/vulkan/VulkanContext.hpp src/pch.hpp @@ -66,10 +66,25 @@ add_executable(Reactor src/core/main.cpp src/vulkan/MeshGenerators.cpp ) -target_link_libraries(Reactor PRIVATE Vulkan::Vulkan Vulkan::Headers glfw fmt::fmt spdlog::spdlog GPUOpen::VulkanMemoryAllocator glm::glm) -target_link_libraries(Reactor PRIVATE imgui::imgui) +target_compile_definitions(ReactorLib PUBLIC GLM_ENABLE_EXPERIMENTAL) -target_precompile_headers(Reactor PRIVATE src/pch.hpp) + +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 assimp::assimp fmt::fmt spdlog::spdlog) +target_link_libraries(BuildModel PRIVATE ReactorLib) diff --git a/src/pch.hpp b/src/pch.hpp index 2ee3cc2..7adaf19 100644 --- a/src/pch.hpp +++ b/src/pch.hpp @@ -20,11 +20,12 @@ #include #include +// VMA allocator #include -#define GLM_ENABLE_EXPERIMENTAL #include #include #include +#include #endif //PCH_HPP diff --git a/src/vulkan/Allocator.hpp b/src/vulkan/Allocator.hpp index 9abc66e..248e432 100644 --- a/src/vulkan/Allocator.hpp +++ b/src/vulkan/Allocator.hpp @@ -2,6 +2,7 @@ #include #include +#include namespace reactor { From 56ad6984d7557ea0594d1a389374c468604261a7 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Tue, 22 Jul 2025 17:24:35 -0500 Subject: [PATCH 16/26] do pragma once --- src/pch.hpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/pch.hpp b/src/pch.hpp index 7adaf19..b990c5e 100644 --- a/src/pch.hpp +++ b/src/pch.hpp @@ -1,9 +1,4 @@ -// -// Created by Robert F. Dickerson on 6/8/25. -// - -#ifndef PCH_HPP -#define PCH_HPP +#pragma once // Common standard library includes #include @@ -28,4 +23,4 @@ #include #include -#endif //PCH_HPP + From 300bf6a4f51ec3f82a33f610ba150c93cddf9377 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Wed, 23 Jul 2025 17:04:24 -0500 Subject: [PATCH 17/26] add more model stuff --- CMakeLists.txt | 2 + src/core/ModelIO.cpp | 163 +++++++++++++++++++++++++++++++++++ src/core/ModelIO.hpp | 18 ++++ src/pch.hpp | 1 + src/tools/ModelBuilder.cpp | 168 +------------------------------------ 5 files changed, 187 insertions(+), 165 deletions(-) create mode 100644 src/core/ModelIO.cpp create mode 100644 src/core/ModelIO.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e4366d..92b01d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,8 @@ add_library(ReactorLib STATIC src/vulkan/Mesh.hpp src/vulkan/MeshGenerators.hpp src/vulkan/MeshGenerators.cpp + src/core/ModelIO.hpp + src/core/ModelIO.cpp ) target_compile_definitions(ReactorLib PUBLIC GLM_ENABLE_EXPERIMENTAL) diff --git a/src/core/ModelIO.cpp b/src/core/ModelIO.cpp new file mode 100644 index 0000000..ea9ab52 --- /dev/null +++ b/src/core/ModelIO.cpp @@ -0,0 +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; +} +} diff --git a/src/core/ModelIO.hpp b/src/core/ModelIO.hpp new file mode 100644 index 0000000..8452cc1 --- /dev/null +++ b/src/core/ModelIO.hpp @@ -0,0 +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&); + +} // namespace reactor \ No newline at end of file diff --git a/src/pch.hpp b/src/pch.hpp index b990c5e..c8dbb98 100644 --- a/src/pch.hpp +++ b/src/pch.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include diff --git a/src/tools/ModelBuilder.cpp b/src/tools/ModelBuilder.cpp index c185add..ad2ae8a 100644 --- a/src/tools/ModelBuilder.cpp +++ b/src/tools/ModelBuilder.cpp @@ -1,169 +1,7 @@ -#include -#include -#include +#include "../core/ModelIO.hpp" #include -#include -#include -#include - -// Include the Vertex definition from your project -#include "../vulkan/Vertex.hpp" - -// A structure to hold mesh data, which is what we'll load back in the engine. -struct MeshData { - std::vector vertices; - std::vector indices; -}; - -// 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; -} - int main() { // Define input and output paths @@ -171,10 +9,10 @@ int main() const std::string outputModel = "./monkey.mesh"; // Run the import and export process - if(importAndExport(inputModel, outputModel)) { + if(reactor::importAndExport(inputModel, outputModel)) { // Optional: Test the loader to verify the export spdlog::info("--- Verifying export by loading file back ---"); - loadModelFromBinary(outputModel); + reactor::loadModelFromBinary(outputModel); } return 0; From df17e14e9133afd2221d2b902f2635ab410057b5 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Wed, 23 Jul 2025 17:34:12 -0500 Subject: [PATCH 18/26] shows a monkey head --- src/vulkan/VulkanRenderer.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 237e96b..285380b 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -4,6 +4,7 @@ #include "../core/Window.hpp" #include "ImageUtils.hpp" #include "VulkanUtils.hpp" +#include "../core/ModelIO.hpp" #include #include @@ -647,16 +648,17 @@ void VulkanRenderer::initScene() 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))}); - // Unit cube mesh (shared for buildings) - auto cubeVerts = generateUnitCubeVertices(); - auto cubeInds = generateUnitCubeIndices(); - auto cubeMesh = std::make_shared(*m_allocator, cubeVerts, cubeInds); + auto meshDataVec = loadModelFromBinary("monkey.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}); + } + - // Several buildings (example: 3 cuboids with different positions and scales) - m_objects.push_back({cubeMesh, glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 2.0f, 0.0f)) * glm::scale(glm::mat4(1.0f), glm::vec3(4.0f, 4.0f, 4.0f))}); // Square building - m_objects.push_back({cubeMesh, glm::translate(glm::mat4(1.0f), glm::vec3(10.0f, 5.0f, 0.0f)) * glm::scale(glm::mat4(1.0f), glm::vec3(3.0f, 10.0f, 3.0f))}); // Tall thin - m_objects.push_back({cubeMesh, glm::translate(glm::mat4(1.0f), glm::vec3(-5.0f, 3.0f, 5.0f)) * glm::scale(glm::mat4(1.0f), glm::vec3(5.0f, 6.0f, 2.0f))}); // Wide short - // Add more as needed } } // namespace reactor From 89e81b95329225b24219a305b33c3551b90f9633 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Thu, 24 Jul 2025 17:30:57 -0500 Subject: [PATCH 19/26] added a lighting descriptor --- shaders/triangle.frag | 34 ++++++++++++++++++++++++++++------ src/core/Uniforms.hpp | 9 +++++---- src/vulkan/VulkanRenderer.cpp | 19 ++++++++++++++++++- src/vulkan/VulkanRenderer.hpp | 2 ++ 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/shaders/triangle.frag b/shaders/triangle.frag index 7e89c28..69440b5 100644 --- a/shaders/triangle.frag +++ b/shaders/triangle.frag @@ -1,16 +1,38 @@ #version 450 -layout(location = 1) in vec3 vNormal; +// input from vertex shader +layout(location = 1) in vec3 inNormal; + +// Uniform buffer for lighting data +layout(binding = 1) uniform LightUBO { + vec4 lightDirection; + vec4 lightColor; + float lightIntensity; +} ubo; // Final output color layout(location = 0) out vec4 outColor; void main() { - // Normalize the incoming normal vector to ensure unit vector. - vec3 normal = normalize(vNormal); - // map normal components in (-1, 1) to (0, 1) - vec3 normalColor = (normal + 1.0) * 0.5; + // Define a base color for object + vec3 objectColor = vec3(0.8, 0.8, 0.8); + + // Ensure the normal is a unit vector + vec3 normal = normalize(inNormal); + + // The direction vector from the uniform is pointing *to* the light. + // We use it directly for the dot product. + vec3 lightDir = normalize(ubo.lightDirection.xyz); + + // Calculate the diffuse factor (Lambertian reflectance) + // max(..., 0.0) ensures that we don't have negative light + float diffuseFactor = max(dot(normal, lightDir), 0.0); + + // Calculate the final diffuse light color + vec3 diffuse = ubo.lightColor.rgb * ubo.lightIntensity * diffuseFactor; + + // The final color is the object's base color modulated by the diffuse light + outColor = vec4(objectColor * diffuse, 1.0); - outColor = vec4(normalColor, 1.0); } \ No newline at end of file diff --git a/src/core/Uniforms.hpp b/src/core/Uniforms.hpp index 314798e..d32be75 100644 --- a/src/core/Uniforms.hpp +++ b/src/core/Uniforms.hpp @@ -6,10 +6,11 @@ struct SceneUBO { glm::mat4 projection; }; -struct LightingUBO { - glm::vec4 lightPosition; - glm::vec4 lightColor; - float lightIntensity; +struct DirectionalLightUBO +{ + glm::vec4 lightPosition = 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; }; struct CompositeUBO { diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 285380b..b9a21f2 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -21,6 +21,7 @@ VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Cam m_uniformManager = std::make_unique(*m_allocator, m_frameManager->getFramesInFlightCount()); m_uniformManager->registerUBO("scene"); m_uniformManager->registerUBO("composite"); + m_uniformManager->registerUBO("lighting"); createPipelineAndDescriptors(); setupUI(); @@ -60,6 +61,8 @@ void VulkanRenderer::createPipelineAndDescriptors() const std::vector bindings = { vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment) + }; m_descriptorSet = std::make_unique(m_context->device(), 2, bindings); const std::vector setLayouts = {m_descriptorSet->getLayout()}; @@ -266,9 +269,15 @@ void VulkanRenderer::drawFrame() ubo.view = m_camera.getViewMatrix(); ubo.projection = m_camera.getProjectionMatrix(); + 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); + + // Create write for Scene UBO at binding 0 vk::WriteDescriptorSet sceneWrite{}; sceneWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); sceneWrite.dstBinding = 0; @@ -276,7 +285,15 @@ void VulkanRenderer::drawFrame() sceneWrite.descriptorCount = 1; sceneWrite.pBufferInfo = &sceneBufferInfo; - m_descriptorSet->updateSet({sceneWrite}); + // 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; + + m_descriptorSet->updateSet({sceneWrite, lightWrite}); beginCommandBuffer(cmd); diff --git a/src/vulkan/VulkanRenderer.hpp b/src/vulkan/VulkanRenderer.hpp index 1af1078..82539d5 100644 --- a/src/vulkan/VulkanRenderer.hpp +++ b/src/vulkan/VulkanRenderer.hpp @@ -17,6 +17,7 @@ #include "VulkanContext.hpp" #include "Mesh.hpp" #include "MeshGenerators.hpp" +#include "../core/Uniforms.hpp" namespace reactor { @@ -79,6 +80,7 @@ class VulkanRenderer std::vector m_depthViews; std::vector m_depthImageDescriptorSets; + DirectionalLightUBO m_light; std::vector m_objects; void createCoreVulkanObjects(); From 4c998bbd61e6b75c34b455be9df762d0888b19e9 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Fri, 25 Jul 2025 15:48:04 -0500 Subject: [PATCH 20/26] add fog --- CMakeLists.txt | 2 +- shaders/composite.frag | 6 ++++-- src/core/Camera.cpp | 1 - src/core/Uniforms.hpp | 3 +++ src/imgui/Imgui.cpp | 2 ++ src/imgui/Imgui.hpp | 2 ++ src/vulkan/VulkanRenderer.cpp | 1 + 7 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92b01d7..fb9a70d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,7 +69,7 @@ add_library(ReactorLib STATIC ) 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 diff --git a/shaders/composite.frag b/shaders/composite.frag index c11c34b..a7e66fe 100644 --- a/shaders/composite.frag +++ b/shaders/composite.frag @@ -14,6 +14,7 @@ layout(set = 0, binding = 1) uniform CompositeParams { 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 @@ -47,7 +48,8 @@ void main() { float fogEnd = 5.0; float viewDistance = linearizeDepth(depth); // Calculate fog amount (0.0 = no fog, 1.0 = full fog) - float fogFactor = smoothstep(fogStart, fogEnd, viewDistance); + //float fogFactor = smoothstep(fogStart, fogEnd, viewDistance); + float fogFactor = uFogDensity; // ---------------------- // Sample the HDR input image @@ -70,7 +72,7 @@ void main() { finalColor = mix(grayscale, finalColor, uSaturation); // Mix the final scene color with the fog color - //finalColor = mix(finalColor, fogColor, fogFactor); + finalColor = mix(finalColor, fogColor, fogFactor); outColor = vec4(finalColor, 1.0); } \ No newline at end of file diff --git a/src/core/Camera.cpp b/src/core/Camera.cpp index 7d91cd1..594f38a 100644 --- a/src/core/Camera.cpp +++ b/src/core/Camera.cpp @@ -1,6 +1,5 @@ #include "Camera.hpp" -#define GLM_ENABLE_EXPERIMENTAL #include #include #include diff --git a/src/core/Uniforms.hpp b/src/core/Uniforms.hpp index d32be75..b928022 100644 --- a/src/core/Uniforms.hpp +++ b/src/core/Uniforms.hpp @@ -11,6 +11,8 @@ struct DirectionalLightUBO glm::vec4 lightPosition = 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 { @@ -19,4 +21,5 @@ struct CompositeUBO { 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/imgui/Imgui.cpp b/src/imgui/Imgui.cpp index be34865..5049f63 100644 --- a/src/imgui/Imgui.cpp +++ b/src/imgui/Imgui.cpp @@ -191,6 +191,8 @@ void Imgui::ShowInspector() { 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(); } diff --git a/src/imgui/Imgui.hpp b/src/imgui/Imgui.hpp index 294fa02..fa19de6 100644 --- a/src/imgui/Imgui.hpp +++ b/src/imgui/Imgui.hpp @@ -22,6 +22,7 @@ class Imgui { [[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; }; @@ -37,6 +38,7 @@ class Imgui { float m_exposure = 1.0f; float m_contrast = 1.0f; float m_saturation = 1.0f; + float m_fogDensity = 0.001f; void ShowDockspace(); void ShowSceneView(); diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index b9a21f2..93631b8 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -263,6 +263,7 @@ void VulkanRenderer::drawFrame() 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); SceneUBO ubo{}; From 880e37b789f7620a45850b24179793c8d0086421 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Mon, 4 Aug 2025 21:44:37 -0500 Subject: [PATCH 21/26] common descriptor pool and shadowmapping begin --- CMakeLists.txt | 2 + src/vulkan/DescriptorSet.cpp | 12 +- src/vulkan/DescriptorSet.hpp | 1 + src/vulkan/ShadowMapping.cpp | 146 ++++++++++++++++ src/vulkan/ShadowMapping.hpp | 54 ++++++ src/vulkan/VulkanRenderer.cpp | 321 ++++++++++++++++++---------------- src/vulkan/VulkanRenderer.hpp | 15 +- 7 files changed, 390 insertions(+), 161 deletions(-) create mode 100644 src/vulkan/ShadowMapping.cpp create mode 100644 src/vulkan/ShadowMapping.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fb9a70d..5869e80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,8 @@ add_library(ReactorLib STATIC src/vulkan/MeshGenerators.cpp src/core/ModelIO.hpp src/core/ModelIO.cpp + src/vulkan/ShadowMapping.hpp + src/vulkan/ShadowMapping.cpp ) target_compile_definitions(ReactorLib PUBLIC GLM_ENABLE_EXPERIMENTAL) diff --git a/src/vulkan/DescriptorSet.cpp b/src/vulkan/DescriptorSet.cpp index c38fae8..f57e2a9 100644 --- a/src/vulkan/DescriptorSet.cpp +++ b/src/vulkan/DescriptorSet.cpp @@ -8,8 +8,8 @@ namespace reactor { - DescriptorSet::DescriptorSet(vk::Device device, size_t framesInFlight, const std::vector &bindings) - : m_device(device) + 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); @@ -20,13 +20,6 @@ namespace reactor { poolSizes.push_back({binding.descriptorType, static_cast(framesInFlight)}); } - vk::DescriptorPoolCreateInfo poolInfo( - vk::DescriptorPoolCreateFlags(), - static_cast(framesInFlight), - static_cast(poolSizes.size()), poolSizes.data()); - - m_pool = device.createDescriptorPool(poolInfo); - // allocate one set per frame std::vector layouts(framesInFlight, m_layout); vk::DescriptorSetAllocateInfo allocInfo(m_pool, layouts); @@ -35,7 +28,6 @@ namespace reactor { } DescriptorSet::~DescriptorSet() { - if (m_pool) m_device.destroyDescriptorPool(m_pool); if (m_layout) m_device.destroyDescriptorSetLayout(m_layout); } diff --git a/src/vulkan/DescriptorSet.hpp b/src/vulkan/DescriptorSet.hpp index 0601f9f..3bd2ac8 100644 --- a/src/vulkan/DescriptorSet.hpp +++ b/src/vulkan/DescriptorSet.hpp @@ -12,6 +12,7 @@ namespace reactor { public: DescriptorSet( vk::Device device, + vk::DescriptorPool pool, size_t framesInFlight, const std::vector& bindings); diff --git a/src/vulkan/ShadowMapping.cpp b/src/vulkan/ShadowMapping.cpp new file mode 100644 index 0000000..2f630c9 --- /dev/null +++ b/src/vulkan/ShadowMapping.cpp @@ -0,0 +1,146 @@ +#include "ShadowMapping.hpp" + +#include "VulkanRenderer.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(glm::mat4), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_AUTO, "MVP Buffer")); + } +} + +void ShadowMapping::createPipeline() +{ + spdlog::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 + .setDescriptorSetLayouts(setLayouts) + .setMultisample(4) + .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(glm::mat4); + + 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}); + } + +} + +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/ShadowMapping.hpp b/src/vulkan/ShadowMapping.hpp new file mode 100644 index 0000000..7993884 --- /dev/null +++ b/src/vulkan/ShadowMapping.hpp @@ -0,0 +1,54 @@ +#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); + + vk::ImageView shadowMapView() const; + vk::Sampler shadowMapSampler() const; + vk::DescriptorSet shadowMapDescriptorSet(size_t frameIndex) const; + + void setLightMatrix(const glm::mat4& lightMVP, size_t frameIndex); + + uint32_t resolution() const + { + return m_resolution; + } + +private: + void createResources(); + void createPipeline(); + void createDescriptors(); + + 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/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 93631b8..db54d6d 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -1,10 +1,10 @@ #include "VulkanRenderer.hpp" +#include "../core/ModelIO.hpp" #include "../core/Uniforms.hpp" #include "../core/Window.hpp" #include "ImageUtils.hpp" #include "VulkanUtils.hpp" -#include "../core/ModelIO.hpp" #include #include @@ -23,6 +23,7 @@ VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Cam m_uniformManager->registerUBO("composite"); m_uniformManager->registerUBO("lighting"); + createDescriptorPool(); createPipelineAndDescriptors(); setupUI(); createMSAAImage(); @@ -33,17 +34,39 @@ VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Cam createDescriptorSets(); createDepthPipelineAndDescriptorSets(); initScene(); + + m_shadowMapping = std::make_unique(*this); +} + +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()); + 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); + 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); @@ -54,6 +77,17 @@ void VulkanRenderer::createSwapchainAndFrameManager() } } +void VulkanRenderer::createDescriptorPool() +{ + std::vector poolSizes = {{vk::DescriptorType::eUniformBuffer, 32}, + {vk::DescriptorType::eCombinedImageSampler, 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; @@ -64,7 +98,7 @@ void VulkanRenderer::createPipelineAndDescriptors() vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment) }; - m_descriptorSet = std::make_unique(m_context->device(), 2, bindings); + 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()) @@ -80,12 +114,14 @@ void VulkanRenderer::createPipelineAndDescriptors() .build(); const std::vector compositeBindings = { - vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), + vk::DescriptorSetLayoutBinding( + 0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment), - vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), + vk::DescriptorSetLayoutBinding( + 2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), }; - m_compositeDescriptorSet = std::make_unique(m_context->device(), 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()) @@ -131,6 +167,8 @@ VulkanRenderer::~VulkanRenderer() m_context->device().destroyImageView(m_sceneViewViews[i]); m_context->device().destroyImageView(m_depthViews[i]); } + + m_context->device().destroyDescriptorPool(m_descriptorPool); } void VulkanRenderer::beginCommandBuffer(vk::CommandBuffer cmd) @@ -140,14 +178,19 @@ void VulkanRenderer::beginCommandBuffer(vk::CommandBuffer cmd) void VulkanRenderer::bindDescriptorSets(vk::CommandBuffer cmd) { - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_pipeline->getLayout(), 0, m_descriptorSet->getCurrentSet(m_frameManager->getFrameIndex()), nullptr); + 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]); + 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}; @@ -178,13 +221,12 @@ 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 depthImageView, - vk::Extent2D extent, - bool clearColor = true, - bool clearDepth = false) +void VulkanRenderer::beginDynamicRendering(vk::CommandBuffer cmd, + vk::ImageView colorImageView, + vk::ImageView depthImageView, + vk::Extent2D extent, + bool clearColor = true, + bool clearDepth = false) { vk::RenderingAttachmentInfo colorAttachment{}; vk::RenderingAttachmentInfo depthAttachment{}; @@ -301,15 +343,14 @@ void VulkanRenderer::drawFrame() // 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); + 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, depthView, extent, false, true); utils::setupViewportAndScissor(cmd, extent); @@ -321,14 +362,13 @@ void VulkanRenderer::drawFrame() // --- 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). - 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, + msaaImage, + vk::ImageLayout::eColorAttachmentOptimal, + vk::PipelineStageFlagBits::eTopOfPipe, // Source Stage + vk::PipelineStageFlagBits::eColorAttachmentOutput, // Destination Stage + {}, // Source Access + vk::AccessFlagBits::eColorAttachmentWrite // Destination Access ); beginDynamicRendering(cmd, msaaView, depthView, extent, true, false); @@ -343,24 +383,22 @@ void VulkanRenderer::drawFrame() // 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. - m_imageStateTracker.transition( - cmd, - msaaImage, - vk::ImageLayout::eTransferSrcOptimal, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eTransfer, - vk::AccessFlagBits::eColorAttachmentWrite, - vk::AccessFlagBits::eTransferRead); + 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); + m_imageStateTracker.transition(cmd, + resolveImage, + vk::ImageLayout::eTransferDstOptimal, + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eTransfer, + {}, + vk::AccessFlagBits::eTransferWrite); utils::resolveMSAAImageTo(cmd, msaaImage, resolveImage, width, height); @@ -368,24 +406,22 @@ void VulkanRenderer::drawFrame() // 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. - m_imageStateTracker.transition( - cmd, - resolveImage, - vk::ImageLayout::eShaderReadOnlyOptimal, - vk::PipelineStageFlagBits::eTransfer, - vk::PipelineStageFlagBits::eFragmentShader, - vk::AccessFlagBits::eTransferWrite, - vk::AccessFlagBits::eShaderRead); + m_imageStateTracker.transition(cmd, + resolveImage, + vk::ImageLayout::eShaderReadOnlyOptimal, + vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, + vk::AccessFlagBits::eTransferWrite, + 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, + swapchainImage, + vk::ImageLayout::eColorAttachmentOptimal, + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + {}, + vk::AccessFlagBits::eColorAttachmentWrite); m_imageStateTracker.transition( cmd, @@ -397,15 +433,14 @@ void VulkanRenderer::drawFrame() 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_depthImages[frameIdx]->get(), + vk::ImageLayout::eDepthStencilReadOnlyOptimal, + vk::PipelineStageFlagBits::eFragmentShader, + vk::PipelineStageFlagBits::eEarlyFragmentTests, + vk::AccessFlagBits::eShaderRead, + vk::AccessFlagBits::eDepthStencilAttachmentRead, + vk::ImageAspectFlagBits::eDepth); beginDynamicRendering(cmd, m_sceneViewViews[frameIdx], nullptr, extent, true); utils::setupViewportAndScissor(cmd, extent); @@ -422,59 +457,58 @@ void VulkanRenderer::drawFrame() 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}}; + 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}}; 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.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + m_compositePipeline->getLayout(), + 0, + m_compositeDescriptorSet->getCurrentSet(m_frameManager->getFrameIndex()), + nullptr); cmd.draw(3, 1, 0, 0); endDynamicRendering(cmd); // -- Prepare sceneView for ImGui - m_imageStateTracker.transition( - cmd, - sceneViewImage, - vk::ImageLayout::eShaderReadOnlyOptimal, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eFragmentShader, - vk::AccessFlagBits::eColorAttachmentWrite, - vk::AccessFlagBits::eShaderRead); + 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); + m_imageStateTracker.transition(cmd, + swapchainImage, + vk::ImageLayout::eColorAttachmentOptimal, + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + {}, + vk::AccessFlagBits::eColorAttachmentWrite); // --- 4. UI Pass --- // The UI is rendered on top of the composited scene. @@ -487,14 +521,13 @@ void VulkanRenderer::drawFrame() // --- 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, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eBottomOfPipe, - vk::AccessFlagBits::eColorAttachmentWrite, - {}); + m_imageStateTracker.transition(cmd, + swapchainImage, + vk::ImageLayout::ePresentSrcKHR, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eBottomOfPipe, + vk::AccessFlagBits::eColorAttachmentWrite, + {}); endCommandBuffer(cmd); @@ -504,8 +537,7 @@ void VulkanRenderer::drawFrame() void VulkanRenderer::createMSAAImage() { vk::Format format = vk::Format::eR16G16B16A16Sfloat; - vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment - | vk::ImageUsageFlagBits::eInputAttachment + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment | vk::ImageUsageFlagBits::eTransferSrc; size_t framesInFlight = m_frameManager->getFramesInFlightCount(); @@ -515,10 +547,7 @@ void VulkanRenderer::createMSAAImage() for (size_t i = 0; i < framesInFlight; ++i) { - auto built = builder.setFormat(format) - .setUsage(usage) - .setSamples(vk::SampleCountFlagBits::e4) - .build(); + auto built = builder.setFormat(format).setUsage(usage).setSamples(vk::SampleCountFlagBits::e4).build(); m_msaaImages[i] = std::move(built.image); m_msaaColorViews[i] = built.view; @@ -529,7 +558,8 @@ 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 + | vk::ImageUsageFlagBits::eTransferDst; size_t framesInFlight = m_frameManager->getFramesInFlightCount(); utils::ImageBuilder builder(m_context->device(), *m_allocator, m_swapchain->getExtent()); @@ -538,9 +568,7 @@ void VulkanRenderer::createResolveImages() for (size_t i = 0; i < framesInFlight; ++i) { - auto built = builder.setFormat(format) - .setUsage(usage) - .build(); // Defaults to e1 samples + auto built = builder.setFormat(format).setUsage(usage).build(); // Defaults to e1 samples m_resolveImages[i] = std::move(built.image); m_resolveViews[i] = built.view; @@ -551,7 +579,8 @@ void VulkanRenderer::createResolveImages() void VulkanRenderer::createSceneViewImages() { vk::Format format = m_swapchain->getFormat(); - vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferSrc; + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled + | vk::ImageUsageFlagBits::eTransferSrc; size_t framesInFlight = m_frameManager->getFramesInFlightCount(); // Destroy old resources if recreating @@ -568,9 +597,7 @@ void VulkanRenderer::createSceneViewImages() for (size_t i = 0; i < framesInFlight; ++i) { - auto built = builder.setFormat(format) - .setUsage(usage) - .build(); // Defaults to e1 samples and color aspect + 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; @@ -602,12 +629,13 @@ void VulkanRenderer::createSampler() 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()); + m_sceneViewImageDescriptorSets[i] = m_imgui->createDescriptorSet(m_sceneViewViews[i], m_sampler->get()); } } void VulkanRenderer::createDepthImages() @@ -668,15 +696,12 @@ void VulkanRenderer::initScene() auto meshDataVec = loadModelFromBinary("monkey.mesh"); - if (!meshDataVec.empty()) { + 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 - ); + 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 82539d5..e297c4a 100644 --- a/src/vulkan/VulkanRenderer.hpp +++ b/src/vulkan/VulkanRenderer.hpp @@ -3,6 +3,7 @@ #include #include "../core/Camera.hpp" +#include "../core/Uniforms.hpp" #include "../core/Window.hpp" #include "../imgui/Imgui.hpp" #include "Allocator.hpp" @@ -10,14 +11,14 @@ #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" -#include "Mesh.hpp" -#include "MeshGenerators.hpp" -#include "../core/Uniforms.hpp" namespace reactor { @@ -47,6 +48,10 @@ class VulkanRenderer void drawFrame(); + vk::Device device() const; + Allocator& allocator(); + vk::DescriptorPool descriptorPool() const; + private: const RendererConfig& m_config; Window& m_window; @@ -64,6 +69,7 @@ class VulkanRenderer std::unique_ptr m_sampler; std::unique_ptr m_uniformManager; std::unique_ptr m_imgui; + std::unique_ptr m_shadowMapping; ImageStateTracker m_imageStateTracker; @@ -83,6 +89,8 @@ class VulkanRenderer DirectionalLightUBO m_light; std::vector m_objects; + vk::DescriptorPool m_descriptorPool; + void createCoreVulkanObjects(); void createSwapchainAndFrameManager(); void createPipelineAndDescriptors(); @@ -95,6 +103,7 @@ class VulkanRenderer void createDepthImages(); void createDepthPipelineAndDescriptorSets(); void initScene(); + void createDescriptorPool(); void handleSwapchainResizing(); void beginCommandBuffer(vk::CommandBuffer cmd); From 1c151f163dccb862948bbb3c5eeff9757e41c0f0 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Tue, 5 Aug 2025 17:36:55 -0500 Subject: [PATCH 22/26] getting closer- there's now a shadow map --- src/vulkan/Buffer.cpp | 10 ++++ src/vulkan/Buffer.hpp | 3 +- src/vulkan/Pipeline.cpp | 14 +++++- src/vulkan/Pipeline.hpp | 2 + src/vulkan/ShadowMapping.cpp | 89 +++++++++++++++++++++++++++++++++-- src/vulkan/ShadowMapping.hpp | 5 +- src/vulkan/VulkanRenderer.cpp | 41 ++++++++++++++++ 7 files changed, 157 insertions(+), 7 deletions(-) diff --git a/src/vulkan/Buffer.cpp b/src/vulkan/Buffer.cpp index d6bdccc..beb45af 100644 --- a/src/vulkan/Buffer.cpp +++ b/src/vulkan/Buffer.cpp @@ -43,5 +43,15 @@ namespace reactor { } } +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 a13bc77..f9db8b4 100644 --- a/src/vulkan/Buffer.hpp +++ b/src/vulkan/Buffer.hpp @@ -22,7 +22,8 @@ class Buffer { [[nodiscard]] VmaAllocation allocation() const { return m_allocation; } [[nodiscard]] vk::DeviceSize size() const { return m_size; } - + void* map(); + void unmap(); private: Allocator& m_allocator; diff --git a/src/vulkan/Pipeline.cpp b/src/vulkan/Pipeline.cpp index 7520625..1743582 100644 --- a/src/vulkan/Pipeline.cpp +++ b/src/vulkan/Pipeline.cpp @@ -87,6 +87,12 @@ namespace reactor return *this; } + Pipeline::Builder& Pipeline::Builder::enableDepthBias(bool enable) + { + m_depthBiasEnable = enable; + return *this; + } + std::unique_ptr Pipeline::Builder::build() const { @@ -120,7 +126,7 @@ namespace reactor // 5. Rasterizer vk::PipelineRasterizationStateCreateInfo rasterizer( - {}, VK_FALSE, VK_FALSE, vk::PolygonMode::eFill, m_cullMode, m_frontFace, VK_FALSE, 0.0f, 0.0f, 0.0f, 1.0f); + {}, 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); @@ -149,6 +155,12 @@ namespace reactor // 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 diff --git a/src/vulkan/Pipeline.hpp b/src/vulkan/Pipeline.hpp index 8f9fef4..bbbf3c9 100644 --- a/src/vulkan/Pipeline.hpp +++ b/src/vulkan/Pipeline.hpp @@ -27,6 +27,7 @@ namespace reactor 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; @@ -41,6 +42,7 @@ namespace reactor 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; diff --git a/src/vulkan/ShadowMapping.cpp b/src/vulkan/ShadowMapping.cpp index 2f630c9..1a6e331 100644 --- a/src/vulkan/ShadowMapping.cpp +++ b/src/vulkan/ShadowMapping.cpp @@ -5,6 +5,12 @@ namespace reactor { +struct SceneUBO +{ + glm::mat4 view; + glm::mat4 projection; +}; + ShadowMapping::ShadowMapping(VulkanRenderer& renderer, uint32_t resolution) : m_renderer(renderer), m_resolution(resolution), m_shadowMapView(VK_NULL_HANDLE), m_shadowMapSampler(VK_NULL_HANDLE) @@ -76,7 +82,11 @@ void ShadowMapping::createResources() m_mvpBuffer.reserve(frameCount); for (size_t i = 0; i < frameCount; ++i) { - m_mvpBuffer.push_back(std::make_unique(m_renderer.allocator(), sizeof(glm::mat4), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_AUTO, "MVP Buffer")); + m_mvpBuffer.push_back(std::make_unique(m_renderer.allocator(), + sizeof(SceneUBO), + vk::BufferUsageFlagBits::eUniformBuffer, + VMA_MEMORY_USAGE_CPU_TO_GPU, + "MVP Buffer")); } } @@ -99,8 +109,10 @@ void ShadowMapping::createPipeline() // No fragment shader, we only want depth output .setVertexInputFromVertex() .setDepthAttachment(vk::Format::eD32Sfloat, true) // depth test and write enabled + .enableDepthBias() .setDescriptorSetLayouts(setLayouts) - .setMultisample(4) + .setMultisample(1) + .setCullMode(vk::CullModeFlagBits::eFront) .setFrontFace(vk::FrontFace::eClockwise) // Match main geometry pipeline .addPushContantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)); @@ -115,7 +127,7 @@ void ShadowMapping::createDescriptors() // Descriptor set bindings: UBO for light's MVP std::vector bindings = { - { 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex } + {0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex} // Add more if you want—for example for a sampler or shadow map }; @@ -128,7 +140,7 @@ void ShadowMapping::createDescriptors() vk::DescriptorBufferInfo uboInfo{}; uboInfo.buffer = m_mvpBuffer[i]->getHandle(); uboInfo.offset = 0; - uboInfo.range = sizeof(glm::mat4); + uboInfo.range = sizeof(SceneUBO); vk::WriteDescriptorSet writes{}; writes.dstSet = m_descriptors->get(i); @@ -140,7 +152,76 @@ void ShadowMapping::createDescriptors() 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 + vk::Viewport viewport = {0.0f, 0.0f, (float)m_resolution, (float)m_resolution, 0.0f, 1.0f}; + 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); +} + +void ShadowMapping::setLightMatrix(const glm::mat4& lightSpaceMatrix, size_t frameIndex) +{ + SceneUBO ubo; + ubo.view = glm::mat4(1.0f); + ubo.projection = lightSpaceMatrix; + + // 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 7993884..298d219 100644 --- a/src/vulkan/ShadowMapping.hpp +++ b/src/vulkan/ShadowMapping.hpp @@ -18,7 +18,7 @@ class ShadowMapping explicit ShadowMapping(VulkanRenderer& renderer, uint32_t resolution = 2048); ~ShadowMapping(); - void recordShadowPass(vk::CommandBuffer cmd, size_t frameIndex); + void recordShadowPass(vk::CommandBuffer cmd, size_t frameIndex, const std::function& drawCallback); vk::ImageView shadowMapView() const; vk::Sampler shadowMapSampler() const; @@ -36,6 +36,9 @@ class ShadowMapping void createPipeline(); void createDescriptors(); + const float depthBiasConstant = 1.25f; + const float depthBiasSlope = 1.75f; + VulkanRenderer& m_renderer; uint32_t m_resolution; diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index db54d6d..031cef7 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -359,6 +359,47 @@ void VulkanRenderer::drawFrame() drawGeometry(cmd); endDynamicRendering(cmd); + // Shadow pass + // --- Prepare for Shadow Pass --- + // 1. Create the orthographic projection matrix for the directional light. + // This defines a "box" in space that will cast shadows. + // You can adjust these values to fit your scene's size. + 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); + + // 2. Create the view matrix from the light's perspective. + // This uses the light's direction, which is already updated in your m_light UBO. + // We place the light "camera" at a position based on its direction and make it look at the scene origin. + glm::vec3 lightPosition = glm::vec3(15.0f, 15.0f, 5.0f); + glm::mat4 lightView = glm::lookAt( + lightPosition, // Position of the light in world space + glm::vec3(0.0f, 0.0f, 0.0f), // The point the light is looking at (scene origin) + glm::vec3(0.0f, 1.0f, 0.0f) // Up vector + ); + + // 3. Combine the matrices to create the final light-space matrix. + // Note: Vulkan's clip space has an inverted Y-axis. We need to add a correction. + 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.5f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + glm::mat4 lightSpaceMatrix = clipCorrection * lightProjection * lightView; + + // 4. Set the matrix for the shadow mapping pass. + m_shadowMapping->setLightMatrix(lightSpaceMatrix, frameIdx); + + //m_shadowMapping->setLightMatrix(lightMVP, frameIdx); + auto drawFunc = [this](vk::CommandBuffer cmd) { + this->drawGeometry(cmd); + }; + + m_shadowMapping->recordShadowPass(cmd, frameIdx, drawFunc); + // --- 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). From 5155917bfc4d643d14c98b361cdf15802f5b0090 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Tue, 5 Aug 2025 20:41:46 -0500 Subject: [PATCH 23/26] small typecast change --- src/vulkan/ShadowMapping.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vulkan/ShadowMapping.cpp b/src/vulkan/ShadowMapping.cpp index 1a6e331..0bf4a7d 100644 --- a/src/vulkan/ShadowMapping.cpp +++ b/src/vulkan/ShadowMapping.cpp @@ -178,8 +178,8 @@ void ShadowMapping::recordShadowPass(vk::CommandBuffer cmd, cmd.beginRendering(&renderingInfo); // set viewport/scissor - vk::Viewport viewport = {0.0f, 0.0f, (float)m_resolution, (float)m_resolution, 0.0f, 1.0f}; - vk::Rect2D scissor = {vk::Offset2D{0, 0}, vk::Extent2D{m_resolution, m_resolution}}; + 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); From 833f942be361c894d04ba59a2f7d4829060a4b42 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Tue, 5 Aug 2025 21:17:22 -0500 Subject: [PATCH 24/26] shadow appears! --- shaders/triangle.frag | 32 +++++++++++-- shaders/triangle.vert | 21 +++++---- src/core/Uniforms.hpp | 1 + src/vulkan/ShadowMapping.cpp | 5 ++ src/vulkan/ShadowMapping.hpp | 1 + src/vulkan/VulkanRenderer.cpp | 86 ++++++++++++++++++++--------------- 6 files changed, 98 insertions(+), 48 deletions(-) diff --git a/shaders/triangle.frag b/shaders/triangle.frag index 69440b5..99cc430 100644 --- a/shaders/triangle.frag +++ b/shaders/triangle.frag @@ -1,18 +1,36 @@ #version 450 -// input from vertex shader +layout(location = 0) in vec3 inWorldPos; layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec4 inLightSpacePos; -// Uniform buffer for lighting data 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() { // Define a base color for object @@ -32,7 +50,15 @@ void main() { // Calculate the final diffuse light color vec3 diffuse = ubo.lightColor.rgb * ubo.lightIntensity * diffuseFactor; + // Calculate shadow factor + float shadow = calculateShadow(); + + // The final color is the object's base color modulated by the diffuse light and shadow + vec3 finalColor = objectColor * diffuse * shadow; + + finalColor += objectColor * 0.1; + // The final color is the object's base color modulated by the diffuse light - outColor = vec4(objectColor * diffuse, 1.0); + outColor = vec4(finalColor, 1.0); } \ No newline at end of file diff --git a/shaders/triangle.vert b/shaders/triangle.vert index e0c6722..c020edc 100644 --- a/shaders/triangle.vert +++ b/shaders/triangle.vert @@ -1,26 +1,29 @@ #version 450 -layout(location = 0) in vec3 inPos; +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; -layout(location = 0) out vec3 fragColor; -layout(location = 1) out vec3 fragNormal; -layout(location = 2) out vec2 fragTexCoord; - void main() { - gl_Position = ubo.projection * ubo.view * push.model * vec4(inPos, 1.0); - fragNormal = mat3(transpose(inverse(push.model))) * inNormal; // Correct for non-uniform scales - fragColor = inColor; - fragTexCoord = inTexCoord; + 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 diff --git a/src/core/Uniforms.hpp b/src/core/Uniforms.hpp index b928022..a6b0b29 100644 --- a/src/core/Uniforms.hpp +++ b/src/core/Uniforms.hpp @@ -4,6 +4,7 @@ struct SceneUBO { glm::mat4 view; glm::mat4 projection; + glm::mat4 lightSpaceMatrix; }; struct DirectionalLightUBO diff --git a/src/vulkan/ShadowMapping.cpp b/src/vulkan/ShadowMapping.cpp index 0bf4a7d..a7faf2c 100644 --- a/src/vulkan/ShadowMapping.cpp +++ b/src/vulkan/ShadowMapping.cpp @@ -212,6 +212,11 @@ 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; diff --git a/src/vulkan/ShadowMapping.hpp b/src/vulkan/ShadowMapping.hpp index 298d219..fe03e4d 100644 --- a/src/vulkan/ShadowMapping.hpp +++ b/src/vulkan/ShadowMapping.hpp @@ -23,6 +23,7 @@ class ShadowMapping 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); diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 031cef7..c7a3bc2 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -36,6 +36,7 @@ VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Cam initScene(); m_shadowMapping = std::make_unique(*this); + m_imageStateTracker.recordState(m_shadowMapping->shadowMapImage(), vk::ImageLayout::eUndefined); } Allocator& VulkanRenderer::allocator() @@ -95,8 +96,8 @@ void VulkanRenderer::createPipelineAndDescriptors() const std::vector bindings = { vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex), - vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment) - + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment), + vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), }; m_descriptorSet = std::make_unique(m_context->device(), m_descriptorPool, 2, bindings); const std::vector setLayouts = {m_descriptorSet->getLayout()}; @@ -308,18 +309,47 @@ void VulkanRenderer::drawFrame() compositeData.uFogDensity = m_imgui->getFogDensity(); m_uniformManager->update(frameIdx, compositeData); + 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 lightPosition = glm::vec3(15.0f, 15.0f, 5.0f); + glm::mat4 lightView = glm::lookAt( + lightPosition, // Position of the light in world space + glm::vec3(0.0f, 0.0f, 0.0f), // 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.5f, + 0.0f, 0.0f, 0.0f, 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 shadowMapImageInfo = {}; + shadowMapImageInfo.sampler = m_shadowMapping->shadowMapSampler(); + shadowMapImageInfo.imageView = m_shadowMapping->shadowMapView(); + shadowMapImageInfo.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal; + // Create write for Scene UBO at binding 0 vk::WriteDescriptorSet sceneWrite{}; sceneWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); @@ -336,7 +366,14 @@ void VulkanRenderer::drawFrame() lightWrite.descriptorCount = 1; lightWrite.pBufferInfo = &lightBufferInfo; - m_descriptorSet->updateSet({sceneWrite, lightWrite}); + 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; + + m_descriptorSet->updateSet({sceneWrite, lightWrite, shadowMapWrite}); beginCommandBuffer(cmd); @@ -359,39 +396,7 @@ void VulkanRenderer::drawFrame() drawGeometry(cmd); endDynamicRendering(cmd); - // Shadow pass - // --- Prepare for Shadow Pass --- - // 1. Create the orthographic projection matrix for the directional light. - // This defines a "box" in space that will cast shadows. - // You can adjust these values to fit your scene's size. - 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); - - // 2. Create the view matrix from the light's perspective. - // This uses the light's direction, which is already updated in your m_light UBO. - // We place the light "camera" at a position based on its direction and make it look at the scene origin. - glm::vec3 lightPosition = glm::vec3(15.0f, 15.0f, 5.0f); - glm::mat4 lightView = glm::lookAt( - lightPosition, // Position of the light in world space - glm::vec3(0.0f, 0.0f, 0.0f), // The point the light is looking at (scene origin) - glm::vec3(0.0f, 1.0f, 0.0f) // Up vector - ); - - // 3. Combine the matrices to create the final light-space matrix. - // Note: Vulkan's clip space has an inverted Y-axis. We need to add a correction. - 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.5f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - glm::mat4 lightSpaceMatrix = clipCorrection * lightProjection * lightView; - - // 4. Set the matrix for the shadow mapping pass. - m_shadowMapping->setLightMatrix(lightSpaceMatrix, frameIdx); //m_shadowMapping->setLightMatrix(lightMVP, frameIdx); auto drawFunc = [this](vk::CommandBuffer cmd) { @@ -400,6 +405,15 @@ void VulkanRenderer::drawFrame() 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); + // --- 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). From 632297643295d819c16d09372a3b32f083c61e4d Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Wed, 6 Aug 2025 08:46:46 -0500 Subject: [PATCH 25/26] shadow finally working! --- shaders/triangle.frag | 29 ++++++++++------------------- src/core/Uniforms.hpp | 2 +- src/vulkan/ShadowMapping.cpp | 10 +++------- src/vulkan/VulkanRenderer.cpp | 17 +++++++++++++---- 4 files changed, 27 insertions(+), 31 deletions(-) diff --git a/shaders/triangle.frag b/shaders/triangle.frag index 99cc430..20b5af0 100644 --- a/shaders/triangle.frag +++ b/shaders/triangle.frag @@ -32,33 +32,24 @@ float calculateShadow() } void main() { - - // Define a base color for object vec3 objectColor = vec3(0.8, 0.8, 0.8); - // Ensure the normal is a unit vector - vec3 normal = normalize(inNormal); - - // The direction vector from the uniform is pointing *to* the light. - // We use it directly for the dot product. - vec3 lightDir = normalize(ubo.lightDirection.xyz); + vec3 ambient = objectColor * 0.1; - // Calculate the diffuse factor (Lambertian reflectance) - // max(..., 0.0) ensures that we don't have negative light + // 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; - // Calculate the final diffuse light color - vec3 diffuse = ubo.lightColor.rgb * ubo.lightIntensity * diffuseFactor; - - // Calculate shadow factor + // 3. Get the shadow factor (1.0 for lit, 0.0 for shadowed). float shadow = calculateShadow(); - // The final color is the object's base color modulated by the diffuse light and shadow - vec3 finalColor = objectColor * diffuse * shadow; - - finalColor += objectColor * 0.1; + // 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); - // The final color is the object's base color modulated by the diffuse light outColor = vec4(finalColor, 1.0); } \ No newline at end of file diff --git a/src/core/Uniforms.hpp b/src/core/Uniforms.hpp index a6b0b29..d11d467 100644 --- a/src/core/Uniforms.hpp +++ b/src/core/Uniforms.hpp @@ -9,7 +9,7 @@ struct SceneUBO { struct DirectionalLightUBO { - glm::vec4 lightPosition = glm::vec4(-0.5f, 1.0f, -0.5f, 0.0f); + 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 diff --git a/src/vulkan/ShadowMapping.cpp b/src/vulkan/ShadowMapping.cpp index a7faf2c..ccf41b8 100644 --- a/src/vulkan/ShadowMapping.cpp +++ b/src/vulkan/ShadowMapping.cpp @@ -1,16 +1,11 @@ #include "ShadowMapping.hpp" #include "VulkanRenderer.hpp" +#include "../core/Uniforms.hpp" namespace reactor { -struct SceneUBO -{ - glm::mat4 view; - glm::mat4 projection; -}; - ShadowMapping::ShadowMapping(VulkanRenderer& renderer, uint32_t resolution) : m_renderer(renderer), m_resolution(resolution), m_shadowMapView(VK_NULL_HANDLE), m_shadowMapSampler(VK_NULL_HANDLE) @@ -219,9 +214,10 @@ vk::Image ShadowMapping::shadowMapImage() const void ShadowMapping::setLightMatrix(const glm::mat4& lightSpaceMatrix, size_t frameIndex) { - SceneUBO ubo; + 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(); diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index c7a3bc2..dcd1521 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -283,6 +283,10 @@ void VulkanRenderer::drawFrame() 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; @@ -309,23 +313,28 @@ 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::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 lightPosition = glm::vec3(15.0f, 15.0f, 5.0f); + 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 - glm::vec3(0.0f, 0.0f, 0.0f), // The point the light is looking at (scene origin) + 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.5f, - 0.0f, 0.0f, 0.0f, 1.0f + 0.0f, 0.0f, 0.5f, 0.0f, + 0.0f, 0.0f, 0.5f, 1.0f }; glm::mat4 lightSpaceMatrix = clipCorrection * lightProjection * lightView; From f4e080e9ac8cfebb9cd71eb53a7bb1a2f07651c5 Mon Sep 17 00:00:00 2001 From: "Robert F. Dickerson" Date: Wed, 6 Aug 2025 11:14:53 -0500 Subject: [PATCH 26/26] small fog adjustment --- shaders/composite.frag | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/shaders/composite.frag b/shaders/composite.frag index a7e66fe..aee3a8a 100644 --- a/shaders/composite.frag +++ b/shaders/composite.frag @@ -9,11 +9,11 @@ layout(binding = 0) uniform sampler2D uInputImage; 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 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; }; @@ -42,15 +42,15 @@ void main() { 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 = 5.0; - float viewDistance = linearizeDepth(depth); - // Calculate fog amount (0.0 = no fog, 1.0 = full fog) - //float fogFactor = smoothstep(fogStart, fogEnd, viewDistance); - float fogFactor = uFogDensity; - // ---------------------- + // --- 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;