Oct
29

Getting Started With DirectX 11 Jon Keon

C++, DirectX11

Now that we have a bit of a basic framework going with the SystemWindow, we can start using that Window to actually display something.

In this case, we’re going to get up and running with DirectX 11. While DirectX 9 is still somewhat relevant, it’s rapidly diminishing and with Microsoft announcing that they will be dropping support for Windows XP, everyone will be moving onward to a new OS (most likely Win7 or Win8). DirectX 10 (and 10.1) are somewhat of a black sheep in that it wasn’t around long enough before 11 came out to gain traction and personally feels a bit like a beta of DirectX 11. So in the interest of keeping relevant and modern, I’m only going to focus on and implement for DirectX 11.

There is DirectX 11.1 but that’s for the Win8 Developer preview build and mostly suited towards building Metro applications. Once Win8 is released and DX11.1 has noticeable improvements to vanilla DX11, I’ll make the jump and upgrade.

Requirements:

  • DirectX 11 Compatible Video Card

There’s really only one requirement and that’s to have a DirectX 11 Compatible Video Card. If you have a recent computer, most likely you’re good to go but just be aware.

Architecture:

I’ll be keeping with the idea behind the SystemWindow class and creating a SystemGraphics class. Any of the “System” classes will eventually be used for Platform Independence should I decide to support running on a Mac/Linux in addition to Windows or allowing for different Graphics API’s such as OpenGL or a Software Rasterizer. Most likely I’ll just be sticking with Windows and DirectX but it’s good to plan ahead.


We’ll start by taking a look at the SystemGraphics.h file.

SystemGraphics.h

/*********************************
*Class: D3D11API
*Description:
*Author: jkeon
**********************************/

#ifndef _SYSTEMGRAPHICS_H_
#define _SYSTEMGRAPHICS_H_

//////////////////////////////////////////////////////////////////////
// LINKING ///////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")

//////////////////////////////////////////////////////////////////////
// INCLUDES //////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

#include <util/DebugUtil.h>
#include <util/Macros.h>
#include <engine/platform/window/SystemWindow.h>
#include <DXGI.h>
#include <D3D11.h>
#include <vector>

//////////////////////////////////////////////////////////////////////
// NAMESPACE /////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

namespace Athena {

	//////////////////////////////////////////////////////////////////////
	// GLOBALS ///////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////

	//////////////////////////////////////////////////////////////////////
	// CLASS DECLARATION /////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////

	class SystemGraphics {

	//TYPEDEFS
		typedef std::vector<IDXGIAdapter1*> IDXGIAdapterVector;
		typedef std::vector<IDXGIOutput*> IDXGIOutputVector;

	//PUBLIC FUNCTIONS
	public:
		SystemGraphics();
		virtual ~SystemGraphics();

		bool Init(SystemWindow *window, int adapterIndex, int outputIndex, int numBackBuffers, bool vsync);
		void Destroy();

		void Begin();
		void End();

		bool ToggleFullScreen(bool fullScreen);

		void GetDXGIAdapters(IDXGIAdapterVector &adapterVector);
		void SetDXGIAdapter(UINT adapterIndex, IDXGIAdapter1 *adapter);

		void GetDXGIOutputs(IDXGIOutputVector &outputVector);
		void SetDXGIOutput(UINT outputIndex, IDXGIOutput *output);

		DXGI_MODE_DESC* GetDisplayModes();
		void SetDisplayMode(DXGI_MODE_DESC *displayMode);

		DXGI_ADAPTER_DESC1* GetAdapterDesc();

	//PUBLIC VARIABLES
	public:

	//PROTECTED FUNCTIONS
	protected:

	//PROTECTED VARIABLES
	protected:

	//PRIVATE FUNCTIONS
	private:
		SystemGraphics(const SystemGraphics& other);
		SystemGraphics& operator = (const SystemGraphics& other);

		bool InitDXGIFactory();
		bool InitDXGIAdapter();
		bool InitDXGIOutput();
		bool InitDisplayModes();
		bool InitSwapChainDesc();
		bool InitDeviceAndSwapChain();
		bool InitBackBuffer();
		bool InitViewport();

		void OnWindowResize(WPARAM wparam, LPARAM lparam);

	//PRIVATE VARIABLES
	private:

		HRESULT hr;

		bool m_fullScreen;
		bool m_vsync;

		SystemWindow* p_window;

		int m_numBackBuffers;
		int m_msaaLevel;
		int m_msaaQuality;

		UINT m_adapterIndex;
		UINT m_outputIndex;

		IDXGIFactory1* p_factory;
		IDXGIAdapter1* p_adapter;
		IDXGIOutput* p_output;

		DXGI_FORMAT m_desiredFormat;
		DXGI_MODE_DESC* p_displayModes;
		DXGI_MODE_DESC m_displayMode;

		DXGI_ADAPTER_DESC1 m_adapterDesc;

		DXGI_SWAP_CHAIN_DESC m_swapChainDesc;

		IDXGISwapChain* p_swapChain;
		ID3D11Device* p_device;
		ID3D11DeviceContext* p_deviceContext;

		ID3D11Texture2D* p_backbuffer;
		ID3D11RenderTargetView* p_backbufferRenderTarget;

		D3D11_VIEWPORT m_viewport;

		D3D_FEATURE_LEVEL m_targetFeatureLevel;
		D3D_FEATURE_LEVEL m_highestFeatureLevel;

	};

	//////////////////////////////////////////////////////////////////////
	// STATICS ///////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////

}
#endif

Not too much to say here as it’s very basic. All we really do is initialize and destroy the various DirectX components and have a few helper methods. Obviously this class isn’t even close to complete as we  will need to add a lot more functionality to be able to actually render something. But again, it’s all about baby steps and making sure you have a solid understanding of the foundations you are building on top of.

It’s boring and unsatisfying to have to do some much grunt work just to get things going but it’s necessary. This also means that you’re going to be doing a lot of reading instead of coding at the beginning. I’d highly recommend the series by Fabian Giesen on the Graphics Pipeline. It’s a 13 part, heavy in the theory series but it gives you a great understanding of how everything works under the hood and makes the functions that the DX11 API exposes much more meaninful.

In addition, I’d also always have a window to MSDN open. It can be tricky to navigate the site but just copy pasting the function or flag into Google generates the first hit and you can see exactly what you need to pass in, what it returns and (hopefully) why.

 

 Constructor:

SystemGraphics::SystemGraphics() {

		m_fullScreen = false;
		m_vsync = false;

		m_adapterIndex = 0;
		m_outputIndex = 0;

		m_numBackBuffers = 1;
		m_msaaLevel = 1;
		m_msaaQuality = 0;

		m_desiredFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
		m_targetFeatureLevel = D3D_FEATURE_LEVEL_11_0;

		p_window = NULL;

		p_factory = NULL;
		p_adapter = NULL;
		p_output = NULL;
		p_displayModes = NULL;

		p_swapChain = NULL;
		p_device = NULL;
		p_deviceContext = NULL;

		p_backbuffer = NULL;
		p_backbufferRenderTarget = NULL;
	}

Once again, the Constructor does nothing but initialize pointers to be NULL and set a few of the member variables to defaults.

Init:

bool SystemGraphics::Init(SystemWindow *window, int adapterIndex, int outputIndex, int numBackBuffers, bool vsync) {

		p_window = window;
		m_fullScreen = p_window->IsFullScreen();
		m_adapterIndex = adapterIndex;
		m_outputIndex = outputIndex;
		m_numBackBuffers = numBackBuffers;
		m_vsync = vsync;

		FAIL_ON_INIT(InitDXGIFactory());
		FAIL_ON_INIT(InitDXGIAdapter());
		FAIL_ON_INIT(InitDXGIOutput());
		FAIL_ON_INIT(InitDisplayModes());
		FAIL_ON_INIT(InitSwapChainDesc());
		FAIL_ON_INIT(InitDeviceAndSwapChain());
		FAIL_ON_INIT(InitBackBuffer());
		FAIL_ON_INIT(InitViewport());

		p_window->AddMessageCallback(WM_SIZE, BIND_MEM_CB(&SystemGraphics::OnWindowResize, this));

		return true;
	}

The Init function is again where everything actually starts up. It takes in a pointer to a SystemWindow so that the SystemGraphics class knows what Window we are going to be rendering to. We also use the window to determine whether we are in FullScreen mode or not.

The adapter index and output index refer to the index of the video card and monitor attached to that video card respectively on your current system. This will seem a bit catch-22 at the moment but once we are saving/loading user configuration settings, these values will make sense.

By default, we’ll have them both set to 0, although in the future we’ll want to default to the user’s best video card and the primary monitor attached to that videocard.

The number of back buffers is a setting for whether we want to use Double Buffering or Triple Buffering.

Vsync is whether we want to run as fast as possible at the expense of potentially wasted frames or limit ourselves to the monitor refresh rate at the potential drastic loss of framerate when one frame takes too long to render. There’s a great explanation of what Vsync is and how it affects your experience here.

Next we initialize all of the sub components of the SystemGraphics class. The FAIL_ON_INIT macro is a very simple macro to make the code a bit more readable and clean that essentially says if any of these functions fail, don’t execute the next one and return false.

//FAIL_ON_INIT - Returns false if the passed value equates to false. Used for exiting a chain of init statements
#define FAIL_ON_INIT(p) {if ((p) == false) { return false;}}

Finally we add a callback to the SystemWindow class for when the Window Resizes. This allows the Graphics components to resize themselves as well to keep in sync with any resizing that happens with our Window.

InitDXGIFactory:

//TODO: If this fails, we should throw up a message box or something and tell the user it's all over.
	bool SystemGraphics::InitDXGIFactory() {
		//Create a DXGI Factory
		hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&p_factory);
		if (FAILED(hr)) {
			etrace("CreateDXGIFactory1 Failed!");
			return false;
		}
		return true;
	}

It all starts with creating an instance of a IDXGIFactory1. Starting with DirectX 10, Microsoft introduced the concept of DXGI or Direct X Graphics Infrastructure. You can read more at the link but essentially it serves to help handle some of the low level operations that need to happen so you can focus more on implementing graphics.

If for whatever reason (and I really mean whatever… I have no idea how or why this would fail) we can’t create a DXGIFactory then it’s really all over and we can’t continue. In the future I’ll add some way of letting the user know this.

InitDXGIAdapter:

//TODO: If this fails, we should fallback to enumerating the Adapters and selecting the first one.
	bool SystemGraphics::InitDXGIAdapter() {
		//Get the Primary Adapter (Video Card)
		hr = p_factory->EnumAdapters1(m_adapterIndex, &p_adapter);
		if (FAILED(hr)) {
			etrace("EnumAdapters1 Failed!");
			return false;
		}
		return true;
	}

Once we have a factory we can use that factory to get access to the video cards attached to your system. Now in most cases, the user will only have one video card. But if you’re on a laptop you might have an integrated graphics solution on your motherboard and a video card. If you’re on a dev machine or you’re a hardcore pc enthusiast, it’s possible that you might have more than one video card.

To get the Adapter we pass in the adapter index. This is the “slot” the video card occupies in your system. By default we set this to 0 since if you only have one video card this is the slot it will occupy. If for some reason there are no video cards in at the slot we’ve specified we should try and see all the video cards attached to the system and choose the best one (Highest memory/processor speed most likely). If there are no video cards then we can’t do any DirectX and we’ll have to let the user know and quite the program.

Again all future problems that we can deal with later when we’re polishing. For now the main thing is Adapter = Video Card, we want the one in Slot 0.

InitDXGIOutput:

//TODO: If this fails, we should fallback to enumerating the Outputs and selecting the first one.
	bool SystemGraphics::InitDXGIOutput() {
		//Get the Primary Output (Monitor) using the Adapter
		hr = p_adapter->EnumOutputs(m_outputIndex, &p_output);
		if (FAILED(hr)) {
			etrace("EnumOutputs Failed!");
			return false;
		}
		return true;
	}

Now that we have an Adapter we need to get the Output associated with it. An Output is usually a Monitor but could be a TV, Projector, Laptop screen etc. If you have the physical video card in front of you, whatever is plugged into the back of it via a VGA/DVI/HDMI is an Output.

Again we’re using the output index which we default to 0 and we want to use the primary monitor to display our graphics to. In the possibility that there is no Output at that slot, we’ll want to loop through all connected Outputs and choose the “best” one. If nothing is connected to the video card, then there’s nothing we can display and we’ll need to exit and let the user know. (Although without a monitor… this may be tricky. Morse code system beeps?)

InitDisplayModes:

bool SystemGraphics::InitDisplayModes() {
		UINT numModes;
		//TODO: Pull these in from elsewhere
		int matchWidth = GetSystemMetrics(SM_CXSCREEN);
		int matchHeight = GetSystemMetrics(SM_CYSCREEN);
		//Get the Display Modes that fit the DXGI FORMAT
		//TODO: Which format should we use? Which Flags? Why?
		hr = p_output->GetDisplayModeList(m_desiredFormat, DXGI_ENUM_MODES_INTERLACED | DXGI_ENUM_MODES_SCALING, &numModes, NULL);
		if (FAILED(hr)) {
			etrace("GetDisplayModeList for Formats Failed!");
			return false;
		}

		//Create the DXGI Mode Description
		p_displayModes = new DXGI_MODE_DESC[numModes];
		if (p_displayModes == NULL) {
			etrace("Creation of DXGI_MODE_DESC Array with %i modes Failed!", numModes);
			return false;
		}

		//Populate the DXGI_MODE_DESC list with structures of supported Display Modes
		hr = p_output->GetDisplayModeList(m_desiredFormat, DXGI_ENUM_MODES_INTERLACED, &numModes, p_displayModes);
		if (FAILED(hr)) {
			etrace("GetDisplayModeList for populating DXGI_MODE_DESC Array Failed!");
			return false;
		}

		//Choose the Display Mode that most closely matches our current screen resolution
		ZeroMemory(&m_displayMode, sizeof(m_displayMode));
		int i;
		int len = (int)numModes;
		for (i = 0; i < len; ++i) {
			if (p_displayModes[i].Width == matchWidth && p_displayModes[i].Height == matchHeight) {
				m_displayMode = p_displayModes[i];
				return true;
			}
		}

		etrace("No suitable display mode was found for resolution of %i, %i!", matchWidth, matchHeight);
		return false;
	}

Great, so now we have a VideoCard and a Monitor from DXGI in the form of an Adapter and Output. We now need to know what Display Modes our Output supports so we can ensure that our rendering formats and timings are compatible.

The first thing we’ll want to do is determine what our current resolution of the screen is with the GetSystemMetrics function. This part is a bit of a catch-22 because in most games you can specify a resolution that is not the same as what your current screen resolution is. Ideally we want to use whatever the user has specified in their settings but if it’s the first time ever, we should default to the current screen size as most LCD monitors only operate really well at their native resolutions.

Next we call the GetDisplayModeList function on our Output. As weird as it is, this function is intended to be called twice. The first time we want to populate our numModes variable to see how many different modes our monitor supports. Then we can create an array of DXGI_MODE_DESC that has that many elements in it and call the function again to populate that array.

You’ll notice that I still need to do some investigation on which format and which flags I should be using to get the modes. For now the desired format is DXGI_FORMAT_R8G8B8A8_UNORM because that is what most of the tutorials set it as. The flags for Interlaced and Scaling are there for the same reason. I don’t think we really care about Interlaced unless we’re outputting to a TV and ideally we want to stay away from scaled resolutions so we can avoid stretching but again, I still need to research this more in depth.

Once we have all the possible display modes that our Output can support, we need to choose one to use. We simply iterate through all of them until we find one that matches our desired screen resolution. If we can’t find one we’ll have to handle this error eventually and let the user choose a supported display mode.

InitSwapChainDesc:

bool SystemGraphics::InitSwapChainDesc() {
		//Construct our SwapChainDesc
		ZeroMemory(&m_swapChainDesc, sizeof(m_swapChainDesc));

		//Double or Triple Buffering
		m_swapChainDesc.BufferCount = m_numBackBuffers;

		//BUFFER DESC
		m_swapChainDesc.BufferDesc.Format = m_desiredFormat;
		m_swapChainDesc.BufferDesc.Height = m_displayMode.Height;
		m_swapChainDesc.BufferDesc.RefreshRate.Numerator = (m_vsync) ? m_displayMode.RefreshRate.Numerator : 0;
		m_swapChainDesc.BufferDesc.RefreshRate.Denominator = (m_vsync) ? m_displayMode.RefreshRate.Denominator : 1;
		m_swapChainDesc.BufferDesc.Scaling = m_displayMode.Scaling;
		m_swapChainDesc.BufferDesc.ScanlineOrdering = m_displayMode.ScanlineOrdering;
		m_swapChainDesc.BufferDesc.Width = m_displayMode.Width;

		m_swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

		m_swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

		m_swapChainDesc.OutputWindow = *p_window->GetHWND();

		//SAMPLE DESC
		m_swapChainDesc.SampleDesc.Count = m_msaaLevel;
		m_swapChainDesc.SampleDesc.Quality = m_msaaQuality;

		m_swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

		m_swapChainDesc.Windowed = TRUE;

		return true;
	}

Next up is creating a DXGI_SWAP_CHAIN_DESC. This is simply a structure to define what our SwapChain will do before we create it.

First this to do is initialize it by zeroing out the memory it occupies. We do this so that all the values are set to defaults in case we forget to set one and doesn’t contain any garbage values.

The BufferCount is where we tie in our desire for Double or Triple buffering.

The BufferDesc is a description structure for how each of those back buffers will behave. We set the format based on our desired format and fill in the rest of the parameters based on the chosen display mode. Note that if we have vsync enabled, we want to override the RefreshRate so that we can draw as a fast as our hardware will let us.

The BufferUsage is a flag to say we will be using these buffers to render to them from DirectX.

The Flags are option settings. We specify the DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH because this will allow us to use Alt-Enter for toggling between Fullscreen and windowed mode and it will also ensure that we can hook into the Size messages from Windows to resize our buffers properly when that switch occurs.

The OutputWindow is simply the handle of our window instance from the SystemWindow.

The SampleDesc specifies what sort of MSAA we want to use. For now we set it to nothing.

The SwapEffect specifies what we want to have happen to our front buffer after we switch to the next back buffer. Sometimes you want to keep that front buffer around in order to perform some sort of post processing on it. But for the most part, once it’s been shown, it’s old news and you’ll want to discard it, hence the flag DXGI_SWAP_EFFECT_DISCARD.

InitDeviceAndSwapChain:

bool SystemGraphics::InitDeviceAndSwapChain() {

		//TODO: Flags we might be concerned with are the Debug D3D11_CREATE_DEVICE_DEBUG

		//Create the Device and Swap Chain - If we specify the Adapter the type must be D3D_DRIVER_TYPE_UNKNOWN
		hr = D3D11CreateDeviceAndSwapChain(p_adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, 0, &m_targetFeatureLevel, 1, D3D11_SDK_VERSION, &m_swapChainDesc, &p_swapChain, &p_device, &m_highestFeatureLevel, &p_deviceContext);
		if (FAILED(hr)) {
			etrace("D3D11CreateDeviceAndSwapChain Failed!");
			return false;
		}
		return true;
	}

Now we can actually create our SwapChain and in the process, create a Device and a DeviceContext. These three pieces are what you will interact with to make DirectX do what you want it to do.

Fortunately there’s a very simple and easy function to do this for us, D3D11CreateDeviceAndSwapChain.

The function takes in our Adapter that we acquired earlier. You can pass NULL in here and the function will take the primary Adapter it finds. I think it’s better to be explicit if we can.

Note that if we specify an Adapter we have to specify that the the DriverType is D3D_DRIVER_TYPE_UNKNOWN. Since we know that our Adapter is actually a video card this will resolve internally to D3D_DRIVER_TYPE_HARDWARE which is what we want anyway.

The next parameter is for if we wanted to use Software Rendering which we don’t so we can just set it to NULL.

Next are flags which i’ve set to 0 for now but will probably set the D3D11_CREATE_DEVICE_DEBUG flag when running in DebugMode. This will require changes to the pragma library links at the top of the header to link against the debug versions of those DLL’s too.

The target feature level is the Feature level that we want to try and get from the Device. Since we’re doing DirectX11 we want to use D3D_FEATURE_LEVEL_11_0.

Next is the number of feature levels in our target feature level array which is currently just 1.

Next you always specify D3D11_SDK_VERSION. Not sure why this isn’t just defaulted.

Then it’s just a pointer to our swap chain description, the pointer to where we want to store our swapchain and the pointer to where we want to store our device.

The next parameter will get written into with the highest feature level we can support. This should equal D3D_FEATURE_LEVEL_11_0 if you have a DirectX 11 compatible video card.

Finally, we pass the location for DirectX to populate our Device Context.

InitBackBuffer:

bool SystemGraphics::InitBackBuffer() {
		hr = p_swapChain->GetBuffer(0, __uuidof(p_backbuffer), reinterpret_cast<void**>(&p_backbuffer));
		if (FAILED(hr)) {
			etrace("Get Buffer Failed!");
			return false;
		}

		//TODO: D3D11_RENDER_TARGET_VIEW_DESC

		hr = p_device->CreateRenderTargetView(p_backbuffer, NULL, &p_backbufferRenderTarget);
		if (FAILED(hr)) {
			etrace("Create Render Target View Failed!");
			return false;
		}

		//TODO: Handle DepthStencil
		p_deviceContext->OMSetRenderTargets(1, &p_backbufferRenderTarget, NULL);

		return true;
	}

Ok, almost there. Next we need to get access to our backbuffer since that is what we will be drawing to. We simply get the first backbuffer and then create a RenderTargetView using that backbuffer.

There are still more things to configure but we can do that later.

Once we have a RenderTargetView we can tell our Device Context to use it to render to.

InitViewport:

bool SystemGraphics::InitViewport() {
		ZeroMemory(&m_viewport, sizeof(m_viewport));

		m_viewport.Width = (float)m_displayMode.Width;
		m_viewport.Height = (float)m_displayMode.Height;
		m_viewport.TopLeftX = 0.0f;
		m_viewport.TopLeftY = 0.0f;
		m_viewport.MinDepth = 0.0f;
		m_viewport.MaxDepth = 1.0f;

		p_deviceContext->RSSetViewports(1, &m_viewport);

		return true;
	}

 

The last step is to create a Viewport. We simply initialize it with our screen size values and tell the device context to use this viewport when rendering to our previously set RenderTargetView.

Begin and End:

void SystemGraphics::Begin() {

		float color[4];
		color[0] = 0.2f;
		color[1] = 0.2f;
		color[2] = 0.2f;
		color[3] = 1.0f;

		p_deviceContext->ClearRenderTargetView(p_backbufferRenderTarget, color);
	}

	void SystemGraphics::End() {
		p_swapChain->Present((m_vsync) ? 1 : 0, 0);
	}

Everything is finally initialized and good to go. Begin and End are two very simple functions that get called every frame. In Begin, we tell the Device Context to clear our RenderTargetView to the color we specified. In this case a dark grey.

In End, we simply present the backbuffer to the screen accounting for vsync.

You won’t get anything exciting but you’ll know it works if you get a dark grey screen.

 

 

 

That’s pretty much it. There is a destroy function to ensure that everything gets cleaned up properly but you can see that in the full source at the end of the article. One thing to note is that you MUST exist fullscreen mode before destroying everything otherwise you might be stuck in a bad state and have to restart your computer.

Before we get to that, there are two other useful functions to cover.

 

OnWindowResize:

void SystemGraphics::OnWindowResize(WPARAM wparam, LPARAM lparam) {
		//If we have a Swap Chain we need to resize it
		if (p_swapChain != NULL) {
			//Release Buffers
			p_deviceContext->OMSetRenderTargets(0, NULL, NULL);

			p_backbufferRenderTarget->Release();
			p_backbuffer->Release();

			HRESULT hr;
			int horizontalResolution = LOWORD(lparam);
			int verticalResolution = HIWORD(lparam);

			dtrace("RESIZING SYSTEM GRAPHICS: Message says %i, %i", horizontalResolution, verticalResolution);

			hr = p_swapChain->ResizeBuffers(0, horizontalResolution, verticalResolution, DXGI_FORMAT_UNKNOWN, 0);

			if (FAILED(hr)) {
				etrace("Resize Buffers Failed!");
				return;
			}

			InitBackBuffer();

			ZeroMemory(&m_viewport, sizeof(m_viewport));

			m_viewport.Width = (float)horizontalResolution;
			m_viewport.Height = (float)verticalResolution;
			m_viewport.TopLeftX = 0.0f;
			m_viewport.TopLeftY = 0.0f;
			m_viewport.MinDepth = 0.0f;
			m_viewport.MaxDepth = 1.0f;

			p_deviceContext->RSSetViewports(1, &m_viewport);
		}
	}

OnWindowResize gets called every time we get a WM_SIZE message from the SystemWindow. In the DXGI Best Practices we will want to respond to these events to resize our buffers to the new size of our window. This will also happen when we go between full screen and windowed mode.

We simply have to make sure to clean everything up, resize our buffers and then recreate them.

 

ToggleFullScreen:

bool SystemGraphics::ToggleFullScreen(bool fullScreen) {
		//Only if there's a change and we have a valid swapchain and monitor
		if (m_fullScreen != fullScreen && p_swapChain != NULL && p_output != NULL) {
			m_fullScreen = fullScreen;

			/*hr = p_swapChain->ResizeTarget(&m_displayMode);
			if (FAILED(hr)) {
				m_fullScreen = !m_fullScreen;
				etrace("Unable to ResizeTarget!");
				return false;
			}*/

			//Try and set the Fullscreen state
			hr = p_swapChain->SetFullscreenState((m_fullScreen) ? TRUE : FALSE, (m_fullScreen) ? p_output : NULL);
			//If we fail, reset our internal state
			if (FAILED(hr)) {
				m_fullScreen = !m_fullScreen;
				etrace("Unable to SetFullScreen State!");
				return false;
			}
			else {

				/*DXGI_MODE_DESC tempMode;
				ZeroMemory(&tempMode, sizeof(tempMode));
				tempMode.Format = (m_fullScreen) ? DXGI_FORMAT_UNKNOWN : m_displayMode.Format;
				tempMode.Height = m_displayMode.Height;
				tempMode.Scaling = m_displayMode.Scaling;
				tempMode.ScanlineOrdering = m_displayMode.ScanlineOrdering;
				tempMode.Width = m_displayMode.Width;

				hr = p_swapChain->ResizeTarget(&tempMode);
				if (FAILED(hr)) {
					m_fullScreen = !m_fullScreen;
					etrace("Unable to ResizeTarget!");
					return false;
				}
				else {
					return true;
				}*/
				return true;
			}
		}
		else {
			return false;
		}
	}

This one is much more complicated and as you can see by the commented code, I still need to investigate this further.

The main thing we want to ensure when using DXGI is that when we are in fullscreen mode, we want to be using a “Flip” operation on the buffers instead of a “Blit” operation. (See DXGI Best Practices again).

The docs say to resize your targets first, then set the fullscreen state and then call resize targets again with a zeroed out RefreshRate.

When I do that though I get some funky sizing behaviors when switching back and forth.

So for now I simply set the fullscreen state which DXGI takes and handles the setting of the Fullscreen state on our SystemWindow for us. Notice that we never tell the Window to switch fullscreen states even though SystemWindow exposes that functionality. The rule of thumb here is that if you’re using SystemGraphics with a SystemWindow, SystemGraphics gets to control the SystemWindow state.

I will probably modify the code to make this more safe in the future, but for now just be aware of it.

When switching state, the WM_SIZE message is dispatched which handles our resizing methods as listed before.

 

 

There you have it, DirectX11 Initialization. Still pretty boring in terms of visuals but again it’s all about the foundations. Visuals will come afterwards.

 

As promised, the full source for SystemGraphics.cpp

SystemGraphics.cpp

//////////////////////////////////////////////////////////////////////
// INCLUDES //////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

#include "SystemGraphics.h"

//////////////////////////////////////////////////////////////////////
// NAMESPACE /////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

namespace Athena {

	//////////////////////////////////////////////////////////////////////
	// STATICS ///////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////

	//////////////////////////////////////////////////////////////////////
	// CONSTRUCTORS //////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////

	SystemGraphics::SystemGraphics() {

		m_fullScreen = false;
		m_vsync = false;

		m_adapterIndex = 0;
		m_outputIndex = 0;

		m_numBackBuffers = 1;
		m_msaaLevel = 1;
		m_msaaQuality = 0;

		m_desiredFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
		m_targetFeatureLevel = D3D_FEATURE_LEVEL_11_0;

		p_window = NULL;

		p_factory = NULL;
		p_adapter = NULL;
		p_output = NULL;
		p_displayModes = NULL;

		p_swapChain = NULL;
		p_device = NULL;
		p_deviceContext = NULL;

		p_backbuffer = NULL;
		p_backbufferRenderTarget = NULL;
	}

	//////////////////////////////////////////////////////////////////////
	// DESTRUCTOR ////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////

	SystemGraphics::~SystemGraphics() {

	}

	//////////////////////////////////////////////////////////////////////
	// INITIALIZE ////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////

	bool SystemGraphics::Init(SystemWindow *window, int adapterIndex, int outputIndex, int numBackBuffers, bool vsync) {

		p_window = window;
		m_fullScreen = p_window->IsFullScreen();
		m_adapterIndex = adapterIndex;
		m_outputIndex = outputIndex;
		m_numBackBuffers = numBackBuffers;
		m_vsync = vsync;

		FAIL_ON_INIT(InitDXGIFactory());
		FAIL_ON_INIT(InitDXGIAdapter());
		FAIL_ON_INIT(InitDXGIOutput());
		FAIL_ON_INIT(InitDisplayModes());
		FAIL_ON_INIT(InitSwapChainDesc());
		FAIL_ON_INIT(InitDeviceAndSwapChain());
		FAIL_ON_INIT(InitBackBuffer());
		FAIL_ON_INIT(InitViewport());

		p_window->AddMessageCallback(WM_SIZE, BIND_MEM_CB(&SystemGraphics::OnWindowResize, this));

		return true;
	}

	//TODO: If this fails, we should throw up a message box or something and tell the user it's all over.
	bool SystemGraphics::InitDXGIFactory() {
		//Create a DXGI Factory
		hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&p_factory);
		if (FAILED(hr)) {
			etrace("CreateDXGIFactory1 Failed!");
			return false;
		}
		return true;
	}

	//TODO: If this fails, we should fallback to enumerating the Adapters and selecting the first one.
	bool SystemGraphics::InitDXGIAdapter() {
		//Get the Primary Adapter (Video Card)
		hr = p_factory->EnumAdapters1(m_adapterIndex, &p_adapter);
		if (FAILED(hr)) {
			etrace("EnumAdapters1 Failed!");
			return false;
		}
		return true;
	}

	//TODO: If this fails, we should fallback to enumerating the Outputs and selecting the first one.
	bool SystemGraphics::InitDXGIOutput() {
		//Get the Primary Output (Monitor) using the Adapter
		hr = p_adapter->EnumOutputs(m_outputIndex, &p_output);
		if (FAILED(hr)) {
			etrace("EnumOutputs Failed!");
			return false;
		}
		return true;
	}

	bool SystemGraphics::InitDisplayModes() {
		UINT numModes;
		//TODO: Pull these in from elsewhere
		int matchWidth = GetSystemMetrics(SM_CXSCREEN);
		int matchHeight = GetSystemMetrics(SM_CYSCREEN);
		//Get the Display Modes that fit the DXGI FORMAT
		//TODO: Which format should we use? Which Flags? Why?
		hr = p_output->GetDisplayModeList(m_desiredFormat, DXGI_ENUM_MODES_INTERLACED | DXGI_ENUM_MODES_SCALING, &numModes, NULL);
		if (FAILED(hr)) {
			etrace("GetDisplayModeList for Formats Failed!");
			return false;
		}

		//Create the DXGI Mode Description
		p_displayModes = new DXGI_MODE_DESC[numModes];
		if (p_displayModes == NULL) {
			etrace("Creation of DXGI_MODE_DESC Array with %i modes Failed!", numModes);
			return false;
		}

		//Populate the DXGI_MODE_DESC list with structures of supported Display Modes
		hr = p_output->GetDisplayModeList(m_desiredFormat, DXGI_ENUM_MODES_INTERLACED, &numModes, p_displayModes);
		if (FAILED(hr)) {
			etrace("GetDisplayModeList for populating DXGI_MODE_DESC Array Failed!");
			return false;
		}

		//Choose the Display Mode that most closely matches our current screen resolution
		ZeroMemory(&m_displayMode, sizeof(m_displayMode));
		int i;
		int len = (int)numModes;
		for (i = 0; i < len; ++i) {
			if (p_displayModes[i].Width == matchWidth && p_displayModes[i].Height == matchHeight) {
				m_displayMode = p_displayModes[i];
				return true;
			}
		}

		etrace("No suitable display mode was found for resolution of %i, %i!", matchWidth, matchHeight);
		return false;
	}

	bool SystemGraphics::InitSwapChainDesc() {
		//Construct our SwapChainDesc
		ZeroMemory(&m_swapChainDesc, sizeof(m_swapChainDesc));

		//Double or Triple Buffering
		m_swapChainDesc.BufferCount = m_numBackBuffers;

		//BUFFER DESC
		m_swapChainDesc.BufferDesc.Format = m_desiredFormat;
		m_swapChainDesc.BufferDesc.Height = m_displayMode.Height;
		m_swapChainDesc.BufferDesc.RefreshRate.Numerator = (m_vsync) ? m_displayMode.RefreshRate.Numerator : 0;
		m_swapChainDesc.BufferDesc.RefreshRate.Denominator = (m_vsync) ? m_displayMode.RefreshRate.Denominator : 1;
		m_swapChainDesc.BufferDesc.Scaling = m_displayMode.Scaling;
		m_swapChainDesc.BufferDesc.ScanlineOrdering = m_displayMode.ScanlineOrdering;
		m_swapChainDesc.BufferDesc.Width = m_displayMode.Width;

		m_swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

		m_swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

		m_swapChainDesc.OutputWindow = *p_window->GetHWND();

		//SAMPLE DESC
		m_swapChainDesc.SampleDesc.Count = m_msaaLevel;
		m_swapChainDesc.SampleDesc.Quality = m_msaaQuality;

		m_swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

		m_swapChainDesc.Windowed = TRUE;

		return true;
	}

	bool SystemGraphics::InitDeviceAndSwapChain() {

		//TODO: Flags we might be concerned with are the Debug D3D11_CREATE_DEVICE_DEBUG

		//Create the Device and Swap Chain - If we specify the Adapter the type must be D3D_DRIVER_TYPE_UNKNOWN
		hr = D3D11CreateDeviceAndSwapChain(p_adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, 0, &m_targetFeatureLevel, 1, D3D11_SDK_VERSION, &m_swapChainDesc, &p_swapChain, &p_device, &m_highestFeatureLevel, &p_deviceContext);
		if (FAILED(hr)) {
			etrace("D3D11CreateDeviceAndSwapChain Failed!");
			return false;
		}
		return true;
	}

	bool SystemGraphics::InitBackBuffer() {
		hr = p_swapChain->GetBuffer(0, __uuidof(p_backbuffer), reinterpret_cast<void**>(&p_backbuffer));
		if (FAILED(hr)) {
			etrace("Get Buffer Failed!");
			return false;
		}

		//TODO: D3D11_RENDER_TARGET_VIEW_DESC

		hr = p_device->CreateRenderTargetView(p_backbuffer, NULL, &p_backbufferRenderTarget);
		if (FAILED(hr)) {
			etrace("Create Render Target View Failed!");
			return false;
		}

		//TODO: Handle DepthStencil
		p_deviceContext->OMSetRenderTargets(1, &p_backbufferRenderTarget, NULL);

		return true;
	}

	bool SystemGraphics::InitViewport() {
		ZeroMemory(&m_viewport, sizeof(m_viewport));

		m_viewport.Width = (float)m_displayMode.Width;
		m_viewport.Height = (float)m_displayMode.Height;
		m_viewport.TopLeftX = 0.0f;
		m_viewport.TopLeftY = 0.0f;
		m_viewport.MinDepth = 0.0f;
		m_viewport.MaxDepth = 1.0f;

		p_deviceContext->RSSetViewports(1, &m_viewport);

		return true;
	}

	//////////////////////////////////////////////////////////////////////
	// DESTROY ///////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////

	void SystemGraphics::Destroy() {
		//It's important to switch back to window mode before closing the application
		if (p_swapChain) {
			p_swapChain->SetFullscreenState(FALSE, NULL);
		}

		SAFE_RELEASE(p_backbufferRenderTarget);
		SAFE_RELEASE(p_backbuffer);
		SAFE_RELEASE(p_deviceContext);
		SAFE_RELEASE(p_device);
		SAFE_RELEASE(p_swapChain);
		p_window = NULL;
		SAFE_DELETE_ARRAY(p_displayModes);
		SAFE_RELEASE(p_output);
		SAFE_RELEASE(p_adapter);
		SAFE_RELEASE(p_factory);
	}

	//////////////////////////////////////////////////////////////////////
	// BODY //////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////

	void SystemGraphics::Begin() {

		float color[4];
		color[0] = 0.2f;
		color[1] = 0.2f;
		color[2] = 0.2f;
		color[3] = 1.0f;

		p_deviceContext->ClearRenderTargetView(p_backbufferRenderTarget, color);
	}

	void SystemGraphics::End() {
		p_swapChain->Present((m_vsync) ? 1 : 0, 0);
	}

	void SystemGraphics::OnWindowResize(WPARAM wparam, LPARAM lparam) {
		//If we have a Swap Chain we need to resize it
		if (p_swapChain != NULL) {
			//Release Buffers
			p_deviceContext->OMSetRenderTargets(0, NULL, NULL);

			p_backbufferRenderTarget->Release();
			p_backbuffer->Release();

			HRESULT hr;
			int horizontalResolution = LOWORD(lparam);
			int verticalResolution = HIWORD(lparam);

			dtrace("RESIZING SYSTEM GRAPHICS: Message says %i, %i", horizontalResolution, verticalResolution);

			hr = p_swapChain->ResizeBuffers(0, horizontalResolution, verticalResolution, DXGI_FORMAT_UNKNOWN, 0);

			if (FAILED(hr)) {
				etrace("Resize Buffers Failed!");
				return;
			}

			InitBackBuffer();

			ZeroMemory(&m_viewport, sizeof(m_viewport));

			m_viewport.Width = (float)horizontalResolution;
			m_viewport.Height = (float)verticalResolution;
			m_viewport.TopLeftX = 0.0f;
			m_viewport.TopLeftY = 0.0f;
			m_viewport.MinDepth = 0.0f;
			m_viewport.MaxDepth = 1.0f;

			p_deviceContext->RSSetViewports(1, &m_viewport);
		}
	}

	bool SystemGraphics::ToggleFullScreen(bool fullScreen) {
		//Only if there's a change and we have a valid swapchain and monitor
		if (m_fullScreen != fullScreen && p_swapChain != NULL && p_output != NULL) {
			m_fullScreen = fullScreen;

			/*hr = p_swapChain->ResizeTarget(&m_displayMode);
			if (FAILED(hr)) {
				m_fullScreen = !m_fullScreen;
				etrace("Unable to ResizeTarget!");
				return false;
			}*/

			//Try and set the Fullscreen state
			hr = p_swapChain->SetFullscreenState((m_fullScreen) ? TRUE : FALSE, (m_fullScreen) ? p_output : NULL);
			//If we fail, reset our internal state
			if (FAILED(hr)) {
				m_fullScreen = !m_fullScreen;
				etrace("Unable to SetFullScreen State!");
				return false;
			}
			else {

				/*DXGI_MODE_DESC tempMode;
				ZeroMemory(&tempMode, sizeof(tempMode));
				tempMode.Format = (m_fullScreen) ? DXGI_FORMAT_UNKNOWN : m_displayMode.Format;
				tempMode.Height = m_displayMode.Height;
				tempMode.Scaling = m_displayMode.Scaling;
				tempMode.ScanlineOrdering = m_displayMode.ScanlineOrdering;
				tempMode.Width = m_displayMode.Width;

				hr = p_swapChain->ResizeTarget(&tempMode);
				if (FAILED(hr)) {
					m_fullScreen = !m_fullScreen;
					etrace("Unable to ResizeTarget!");
					return false;
				}
				else {
					return true;
				}*/
				return true;
			}
		}
		else {
			return false;
		}
	}

	//////////////////////////////////////////////////////////////////////
	// ADAPTERS //////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////

	void SystemGraphics::GetDXGIAdapters(IDXGIAdapterVector &adapterVector) {
		//TODO: Should we remove and release all the adapters inside?
		UINT i = 0; 
		IDXGIAdapter1* adapter; 
		while(p_factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND) { 
			adapterVector.push_back(adapter); 
			++i; 
		} 
	}

	void SystemGraphics::SetDXGIAdapter(UINT adapterIndex, IDXGIAdapter1* adapter) {
		//If we're setting the same adapter, return
		if (adapterIndex == m_adapterIndex) {
			return;
		}
		//Check to see if we already have an adapter and clean it up nicely
		SAFE_RELEASE(p_adapter);

		m_adapterIndex = adapterIndex;
		p_adapter = adapter;
		//TODO: Invalidate all dependent setup etc
	}

	DXGI_ADAPTER_DESC1* SystemGraphics::GetAdapterDesc() {
		ZeroMemory(&m_adapterDesc, sizeof(m_adapterDesc));
		//Get the DXGI_ADAPTER_DESC of the Adapter
		hr = p_adapter->GetDesc1(&m_adapterDesc);
		if (FAILED(hr)) {
			etrace("GetDesc1 Failed!");
			return NULL;
		}
		return &m_adapterDesc;
	}

	//////////////////////////////////////////////////////////////////////
	// OUTPUTS ///////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////

	void SystemGraphics::GetDXGIOutputs(IDXGIOutputVector &outputVector) {
		//TODO: Should we remove and release all the outputs inside?
		UINT i = 0;
		IDXGIOutput* output;
		while(p_adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND){
			outputVector.push_back(output);
			++i;
		}
	}

	void SystemGraphics::SetDXGIOutput(UINT outputIndex, IDXGIOutput* output) {
		//If we're setting the same output, return
		if (outputIndex == m_outputIndex) {
			return;
		}
		//Check to see if we already have an output and clean it up nicely
		SAFE_RELEASE(p_output);

		m_outputIndex = outputIndex;
		p_output = output;
		//TODO: Invalidate all dependent setup etc
	}

	//////////////////////////////////////////////////////////////////////
	// DISPLAY MODES /////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////

	DXGI_MODE_DESC* SystemGraphics::GetDisplayModes() {
		return p_displayModes;
	}

	void SystemGraphics::SetDisplayMode(DXGI_MODE_DESC* displayMode) {
		m_displayMode = *displayMode;
		//TODO: Invalidate all dependent setup etc
	}

	//////////////////////////////////////////////////////////////////////
	// GETTERS/SETTERS ///////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////

}

This entry was posted on Saturday, October 29th, 2011 at 12:01 pm and is filed under C++, DirectX11. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.