BannerLeft BannerRightToymaker

   

Direct 3D 10 Setup

This page takes you through the steps required to get a Direct3D 10 application initialised and processing a simple render loop.

The demo source files to accompany these notes can be downloaded here: D3D10Create.rar (coming soon)

Creating a Windows application / Direct3D 10 library

The very first step in setting up Direct3D 10 is to create a base Windows application that shows a window on the desktop. This has not changed for Direct3D 10 running on Vista from how it was with Direct3D 9 and Windows XP. Therefore please see the existing notes on setting up a Windows application.

You will also need to include the Direct3D 10 headers and link to the correct Direct3D 10 library files:

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

The first header is for the core Direct3D 10 functionality while the second is for the extended set of helper functions called D3DX.

You need to link with the corresponding library files: d3d10.lib and d3dx10d.lib. Place these in your project settings under Linker / Input / Additional Dependencies. You should link to d3dx10d.lib in debug build and d3dx10.lib in release build.

Note: make sure that the DirectX paths are set correctly in Visual Studio. These should be set globally via Tools / Options / Projects and Solutions / VC++ Project Settings. You will need a path for Include Files e.g. on my C:\Program Files\Microsoft DirectX SDK ([DATE])\Include. You also need one for the library files. For 32 bit development this is: C:\Program Files\Microsoft DirectX SDK ([DATE])\Lib\x86.

Creating the Direct3D 10 Device

Unlike Direct3D 9 (from now on I will write D3D for Direct3D to save typing) there is no need to create a D3D object from which to create the device, instead there are two available functions D3D10CreateDevice and D3D10CreateDeviceAndSwapChain. Both functions create a D3D 10 device object which provides the interfaces (functions) to enable drawing. The second call however also allows the creation of the swap chain at the same time and is therefore the one described here.

A swap chain is a set of buffers that can be drawn to and displayed in sequence. Normally there are two buffers, a screen buffer (front buffer) and a second buffer (called a back buffer). All drawing is carried out on this second buffer and when drawing is finished that buffer is then swapped with the one being displayed. The process can then start again. This method prevents tearing artefacts (e.g. the top of the screen is being drawn while the bottom is still showing the previous frame - the user being able to see the drawing process) that may be seen if  we were to draw straight to the screen. We therefore only ever write to the back buffer and never the front buffer.

The parameters required by the D3D10CreateDeviceAndSwapChain function are shown below

D3D10CreateDeviceAndSwapChain( IDXGIAdapter *adapter, D3D10_DRIVER_TYPE driverType, HMODULE software, UINT layerFlags, UINT SDKVersion, DXGI_SWAP_CHAIN_DESC *swapChainDesc, IDXGISwapChain **swapChain, ID3D10Device **device)

  • adapter - this describes the display device you wish to use, normally this is the default video card and so this can be set to 0
  • driverType - you can choose to use a hardware driver by providing the flag value D3D10_DRIVER_TYPE_HARDWARE or if the target machine cannot support this you can use D3D10_DRIVER_TYPE_WARP to select a software driver
  • software - for advanced rendering using a software rasterizer dll. Set to NULL
  • Flags - these flags allow you to change the API interfaces in some way. A value of 0 is sufficient but for debug purposes it is useful to specify D3D10_CREATE_DEVICE_DEBUG
  • SDKVersion - this is a sanity check really to make sure you are using the correct libraries. In here you simply put the define D3D_SDK_VERSION.
  • swapChainDesc - this is the address of a structure that describes the swap chain. The structure is called DXGI_SWAP_CHAIN_DESC which has a number of members describing how you want the swap chain creating. It includes things like the display mode, how many buffers you want, whether to run in a window etc. I will describe this structure more fully in a moment.
  • swapChain - here you provide the address of a pointer to a  IDXGISwapChain interface class. If the function is successful this pointer will point to an instance of a swap chain that you can use to control the drawing and displaying process.
  • device - this should be the address of a pointer to a ID3D10Device interface class. If the function is successful this pointer will point to an instance of a device through which your rendering can be carried out.

So what does all this give us? Well the purpose of the function is to create a device object to represent the graphic card functionality along with a swap chain object to control the rendering process. If successful this function creates the objects and initialises them with our requirements.

Before calling the function we need to create some of the parameters in advance. In particular we need to create and fill in the members of the DXGI_SWAP_CHAIN_DESC structure to tell D3D how we want our swap chain to perform.

Note: DXGI stands for DirectX Graphics Infrastructure and manages low level tasks like displaying screen buffers on the hardware. It is new in Direct3D 10. In Direct3D 9 this was all part of the 3D runtime.

DXGI_SWAP_CHAIN_DESC

  • DXGI_MODE_DESC BufferDesc - this is another structure variable that you use to specify the display mode of the back buffer. This structure contains the following:
    • UINT Width, Height - dimensions of the screen. You will want to provide the width and height of the display area of your window here. Note that this will not be the size you created the window but the area within the window that can be drawn to. This is normally smaller. You can find the size via the Windows API call GetClientRect.
    • DXGI_RATIONAL RefreshRate - this is a rational number type representing the required refresh rate. The rational type has a numerator and a denominator. To set a refresh rate of 70 you could set the numerator to 70 and the denominator to 1.
    • DXGI_FORMAT Format - this is where you can specify the display format you require i.e. the make up of the colour components. A common value would be  DXGI_FORMAT_R8G8B8A8_UNORM which means you want a 32 bit display format with 8 bits for each of the channels (red, green, blue and alpha).
    • DXGI_MODE_SCANLINE_ORDER ScanlineOrdering -  0 (unspecified) is fine here.
    • DXGI_MODE_SCALING Scaling -  this is usually set to 0 (unspecified).
  • DXGI_SAMPLE_DESC SampleDesc - this structure allows you to specify multi-sampling values. If you do not want to use multi sampling just set Count to 1 and Quality to 0.
  • DXGI_USAGE BufferUsage - how you want to use the back buffer. For your swap chain you just want the usage to be as a render target (where you are drawing to) by setting it to DXGI_USAGE_RENDER_TARGET_OUTPUT .
  • UINT BufferCount - the number of buffers you want in the swap chain. Normally set to 1.
  • HWND OutputWindow - the Windows API handle to the window to use
  • BOOL Windowed - true to run in a window, false to run full screen
  • DXGI_SWAP_EFFECT SwapEffect - a flag allowing some control over how the swap chain is displayed. Normally just use 0 (DXGI_SWAP_EFFECT_DISCARD) which means use the most efficient method available.
  • UINT Flags - flags that change how the swap chain will be created. A value of 0 is fine here.

Before calling the function we need to fill in the swap chain structure and declare pointers that will point to the D3D10 device object and also the swap chain object. We pass the address of these pointers to the D3D function and the function, if successful, will create instances of the objects and point our pointers at them.

Assuming you have already created a window and have a handle to it (HWND hWnd) the following code creates a Direct3D 10 device with the most common settings.

// Determine the size of the drawable area of the window
RECT drawArea;
GetClientRect( hWnd, &drawArea );
UINT width = drawArea.right - drawArea.left;
UINT height = drawArea.bottom - drawArea.top;

// Create and fill in the values for our swap chain requirements. Note that we clear the structure to 0 first so that all values that use a default of 0 do not need specifying
DXGI_SWAP_CHAIN_DESC swapChainDesc;
memset(&swapChainDesc, 0, sizeof(swapChainDesc));
swapChainDesc.BufferCount = 1;
swapChainDesc.BufferDesc.Width = width;
swapChainDesc.BufferDesc.Height = height;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
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;

// Initialise pointers that the create function will take and point to the successfully initialised objects
// Note that you will want to make these globally available
ID3D10Device* device = 0;
IDXGISwapChain* swapChain = 0;

We now call our create device function like this:

HRESULT hr = D3D10CreateDeviceAndSwapChain(0, D3D10_DRIVER_TYPE_HARDWARE, 0, D3D10_CREATE_DEVICE_DEBUG, D3D10_SDK_VERSION, &swapChainDesc, &swapChain, &device)

Like most DirectX functions this call returns a success code. You should always test these codes for success. The easiest way is to use the FAILED macro e.g.

if (FAILED(hr))
    return false;

Note: 0 is the same as using NULL. Nowadays 0 is the preferred form.

Setting up the view

D3D 10 differs from D3D 9 in that it introduces the concept of a 'view'. This is a way of grouping resources together for a stage of the pipeline to use. It is quite a complex concept however for our simple application we just want to create a view to represent the resources of our render target (the back buffer). This can be achieved by calling the device function CreateRenderTargetView

HRESULT CreateRenderTargetView( ID3D10Resource *resource, const D3D10_RENDER_TARGET_VIEW_DESC desc, ID3D10RenderTargetView **RTView )

  • resource - a pointer to our render target. This can be obtained from our swap chain by calling GetBuffer (see below).
  • desc - set to 0
  • RTView - if the function is successful this pointer is set to point to an instance of a ID3D10RenderTargetView interface object

Once you have created the render target view you set it using the device function OMSetRenderTargets

// Create a render target view
ID3D10RenderTargetView* gRenderTargetView = 0;
ID3D10Texture2D *buffer;

gSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ), (LPVOID*)&buffer );
gDevice->CreateRenderTargetView( buffer, NULL, &gRenderTargetView );
buffer->Release();

gDevice->OMSetRenderTargets( 1, &gRenderTargetView, NULL );

Note: for the sake of clarity I have missed out HRESULT checks in this code however you should get into the habit of always checking the HRESULT return codes as it will save you a lot of pain later!

Direct3D 10 Render Loop

Now that we have created a D3D 10 device to represent our graphics card we can go ahead and do all our rendering. When rendering we will want to ignore the Windows API messages and instead render as fast as we can. This can be achieved by changing the standard message loop to:

MSG msg;
ZeroMemory( &msg, sizeof(msg) );

while( msg.message!=WM_QUIT )
{
   if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
   {
      TranslateMessage( &msg );
      DispatchMessage( &msg );
   }
   else
      .. update our game, render etc.
}

Note: this causes us to hog Windows' resources from other applications

When rendering we need to perform the following

1. Clear the render target view (the back buffer) to a colour. This is achieved by calling the device function ClearRenderTargetView with our view object and a 4 dimensional float array representing the desired colour. e.g. to clear to a horrid pink colour:

float ClearColor[4] = { 1.0f, 0.0f, 0.4f, 1.0f }; // red,green,blue,alpha
gDevice->ClearRenderTargetView( gRenderTargetView, ClearColor );

2. Do all our rendering. This is where we render all our geometry etc. The demo does nothing here.

3.  Present the information rendered to the back buffer to the front buffer (the screen). This is achieved via the swap chain's present function like so:

gSwapChain->Present( 0, 0 );

Take a look at the provided demo to see the above code working to set up a Direct3D 10 application and clear the screen to pink.

Closing down Direct3D 10

DirectX objects all implement a release interface to clean up their memory (this comes from the COM object model used). In addition the device object has a function called ClearState that sets all the device values back to what they were to start with. You should call this first before releasing objects. It is usually a good idea to release objects in the reverse order of creation and to release the device last. Note that there is no need to delete the objects themselves.

gDevice->ClearState();
gRenderTargetView->Release()
gSwapChain->Release();
gDevice->Release();

Summary

As you can see there is quite a lot of code required just to get a Direct3D 10 application up and running to do the basics but it is important to understand what is going on for future Direct3D 10 programming. This stage also creates the objects needed for later rendering. In particular the device object is used for all future drawing and interfacing with the video card driver.



© 2004-2009 Keith Ditchburn  (A lecturer on the Games Programming Course at Teesside University)