[PATCH v2 0/2] MR11058: winemac: Implement ExtEscape queries for Metal layer creation and release.
This exposes similar functionality to `vkCreateWin32SurfaceKHR`, just for projects that implement some sort of graphics backend directly on top of Metal that do not use Vulkan as an in-between layer, like DXMT. -- v2: winemac: Use MACDRV_ESCAPE_*_SURFACE in macdrv_vulkan_surface_create. winemac: Implement ExtEscape queries for Metal layer creation and release. https://gitlab.winehq.org/wine/wine/-/merge_requests/11058
From: Marc-Aurel Zent <mzent@codeweavers.com> --- dlls/winemac.drv/gdi.c | 60 +++++++++++++++++++++++++++++++++++++++ dlls/winemac.drv/macdrv.h | 14 +++++++++ 2 files changed, 74 insertions(+) diff --git a/dlls/winemac.drv/gdi.c b/dlls/winemac.drv/gdi.c index a0f314234b9..a7d9b4b1294 100644 --- a/dlls/winemac.drv/gdi.c +++ b/dlls/winemac.drv/gdi.c @@ -231,11 +231,71 @@ static INT macdrv_GetDeviceCaps(PHYSDEV dev, INT cap) } +/*********************************************************************** + * ExtEscape (MACDRV.@) + */ +static INT macdrv_ExtEscape(PHYSDEV dev, INT escape, INT in_count, LPCVOID in_data, + INT out_count, LPVOID out_data) +{ + switch (escape) + { + case QUERYESCSUPPORT: + if (in_data && in_count >= sizeof(DWORD)) + { + switch (*(const INT *)in_data) + { + case MACDRV_ESCAPE_GET_SURFACE: + case MACDRV_ESCAPE_RELEASE_SURFACE: + return TRUE; + } + } + break; + + case MACDRV_ESCAPE_GET_SURFACE: + { + struct macdrv_escape_surface *data = out_data; + struct macdrv_client_surface *surface; + HWND hwnd = NtUserWindowFromDC(dev->hdc); + + if (out_count < sizeof(*data)) return FALSE; + + if (!hwnd || !(surface = macdrv_client_surface_create(hwnd))) return FALSE; + + if (!macdrv_client_surface_acquire_metal_swapchain(surface)) + { + client_surface_release(&surface->client); + return FALSE; + } + + data->surface = (UINT_PTR)surface; + data->layer = (UINT_PTR)macdrv_swapchain_get_layer(surface->metal_swapchain); + return TRUE; + } + case MACDRV_ESCAPE_RELEASE_SURFACE: + { + const struct macdrv_escape_surface *data = in_data; + struct macdrv_client_surface *surface; + + if (in_count < sizeof(*data)) return FALSE; + + if (!data) return FALSE; + + surface = (struct macdrv_client_surface *)(UINT_PTR)data->surface; + if (surface) client_surface_release(&surface->client); + return TRUE; + } + } + + return FALSE; +} + + static const struct user_driver_funcs macdrv_funcs = { .dc_funcs.pCreateCompatibleDC = macdrv_CreateCompatibleDC, .dc_funcs.pCreateDC = macdrv_CreateDC, .dc_funcs.pDeleteDC = macdrv_DeleteDC, + .dc_funcs.pExtEscape = macdrv_ExtEscape, .dc_funcs.pGetDeviceCaps = macdrv_GetDeviceCaps, .dc_funcs.pGetDeviceGammaRamp = macdrv_GetDeviceGammaRamp, .dc_funcs.pSetDeviceGammaRamp = macdrv_SetDeviceGammaRamp, diff --git a/dlls/winemac.drv/macdrv.h b/dlls/winemac.drv/macdrv.h index 4f7107baf68..46d6a990a78 100644 --- a/dlls/winemac.drv/macdrv.h +++ b/dlls/winemac.drv/macdrv.h @@ -347,4 +347,18 @@ static inline UINT asciiz_to_unicode(WCHAR *dst, const char *src) return (p - dst) * sizeof(WCHAR); } +/* ExtEscape definitions */ + +enum macdrv_escape_codes +{ + MACDRV_ESCAPE_GET_SURFACE = 6790, + MACDRV_ESCAPE_RELEASE_SURFACE, +}; + +struct macdrv_escape_surface +{ + UINT64 surface; + UINT64 layer; +}; + #endif /* __WINE_MACDRV_H */ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11058
From: Marc-Aurel Zent <mzent@codeweavers.com> --- dlls/winemac.drv/vulkan.c | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/dlls/winemac.drv/vulkan.c b/dlls/winemac.drv/vulkan.c index f1643e3e5c4..5a7c78ab010 100644 --- a/dlls/winemac.drv/vulkan.c +++ b/dlls/winemac.drv/vulkan.c @@ -44,13 +44,16 @@ static VkResult macdrv_vulkan_surface_create(HWND hwnd, const struct vulkan_inst struct client_surface **client) { VkResult res; - struct macdrv_client_surface *surface; + struct macdrv_escape_surface surface_info = { 0 }; + HDC hdc = NtUserGetDC(hwnd); TRACE("%p %p %p %p\n", hwnd, instance, handle, client); - if (!(surface = macdrv_client_surface_create(hwnd))) return VK_ERROR_OUT_OF_HOST_MEMORY; - - if (!macdrv_client_surface_acquire_metal_swapchain(surface)) goto err; + if (!NtGdiExtEscape(hdc, NULL, 0, MACDRV_ESCAPE_GET_SURFACE, 0, NULL, sizeof(surface_info), (char *)&surface_info)) + { + NtUserReleaseDC(hwnd, hdc); + return VK_ERROR_INCOMPATIBLE_DRIVER; + } if (instance->p_vkCreateMetalSurfaceEXT) { @@ -58,7 +61,7 @@ static VkResult macdrv_vulkan_surface_create(HWND hwnd, const struct vulkan_inst create_info_host.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; create_info_host.pNext = NULL; create_info_host.flags = 0; /* reserved */ - create_info_host.pLayer = macdrv_swapchain_get_layer(surface->metal_swapchain); + create_info_host.pLayer = (const CAMetalLayer *)surface_info.layer; res = instance->p_vkCreateMetalSurfaceEXT(instance->host.instance, &create_info_host, NULL /* allocator */, handle); } @@ -68,23 +71,25 @@ static VkResult macdrv_vulkan_surface_create(HWND hwnd, const struct vulkan_inst create_info_host.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; create_info_host.pNext = NULL; create_info_host.flags = 0; /* reserved */ - create_info_host.pView = macdrv_swapchain_get_layer(surface->metal_swapchain); + create_info_host.pView = (const void *)surface_info.layer; res = instance->p_vkCreateMacOSSurfaceMVK(instance->host.instance, &create_info_host, NULL /* allocator */, handle); } if (res != VK_SUCCESS) { ERR("Failed to create MoltenVK surface, res=%d\n", res); - goto err; + NtGdiExtEscape(hdc, NULL, 0, MACDRV_ESCAPE_RELEASE_SURFACE, sizeof(surface_info), (const char *)&surface_info, 0, NULL); + res = VK_ERROR_INCOMPATIBLE_DRIVER; + } + else + { + struct macdrv_client_surface *surface = (struct macdrv_client_surface *)surface_info.surface; + *client = &surface->client; + TRACE("Created surface=0x%s, client=%p\n", wine_dbgstr_longlong(*handle), *client); } - *client = &surface->client; - TRACE("Created surface=0x%s, client=%p\n", wine_dbgstr_longlong(*handle), *client); - return VK_SUCCESS; - -err: - client_surface_release(&surface->client); - return VK_ERROR_INCOMPATIBLE_DRIVER; + NtUserReleaseDC(hwnd, hdc); + return res; } static VkBool32 macdrv_get_physical_device_presentation_support(struct vulkan_physical_device *physical_device, -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11058
On Wed Jun 3 10:26:25 2026 +0000, Rémi Bernon wrote:
I see, it's indeed better if doesn't depend on some internal struct, but it still feels quite brittle and difficult to maintain as there's no code user for this API in Wine source. It's then probably up to @julliard if we want to support such private interface. Fwiw I believe there's a precedent in winex11 with some escape codes being used by Gallium Nine, as IIUC it has been broken a couple times and I'm not sure how well we can maintain it in the future either. In v2 the API is now consumed in `macdrv_vulkan_surface_create`, so it should be less brittle now (and also serves a little bit as documentation for external projects as well).
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11058#note_143257
On Tue Jun 16 14:49:05 2026 +0000, Marc-Aurel Zent wrote:
In v2 the API is now consumed in `macdrv_vulkan_surface_create`, so it should be less brittle now (and also serves a little bit as documentation for external projects as well). Well as you can see it's been broken by https://gitlab.winehq.org/wine/wine/-/merge_requests/11093, and I'm sorry about it but I think it's more of an indication that it's not a good idea to integrate with Wine internal surface details that way.
It might be more future proof to integrate through Vulkan, for instance with a Vulkan extension to retrieve a Metal layer from a Vulkan swapchain. Ideally that extension would be an official one, but we could perhaps consider keeping it only in Wine if upstreaming such a Vulkan extension is problematic for some reason. Note as well that I don't think we can make any guarantee that every Vulkan swapchain will ever been implemented on top of Metal layers, so any consumer should be ready to handle cases where it's not. I expect this to be the case for instance whenever we will want to implement composited Vulkan swapchains, which will likely use fake swapchain and Vulkan images we control. I don't know how this could then work with third party D3D implementations that don't use Vulkan, I'm not sure it can, but I also don't see any way we can support that with current Wine architecture. Ultimately, and if Metal needs to be a first class citizen we would probably need to have some win32u public interface the same way we have GL and Vulkan, but I don't expect that to be an easy sell. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11058#note_143315
Well as you can see it's been broken by https://gitlab.winehq.org/wine/wine/-/merge_requests/11093, and I'm sorry about it but I think it's more of an indication that it's not a good idea to integrate with Wine internal surface details that way.
This was just a bit unfortunately timed... With the ExtEscape version the same change could have been made in the new place instead.
It might be more future proof to integrate through Vulkan, for instance with a Vulkan extension to retrieve a Metal layer from a Vulkan swapchain. Ideally that extension would be an official one, but we could perhaps consider keeping it only in Wine if upstreaming such a Vulkan extension is problematic for some reason.
Yeah I prototyped that approach, but it is a bit annoying for a few reasons... There are already a few Vulkan to Metal extensions, but the exact one we need here does not exist, and having to depend on a specific version of a Vulkan driver to actually make the D3D on Metal implementation possible was met with some pushback. Also, even after upstreaming such an extension, it would still have to be implemented and distributed by the Vulkan to Metal translation layers.
I don't know how this could then work with third party D3D implementations that don't use Vulkan, I'm not sure it can, but I also don't see any way we can support that with current Wine architecture.
I am not entirely sure what is meant with "composited Vulkan swapchains", but if it is related to implementing a compositor in Wine, wouldn't it make more sense to implement this on top of D3D?
Ultimately, and if Metal needs to be a first class citizen we would probably need to have some win32u public interface the same way we have GL and Vulkan, but I don't expect that to be an easy sell.
I actually do also have this version as a draft (and I this would be personally my preferred solution), but as you said maybe a harder sell... I can brush it up and open that as alternative to this MR, if that makes more sense. It adds user driver infrastructure similar to the GL and vulkan drivers and adds a PE winemetal DLL, that for now only exposes the hwnd to metal layer functionality, but could later down the line be a more complete implementation that fully replaces the unix part of DXMT. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11058#note_143325
I am not entirely sure what is meant with "composited Vulkan swapchains", but if it is related to implementing a compositor in Wine
Yes, I mean implementing compositing in Wine. For instance for proper child window clipping, GDI interop, DPI scaling, cross-process rendering and so on. Some of it may be delegated to the host compositing features, and we need to try to keep using as much possible for performance reasons, and as the most common cases that don't require compositing. But many exotic use cases can't reliably be implemented through the host composition, especially not in a sane way considering all the platforms we need to support.
wouldn't it make more sense to implement this on top of D3D?
I don't see how it would, this also includes client GL/VK/GDI interop, not just D3D. Pretty much has to live on the unix side, or be delegated to some other process but that's mostly the same. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11058#note_143326
This was just a bit unfortunately timed... With the ExtEscape version the same change could have been made in the new place instead.
Don't expect anything to be settled even at this point, I don't even yet have a clear vision of how things would end up and I'm mostly still untangling things and factoring code to win32u for practical reasons. I think it's mostly safe to assume that Vulkan swapchains would stay more or less stable, when direct rendering is possible (and end up onto Metal layers for macOS), because that's what we want for performance, but other than that nothing is settled and I don't think we can commit onto an ABI for external projects. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11058#note_143327
participants (3)
-
Marc-Aurel Zent -
Marc-Aurel Zent (@mzent) -
Rémi Bernon (@rbernon)