C++ and DirectX 11
Initialize
After finishing the basic framework you can continue with initializing DirectX. All DirectX functions will be encapsulated in a new class. Start by creating the dxManager.h header file.
dxManager.h
#pragma once
#include <d3d11.h>
#include "macros.h"
#pragma comment (lib, "d3d11.lib")
static bool Error(const LPCSTR msg)
{
MessageBoxA(nullptr, msg, "Error", MB_ICONERROR);
return false;
}
class DXManager
{
public:
DXManager();
void Shutdown();
bool Initialize(HWND, UINT, UINT);
void EndFrameRender();
void StartFrameRender();
private:
float _clearColor[4];
ID3D11Device *_device;
ID3D11DeviceContext *_deviceContext;
IDXGISwapChain *_swapChain;
ID3D11RenderTargetView *_renderTargetView;
};
[2] This include file has everything related to DirectX which is needed for this tutorial. For example variables with the prefix ID3D11 are defined there.
[4] #pragma comment is a compiler directive. The (lib, "d3d11.lib") is a convenient way for telling the linker to add the d3d11.lib library to the list of library dependencies. More info about pragma comment is on MSDN. The same effect can be achieved by adding the library to the project dependencies: Right click on Project and choose Properties navigate to Linker -> Input -> Additional Dependencies and insert d3d11.lib.
[6-10] The Error function is a quick (and dirty) way to inform the user if something went wrong. It will be used in the DXManager to display a short description if a certain method fails. Feel free to replace the error handling with your own.
[15-17] You already know the constructor and Shutdown method from the Core class. I will not mention them any longer. Worth mentioning though are the arguments of the Initialize method. You need the screen width, height and the window handle to initialize DirectX in the previously generated window.
[18,19] StartFrameRender and EndFrameRender will be called in your game render method. The first prepares the screen for a new frame. And the second displays the screen after all the drawing has been completed. Next create the corresponding DXManager source file.
dxManager.cpp
I will address the code blocks individually, because the classes and methods tend to get longer from now on.
#include "dxManager.h"
First thing you have to do is adding the header file. This is also a step I will skip in further tutorials.
DXManager::DXManager() :
_device(nullptr),
_deviceContext(nullptr),
_swapChain(nullptr),
_renderTargetView(nullptr)
{
_clearColor[0] = 0.6f;
_clearColor[1] = 0.6f;
_clearColor[2] = 0.6f;
_clearColor[3] = 1.f;
}
Always set your pointer to null. The float array _clearColor is used in the StartFrameRender method to overwrite everything from the last frame. It represents the red, green, blue and alpha component respectively.
bool DXManager::Initialize(HWND hwnd, UINT screenWidth, UINT screenHeight)
{
DXGI_SWAP_CHAIN_DESC swapChainDesc;
ZeroMemory(&swapChainDesc , sizeof(swapChainDesc));
swapChainDesc.BufferCount = 1;
swapChainDesc.BufferDesc.Width = screenWidth;
swapChainDesc.BufferDesc.Height = screenHeight;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow = hwnd;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.Windowed = true;
swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
swapChainDesc.Flags = 0;
D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0;
if (FAILED(D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
0, &featureLevel , 1, D3D11_SDK_VERSION,
&swapChainDesc, &_swapChain, &_device , nullptr, &_deviceContext ))) {
return Error("failed to create device and swap chain");
}
ID3D11Texture2D *backBufferPtr;
if (FAILED(_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr ))) {
return Error("failed to receive back buffer pointer");
}
if (FAILED(_device->CreateRenderTargetView(backBufferPtr, nullptr, &_renderTargetView ))) {
return Error("failed to create render target view");
}
backBufferPtr->Release();
return true;
}
What looks like a lot is actually the minimal approach of creating everything that is needed for a DirectX application. More code is needed if you want to find out which resolutions are supported or what DirectX version is available. If you are interested in more in-depth tutorials let me know.
[17-33] The first thing here is filling out a struct which describes your swap chain. A lot of things in DirectX require such structs. The swap chain represents the front and back buffer to which you draw. The idea is to have a back buffer where all the drawing happens and after completion it is swapped (hence the name) with the front buffer. More information of particular variables can be found on MSDN.
[18] ZeroMemory is a convenient function that sets all struct members to zero. It is just a shorter version of a memset call with 0 as parameter.
[35] The featureLevel variable defines the DirectX version you want to use.
[36-40] D3D11CreateDeviceAndSwapChain then creates the swap chain, the device and the device context. The device and device context are your main interface with DirectX so you will use them a lot in further tutorials.
[42-49] With a working swap chain you can easily create the render target view by getting the back buffer an then calling the corresponding method. If one of the methods fail, an error message dialog pops up and false is returned. Finally the back buffer is released to prevent memory leaks.
void DXManager::Shutdown()
{
SAFE_RELEASE(_renderTargetView);
SAFE_RELEASE(_swapChain);
SAFE_RELEASE(_deviceContext);
SAFE_RELEASE(_device);
}
Within the Shutdown method all used pointers are released. SAFE_RELEASE is a new macro which will be used for all DirectX variables.
macros.h
#define SAFE_RELEASE(x) if(x!=nullptr) { x->Release(); x = nullptr; }
SAFE_RELEASE first checks if the pointer is valid and then it calls the Release method. This method is inherited from the IUnknown interface and all DirectX variables have it.
dXManager.cpp
void DXManager::StartFrameRender()
{
_deviceContext->ClearRenderTargetView(_renderTargetView, _clearColor);
}
On the start of each frame the render target is cleared with the specified clear color. In the next tutorial this method will also be used to clear the depth stencil view.
void DXManager::EndFrameRender()
{
_swapChain->Present(0, 0);
}
The last method in your class will be called on the end of the frame render procedure. It tells the swap chain to present the render target view to the user. The arguments can be used to configure v-sync. For now it just presents the new view as fast as possible. The last step is to use the new class in your framework. This can be done very easily with the following steps.
core.h
#include "dXManager.h" //#inlcude <Windows.h>
Replace the Windows.h include with your new dXManager header file. This works because the d3d11.h include inside the dXManager header already defines the Windows.h import.
DXManager *_DXManager;
void OnFrameRender(void);
Add a new private pointer for the DXManager and a new private method which will be used to encapsulate all drawing calls.
core.cpp
_DXManager(nullptr)
Add the initialization of the _DXManager to the Core::Core() constructor.
_DXManager = new DXManager();
if (!_DXManager->Initialize(_hwnd, kWidth, kHeight)) {
return false;
}
Create the _DXManager by first instantiating a new manager and then calling Initialize inside the Core::Initialize() method.
} else {
OnFrameRender();
}
Add OnFrameRender() to the else block inside the Core::Run() method.
SAFE_SHUTDOWN(_DXManager);
Insert a SAFE_SHUTDOWN call for the _DXManager into Core::Shutdown().
void Core::OnFrameRender()
{
_DXManager->StartFrameRender();
//TODO: render something
_DXManager->EndFrameRender();
}
And finally create the OnFrameRender method. There you call the start and end methods from the new DXManager class. In the future all drawing will be between those two methods.
That's it! If you build your project you will see the window from the previous tutorial with the change that now the background color is your specified clear color (and not black). For the sake of clarity and ease of comprehension, I skipped some DirectX content (namely the viewport and the depth stencil view). This will be covered in the next tutorial where DirectX is used to draw a triangle.
Download Executable and Source
Back to Window or continue with Triangle