Xbox LIVE Indie Games
Sort Discussions: Previous Discussion Next Discussion
Page 1 of 1 (8 posts)

IDXGISwapchain::Present compatibility break in Fall Creators Update?

Last post 11/20/2017 3:13 PM by Soldier of Light. 7 replies.
  • 11/7/2017 2:32 PM

    IDXGISwapchain::Present compatibility break in Fall Creators Update?

    Hi,
    I have a DX11 rendering stuff which is kind of an emulator for old games.
    However, after upgrading to Fall Creators Update me (and others) badly experienced that it isn't working properly anymore.
    I myself noticed that applications using this renderer lose the window focus, and some of them jumps into minimized state because of that. I traced down the cause.

    Every single call to IDXGISwapchain::Present checks if the window of the swapchain is occluded or not by other windows.
    If it is, and the swapchain is in fullscreen mode then DXGI force-switches the swapchain into windowed mode.
    DXGI::Present's occlusion detection algorhytm roughly does the following:
    First of all, it uses an invisible proxy window (named DXGIProxyWindow but renamed to D3DProxyWnd in FCU) for fullscreen state which is deeper in the window-z order list than the swapchain rendering window itself.
    Then, starting from the z-level of the proxy window, it steps upward in the z-hiearchy, window by window (max 200 steps) and checks every single window if that occludes the proxy one.
    It's how it worked so far (up to Creators Update).

    What changed in FCU is DXGI::Present now checks if the proxy window is fully occluded by another one, and if it is, then it not only switches the swapchain into windowed mode but even 'switches away' from the application, see the following code snippet:

    5EF6A220 FF 15 F8 40 FA 5E     call        dword ptr [__imp__GetForegroundWindow@0 (5EFA40F8h)]   
    5EF6A226 3B 86 C0 00 00 00    cmp         eax,dword ptr [esi+0C0h]   
    5EF6A22C 75 23                jne         ATL::CComObject<CDXGILightweightDevice>::Release+0F931h (5EF6A251h)   
    5EF6A22E FF 15 FC 40 FA 5E    call        dword ptr [__imp__GetDesktopWindow@0 (5EFA40FCh)]                                    <---- 
    5EF6A234 50                   push        eax   
    5EF6A235 FF 15 00 41 FA 5E    call        dword ptr [__imp__SetForegroundWindow@4 (5EFA4100h)]                                 <----  
    5EF6A23B 85 C0                test        eax,eax   
    5EF6A23D 75 12                jne         ATL::CComObject<CDXGILightweightDevice>::Release+0F931h (5EF6A251h)   
    5EF6A23F FF 15 40 20 FA 5E    call        dword ptr [__imp__GetLastError@0 (5EFA2040h)]   
    5EF6A245 51                   push        ecx   
    5EF6A246 68 10 B9 F3 5E       push        offset string "SetForegroundWindow failed to sw"... (5EF3B910h)                        <---- "SetForegroundWindow failed to switch away from the app" 
    5EF6A24B 50                   push        eax   
    5EF6A24C E8 6F DB FD FF       call        CModule::RecordJournalImpl (5EF47DC0h)   
    5EF6A251 6A 00                push        0   
    5EF6A253 6A 00                push        0   
    5EF6A255 8B CE                mov         ecx,esi   
    5EF6A257 E8 71 C1 FE FF       call        CDXGISwapChain::ScenarioEnterOrLeaveFullscreen (5EF563CDh)  

    My problem is that in FCU DXGI usually finds that the proxy window (which has the same size and position) is fully occluded by the swapchain rendering window.
    In other words, the swapchain window is occluded by itself, so DXGI 'switches away' from my application.

    I checked out one game with which the problem is steadily reproducible: on FCU, DXGI always run into that self-occlusion problem, but never on Win Creators Update.
    I can't really understand the difference because the occlusion detection function seems to be the same for both.
    In spite of that, on FCU the algorhytm finds its own window when stepping up in the z-hierachy, but not on CU. Something with the z-hiearchy, and on CU the cycle exits before finds its own window, I guess.

    As you can see on the screenshot, CheckOcclusion finds 'SplinterCellUnrealWWindowsViewportWindow' as the occluding window, but this one is the rendering window of the swapchain.
    Spy++ clearly shows that it's only 3 levels above the DXGI proxy window 'D3DProxyWindow' in the z-hiearchy. Even a message is recorded about it, "Occluder wnd ('Code' is HWND)...".
    On CU, DXGI never runs onto this path.


    My question is, is there anything I could do to avoid this case?
    If not, then can I expect Microsoft to fix it somehow because I cannot hack or workaround this from the driving side (my code).
  • 11/14/2017 4:23 PM In reply to

    Re: IDXGISwapchain::Present compatibility break in Fall Creators Update?

    I suspect the real problem is that either the D3DProxyWnd ended up topmost, or the application window ended up non-topmost.

    The occlusion logic that DXGI uses allows windows to be on top of the proxy window, but only if they're topmost and underneath the application window in the topmost band. So if you go fullscreen with some topmost window visible, the app should come on top of it (there are exceptions, e.g. the task manager's always-on-top mode), and DXGI's occlusion logic should ignore it. It's only if you bring a window to the front (whether or not it's topmost) that the occlusion logic should kick in.

    Are you somehow removing the topmost property from the application window?
  • 11/15/2017 8:08 PM In reply to

    Re: IDXGISwapchain::Present compatibility break in Fall Creators Update?

    No, I don't remove any property from the application window. It's a game window I get it from an external source and pass that to DXGI unchanged.
    I checked it all again, neither the game window nor D3DProxyWnd was topmost. I guess DXGI shouldn't/doesn't push a swapchain window into topmost state when the swapchain is switched into fullscreen mode.

    I also examined DXGI occlusion logic, in C++ code it is roughly the following:

    HRESULT TestOcclusion (HWND swapchainWnd, HWND d3dProxyWnd, bool* occluded) 
        { 
            RECT rect; 
            if (GetClientRect (swapchainWnd, &rect)) { 
                if (rect.left >= rect.right || rect.top >= rect.bottom) { 
                    return DXGI_ERROR_INVALID_CALL; 
                } 
            } 
        } 
         
        if (swapchainWnd == d3dProxyWnd) { 
            return S_OK; 
        } 
     
        if (IsWindowCloaked (d3dProxyWnd) || IsWindowCloaked (swapchainWnd)) { 
            RecordJournal ("App window cloaked", DXGI_ERROR_INVALID_CALL); 
            return DXGI_ERROR_INVALID_CALL; 
        } 
     
        DWORD param; 
        if (SystemParametersInfo (0xAA, 0, &param, 0)) { 
            if (param != 0) { 
                RecordJournal ("Lock screen active", DXGI_ERROR_INVALID_CALL); 
                return DXGI_ERROR_INVALID_CALL; 
            } 
        } 
     
        RECT rect; 
        GetWindowRect (d3dProxyWnd, &rect); 
        *occluded = false
     
        HWND currentWindow = d3dProxyWnd; 
        for (int i = 200; currentWindow != NULL && i != 0; i--) { 
            currentWindow = GetWindow (currentWindow, GW_HWNDPREV); 
            if (currentWindow != NULL) { 
                LONG exstyle = GetWindowLong (currentWindow, GWL_EXSTYLE); 
                if (exstyle & WS_EX_TOPMOST) { 
                    GetWindowRect (swapchainWnd, &rect); 
                } else { 
                    if (IsWindowVisible (currentWindow)) { 
                        WINDOWINFO wi; 
                        ZeroMemory (wi, sizeof (wi)); 
                        wi.cbSize = sizeof (wi); 
                        if (GetWindowInfo (currentWindow, &wi)) { 
                            if (!IsWindowTransparent (currentWindow, &wi)) { 
                                RECT dummy; 
                                if (IntersectRect (&dummy, &rect, &wi.rcWindow)) { 
                                    char className[256]; 
                                    if (GetClassName (currentWindow, className, 256)) { 
                                        if (strcmp (className, "Shell_TrayWnd") != 0 && 
                                            strcmp (className, "Shell_SecondaryTrayWnd") != 0 && 
                                            strcmp (className, "Snapped Desktop") != 0) { 
     
                                            RecordJournal ("Occluder wnd ('Code' is HWND):%s", DXGI_ERROR_INVALID_CALL); 
                                            *occluded = true
                                            return DXGI_ERROR_INVALID_CALL; 
                                        } 
                                    } 
                                } 
                            } 
                        } 
                    } 
                } 
            } 
        } 
        return S_OK; 

    It's true that occlusion logic filters out topmost windows. On the other side, I can only see a single for-cycle doing max 200 iterations, walking upward in the window z-order starting from D3DProxyWnd, and checking non-transparent windows if one of them is intersecting with the proxy window. If such a one is found then swapchain window is treated as "occluded", but the code doesn't even check if the window found is the same as the swapchain window (IMO it should be filtered out too).
    When I debugged both Windows Creators Update and Falls Creators Update to see the difference between them, in fact I only saw that proxy wnd was named 'DXGIProxyWnd' in CU and it was deeper in the z-order than the swapchain window. And, on CU, TestOcclusion never found its own swapchain window just because GetWindow (currentWindow, GW_HWNDPREV) returned NULL early, for some reason and the cycle was aborted.
  • 11/16/2017 6:21 PM In reply to

    Re: IDXGISwapchain::Present compatibility break in Fall Creators Update?

    The thing your analysis is missing is the logic around the WS_EX_TOPMOST checks.

            if (!bCheckingMainWindow) 
            { 
                LONG StyleEx = GetWindowLong(hwndChecking, GWL_EXSTYLE); 
                if (StyleEx & WS_EX_TOPMOST) 
                { 
                    bCheckingMainWindow = TRUE; 
                    hwndChecking = hAppWindow; 
                    GetWindowRect(hAppWindow, &RectToCheck); 
                    continue
                } 
            } 

    So once a topmost window is encountered, the topmost windows that are under the app window are ignored.

    Setting a swapchain to fullscreen does set the window to topmost if it wasn't already. I'd be interested if you could double-check the behavior on the CU to see if the application window ended up topmost there. And if not, is it possible that there was a temporary case where the window was occluded due to that, but then the app recovered, since it didn't lose focus?

    Can you share the app that we're talking about here?
  • 11/17/2017 9:22 AM In reply to

    Re: IDXGISwapchain::Present compatibility break in Fall Creators Update?

    Indeed, thanks for the clarification about the topmost state!

    Now, knowing all that, I checked out again what happens with this particular game, both on CU and FCU:

    1. The game creates a borderless window which is used as the swapchain rendering window in my driver. The window is non-topmost.
    2. My driver pushes the swapchain into fullscreen state (IDXGISwapChain::SetFullscreenState). DXGI resizes the window to cover the entire screen. Right after this step, the window also has the state WS_EX_TOPMOST. Everything is as expected.
    3. The game code resizes the window to the size of the screen (it's an old DX8 game, wrapped to DX11), so, in response DXGI pushes the swapchain back to windowed mode, WS_EX_TOPMOST state is lost.
    4. And now the difference between CU and FCU:
       CU: Since the window covers the entire screen and it's borderless, the game runs further in this "fake" fullscreen mode.
       FCU: DXGI's CheckOcclusion detects the swapchain window as the occluding one, and switches away from the application (the situation in my first post), generating a WM_ACTIVATEAPP message, so my driver minimizes the window in respond.

    I know that occlusion detection is done for windowed mode too. But I don't know if switching away from the application when the swapchain is in windowed mode is by design or a bug. I mean, DXGI may think the swapchain is in fullscreen state in spite of it was forced back to windowed mode earlier.

    Unfortunately the game I talking about (Splinter Cell) is currently the only one with which I myself can reproduce the issue but I got reports about others. I'd share what I have but it's 2GB. I may try to write a simple repro app instead.

  • 11/17/2017 5:27 PM In reply to

    Re: IDXGISwapchain::Present compatibility break in Fall Creators Update?

    DegeVoodoo:
    3. The game code resizes the window to the size of the screen (it's an old DX8 game, wrapped to DX11), so, in response DXGI pushes the swapchain back to windowed mode, WS_EX_TOPMOST state is lost.

    Are we sure that DXGI is the one causing the window to lose the WS_EX_TOPMOST property? Sounds like the app resizes its window, and in the process inadvertently sends the window back to non-topmost. DXGI does run some occlusion checks while windowed, but really just making sure the window's not minimized, so the fact that the occlusion check is kicking in sounds like DXGI still thinks it's fullscreen.

    I'd be interested to know what the result of IDXGISwapChain::GetFullscreenState is in stage 4 of the above on CU. My guess is that it's false - your game is actually running in a borderless windowed mode because the app window occluded its own fullscreen swapchain, and kicked it out of fullscreen.

    I think the fix here is to only apply the lose-focus logic when the occluding window belongs to another process. That should fix this scenario, along with any other potential scenarios where the app intentionally or unintentionally self-occludes and continues running as windowed.
  • 11/18/2017 11:48 AM In reply to

    Re: IDXGISwapchain::Present compatibility break in Fall Creators Update?

    Soldier of Light:
    Are we sure that DXGI is the one causing the window to lose the WS_EX_TOPMOST property?
    Well, not...
    Soldier of Light:
    Sounds like the app resizes its window, and in the process inadvertently sends the window back to non-topmost. DXGI does run some occlusion checks while windowed, but really just making sure the window's not minimized, so the fact that the occlusion check is kicking in sounds like DXGI still thinks it's fullscreen.
    You were right from the beginning. When I was writing the repro app, I realized that a simple window resize won't trigger DXGI to force it back to windowed mode. It was a misconception on my side, again.
    The only way was removing the window from the TOPMOST band intentionally. That's what the game does too, for some reason.

    Soldier of Light:

    I'd be interested to know what the result of IDXGISwapChain::GetFullscreenState is in stage 4 of the above on CU. My guess is that it's false - your game is actually running in a borderless windowed mode because the app window occluded its own fullscreen swapchain, and kicked it out of fullscreen.
    Yes, exactly that is what happens.
    I whipped up a test application to mimic the behavior of the game: http://dege.fw.hu/temp/OcclusionTest.zip
    It's animating a rotating triangle in full screen (quit only by Alt-F4). On CU it's running in fake fullscreen. On FCU it minimizes right after startup and cannot even be restored later.

    Now that I understand how occlusion check of DXGI works, I did another try: I hooked the window proc of the game to prevent removing the rendering window from the TOPMOST band when the swapchain is in fullscreen mode. And now it works properly! :)

    Soldier of Light:
    I think the fix here is to only apply the lose-focus logic when the occluding window belongs to another process. That should fix this scenario, along with any other potential scenarios where the app intentionally or unintentionally self-occludes and continues running as windowed.
    Yes, that'd be cool! But now I see that the real solution is not to modify the z-order of the swapchain window when it's in fullscreen because that conflicts with DXGI.

    I really appreciate your help in resolving my problem!! Very big thank to you!



  • 11/20/2017 3:13 PM In reply to

    Re: IDXGISwapchain::Present compatibility break in Fall Creators Update?

    Great! I'm glad we're able to understand what's going on here.

    I've filed a bug on our side to make the behavior change in FCU apply only to external occlusion cases, so if there are other apps affected by this behavior, that should resolve them. Of course, this will only prevent the apps from losing focus - they'll still get kicked out of exclusive fullscreen mode, just like they have since at least Windows 8, if not since the beginning of DXGI.

    But yes, in general once a swapchain has gone fullscreen, the app really shouldn't be messing with its window anymore - especially not changing its z-order. That's just asking for trouble.
Page 1 of 1 (8 posts) Previous Discussion Next Discussion