[PATCH 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). -- 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/device.c | 16 ++++++++++++++++ dlls/wined3d/swapchain.c | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/dlls/wined3d/device.c b/dlls/wined3d/device.c index 443bf10ab76..fb515e8c3be 100644 --- a/dlls/wined3d/device.c +++ b/dlls/wined3d/device.c @@ -4979,6 +4979,19 @@ static void update_swapchain_flags(struct wined3d_texture *texture) texture->flags &= ~WINED3D_TEXTURE_GET_DC; } +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); +} + HRESULT CDECL wined3d_device_reset(struct wined3d_device *device, const struct wined3d_swapchain_desc *swapchain_desc, const struct wined3d_display_mode *mode, wined3d_device_reset_cb callback, BOOL reset_state) @@ -5135,6 +5148,9 @@ HRESULT CDECL wined3d_device_reset(struct wined3d_device *device, swapchain_state->style = 0; swapchain_state->exstyle = 0; + set_window_present_rect(swapchain_state->device_window, output_desc.desktop_rect.left, + output_desc.desktop_rect.top, swapchain_desc->backbuffer_width, + swapchain_desc->backbuffer_height); wined3d_swapchain_state_setup_fullscreen(swapchain_state, swapchain_state->device_window, output_desc.desktop_rect.left, output_desc.desktop_rect.top, swapchain_desc->backbuffer_width, swapchain_desc->backbuffer_height); diff --git a/dlls/wined3d/swapchain.c b/dlls/wined3d/swapchain.c index a1000ec2393..d1f835fa809 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; @@ -78,12 +91,14 @@ void wined3d_swapchain_cleanup(struct wined3d_swapchain *swapchain) { wined3d_swapchain_state_restore_from_fullscreen(&swapchain->state, swapchain->state.device_window, &swapchain->state.original_window_rect); + set_window_present_rect(swapchain->state.device_window, 0, 0, 0, 0); wined3d_device_release_focus_window(swapchain->device); } } else { wined3d_swapchain_state_restore_from_fullscreen(&swapchain->state, swapchain->state.device_window, NULL); + set_window_present_rect(swapchain->state.device_window, 0, 0, 0, 0); } } } @@ -1559,6 +1574,8 @@ static HRESULT wined3d_swapchain_init(struct wined3d_swapchain *swapchain, struc goto err; } + set_window_present_rect(window, output_desc.desktop_rect.left, output_desc.desktop_rect.top, + desc->backbuffer_width, desc->backbuffer_height); wined3d_swapchain_state_setup_fullscreen(&swapchain->state, window, output_desc.desktop_rect.left, output_desc.desktop_rect.top, desc->backbuffer_width, desc->backbuffer_height); @@ -2432,6 +2449,8 @@ HRESULT CDECL wined3d_swapchain_state_set_fullscreen(struct wined3d_swapchain_st return hr; } + set_window_present_rect(state->device_window, output_desc.desktop_rect.left, + output_desc.desktop_rect.top, width, height); if (state->desc.windowed) { /* Switch from windowed to fullscreen */ @@ -2460,6 +2479,7 @@ HRESULT CDECL wined3d_swapchain_state_set_fullscreen(struct wined3d_swapchain_st if (state->desc.flags & WINED3D_SWAPCHAIN_RESTORE_WINDOW_RECT) window_rect = &state->original_window_rect; wined3d_swapchain_state_restore_from_fullscreen(state, state->device_window, window_rect); + set_window_present_rect(state->device_window, 0, 0, 0, 0); } state->desc.output = swapchain_desc->output; -- 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 d1f835fa809..f6b71021bd4 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; @@ -235,7 +260,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; } @@ -583,7 +608,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
Anything I should do here? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9585#note_124283
Anything I should do here?
No, sorry, it's just a major US holiday, after which I caught a cold and was bedridden for a few days. I am actually very glad to see work on this; this is a very old problem that we've wanted to fix for ages. See also bugs 2082, 29301. Here's my comments on 2/3: * At the risk of making a review comment before fully understanding the problem myself, do we actually need to be passing all the dimensions? We even have a D3DKMT device handle we could be passing. Though this is bikeshedding, which isn't really warranted at this point. * However, I think the right thing to do is to make this call from wined3d_swapchain_state_setup_fullscreen() and wined3d_swapchain_state_restore_from_fullscreen(), rather than their callers. I haven't gotten to 3/3 yet; that'll require more research on my part. I'd also definitely appreciate review from Henri and/or Stefan as they were quite involved in this bug in the past. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9585#note_124515
Wait, I'm confused. win->present_rect isn't used anywhere. Are we missing some changes here? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9585#note_124516
At the risk of making a review comment before fully understanding the problem myself, do we actually need to be passing all the dimensions? We even have a D3DKMT device handle we could be passing. Though this is bikeshedding, which isn't really warranted at this point.
D3DKMT device only map to a GPU, not really to a monitor. In any case, I don't think going through a proper D3DKMT route is feasible right now, we're missing too much infrastructure and I'm not even sure we want to bypass the host WSI completely (which a D3DKMTPresent would probably need to do). Passing the full present rectangle to win32u helps it decide how to map the window surface onto the screen, there's additional issues on the win32u side related to display mode emulation, that need to be handled properly, and having a full rect helps I think.
However, I think the right thing to do is to make this call from wined3d_swapchain_state_setup_fullscreen() and wined3d_swapchain_state_restore_from_fullscreen(), rather than their callers.
I don't think so, `wined3d_swapchain_state_setup_fullscreen` is only called once when entering fullscreen and not when fullscreen mode changes. We need to update the present rect in that case as well, so win32u can keep the window fullscreen.
Wait, I'm confused. win-\>present_rect isn't used anywhere. Are we missing some changes here?
Yes, like I said only introduces the mechanism and uses it from wined3d. I wanted to keep things simple to make it easier to review the client side API. Implementing the bits on win32u / winex11 is fairly straightforward after that. I've pushed the additional changes to https://gitlab.winehq.org/rbernon/wine/-/commits/mr/9585. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9585#note_124517
At the risk of making a review comment before fully understanding the problem myself, do we actually need to be passing all the dimensions? We even have a D3DKMT device handle we could be passing. Though this is bikeshedding, which isn't really warranted at this point.
D3DKMT device only map to a GPU, not really to a monitor. In any case, I don't think going through a proper D3DKMT route is feasible right now, we're missing too much infrastructure and I'm not even sure we want to bypass the host WSI completely (which a D3DKMTPresent would probably need to do).
Er, right, I meant the video present node source ID, that's the monitor as I understand.
Passing the full present rectangle to win32u helps it decide how to map the window surface onto the screen, there's additional issues on the win32u side related to display mode emulation, that need to be handled properly, and having a full rect helps I think.
Well, the point is, it's always going to be the full monitor, isn't it?
However, I think the right thing to do is to make this call from wined3d_swapchain_state_setup_fullscreen() and wined3d_swapchain_state_restore_from_fullscreen(), rather than their callers.
I don't think so, `wined3d_swapchain_state_setup_fullscreen` is only called once when entering fullscreen and not when fullscreen mode changes. We need to update the present rect in that case as well, so win32u can keep the window fullscreen.
Right, we also need that call. I think it's still better arrangement to have calls in those three places.
Wait, I'm confused. win-\>present_rect isn't used anywhere. Are we missing some changes here?
Yes, like I said only introduces the mechanism and uses it from wined3d. I wanted to keep things simple to make it easier to review the client side API. Implementing the bits on win32u / winex11 is fairly straightforward after that. I've pushed the additional changes to https://gitlab.winehq.org/rbernon/wine/-/commits/mr/9585.
Thanks. I know my review isn't requested for those parts, but I do also need to do things like run tests as part of the d3d review. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9585#note_124638
participants (2)
-
Elizabeth Figura (@zfigura) -
Rémi Bernon