How to properly switch to fullscreen with DXGI?

Started by
19 comments, last by DwarvesH 9 years, 2 months ago

So i finally managed to create a stable Hello World for DirectX 11 (for some reason 10 and 11 never wanted to cooperate with me).

Yet most of the basic functionality is not working as expected.

For starters switching to fullscreen. All the documentation and other posts on the Internet say that DirectX 10/11 is so easy and low maintenance compared to 9 and just let DXGI take care of everything. Well, things are finicky to say the least.

So for starters, I would like to get DXGI resolution fullscreen switching to work as expected.

Here is the scenario:

1. I have a RenderForm class that creates a window. Specifying WS_POPUP is key for the behavior I am experiencing:


handle = CreateWindowEx(WS_EX_APPWINDOW, L"D3D10WindowClass", aTitle.c_str(), WS_CLIPSIBLINGS | WS_CLIPCHILDREN 
		| WS_POPUP /*WS_OVERLAPPEDWINDOW*/, 0, 0, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, hInstance, nullptr);

2. To better test everything, I am running in "fake" fullscreen mode, so the window has no frame and has the same dimensions as my desktop. I get a FPS of 670.

3. I switch to fullscreen mode with DXGI (Alt-Enter). I get 630 FPS. Normally one expects FPS to go up when switching to fullscreen. The swap chain when created has the flag for mode switching. Without ti, if I change from a less than native resolution to fullscreen, I get ugly artifacts I have never seen before.


swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

If I create the swap chain in fullscreen mode directly, I get 1370 FPS. The framerate is almost double compared to when I start in fullscreen mode. But that is not the extent of the problems. If I switch from this initial fullscreen mode to windowed, I get a white screen. If I switch back again I get the 630 fullscreen mode, the original high performance one being lost.

I do have code to handle window resize the swap chain, but because of WS_POPUP it is never called. If I create the window with WS_OVERLAPPEDWINDOW, things go to hell. The fullscreen window content is all squashed horizontally in the middle of the screen.

I could upload the code, but I think it is better to determine the correct way to handle going to fullscreen with DXGI before and try it out to see how that behaves.

Advertisement

I found an easy way to reproduce the problem. Take any Rastertek tutorial as a sample, like this one:

http://www.rastertek.com/dx11tut04.html

Setting vsync off and changing the SystemClass::Initialize method to supply the correct maximum desktop resolution instead of 800x600 allow for easy reproduction.

All one needs to do is start the tutorial in windowed mode, switch to fullscreen and compare to it running in fullcreen mode directly. Every time I get only around 50% performance.

Your FPS don't matter as they're so high, and 670 to 630 means 95 microseconds which is nothing.

Display average frame-time in milliseconds or microseconds instead of the number of frames per second to get an accurate picture.

The GPU doesn't become faster in fullscreen. What it will do is use (usually) much better synchronization, to avoid artifacts from bad vsync etc.

It can often make sure that if one frame takes 10 ms, the next takes 13 ms, and then the third 10 ms again, they will all be displayed at 11 ms intervals if that matches the monitor refresh rate, instead of allowing such small random changes to cause stuttering.

DXGI can either be instructed to simply go into fullscreen mode on the current desktop resolution (default), or actually try to change the display mode to match your back-buffer resolution (mode-switch). I would recommend against mode switching.

Lastly, if the back-buffer size doesn't match the monitor size, it will be stretched. I guess this is where your artifacts come from. You need to handle for example WM_SIZE or in some other way detect when your window is resized, and when that happens Release your render target(s) and references to the back-buffer. Then call ResizeBuffers to resize the back-buffer to the new correct size and re-create your render target views and reset the view-port to the new size etc.

Your FPS don't matter as they're so high, and 670 to 630 means 95 microseconds which is nothing.

Display average frame-time in milliseconds or microseconds instead of the number of frames per second to get an accurate picture.

The GPU doesn't become faster in fullscreen. What it will do is use (usually) much better synchronization, to avoid artifacts from bad vsync etc.

It can often make sure that if one frame takes 10 ms, the next takes 13 ms, and then the third 10 ms again, they will all be displayed at 11 ms intervals if that matches the monitor refresh rate, instead of allowing such small random changes to cause stuttering.

Yes, the actual value is not important, but the general trend is. When switching to full-screen framerates is always higher. Not by much, but higher. I have 5 years of anecdotal evidence with this. Makes a lot of sense. In desktop mode even if you have the same bit depth, you color format might not match, while in fullscreen you change the color format too. Even if they are the same, in window mode Windows needs to blit you backbuffer to the window. This is a copy that takes a bit of time, plus the entire windowing system is involved. In exclusive fullscreen mode there should be no bliting under ideal circumstances.

But my trend is not up, but down. Something is terribly wrong. Even if I ignore this, the difference compounds. With vsync and 8x MSAA and an empty scene, my framerate drops from 60 to 38 when going fullscreen.

And even if I ignore this, if I start my application in fullscreen mode, and I hit Alt-Enter, I either get a white screen, or sometimes the rendering is frozen.

DXGI can either be instructed to simply go into fullscreen mode on the current desktop resolution (default), or actually try to change the display mode to match your back-buffer resolution (mode-switch). I would recommend against mode switching.

Why wouldn't you recommend against mode switching? It is the logical and best thing to do. Without it you are guaranteed to have a performance detriment when switching to fullscreen. Even if I change modes Present complains that I did not use ResizeBuffers when going to fullscreen mode. I can't use it, because DXGI is in charge of going fullscreen. It does not notify me of this change. I guess I could try to detect in the render loop if fullscreen mode has changed and call ResizeBuffers redundantly.

Lastly, if the back-buffer size doesn't match the monitor size, it will be stretched. I guess this is where your artifacts come from. You need to handle for example WM_SIZE or in some other way detect when your window is resized, and when that happens Release your render target(s) and references to the back-buffer. Then call ResizeBuffers to resize the back-buffer to the new correct size and re-create your render target views and reset the view-port to the new size etc.

No, I know how stretching looks like. My artifacts look like if scan-lines would be randomly shortened. So should DXGI be in charge of going fullscreen or not? Because if not, I'll disable the mode switch and do it myself. I can't work any worse than it does today...

Does anybody have a link to a very well behaved DirectX 10/11 tutorial in C++? Nothing fancy, just render a cube or triangle at most, but it should be super stable when going in fullscreen mode an back. And not rastertek, does are not stable for me.

Have you read http://msdn.microsoft.com/en-us/library/windows/desktop/bb205075%28v=vs.85%29.aspx#Care_and_Feeding_of_the_Swap_Chain ?


No, I know how stretching looks like. My artifacts look like if scan-lines would be randomly shortened. So should DXGI be in charge of going fullscreen or not? Because if not, I'll disable the mode switch and do it myself. I can't work any worse than it does today...


But my trend is not up, but down. Something is terribly wrong. Even if I ignore this, the difference compounds. With vsync and 8x MSAA and an empty scene, my framerate drops from 60 to 38 when going fullscreen.

The scanlines sound like a broken GPU or driver bug. Performance could be fullscreen somehow fails and thinks it's occluded by another window or something I guess..


Does anybody have a link to a very well behaved DirectX 10/11 tutorial in C++?

Found this test that I did a couple of years ago.. still works well for me on Win7. Numpad + and - changes between available modes, and standard DXGI alt enter for fullscreen.

I know that they have changed some things for Win8 and store apps so if you're using that you probably want to read the docs on the new swap effects and format requirements...


#include <windows.h>
#include <D3D11.h>
#include <stdio.h>

#pragma comment (lib, "D3D11.lib")

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// Main
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	// Register windowclass
	WNDCLASSEX wc;
	ZeroMemory(&wc, sizeof(wc));
	wc.cbSize = sizeof(wc);
	wc.lpszClassName = TEXT("MyClass");
	wc.hInstance = hInstance;
	wc.lpfnWndProc = WndProc;
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	RegisterClassEx(&wc);
	
	// Create window
	HWND hWnd = CreateWindow(
		wc.lpszClassName,
		TEXT("D3D11 Window"),
		WS_OVERLAPPEDWINDOW |
			WS_CLIPSIBLINGS |
			WS_CLIPCHILDREN,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		NULL,
		NULL,
		hInstance,
		NULL
	);
	
	// Create device and swapchain
	DXGI_SWAP_CHAIN_DESC scd;
	IDXGISwapChain *pSwapChain;
	ID3D11Device *pDevice;
	ID3D11DeviceContext *pDeviceContext;
	
	ZeroMemory(&scd, sizeof(scd));
	scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	scd.SampleDesc.Count = 1;
	scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	scd.BufferCount = 1;
	scd.OutputWindow = hWnd;
	scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	scd.Windowed = TRUE;
	scd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
	
	HRESULT hResult = D3D11CreateDeviceAndSwapChain(
		NULL,
		D3D_DRIVER_TYPE_HARDWARE,
		NULL,
		D3D11_CREATE_DEVICE_DEBUG,
		NULL,
		0,
		D3D11_SDK_VERSION,
		&scd,
		&pSwapChain,
		&pDevice,
		NULL,
		&pDeviceContext
	);
	if(FAILED(hResult)) {
		MessageBox(NULL, TEXT("D3D11CreateDeviceAndSwapChain"), TEXT("D3D11CreateDeviceAndSwapChain"), MB_OK);
		return 0;
	}
	
	// Render target
	ID3D11Texture2D *pBackBuffer;
	ID3D11RenderTargetView *pRTV;
	D3D11_TEXTURE2D_DESC backBufferDesc;
	
	pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&pBackBuffer);
	pDevice->CreateRenderTargetView(pBackBuffer, NULL, &pRTV);
	pBackBuffer->GetDesc(&backBufferDesc);
	pBackBuffer->Release();
	
	// Mode switching
	UINT currentMode = 0;
	bool modeChanged = false;
	BOOL currentFullscreen = FALSE;
	pSwapChain->GetFullscreenState(&currentFullscreen, NULL);
	
	// Main loop
	ShowWindow(hWnd, nCmdShow);
	
	bool loop = true;
	while(loop) {
		MSG msg;
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) != 0) {
			if(msg.message == WM_QUIT)
				loop = false;
			else {
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}
		else {
			// Check for mode change input
			bool changeMode = false;
			if(GetAsyncKeyState(VK_ADD) & 0x8000) {
				if(!modeChanged) {
					++currentMode;
					changeMode = true;
				}
			}
			else if(GetAsyncKeyState(VK_SUBTRACT) & 0x8000) {
				if(!modeChanged) {
					--currentMode;
					changeMode = true;
				}
			}
			else
				modeChanged = false;
			
			// Change mode
			bool changedThisFrame = false;
			
			if(changeMode) {
				IDXGIOutput *pOutput;
				pSwapChain->GetContainingOutput(&pOutput);
				
				UINT numModes = 1024;
				DXGI_MODE_DESC modes[1024];
				pOutput->GetDisplayModeList(scd.BufferDesc.Format, 0, &numModes, modes);
				
				if(currentMode < numModes) {
					DXGI_MODE_DESC mode = modes[currentMode];
					
					TCHAR str[255];
					wsprintf(str, TEXT("Switching to mode: %u / %u, %ux%u@%uHz (%u, %u, %u)\n"),
						currentMode+1,
						numModes,
						mode.Width,
						mode.Height,
						mode.RefreshRate.Numerator / mode.RefreshRate.Denominator,
						mode.Scaling,
						mode.ScanlineOrdering,
						mode.Format
					);
					OutputDebugString(str);
					
					pSwapChain->ResizeTarget(&(modes[currentMode]));
					
					changedThisFrame = true;
				}
				
				modeChanged = true;
			}
			
			// Check fullscreen state
			BOOL newFullscreen;
			pSwapChain->GetFullscreenState(&newFullscreen, NULL);
			
			// Resize if needed
			RECT rect;
			GetClientRect(hWnd, &rect);
			UINT width = static_cast<UINT>(rect.right);
			UINT height = static_cast<UINT>(rect.bottom);
			
			if(width != backBufferDesc.Width || height != backBufferDesc.Height || changedThisFrame || newFullscreen != currentFullscreen) {
				pDeviceContext->ClearState();
				pRTV->Release();
				
				pSwapChain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH);
				
				// Recreate render target
				pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&pBackBuffer);
				pDevice->CreateRenderTargetView(pBackBuffer, NULL, &pRTV);
				pBackBuffer->GetDesc(&backBufferDesc);
				pBackBuffer->Release();
			}
			
			// Remember fullscreen state
			currentFullscreen = newFullscreen;
			
			// Clear backbuffer
			float color[4] = {1.0f, 0.0f, 1.0f, 1.0f};
			pDeviceContext->ClearRenderTargetView(pRTV, color);
			
			// Present
			pSwapChain->Present(1, 0);
		}
	}
	
	// Release
	pSwapChain->SetFullscreenState(FALSE, NULL);
	
	pDeviceContext->ClearState();
	
	pRTV->Release();
	pDeviceContext->Release();
	
	ID3D11Debug *debugInterface = NULL;
	//hResult = pDevice->QueryInterface(__uuidof(ID3D11Debug), reinterpret_cast<void**>(&debugInterface));
	//if(FAILED(hResult)) {
	//}
	
	pDevice->Release();
	pSwapChain->Release();
	
	if(debugInterface != NULL) {
		debugInterface->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
		
		debugInterface->Release();
	}
	
	UnregisterClass(wc.lpszClassName, hInstance);
	
	return 0;
}

// Window procedure
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	switch(msg) {
		case WM_DESTROY:
			PostQuitMessage(0);
		return 0;
	}
	
	return DefWindowProc(hWnd, msg, wParam, lParam);
}

Thank you Erik!

That sample is indeed a lot more stable! I will try something like that detection of mode change on Monday. But I did test it on my home computers and things are looking good.

Except for starting up in fullscreen mode. If I put:


scd.Windowed = FALSE;

The application starts correctly, but first Alt-Enter leaves me with a nonfunctional window. Second crashes.

@Erik Rufelt: Can you throw that code into a GitHub / Codeplex repository? It would be great to have a few samples to point at when questions like these come up (which honestly seems to be more frequent than expected...).


The application starts correctly, but first Alt-Enter leaves me with a nonfunctional window. Second crashes.

It's better to start in window mode and then switch to fullscreen. It's discussed under Remarks here: http://msdn.microsoft.com/en-us/library/windows/desktop/bb174537%28v=vs.85%29.aspx

I updated the sample to allow starting to fullscreen and changed the "unknown" format for the back-buffer which is probably better if there are different formats (like 30-bit support on some modes). Check the link below for the new code.


@Erik Rufelt: Can you throw that code into a GitHub / Codeplex repository? It would be great to have a few samples to point at when questions like these come up (which honestly seems to be more frequent than expected...).

Sure!
https://github.com/rufelt/simpled3d11window

Holly shit! A stable DX11 app! Thank you! That was far harder than it needed to be. DirectX 9 is easier smile.png.

The only bit of instability I noticed is that if I start with MSAA and FRAPS and a debugger, I'll get a segfault on application exit when the swap chain is destroyed. I'll continue testing for a couple of days and also try to integrate it with my Application class and RenderForm class.

It's better to start in window mode and then switch to fullscreen. It's discussed under Remarks here: http://msdn.microsoft.com/en-us/library/windows/desktop/bb174537%28v=vs.85%29.aspx

GG Microsoft. I saw nothing that wouldn't lead me to believe that DXGI is a bit shit.

I updated the sample to allow starting to fullscreen and changed the "unknown" format for the back-buffer


What do you mean by "unknown" format?

This topic is closed to new replies.

Advertisement