Of course Evil’s afoot. If it had switched to the metric system it’d be up to a meter by now.
– Jim Butcher
Why is there an entire tutorial about going full-screen? Can’t we simply press ALT+ENTER and be done with it?
Unfortunately not, here is an explanation as to why, taken directly from
the MSDN.
When IDXGISwapChain::Present is called in a fullscreen application, the swap chain flips, and no longer blits,
the contents of the back buffer to the front buffer. This requires that the swap chain was created by using an
enumerated display mode, specified in DXGI_SWAP_CHAIN_DESC. If no valid display mode is specified, the swap chain may
perform a bit-block transfer, which causes an extra stretching copy as well as some increased video memory usage, and is
difficult to detect.
To avoid this problem, and to ensure maximal performance in full-screen, as well as to avoid extra memory overhead,
display modes should be enumerated before the swap chain is created. Thankfully, the swap chain was created with great
care in the previous tutorials, thus most of the hard work is already done. Remember that the desired screen resolution
is read from a configuration file and a call to IDXGISwapChain::ResizeBuffers resizes the back buffer to the size that
was passed as parameters in WM_SIZE each time the size of the window changes.
Going Fullscreen
Actually going into full-screen mode is childishly easy: Since the swap chain was created with the
DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH flag, all that needs to be done to go into full-screen is to actually press
ALT+ENTER.
To make sure that the application still exits gracefully, full-screen mode should be disabled before releasing all the
objects and allowing Windows to kill the window:
The first parameter of the
IDXGISwapChain::SetFullscreenState
method specifies the state the application should switch to and the second parameter refers to the video adapter to use.
This can be set to nullptr to let DirectX handle this (usually there will only be one available GPU anyway).
Catching Fullscreen State Changes
To make sure we catch changes in the full-screen state of the window, we have to listen to yet another Windows event,
the WM_WINDOWPOSCHANGED message:
Each time the full-screen state of the window changes, all resources, especially the back buffers, must obviously be
resized as well.
The IDXGISwapChain::GetFullscreenState
method is rather self-explanatory.
Starting in Fullscreen
To start the application in full-screen, a simple call of the SetFullscreenState method is enough:
To easily switch between starting in full-screen and in windowed mode, without having to recompile the entire project, a
variable was added to the LUA configuration file:
Supported Resolutions
Most games allow the player to choose his desired screen resolution and while we have no user interface yet, it is,
however, time to learn how to enumerate all the supported resolutions. For that, a few more member variables are needed
in the Direct3D class:
The initialization process will change a little bit: Right after the creation of the swap chain, all supported modes
will be enumerated and stored in the supportedModes array.
To actually tackle that task, a representation of the output adapter, a
IDXGIOutput interface, which
represents the display adapter, is needed. Thankfully, DirectX offers the
IDXGISwapChain::GetContainingOutput
method to easily retrieve said adapter:
As you can see, the method is completely straightforward to use:
Now, to enumerate all supported screen resolutions, a query for the actual number of supported resolutions must be
completed first. Afterwards, a large enough array to hold all the mode descriptions will be created and filled. All of
that is done using the
IDXGIOutput::GetDisplayModeList
method:
The first parameter specifies the colour format the application is using. The second parameter is optional, check the
MSDN for a list of all available flags.
The fourth parameter is optional, but important. If it is set to NULL, the third parameter is used as an output and
returns the actual number of supported modes. If the fourth parameter is not NULL, it returns the actual mode
descriptions of the supported modes, and the third parameter is then used as an input specifying the number of supported
modes.
This method can thus be used twice, once to get the number of supported modes and once to get the actual descriptions:
Note that once all the supported display modes are enumerated, it is easy to check whether the resolution the window was
created with is supported or not. If the resolution is supported, then nothing needs to be done besides storing the
current window resolution as the current display mode. Else, the lowest supported resolution will be chosen as the
current resolution and written to the configuration file, such that when the game starts the next time, the initial
window will be created with a supported screen resolution.
Now the only thing left to do is to resize the target to the selected screen resolution, which is done by using the
ResizeTarget method:
Please note that for now, if the window is dragged and resized, the ResizeTarget method restores the window to the
original resolution, once the dragging stops. This is working as intended, as there is no real need for arbitrary screen
resolutions in full-screen games.
Dynamic Screen Resolution
To prepare to be able to change the resolution to whatever resolution the player choses (as long as it is supported; and
once we have an user interface), a few more variables have to be added to the Direct3D class to actually track
resolution changes:
Since there is no user interface yet, we will use the PAGE_UP and PAGE_DOWN keys to change resolutions:
The changeResolution method is very simple, as all that needs to be done is to increase or decrease the index of the
desired screen mode:
Take attention though, once the mode changes, all resources, especially the back buffers, must be resized to the new
screen resolution as well, else the application will be hit by massive FPS drops, as explained in the introduction
of this tutorial.
And here is the updated onResize function:
We have not mentioned a borderless full-screen mode, useful when running a multiple monitor setup, but I believe that is
a topic for a later time, as for now we want to concentrate on programming a game.
Enjoy the star field from the previous tutorial in glorious full-screen mode!