If the swapchain image size is not equal to the presentation size (e.g. because of DPI virtualization or display mode change emulation), MoltenVK's `vkQueuePresentKHR` returns `VK_SUBOPTIMAL_KHR` (see [`MVKSwapchain::hasOptimalSurface()`](https://github.com/KhronosGroup/MoltenVK/blob/a046e779df332f5c23a03df1cb6de4...). I can see this with a vkcube hacked to run in fullscreen, and it's especially problematic with wined3d which recreates the swapchain on `VK_SUBOPTIMAL_KHR` (every frame).
Create the swapchain with `VkSwapchainPresentScalingCreateInfoEXT` when the window is being scaled to avoid this. I'm not sure if it's proper to be enabling the extension in winevulkan and then using it in win32u, but I don't see an obvious way to hook `vkCreateDevice` in win32u.
-- v2: win32u: Create Vulkan swapchains with VkSwapchainPresentScalingCreateInfoEXT when the surface will be scaled. winevulkan: Enable VK_EXT_swapchain_maintenance1 when available.
From: Brendan Shanks bshanks@codeweavers.com
--- dlls/winevulkan/vulkan.c | 11 ++++++++++- include/wine/vulkan_driver.h | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/dlls/winevulkan/vulkan.c b/dlls/winevulkan/vulkan.c index 61b3a42d63f..ac93931092b 100644 --- a/dlls/winevulkan/vulkan.c +++ b/dlls/winevulkan/vulkan.c @@ -384,6 +384,8 @@ static VkResult wine_vk_physical_device_init(struct wine_phys_dev *object, VkPhy have_external_memory_host = TRUE; else if (!strcmp(host_properties[i].extensionName, "VK_EXT_map_memory_placed")) have_memory_placed = TRUE; + else if (!strcmp(host_properties[i].extensionName, "VK_EXT_swapchain_maintenance1")) + object->obj.has_swapchain_maintenance1 = true; else if (!strcmp(host_properties[i].extensionName, "VK_KHR_map_memory2")) have_map_memory2 = TRUE; } @@ -545,7 +547,7 @@ static VkResult wine_vk_device_convert_create_info(VkPhysicalDevice client_physi struct conversion_context *ctx, const VkDeviceCreateInfo *src, VkDeviceCreateInfo *dst) { struct wine_phys_dev *phys_dev = wine_phys_dev_from_handle(client_physical_device); - const char *extra_extensions[2], * const*extensions = src->ppEnabledExtensionNames; + const char *extra_extensions[3], * const*extensions = src->ppEnabledExtensionNames; unsigned int i, extra_count = 0, extensions_count = src->enabledExtensionCount;
*dst = *src; @@ -590,6 +592,13 @@ static VkResult wine_vk_device_convert_create_info(VkPhysicalDevice client_physi extra_extensions[extra_count++] = "VK_EXT_external_memory_host"; }
+ /* win32u uses VkSwapchainPresentScalingCreateInfoEXT if available. */ + if (phys_dev->obj.has_swapchain_maintenance1) + { + if (!find_extension(extensions, extensions_count, "VK_EXT_swapchain_maintenance1")) + extra_extensions[extra_count++] = "VK_EXT_swapchain_maintenance1"; + } + if (extra_count) { const char **new_extensions; diff --git a/include/wine/vulkan_driver.h b/include/wine/vulkan_driver.h index 55613f3211f..0579ea7773f 100644 --- a/include/wine/vulkan_driver.h +++ b/include/wine/vulkan_driver.h @@ -21,6 +21,7 @@ #define __WINE_VULKAN_DRIVER_H
#include <stdarg.h> +#include <stdbool.h> #include <stddef.h>
#include <windef.h> @@ -90,6 +91,7 @@ struct vulkan_physical_device { VULKAN_OBJECT_HEADER( VkPhysicalDevice, physical_device ); struct vulkan_instance *instance; + bool has_swapchain_maintenance1; };
static inline struct vulkan_physical_device *vulkan_physical_device_from_handle( VkPhysicalDevice handle )
From: Brendan Shanks bshanks@codeweavers.com
--- dlls/win32u/vulkan.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-)
diff --git a/dlls/win32u/vulkan.c b/dlls/win32u/vulkan.c index 2e0c4627d71..722bdd845cf 100644 --- a/dlls/win32u/vulkan.c +++ b/dlls/win32u/vulkan.c @@ -269,9 +269,15 @@ static VkBool32 win32u_vkGetPhysicalDeviceWin32PresentationSupportKHR( VkPhysica return driver_funcs->p_vkGetPhysicalDeviceWin32PresentationSupportKHR( physical_device->host.physical_device, queue ); }
+static BOOL extents_equals( const VkExtent2D *extents, const RECT *rect ) +{ + return extents->width == rect->right - rect->left && extents->height == rect->bottom - rect->top; +} + static VkResult win32u_vkCreateSwapchainKHR( VkDevice client_device, const VkSwapchainCreateInfoKHR *create_info, const VkAllocationCallbacks *allocator, VkSwapchainKHR *ret ) { + VkSwapchainPresentScalingCreateInfoEXT scaling = {.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_SCALING_CREATE_INFO_EXT}; struct swapchain *swapchain, *old_swapchain = swapchain_from_handle( create_info->oldSwapchain ); struct surface *surface = surface_from_handle( create_info->surface ); struct vulkan_device *device = vulkan_device_from_handle( client_device ); @@ -280,6 +286,7 @@ static VkResult win32u_vkCreateSwapchainKHR( VkDevice client_device, const VkSwa VkSwapchainCreateInfoKHR create_info_host = *create_info; VkSurfaceCapabilitiesKHR capabilities; VkSwapchainKHR host_swapchain; + RECT client_rect; VkResult res;
if (!NtUserIsWindow( surface->hwnd )) @@ -298,6 +305,18 @@ static VkResult win32u_vkCreateSwapchainKHR( VkDevice client_device, const VkSwa create_info_host.imageExtent.width = max( create_info_host.imageExtent.width, capabilities.minImageExtent.width ); create_info_host.imageExtent.height = max( create_info_host.imageExtent.height, capabilities.minImageExtent.height );
+ /* If the swapchain image size is not equal to the presentation size (e.g. because of DPI virtualization or + * display mode change emulation), MoltenVK's vkQueuePresentKHR returns VK_SUBOPTIMAL_KHR. + * Create the swapchain with VkSwapchainPresentScalingCreateInfoEXT to avoid this. + */ + if (NtUserGetClientRect( surface->hwnd, &client_rect, NtUserGetWinMonitorDpi( surface->hwnd, MDT_RAW_DPI ) ) && + !extents_equals( &create_info_host.imageExtent, &client_rect ) && + physical_device->has_swapchain_maintenance1) + { + scaling.scalingBehavior = VK_PRESENT_SCALING_STRETCH_BIT_EXT; + create_info_host.pNext = &scaling; + } + if (!(swapchain = calloc( 1, sizeof(*swapchain) ))) return VK_ERROR_OUT_OF_HOST_MEMORY;
if ((res = device->p_vkCreateSwapchainKHR( device->host.device, &create_info_host, NULL, &host_swapchain ))) @@ -331,11 +350,6 @@ void win32u_vkDestroySwapchainKHR( VkDevice client_device, VkSwapchainKHR client free( swapchain ); }
-static BOOL extents_equals( const VkExtent2D *extents, const RECT *rect ) -{ - return extents->width == rect->right - rect->left && extents->height == rect->bottom - rect->top; -} - static VkResult win32u_vkAcquireNextImage2KHR( VkDevice client_device, const VkAcquireNextImageInfoKHR *acquire_info, uint32_t *image_index ) {
On Thu Aug 7 22:58:30 2025 +0000, Brendan Shanks wrote:
changed this line in [version 2 of the diff](/wine/wine/-/merge_requests/8718/diffs?diff_id=199320&start_sha=c6c8d93418864258118170130c9d9add6951767b#05000c6e183209e030beb05f7baba884c43ccc3e_83_83)
Yes that simplifies things, thanks!
This merge request was approved by Rémi Bernon.