From 24cf1e3a7ff690c0bce6f48fb1ffc916ba211453 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 12 Jul 2026 20:17:44 -0400 Subject: [PATCH] [Perf] (MG_Backend/DirectVulkan): frames-in-flight from MOBILEGL_MAGMA_FRAMESINFLIGHT env (fallback 3), clamped to surface maxImageCount at init --- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 56 +++++++++++++++++++ .../DirectVulkan/VulkanRendererConfig.h | 5 ++ 2 files changed, 61 insertions(+) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 10c6fa90..058b0b33 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -1901,6 +1901,62 @@ void main() { CreateAllocator(); CreateCommandPool(); + + // Frames-in-flight is a request, not a guarantee: it also seeds the swapchain image + // count (SwapchainObject clamps the hint into [minImageCount, maxImageCount]). Not every + // driver/surface supports >= 3 swapchain images, and keeping more frame slots than the + // surface can present would leave the surplus slots stalling on vkAcquireNextImageKHR. + // So clamp to the surface's real limits here, before any per-frame resource is sized off + // it. (The standalone driver POST is headless and has no surface, so this check lives at + // renderer init.) Existing logs already report the swapchain's min/actual image count; + // this one adds the frames-in-flight decision itself. + { + // Desired depth comes from the MOBILEGL_MAGMA_FRAMESINFLIGHT env var (so it can be + // tuned per device without a rebuild); if unset/invalid, fall back to the config value. + Uint32 requestedFramesInFlight = m_config.MaxFramesInFlight; + if (const char* framesEnv = std::getenv("MOBILEGL_MAGMA_FRAMESINFLIGHT")) { + char* parseEnd = nullptr; + const long parsedFrames = std::strtol(framesEnv, &parseEnd, 10); + if (parseEnd != framesEnv && *parseEnd == '\0' && parsedFrames >= 1 && parsedFrames <= 64) { + requestedFramesInFlight = static_cast(parsedFrames); + MGLOG_I("MaxFramesInFlight: MOBILEGL_MAGMA_FRAMESINFLIGHT=%ld requested", parsedFrames); + } else { + MGLOG_W("MaxFramesInFlight: ignoring invalid MOBILEGL_MAGMA_FRAMESINFLIGHT='%s'; " + "falling back to %u", framesEnv, requestedFramesInFlight); + } + } + + VkSurfaceCapabilitiesKHR surfaceCaps{}; + const VkResult capsResult = vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + m_physicalDevice.handle, m_surface, &surfaceCaps); + if (capsResult != VK_SUCCESS) { + MGLOG_W("MaxFramesInFlight: vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed (VkResult=%d); " + "keeping requested %u", static_cast(capsResult), requestedFramesInFlight); + } else { + // Frames-in-flight is the CPU pipeline depth; it only needs to stay <= the number + // of swapchain images the surface can provide (maxImageCount), so the extra slots + // never stall on vkAcquireNextImageKHR. It must NOT be forced up to minImageCount: + // the swapchain independently gets >= minImageCount images (SwapchainObject raises + // the count), and inflating the CPU depth would only add latency + memory. + Uint32 chosenFramesInFlight = requestedFramesInFlight; + if (surfaceCaps.maxImageCount != 0 && chosenFramesInFlight > surfaceCaps.maxImageCount) { + chosenFramesInFlight = surfaceCaps.maxImageCount; // 0 == no upper bound + } + if (chosenFramesInFlight < 2) { + chosenFramesInFlight = 2; // never drop below double buffering + } + m_config.MaxFramesInFlight = chosenFramesInFlight; + if (chosenFramesInFlight != requestedFramesInFlight) { + MGLOG_W("MaxFramesInFlight: requested %u unsupported by surface (minImageCount=%u, " + "maxImageCount=%u); using %u", requestedFramesInFlight, surfaceCaps.minImageCount, + surfaceCaps.maxImageCount, chosenFramesInFlight); + } else { + MGLOG_I("MaxFramesInFlight: using %u (surface minImageCount=%u, maxImageCount=%u)", + chosenFramesInFlight, surfaceCaps.minImageCount, surfaceCaps.maxImageCount); + } + } + } + VK_VERIFY(m_frameContext.Initialize(m_device, m_commandPool, m_config.MaxFramesInFlight), "CreateFrameContexts"); MGLOG_I("CreateFrameContexts completed"); diff --git a/MobileGL/MG_Backend/DirectVulkan/VulkanRendererConfig.h b/MobileGL/MG_Backend/DirectVulkan/VulkanRendererConfig.h index 5b5aa784..f7ed3ce4 100644 --- a/MobileGL/MG_Backend/DirectVulkan/VulkanRendererConfig.h +++ b/MobileGL/MG_Backend/DirectVulkan/VulkanRendererConfig.h @@ -11,6 +11,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { struct VulkanRendererConfig { + // Fallback CPU pipeline depth used when the MOBILEGL_MAGMA_FRAMESINFLIGHT env var is + // unset/invalid. A deeper pipeline lets the CPU run further ahead of the GPU, hiding + // per-frame GPU-completion latency. Whatever value is chosen (env or this fallback) is + // only a request: VulkanRenderer::Initialize clamps it down to the surface's maxImageCount + // (and never below 2), since not every driver allows that many swapchain images. Uint32 MaxFramesInFlight = 3; String AppName = "MobileGL-VulkanRenderer"; MobileGL::Version Version = MG_Config::CoreVersion;