From fa1867cf2f05749bcddda623a8430cd2b9cdebe6 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sun, 22 Feb 2026 03:05:55 -0800 Subject: [PATCH] Fix MSAA 8x crash and eliminate redundant GPU stalls - Add error handling: revert to 1x if recreateSwapchain fails - Clamp requested MSAA to device maximum before applying - Retry MSAA color image allocation without TRANSIENT on failure - Remove redundant vkDeviceWaitIdle from WMO/M2/Character recreatePipelines (caller already waits once, was causing ~13 stalls instead of 1) --- src/rendering/character_renderer.cpp | 1 - src/rendering/m2_renderer.cpp | 1 - src/rendering/renderer.cpp | 12 ++++++++++-- src/rendering/vk_context.cpp | 9 +++++++-- src/rendering/wmo_renderer.cpp | 1 - 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index 63ea6833..635cecbb 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -2568,7 +2568,6 @@ void CharacterRenderer::dumpAnimations(uint32_t instanceId) const { void CharacterRenderer::recreatePipelines() { if (!vkCtx_) return; VkDevice device = vkCtx_->getDevice(); - vkDeviceWaitIdle(device); // Destroy old main-pass pipelines (NOT shadow, NOT pipeline layout) if (opaquePipeline_) { vkDestroyPipeline(device, opaquePipeline_, nullptr); opaquePipeline_ = VK_NULL_HANDLE; } diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index 7514e361..7caed4db 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -3683,7 +3683,6 @@ float M2Renderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3& void M2Renderer::recreatePipelines() { if (!vkCtx_) return; VkDevice device = vkCtx_->getDevice(); - vkDeviceWaitIdle(device); // Destroy old main-pass pipelines (NOT shadow, NOT pipeline layouts) if (opaquePipeline_) { vkDestroyPipeline(device, opaquePipeline_, nullptr); opaquePipeline_ = VK_NULL_HANDLE; } diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index fccef846..a6187cbf 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -726,16 +726,25 @@ void Renderer::shutdown() { void Renderer::setMsaaSamples(VkSampleCountFlagBits samples) { if (!vkCtx) return; + // Clamp to device maximum + VkSampleCountFlagBits maxSamples = vkCtx->getMaxUsableSampleCount(); + if (samples > maxSamples) samples = maxSamples; + VkSampleCountFlagBits current = vkCtx->getMsaaSamples(); if (samples == current) return; LOG_INFO("Changing MSAA from ", static_cast(current), "x to ", static_cast(samples), "x"); + // Single GPU wait — all subsequent operations are CPU-side object creation vkDeviceWaitIdle(vkCtx->getDevice()); // Set new MSAA and recreate swapchain (render pass, depth, MSAA image, framebuffers) vkCtx->setMsaaSamples(samples); - vkCtx->recreateSwapchain(window->getWidth(), window->getHeight()); + if (!vkCtx->recreateSwapchain(window->getWidth(), window->getHeight())) { + LOG_ERROR("MSAA change failed — reverting to 1x"); + vkCtx->setMsaaSamples(VK_SAMPLE_COUNT_1_BIT); + vkCtx->recreateSwapchain(window->getWidth(), window->getHeight()); + } // Recreate all sub-renderer pipelines (they embed sample count from render pass) if (terrainRenderer) terrainRenderer->recreatePipelines(); @@ -758,7 +767,6 @@ void Renderer::setMsaaSamples(VkSampleCountFlagBits samples) { if (auto* lf = skySystem->getLensFlare()) lf->recreatePipelines(); } - // Lightning is standalone (not instantiated in Renderer, no action needed) // Selection circle + overlay use lazy init, just destroy them VkDevice device = vkCtx->getDevice(); if (selCirclePipeline) { vkDestroyPipeline(device, selCirclePipeline, nullptr); selCirclePipeline = VK_NULL_HANDLE; } diff --git a/src/rendering/vk_context.cpp b/src/rendering/vk_context.cpp index 5d5a37a6..701ffe8c 100644 --- a/src/rendering/vk_context.cpp +++ b/src/rendering/vk_context.cpp @@ -384,8 +384,13 @@ bool VkContext::createMsaaColorImage() { allocInfo.preferredFlags = VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT; if (vmaCreateImage(allocator, &imgInfo, &allocInfo, &msaaColorImage_, &msaaColorAllocation_, nullptr) != VK_SUCCESS) { - LOG_ERROR("Failed to create MSAA color image"); - return false; + // Retry without TRANSIENT (some drivers reject it at high sample counts) + imgInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + allocInfo.preferredFlags = 0; + if (vmaCreateImage(allocator, &imgInfo, &allocInfo, &msaaColorImage_, &msaaColorAllocation_, nullptr) != VK_SUCCESS) { + LOG_ERROR("Failed to create MSAA color image"); + return false; + } } VkImageViewCreateInfo viewInfo{}; diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index 3e9f0f65..a5ad0788 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -2884,7 +2884,6 @@ float WMORenderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3 void WMORenderer::recreatePipelines() { if (!vkCtx_) return; VkDevice device = vkCtx_->getDevice(); - vkDeviceWaitIdle(device); // Destroy old main-pass pipelines (NOT shadow, NOT pipeline layout) if (opaquePipeline_) { vkDestroyPipeline(device, opaquePipeline_, nullptr); opaquePipeline_ = VK_NULL_HANDLE; }