1

So I am trying to handle Alt+Tab in DirectX12 while in fullscreen correctly. The way I want it to behave is upon alt tab in fullscreen it is supposed to minimize the screen and then when you unminimize it is supposed to go back into fullscreen. (As this is what every modern video game does that handles it properly). So I expected this when using the SetFullscreenState swap chain method. However it transitions from fullscreen into windowed mode when Alt + Tab is pressed, which isn't desired.

I know I can use ShowWindow to take it to minimized by detecting WM_SIZE and a bool tracking what state the user wants the window to be in (fullscreen or not), to achieve the final result. However, you can clearly see the transition from fullscreen into the windowed mode before minimizing it and clearly see the minimization animation. Which is not what I see in other applications. So how do I use SetFullscreenState correctly so that Alt+Tab minimizes the screen without the whole to window transition then minimization animation.

What I do:

    isFullscreen = !isFullscreen;
    if( !FlushCommandQueue() ) //flushes the command queue
    {
        CloseProgram();
        return;
    }
    swapChain->SetFullscreenState(isFullscreen?TRUE:FALSE, NULL);

then in the WM_SIZE handling

        u32 temp_screen_width = LOWORD( LParam );
        u32 temp_screen_height = HIWORD( LParam );
        bool wasMinimized = isMinimized;
        if( WParam == SIZE_MINIMIZED || temp_screen_width == 0 || temp_screen_height == 0)
        {
            isMinimized = true;
        }
        else
        {
            isMinimized = false;
        }
        screen_width =  max( 1, temp_screen_width );
        screen_height = max( 1, temp_screen_height );

        viewport.Width = (f32)screen_width;
        viewport.Height = (f32)screen_height;

        scissorRect.right = screen_width;
        scissorRect.bottom = screen_height;

        if( swapChain ) 
        {
            BOOL fullscreenState;
            BOOL currentState = isFullscreen ? TRUE : FALSE;
            swapChain->GetFullscreenState( &fullscreenState, NULL );
            if( fullscreenState != currentState )
            {
                if( !wasMinimized )
                {
                    ShowWindow(Window,SW_MINIMIZE);
                }
            }
            ResizeBuffers();
            UpdateCurrentFrame();
            DrawScene();
            UpdateCurrentFrame();
        }
yosmo78
  • 489
  • 4
  • 13

1 Answers1

2

The simplest and most robust way to do 'fullscreen' is to just use a maximized borderless window. This is what I've implemented in my GitHub VS templates. This mimics what UWP does as well for 'fullscreen'.

// To start as 'fullscreen':
HWND hwnd = CreateWindowExW(WS_EX_TOPMOST, L"yourwindowclassname", g_szAppName, WS_POPUP,
    CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top,
    nullptr, nullptr, hInstance,
    nullptr);

Be sure to disable DXGI 'automatic' ALT+ENTER behavior:

hr = m_dxgiFactory->MakeWindowAssociation(m_window, DXGI_MWA_NO_ALT_ENTER);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static bool s_in_sizemove = false;
    static bool s_in_suspend = false;
    static bool s_minimized = false;
    static bool s_fullscreen = true;

    auto game = reinterpret_cast<Game*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));

    switch (message)
    {
...
    case WM_SIZE:
        if (wParam == SIZE_MINIMIZED)
        {
            if (!s_minimized)
            {
                s_minimized = true;
                if (!s_in_suspend && game)
                    game->OnSuspending();
                s_in_suspend = true;
            }
        }
        else if (s_minimized)
        {
            s_minimized = false;
            if (s_in_suspend && game)
                game->OnResuming();
            s_in_suspend = false;
        }
        else if (!s_in_sizemove && game)
        {
            game->OnWindowSizeChanged(LOWORD(lParam), HIWORD(lParam));
        }
        break;

    case WM_ENTERSIZEMOVE:
        s_in_sizemove = true;
        break;

    case WM_EXITSIZEMOVE:
        s_in_sizemove = false;
        if (game)
        {
            RECT rc;
            GetClientRect(hWnd, &rc);

            game->OnWindowSizeChanged(rc.right - rc.left, rc.bottom - rc.top);
        }
        break;


    case WM_SYSKEYDOWN:
        if (wParam == VK_RETURN && (lParam & 0x60000000) == 0x20000000)
        {
            // Implements the classic ALT+ENTER fullscreen toggle
            if (s_fullscreen)
            {
                SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
                SetWindowLongPtr(hWnd, GWL_EXSTYLE, 0);

                int width = 800;
                int height = 600;
                if (game)
                    game->GetDefaultSize(width, height);

                ShowWindow(hWnd, SW_SHOWNORMAL);

                SetWindowPos(hWnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED);
            }
            else
            {
                SetWindowLongPtr(hWnd, GWL_STYLE, WS_POPUP);
                SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST);

                SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);

                ShowWindow(hWnd, SW_SHOWMAXIMIZED);
            }

            s_fullscreen = !s_fullscreen;
        }
        break;

If you want to actually change the display screen resolution, this is where you'd make use of 'fullscreen state'. That said, keep in mind that LCDs look best at their native resolution, and that's typically what the system is already using. In that case, you can scale performance by creating a smaller render target, then scale it up to the 'real' resolution.

Also keep in mind that "Fullscreen Exclusive (FSE)" doesn't really exist for DirectX 12. It's always using eFSE which is just a borderless window. This does still let you change the resolution of the display.

Note that modern PC games typically offer three modes:

  1. "Fake" Full-screen which changes the display resolution, but is still using a borderless window. This greatly improves multi-tasking, multi-monitor scenarios, pop-ups, notifications, etc. This is essentially what 'eFSE' accomplishes. This is often the default.

  2. "Fullscreen exclusive" which is the classic 'true FSE' implementation, which has historically been need on Windows XP/Vista/7 for best performance and is for multi-GPU scenarios. As I said, however, with DX12 on Windows 10/11, this doesn't really exist and is always 'promoted' to eFSE. The performance should be basically the same on modern versions of Windows.

  3. Windowed mode where the game is just rendering in a bordered window. This one is particular popular when playing MMOs where the user is often referencing other applications, using third party chat, etc.

See this blog series and the official DX12 Fullscreen sample.

Chuck Walbourn
  • 38,259
  • 2
  • 58
  • 81
  • Does this handle minimizing Alt+Tab in fullscreen though? Also because DX12 has DXGI_SWAP_EFFECT_FLIP_DISCARD on the swap chain is that why it doesn't have true FSE, (It being eFSE, does that mean the performance isn't as good in fullscreen even though it has DXGI_SWAP_EFFECT_FLIP_DISCARD )? – yosmo78 May 07 '22 at 22:50
  • It's a good idea, at least in Debug mode, to allow Alt+Enter between eFSE and windowed mode to ensure that window size dependent resources are resizing correctly, for example in depth and SSAO passes. Speaking of which, I suppose this is an FYI for @ChuckWalbourn. I successfully implemented Luna's SSAO algorithm in DirectX Tool Kit, however when configuring the PSO for the normals & depth pass, the DXTK vertex type `VertexPositionNormal` throws a linker error. Other vertex types, such as `VertexPositionNormalTexture` are OK. – Maico De Blasio May 08 '22 at 08:02
  • Doh! You are quite right. Fixing it now. – Chuck Walbourn May 08 '22 at 08:04
  • 1
    @MaicoDeBlasio Issue tracked by this: https://github.com/microsoft/DirectXTK12/issues/128 Fixed. – Chuck Walbourn May 08 '22 at 08:49
  • @yosmo78, I believe eFSE matches traditional FSE frames per second performance, at least on modern PCs. – Maico De Blasio May 08 '22 at 09:03