diff --git a/MobileGL/Config.h b/MobileGL/Config.h index 1702f2a2..c9a85ac8 100644 --- a/MobileGL/Config.h +++ b/MobileGL/Config.h @@ -175,6 +175,13 @@ namespace MobileGL::MG_Config { // ahead of the server, is derived from it: max(1, N - 2), so the default 3 keeps the // shipped credit of 1 and N = 4 is credit 2 (one more frame of latency). Uint32 FramesInFlight = 3; + // MOBILEGL_MAGMA_EXTRA_SWAPCHAIN_IMAGES: swapchain images Magma asks for beyond the + // surface's minimum (the frames-in-flight count still raises the request when it is + // larger), clamped to the surface's maximum. With more images, the one the display has + // just released is reused later, by when its release has usually signaled, so fewer + // frames start their window pass behind it. Each image costs a full-size colour buffer + // plus the default framebuffer's depth/stencil buffer of the same size. + Uint32 MagmaExtraSwapchainImages = 0; // MOBILEGL_MAGMA_MAX_DRAWS_PER_COMMAND_BUFFER: GL draws and dispatches Magma records into // one command buffer before it submits it and continues on a fresh one. 0 = unbounded. // Drivers back a command buffer with GPU memory that is only returned when the buffer is diff --git a/MobileGL/ConfigLoader.cpp b/MobileGL/ConfigLoader.cpp index 3615f109..fb2a8074 100644 --- a/MobileGL/ConfigLoader.cpp +++ b/MobileGL/ConfigLoader.cpp @@ -386,6 +386,7 @@ namespace MobileGL::MG_ConfigLoader { if (acceptedEnvVariablesMap->count("MOBILEGL_MAGMA_FRAMESINFLIGHT") != 0) MGLOG_W("Config: MOBILEGL_MAGMA_FRAMESINFLIGHT is deprecated; use MOBILEGL_FRAMES_IN_FLIGHT"); } + features.MagmaExtraSwapchainImages = QueryEnvUint32("MOBILEGL_MAGMA_EXTRA_SWAPCHAIN_IMAGES", 0, 0, 16); features.MagmaMaxDrawsPerCommandBuffer = QueryEnvUint32("MOBILEGL_MAGMA_MAX_DRAWS_PER_COMMAND_BUFFER", 16384, 0, 1u << 24); features.MagmaDescriptorTrimFrames = QueryEnvUint32("MOBILEGL_MAGMA_DESCRIPTOR_TRIM_FRAMES", 120, 0, 1u << 20); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp index 8be6d298..2bd06253 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp @@ -173,7 +173,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void SwapchainObject::Create(VkDevice device, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, Uint32 graphicsQueueFamily, Uint32 presentQueueFamily, Uint32 minImageCountHint, - VkExtent2D desiredExtent, Optional swapInterval) { + Uint32 extraImageCount, VkExtent2D desiredExtent, Optional swapInterval) { const auto swapchainCapabilities = GetSwapchainCapabilities(physicalDevice, surface); MOBILEGL_ASSERT(swapchainCapabilities.IsComplete(), "SwapchainObject::Create failed: incomplete swapchain capabilities"); @@ -198,11 +198,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_supportedPresentModes = swapchainCapabilities.presentModes; const auto& swapchainCaps = swapchainCapabilities.capabilities; - Uint32 targetImageCount = std::max(minImageCountHint, swapchainCaps.minImageCount); + // The extra images count from the surface's own minimum: that is how many the platform + // keeps for itself and the application, so only images beyond it delay a buffer's reuse. + Uint32 targetImageCount = + std::max(minImageCountHint, swapchainCaps.minImageCount + extraImageCount); if (swapchainCaps.maxImageCount != 0) { targetImageCount = std::min(targetImageCount, swapchainCaps.maxImageCount); } - MGLOG_I("Set minImageCount = %u", targetImageCount); + MGLOG_I("Set minImageCount = %u (surface min %u, max %u, extra %u)", targetImageCount, + swapchainCaps.minImageCount, swapchainCaps.maxImageCount, extraImageCount); MGLOG_I("Swapchain currentTransform = %s", string_VkSurfaceTransformFlagBitsKHR(swapchainCaps.currentTransform)); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.h index 53d8896e..13a4a8ad 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.h @@ -36,8 +36,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { static const char* GetPresentModeName(VkPresentModeKHR presentMode); void Create(VkDevice device, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, Uint32 graphicsQueueFamily, - Uint32 presentQueueFamily, Uint32 minImageCountHint, VkExtent2D desiredExtent, - Optional swapInterval); + Uint32 presentQueueFamily, Uint32 minImageCountHint, Uint32 extraImageCount, + VkExtent2D desiredExtent, Optional swapInterval); void Shutdown(VkDevice device); // Tells the frontend (or, under a transport, the client) the default framebuffer's size // and formats. Create does this; a renderer switching between surfaces repeats it. diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 0938e8eb..077dda9d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -8510,7 +8510,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_swapchainObject.Create(m_device, m_physicalDevice.handle, m_surface, static_cast(m_physicalDevice.queueFamilies.graphicsFamily), static_cast(m_physicalDevice.queueFamilies.presentFamily), - m_config.MaxFramesInFlight, desiredExtent, m_config.SwapInterval); + m_config.MaxFramesInFlight, MG_Config::Features.MagmaExtraSwapchainImages, + desiredExtent, m_config.SwapInterval); if (m_config.SwapInterval && m_config.SwapInterval != m_swapchainSwapInterval) { MGLOG_D("DirectVulkan: swap interval %d -> %s", *m_config.SwapInterval, SwapchainObject::GetPresentModeName(m_swapchainObject.GetPresentMode())); diff --git a/README.md b/README.md index 90baf234..1cce3826 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,7 @@ MobileGL supports runtime configuration via environment variables. | `MOBILEGL_FRAMES_IN_FLIGHT` | How many frames may be queued ahead of the GPU (Magma: its frame contexts; Espryt: how far presents may run ahead). The old name `MOBILEGL_MAGMA_FRAMESINFLIGHT` still works but is deprecated. | Integer `1`–`64` | `3` | | `MOBILEGL_MAGMA_MAX_DRAWS_PER_COMMAND_BUFFER` | Draws and dispatches Magma records into one command buffer before it submits the buffer mid-frame and continues on a fresh one. `0` never splits. | Integer `0`–`16777216` | `16384` | | `MOBILEGL_MAGMA_DESCRIPTOR_TRIM_FRAMES` | Frames a frame slot's descriptor pools must stay under a quarter full before Magma frees the slot's cached descriptor sets and grown pools. `0` never trims. | Integer `0`–`1048576` | `120` | +| `MOBILEGL_MAGMA_EXTRA_SWAPCHAIN_IMAGES` | Swapchain images Magma asks for beyond the surface's minimum, capped at its maximum. More images delay the reuse of the window buffer the display has just released, so fewer frames wait for its release; each costs a full-size colour buffer and a depth/stencil buffer. | Integer `0`–`16` | `0` | | `MOBILEGL_ESPRYT_AVOID_SAMPLER_MIPMAP_MIN_FILTER` | Avoid sampler mipmap minification filters. | `0`, `1` | `0` | | `MOBILEGL_COHERENT_AS_FLUSH` | Treat persistent `GL_MAP_FLUSH_EXPLICIT_BIT` maps as coherent (app-compat for engines like Flywheel that never flush them). | `0`, `1` | `0` | | `MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION` | Always emulate depth/stencil `glReadPixels`/`glGetTexImage` by shader sampling on Espryt, instead of using the driver's own depth/stencil readback where it has one. | `0`, `1` | `0` | diff --git a/userguide/configuration.md b/userguide/configuration.md index c58fde62..f18f520d 100644 --- a/userguide/configuration.md +++ b/userguide/configuration.md @@ -74,6 +74,7 @@ environment too. The backend itself is the exception: the client asks for it (se | `MOBILEGL_MAGMA_R11G11B10F_FALLBACK` | flag | off | Use a fallback format for `GL_R11F_G11F_B10F` | | `MOBILEGL_MAGMA_MAX_DRAWS_PER_COMMAND_BUFFER` | 0-16777216 | 16384 | Submit a command buffer mid-frame after this many draws/dispatches (0 = never). Bounds driver memory in huge loading frames. | | `MOBILEGL_MAGMA_DESCRIPTOR_TRIM_FRAMES` | 0-1048576 | 120 | Free grown descriptor pools after this many quiet frames (0 = never) | +| `MOBILEGL_MAGMA_EXTRA_SWAPCHAIN_IMAGES` | 0-16 | 0 | Ask for this many swapchain images beyond the surface's minimum (capped at its maximum). The window buffer the display has just released is then reused later, when its release has usually signaled, so fewer frames wait for it. Each image costs a full-size colour buffer plus a depth/stencil buffer of the same size. | | `VK_ICD_FILENAMES` | path to an ICD JSON | loader default | Standard Vulkan loader variable: picks the Vulkan driver | ## Logging and diagnostics