Sign in to follow this  

Switching between full screen and windowed mode

Recommended Posts

Before creating my main window and swap chain (in Windows 8.1 and Direct3D 11), my application displays some graphics settings the user can configure such as choosing between windowed and full screen mode.

 

Based on the outcome, I create my main window via CreateWindow with the (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX) or WS_POPUP flags.

 

My DXGI_SWAP_CHAIN_DESC1 looks like:

swap_chain_desc.Width = width;
swap_chain_desc.Height = height;
swap_chain_desc.Format = g_device_enumeration->GetDisplayMode()->Format;
swap_chain_desc.SampleDesc.Count = 1;
swap_chain_desc.SampleDesc.Quality = 0;
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.BufferCount = 1;

Is this correct to use only 1 buffer in full screen mode? Or do I need to set this to at least 2 buffers?

 

 

Furthermore, I explicitly set:

dxgi_factory3->MakeWindowAssociation(m_hwindow, DXGI_MWA_NO_ALT_ENTER);

and do not set:

swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; 

This all avoids switching between full screen and windowed mode.

 

I wonder if it is possible to allow switching between full screen and windowed mode, but also between WS_OVERLAPPED and WS_POPUP without needing two different windows? For instance a resolution of 1600x900 in full screen mode can be switched to a resolution of 1600x900 in windowed mode that contains a border. When i stick to WS_POPUP, everything seems to work but I cannot move the window since there is no border (which is also a bit ugly in my opinion).

 

Games like Watch Dogs and Assassin's Creed allow a similar switching between modes, I think. Or do they position the border outside the visible view and just re-center the window when ALT+ENTER is pressed (if the user moved the window) so that they actually always operate in windowed mode?

 

Share this post


Link to post
Share on other sites

First, if you do not set allow mode switch "and do not set", then you deny dxgi to change the desktop resolution.

 

If you want to handle a window<>fullscreen switch, you should use  DXGI_MWA_NO_WINDOW_CHANGES in MakeWindowAssociation and handle the ResizeBuffers and SetFullscreenState yourself.

 

You can always update the window style at the transition, but usually, you can just ignore that as in fullscreen, windows enforce a no border style.

Share this post


Link to post
Share on other sites

Ok, so I added this again:

swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

and added this which is new for me (thank you):

dxgi_factory3->MakeWindowAssociation(m_hwindow, DXGI_MWA_NO_WINDOW_CHANGES);

and I now always use for registering the window:

WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX

I tested this approach while only rendering a background color each frame. Switching from windowed to full screen mode works fine. Switching from full screen mode to windowed mode produces a white window. So I guess this has something to do with the fact I currently do not resize my buffers. But why is this necessary, if you keep the same resolution?

 

P.S.: I also do not call the SetFullscreenState method since pressing ALT+ENTER seems to handle this already.

Edited by matt77hias

Share this post


Link to post
Share on other sites

You do not want to let Windows deal with alt enter and windows change. Any serious windowed/fullscreen switch code does it itself to control the process. So first :

MakeWindowAssociation(hwnd, DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_PRINT_SCREEN);

Then in your message loop :

case WM_MENUCHAR:
        return MNC_CLOSE<<16; // prevent windows to beep on alt enter from fullscreen
case WM_SYSKEYDOWN:
	if (wParam==VK_RETURN) {
		window->SetToggleScreen(); // code that set a flag in your app to signal that a request has been done
		return 0;
	}
	return DefWindowProc(hwnd,uMsg,wParam,lParam);

Then, you have to monitor for alt+tab and similar because you cannot prevent them to force you to leave fullscreen, so at the beginning of your app render loop.

// handle fullscreen switch
bool altTab = sc_.LostFS();
if (wnd_.ToggleScreen()||altTab) {
	sc_.Toggle(device_,!altTab);
	wnd_.ClearToggleScreen();
}

bool SwapChain::LostFS() {
	BOOL current = false;
	sc_->GetFullscreenState(&current, nullptr);
	return current == false && isFS_ == true;
}
void SwapChain::Toggle(Device& device, bool toggle, Output* output ) {
	buffers_.clear(); // Clear any reference to swap chain buffers or ResizeBuffers will fail

	BOOL current = false;
	if (toggle) { // the toggle case, skipped in case of alt tab as we just want to recreate the buffers
		sc_->GetFullscreenState(&current, nullptr);
		current = !current;
		sc_->SetFullscreenState(current, output ? output->Get() : nullptr );
	}
	sc_->ResizeBuffers(desc_.BufferCount, desc_.Width, desc_.Height, desc_.Format, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH);
	CreateRTVs(device); // recreate references

	sc_->GetFullscreenState(&current, nullptr); // windows may deny the change, test the current status
	isFS_ = current != 0;
}

Share this post


Link to post
Share on other sites

 

You do not want to let Windows deal with alt enter and windows change. Any serious windowed/fullscreen switch code does it itself to control the process. So first :

MakeWindowAssociation(hwnd, DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_PRINT_SCREEN);

Then in your message loop :

case WM_MENUCHAR:
        return MNC_CLOSE<<16; // prevent windows to beep on alt enter from fullscreen
case WM_SYSKEYDOWN:
	if (wParam==VK_RETURN) {
		window->SetToggleScreen(); // code that set a flag in your app to signal that a request has been done
		return 0;
	}
	return DefWindowProc(hwnd,uMsg,wParam,lParam);

Then, you have to monitor for alt+tab and similar because you cannot prevent them to force you to leave fullscreen, so at the beginning of your app render loop.

// handle fullscreen switch
bool altTab = sc_.LostFS();
if (wnd_.ToggleScreen()||altTab) {
	sc_.Toggle(device_,!altTab);
	wnd_.ClearToggleScreen();
}

bool SwapChain::LostFS() {
	BOOL current = false;
	sc_->GetFullscreenState(&current, nullptr);
	return current == false && isFS_ == true;
}
void SwapChain::Toggle(Device& device, bool toggle, Output* output ) {
	buffers_.clear(); // Clear any reference to swap chain buffers or ResizeBuffers will fail

	BOOL current = false;
	if (toggle) { // the toggle case, skipped in case of alt tab as we just want to recreate the buffers
		sc_->GetFullscreenState(&current, nullptr);
		current = !current;
		sc_->SetFullscreenState(current, output ? output->Get() : nullptr );
	}
	sc_->ResizeBuffers(desc_.BufferCount, desc_.Width, desc_.Height, desc_.Format, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH);
	CreateRTVs(device); // recreate references

	sc_->GetFullscreenState(&current, nullptr); // windows may deny the change, test the current status
	isFS_ = current != 0;
}

 

Thanks for the lost full screen situation. I was already wondering, while debugging, what I was doing wrong since I couldn't keep track of the fullscreen/window status myself. Once my renderloop started, my manual status seemed to be diverged for no clear reason.

 

At first, I also had some issues with calls to SetFullscreenState failing due to an 0x887a0004 error. I also got this error for creating a device if a non-nullptr pAdapter was passed. A bit strange though, since when I pass a nullptr and retrieve the adapter afterwards, the descriptor said it was the same adapter anyway. So apparently I now also pass D3D_FEATURE_LEVEL_11_0 instead of only D3D_FEATURE_LEVEL_11_1 for my GTX 960M (which was not necessary for my GTX 970).

 

So it now works nearly fine. Somehow I need to press enter twice (quickly enough), but I am still looking into it.

Share this post


Link to post
Share on other sites

Fix the problem (although still figuring out whether the solution is "correct")

 

Instead of your:

return current == false && isFS_ == true;

or (in my code):

return  m_fullscreen && IsWindowed();

I check for a full screen or windowed loss (i.e. m_fullscreen does not match the full screen state of the swap chain):

return  m_fullscreen == IsWindowed();
Edited by matt77hias

Share this post


Link to post
Share on other sites

Message delegate:

case WM_SYSKEYDOWN: {
    if (wParam == VK_RETURN) {
        g_engine->SetModeSwitchFlag(true);
        return 0;
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

Renderer setup:

dxgi_factory3>MakeWindowAssociation(m_hwindow, DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_PRINT_SCREEN);
...
swap_chain_desc.Flags              = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

Renderer setup (after swap chain creation but before RTV creation):

// Set mode (full screen or windowed).
BOOL current = g_device_enumeration->IsFullScreen();
m_swap_chain2->SetFullscreenState(current, nullptr);
m_swap_chain2->GetFullscreenState(&current, nullptr);
m_fullscreen = (current != 0);

Render loop:

// Handle switch between full screen and windowed mode.
const bool lost_full_screen = m_renderer->LostFullScreen();
if (m_mode_switch || lost_full_screen) {
    m_renderer->SwitchMode(!lost_full_screen);
    m_mode_switch = false;
}

Switch mode:

void Renderer::SwitchMode(bool toggle) {

        // Release the swap chain buffers.
        m_render_target_view->Release();

        BOOL current = false;
        if (toggle) {
            m_swap_chain2->GetFullscreenState(&current, nullptr);
            current = !current;
            m_swap_chain2->SetFullscreenState(current, nullptr);
        }

        // Recreate the swap chain buffers.
        m_swap_chain2->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH);
        SetupRenderTargetView();

        m_swap_chain2->GetFullscreenState(&current, nullptr);
        m_fullscreen = (current != 0);
    }

Windowed mode > Full screen > Windowed mode > Full Screen > ... works fine.

 

Full screen > Windowed mode: results in a white client area on the window which I cannot control anymore > Full screen: works fine 

Edited by matt77hias

Share this post


Link to post
Share on other sites

You should test every function error code to find where things get bad. One thing tho, Microsoft does not recommend to initialize directly in fullscreen, because as you know, it can be denied, and other unpleasant behavior. The proper way to start fullscreen is to start windowed and then call SetFullscreenState. You can keep your window hidden to not see it pop for an instant.

Share this post


Link to post
Share on other sites

You should test every function error code to find where things get bad. One thing tho, Microsoft does not recommend to initialize directly in fullscreen, because as you know, it can be denied, and other unpleasant behavior. The proper way to start fullscreen is to start windowed and then call SetFullscreenState. You can keep your window hidden to not see it pop for an instant.

 

I read that on MSDN but doesn't this only apply for a DXGI_SWAP_CHAIN_DESC which has an explicit Windowed member variable?

I use a DXGI_SWAP_CHAIN_DESC1 which does not seem to support full screen while creating the swap chain. So therefore, I do:

BOOL current = g_device_enumeration->IsFullScreen();
m_swap_chain2->SetFullscreenState(current, nullptr);
m_swap_chain2->GetFullscreenState(&current, nullptr);
m_fullscreen = (current != 0);

after the swap chain creation.

Share this post


Link to post
Share on other sites

In theory you can because there is an extra parameter, but it is not recommended, as you may not get it fullscreen anyway ( like if an other app has the focus when you request fullscreen ).

IDXGIFactory2::CreateSwapChainForHwnd( /**/ [in, optional] const DXGI_SWAP_CHAIN_FULLSCREEN_DESC *pFullscreenDesc, /**/)

You should not have much left to fix your last problem, log all the error codes, and potentially turn on the d3d debug device for any suspicious catches.

Share this post


Link to post
Share on other sites

Used:

#ifdef _DEBUG
        create_device_flags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

but so far does not result in some different behavior.

 

Furthermore, I logged all returned HRESULT values till the Present call (which is just changing the background color for testing). Never a FAILURE(...), always S_OK. Still two issues remain:

  1. Only resolution 1920x1080 of my possible output resolutions, results in a white background color when started in windowed mode. When switched to full screen mode, the correct background color is displayed. When switched to windowed mode again, the correct background color is displayed. (All other transitions starting from windowed mode at startup work fine).
  2. All transitions to windowed mode starting from fullscreen mode at startup result in a window, I could not move for example (this total loss of control is shown below). But I can still go back to full screen mode.

It is also strange since the only D3D11 related code corresponds to my SwitchMode call and a Render call (which does ClearRenderTargetView, ClearDepthStencilView, Present). (I didn't use the depth buffer, but released, renewed and rebound it anyway but no change).

 

A stripped down version can be found at http://bit.ly/2fllALW. The version uses a fixed 1600x900 resolution (which is probably different from the current resolution everyone is using so you are sure to see some difference) and starts in windowed mode by default (parameter to Engine constructor).

 

 

Clipboard01.png

Edited by matt77hias

Share this post


Link to post
Share on other sites

Ok, so I left this problem simmering for a while due to the absence of HRESULTs pointing in the right direction. Apparently, there was a clue in the output log, I overlooked: 

DXGI WARNING: IDXGISwapChain::Present: Fullscreen presentation inefficiencies incurred due to application not using IDXGISwapChain::ResizeBuffers appropriately, specifying a DXGI_MODE_DESC not available in IDXGIOutput::GetDisplayModeList, or not using DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH.DXGI_SWAP_CHAIN_DESC::BufferDesc = { 1600, 900, { 0, 0 }, R8G8B8A8_UNORM, 0, 0 }; DXGI_SWAP_CHAIN_DESC::SampleDesc = { 1, 0 }; DXGI_SWAP_CHAIN_DESC::Flags = 0x2; [ MISCELLANEOUS WARNING #98: ]

This runtime warning was triggered when I start in fullscreen mode and switch to windowed mode. In the other direction everything works fine (no flickering and no errors/warnings).

 

So based on the comments above and the few (identical) online samples, which iterate and switch the DXGI_MODE_DESCs and explicitly treat their first frame differently, I now always create my swap chain in windowed mode and switch to fullscreen before starting my frame loop (if desired). More specifically:

// Set the specified window's show state.
ShowWindow(m_hwindow, nCmdShow);

if (start_fullscreen) {
    m_renderer->SwitchMode(true);
}

// Enter the message loop.

This works fine for all switches between windowed and fullscreen mode.

 

The following code instead, still results in flickering:

if (start_fullscreen) {
    m_renderer->SwitchMode(true);
}

// Set the specified window's show state.
ShowWindow(m_hwindow, nCmdShow);

// Enter the message loop.

This behavior persists if you move the mode switch more upstream (Engine initialization, Renderer initialization, Swap Chain initialization). So somehow the ShowWindow affects the flickering.

 

Anyway, it works and a complete version can be found in this repo.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this