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.
From: Brendan Shanks bshanks@codeweavers.com
--- dlls/winevulkan/vulkan.c | 11 ++++++++++- dlls/winevulkan/vulkan_private.h | 1 + 2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/dlls/winevulkan/vulkan.c b/dlls/winevulkan/vulkan.c index 61b3a42d63f..d4b9dd9ba02 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->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->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/dlls/winevulkan/vulkan_private.h b/dlls/winevulkan/vulkan_private.h index 59b900930b3..db0a72302f3 100644 --- a/dlls/winevulkan/vulkan_private.h +++ b/dlls/winevulkan/vulkan_private.h @@ -80,6 +80,7 @@ struct wine_phys_dev
uint32_t external_memory_align; uint32_t map_placed_align; + uint32_t swapchain_maintenance1; };
struct wine_debug_report_callback;
From: Brendan Shanks bshanks@codeweavers.com
--- dlls/win32u/vulkan.c | 54 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 5 deletions(-)
diff --git a/dlls/win32u/vulkan.c b/dlls/win32u/vulkan.c index 2e0c4627d71..868cd638078 100644 --- a/dlls/win32u/vulkan.c +++ b/dlls/win32u/vulkan.c @@ -269,9 +269,45 @@ 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 BOOL has_swapchain_maintenance1( struct vulkan_instance *instance, VkPhysicalDevice physical_device ) +{ + VkExtensionProperties *properties = NULL; + uint32_t num_properties; + BOOL ret = FALSE; + unsigned int i; + VkResult res; + + res = instance->p_vkEnumerateDeviceExtensionProperties( physical_device, NULL, &num_properties, NULL); + if (res != VK_SUCCESS) return FALSE; + + properties = calloc(num_properties, sizeof(*properties)); + if (!properties) return FALSE; + + res = instance->p_vkEnumerateDeviceExtensionProperties( physical_device, NULL, &num_properties, properties); + if (res != VK_SUCCESS) num_properties = 0; + + for (i = 0; i < num_properties; i++) + { + if (!strcmp(properties[i].extensionName, "VK_EXT_swapchain_maintenance1")) + { + ret = TRUE; + break; + } + } + + free(properties); + return ret; +} + 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 +316,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 +335,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 ) && + has_swapchain_maintenance1( instance, physical_device->host.physical_device )) + { + 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 +380,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 ) {
Rémi Bernon (@rbernon) commented about dlls/winevulkan/vulkan_private.h:
uint32_t external_memory_align; uint32_t map_placed_align;
- uint32_t swapchain_maintenance1;
This could probably be moved to `struct vulkan_physical_device` which is shared with win32u, so we then wouldn't need `has_swapchain_maintenance1`.