[PATCH v3 0/3] MR9585: win32u: Introduce a D3DKMT escape code to set fullscreen present rect. (1/2)
These calls are Wine-specific and expected to fail on Windows, as the escape codes and parameter combinations are likely invalid there. There doesn't seem to be any mechanism to do this otherwise. When a window presentation rect has been set, the window is meant to be presented to that rect on screen exactly, mapped to physical coords. Then this also has the following side effects: * In exclusive fullscreen mode, GDI drawn child windows are clipped out of the rendered screen, regardless of the normal clipping rules. * The window position and size changes should be ignored wrt its visible area, and in particular the window should not be unmapped even if its Win32 rect is moved to an invisible location. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58844 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58443 --- This only introduces the mechanism and calls it from Wine D3D side. The side effects will be implemented later in win32u and the user drivers. This is necessary both for Command & Conquer 2, which has dialog child windows covering the entire game window while using WS_CLIPCHILDREN, and for Fallout 3 which does a bogus call sequence after window restoration which moves the window outside of screen area and causes its window and client rects to be empty: ``` SetRect(&rect, 0, 0, width, height); AdjustWindowRectEx(&rect, style, FALSE, 0); SetWindowPos(hwnd, 0, rect.left, rect.bottom, rect.right - rect.left, rect.top - rect.bottom, 0); ``` We need either this kind of mechanism (which could be later changed to some D3DKMTPresent call, but it would require a lot more work and I'd like to fix those regressions before the release), or to hook the window proc like modern Windows seems to be doing as tested in !9549 (and more tests suggest that invalid window moves are also now inhibited and corrected on modern Windows). -- v3: wined3d: Use the present rather than client rect when fullscreen. wined3d: Set the window present rect when entering fullscreen mode. win32u: Introduce a D3DKMT escape code to set fullscreen present rect. https://gitlab.winehq.org/wine/wine/-/merge_requests/9585
From: Rémi Bernon <rbernon(a)codeweavers.com> These calls are Wine-specific and expected to fail on Windows, as the escape codes and parameter combinations are likely invalid there. There doesn't seem to be any mechanism to do this otherwise. When a window presentation rect has been set, the window is meant to be presented to that rect on screen exactly, mapped to physical coords. Then this also has the following side effects: * In exclusive fullscreen mode, GDI drawn child windows are clipped out of the rendered screen, regardless of the normal clipping rules. * The window position and size changes should be ignored wrt its visible area, and in particular the window should not be unmapped even if its Win32 rect is moved to an invisible location. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58844 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58443 --- dlls/win32u/d3dkmt.c | 17 +++++++++++++++++ dlls/win32u/ntuser_private.h | 1 + include/ddk/d3dkmthk.h | 1 + 3 files changed, 19 insertions(+) diff --git a/dlls/win32u/d3dkmt.c b/dlls/win32u/d3dkmt.c index 8da6886154a..baf99f69291 100644 --- a/dlls/win32u/d3dkmt.c +++ b/dlls/win32u/d3dkmt.c @@ -547,6 +547,23 @@ NTSTATUS WINAPI NtGdiDdDDIEscape( const D3DKMT_ESCAPE *desc ) return d3dkmt_object_update( &resource->obj, desc->pPrivateDriverData, desc->PrivateDriverDataSize ); } + case D3DKMT_ESCAPE_SET_PRESENT_RECT_WINE: + { + HWND hwnd = UlongToHandle( desc->hContext ); + RECT *rect = desc->pPrivateDriverData; + UINT dpi = get_dpi_for_window( hwnd ); + WND *win; + + if (desc->PrivateDriverDataSize != sizeof(*rect)) return STATUS_INVALID_PARAMETER; + + TRACE( "hwnd %p, rect %s\n", hwnd, wine_dbgstr_rect( rect ) ); + if (!(win = get_win_ptr( hwnd ))) return STATUS_INVALID_PARAMETER; + win->present_rect = map_dpi_rect( *rect, get_thread_dpi(), dpi ); + release_win_ptr( win ); + + return STATUS_SUCCESS; + } + default: FIXME( "(%p): stub\n", desc ); return STATUS_NO_MEMORY; diff --git a/dlls/win32u/ntuser_private.h b/dlls/win32u/ntuser_private.h index 14dcff3ad38..697e4c74203 100644 --- a/dlls/win32u/ntuser_private.h +++ b/dlls/win32u/ntuser_private.h @@ -51,6 +51,7 @@ typedef struct tagWND HINSTANCE hInstance; /* Window hInstance (from CreateWindow) */ struct window_rects rects; /* window rects in window DPI, relative to the parent client area */ RECT normal_rect; /* Normal window rect saved when maximized/minimized */ + RECT present_rect; /* present rect for exclusive fullscreen mode */ POINT min_pos; /* Position for minimized window */ POINT max_pos; /* Position for maximized window */ WCHAR *text; /* Window text */ diff --git a/include/ddk/d3dkmthk.h b/include/ddk/d3dkmthk.h index 9f7e6d55a3d..a4da022e9aa 100644 --- a/include/ddk/d3dkmthk.h +++ b/include/ddk/d3dkmthk.h @@ -770,6 +770,7 @@ typedef enum _D3DKMT_ESCAPETYPE D3DKMT_ESCAPE_DIAGNOSTICS, /* Wine-specific escape codes */ D3DKMT_ESCAPE_UPDATE_RESOURCE_WINE = 0x80000000, + D3DKMT_ESCAPE_SET_PRESENT_RECT_WINE = 0x80000001, } D3DKMT_ESCAPETYPE; typedef struct _D3DKMT_ESCAPE -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9585
From: Rémi Bernon <rbernon(a)codeweavers.com> And unset it when exiting fullscreen. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58844 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58443 --- dlls/wined3d/swapchain.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/dlls/wined3d/swapchain.c b/dlls/wined3d/swapchain.c index a1000ec2393..f19f51d0e51 100644 --- a/dlls/wined3d/swapchain.c +++ b/dlls/wined3d/swapchain.c @@ -27,6 +27,19 @@ WINE_DEFAULT_DEBUG_CHANNEL(d3d); WINE_DECLARE_DEBUG_CHANNEL(d3d_perf); +static BOOL set_window_present_rect(HWND hwnd, UINT x, UINT y, UINT width, UINT height) +{ + RECT rect = {x, y, x + width, y + height}; + D3DKMT_ESCAPE escape = {0}; + + escape.Type = D3DKMT_ESCAPE_SET_PRESENT_RECT_WINE; + escape.hContext = HandleToULong(hwnd); + escape.pPrivateDriverData = ▭ + escape.PrivateDriverDataSize = sizeof(rect); + + return !D3DKMTEscape(&escape); +} + void wined3d_swapchain_cleanup(struct wined3d_swapchain *swapchain) { HRESULT hr; @@ -2258,6 +2271,8 @@ HRESULT wined3d_swapchain_state_setup_fullscreen(struct wined3d_swapchain_state return WINED3DERR_NOTAVAILABLE; } + set_window_present_rect(window, x, y, width, height); + if (!(s = malloc(sizeof(*s)))) return E_OUTOFMEMORY; s->window = window; @@ -2301,6 +2316,8 @@ void wined3d_swapchain_state_restore_from_fullscreen(struct wined3d_swapchain_st struct wined3d_window_state *s; LONG style, exstyle; + set_window_present_rect(window, 0, 0, 0, 0); + if (!state->style && !state->exstyle) return; @@ -2444,6 +2461,9 @@ HRESULT CDECL wined3d_swapchain_state_set_fullscreen(struct wined3d_swapchain_st HWND window = state->device_window; BOOL filter; + set_window_present_rect(state->device_window, output_desc.desktop_rect.left, + output_desc.desktop_rect.top, width, height); + /* Fullscreen -> fullscreen mode change */ filter = wined3d_filter_messages(window, TRUE); MoveWindow(window, output_desc.desktop_rect.left, output_desc.desktop_rect.top, width, -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9585
From: Rémi Bernon <rbernon(a)codeweavers.com> The client rect might be empty or invalid. Fallout 3 in particular does a bogus call sequence after restoring minimized window which causes the window and client rects to be empty: SetRect(&rect, 0, 0, width, height); AdjustWindowRectEx(&rect, style, FALSE, 0); SetWindowPos(hwnd, 0, rect.left, rect.bottom, rect.right - rect.left, rect.top - rect.bottom, 0); We either need to hook the window messages and inhibit such window size changes, or we need to avoid relying on client and window rect when in fullscreen mode. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58844 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58443 --- dlls/wined3d/context_gl.c | 18 ++++++++++++++++++ dlls/wined3d/swapchain.c | 29 +++++++++++++++++++++++++++-- dlls/wined3d/texture.c | 15 +++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/dlls/wined3d/context_gl.c b/dlls/wined3d/context_gl.c index ac1b9370fc1..223652a51cc 100644 --- a/dlls/wined3d/context_gl.c +++ b/dlls/wined3d/context_gl.c @@ -2393,6 +2393,24 @@ static void wined3d_context_gl_get_rt_size(const struct wined3d_context_gl *cont { RECT window_size; + if (!rt->swapchain->state.desc.windowed) + { + struct wined3d_display_mode actual_mode; + struct wined3d_output *output; + HRESULT hr; + + if (!(output = wined3d_swapchain_get_output(rt->swapchain))) + ERR("Failed to get swapchain output.\n"); + else if (FAILED(hr = wined3d_output_get_display_mode(output, &actual_mode, NULL))) + ERR("Failed to get display mode, hr %#lx.\n", hr); + else + { + size->cx = actual_mode.width; + size->cy = actual_mode.height; + return; + } + } + GetClientRect(context_gl->window, &window_size); size->cx = window_size.right - window_size.left; size->cy = window_size.bottom - window_size.top; diff --git a/dlls/wined3d/swapchain.c b/dlls/wined3d/swapchain.c index f19f51d0e51..26aea32759d 100644 --- a/dlls/wined3d/swapchain.c +++ b/dlls/wined3d/swapchain.c @@ -40,6 +40,31 @@ static BOOL set_window_present_rect(HWND hwnd, UINT x, UINT y, UINT width, UINT return !D3DKMTEscape(&escape); } +static void get_swapchain_present_rect(struct wined3d_swapchain *swapchain, RECT *rect) +{ + if (!swapchain->state.desc.windowed) + { + struct wined3d_display_mode actual_mode; + struct wined3d_output_desc output_desc; + struct wined3d_output *output; + HRESULT hr; + + if (!(output = wined3d_swapchain_get_output(swapchain))) + ERR("Failed to get swapchain output.\n"); + else if (FAILED(hr = wined3d_output_get_desc(output, &output_desc))) + ERR("Failed to get output description, hr %#lx.\n", hr); + else if (FAILED(hr = wined3d_output_get_display_mode(output, &actual_mode, NULL))) + ERR("Failed to get display mode, hr %#lx.\n", hr); + else + SetRect(rect, output_desc.desktop_rect.left, output_desc.desktop_rect.top, + output_desc.desktop_rect.left + actual_mode.width, + output_desc.desktop_rect.top + actual_mode.height); + return; + } + + GetClientRect(swapchain->win_handle, rect); +} + void wined3d_swapchain_cleanup(struct wined3d_swapchain *swapchain) { HRESULT hr; @@ -233,7 +258,7 @@ HRESULT CDECL wined3d_swapchain_present(struct wined3d_swapchain *swapchain, if (!dst_rect) { - GetClientRect(swapchain->win_handle, &d); + get_swapchain_present_rect(swapchain, &d); dst_rect = &d; } @@ -581,7 +606,7 @@ static bool swapchain_present_is_partial_copy(struct wined3d_swapchain *swapchai if (swap_effect != WINED3D_SWAP_EFFECT_COPY && swap_effect != WINED3D_SWAP_EFFECT_COPY_VSYNC) return false; - GetClientRect(swapchain->win_handle, &client_rect); + get_swapchain_present_rect(swapchain, &client_rect); t = client_rect.right - client_rect.left; if ((dst_rect->left && dst_rect->right) || abs(dst_rect->right - dst_rect->left) != t) diff --git a/dlls/wined3d/texture.c b/dlls/wined3d/texture.c index dd61a2f0d3c..0d2ccea4541 100644 --- a/dlls/wined3d/texture.c +++ b/dlls/wined3d/texture.c @@ -82,6 +82,21 @@ void wined3d_texture_translate_drawable_coords(const struct wined3d_texture *tex } GetClientRect(window, &windowsize); + + if (!texture->swapchain->state.desc.windowed) + { + struct wined3d_display_mode actual_mode; + struct wined3d_output *output; + HRESULT hr; + + if (!(output = wined3d_swapchain_get_output(texture->swapchain))) + ERR("Failed to get swapchain output.\n"); + else if (FAILED(hr = wined3d_output_get_display_mode(output, &actual_mode, NULL))) + ERR("Failed to get display mode, hr %#lx.\n", hr); + else + SetRect(&windowsize, 0, 0, actual_mode.width, actual_mode.height); + } + drawable_height = windowsize.bottom - windowsize.top; rect->top = drawable_height - rect->top; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9585
Well, the point is, it's always going to be the full monitor, isn't it?
Perhaps, but I don't think it really matters as this is a custom and probably a temporary mechanism. Ultimately we might want to do things differently but here I'm only aiming at something simple that works for Wine 11, not something perfect that will take time to design properly. We have the rect at hand, while getting a monitor would require something more complicated possibly with more unforeseen issues.
Yeah, like I said it's just unimportant bikeshedding.
Right, we also need that call. I think it's still better arrangement to have calls in those three places.
Sure, changed it to this.
Thanks! Henri pointed out a couple other things: * "present_rect" is a bit of an unfortunate name, since that could refer to one or more of the rects passed to wined3d_swapchain_present(). "drawable_rect" would be more in line with existing terminology. * The GetClientRect() usage replaced by patch 3/3 exists because native d3d will present to the client rect of a window, even if it's resized without resizing the swapchain. However, the same is *not* true of fullscreen mode—if the application or user changes the mode, that loses the d3d device, and causes subsequent presentation attempts to fail until the swapchain is recreated. The upshot of this is that the whole of get_swapchain_present_rect() is pretty much unnecessary; you can just use the swapchain size directly. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9585#note_124753
participants (2)
-
Elizabeth Figura (@zfigura) -
Rémi Bernon