From 46d1d5ffbe3d95029a9d4e2bd82bccdc8f0ddf70 Mon Sep 17 00:00:00 2001 From: vii Date: Wed, 7 Oct 2026 14:20:16 -0500 Subject: [PATCH] fix(graphics): retry swapchain creation with the game's present mode A driver can reject a present mode the device did not enable. Retry once with the mode the game asked for, except after errors another mode cannot fix. --- .../mocktail/graphics/present_mode_policy.h | 31 ++++++++ src/graphics/bionic_vulkan_loader_adapter.cc | 6 +- tests/present_mode_policy_test.cc | 77 +++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/include/mocktail/graphics/present_mode_policy.h b/include/mocktail/graphics/present_mode_policy.h index 0c32f1c..984c899 100644 --- a/include/mocktail/graphics/present_mode_policy.h +++ b/include/mocktail/graphics/present_mode_policy.h @@ -2,6 +2,7 @@ #define MOCKTAIL_GRAPHICS_PRESENT_MODE_POLICY_H_ #include +#include #include #include @@ -29,6 +30,36 @@ FilterPresentModes(PresentModePolicy policy, const char *PresentModePolicyName(PresentModePolicy policy); const char *PresentModeKhrName(VkPresentModeKHR mode); +// Creates the swapchain with the mode in `info`. A driver can reject a mode +// that the device did not enable, so a failed create is retried once with the +// mode the game asked for. `create` takes the info and returns a VkResult. +template +VkResult CreateSwapchainWithPresentModeFallback(VkSwapchainCreateInfoKHR &info, + VkPresentModeKHR requested, + Create create) { + const VkResult result = create(info); + if (result == VK_SUCCESS || info.presentMode == requested) { + return result; + } + switch (result) { + case VK_ERROR_OUT_OF_HOST_MEMORY: + case VK_ERROR_OUT_OF_DEVICE_MEMORY: + case VK_ERROR_DEVICE_LOST: + case VK_ERROR_SURFACE_LOST_KHR: + case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: + return result; // Another present mode cannot fix these. + default: + break; + } + std::fprintf(stderr, + " [vulkan] swapchain presentMode %s failed (%d); retrying " + "with %s\n", + PresentModeKhrName(info.presentMode), static_cast(result), + PresentModeKhrName(requested)); + info.presentMode = requested; + return create(info); +} + // Extra images so acquire does not wait on the compositor-held buffer. std::uint32_t PreferSwapchainMinImageCount(PresentModePolicy policy, std::uint32_t requested, diff --git a/src/graphics/bionic_vulkan_loader_adapter.cc b/src/graphics/bionic_vulkan_loader_adapter.cc index 4e86cd5..9dbc28d 100644 --- a/src/graphics/bionic_vulkan_loader_adapter.cc +++ b/src/graphics/bionic_vulkan_loader_adapter.cc @@ -2000,7 +2000,11 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateSwapchainKHR( } } const VkResult result = - host_create(device, &host_info, allocator, swapchain); + mocktail::graphics::CreateSwapchainWithPresentModeFallback( + host_info, create_info->presentMode, + [&](const VkSwapchainCreateInfoKHR& attempt) { + return host_create(device, &attempt, allocator, swapchain); + }); if (result != VK_SUCCESS) { return result; } diff --git a/tests/present_mode_policy_test.cc b/tests/present_mode_policy_test.cc index 6688b9a..c16260e 100644 --- a/tests/present_mode_policy_test.cc +++ b/tests/present_mode_policy_test.cc @@ -73,6 +73,83 @@ TEST(PresentModePolicyTest, RequestsExtraSwapchainImagesWhenUnthrottled) { 5U); } +TEST(PresentModePolicyTest, RetriesWithTheGamesModeWhenTheDriverRejectsOurs) { + VkSwapchainCreateInfoKHR info{}; + info.presentMode = VK_PRESENT_MODE_FIFO_LATEST_READY_KHR; + std::vector attempts; + const VkResult result = CreateSwapchainWithPresentModeFallback( + info, VK_PRESENT_MODE_FIFO_KHR, + [&](const VkSwapchainCreateInfoKHR &attempt) { + attempts.push_back(attempt.presentMode); + return attempt.presentMode == VK_PRESENT_MODE_FIFO_LATEST_READY_KHR + ? VK_ERROR_INITIALIZATION_FAILED + : VK_SUCCESS; + }); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(attempts, (std::vector{ + VK_PRESENT_MODE_FIFO_LATEST_READY_KHR, + VK_PRESENT_MODE_FIFO_KHR})); + EXPECT_EQ(info.presentMode, VK_PRESENT_MODE_FIFO_KHR); +} + +TEST(PresentModePolicyTest, DoesNotRetryWhenThereIsNothingToFallBackTo) { + VkSwapchainCreateInfoKHR info{}; + info.presentMode = VK_PRESENT_MODE_MAILBOX_KHR; + int calls = 0; + EXPECT_EQ(CreateSwapchainWithPresentModeFallback( + info, VK_PRESENT_MODE_FIFO_KHR, + [&](const VkSwapchainCreateInfoKHR &) { + ++calls; + return VK_SUCCESS; + }), + VK_SUCCESS); + EXPECT_EQ(calls, 1); + + info.presentMode = VK_PRESENT_MODE_FIFO_KHR; + calls = 0; + EXPECT_EQ(CreateSwapchainWithPresentModeFallback( + info, VK_PRESENT_MODE_FIFO_KHR, + [&](const VkSwapchainCreateInfoKHR &) { + ++calls; + return VK_ERROR_SURFACE_LOST_KHR; + }), + VK_ERROR_SURFACE_LOST_KHR); + EXPECT_EQ(calls, 1); +} + +TEST(PresentModePolicyTest, DoesNotRetryAfterErrorsAnotherModeCannotFix) { + for (const VkResult error : + {VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_OUT_OF_DEVICE_MEMORY, + VK_ERROR_DEVICE_LOST, VK_ERROR_SURFACE_LOST_KHR, + VK_ERROR_NATIVE_WINDOW_IN_USE_KHR}) { + VkSwapchainCreateInfoKHR info{}; + info.presentMode = VK_PRESENT_MODE_FIFO_LATEST_READY_KHR; + int calls = 0; + EXPECT_EQ(CreateSwapchainWithPresentModeFallback( + info, VK_PRESENT_MODE_FIFO_KHR, + [&](const VkSwapchainCreateInfoKHR &) { + ++calls; + return error; + }), + error); + EXPECT_EQ(calls, 1) << "error " << static_cast(error); + } +} + +TEST(PresentModePolicyTest, ReturnsTheRetrysResultWhenItAlsoFails) { + VkSwapchainCreateInfoKHR info{}; + info.presentMode = VK_PRESENT_MODE_FIFO_LATEST_READY_KHR; + int calls = 0; + EXPECT_EQ(CreateSwapchainWithPresentModeFallback( + info, VK_PRESENT_MODE_FIFO_KHR, + [&](const VkSwapchainCreateInfoKHR &) { + return ++calls == 1 ? VK_ERROR_INITIALIZATION_FAILED + : VK_ERROR_OUT_OF_HOST_MEMORY; + }), + VK_ERROR_OUT_OF_HOST_MEMORY); + EXPECT_EQ(calls, 2); +} + } // namespace } // namespace graphics } // namespace mocktail