Friday 29 June 2012

Lets go…

So, the first chore I set myself was to find out how to create a window and a graphics device, and that's what I am going to cover now.
Before I start, I do have the long term goal of creating an engine for DX11, like I did with XNA that I can then use to set up and test my ideas on, this post won't contribute to that engine, in fact, it may be a while before my code become coherent enough to do this, so if you after an engine, please keep with me, we will get there in the end :)
I guess the first thing you need to do is go and download the latest DirectX SDK and get that installed on your system.
At the moment I am using the Visual Studio 12 RC for this, so I guess to avoid confusion, you should to.
Unlike XNA, and forgive me for constantly mentioning XNA, but it’s where I am coming from, you can’t just create a project with it all set up ready to go, well at least not that I know of. So I created an empty Win32 project.  So, in your nice new shiny IDE, open up a new project, and create a Win32 Project
image
In the Application Settings section after that screen, select the Empty Project check box and hit finish
image
You should now have an empty Win32 C++ project looking something like this
image
Now, in order for us to start using all the DirectX 11 goodness we need to set up the project so that it knows where DirectX 11 is, so right click on the project name and select Properties and you should get a screen that looks like this.
image
Select the VC++ Directories and then point the Include Directories and the Library Directories at the relevant folders in the DirectX 11 SDK you have installed, so this is how I set mine up
Select the drop down for the Include Directories and select <Edit…>
image
In the following popup, select the folder icon, then click the button with the three dots
image
Now navigate to the include folder in the DirectX SDK you have installed and hit Select Folder
image
Then hit OK, and now we do the same with the Library Directory
image
image
The difference with the lib is we can choose x64 or x86
image
And you should then have both the folders set like this in the project properties
image
And hit OK, and we are set up for some basic DirectX stuff, we will have to alter the projects properties again later on, but for now this will be enough to get us going, also I want to show you the sort of compile/linking errors you can get when the project is not set up correctly.
So our project is now ready, the first thing we need to do is create the initial entry point for application, a function called WinMain, and so we are going to add a file to the project called main.cpp
Right click on the Source Files folder in the project and select Add from the popup menu followed by New Item in the next
image
Now select the C++ File (.cpp) and give it the name main.cpp and click Add
image
We now have our first source file, first thing we will do is add a #include for the Windows.h header file.
#include <Windows.h>
We can now create our entry point
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdshow)
{
    int result = 0;

    return result;
}
Once you have this code in you should be able to build the project, even run it, only it wont do a lot as it will enter WinMain and leave it strait away. Congratulations you have written your first C++ program :P
Next we need to create a window, and to do this we are going to write our first class. I am going to call this class RandomchaosDX11Utility, over the next few posts we will add to this utility class and use it for a number of things. As I mentioned earlier, these first posts don’t really have a design in mind, at the moment, the classes are a means to an end.
So now we are going to create our first C++ class, right click on the project as we did before, choose add, but then this time select Class from the last pop up menu
image
Click Add
image
In the next window we will give the class it’s name, and hit finish
image
This will build us two files a .cpp and a .h file, the .h file holds the definition of our class, and to start with it will have a constructor and a destructor like this
#pragma once
class RandomchaosDX113DUtility
{
public:
    RandomchaosDX113DUtility(void);
    ~RandomchaosDX113DUtility(void);
};
The first statement in this file tells the compiler to only open this file once during compile time, this can help reduce compile time.
The next block of code is our class definition, as you can see you can specify an access type for a block of class members, in this case the constructor and destructor are declared here as public.
Before we start writing any code in here we need to add the Windows.h file again, this time write it under the #pragma once line.
#pragma once
#include <Windows.h>

class RandomchaosDX113DUtility
{
public:
    RandomchaosDX113DUtility(void);
    ~RandomchaosDX113DUtility(void);
};
We are now going to define the method we will use to create a window for our application, we are going to call this method InitWindow.
#pragma once
#include <Windows.h>

class RandomchaosDX113DUtility
{
public:
    RandomchaosDX113DUtility(void);
    ~RandomchaosDX113DUtility(void);

    // Method to initialize the window
    HWND InitWindow(LPCTSTR str_Title,int int_XPos, int int_YPos, int int_Width, int int_Height, WNDPROC WinProc, int colorBrush = COLOR_BTNFACE);
};
We now have a method defined to create a window, we can even build and run this code now, it wont do much, but it should still run at this point.
There are a few things going on here, so lest have a look at the parameters and the return type.
The return type is HWND, this will be a handle to our window, we then have a number of parameters, the first is a LPCTSTR, it’s a string type, this parameter is used to populate the windows title, the next four integers are used to place a size the window, we then have the WNDPROC parameter, and this will be a callback method used to handle messages sent to the window, we will use it in this post to know when the window is being closed.
Now we need to create the function body, open up the .cpp file we have for this class
#include "RandomchaosDX113DUtility.h"


RandomchaosDX113DUtility::RandomchaosDX113DUtility(void)
{
}


RandomchaosDX113DUtility::~RandomchaosDX113DUtility(void)
{
}
We can see we already have the header file for the class included here, that means that not only the class definition is visible here, but also the information from the includes in that header file, so in this case Windows.h, so we don’t have to include that here again.
We also have the constructor and the destructor stubs already written for us, at this point we wont be populating them yet, also note that the methods are prefixed with the class name.
Now we will create the stub for our new function.
HWND RandomchaosDX113DUtility::InitWindow(LPCTSTR str_Title,int int_XPos, int int_YPos, int int_Width, int int_Height, WNDPROC WinProc, int colorBrush)
{
    
}
We can now populate the stub so that it will look like this
HWND RandomchaosDX113DUtility::InitWindow(LPCTSTR str_Title,int int_XPos, int int_YPos, int int_Width, int int_Height, WNDPROC WinProc, int colorBrush)
{
    // Register class
    WNDCLASSEX wnd_Structure;

    wnd_Structure.cbSize = sizeof(WNDCLASSEX);
    wnd_Structure.style = CS_HREDRAW | CS_VREDRAW;
    wnd_Structure.lpfnWndProc = WinProc;
    wnd_Structure.cbClsExtra = 0;
    wnd_Structure.cbWndExtra = 0;
    wnd_Structure.hInstance = GetModuleHandle(NULL);
    wnd_Structure.hIcon = NULL;
    wnd_Structure.hCursor = NULL;
    wnd_Structure.hbrBackground = GetSysColorBrush(colorBrush);
    wnd_Structure.lpszMenuName = NULL;
    wnd_Structure.lpszClassName = L"WindowClassName";
    wnd_Structure.hIconSm = LoadIcon(NULL,IDI_APPLICATION);    

    if( !RegisterClassEx( &wnd_Structure ) )
        return HWND(-1);

    return CreateWindowEx(WS_EX_CONTROLPARENT, L"WindowClassName", str_Title, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE | WS_SIZEBOX | WS_MAXIMIZEBOX, int_XPos, int_YPos, int_Width, int_Height, NULL, NULL, GetModuleHandle(NULL), NULL);
}
It’s a lump of code, but it’s only really doing three things, defining the window we want, verifying that definition, then creating it. We can now build the project again and run it if you want, but again, nothing much will happen as we have not implemented the call to create the window.
We are now going to implement the window creation code, we will go back to main.cpp and at the top we are going to include our new header file, we are going to create an instance of our utility class and create our callback method as well as implement the code to create the window
#include <Windows.h>
#include "RandomchaosDX113DUtility.h"

RandomchaosDX113DUtility utils;

LRESULT CALLBACK ourWinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    switch(message)
    {
        case WM_CLOSE:
        {
            break;
        }
    }    
    return DefWindowProc(hWnd,message,wParam,lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdshow)
{
    int result = 0;

    HWND hWnd = utils.InitWindow(L"RCDXTutorial1",0,0,800,420,ourWinProc);

    return result;
}
We now have all our code set up to run, but executing the application will not make much difference, the window will pop up, for only for the shortest of times as the WinMain function closes pretty quick, so we need a loop, and in the most simple of ways, this is going to be our basic game loop.
So, we are going to use a while loop, and while it’s condition is true our game will continue to run. We will control the condition with a global integer called int_AppRunning and define it at the top of our main.cpp file, just under the instantiation of our utility class.
int int_AppRunning = 1;
We can then modify the WinMain to look like this
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdshow)
{
    int result = 0;

    HWND hWnd = utils.InitWindow(L"RCDXTutorial1",0,0,800,420,ourWinProc);

    if(hWnd != HWND(-1))
    {
        while(int_AppRunning)
        {
            
        }
    
        DestroyWindow(hWnd);     
    }

    return result;
}
Running the application now will show the window, but when you go to close it you’ll find your input is being ignored, this is because we have not implemented a message pump for the application.
We will define a MSG object to store incoming messages, we can then implement the pump in the game loop.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdshow)
{
    int result = 0;
    MSG msg_Message;

    HWND hWnd = utils.InitWindow(L"RCDXTutorial1",0,0,800,420,ourWinProc);

    if(hWnd != HWND(-1))
    {
        while(int_AppRunning)
        {
            if(PeekMessage(&msg_Message,hWnd,0,0,PM_REMOVE))
                DispatchMessage(&msg_Message);
        }
    
        DestroyWindow(hWnd);     
    }

    return result;
}
Running the application now and our application runs and reacts with our mouse input, we can move it about and we can close it, BUT, our IDE seems to think the application is still running, and that’s because it is, it’s still in our game loop.
In our WinProc callback method where we have a switch looking at the incoming messages, we have a case for WM_CLOSE, this is the message passed to our application when the window is being closed, so in here all we have to do is set our global to 0
LRESULT CALLBACK ourWinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    switch(message)
    {
        case WM_CLOSE:
        {
            int_AppRunning = 0;
            break;
        }
    }    
    return DefWindowProc(hWnd,message,wParam,lParam);
}
Running the application now will result in the behavior we expect.
image
Now, this next bit, I have totally taken from the DirectX 11 SDK code Tutorials, so the information from here on in is my interpretation on this code.
We are going to have a fair bit going on here, first thing we need to do is add some more includes to our source for the DirectX elements we are going to be using.
In the header file we have for the utility class we are going to add the following
#include <D3D11.h>
#include <D3DX11.h>
Now that we have added these there are a few modifications we need to make to the project configuration, we need to add some extra libraries, namely d3d11.lib. As before, open up the projects properties, this time open up the Linker option, and in there the Input option, in there we need to alter the Additional Dependencies and add the lib to the list.
image
Now that’s set up the first thing I want to do is include two new headers to our utility class.
#include <D3D11.h>
#include <D3DX11.h>
I also want to create a HWND member to our utility class so we can then use that handle as and when we need to, we then need to add a number of DirectX objects and we are going to make them protected.
protected:
    D3D_DRIVER_TYPE driverType;
    IDXGISwapChain* pSwapChain;
    ID3D11Device*  pd3dDevice;
      D3D_FEATURE_LEVELfeatureLevel;
    ID3D11RenderTargetView* pRenderTargetView;
    ID3D11DepthStencilView* pDepthStencilView;
    ID3D11Texture2D* pDepthStencil;
    
    HWND hWnd;
We are also going to create a public device context, my take on it is that it’s what in XNA would be the GraphicsDevice.
// GraphicsDevice
ID3D11DeviceContext* pImmediateContext;
Now create a public method that will construct our device
// Creates our graphics device
HRESULT InitDevice();
And one to make the draw call
// Draw call
void Draw();
First thing we need to do is to ensure that our InitWindow method now populates the protected HWND member variable, so we will change the method to do that in our utility .cpp file
HWND RandomchaosDX113DUtility::InitWindow(LPCTSTR str_Title,int int_XPos, int int_YPos, int int_Width, int int_Height, WNDPROC WinProc, int colorBrush)
{
    // Register class
    WNDCLASSEX wnd_Structure;

    wnd_Structure.cbSize = sizeof(WNDCLASSEX);
    wnd_Structure.style = CS_HREDRAW | CS_VREDRAW;
    wnd_Structure.lpfnWndProc = WinProc;
    wnd_Structure.cbClsExtra = 0;
    wnd_Structure.cbWndExtra = 0;
    wnd_Structure.hInstance = GetModuleHandle(NULL);
    wnd_Structure.hIcon = NULL;
    wnd_Structure.hCursor = NULL;
    wnd_Structure.hbrBackground = GetSysColorBrush(colorBrush);
    wnd_Structure.lpszMenuName = NULL;
    wnd_Structure.lpszClassName = L"WindowClassName";
    wnd_Structure.hIconSm = LoadIcon(NULL,IDI_APPLICATION);    

    if( !RegisterClassEx( &wnd_Structure ) )
        return HWND(-1);

    hWnd = CreateWindowEx(WS_EX_CONTROLPARENT, L"WindowClassName", str_Title, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE | WS_SIZEBOX | WS_MAXIMIZEBOX, int_XPos, int_YPos, int_Width, int_Height, NULL, NULL, GetModuleHandle(NULL), NULL);

    return hWnd;
}
We now need to create the method bodies of these methods in the .cpp file, first the InitDevice method
HRESULT RandomchaosDX113DUtility::InitDevice()
{
    HRESULT hr = S_OK;

    RECT rc;
    GetClientRect( hWnd, &rc );
    UINT width = rc.right - rc.left;
    UINT height = rc.bottom - rc.top;

    UINT createDeviceFlags = 0;
#ifdef _DEBUG
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    D3D_DRIVER_TYPE driverTypes[] =
    {
        D3D_DRIVER_TYPE_HARDWARE,
        D3D_DRIVER_TYPE_WARP,
        D3D_DRIVER_TYPE_REFERENCE,
    };
    UINT numDriverTypes = ARRAYSIZE( driverTypes );

    D3D_FEATURE_LEVEL featureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
    };
    UINT numFeatureLevels = ARRAYSIZE( featureLevels );

    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory( &sd, sizeof( sd ) );
    sd.BufferCount = 1;
    sd.BufferDesc.Width = width;
    sd.BufferDesc.Height = height;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = hWnd;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.Windowed = TRUE;

    for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )
    {
        driverType = driverTypes[driverTypeIndex];
        hr = D3D11CreateDeviceAndSwapChain( NULL, driverType, NULL, createDeviceFlags, featureLevels, numFeatureLevels,
                                            D3D11_SDK_VERSION, &sd, &pSwapChain, &pd3dDevice, &featureLevel, &pImmediateContext );
        if( SUCCEEDED( hr ) )
            break;
    }
    if( FAILED( hr ) )
        return hr;

    // Create a render target view
    ID3D11Texture2D* pBackBuffer = NULL;
    hr = pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( LPVOID* )&pBackBuffer );
    if( FAILED( hr ) )
        return hr;

    hr = pd3dDevice->CreateRenderTargetView( pBackBuffer, NULL, &pRenderTargetView );
    pBackBuffer->Release();

    if( FAILED( hr ) )
        return hr;

    // Create depth stencil texture
    D3D11_TEXTURE2D_DESC descDepth;
    ZeroMemory( &descDepth, sizeof(descDepth) );
    descDepth.Width = width;
    descDepth.Height = height;
    descDepth.MipLevels = 1;
    descDepth.ArraySize = 1;
    descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    descDepth.SampleDesc.Count = 1;
    descDepth.SampleDesc.Quality = 0;
    descDepth.Usage = D3D11_USAGE_DEFAULT;
    descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
    descDepth.CPUAccessFlags = 0;
    descDepth.MiscFlags = 0;
    hr = pd3dDevice->CreateTexture2D( &descDepth, NULL, &pDepthStencil );
    if( FAILED( hr ) )
        return hr;

    // Create the depth stencil view
    D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
    ZeroMemory( &descDSV, sizeof(descDSV) );
    descDSV.Format = descDepth.Format;
    descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
    descDSV.Texture2D.MipSlice = 0;
    hr = pd3dDevice->CreateDepthStencilView( pDepthStencil, &descDSV, &pDepthStencilView );
    if( FAILED( hr ) )
        return hr;

    pImmediateContext->OMSetRenderTargets( 1, &pRenderTargetView, pDepthStencilView );

    return S_OK;
}
Now as I have said, this it lifted from the SDK tutorial code, so this is what I see it doing.
The method starts off getting the windows size, sets the device creation flags, it then sets up a few arrays to help detect what capabilities the card can do. It then sets up a swap chain definition to describe the device, it then loops through the types until it finds a driver type the card supports, if it doesn’t then it returns HRESULT which I imagine would be –1. It then sets up the back buffer, a render target and a depth stencil.
We then set up the Draw call, which in this post is just us clearing the “GraphicsDevice”
void RandomchaosDX113DUtility::Draw()
{
    //
    // Clear the back buffer
    //
    float ClearColor[4] = { 0.392156862745098f, 0.5843137254901961f, 0.9294117647058824f, 1.0f }; // red, green, blue, alpha
    pImmediateContext->ClearRenderTargetView( pRenderTargetView, ClearColor );

    //
    // Clear the depth buffer to 1.0 (max depth)
    //
    pImmediateContext->ClearDepthStencilView( pDepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0 );

    //
    // Present our back buffer to our front buffer
    //
    pSwapChain->Present( 0, 0 );
}
So in here we clear the render target and the depth stencil.
We now need to implement these calls, so we now open up our main.cpp file, after we initialize the window, we will initialize the device, then in the game loop we will make the draw call.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdshow)
{
    int result = 0;
    MSG msg_Message;

    HWND hWnd = utils.InitWindow(L"RCDXTutorial1",0,0,800,420,ourWinProc);
    
    if(hWnd != HWND(-1))
    {
        utils.InitDevice();

        while(int_AppRunning)
        {
            if(PeekMessage(&msg_Message,hWnd,0,0,PM_REMOVE))
                DispatchMessage(&msg_Message);

            utils.Draw();
        }
    
        DestroyWindow(hWnd);     
    }

    return result;
}
And if we run the application we get this familiar looking screen :)
image
So, what did we do here?
  • We set up the project to use DirectX
  • We created our first C++ application
  • We created our first windowed application
  • We created a graphics device
  • We cleared the graphics device
As I mentioned before, I am pretty rusty with C++, I am new to DirectX 11, so if you have any comments on this post, then please post them, not only will it help me improve my knowledge and post, but will help others to.
Thanks for reading and I hope it will be of some use to you.
You can find the source code for this post here