-
|
|
Unexpected behavior from DXGI
|
I'm attempting to write an application that uses DXGI and D3D10. I would like to run this application in fullscreen as efficiently as possible. As such, I've gone over a plethora of documentation regarding proper DXGI and D3D10 usage related to fullscreen applications. However, I am experiencing unexpected behavior with SetFullscreenState and the WM_SIZE message.
Your window will receive a WM_SIZE message whenever such a transition happens, and calling IDXGISwapChain::ResizeBuffers is the swap chain's chance to re-allocate the buffers' storage for optimal presentation.
Thus, a call to ResizeBuffers on WM_SIZE is always recommended, since WM_SIZE is always sent during a fullscreen transition.
The documentation everywhere seems to imply that it is sufficient to call ResizeBuffers in the applications WM_SIZE event handler to ensure that fullscreen performance is optimal. It also suggests that applications be created in windowed mode and then switched to fullscreen mode. This is what I am doing. It also explicitly states that WM_SIZE is always sent during a fullscreen transition.
I have found that this is not the case. If I create a window and swap chain initially set to the desired fullscreen resolution but in windowed mode and then switch to fullscreen mode using SetFullscreenState, I will not receive a WM_SIZE event.
Now this would not be a problem if DXGI recognized that my buffers were already the correct size and did not need to be resized. However, this is not the case and I receive the 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 = { 1680, 1050, { 0, 0 }, R8G8B8A8_UNORM_SRGB, 0, 0 }; DXGI_SWAP_CHAIN_DESC::SampleDesc = { 1, 0 }; DXGI_SWAP_CHAIN_DESC::Flags = 0x2;
as soon as I call Present for the first time. It is absolutely the case that I created the swap chain with the DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH flag. I ensure that I call FindClosestMatchingMode and use its result to call ResizeTarget. Here is the relevant stretch of code from my reproduction case (with error checking removed for ease of reading):
| DXGI_OUTPUT_DESC desc; | | pOutput->GetDesc( &desc ); | | | | RECT rect = desc.DesktopCoordinates; | | AdjustWindowRectEx( &rect, WS_POPUP, FALSE, 0 ); | | | | int x = rect.left; | | int y = rect.top; | | int width = rect.right - rect.left; | | int height = rect.bottom - rect.top; | | | | g_hWnd = CreateWindowEx( 0, kApplicationClassName, kApplicationClassName, WS_POPUP, x, y, width, height, NULL, NULL, hInstance, NULL ); | | | | ShowWindow( g_hWnd, nCmdShow ); | | UpdateWindow( g_hWnd ); | | | | DXGI_MODE_DESC requestedMode; | | requestedMode.Width = width; | | requestedMode.Height = height; | | requestedMode.RefreshRate.Numerator = 0; | | requestedMode.RefreshRate.Denominator = 0; | | requestedMode.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; | | requestedMode.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; | | requestedMode.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; | | | | DXGI_SWAP_CHAIN_DESC scDesc; | | scDesc.BufferDesc = requestedMode; | | scDesc.SampleDesc.Count = 1; | | scDesc.SampleDesc.Quality = 0; | | scDesc.BufferUsage = DXGI_USAGE_BACK_BUFFER | DXGI_USAGE_RENDER_TARGET_OUTPUT; | | scDesc.BufferCount = 2; | | scDesc.OutputWindow = g_hWnd; | | // We always create in windowed mode first and then switch to fullscreen if requested. | | scDesc.Windowed = TRUE; | | scDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; | | scDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; | | | | pFactory->CreateSwapChain( g_pDevice, &scDesc, &g_pSwapChain ); | | | | g_pSwapChain->GetBuffer( 0, __uuidof(g_pBackBuffer), reinterpret_cast<void**>( &g_pBackBuffer ) ); | | | | g_pDevice->CreateRenderTargetView( g_pBackBuffer, NULL, &g_pRenderTarget ); | | | | DXGI_MODE_DESC mode; | | pOutput->FindClosestMatchingMode( &requestedMode, &mode, g_pDevice ); | | | | g_pSwapChain->ResizeTarget( &mode ); | | | | g_pSwapChain->SetFullscreenState( TRUE, pOutput ); | | | | // Microsoft best practices advises this step... | | mode.RefreshRate.Numerator = 0; | | mode.RefreshRate.Denominator = 0; | | g_pSwapChain->ResizeTarget( &mode ); |
And for completeness, here is the WM_SIZE handler, though again this is never invoked because WM_SIZE is never received during the fullscreen transition:
| WORD width = LOWORD(lParam); | | WORD height = HIWORD(lParam); | | | | if( !g_pSwapChain || !g_pDevice ) | | break; | | | | if( g_pRenderTarget ) | | { | | g_pRenderTarget->Release(); | | g_pRenderTarget = 0; | | } | | if( g_pBackBuffer ) | | { | | g_pBackBuffer->Release(); | | g_pBackBuffer = 0; | | } | | | | DXGI_SWAP_CHAIN_DESC desc; | | g_pSwapChain->GetDesc( &desc ); | | | | if( width != 0 && height != 0 ) | | { | | g_pSwapChain->ResizeBuffers( desc.BufferCount, width, height, desc.BufferDesc.Format, desc.Flags ); | | | | g_pSwapChain->GetBuffer( 0, __uuidof(g_pBackBuffer), reinterpret_cast<void**>( &g_pBackBuffer ) ); | | | | g_pDevice->CreateRenderTargetView( g_pBackBuffer, NULL, &g_pRenderTarget ); | | } | | return 0; |
I'm at a loss as to what to do about this. If I explicitly call ResizeBuffers after SetFullscreenState, then I do not get the warning message. However, this seems to be inelegant and would cause ResizeBuffers to be called too often in those cases where the WM_SIZE handler actually worked. It also clashes with the documentation on the subject. If I ensure that the window is created at the wrong size and do not call ResizeTarget before SetFullscreenState, then I will force a WM_SIZE event and thus call ResizeBuffers, preventing the warning. However, aside from being inelegant, it also directly clashes with Microsoft's best practices documentation which advise calling ResizeTarget before SetFullscreenState.
Is this a known issue? Have others encountered it? Why does DXGI documentation state that I will receive a WM_SIZE event on all fullscreen transitions when clearly I do not? If it is expected that WM_SIZE will not be sent in this case, what is the best practice for ensuring the ResizeBuffers gets called without calling it redundantly? Is this error message erroneous? Is this a bug somewhere in DXGI, D3D10, Windows, etc?
I'm currently running Windows 7 Professional 64-bit. I am using the August 2009 DirectX SDK and Visual Studio 2005 with SP1 and Vista updates. I'm developing the application in C++. I was previously developing this application on Windows Vista 32-bit and do not recall this issue occurring, though it's possible I might have missed it. Has this behavior changed between Windows Vista and Windows 7? Is it a difference between 32-bit and 64-bit Windows?
Finally, here is the complete source code for a reproduction case. All you gotta do is compile and run and you should note the warning above after the first time Present is called. This has happened 100% of the time I have run this code.
| #define WIN32_LEAN_AND_MEAN | | #include <windows.h> | | #include <stdlib.h> | | #include <malloc.h> | | #include <memory.h> | | #include <tchar.h> | | #include <DXGI.h> | | #include <D3D10.h> | | | | static const TCHAR* kApplicationClassName = _T("DX10 Fullscreen Test"); | | | | HWND g_hWnd = 0; | | ID3D10Device* g_pDevice = 0; | | IDXGISwapChain* g_pSwapChain = 0; | | ID3D10Resource* g_pBackBuffer = 0; | | ID3D10RenderTargetView* g_pRenderTarget = 0; | | | | LRESULT WINAPI WindowProcedure( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) | | { | | | | switch( message ) | | { | | case WM_SIZE: | | { | | WORD width = LOWORD(lParam); | | WORD height = HIWORD(lParam); | | | | if( !g_pSwapChain || !g_pDevice ) | | break; | | | | if( g_pRenderTarget ) | | { | | g_pRenderTarget->Release(); | | g_pRenderTarget = 0; | | } | | if( g_pBackBuffer ) | | { | | g_pBackBuffer->Release(); | | g_pBackBuffer = 0; | | } | | | | DXGI_SWAP_CHAIN_DESC desc; | | HRESULT hr = g_pSwapChain->GetDesc( &desc ); | | if( FAILED( hr ) ) | | break; | | | | if( width != 0 && height != 0 ) | | { | | hr = g_pSwapChain->ResizeBuffers( desc.BufferCount, width, height, desc.BufferDesc.Format, desc.Flags ); | | if( FAILED( hr ) ) | | break; | | | | hr = g_pSwapChain->GetBuffer( 0, __uuidof(g_pBackBuffer), reinterpret_cast<void**>( &g_pBackBuffer ) ); | | if( FAILED( hr ) ) | | break; | | | | hr = g_pDevice->CreateRenderTargetView( g_pBackBuffer, NULL, &g_pRenderTarget ); | | if( FAILED( hr ) ) | | break; | | } | | | | return 0; | | } | | break; | | case WM_DESTROY: | | { | | if( g_pSwapChain ) | | { | | g_pSwapChain->SetFullscreenState( FALSE, NULL ); | | } | | PostQuitMessage(0); | | g_hWnd = NULL; | | return 0; | | } | | break; | | } | | | | // Fall back on the default window procedure. | | return DefWindowProc( hWnd, message, wParam, lParam ); | | } | | | | int APIENTRY _tWinMain(HINSTANCE hInstance, | | HINSTANCE hPrevInstance, | | LPTSTR lpCmdLine, | | int nCmdShow) | | { | | UNREFERENCED_PARAMETER(hPrevInstance); | | UNREFERENCED_PARAMETER(lpCmdLine); | | | | IDXGIFactory* pFactory = 0; | | IDXGIAdapter* pAdapter = 0; | | IDXGIOutput* pOutput = 0; | | | | try | | { | | WNDCLASSEX wndClass; | | ZeroMemory( &wndClass, sizeof(wndClass) ); | | wndClass.cbSize = sizeof(wndClass); | | wndClass.lpszClassName = kApplicationClassName; | | wndClass.lpfnWndProc = &WindowProcedure; | | wndClass.hInstance = hInstance; | | wndClass.hCursor = NULL; | | wndClass.hIcon = NULL; | | wndClass.hIconSm = NULL; | | wndClass.hbrBackground = GetSysColorBrush(COLOR_BACKGROUND); | | wndClass.lpszMenuName = NULL; | | wndClass.style = CS_OWNDC; | | wndClass.cbWndExtra = 0; | | ATOM classAtom = RegisterClassEx( &wndClass ); | | if( !classAtom ) | | throw -1; | | | | HRESULT hr = CreateDXGIFactory( __uuidof(IDXGIFactory), reinterpret_cast<void**>(&pFactory) ); | | if( FAILED( hr ) ) | | throw -1; | | | | hr = pFactory->EnumAdapters( 0, &pAdapter ); | | if( FAILED( hr ) ) | | throw -1; | | | | hr = D3D10CreateDevice( pAdapter, D3D10_DRIVER_TYPE_HARDWARE, NULL, D3D10_CREATE_DEVICE_DEBUG, D3D10_SDK_VERSION, &g_pDevice ); | | if( FAILED( hr ) ) | | throw -1; | | | | hr = pAdapter->EnumOutputs( 0, &pOutput ); | | if( FAILED( hr ) ) | | throw -1; | | | | DXGI_OUTPUT_DESC desc; | | hr = pOutput->GetDesc( &desc ); | | if( FAILED( hr ) ) | | throw -1; | | | | | | RECT rect = desc.DesktopCoordinates; | | BOOL res = AdjustWindowRectEx( &rect, WS_POPUP, FALSE, 0 ); | | if( !res ) | | throw -1; | | | | int x = rect.left; | | int y = rect.top; | | int width = rect.right - rect.left; | | int height = rect.bottom - rect.top; | | | | g_hWnd = CreateWindowEx( 0, kApplicationClassName, kApplicationClassName, WS_POPUP, x, y, width - 1, height - 1, NULL, NULL, hInstance, NULL ); | | if( !g_hWnd ) | | throw -1; | | | | ShowWindow( g_hWnd, nCmdShow ); | | res = UpdateWindow( g_hWnd ); | | if( !res ) | | throw -1; | | | | DXGI_MODE_DESC requestedMode; | | requestedMode.Width = width - 1; | | requestedMode.Height = height - 1; | | requestedMode.RefreshRate.Numerator = 0; | | requestedMode.RefreshRate.Denominator = 0; | | requestedMode.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; | | requestedMode.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; | | requestedMode.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; | | | | DXGI_SWAP_CHAIN_DESC scDesc; | | scDesc.BufferDesc = requestedMode; | | scDesc.SampleDesc.Count = 1; | | scDesc.SampleDesc.Quality = 0; | | scDesc.BufferUsage = DXGI_USAGE_BACK_BUFFER | DXGI_USAGE_RENDER_TARGET_OUTPUT; | | scDesc.BufferCount = 2; | | scDesc.OutputWindow = g_hWnd; | | // We always create in windowed mode first and then switch to fullscreen if requested. | | scDesc.Windowed = TRUE; | | scDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; | | scDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; | | | | hr = pFactory->CreateSwapChain( g_pDevice, &scDesc, &g_pSwapChain ); | | if( FAILED( hr ) ) | | throw -1; | | | | hr = g_pSwapChain->GetBuffer( 0, __uuidof(g_pBackBuffer), reinterpret_cast<void**>( &g_pBackBuffer ) ); | | if( FAILED( hr ) ) | | throw -1; | | | | hr = g_pDevice->CreateRenderTargetView( g_pBackBuffer, NULL, &g_pRenderTarget ); | | if( FAILED( hr ) ) | | throw -1; | | | | DXGI_MODE_DESC mode; | | hr = pOutput->FindClosestMatchingMode( &requestedMode, &mode, g_pDevice ); | | if( FAILED( hr ) ) | | throw -1; | | | | hr = g_pSwapChain->ResizeTarget( &mode ); | | if( FAILED( hr ) ) | | throw -1; | | | | hr = g_pSwapChain->SetFullscreenState( TRUE, pOutput ); | | if( FAILED( hr ) ) | | throw -1; | | | | // Microsoft best practices advises this step... | | mode.RefreshRate.Numerator = 0; | | mode.RefreshRate.Denominator = 0; | | hr = g_pSwapChain->ResizeTarget( &mode ); | | if( FAILED( hr ) ) | | throw -1; | | | | bool running = true; | | while( running ) | | { | | MSG msg; | | while( PeekMessage( &msg, g_hWnd, 0, 0, true ) ) | | { | | if( msg.message == WM_QUIT ) | | { | | running = false; | | g_hWnd = NULL; | | break; | | } | | | | TranslateMessage( &msg ); | | DispatchMessage( &msg ); | | } | | | | if( running ) | | { | | FLOAT color[] = { 1.0f, 0.0f, 0.0f, 1.0f }; | | g_pDevice->ClearRenderTargetView( g_pRenderTarget, color ); | | hr = g_pSwapChain->Present(0, 0); | | if( FAILED( hr ) ) | | throw -1; | | } | | } | | } | | catch( ... ) | | { | | } | | | | // Cleanup | | if( g_pRenderTarget ) | | g_pRenderTarget->Release(); | | if( g_pBackBuffer ) | | g_pBackBuffer->Release(); | | if( g_pSwapChain ) | | g_pSwapChain->Release(); | | if( g_pDevice ) | | g_pDevice->Release(); | | if( pOutput ) | | pOutput->Release(); | | if( pAdapter ) | | pAdapter->Release(); | | if( pFactory ) | | pFactory->Release(); | | if( g_hWnd ) | | DestroyWindow( g_hWnd ); | | | | return 0; | | } |
|
|
-
|
|
Re: Unexpected behavior from DXGI
|
Also, I should note that even though I am developing in 64-bit Windows, that application itself is still using Win32, not Win64.
|
|
-
|
|
Re: Unexpected behavior from DXGI
|
OK, I followed up on this issue this morning by running my reproduction case above on a Windows Vista 32-bit machine. On this machine, the first ResizeTarget does cause a WM_SIZE message to be sent even though the size of the window is not changing. This is the behavior I was expecting, because it ensures that each ResizeTarget call leads to a ResizeBuffers call. In any case, because the WM_SIZE message was sent, ResizeBuffers is called naturally and I do not receive the performance warning.
However, this machine is also using the March 2009 SDK. I'm downloading the August 2009 SDK now to see if it has the same issue. I will follow up at that point. Still, something has changed, whether between Windows 7, or 64-bit Windows, or DirectX SDK releases. I'd really like to know why this change has occurred and how I'm supposed to deal with it correctly. It's kind of important that I used to get WM_SIZE messages and now I don't.
|
|
-
|
|
Re: Unexpected behavior from DXGI
|
Having updated to the August 2009 DirectX SDK, I can verify that it does not cause this issue to occur on Windows Vista 32-bit. Even with identical SDKs, it occurs on Windows 7 64-bit, but not Windows Vista 32-bit.
It would appear then that some difference between Windows Vista 32-bit and Windows 7 64-bit is causing DXGI not to send the WM_SIZE message when I call ResizeTarget when the window is already the size to which ResizeTarget would set it. This causes ResizeBuffers not to be called, which causes the performance warning. I don't have access to Windows 7 32-bit or Windows Vista 64-bit, so I can't narrow the difference down to 32-bit versus 64-bit or Windows 7 versus Windows Vista.
Is this behavior correct? Why the difference? Can anyone with direct knowledge of DXGI explain this discrepancy? Is this difference in behavior known? Is it documented somewhere? Is there some detail I'm missing that would point me in the right direction? What is the correct way to handle this situation?
|
|
-
|
|
Re: Unexpected behavior from DXGI
|
You can always call ResizeBuffers manually, just after every ResizeTarget call, and do not wait for WM_SIZE. Of course, ResizeBuffers call should be still used in WM_SIZE message handler function.
|
|
-
|
|
Re: Unexpected behavior from DXGI
|
Hi jlynch,
I have exactly the same issue as you and have been stuck on it for a long time now without finding any solution. Even the SDK samples suffer from this problem.
Here's a thread I made recently in the D3D11 forum http://forums.xna.com/forums/t/45287.aspx
And this: http://forums.xna.com/forums/p/39302/259931.aspx#270292
I would really appreciate if anyone from the DX team could comment on the issue, or if anyone can find a workaround.
|
|
-
|
|
Re: Unexpected behavior from DXGI
|
Charlie3D:You can always call ResizeBuffers manually, just after every ResizeTarget call, and do not wait for WM_SIZE. Of course, ResizeBuffers call should be still used in WM_SIZE message handler function.
Yes, but this is exactly the type of near-hack that I'm hoping to avoid. The DXGI documentation is clear that handling the WM_SIZE event by calling ResizeBuffers is both necessary and sufficient to ensure optimal performance with respect to buffer sizes. If this is not the case, then I'd really like to see updated documentation informing users of DXGI what the correct procedure is. I shouldn't have to take blind guesses like this.
|
|
|