Before 3D rendering can occur, we need to access the low-level portion of Windows that deals with sending images to the physical screen or monitor.
Once we've accessed it, we can customize it and set it up to our liking.
Before 3D rendering can occur, we need to access the low-level portion of Windows that deals with sending images to the physical screen or monitor.
Once we've accessed it, we can customize it and set it up to our liking.
A GPU contains in its memory a pointer to a buffer of pixels that contains the image currently being displayed on the screen. When you need to render something, such as a 3D model or image, the GPU updates this array and sends the information to the monitor to display. The monitor then redraws the screen from top to bottom, replacing the old image with the new.
However, there is a slight problem with this in that the monitor does not refresh as fast as needed for real-time rendering. Most refresh rates range from 60 Hz (60 fps) to about 100 Hz. If another model were rendered to the GPU while the monitor was refreshing, the image displayed would be cut in two, the top portion containing the old image and the bottom portion containing the new. This effect is called tearing.
To avoid this, the DXGI implements a feature called swapping.
Instead of rendering new images directly to the monitor, the DXGI automatically draws your images onto a secondary buffer of pixels, called the back buffer. The front buffer would be the buffer currently being displayed. You draw all your images onto the back buffer, and when you are done, DXGI will update the front buffer with the contents of the back buffer, discarding the old image.
However, doing this can still cause tearing, because the image transfer can still occur while the monitor is refreshing (the GPU is way faster than the monitor).
In order to avoid this (and to make the whole thing go faster anyway), DXGI uses a pointer for each buffer (both front and back) and simply switches their values. The back buffer then becomes the front buffer (and vice versa), and no tearing occurs.
Of course, we could make our game have better performance by adding additional back buffers, like this.
This setup is called the swap chain, as it is a chain of buffers, swapping positions each time a new frame is rendered.
The swap chain is represented by a new COM interface, called IDXGISwapChain1.
Setting up the swap chain, unlike initializing the device, is a several step process.
1. Obtain a pointer to a DXGI Factory, an object that is capable of creating other DXGI objects.
2. Customize the swap chain by filling out a swap chain description struct.
3. Use the DXGI Factory to call CreateSwapChainForCoreWindow().
In the next few sections, we'll take a look at how each of these is done.
A DXGI Factory is an object that is capable of creating other DXGI objects.
We don't want just any DXGI Factory though, we want the factory that created our adapter and device objects.
If you recall from last lesson, we had an option to provide a DXGIAdapter. We left it blank, indicating that Direct3D should figure it out for us. It did. It created the adapter object based on what it already knew about the hardware. If we can access that DXGIAdapter object, it will tell us the address of the factory.
So how do we do this? First, we convert our current ID3D11Device1 interface into an IDXGIDevice1 interface using the As() function. Second, we call GetAdapter() on that interface to get the adapter. Third, we call GetParent() on that object to get the factory.
It looks like this:
Simple, yet not so simple.
There are a few new players here, so let's talk about them.
IDXGIDevice1
For every ID3D11Device1 there is a corresponding IDXGIDevice1 that lets us access DXGI components. We won't need to do this often, but accessing the DXGI Factory is one of the exceptions.
IDXGIAdapter
This could simply be described as the virtutal representation of the video card (assuming the video card is separate, and not built into the motherboard).
The parent of this object is the factory that was used to create our device.
GetParent()
Calling GetParent() gets us access to the factory of our adapter and of the device. It has two parameters: the type of interface we are obtaining, and a pointer to store the address in.
__uuidof()
This function is COM-speak for the unique identifying code for an associated COM object. We use it here to tell GetParent() what type of interface it is fetching for us.
IDXGIFactory2
This is the interface to our factory. From this interface we can create our swap chain. The 2 at the end indicates that this is version 2 of the interface. We use this version because earlier versions do not support Windows 8 style apps.
To conclude with a brief summary, what we are doing here is gaining access to our DXGI Factory. We obtain the factory from our adapter interface, which we obtain from our device interface.
In addition to obtaining the DXGI Factory, we need to prepare and fill out a swap chain description. This is a struct filled with all kinds of options for our swap chain.
What options? Pretty much everything. We can control the size of it, we can control how many back buffers there are, and much more.
We're not going to talk about everything in this struct, because some of it is complex, and some of it is completely irrelevant to us. What I'll do is describe each one, then show you which ones are required to carry forward.
The struct is called DXGI_SWAP_CHAIN_DESC1. It has the following members:
Width
An integer value indicating the width of the swapchain buffers in pixels. You can use this to force your game into lower or higher resolutions than the screen shows by default. If you set it to 0, the swap chain automatically sizes itself to the current window resolution.
Height
Same as the width but, you know, the height instead.
Scaling
Scaling is a special value with two possible choices: DXGI_SCALING_STRETCH and DXGI_SCALING_NONE. Choosing the stretch option only matters if you set a custom width and height. The swap chain will stretch the image to fill the entire screen (or shrink it to fit the screen, if you chose a larger resolution). If you choose the none option, the rendered images will just appear in the top-left corner of the window.
BufferCount
BufferCount tells the swap chain how many buffers to create. We want one front buffer and one back buffer, so we'll typically put 2 in this value.
Format
This value is a bit more complex. This tells the swap chain what format our pixels are stored in. In other words, what arrangement of bits are we using to store the various colors. There are plenty of options to select from.
For all the programs in this tutorial we will use DXGI_FORMAT_B8G8R8A8_UNORM. What this means is that we reserve 8 bits for blue, 8 bits for green, 8 bits for red, and 8 bits for transparency, in that order, and each color will be stored in a special format called an unsigned normalized integer, which is optimized for GPU reading.
SampleDesc.Count
This member is used to tell the swap chain how to perform anti-aliased rendering. More specifically, it tells the swap chain how many times to perform anti-aliasing on each pixel. The minimum requirement for this value is 1, which means no anti-aliasing. We'll use this because not all GPUs support it.
SampleDesc.Quality
This indicates the quality of the anti-aliasing. Leaving it at 0 will work for us.
BufferUsage
This value is required, and it tells DXGI what this swap chain is to be used for. There's only one value we will probably ever use here, and that is DXGI_USAGE_RENDER_TARGET_OUTPUT.
SwapEffect
This is another required value that we will probably never change. It tells DXGI what to do with the buffers once they have been shown and are no longer of use. We'll use the value DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL.
AlphaMode
This is a value that sets how semi-transparency works. We won't work with this value until later.
Stereo
This is a boolean value which allows an advanced programmer to enable use of 3D glasses. Cool! If you in to that sort of thing...
Flags
Here we can place some even more advanced options. We won't ever use this as it is beyond the scope of this tutorial.
Let's now look at how to actually write the code. I've prepared an example here, and it only uses the minimum required changes. Everything else is set to 0 by default.
And that's a quick look at the swap chain description. Feel free to explore the rest of the options on your own.
We are one function call away from having a functioning swap chain to draw on. The function is called CreateSwapChainForCoreWindow().
It looks like this:
It's actually really simple. Let's break down the prototype:
Let's study these.
IUnknown* pDevice,
This first parameter is a pointer to our ID3D11Device1 interface. IUnknown is COM-speak for any COM object. It's trusting us to provide the right type of object (a device).
The Get() function obtains a direct pointer to the interface (as opposed to the ComPtr to the interface).
IUnknown* pWindow,
The next parameter is a pointer to a COM object representing the window. Under the hood, WinRT classes are really just COM objects in disguise, and you can easily use reinterpret_cast to change it over.
Therefore, in this parameter we take the Window ref pointer and reinterpret_cast it to an IUnknown pointer.
DXGI_SWAP_CHAIN_DESC1* pDesc,
The third parameter is easy. It's the address of the swap chain description struct.
IDXGIOutput* pRestrictToOutput,
This is an advanced parameter that allows us to restrict graphics to a specific monitor in a multi-monitor system. We want the user to pick any monitor they choose, so we'll put nullptr here.
IDXGISwapChain1** ppSwapChain
Finally, we place a pointer to the swapchain ComPtr we defined earlier.
And that's it! We have a working swap chain! Now let's put it to use.
Using a swap chain is almost as complex as creating it. Just kidding!
It takes one line of code. We'll put it inside our Render() function.
The Present() function has a very simple task. It swaps the back buffer and the front buffer.
The two parameters, set to 1 and 0, are not going to be used much in this tutorial.
Once again, let's add everything we've learned this lesson to what we already have.
[Game.cpp] [Game.h] [App.cpp]When you run this program, you'll probably find yourself with a black screen. What you are seeing here are the empty contents of the two swapchain buffers.
Just like last lesson, I've set up a few questions and exercises for you to try out. If you can do these, you should be ready to move forward.
Questions
1. What is a swap chain?
2. What properties could you change to resize the swapchain?
Exercises
1. Try setting the swap chain to a different size than the window.
2. Pick a different pixel format and set it in the swap chain description.
Next Lesson: Rendering Frames