yosmo78
yosmo78

Reputation: 619

Alt Tab in Fullscreen with SetFullscreenState DirectX12 does not minimize window

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();
        }

Upvotes: 1

Views: 1338

Answers (1)

Chuck Walbourn
Chuck Walbourn

Reputation: 41047

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.

Upvotes: 2

Related Questions