From 20a76a423399ce8d42ef60bd24dd12faeff4680e Mon Sep 17 00:00:00 2001 From: dark-dylan-dev Date: Sat, 22 Nov 2025 03:06:38 +0100 Subject: [PATCH 1/4] Will be removed shortly --- KaguEngine/assets/textures/dummy_texture.png | Bin 349 -> 0 bytes KaguEngine/src/App.cpp | 26 +- KaguEngine/src/ImGuiContext.cpp | 27 +- KaguEngine/src/ImGuiContext.ixx | 6 +- KaguEngine/src/Renderer.cpp | 391 ++++++++++--------- KaguEngine/src/Renderer.ixx | 31 +- KaguEngine/src/SwapChain.cpp | 3 +- KaguEngine/src/Window.ixx | 1 + 8 files changed, 246 insertions(+), 239 deletions(-) delete mode 100644 KaguEngine/assets/textures/dummy_texture.png diff --git a/KaguEngine/assets/textures/dummy_texture.png b/KaguEngine/assets/textures/dummy_texture.png deleted file mode 100644 index 3b56293848dde9f3cf941628bf458d26b70c37b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 349 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2s6ii6yp7}lMWc?smOq&xaLGB9lH z=l+w(3gmMZctjR6Fz_7&Va6R3v)=**B}!Z)N`mv#O3D+9QW*jgGxJLH{9Hp6O!W-) zETl7EtpKXomKx!i=IN=$zyaj2GDtD9f^GH!@}!|`kZUv;nZe>9_ZTuVF$e(ZC?L*k zX90_60ofpMvy7491v7$1vy=fSHG!Rh1*p=%$k>2!0mM|0Us)GGOqv2@g8&oITqdx} zAWI7%3#!Y|zyKusv^KLfDRAmOAd|<_#W6%8JUL=R0wV+S5ypphd6#-YYCK*2T-G@y GGywp}bU&;B diff --git a/KaguEngine/src/App.cpp b/KaguEngine/src/App.cpp index b093810..190c8b9 100644 --- a/KaguEngine/src/App.cpp +++ b/KaguEngine/src/App.cpp @@ -10,6 +10,8 @@ module; #define GLFW_INCLUDE_VULKAN #include +#include "imgui.h" + module App; // std @@ -51,15 +53,15 @@ void App::run() { RenderSystem renderSystem{ m_Device, - m_Renderer.getOffscreenFormat(), - m_Renderer.getOffscreenDepthFormat(), + m_Renderer.getFormat(), + m_Renderer.getDepthFormat(), m_GlobalSetLayout->getDescriptorSetLayout(), // set = 0 (UBO) m_MaterialSetLayout->getDescriptorSetLayout() // set = 1 (textures) }; PointLightSystem pointLightSystem{ m_Device, - m_Renderer.getOffscreenFormat(), - m_Renderer.getOffscreenDepthFormat(), + m_Renderer.getFormat(), + m_Renderer.getDepthFormat(), m_GlobalSetLayout->getDescriptorSetLayout() }; Camera camera{}; @@ -86,6 +88,12 @@ void App::run() { KeyboardMovementController cameraController{}; glfwPollEvents(); + if (m_Window.windowResized()) { + m_Renderer.recreateSwapChain(); + imGuiContext.recreateSwapChain(); + m_Window.resetWindowResizedFlag(); + } + auto newTime = std::chrono::high_resolution_clock::now(); float frameTime = std::chrono::duration(newTime - currentTime).count(); currentTime = newTime; @@ -121,13 +129,9 @@ void App::run() { m_Renderer.endOffscreenRendering(commandBuffer); // ImGui rendering - m_Renderer.transitionOffscreenImageForImGui(commandBuffer); - imGuiContext.render(m_Renderer); - - // Present the image - m_Renderer.beginSwapChainRendering(commandBuffer); - ImGuiContext::onPresent(commandBuffer); - m_Renderer.endSwapChainRendering(commandBuffer); + m_Renderer.beginRendering(commandBuffer); + imGuiContext.render(m_Renderer, commandBuffer); + m_Renderer.endRendering(commandBuffer); m_Renderer.endFrame(); } diff --git a/KaguEngine/src/ImGuiContext.cpp b/KaguEngine/src/ImGuiContext.cpp index a0f11e6..fa97778 100644 --- a/KaguEngine/src/ImGuiContext.cpp +++ b/KaguEngine/src/ImGuiContext.cpp @@ -122,6 +122,10 @@ ImGuiContext::~ImGuiContext() { ImGui::DestroyContext(); } +void ImGuiContext::recreateSwapChain() const { + ImGui_ImplVulkan_SetMinImageCount(3); +} + void ImGuiContext::setupConfigFlags() { ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; @@ -167,9 +171,9 @@ void ImGuiContext::setupContext() const { init_info.DescriptorPool = poolRef->getDescriptorPool(); init_info.Allocator = nullptr; init_info.Subpass = 0; - init_info.MinImageCount = SwapChain::MAX_FRAMES_IN_FLIGHT; init_info.MSAASamples = deviceRef.getSampleCount(); - init_info.ImageCount = SwapChain::MAX_FRAMES_IN_FLIGHT; + init_info.MinImageCount = 3; + init_info.ImageCount = 3; init_info.UseDynamicRendering = true; init_info.PipelineRenderingCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO; init_info.PipelineRenderingCreateInfo.colorAttachmentCount = 1; @@ -226,13 +230,12 @@ void ImGuiContext::setStyleVars() { style.ButtonTextAlign = ImVec2(0.5f, 0.5f); } -void ImGuiContext::recreateSwapChain() { - ImGui_ImplVulkan_SetMinImageCount(SwapChain::MAX_FRAMES_IN_FLIGHT); -} - -void ImGuiContext::render(const Renderer& renderer) { +void ImGuiContext::render(const Renderer& renderer, VkCommandBuffer commandBuffer) { beginRender(); onRender(renderer); + ImGui::Render(); + ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commandBuffer); + endRender(); } void ImGuiContext::beginRender() { @@ -367,7 +370,7 @@ void ImGuiContext::render3DScene(const Renderer& renderer) const { (viewportSpace.y - renderSize.y) * 0.5f }; ImGui::SetCursorPos(imagePos); - ImGui::Image(reinterpret_cast(renderer.getOffscreenImGuiDescriptorSet()), renderSize); + ImGui::Image(reinterpret_cast(renderer.getSceneDescriptorSet()), renderSize); ImGui::End(); ImGui::PopStyleVar(); @@ -540,7 +543,7 @@ void ImGuiContext::renderStatusBar() { UI_Helpers::ImGui_Text(Config::compiler); // --- Right Side: Frame Rate --- - const std::string framerate = std::to_string(static_cast(ImGui::GetIO().Framerate)) + " FPS"; + const std::string framerate = std::to_string(static_cast(1.0f / ImGui::GetIO().DeltaTime)) + " FPS"; // Right-alignment position constexpr float internPadding = 8.0f; const auto align_pos = ImGui::GetWindowWidth() - ImGui::CalcTextSize(framerate.c_str()).x - ImGui::GetStyle().FramePadding.x - internPadding; @@ -553,12 +556,6 @@ void ImGuiContext::renderStatusBar() { } } -void ImGuiContext::onPresent(VkCommandBuffer commandBuffer) { - ImGui::Render(); - ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commandBuffer); - endRender(); -} - void ImGuiContext::endRender() { ImGuiIO& io = ImGui::GetIO(); if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { diff --git a/KaguEngine/src/ImGuiContext.ixx b/KaguEngine/src/ImGuiContext.ixx index c57e468..357520f 100644 --- a/KaguEngine/src/ImGuiContext.ixx +++ b/KaguEngine/src/ImGuiContext.ixx @@ -31,10 +31,8 @@ public: ); ~ImGuiContext(); - static void recreateSwapChain(); - void render(const Renderer& renderer); - - static void onPresent(VkCommandBuffer commandBuffer); + void recreateSwapChain() const; + void render(const Renderer& renderer, VkCommandBuffer commandBuffer); // Specs [[nodiscard]] float getDepth() const { return m_MaxDepth[m_CamIdx]; } diff --git a/KaguEngine/src/Renderer.cpp b/KaguEngine/src/Renderer.cpp index a1c88a9..d181450 100644 --- a/KaguEngine/src/Renderer.cpp +++ b/KaguEngine/src/Renderer.cpp @@ -19,7 +19,7 @@ import KaguEngine.Window; namespace KaguEngine { namespace { // Anonymous namespace for internal helpers -void cmdTransitionImageLayout( +void cmdTransitionImage( VkCommandBuffer commandBuffer, VkImage image, VkImageLayout oldLayout, @@ -43,8 +43,7 @@ void cmdTransitionImageLayout( vkCmdPipelineBarrier( commandBuffer, - srcStageMask, - dstStageMask, + srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, @@ -55,13 +54,12 @@ void cmdTransitionImageLayout( Renderer::Renderer(Window &window, Device &device) : windowRef{window}, deviceRef{device} { m_currentImageIndex = 0; recreateSwapChain(); - createOffscreenResources(); createCommandBuffers(); } Renderer::~Renderer() { freeCommandBuffers(); - cleanupOffscreenResources(); + cleanupOffscreenResources(true); } void Renderer::recreateSwapChain() { @@ -71,16 +69,18 @@ void Renderer::recreateSwapChain() { glfwWaitEvents(); } vkDeviceWaitIdle(deviceRef.device()); - cleanupOffscreenResources(); + m_OldSwapChain.reset(); if (m_SwapChain == nullptr) { m_SwapChain = std::make_unique(deviceRef, extent); } else { - std::shared_ptr oldSwapChain = std::move(m_SwapChain); - m_SwapChain = std::make_unique(deviceRef, extent, oldSwapChain); - if (!oldSwapChain->compareSwapFormats(*m_SwapChain)) { + m_OldSwapChain = std::move(m_SwapChain); + m_OldSwapChainCleanupTimer = 2; + m_SwapChain = std::make_unique(deviceRef, extent, m_OldSwapChain); + if (!m_OldSwapChain->compareSwapFormats(*m_SwapChain)) { throw std::runtime_error("Swap chain image(or depth) format has changed!"); } } + m_currentFrameIndex = 0; createOffscreenResources(); } @@ -104,143 +104,20 @@ void Renderer::freeCommandBuffers() { m_commandBuffers.clear(); } -VkCommandBuffer Renderer::beginFrame() { - assert(!m_isFrameStarted && "Can't call beginFrame while already in progress"); - - const auto result = m_SwapChain->acquireNextImage(&m_currentImageIndex); - if (result == VK_ERROR_OUT_OF_DATE_KHR) { - recreateSwapChain(); - return nullptr; - } - - if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - - m_isFrameStarted = true; - - const auto commandBuffer = getCurrentCommandBuffer(); - VkCommandBufferBeginInfo beginInfo{}; - beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - - if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { - throw std::runtime_error("failed to begin recording command buffer!"); - } - return commandBuffer; -} - -void Renderer::endFrame() { - assert(m_isFrameStarted && "Can't call endFrame while frame is not in progress"); - const auto commandBuffer = getCurrentCommandBuffer(); - if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } - - if (const auto result = m_SwapChain->submitCommandBuffers(&commandBuffer, &m_currentImageIndex); - result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || windowRef.windowResized()) { - windowRef.resetWindowResizedFlag(); - recreateSwapChain(); - } else if (result != VK_SUCCESS) { - throw std::runtime_error("failed to present swap chain image!"); - } - - m_isFrameStarted = false; - m_currentFrameIndex = (m_currentFrameIndex + 1) % SwapChain::MAX_FRAMES_IN_FLIGHT; -} - -void Renderer::beginSwapChainRendering(const VkCommandBuffer commandBuffer) const { - assert(m_isFrameStarted && "Can't call beginSwapChainRendering if frame is not in progress"); - assert(commandBuffer == getCurrentCommandBuffer() && "Can't begin rendering on command buffer from a different frame"); - - VkImageSubresourceRange subresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - cmdTransitionImageLayout( - commandBuffer, - m_SwapChain->getImage(m_currentImageIndex), - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - subresourceRange, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - 0, - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); - - VkRenderingAttachmentInfo colorAttachment{}; - colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; - colorAttachment.imageView = m_SwapChain->getMultisampleColorImageView(m_currentImageIndex); - colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - colorAttachment.resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT; - colorAttachment.resolveImageView = m_SwapChain->getImageView(m_currentImageIndex); - colorAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.clearValue.color = { clearColor.r, clearColor.g, clearColor.b, clearColor.a }; - - VkRenderingAttachmentInfo depthAttachment{}; - depthAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; - depthAttachment.imageView = m_SwapChain->getDepthImageView(m_currentImageIndex); - depthAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthAttachment.clearValue.depthStencil = {1.0f, 0}; - - VkRenderingInfo renderingInfo{}; - renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; - renderingInfo.renderArea = {{0, 0}, m_SwapChain->getSwapChainExtent()}; - renderingInfo.layerCount = 1; - renderingInfo.colorAttachmentCount = 1; - renderingInfo.pColorAttachments = &colorAttachment; - renderingInfo.pDepthAttachment = &depthAttachment; - renderingInfo.pStencilAttachment = nullptr; - - vkCmdBeginRendering(commandBuffer, &renderingInfo); - - VkViewport viewport{}; - viewport.x = 0.0f; - viewport.y = 0.0f; - viewport.width = static_cast(m_SwapChain->getSwapChainExtent().width); - viewport.height = static_cast(m_SwapChain->getSwapChainExtent().height); - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - const VkRect2D scissor{{0, 0}, m_SwapChain->getSwapChainExtent()}; - vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdSetScissor(commandBuffer, 0, 1, &scissor); -} - -void Renderer::endSwapChainRendering(const VkCommandBuffer commandBuffer) const { - assert(m_isFrameStarted && "Can't call endSwapChainRendering if frame is not in progress"); - assert(commandBuffer == getCurrentCommandBuffer() && "Can't end rendering on command buffer from a different frame"); - - vkCmdEndRendering(commandBuffer); - - VkImageSubresourceRange subresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - cmdTransitionImageLayout( - commandBuffer, - m_SwapChain->getImage(m_currentImageIndex), - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - subresourceRange, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - 0); -} - void Renderer::createOffscreenResources() { cleanupOffscreenResources(); - - m_offscreenExtent = m_SwapChain->getSwapChainExtent(); - m_offscreenFormat = VK_FORMAT_B8G8R8A8_UNORM; + static bool persistent = false; // Create color attachment VkImageCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; createInfo.imageType = VK_IMAGE_TYPE_2D; - createInfo.extent.width = m_offscreenExtent.width; - createInfo.extent.height = m_offscreenExtent.height; + createInfo.extent.width = m_SwapChain->getSwapChainExtent().width; + createInfo.extent.height = m_SwapChain->getSwapChainExtent().height; createInfo.extent.depth = 1; createInfo.mipLevels = 1; createInfo.arrayLayers = 1; - createInfo.format = m_offscreenFormat; + createInfo.format = getFormat(); createInfo.tiling = VK_IMAGE_TILING_OPTIMAL; createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; createInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; @@ -252,56 +129,58 @@ void Renderer::createOffscreenResources() { VkImageCreateInfo colorResolveCreateInfo{}; colorResolveCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; colorResolveCreateInfo.imageType = VK_IMAGE_TYPE_2D; - colorResolveCreateInfo.extent.width = m_offscreenExtent.width; - colorResolveCreateInfo.extent.height = m_offscreenExtent.height; + colorResolveCreateInfo.extent.width = m_SwapChain->getSwapChainExtent().width; + colorResolveCreateInfo.extent.height = m_SwapChain->getSwapChainExtent().height; colorResolveCreateInfo.extent.depth = 1; colorResolveCreateInfo.mipLevels = 1; colorResolveCreateInfo.arrayLayers = 1; - colorResolveCreateInfo.format = m_offscreenFormat; + colorResolveCreateInfo.format = getFormat(); colorResolveCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; colorResolveCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - colorResolveCreateInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + colorResolveCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; colorResolveCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; colorResolveCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; colorResolveCreateInfo.flags = 0; // Multi sampled color deviceRef.createImageWithInfo(createInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_offscreenImage, m_offscreenImageMemory); - m_offscreenImageView = m_SwapChain->createImageView(m_offscreenImage, m_offscreenFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); + m_offscreenImageView = m_SwapChain->createImageView(m_offscreenImage, getFormat(), VK_IMAGE_ASPECT_COLOR_BIT, 1); // Resolve (single sampled) deviceRef.createImageWithInfo(colorResolveCreateInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_offscreenResolveImage, m_offscreenResolveMemory); - m_offscreenResolveImageView = m_SwapChain->createImageView(m_offscreenResolveImage, m_offscreenFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); - - // Create sampler - VkSamplerCreateInfo samplerInfo{}; - samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - samplerInfo.magFilter = VK_FILTER_LINEAR; - samplerInfo.minFilter = VK_FILTER_LINEAR; - samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - samplerInfo.anisotropyEnable = VK_FALSE; - samplerInfo.maxAnisotropy = 1.0f; - samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; - samplerInfo.unnormalizedCoordinates = VK_FALSE; - samplerInfo.compareEnable = VK_FALSE; - samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - if (vkCreateSampler(deviceRef.device(), &samplerInfo, nullptr, &m_offscreenSampler) != VK_SUCCESS) { - throw std::runtime_error("failed to create offscreen sampler!"); + m_offscreenResolveImageView = m_SwapChain->createImageView(m_offscreenResolveImage, getFormat(), VK_IMAGE_ASPECT_COLOR_BIT, 1); + + if (!persistent) { + // Create sampler + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerInfo.anisotropyEnable = VK_FALSE; + samplerInfo.maxAnisotropy = 1.0f; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + if (vkCreateSampler(deviceRef.device(), &samplerInfo, nullptr, &m_offscreenSampler) != VK_SUCCESS) { + throw std::runtime_error("failed to create offscreen sampler!"); + } + persistent = true; } // Create depth image - m_offscreenDepthFormat = m_SwapChain->findDepthFormat(); VkImageCreateInfo imageInfo{}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.extent.width = m_offscreenExtent.width; - imageInfo.extent.height = m_offscreenExtent.height; + imageInfo.extent.width = m_SwapChain->getSwapChainExtent().width; + imageInfo.extent.height = m_SwapChain->getSwapChainExtent().height; imageInfo.extent.depth = 1; imageInfo.mipLevels = 1; imageInfo.arrayLayers = 1; - imageInfo.format = m_offscreenDepthFormat; + imageInfo.format = getDepthFormat(); imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; @@ -315,7 +194,7 @@ void Renderer::createOffscreenResources() { viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = m_offscreenDepthImage; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = m_offscreenDepthFormat; + viewInfo.format = getDepthFormat(); viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; viewInfo.subresourceRange.baseMipLevel = 0; viewInfo.subresourceRange.levelCount = 1; @@ -329,20 +208,20 @@ void Renderer::createOffscreenResources() { // Initial layout transition auto cmd = deviceRef.beginSingleTimeCommands(); VkImageSubresourceRange subresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - cmdTransitionImageLayout( + cmdTransitionImage( cmd, m_offscreenResolveImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - 0, VK_ACCESS_SHADER_READ_BIT); + VK_ACCESS_NONE, VK_ACCESS_SHADER_READ_BIT); deviceRef.endSingleTimeCommands(cmd); m_offscreenCurrentLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; createOffscreenDescriptorSet(); } -void Renderer::cleanupOffscreenResources() { +void Renderer::cleanupOffscreenResources(bool lastCall) { const auto device = deviceRef.device(); if (m_offscreenImGuiDescriptorSet) { @@ -382,10 +261,6 @@ void Renderer::cleanupOffscreenResources() { vkFreeMemory(device, m_offscreenImageMemory, nullptr); m_offscreenImageMemory = VK_NULL_HANDLE; } - if (m_offscreenSampler) { - vkDestroySampler(device, m_offscreenSampler, nullptr); - m_offscreenSampler = VK_NULL_HANDLE; - } if (m_offscreenDepthView) { vkDestroyImageView(device, m_offscreenDepthView, nullptr); m_offscreenDepthView = VK_NULL_HANDLE; @@ -398,6 +273,10 @@ void Renderer::cleanupOffscreenResources() { vkFreeMemory(device, m_offscreenDepthMemory, nullptr); m_offscreenDepthMemory = VK_NULL_HANDLE; } + if (lastCall && m_offscreenSampler) { + vkDestroySampler(device, m_offscreenSampler, nullptr); + m_offscreenSampler = VK_NULL_HANDLE; + } } void Renderer::createOffscreenDescriptorSet() { @@ -461,19 +340,80 @@ void Renderer::createOffscreenDescriptorSet() { vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr); } +VkCommandBuffer Renderer::beginFrame() { + assert(!m_isFrameStarted && "Can't call beginFrame while already in progress"); + + if (m_OldSwapChain) { + if (m_OldSwapChainCleanupTimer > 0) { + m_OldSwapChainCleanupTimer--; + } else { + // Timer expired, safe to destroy the old swapchain now. + // This happens frames LATER, spreading the cost and avoiding the AMD stall. + m_OldSwapChain.reset(); + } + } + + auto start = std::chrono::high_resolution_clock::now(); + const auto result = m_SwapChain->acquireNextImage(&m_currentImageIndex); + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end - start); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + std::cerr << "acquireNextImage() in " << duration.count() << "ms when VK_ERROR_OUT_OF_DATE_KHR in beginFrame()\n"; + return nullptr; + } + + if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + m_isFrameStarted = true; + + const auto commandBuffer = getCurrentCommandBuffer(); + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + return commandBuffer; +} + +bool Renderer::endFrame() { + assert(m_isFrameStarted && "Can't call endFrame while frame is not in progress"); + + const auto commandBuffer = getCurrentCommandBuffer(); + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + + const auto result = m_SwapChain->submitCommandBuffers(&commandBuffer, &m_currentImageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR || windowRef.windowResized()) { + windowRef.setFramebufferResizedFlag(true); + return false; + } + else if (result == VK_SUBOPTIMAL_KHR) { + // Skip + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + m_isFrameStarted = false; + m_currentFrameIndex = (m_currentFrameIndex + 1) % SwapChain::MAX_FRAMES_IN_FLIGHT; + + return true; +} + void Renderer::beginOffscreenRendering(VkCommandBuffer commandBuffer) { if (m_offscreenCurrentLayout != VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { VkImageSubresourceRange subresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - cmdTransitionImageLayout( + cmdTransitionImage( commandBuffer, m_offscreenResolveImage, - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, subresourceRange, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_ACCESS_SHADER_READ_BIT, - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); m_offscreenCurrentLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; } @@ -498,7 +438,7 @@ void Renderer::beginOffscreenRendering(VkCommandBuffer commandBuffer) { VkRenderingInfo renderingInfo{}; renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; - renderingInfo.renderArea = {{0, 0}, m_offscreenExtent}; + renderingInfo.renderArea = {{0, 0}, m_SwapChain->getSwapChainExtent()}; renderingInfo.layerCount = 1; renderingInfo.colorAttachmentCount = 1; renderingInfo.pColorAttachments = &colorAttachment; @@ -510,34 +450,99 @@ void Renderer::beginOffscreenRendering(VkCommandBuffer commandBuffer) { VkViewport viewport{}; viewport.x = 0.0f; viewport.y = 0.0f; - viewport.width = static_cast(m_offscreenExtent.width); - viewport.height = static_cast(m_offscreenExtent.height); + viewport.width = static_cast(m_SwapChain->getSwapChainExtent().width); + viewport.height = static_cast(m_SwapChain->getSwapChainExtent().height); viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; - const VkRect2D scissor{{0, 0}, m_offscreenExtent}; + const VkRect2D scissor{{0, 0}, m_SwapChain->getSwapChainExtent()}; vkCmdSetViewport(commandBuffer, 0, 1, &viewport); vkCmdSetScissor(commandBuffer, 0, 1, &scissor); } -void Renderer::endOffscreenRendering(VkCommandBuffer commandBuffer) const { +void Renderer::endOffscreenRendering(VkCommandBuffer commandBuffer) { vkCmdEndRendering(commandBuffer); -} - -void Renderer::transitionOffscreenImageForImGui(VkCommandBuffer commandBuffer) { if (m_offscreenCurrentLayout != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { VkImageSubresourceRange subresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - cmdTransitionImageLayout( + cmdTransitionImage( commandBuffer, m_offscreenResolveImage, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT); + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); m_offscreenCurrentLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } } +void Renderer::beginRendering(const VkCommandBuffer commandBuffer) const { + assert(m_isFrameStarted && "Can't call beginRendering if frame is not in progress"); + assert(commandBuffer == getCurrentCommandBuffer() && "Can't begin rendering on command buffer from a different frame"); + + VkImageSubresourceRange subresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + cmdTransitionImage( + commandBuffer, + m_SwapChain->getImage(m_currentImageIndex), + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + subresourceRange, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_ACCESS_NONE, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = m_SwapChain->getMultisampleColorImageView(m_currentImageIndex); + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachment.resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT; + colorAttachment.resolveImageView = m_SwapChain->getImageView(m_currentImageIndex); + colorAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue.color = { 0, 0, 0, 0 }; + + VkRenderingAttachmentInfo depthAttachment{}; + depthAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + depthAttachment.imageView = m_SwapChain->getDepthImageView(m_currentImageIndex); + depthAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + depthAttachment.clearValue.depthStencil = {1.0f, 0}; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = {{0, 0}, m_SwapChain->getSwapChainExtent()}; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + renderingInfo.pDepthAttachment = &depthAttachment; + renderingInfo.pStencilAttachment = nullptr; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = static_cast(m_SwapChain->getSwapChainExtent().width); + viewport.height = static_cast(m_SwapChain->getSwapChainExtent().height); + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + const VkRect2D scissor{{0, 0}, m_SwapChain->getSwapChainExtent()}; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); +} + +void Renderer::endRendering(const VkCommandBuffer commandBuffer) const { + assert(m_isFrameStarted && "Can't call endRendering if frame is not in progress"); + assert(commandBuffer == getCurrentCommandBuffer() && "Can't end rendering on command buffer from a different frame"); + + vkCmdEndRendering(commandBuffer); + + VkImageSubresourceRange subresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + cmdTransitionImage( + commandBuffer, + m_SwapChain->getImage(m_currentImageIndex), + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + subresourceRange, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_NONE); +} + } // Namespace KaguEngine \ No newline at end of file diff --git a/KaguEngine/src/Renderer.ixx b/KaguEngine/src/Renderer.ixx index 48225af..a466bf5 100644 --- a/KaguEngine/src/Renderer.ixx +++ b/KaguEngine/src/Renderer.ixx @@ -40,31 +40,35 @@ public: } VkCommandBuffer beginFrame(); - void endFrame(); - void beginSwapChainRendering(VkCommandBuffer commandBuffer) const; - void endSwapChainRendering(VkCommandBuffer commandBuffer) const; + bool endFrame(); - // Off screen render pass + // Off-screen rendering void beginOffscreenRendering(VkCommandBuffer commandBuffer); - void endOffscreenRendering(VkCommandBuffer commandBuffer) const; - void transitionOffscreenImageForImGui(VkCommandBuffer commandBuffer); + void endOffscreenRendering(VkCommandBuffer commandBuffer); - [[nodiscard]] VkDescriptorSet getOffscreenImGuiDescriptorSet() const { return m_offscreenImGuiDescriptorSet; } - [[nodiscard]] VkExtent2D getOffscreenExtent() const { return m_offscreenExtent; } - [[nodiscard]] VkFormat getOffscreenFormat() const { return m_offscreenFormat; } - [[nodiscard]] VkFormat getOffscreenDepthFormat() const { return m_offscreenDepthFormat; } + // + void beginRendering(VkCommandBuffer commandBuffer) const; + void endRendering(VkCommandBuffer commandBuffer) const; + + [[nodiscard]] VkDescriptorSet getSceneDescriptorSet() const { return m_offscreenImGuiDescriptorSet; } + [[nodiscard]] VkExtent2D getExtent() const { return m_SwapChain->getSwapChainExtent(); } + [[nodiscard]] VkFormat getFormat() const { return *m_SwapChain->getSwapChainImageFormat(); } + [[nodiscard]] VkFormat getDepthFormat() const { return m_SwapChain->findDepthFormat(); } std::unique_ptr& getSwapChain() { return m_SwapChain; } glm::vec4 clearColor = { 0.1f, 0.1f, 0.15f, 1.0f }; + bool needsResize = false; + void recreateSwapChain(); private: void createCommandBuffers(); void freeCommandBuffers(); - void recreateSwapChain(); Window &windowRef; Device &deviceRef; std::unique_ptr m_SwapChain; + std::shared_ptr m_OldSwapChain; + uint32_t m_OldSwapChainCleanupTimer = 0; std::vector m_commandBuffers; uint32_t m_currentImageIndex; @@ -75,9 +79,6 @@ private: VkDescriptorPool m_offscreenDescriptorPool = VK_NULL_HANDLE; VkImageLayout m_offscreenCurrentLayout = VK_IMAGE_LAYOUT_UNDEFINED; VkDescriptorSetLayout m_offscreenDescriptorSetLayout = VK_NULL_HANDLE; - VkFormat m_offscreenFormat = VK_FORMAT_B8G8R8A8_UNORM; - VkFormat m_offscreenDepthFormat; - VkExtent2D m_offscreenExtent{}; VkSampler m_offscreenSampler = VK_NULL_HANDLE; // Multi sampled color image @@ -95,7 +96,7 @@ private: VkImageView m_offscreenDepthView = VK_NULL_HANDLE; void createOffscreenResources(); - void cleanupOffscreenResources(); + void cleanupOffscreenResources(bool lastCall = false); void createOffscreenDescriptorSet(); }; diff --git a/KaguEngine/src/SwapChain.cpp b/KaguEngine/src/SwapChain.cpp index 827f86b..d98afb0 100644 --- a/KaguEngine/src/SwapChain.cpp +++ b/KaguEngine/src/SwapChain.cpp @@ -92,7 +92,7 @@ VkResult SwapChain::acquireNextImage(uint32_t *imageIndex) const { VkResult SwapChain::submitCommandBuffers(const VkCommandBuffer* buffers, const uint32_t* imageIndex) { if (m_ImagesInFlight[*imageIndex] != VK_NULL_HANDLE) { - vkWaitForFences(deviceRef.device(), 1, &m_ImagesInFlight[*imageIndex], VK_TRUE, UINT64_MAX); + vkWaitForFences(deviceRef.device(), 1, &m_ImagesInFlight[*imageIndex], VK_TRUE, 100); } m_ImagesInFlight[*imageIndex] = m_InFlightFences[m_CurrentFrame]; @@ -352,6 +352,7 @@ VkPresentModeKHR SwapChain::chooseSwapPresentMode(const std::vector(m_Width), static_cast(m_Height)}; } + void setFramebufferResizedFlag(bool flag) { m_FramebufferResized = flag; } void resetWindowResizedFlag() { m_FramebufferResized = false; } void createWindowSurface(VkInstance instance, VkSurfaceKHR *surface) const; From 87f25c6d913b8f8c4a25212e9a6014c6e544344b Mon Sep 17 00:00:00 2001 From: dark-dylan-dev Date: Sat, 22 Nov 2025 20:16:21 +0100 Subject: [PATCH 2/4] Discord advices. --- KaguEngine/src/App.cpp | 4 +-- KaguEngine/src/ImGuiContext.cpp | 2 +- KaguEngine/src/ImGuiContext.ixx | 2 +- KaguEngine/src/MovementController.cpp | 2 ++ KaguEngine/src/Renderer.cpp | 16 +++++------- KaguEngine/src/SwapChain.cpp | 36 ++++++++++++++++----------- KaguEngine/src/SwapChain.ixx | 8 +++--- 7 files changed, 36 insertions(+), 34 deletions(-) diff --git a/KaguEngine/src/App.cpp b/KaguEngine/src/App.cpp index 190c8b9..29ddf11 100644 --- a/KaguEngine/src/App.cpp +++ b/KaguEngine/src/App.cpp @@ -10,8 +10,6 @@ module; #define GLFW_INCLUDE_VULKAN #include -#include "imgui.h" - module App; // std @@ -90,7 +88,7 @@ void App::run() { if (m_Window.windowResized()) { m_Renderer.recreateSwapChain(); - imGuiContext.recreateSwapChain(); + ImGuiContext::recreateSwapChain(); m_Window.resetWindowResizedFlag(); } diff --git a/KaguEngine/src/ImGuiContext.cpp b/KaguEngine/src/ImGuiContext.cpp index fa97778..56eaa46 100644 --- a/KaguEngine/src/ImGuiContext.cpp +++ b/KaguEngine/src/ImGuiContext.cpp @@ -122,7 +122,7 @@ ImGuiContext::~ImGuiContext() { ImGui::DestroyContext(); } -void ImGuiContext::recreateSwapChain() const { +void ImGuiContext::recreateSwapChain() { ImGui_ImplVulkan_SetMinImageCount(3); } diff --git a/KaguEngine/src/ImGuiContext.ixx b/KaguEngine/src/ImGuiContext.ixx index 357520f..9290001 100644 --- a/KaguEngine/src/ImGuiContext.ixx +++ b/KaguEngine/src/ImGuiContext.ixx @@ -31,7 +31,7 @@ public: ); ~ImGuiContext(); - void recreateSwapChain() const; + static void recreateSwapChain() ; void render(const Renderer& renderer, VkCommandBuffer commandBuffer); // Specs diff --git a/KaguEngine/src/MovementController.cpp b/KaguEngine/src/MovementController.cpp index 315dd14..fb94906 100644 --- a/KaguEngine/src/MovementController.cpp +++ b/KaguEngine/src/MovementController.cpp @@ -33,6 +33,7 @@ void KeyboardMovementController::moveInPlaneXZ(GLFWwindow* window, const float d rotate.x -= 1.f; // Fullscreen check + /* if (glfwGetKey(window, keys.fullScreen) == GLFW_PRESS && !isHolding) { if (!isFullscreen) { glfwGetWindowSize(window, &width, &height); @@ -49,6 +50,7 @@ void KeyboardMovementController::moveInPlaneXZ(GLFWwindow* window, const float d else if (glfwGetKey(window, keys.fullScreen) == GLFW_RELEASE && isHolding) { isHolding = false; } + */ if (glm::dot(rotate, rotate) > std::numeric_limits::epsilon()) { entity.transform.rotation += lookSpeed * dt * glm::normalize(rotate); diff --git a/KaguEngine/src/Renderer.cpp b/KaguEngine/src/Renderer.cpp index d181450..5dcbc42 100644 --- a/KaguEngine/src/Renderer.cpp +++ b/KaguEngine/src/Renderer.cpp @@ -69,18 +69,18 @@ void Renderer::recreateSwapChain() { glfwWaitEvents(); } vkDeviceWaitIdle(deviceRef.device()); - m_OldSwapChain.reset(); - if (m_SwapChain == nullptr) { + m_OldSwapChain = std::move(m_SwapChain); + if (m_OldSwapChain == nullptr) { m_SwapChain = std::make_unique(deviceRef, extent); } else { - m_OldSwapChain = std::move(m_SwapChain); - m_OldSwapChainCleanupTimer = 2; + m_OldSwapChainCleanupTimer = 3; m_SwapChain = std::make_unique(deviceRef, extent, m_OldSwapChain); if (!m_OldSwapChain->compareSwapFormats(*m_SwapChain)) { throw std::runtime_error("Swap chain image(or depth) format has changed!"); } } m_currentFrameIndex = 0; + m_currentImageIndex = 0; createOffscreenResources(); } @@ -346,19 +346,15 @@ VkCommandBuffer Renderer::beginFrame() { if (m_OldSwapChain) { if (m_OldSwapChainCleanupTimer > 0) { m_OldSwapChainCleanupTimer--; + std::cout << "Frames before release old swap chain : " << m_OldSwapChainCleanupTimer << std::endl; } else { - // Timer expired, safe to destroy the old swapchain now. - // This happens frames LATER, spreading the cost and avoiding the AMD stall. m_OldSwapChain.reset(); + std::cout << "Destroyed the old swap chain\n"; } } - auto start = std::chrono::high_resolution_clock::now(); const auto result = m_SwapChain->acquireNextImage(&m_currentImageIndex); - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); if (result == VK_ERROR_OUT_OF_DATE_KHR) { - std::cerr << "acquireNextImage() in " << duration.count() << "ms when VK_ERROR_OUT_OF_DATE_KHR in beginFrame()\n"; return nullptr; } diff --git a/KaguEngine/src/SwapChain.cpp b/KaguEngine/src/SwapChain.cpp index d98afb0..797c922 100644 --- a/KaguEngine/src/SwapChain.cpp +++ b/KaguEngine/src/SwapChain.cpp @@ -20,7 +20,6 @@ SwapChain::SwapChain(Device &deviceRef, const VkExtent2D windowExtent) : SwapChain::SwapChain(Device &deviceRef, const VkExtent2D windowExtent, const std::shared_ptr previous) : m_OldSwapChain{previous}, m_WindowExtent{windowExtent}, deviceRef{deviceRef} { init(); - m_OldSwapChain = nullptr; } void SwapChain::init() { @@ -58,12 +57,6 @@ SwapChain::~SwapChain() { m_DepthImages.clear(); m_DepthImageMemories.clear(); - // Destroy the swapchain - if (m_SwapChain != nullptr) { - vkDestroySwapchainKHR(deviceRef.device(), m_SwapChain, nullptr); - m_SwapChain = nullptr; - } - // Destroy synchronization objects for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { vkDestroySemaphore(deviceRef.device(), m_ImageAvailableSemaphores[i], nullptr); @@ -76,30 +69,41 @@ SwapChain::~SwapChain() { m_ImageAvailableSemaphores.clear(); m_InFlightFences.clear(); m_ImagesInFlight.clear(); + + // Destroy the swapchain + if (m_SwapChain != nullptr) { + vkDestroySwapchainKHR(deviceRef.device(), m_SwapChain, nullptr); + m_SwapChain = nullptr; + } } VkResult SwapChain::acquireNextImage(uint32_t *imageIndex) const { vkWaitForFences(deviceRef.device(), 1, &m_InFlightFences[m_CurrentFrame], VK_TRUE, std::numeric_limits::max()); + auto start = std::chrono::high_resolution_clock::now(); const VkResult result = - vkAcquireNextImageKHR(deviceRef.device(), m_SwapChain, std::numeric_limits::max(), + vkAcquireNextImageKHR(deviceRef.device(), m_SwapChain, UINT64_MAX, m_ImageAvailableSemaphores[m_CurrentFrame], // must be a not signaled semaphore VK_NULL_HANDLE, imageIndex); + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end - start); + if (duration.count() > 0) + std::cout << "vkAcquireNextImageKHR in " << duration.count() << "ms" << std::endl; return result; } VkResult SwapChain::submitCommandBuffers(const VkCommandBuffer* buffers, const uint32_t* imageIndex) { if (m_ImagesInFlight[*imageIndex] != VK_NULL_HANDLE) { - vkWaitForFences(deviceRef.device(), 1, &m_ImagesInFlight[*imageIndex], VK_TRUE, 100); + vkWaitForFences(deviceRef.device(), 1, &m_ImagesInFlight[*imageIndex], VK_TRUE, UINT64_MAX); } m_ImagesInFlight[*imageIndex] = m_InFlightFences[m_CurrentFrame]; VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphore = m_ImageAvailableSemaphores[m_CurrentFrame]; + VkSemaphore waitSemaphore = m_ImageAvailableSemaphores[m_CurrentFrame]; VkSemaphore signalSemaphore = m_RenderFinishedSemaphores[*imageIndex]; constexpr VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; @@ -179,11 +183,14 @@ void SwapChain::createSwapChain() { createInfo.oldSwapchain = m_OldSwapChain == nullptr ? VK_NULL_HANDLE : m_OldSwapChain->m_SwapChain; + if (m_OldSwapChain != nullptr && m_OldSwapChain->m_SwapChain == m_SwapChain) { + std::cout << "SAME\n"; + } + if (vkCreateSwapchainKHR(deviceRef.device(), &createInfo, nullptr, &m_SwapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - vkGetSwapchainImagesKHR(deviceRef.device(), m_SwapChain, &m_ImageCount, nullptr); m_SwapChainImages.resize(m_ImageCount); vkGetSwapchainImagesKHR(deviceRef.device(), m_SwapChain, &m_ImageCount, m_SwapChainImages.data()); @@ -300,7 +307,7 @@ void SwapChain::createSyncObjects() { m_ImageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); m_RenderFinishedSemaphores.resize(m_ImageCount); m_InFlightFences.resize(MAX_FRAMES_IN_FLIGHT); - m_ImagesInFlight.resize(m_ImageCount, VK_NULL_HANDLE); + m_ImagesInFlight.assign(m_ImageCount, VK_NULL_HANDLE); VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; @@ -310,11 +317,10 @@ void SwapChain::createSyncObjects() { fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - if (vkCreateSemaphore(deviceRef.device(), &semaphoreInfo, nullptr, &m_ImageAvailableSemaphores[i]) != - VK_SUCCESS || + if (vkCreateSemaphore(deviceRef.device(), &semaphoreInfo, nullptr, &m_ImageAvailableSemaphores[i]) != VK_SUCCESS || vkCreateFence(deviceRef.device(), &fenceInfo, nullptr, &m_InFlightFences[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create per-frame synchronization objects"); - } + } } for (uint32_t i = 0; i < m_ImageCount; i++) { diff --git a/KaguEngine/src/SwapChain.ixx b/KaguEngine/src/SwapChain.ixx index 5c31a82..966f20e 100644 --- a/KaguEngine/src/SwapChain.ixx +++ b/KaguEngine/src/SwapChain.ixx @@ -24,10 +24,10 @@ public: SwapChain(const SwapChain &) = delete; SwapChain &operator=(const SwapChain &) = delete; - [[nodiscard]] VkImageView getImageView(const int index) const { return m_SwapChainImageViews[index]; } - [[nodiscard]] VkImage getImage(const int index) const { return m_SwapChainImages[index]; } - [[nodiscard]] VkImageView getDepthImageView(const int index) const { return m_DepthImageViews[index]; } - [[nodiscard]] VkImageView getMultisampleColorImageView(const int index) const { return m_MultisampleColorImageViews[index]; } + [[nodiscard]] VkImageView getImageView(const uint32_t index) const { return m_SwapChainImageViews[index]; } + [[nodiscard]] VkImage getImage(const uint32_t index) const { return m_SwapChainImages[index]; } + [[nodiscard]] VkImageView getDepthImageView(const uint32_t index) const { return m_DepthImageViews[index]; } + [[nodiscard]] VkImageView getMultisampleColorImageView(const uint32_t index) const { return m_MultisampleColorImageViews[index]; } [[nodiscard]] size_t imageCount() const { return m_SwapChainImages.size(); } [[nodiscard]] VkFormat* getSwapChainImageFormat() { return &m_SwapChainImageFormat; } [[nodiscard]] VkExtent2D getSwapChainExtent() const { return m_SwapChainExtent; } From 47507d1bf2c85bacf6f6c4970cdceb268dc7bca1 Mon Sep 17 00:00:00 2001 From: dark-dylan-dev Date: Sun, 23 Nov 2025 23:05:41 +0100 Subject: [PATCH 3/4] - Commented the AMD GPU Driver issue inside SwapChain.cpp - Renamed m_ImageAvailableSemaphores to m_acquiredSemaphores, makes more sense. --- KaguEngine/src/Renderer.cpp | 7 +------ KaguEngine/src/SwapChain.cpp | 26 +++++++++----------------- KaguEngine/src/SwapChain.ixx | 3 +-- 3 files changed, 11 insertions(+), 25 deletions(-) diff --git a/KaguEngine/src/Renderer.cpp b/KaguEngine/src/Renderer.cpp index 5dcbc42..28295c1 100644 --- a/KaguEngine/src/Renderer.cpp +++ b/KaguEngine/src/Renderer.cpp @@ -346,10 +346,8 @@ VkCommandBuffer Renderer::beginFrame() { if (m_OldSwapChain) { if (m_OldSwapChainCleanupTimer > 0) { m_OldSwapChainCleanupTimer--; - std::cout << "Frames before release old swap chain : " << m_OldSwapChainCleanupTimer << std::endl; } else { m_OldSwapChain.reset(); - std::cout << "Destroyed the old swap chain\n"; } } @@ -387,10 +385,7 @@ bool Renderer::endFrame() { windowRef.setFramebufferResizedFlag(true); return false; } - else if (result == VK_SUBOPTIMAL_KHR) { - // Skip - } - else if (result != VK_SUCCESS) { + if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { throw std::runtime_error("failed to present swap chain image!"); } diff --git a/KaguEngine/src/SwapChain.cpp b/KaguEngine/src/SwapChain.cpp index 797c922..fa14e6f 100644 --- a/KaguEngine/src/SwapChain.cpp +++ b/KaguEngine/src/SwapChain.cpp @@ -59,14 +59,14 @@ SwapChain::~SwapChain() { // Destroy synchronization objects for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vkDestroySemaphore(deviceRef.device(), m_ImageAvailableSemaphores[i], nullptr); + vkDestroySemaphore(deviceRef.device(), m_AcquireSemaphores[i], nullptr); vkDestroyFence(deviceRef.device(), m_InFlightFences[i], nullptr); } for (uint32_t i = 0; i < m_ImageCount; i++) { vkDestroySemaphore(deviceRef.device(), m_RenderFinishedSemaphores[i], nullptr); } m_RenderFinishedSemaphores.clear(); - m_ImageAvailableSemaphores.clear(); + m_AcquireSemaphores.clear(); m_InFlightFences.clear(); m_ImagesInFlight.clear(); @@ -78,18 +78,14 @@ SwapChain::~SwapChain() { } VkResult SwapChain::acquireNextImage(uint32_t *imageIndex) const { - vkWaitForFences(deviceRef.device(), 1, &m_InFlightFences[m_CurrentFrame], VK_TRUE, - std::numeric_limits::max()); + vkWaitForFences(deviceRef.device(), 1, &m_InFlightFences[m_CurrentFrame], VK_TRUE, UINT64_MAX); - auto start = std::chrono::high_resolution_clock::now(); + // On AMD GPUs, this call takes exactly two seconds to acquire the next [imageCount()] images after recreating + // the swap chain. const VkResult result = vkAcquireNextImageKHR(deviceRef.device(), m_SwapChain, UINT64_MAX, - m_ImageAvailableSemaphores[m_CurrentFrame], // must be a not signaled semaphore + m_AcquireSemaphores[m_CurrentFrame], // must be a not signaled semaphore VK_NULL_HANDLE, imageIndex); - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - if (duration.count() > 0) - std::cout << "vkAcquireNextImageKHR in " << duration.count() << "ms" << std::endl; return result; } @@ -103,7 +99,7 @@ VkResult SwapChain::submitCommandBuffers(const VkCommandBuffer* buffers, const u VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphore = m_ImageAvailableSemaphores[m_CurrentFrame]; + VkSemaphore waitSemaphore = m_AcquireSemaphores[m_CurrentFrame]; VkSemaphore signalSemaphore = m_RenderFinishedSemaphores[*imageIndex]; constexpr VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; @@ -183,10 +179,6 @@ void SwapChain::createSwapChain() { createInfo.oldSwapchain = m_OldSwapChain == nullptr ? VK_NULL_HANDLE : m_OldSwapChain->m_SwapChain; - if (m_OldSwapChain != nullptr && m_OldSwapChain->m_SwapChain == m_SwapChain) { - std::cout << "SAME\n"; - } - if (vkCreateSwapchainKHR(deviceRef.device(), &createInfo, nullptr, &m_SwapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } @@ -304,7 +296,7 @@ void SwapChain::createColorResources() { } void SwapChain::createSyncObjects() { - m_ImageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + m_AcquireSemaphores.resize(MAX_FRAMES_IN_FLIGHT); m_RenderFinishedSemaphores.resize(m_ImageCount); m_InFlightFences.resize(MAX_FRAMES_IN_FLIGHT); m_ImagesInFlight.assign(m_ImageCount, VK_NULL_HANDLE); @@ -317,7 +309,7 @@ void SwapChain::createSyncObjects() { fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - if (vkCreateSemaphore(deviceRef.device(), &semaphoreInfo, nullptr, &m_ImageAvailableSemaphores[i]) != VK_SUCCESS || + if (vkCreateSemaphore(deviceRef.device(), &semaphoreInfo, nullptr, &m_AcquireSemaphores[i]) != VK_SUCCESS || vkCreateFence(deviceRef.device(), &fenceInfo, nullptr, &m_InFlightFences[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create per-frame synchronization objects"); } diff --git a/KaguEngine/src/SwapChain.ixx b/KaguEngine/src/SwapChain.ixx index 966f20e..e250b57 100644 --- a/KaguEngine/src/SwapChain.ixx +++ b/KaguEngine/src/SwapChain.ixx @@ -58,7 +58,6 @@ private: // Helper functions static VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats); - static VkPresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes); [[nodiscard]] VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR &capabilities) const; @@ -81,7 +80,7 @@ private: VkSwapchainKHR m_SwapChain; std::shared_ptr m_OldSwapChain; - std::vector m_ImageAvailableSemaphores; + std::vector m_AcquireSemaphores; std::vector m_RenderFinishedSemaphores; std::vector m_InFlightFences; std::vector m_ImagesInFlight; From dd53b07006d454ee6a861a8593a21bf3b3de35e4 Mon Sep 17 00:00:00 2001 From: dark-dylan-dev Date: Sun, 23 Nov 2025 23:25:10 +0100 Subject: [PATCH 4/4] Cleaning before merging. --- .gitignore | 5 ++++- KaguEngine/src/MovementController.cpp | 2 -- KaguEngine/src/Renderer.cpp | 1 + KaguEngine/src/Renderer.ixx | 1 - KaguEngine/src/SwapChain.cpp | 1 - 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 8c257a7..fe94d78 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ # Unfinished scripts build.bat build.sh +build_debug.bat +build_debug.sh KaguEngine/assets/shaders/compile.bat KaguEngine/assets/shaders/compile.sh @@ -16,4 +18,5 @@ KaguEngine/assets/shaders/compile.sh /cmake-build-minsizerel/ /cmake-build-release/ /cmake-build-relwithdebinfo/ -/build/ \ No newline at end of file +/build/ +/build_debug/ \ No newline at end of file diff --git a/KaguEngine/src/MovementController.cpp b/KaguEngine/src/MovementController.cpp index fb94906..315dd14 100644 --- a/KaguEngine/src/MovementController.cpp +++ b/KaguEngine/src/MovementController.cpp @@ -33,7 +33,6 @@ void KeyboardMovementController::moveInPlaneXZ(GLFWwindow* window, const float d rotate.x -= 1.f; // Fullscreen check - /* if (glfwGetKey(window, keys.fullScreen) == GLFW_PRESS && !isHolding) { if (!isFullscreen) { glfwGetWindowSize(window, &width, &height); @@ -50,7 +49,6 @@ void KeyboardMovementController::moveInPlaneXZ(GLFWwindow* window, const float d else if (glfwGetKey(window, keys.fullScreen) == GLFW_RELEASE && isHolding) { isHolding = false; } - */ if (glm::dot(rotate, rotate) > std::numeric_limits::epsilon()) { entity.transform.rotation += lookSpeed * dt * glm::normalize(rotate); diff --git a/KaguEngine/src/Renderer.cpp b/KaguEngine/src/Renderer.cpp index 28295c1..6e5dfd7 100644 --- a/KaguEngine/src/Renderer.cpp +++ b/KaguEngine/src/Renderer.cpp @@ -382,6 +382,7 @@ bool Renderer::endFrame() { const auto result = m_SwapChain->submitCommandBuffers(&commandBuffer, &m_currentImageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR || windowRef.windowResized()) { + m_isFrameStarted = false; windowRef.setFramebufferResizedFlag(true); return false; } diff --git a/KaguEngine/src/Renderer.ixx b/KaguEngine/src/Renderer.ixx index a466bf5..bb3b291 100644 --- a/KaguEngine/src/Renderer.ixx +++ b/KaguEngine/src/Renderer.ixx @@ -57,7 +57,6 @@ public: std::unique_ptr& getSwapChain() { return m_SwapChain; } glm::vec4 clearColor = { 0.1f, 0.1f, 0.15f, 1.0f }; - bool needsResize = false; void recreateSwapChain(); private: diff --git a/KaguEngine/src/SwapChain.cpp b/KaguEngine/src/SwapChain.cpp index fa14e6f..2e3ddad 100644 --- a/KaguEngine/src/SwapChain.cpp +++ b/KaguEngine/src/SwapChain.cpp @@ -350,7 +350,6 @@ VkPresentModeKHR SwapChain::chooseSwapPresentMode(const std::vector