From fba26ea1694e1a13469b01b4d1ca36ca963c471b Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Fri, 31 Jul 2026 16:15:50 -0400 Subject: [PATCH] [Fix] (DirectVulkan): round renderbuffer MSAA requests to supported counts glRenderbufferStorageMultisample accepts any sample count up to MAX_SAMPLES (including non-powers-of-two like 3) and promises at-least allocation, but the renderbuffer path required an exact Vulkan sample-count match and failed on devices like llvmpipe that expose 1x/4x only. Round the request up to a power of two and then to the nearest count the device supports for the format, cached per format so per-draw resolution does not re-query the physical device. Un-crashes KHR-GL33.packed_depth_stencil.blit.* (2x/3x MSAA renderbuffers). --- .../Renderer/VkRenderPassManager.cpp | 72 +++++++++++++------ .../Renderer/VkRenderPassManager.h | 3 + 2 files changed, 54 insertions(+), 21 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index b5545d9c..d0c4b00c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -16,31 +16,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { static Bool TryResolveSampleCountFlagBits(Int requestedSamples, VkSampleCountFlagBits& outSampleCount) { - switch (requestedSamples <= 0 ? 1 : requestedSamples) { - case 1: + // GL promises "at least the requested samples", so a non-power-of-two + // request (legal in GL, e.g. 3) rounds up to the next Vulkan bit. + if (requestedSamples <= 1) { outSampleCount = VK_SAMPLE_COUNT_1_BIT; return true; - case 2: - outSampleCount = VK_SAMPLE_COUNT_2_BIT; - return true; - case 4: - outSampleCount = VK_SAMPLE_COUNT_4_BIT; - return true; - case 8: - outSampleCount = VK_SAMPLE_COUNT_8_BIT; - return true; - case 16: - outSampleCount = VK_SAMPLE_COUNT_16_BIT; - return true; - case 32: - outSampleCount = VK_SAMPLE_COUNT_32_BIT; - return true; - case 64: - outSampleCount = VK_SAMPLE_COUNT_64_BIT; - return true; - default: + } + if (requestedSamples > 64) { return false; } + Uint32 bit = 1; + while (bit < static_cast(requestedSamples)) { + bit <<= 1; + } + outSampleCount = static_cast(bit); + return true; } static VkImageAspectFlags ResolveImageAspectMaskForFormat(VkFormat format) { @@ -314,6 +304,46 @@ namespace MobileGL::MG_Backend::DirectVulkan { : VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + // GL allows the implementation to allocate more samples than requested + // (glRenderbufferStorageMultisample only promises "at least"), and devices + // like llvmpipe expose 1x/4x but not 2x. Round the request up to the + // nearest supported count for this format. + if (renderbuffer->GetSamples() > 0) { + auto supportedIt = m_attachmentSampleCountsByFormat.find(format); + if (supportedIt == m_attachmentSampleCountsByFormat.end()) { + VkImageFormatProperties formatProperties{}; + VkSampleCountFlags supported = VK_SAMPLE_COUNT_1_BIT; + if (vkGetPhysicalDeviceImageFormatProperties(m_physicalDevice, format, VK_IMAGE_TYPE_2D, + VK_IMAGE_TILING_OPTIMAL, imageUsage, 0, + &formatProperties) == VK_SUCCESS) { + supported = formatProperties.sampleCounts; + } + supportedIt = m_attachmentSampleCountsByFormat.emplace(format, supported).first; + } + const VkSampleCountFlags supported = supportedIt->second; + if ((supported & sampleCount) == 0) { + // Smallest supported count above the request, else the largest below it. + Uint32 rounded = 0; + for (Uint32 bit = static_cast(sampleCount) << 1; bit <= VK_SAMPLE_COUNT_64_BIT; bit <<= 1) { + if ((supported & bit) != 0) { + rounded = bit; + break; + } + } + if (rounded == 0) { + for (Uint32 bit = static_cast(sampleCount) >> 1; bit != 0; bit >>= 1) { + if ((supported & bit) != 0) { + rounded = bit; + break; + } + } + } + if (rounded != 0) { + sampleCount = static_cast(rounded); + } + } + } + auto& resource = m_renderbufferResources[renderbuffer.get()]; const Bool needsCreate = resource.image == VK_NULL_HANDLE || diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h index 84dee790..b844b9ab 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h @@ -313,6 +313,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { UnorderedMap m_renderbufferResources; UnorderedMap m_pendingRenderbufferClears; Vector m_deferredRenderbufferReleases; + // Supported sample counts per attachment format, so per-draw resource lookups + // do not repeat vkGetPhysicalDeviceImageFormatProperties. + UnorderedMap m_attachmentSampleCountsByFormat; Bool HasPendingRenderbufferClear( const MG_State::GLState::FramebufferAttachmentObject& attachment) const;