Correct way of creating the swapchain fullscreen/windowed/VSync about refresh rate

Started by
4 comments, last by Alundra 7 years ago

Hi,
When you create the swapchain you have to set the numerator and the denominator for the refresh rate.
Here what MSDN says :

The DXGI_RATIONAL structure operates under the following rules:

  • 0/0 is legal and will be interpreted as 0/1.
  • 0/anything is interpreted as zero.
  • If you are representing a whole number, the denominator should be 1.

What is the good values for fullscreen and windowed, also about the VSync ?
When I call "present" I do like that :


m_SwapChain->Present( m_VSync ? 1 : 0, 0 );

Is it correct to only use 1 as VSync interval ?
What is the real meaning of 0/1, is it bad to use it ?
What is the good way to find the good refresh rate ?
Apparently, MSDN also recommend to do that after CreateSwapChain :


// Detect if newly created full-screen swap chain isn't actually full screen.
IDXGIOutput* pTarget; BOOL bFullscreen;
if (SUCCEEDED(pSwapChain->GetFullscreenState(&bFullscreen, &pTarget)))
{
  pTarget->Release();
}
else
  bFullscreen = FALSE;
// If not full screen, enable full screen again.
if (!bFullscreen)
{
  ShowWindow(hWnd, SW_MINIMIZE);
  ShowWindow(hWnd, SW_RESTORE);
  pSwapChain->SetFullscreenState(TRUE, NULL);
}

Also that :

Because the target output can't be chosen explicitly when the swap chain is created, we recommend not to create a full-screen swap chain. This can reduce presentation performance if the swap chain size and the output window size do not match. Here are two ways to ensure that the sizes match:

  • Create a windowed swap chain and then set it full-screen using IDXGISwapChain::SetFullscreenState.
  • Save a pointer to the swap chain immediately after creation, and use it to get the output window size during a WM_SIZE event. Then resize the swap chain buffers (with IDXGISwapChain::ResizeBuffers) during the transition from windowed to full-screen.

Last point :

If the swap chain is in full-screen mode, before you release it you must use SetFullscreenState to switch it to windowed mode.

Thanks

Advertisement

I'm going to write what will hopefully be some reference material here, so, sorry if this ends up a little long-winded.

At a high-level, a swapchain is a mechanism for getting contents from a back buffer, where you've rendered something into a surface, into a front buffer, where it is being displayed on the screen. This holds pretty much true across all of the properties of swapchains, whether windowed or fullscreen. Since your post is about fullscreen, I'll deep dive a bit on fullscreen.

When a DXGI swapchain gets created, it always starts out windowed. You end up with a set of back buffers. If you requested to create it fullscreen, it will transition to fullscreen before it returns to you, but it always does this as a second step in the creation process. As part of this transition to fullscreen, you end up with a front buffer being allocated for your swapchain.

The properties of this front buffer can depend on up to two things. Firstly, if your swapchain does not have the ALLOW_MODE_CHANGE flag set, then this front buffer's properties will match the current desktop mode, i.e. what was set in the control panel for the resolution and refresh rate. If your swapchain does have the ALLOW_MODE_CHANGE flag set, then we'll go ahead and find the mode which most closely matches your back buffer properties. At this point, the back buffers for your swapchain cannot be scanned out directly, because they were allocated as windowed back buffers, so they are not capable of being flipped/swapped/rotated to the screen.

The next step in the process of transitioning to fullscreen is to reallocate your back buffers so that they can be scanned out directly. This is done by calling ResizeBuffers. At this point, if you're already fullscreen, we'll check if we can make back buffers for your swapchain that can match the front buffers exactly, and if so, we'll set you up so that your back buffers and front buffers can be swapped through in a single chain. If not, you end up in what we call proxy mode, where each Present() does a copy to a front-buffer-like surface, which then flips with the currently displayed front buffer. Note that if you request to create a fullscreen swapchain, this happens before the swapchain is returned to you.

Now, a word of warning. During the ResizeBuffers step, we check if your back and front buffers match exactly. The refresh rate can sometimes screw you here. The refresh rate of the back buffers is something that can only be set during swapchain creation time. The refresh rate of the front buffers comes from a display mode. If there is no display mode that is an exact match for your swapchain's specified refresh rate, you will end up permanently in proxy mode. If your swapchain's refresh rate is 0/0 or 0/1, then the refresh rate isn't considered when figuring out if it's a match, and you're more likely to end up getting the more optimal flipping path. I've seen this behavior too many times to be able to recommend setting a refresh rate in the swapchain other than 0.

If you do need to be able to set a specific refresh rate for your fullscreen swapchain, I suggest using the ResizeTarget API while fullscreen to achieve the desired mode change. This runs the same FindClosestMatchingMode logic that SetFullscreenState does, but lets you specify the desired properties instead of inferring them from the back buffer properties.

Now for the rest of your questions:

1. Is it correct to only use 1 as VSync interval?
If your target framerate is 60hz, and the current mode's refresh rate is 60hz, then a sync interval of 1 will get you 60hz (assuming your app can keep up). A sync interval of 2 will get you 30hz. Note: If you're targeting 30hz due to expecting long-running rendering, I can only recommend sync interval 2 when running windowed with the FLIP_SEQUENTIAL or FLIP_DISCARD sync intervals, due to slightly different semantics from these modes compared to legacy blt or fullscreen modes.

2. What is the good way to find the refresh rate?
Use IDXGIOutput::FindClosestMatchingMode or IDXGIOutput::GetDisplayModeList. If you want the current mode, use unspecified values for all properties in FindClosestMatchingMode.

3. You can pass a null pointer for the output so you don't have to release it... You also don't need to explicitly minimize and restore your window.

4. Yes, destroying a fullscreen swapchain will crash your app.

Thanks for the long answer, very useful.

Use IDXGIOutput::FindClosestMatchingMode or IDXGIOutput::GetDisplayModeList. If you want the current mode, use unspecified values for all properties in FindClosestMatchingMode.

Is it possible to have more information about what you call current mode ? Does that means the main screen ? Is it a safe way ?

3. You can pass a null pointer for the output so you don't have to release it... You also don't need to explicitly minimize and restore your window.

So, the needed code is only :


// Detect if newly created full-screen swap chain isn't actually full screen.
BOOL bFullscreen;
pSwapChain->GetFullscreenState(&bFullscreen, nullptr);
// If not full screen, enable full screen again.
if( bFullscreen == false )
  pSwapChain->SetFullscreenState(TRUE, NULL);

On the previous code from MSDN, the bFullscreen was set to FALSE if the function FAILED, Is it really needed or it's guaranteed to output one value ?
Putting all here what I have :


void CDirect3D11RenderWindow::CreateSwapChain( const UInt32 Width, const UInt32 Height, const bool Windowed )
{
  // Get the window info.
  SDL_SysWMinfo SysWMInfo;
  SDL_VERSION( &SysWMInfo.version );
  SDL_GetWindowWMInfo( m_SDLWindow, &SysWMInfo );

  // Get D3D11 interface.
  IDXGIDevice* DXGIDevice;
  CDirect3D11Device::GetDevice()->QueryInterface( __uuidof( IDXGIDevice ), reinterpret_cast< void** >( &DXGIDevice ) );
  IDXGIAdapter* DXGIAdapter;
  DXGIDevice->GetParent( __uuidof( IDXGIAdapter ), reinterpret_cast< void** >( &DXGIAdapter ) );
  IDXGIFactory* DXGIFactory;
  DXGIAdapter->GetParent( __uuidof( IDXGIFactory ), reinterpret_cast< void** >( &DXGIFactory ) );

  // Swap chain desc.
  DXGI_SWAP_CHAIN_DESC sd;
  sd.BufferCount   = 1;
  sd.BufferDesc.Width   = Width;
  sd.BufferDesc.Height   = Height;
  sd.BufferDesc.Format   = DXGI_FORMAT_R8G8B8A8_UNORM;
  sd.BufferDesc.RefreshRate.Numerator   = 60;
  sd.BufferDesc.RefreshRate.Denominator = 1;
  sd.BufferDesc.Scaling   = DXGI_MODE_SCALING_UNSPECIFIED;
  sd.BufferDesc.ScanlineOrdering   = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
  sd.BufferUsage   = DXGI_USAGE_RENDER_TARGET_OUTPUT;
  sd.Flags   = 0;
  sd.OutputWindow   = SysWMInfo.info.win.window;
  sd.SampleDesc.Count   = 1;
  sd.SampleDesc.Quality   = 0;
  sd.SwapEffect   = DXGI_SWAP_EFFECT_DISCARD;
  sd.Windowed   = TRUE;

  // Create the swap chain.
  DXGIFactory->CreateSwapChain( CDirect3D11Device::GetDevice(), &sd, &m_SwapChain );

  // Release D3D11 interfaces.
  DXGIFactory->Release();
  DXGIAdapter->Release();
  DXGIDevice->Release();

  // Check if the fullscreen mode has to be set.
  if( Windowed == false )
  {
    // Set the fullscreen state.
    m_SwapChain->SetFullscreenState( TRUE, nullptr );

    // Get the fullscreen state of the swapchain.
    BOOL CurrentFullscreenstate;
    if( FAILED( m_SwapChain->GetFullscreenState( &CurrentFullscreenstate, nullptr ) ) )
      CurrentFullscreenstate = FALSE;

    // Enable full screen again if needed.
    if( CurrentFullscreenstate == false )
      m_SwapChain->SetFullscreenState( TRUE, nullptr );
  }
}

About FindClosestMatchingMode I'm not sure if it's correct to do that :


// Get the first output.
IDXGIOutput* DXGIOutput;
DXGIAdapter->EnumOutputs( 0, &DXGIOutput );

// Find the closest maching mode.
DXGI_MODE_DESC ClosestMatchMode;
DXGIOutput->FindClosestMatchingMode( nullptr, &ClosestMatchMode, nullptr );

And then set the refresh rate like that :


sd.BufferDesc.RefreshRate.Numerator   = ClosestMatchMode.RefreshRate.Numerator;
sd.BufferDesc.RefreshRate.Denominator = ClosestMatchMode.RefreshRate.Denominator;

But doesn't look to be correct because I have the message on visual studio :

DXGI ERROR: IDXGIOutput::FindClosestMatchingMode: either pModeToMatch or pClosestMatch pointer is NULL. [ MISCELLANEOUS ERROR #70: ]

As discussed, the message :

DXGI ERROR: IDXGISwapChain::Release: Swapchain Released while fullscreen. Switch it to the windowed state first. [ MISCELLANEOUS ERROR #66: ]

And to solve, as discussed :


// Release the swapchain if valid.
if( m_SwapChain )
{
  // Get the fullscreen state of the swapchain.
  BOOL CurrentFullscreenState;
  if( FAILED( m_SwapChain->GetFullscreenState( &CurrentFullscreenState, nullptr ) ) )
    CurrentFullscreenState = FALSE;

  // Switch to windowed if the swapchain is in fullscreen.
  if( CurrentFullscreenState )
    m_SwapChain->SetFullscreenState( FALSE, nullptr );

  // Release the swapchain.
  m_SwapChain->Release();
  m_SwapChain = nullptr;
}

I have this message :

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 = { 2560, 1440, { 60, 1 }, R8G8B8A8_UNORM, 0, 0 }; DXGI_SWAP_CHAIN_DESC::SampleDesc = { 1, 0 }; DXGI_SWAP_CHAIN_DESC::Flags = 0; [ MISCELLANEOUS WARNING #98: ]

Apparently it's only because flags is at 0 and should be :


sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

But I still have the warning even using this flag.
Here the resize buffers call :


m_SwapChain->ResizeBuffers( 1, Width, Height, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH );

EDIT :
Ok, Using correctly the FindClosestMatchingMode, the warning is then not there.

  • Create a windowed swap chain and then set it full-screen using IDXGISwapChain::SetFullscreenState.
  • Save a pointer to the swap chain immediately after creation, and use it to get the output window size during a WM_SIZE event. Then resize the swap chain buffers (with IDXGISwapChain::ResizeBuffers) during the transition from windowed to full-screen.

Hi, Jesse Natalie.

When I use this way to realize fullscreen mode , I have encountered some problem.


//Clear all resouces
ClearRenderTargets();
//Resize buffers
HRESULT hr = m_pLobbyDXGISwapChain->ResizeBuffers(
				1,
				m_RenderTargetSize.Width,
				m_RenderTargetSize.Height,
				DXGI_FORMAT_R8G8B8A8_UNORM,
				DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH);
DX_ERROR_VRETURN(hr);
//recreate resoureces
SetupRenderTargets();

The resizebuffer failed , and dx output is

DXGI ERROR: IDXGISwapChain::ResizeBuffers: Swapchain cannot be resized unless all outstanding buffer references have been released. [ MISCELLANEOUS ERROR #19: ]

I have checked really carefully ,but I can not find why this happend.

Can anyone teach me how to debug this ? Thanks a lot!


		bool xxxx::ClearRenderTargets()
		{
			// set swap chain to non full screen mode
			IDXGISwapChain* pActiveSwapChain = GetActiveSwapChain();
			if (pActiveSwapChain)
			{
				pActiveSwapChain->SetFullscreenState(FALSE, nullptr);
			}
			SAFE_RELEASE(m_D3DRasterizerState);

			SAFE_RELEASE(m_D3DDepthStencilView);
			SAFE_RELEASE(m_D3DDepthStencilState);
			SAFE_RELEASE(m_D3DDepthStencilBuffer);

			SAFE_RELEASE(m_D3DRenderTargetView);

			// release gamelib's swapchian
			SAFE_RELEASE(m_DXGIExternalSwapChain);

			return true;
		}

Stay hungry, stay foolish!

You need to call ID3D11DeviceContext::ClearState before ResizeBuffers, otherwise your rendertargets will still be bound. Simply Releasing them is not enough because D3D11 calls will AddRef objects, meaning that there is still an outstanding reference.

OMSetRenderTargets with NULL would also be sufficient, but I prefer ClearState as it will also clear down anything else that may need clearing, and is a good way to validate the robustness of your program's state management.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

It's also good to call ClearState and Flush when shutdown :


void Shutdown()
{
  // Restore all default settings and flush.
  if( DeviceContext )
  {
    DeviceContext->ClearState();
    DeviceContext->Flush();
  }

  // Release device data.
  SAFE_RELEASE( DeviceContext );
  SAFE_RELEASE( Device );
}

I actually have issue when switching from Windowed to Fullscreen, I got the warning :

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...

But initializing the swapchain in windowed or fullscreen using the way I said before gives 0 warning.
My resize function only call ResizeBuffers, maybe more works neeed for the fullscreen case.

This topic is closed to new replies.

Advertisement