Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DirectX11 Swapchain and window losing fullscreen status

I just stumbled on this little annoying behavior, while adding full screen support on a sample program.

Creating a full screen window works, but as soon as I move any window (from another application) on the output that contains my fullscreen window, it automatically switches back to windowed.

Is there any way to prevent this behavior (so full screen window do not go back to windowed)?

As a reference, this is a small standalone example (so problem can be replicated easily).

Also if that is useful, I'm running on Windows 8.1.

I already tried to change WindowAssociationFlags and SwapChainFlags, both with no success, same as using FlipSequential instead of Discard

SharpDX.DXGI.Factory2 factory = new SharpDX.DXGI.Factory2();
SharpDX.DXGI.Adapter adapter = factory.GetAdapter(0);

var renderForm1 = new RenderForm("Form 1");
factory.MakeWindowAssociation(renderForm1.Handle, SharpDX.DXGI.WindowAssociationFlags.IgnoreAll);

Device device = new Device(adapter, DeviceCreationFlags.BgraSupport);

SharpDX.DXGI.SwapChainDescription sd = new SharpDX.DXGI.SwapChainDescription()
{
    BufferCount = 2,
    ModeDescription = new SharpDX.DXGI.ModeDescription(0, 0, new SharpDX.DXGI.Rational(50, 1),  SharpDX.DXGI.Format.R8G8B8A8_UNorm),
    IsWindowed = true,
    OutputHandle = renderForm1.Handle,
    SampleDescription = new SharpDX.DXGI.SampleDescription(1,0),
    SwapEffect = SharpDX.DXGI.SwapEffect.Discard,
    Usage = SharpDX.DXGI.Usage.RenderTargetOutput,
    Flags = SharpDX.DXGI.SwapChainFlags.None
};

var swapChain1 = new SharpDX.DXGI.SwapChain(factory, device, sd);

renderForm1.Left = 1922; //Just hardcoded here to move window to second screen
renderForm1.Width = 1920;
renderForm1.Height = 1080;
renderForm1.FormBorderStyle = FormBorderStyle.None;

swapChain1.SetFullscreenState(true, null);
swapChain1.ResizeBuffers(2, 1920, 1080, SharpDX.DXGI.Format.R8G8B8A8_UNorm, SharpDX.DXGI.SwapChainFlags.AllowModeSwitch);

var resource = Texture2D.FromSwapChain<Texture2D>(swapChain1, 0);
var renderView = new RenderTargetView(device, resource);

RenderLoop.Run(renderForm1, () =>
{
    device.ImmediateContext.ClearRenderTargetView(renderView, new SharpDX.Color4(1, 0, 0, 1));
    swapChain1.Present(1, SharpDX.DXGI.PresentFlags.None);
});

Edit: I also tried a c++ sample (just taken DirectX11 basic tutorial from Microsoft and added full screen switch), this leads to the same behavior, so this is not a SharpDX specific issue.

I looked at the message loop, and once this occurs, first fullscreen mode is changed back to windowed, and I receive a WM_DISPLAYCHANGE message).

like image 246
mrvux Avatar asked Oct 20 '15 10:10

mrvux


2 Answers

This sounds like expected behavior. If you have a full screen 'exclusive' mode swapchain and the associated window loses focus, the system automatically switches the application out of full screen mode back to windowed mode by design.

With a single monitor, it mostly works as long as you have your applications' window sized to fill the display. Users can't use the mouse to change focus of your window, and it requires something like ALT+TAB to switch focus.

With multiple monitors, it's a real problem. If you click on another window on another display, your app loses focus and the full screen mode is again switched out. There are also limitations that prevent you from setting full screen 'exclusive' mode on more than one monitor.

Furthermore, on Windows Vista or later the notion of 'exclusive' mode is an illusion: the GPU is always shared anyhow. The 'focus' application gets priority whether it is a full screen or a windowed swap chain.

For a Windows desktop apps you have three choices for a full screen style experience:

  1. Use the traditional full screen 'exclusive' mode with a window sized to fill the display, along with setting the display mode which may not be what the user has set for Windows generally. Here you have IsWindowed = false.
  2. You set the window size to fill the full display (i.e. maximized). You can use windows styles to ensure that the window has no frame which results in a full screen style experience (WS_POPUP). Here you have IsWindowed = true, and you should be sure to set DXGI_MWA_NO_ALT_ENTER to avoid allowing DXGI to try to take you to use the 1 case.
  3. You can do the same as 2 with IsWindowed = true and the borderless window sized to match the screen, but you change the display mode to something other than the system default. This is commonly referred to as 'fake full screen'. The display mode gets changed back whenever you exit the application.

1 has all has all the problems with multi-tasking and focus we just described. 2 and 3 allow system notifications and other pop-ups to show up over the game and not force a mode switch. 2 and 3 also work a lot better in multi-monitor setups where you can play your game on one display and use other apps on another display. For multi-tasking most people to prefer a classic window style with a frame border.

Windows Store UWP notions of full screen mode is basically like 2 above. You can't change the display mode with a UWP.

Debugging a full-screen setup is quite challenging. With multiple monitors, 2 and 3 can work with your debugger on the other screen. For true full-screen exclusive mode, really the only option is to use remote debugging from another PC.

Another issue with 1 and 3 is that you can set the display mode to something that won't sync with the display leaving the user with a system with no UI and no way to exit. Ideally with the right driver setup, the DXGI enumeration list does not contain unsupported modes, but it is something to be aware of. For this reason, your UI for selecting a display mode should have a timeout and you should make sure there's a reasonable way to abort the application with the keyboard if the display mode fails to sync at some point in the future. Using the existing display mode as we do in 2 above is always the safest option.

The main reason to use full screen exclusive mode (1) above is to try to get 'flip' rather than 'blit' of the backbuffer/frontbuffer. For most modern systems, this is a negligible performance difference. The other reason to go through the pain of using it is for SLI/Crossfire multi-GPU rendering going to a single display. There are a number of other optimizations required to really make that scenario work, and it's pretty niche. You should seek out the vendor optimization guides for the details.

Most modern games default to using fake full screen rather than full screen 'exclusive' mode. They offer the ability to use a true windowed mode as many users want to be able to multi-task while playing (like looking up hints online, use IM or external voice chat, etc.). AAA Windows desktop games that want to support tuned high-performance gaming for SLI/Crossfire will offer a full screen 'exclusive' mode, but this requires some work to get working fully and entails more work than just some DXGI code.

See DXGI Overview and DirectX Graphics Infrastructure (DXGI): Best Practices

like image 163
Chuck Walbourn Avatar answered Nov 11 '22 15:11

Chuck Walbourn


After several attempts and trials, here are the different workarounds I used, none are ideal but all are somehow better than getting a mode change.

1/Force cursor in the middle of the full screen window, with a keyboard shortcut to get control again. This is not ideal since we can't really do anything while our part is running, but at least prevents accidental "disaster click". It does not prevent keyboard interaction either.

2/Use a DX9 renderer with a shared texture. DX9 Swapchain can have it's parent window set to desktop, so it does not lose focus when moving to something else. Having a focused window on top show little borders visible while moving it, but that is a bit more acceptable than losing everything. Not future proof but guess will stay actual for a while.

3/Stay on Windows 7 and Disable DWM Service:

Doesn't work in Windows 8 anymore, but in my use case since most media companies I work for are still on Windows 7, it stays a valid solution for at least 5 to 10 years.

4/Force the DX11 Window on foreground

Basically continuously call SetForegroundWindow to avoid another window to take focus.

5/Prevent mode switch at presentation level.

Since on my application I got access to when presentation occurs, I use the following routine (before to call Present)

-Get Foreground window handle (using GetForegroundWindow), If Foreground handle is our fullscreen window, just call Present as usual.

If Foreground handle is not our fullscreen window, perform the following. Please note that visibility check is not needed, since even an invisible overlapping window will also cause a full screen loss! (seriously, this is just so bad...)

-Verify if our foreground window overlaps with the monitor: Call GetWindowRect to get the bounds, and perform intersection with the monitor location.

Alternatively, call Present on the swapchain with the DXGI_PRESENT_TEST flag. If a window is overlapping, the Present call will return DXGI_STATUS_OCCLUDED

If a window overlaps, either Hide it or move it in another monitor (anywhere so it does not overlap): ShowWindow and SetWindowPos are aperfect fit for this task.

Repeat that Test present call in a loop until it doesn't return the occluded status (this is important, since windows might not have processed the messages immediately); Once occluded flag is gone, call Present as usual.

like image 20
mrvux Avatar answered Nov 11 '22 14:11

mrvux