When Windows detected a fullscreen window occlusion, it will set the fullscreen window to windowed and return DXGI_STATUS_OCCLUDED in IDXGISwapChain::Present.
Signed-off-by: Zhiyi Zhang zzhang@codeweavers.com --- dlls/dxgi/swapchain.c | 64 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+)
diff --git a/dlls/dxgi/swapchain.c b/dlls/dxgi/swapchain.c index 9f8539fd28..d1df1ad726 100644 --- a/dlls/dxgi/swapchain.c +++ b/dlls/dxgi/swapchain.c @@ -265,6 +265,42 @@ BOOL dxgi_validate_swapchain_desc(const DXGI_SWAP_CHAIN_DESC1 *desc) return TRUE; }
+/* A correct occlusion detection method must consider following scenarios: + * + * 1. On multiple monitors system. For example, the full-screen is on primary display, any window on + * on secondary monitor shouldn't be able to occlude the full-screen window, regardless being foreground or active. + * 2. A non-Wine window, e.g., a X window occludes the full-screen window should be detected. + * + * Current method will incorrectly report occluded status where a window on a secondary monitor becomes foreground, + * which shouldn't be too harmful though. The actual implementation on Window probably has help from DWM. + * + * X11 VisibilityNotify is not useful here. The events doesn't work as expected. + */ +static BOOL dxgi_is_window_occluded(HWND hwnd) +{ + RECT fullscreen_rect; + RECT intersect_rect; + RECT window_rect; + HWND previous_hwnd; + + if (GetForegroundWindow() != hwnd) + return TRUE; + + /* Check if windows with higher z-order intersect with the full-screen window */ + GetWindowRect(hwnd, &fullscreen_rect); + previous_hwnd = GetWindow(hwnd, GW_HWNDPREV); + while (previous_hwnd) + { + GetWindowRect(previous_hwnd, &window_rect); + if (IntersectRect(&intersect_rect, &window_rect, &fullscreen_rect)) + return TRUE; + + previous_hwnd = GetWindow(previous_hwnd, GW_HWNDPREV); + } + + return FALSE; +} + static HRESULT dxgi_get_output_from_window(IDXGIAdapter *adapter, HWND window, IDXGIOutput **dxgi_output) { DXGI_OUTPUT_DESC desc; @@ -456,12 +492,27 @@ static HRESULT STDMETHODCALLTYPE d3d11_swapchain_GetDevice(IDXGISwapChain1 *ifac static HRESULT d3d11_swapchain_present(struct d3d11_swapchain *swapchain, unsigned int sync_interval, unsigned int flags) { + BOOL fullscreen = FALSE; + HRESULT hr; + HWND hwnd; + if (sync_interval > 4) { WARN("Invalid sync interval %u.\n", sync_interval); return DXGI_ERROR_INVALID_CALL; }
+ hr = IDXGISwapChain1_GetFullscreenState(&swapchain->IDXGISwapChain1_iface, &fullscreen, NULL); + if (SUCCEEDED(hr) && fullscreen) + { + hr = IDXGISwapChain1_GetHwnd(&swapchain->IDXGISwapChain1_iface, &hwnd); + if (SUCCEEDED(hr) && dxgi_is_window_occluded(hwnd)) + { + IDXGISwapChain1_SetFullscreenState(&swapchain->IDXGISwapChain1_iface, FALSE, NULL); + return DXGI_STATUS_OCCLUDED; + } + } + if (flags & ~DXGI_PRESENT_TEST) FIXME("Unimplemented flags %#x.\n", flags); if (flags & DXGI_PRESENT_TEST) @@ -2118,8 +2169,10 @@ static VkResult d3d12_swapchain_queue_present(struct d3d12_swapchain *swapchain, static HRESULT d3d12_swapchain_present(struct d3d12_swapchain *swapchain, unsigned int sync_interval, unsigned int flags) { + BOOL fullscreen = FALSE; VkQueue vk_queue; VkResult vr; + HWND hwnd; HRESULT hr;
if (sync_interval > 4) @@ -2128,6 +2181,17 @@ static HRESULT d3d12_swapchain_present(struct d3d12_swapchain *swapchain, return DXGI_ERROR_INVALID_CALL; }
+ hr = IDXGISwapChain3_GetFullscreenState(&swapchain->IDXGISwapChain3_iface, &fullscreen, NULL); + if (SUCCEEDED(hr) && fullscreen) + { + hr = IDXGISwapChain3_GetHwnd(&swapchain->IDXGISwapChain3_iface, &hwnd); + if (SUCCEEDED(hr) && dxgi_is_window_occluded(hwnd)) + { + IDXGISwapChain3_SetFullscreenState(&swapchain->IDXGISwapChain3_iface, FALSE, NULL); + return DXGI_STATUS_OCCLUDED; + } + } + if (flags & ~DXGI_PRESENT_TEST) FIXME("Unimplemented flags %#x.\n", flags); if (flags & DXGI_PRESENT_TEST)
On Tue, 23 Apr 2019 at 17:29, Zhiyi Zhang zzhang@codeweavers.com wrote:
+/* A correct occlusion detection method must consider following scenarios:
- On multiple monitors system. For example, the full-screen is on primary display, any window on
- on secondary monitor shouldn't be able to occlude the full-screen window, regardless being foreground or active.
- A non-Wine window, e.g., a X window occludes the full-screen window should be detected.
- Current method will incorrectly report occluded status where a window on a secondary monitor becomes foreground,
- which shouldn't be too harmful though. The actual implementation on Window probably has help from DWM.
- X11 VisibilityNotify is not useful here. The events doesn't work as expected.
- */
What is the actual issue with VisibilityNotify?
In any case, I imagine we should implement this on top of D3DKMTCheckOcclusion(). That would then still need to be implemented in gdi32, of course. It may also make more sense to check for swapchain occlusion in wined3d, since e.g. d3d9ex is also supposed to check for that.
On 4/24/19 12:51 AM, Henri Verbeet wrote:
On Tue, 23 Apr 2019 at 17:29, Zhiyi Zhang zzhang@codeweavers.com wrote:
+/* A correct occlusion detection method must consider following scenarios:
- On multiple monitors system. For example, the full-screen is on primary display, any window on
- on secondary monitor shouldn't be able to occlude the full-screen window, regardless being foreground or active.
- A non-Wine window, e.g., a X window occludes the full-screen window should be detected.
- Current method will incorrectly report occluded status where a window on a secondary monitor becomes foreground,
- which shouldn't be too harmful though. The actual implementation on Window probably has help from DWM.
- X11 VisibilityNotify is not useful here. The events doesn't work as expected.
- */
What is the actual issue with VisibilityNotify?
The feature that's needed here is determining whether a full-screen window is occluded, if it's then exit full-screen mode. The occlusion detection is implemented by DWM[1] and checked by IDXGISwapChain::Present[2]
The problem here is that to implement occlusion detection correctly 100%, we need something like VisibilityNotify events[3] on Linux. But for VisibilityNotify events to work reliably, we need to disable compositing. Modern window managers are compositing managers for those fancy visual effects, so they are using compositor by default, thus making VisibilityNotify events unreliable and even GTK stopped using them[4]. For some window managers, we could disable compositing when entering a full-screen app to get VisibilityNotify events working. But for GNOME Shell 3, I didn't find a method to disable its composition. And what's more, disabling compositing on DWM is per monitor while current Linux window managers are per screen. So if you disable compositing, you lose all the fancy effects on other monitors as well on Linux.
To test window manager compositing and VisibilityNotify events. You can see this commit[5] for commands to disable compositor. Then use `xev` to open a test window and try moving another window over it and see if VisibilityNotify events show up.
To pursue this window manager path, we need to adapt to different window managers to disable/enable compositor accordingly. If the window manager doesn't support disabling compositor, then we use a fallback method. The rationale behind why I am currently not touching the window manager side is that there are problems as stated above. Secondly, the fallback method works so well. So in the end, there is only the fallback method making this patch[6].
Feel free to let me know if there are better approaches.
[1]: D3DKMTCheckOcclusion (https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/d3dkmt...) [2]: IDXGISwapChain::Present (https://docs.microsoft.com/en-us/windows/desktop/api/dxgi/nf-dxgi-idxgiswapc...) [3]: VisibilityNotify (https://tronche.com/gui/x/xlib/events/window-state-change/visibility.html) [4]: gdk: Remove VisibilityNotify events (https://github.com/GNOME/gtk/commit/bd6b6ed93c075c2055dff5cdff6f85a886bc6571) [5]: disable the compositor for better performance (https://github.com/lutris/lutris/pull/881/commits/6dc34a53d7efa6f69c28029980...) [6]: dxgi: Implement fullscreen window occlusion detection. (https://source.winehq.org/patches/data/163529)
In any case, I imagine we should implement this on top of D3DKMTCheckOcclusion(). That would then still need to be implemented in gdi32, of course. It may also make more sense to check for swapchain occlusion in wined3d, since e.g. d3d9ex is also supposed to check for that.
Yes. I didn't notice there is a D3DKMTCheckOcclusion before. I will make the changes.
Thanks, Zhiyi
On Wed, 24 Apr 2019 at 06:29, Zhiyi Zhang zzhang@codeweavers.com wrote:
On 4/24/19 12:51 AM, Henri Verbeet wrote:
What is the actual issue with VisibilityNotify?
The problem here is that to implement occlusion detection correctly 100%, we need something like VisibilityNotify events[3] on Linux. But for VisibilityNotify events to work reliably, we need to disable compositing. Modern window managers are compositing managers for those fancy visual effects, so they are using compositor by default, thus making VisibilityNotify events unreliable and even GTK stopped using them[4]. For some window managers, we could disable compositing when entering a full-screen app to get VisibilityNotify events working. But for GNOME Shell 3, I didn't find a method to disable its composition. And what's more, disabling compositing on DWM is per monitor while current Linux window managers are per screen. So if you disable compositing, you lose all the fancy effects on other monitors as well on Linux.
To test window manager compositing and VisibilityNotify events. You can see this commit[5] for commands to disable compositor. Then use `xev` to open a test window and try moving another window over it and see if VisibilityNotify events show up.
Ok, so the issue is that we're not getting VisibilityNotify when the user is running a compositor. (For reference, VisibilityNotify events do work here on fvwm, but I can also confirm that running e.g. xcompmgr or compton effectively disables them.)
In any case, it looks like Vincent has provided a workable alternative suggestion in a parallel private thread. Ideally that discussion would have happened here on the public mailing list, but oh well.