Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Beginning Direct3D 10

0.00/5 (No votes)
10 Apr 2008 1  
Create your first application using Direct3D 10.

Introduction

Welcome to a new series of tutorials about Direct3D 10 programming. To be able to follow this tutorial, you need to be familiar with C++ programming. I presume that you also have a little knowledge about Win32 API programming and that you know how to create a simple window using this API (if you don’t, please check these Win32 API tutorials first).

Direct3D 10 came with a lot of promises; it has come to bring the visual level of games and real-time applications to the next level. A dramatic increase of quality and performance of recent graphics hardware has created new needs and necessities, and Direct3D 10 is going to answer to those, and install a flexible platform for further improvements and new features.

Basic Concepts

Front Buffer

A buffer which is bound to the actual pixels on the monitor screen.

Back Buffer

D3D uses back buffer as an output buffer, and then swaps the back buffer and the front buffer in each frame to represent the scene. This avoids flickering of the image on the monitor.

RenderTarget

A surface that is used by the Direct3D device to render onto.

DXGI

The first new component in DirectX 10’s design which we are going to talk about is the DirectX Graphics Infrastructure which is the primary device independent layer that provides access to the graphics hardware; DXGI handles some basic functionalities that were implemented previously in the D3D API.

dxgi.jpg

The application can talk to DXGI through the Direct3D 10 API, but sometimes you need direct access to DXGI, for example, to enumerate video adapters, present the scene, and etc.

Direct3D Device

Most of Direct3D’s core functionalities are accessible through the Direct3D Device Interface, so we need to create a D3D Device first. But, before that, we need to have a visible window.

// Create A Window Class Structure 
WNDCLASSEX wc; 
ZeroMemory(&wc, sizeof(wc));        
wc.cbSize = sizeof(wc);                
wc.hInstance = GetModuleHandle(NULL);        
wc.lpfnWndProc = WndProc;                    
wc.lpszClassName = "GPORG";                        
wc.style = CS_HREDRAW | CS_VREDRAW;            

// Register Window Class 
RegisterClassEx(&wc); 

// Create Window 
hWnd = CreateWindowEx(0, "GPORG", 
       "GameProgrammer.org Direct3D 10 Tutorial",
       WS_OVERLAPPEDWINDOW|WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, width,
       height, NULL,NULL,wc.hInstance,0);

This code registers a new window class and creates a new window using the class. (This code is fully explained in the Win32 SDK tutorials section). Now, we create a D3D10 Device by calling the D3D10CreateDeviceAndSwapChain() function.

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

if( FAILED( D3D10CreateDeviceAndSwapChain( NULL, 
    D3D10_DRIVER_TYPE_HARDWARE, NULL, 0, 
    D3D10_SDK_VERSION, &swapChainDesc, 
    &pSwapChain, &pDevice ) ) )
{
    Error("Failed to create device and swap chain.");
    return false;
}

As its name implies, this function creates a Direct3D device and a DXGI swap chain. The swap chain is responsible for getting the data from the D3D device and representing it on the screen. To create a swap chain, D3D needs to know a couple of details about it, which you need to provide using the DXGI_SWAP_CHAIN_DESC structure. Take a look at the DXGI_SWAP_CHAIN_DESC definition, and you can see that it contains two other structures: DXGI_MODE_DESC and DXGI_SAMPLE_DESC.

typedef struct DXGI_SWAP_CHAIN_DESC
{
    DXGI_MODE_DESC BufferDesc;
    DXGI_SAMPLE_DESC SampleDesc;
    DXGI_USAGE BufferUsage;
    UINT BufferCount;
    HWND OutputWindow;
    BOOL Windowed;
    DXGI_SWAP_EFFECT SwapEffect;
    UINT Flags;
}     DXGI_SWAP_CHAIN_DESC;

DXGI_MODE_DESC holds the description of the display mode:

typedef struct DXGI_MODE_DESC
{
    UINT Width;
    UINT Height;
    DXGI_RATIONAL RefreshRate;
    DXGI_FORMAT Format;
    DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
    DXGI_MODE_SCALING Scaling;
}     DXGI_MODE_DESC;

We’ve just used the Width, Height, and the Format parameters in here, which describe the width, height, and display format of our buffer, respectively. The DXGI_SAMPLE_DESC describes multi-sampling parameters for a resource.

typedef struct DXGI_SAMPLE_DESC
{
    UINT Count;
    UINT Quality;
}     DXGI_SAMPLE_DESC;

Count represents the number of samples per pixel, and the second parameter describes the image quality level. By setting Count to 1 and Quality to 0, we disable the multi-sampling feature.

Back to the DXGI_SWAP_CHAIN_DESC structure, we’ll cover the rest of the parameters: BufferCount is the number of buffers in the swap chain; we only want to create a front buffer in here, so we set it to 1. By setting BufferUsage to DXGI_USAGE_RENDER_TARGET_OUTPUT, we declare that the buffer will be used as an output render target. OutputWindow is a handle to the window that we want to use to represent the rendering output. The final parameter, Windowed, indicates whether the application starts in full screen mode or in windowed mode. (You can toggle the full screen mode with ALT+Enter in runtime).

Now, let’s have a closer look at the D3D10CreateDeviceAndSwapChain() function:

HRESULT D3D10CreateDeviceAndSwapChain(
    IDXGIAdapter *pAdapter,
    D3D10_DRIVER_TYPE DriverType,
    HMODULE Software,
    UINT Flags,
    UINT SDKVersion,
    DXGI_SWAP_CHAIN_DESC *pSwapChainDesc,
    IDXGISwapChain **ppSwapChain,    
    ID3D10Device **ppDevice);

The first parameter is a pointer to a DXGI adapter; by setting it to NULL, Direct3D will create a default adapter for us. The second parameter describes which type of display driver to use; most likely, we want to use the hardware driver, so we set it to D3D10_DRIVER_TYPE_HARDWARE. The next parameter should be null unless you want to use a custom software rasterizer, which is very unlikely. We don’t use any special flags, so we set Flags to 0. SDKVersion should always be set to D3D10_SDK_VERSION. For the next parameter, we should pass a pointer to the DXGI_SWAP_CHAIN_DESC structure that we just created. The two last parameters are the pointers to the swap chain and the D3D device that Direct3D will create for us.

Now that we’ve created a D3D device and a swap chain, we need to connect them so that the back buffer in the swap chain can be used as a render target by the D3D device (you may wonder why this isn’t done automatically by the D3D10CreateDeviceAndSwapChain() function; the answer is, it gives you a lot more flexibility in this way). To make this connection, we need to get the back buffer from the swap chain as a texture, create a render target using the texture, and pass it to D3D device as the active render target, and this code will just do that:

ID3D10Texture2D *pBackBuffer;

if( FAILED( pSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ), 
          (LPVOID*)&pBackBuffer ) ) )
{
    Error("Failed to create back buffer.");
    return false;
}

if(FAILED( pDevice->CreateRenderTargetView( pBackBuffer, NULL, 
                                               &pRenderTargetView )))
{
    Error("Failed to create render target view.");
      return false;
}

pBackBuffer->Release();

pDevice->OMSetRenderTargets( 1, &pRenderTargetView, NULL );

The initialization part is done here; now we want to use Direct3D to clear the screen:

pDevice->ClearRenderTargetView( pRenderTargetView, D3DXVECTOR4(0, 0, 0, 1) );

pSwapChain->Present( 0, 0 );

The pDevice->ClearRenderTargetView() function clears the entire render target with the supplied color, which is black here (r=0, g=0, b=0, a=1); after that, we’ve used the pSwapChain->Present() to present the scene on the screen. The swap chain will handle swapping the back buffer and the front buffer for us automatically. And we’re done, we’ve just created our first Direct3D 10 application here. It would be more fun if we had some shapes on the screen. You can refer to MSDN for more information about Direct3D 10 or wait for our nest tutorial here.

Code

At last, I provide the complete source code of the application. To make it work, you need to create an empty project in Visual Studio, set the character-set to “Use Multi-Byte Character Set” in project settings, and add a cpp file including this code:

#include <windows.h> 
#include <d3d10.h> 
#include <d3dx10.h>

#define Error(X) MessageBox(NULL, X, "Error", MB_OK)

class Application
{
public:

    Application();
    ~Application();

    bool CreateD3DWindow(int width, int height);

    bool Initialize(); 
    void Render(float deltaTime);
    void MainLoop();

private:

    static LRESULT CALLBACK WndProc( HWND hWnd , UINT message , 
                                     WPARAM wParam , LPARAM lParam);

    HWND                        hWnd; 
    IDXGISwapChain*                pSwapChain;
    ID3D10RenderTargetView*        pRenderTargetView;
    ID3D10Device*                pDevice; 
    int                            width, height;
};

#pragma comment (lib, "d3d10.lib")
#pragma comment (lib, "d3dx10.lib")

Application::Application()
{
}

Application::~Application()
{
}

LRESULT CALLBACK Application::WndProc( HWND hWnd , UINT message , 
                                       WPARAM wParam , LPARAM lParam)
{ 
    switch(message){ 
    case WM_CLOSE: 
    case WM_DESTROY: 
        PostQuitMessage(0); 
        break;; 
    } 
    return DefWindowProc(hWnd,message,wParam,lParam); 
} 

bool Application::CreateD3DWindow(int width, int height)
{ 
    this->width = width;
    this->height = height;
    
    // Create A Window Class Structure 
    WNDCLASSEX wc; 
    ZeroMemory(&wc, sizeof(wc));        
    wc.cbSize = sizeof(wc);                
    wc.hInstance = GetModuleHandle(NULL);        
    wc.lpfnWndProc = WndProc;                    
    wc.lpszClassName = "GPORG";                        
    wc.style = CS_HREDRAW | CS_VREDRAW;            
    
    // Register Window Class 
    RegisterClassEx(&wc); 
    
    // Create Window 
    hWnd = CreateWindowEx(0, "GPORG", 
           "GameProgrammer.org Direct3D 10 Tutorial", 
           WS_OVERLAPPEDWINDOW|WS_VISIBLE, CW_USEDEFAULT, 
           CW_USEDEFAULT, width, height, NULL,NULL,wc.hInstance,0);
    
    return true; 
} 

bool Application::Initialize()
{
    DXGI_SWAP_CHAIN_DESC swapChainDesc;
    ZeroMemory( &swapChainDesc, sizeof(swapChainDesc) );
    swapChainDesc.BufferCount = 1;
    swapChainDesc.BufferDesc.Width = width;
    swapChainDesc.BufferDesc.Height = height;
    swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapChainDesc.OutputWindow = hWnd;
    swapChainDesc.SampleDesc.Count = 1;
    swapChainDesc.SampleDesc.Quality = 0;
    swapChainDesc.Windowed = TRUE;

    if( FAILED( D3D10CreateDeviceAndSwapChain( NULL, 
        D3D10_DRIVER_TYPE_HARDWARE, NULL, 
        0, D3D10_SDK_VERSION, &swapChainDesc, 
        &pSwapChain, &pDevice ) ) )
    {
        if( FAILED( D3D10CreateDeviceAndSwapChain( NULL, 
            D3D10_DRIVER_TYPE_REFERENCE, NULL, 
            0, D3D10_SDK_VERSION, &swapChainDesc, 
            &pSwapChain, &pDevice ) ) )
        {
            Error("Failed to create device and swap chain.");
            return false;
        }
    }

    ID3D10Texture2D *pBackBuffer;
    if( FAILED( pSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ), 
              (LPVOID*)&pBackBuffer ) ) )
    {
        Error("Failed to create back buffer.");
        return false;
    }

    if(FAILED( pDevice->CreateRenderTargetView( pBackBuffer, 
               NULL, &pRenderTargetView )))
    {
        Error("Failed to create render target view.");
        return false;
    }

    pBackBuffer->Release();

    pDevice->OMSetRenderTargets( 1, &pRenderTargetView, NULL );

    D3D10_VIEWPORT vp = {0, 0, width, height, 0, 1};
    pDevice->RSSetViewports( 1, &vp );
    
    return true; 
} 

void Application::Render(float deltaTime)
{
    pDevice->ClearRenderTargetView( pRenderTargetView, 
                                       D3DXVECTOR4(0, 0, 0, 1) );

    pSwapChain->Present( 0, 0 );
} 

void Application::MainLoop()
{ 
    MSG msg; 
    long prevTime = GetTickCount(), curTime = GetTickCount();
    while(true)
    {
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if(msg.message==WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else{
            Render((curTime-prevTime)/1000.f);
            prevTime = curTime;
            curTime = GetTickCount();
        }
    }
} 

INT WINAPI WinMain( HINSTANCE , HINSTANCE , LPSTR , INT )
{ 
    Application app;
    
    if(app.CreateD3DWindow(640, 480))
    {
        if(app.Initialize())
        {
            app.MainLoop();
        }
    }
    
    return 0; 
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here