BannerLeft BannerLeft

 

Setting up Direct3D

This page is a set of notes describing how to initialise Direct3D and enter a render loop. It assumes you already have a program that can display a window. If not take a look at the section about Windows API programming.

Note that there is a demo application with source code showing off a working Direct3D application here: Cube Demo

Project Settings

Since you want to use Direct3D code you must link to the Direct3D library and include its header file. In Viz go to the menu project - properties and click linker then input. There will be a list of library files your program is already linking to under dependencies. Add to the beginning of this list the Direct3D library: d3d9.lib.

You also need to include the function definitions and data structures used by the library so add an include for d3d9.h at the top of your main.cpp source file.

Demo

You can download the source code for a very simple demo that draws a triangle using Direct3D here: D3dTriangle.zip. While very simple it should help to show how Direct3D can be initialised and used to draw a primitive.

COM Objects

There are two main COM objects you need to create in order to initialise Direct3D. You need to obtain a pointer to the main Direct3D object and use it to obtain a device pointer. It is the device pointer through which you do all your graphic operations. It is common to create a new function in which you write this set-up code. You should call it after creating your window and just before you display it. Make sure you always check for error conditions and exit your program if one occurs. See the notes on tracking DirectX bugs.

Creating the main Direct3D object

Note: The Direct3D object and device pointers are used throughout your code so it is common to make them available globally. As usual when declaring a pointer assign it initially to NULL e.g.

LPDIRECT3D9 d3dObject=NULL;
LPDIRECT3DDEVICE9 d3dDevice=NULL;

The above creates a variable named d3dObject with the type LPDIRECT3D9. This is a typedef (synonym) for IDirect3D9* i.e. it is a pointer to the IDirect3D9 class. The 'I' in the name is used to indicate it provides interfaces (functions).  The above lines could equally be written like this:

IDirect3D9* d3dObject=NULL;
IDirect3DDevice9* d3dDevice=NULL;

To create an instance of the object you call the API function as shown below:

d3dObject=Direct3DCreate9(D3D_SDK_VERSION);

You should check to see if the pointer is valid (not NULL) before proceeding. If it is NULL you should exit your program with an error as Direct3D could not be initialised. The version define is passed in simply so that a check is made that you are using the correct headers with the correct libraries.

Creating the Direct3D device

Once you have obtained a pointer to the Direct3D object you can use it to obtain a device pointer. The device pointer is used throughout your graphics code:

IDirect3DDevice9* d3dDevice=NULL;

The above creates a variable named d3dDevice with the type IDirect3DDevice9*.  Note sometimes you see this written as LPDIRECT3DDEVICE9 which is just a synonym for IDirect3DDevice9*  a pointer to a IDirect3DDevice9 class. To create an instance of the device we use our main Direct3D object pointer which provides the following function (the definition is shown):

HRESULT IDirect3D9::CreateDevice(UINT adapter, D3DDEVTYPE deviceType, HWND focusWindow,DWORD behaviourFlags, D3DPRESENT_PARAMETERS *presentationParameters, IDirect3DDevice9** device);

Note: nearly every DirectX function returns a code indicating success or failure. The code is of type HRESULT and the API provides you with two macros that you can use to determine the success of the call. The two macros are SUCCEEDED and FAILED. Be careful not to test against true and false, you must use the macros. If you wish to find out more information about a return error look at this answer here: Dx Errors

CreateDevice parameters

The CreateDevice function takes a number of parameters that allow you to specify the kind of device you require. The parameters are:

  • adapter - the index of the adapter you wish to use. You may have more than one video card in your PC. Use D3DADAPTER_DEFAULT here to use the primary adapter.
  • deviceType - a member of the D3DDEVTYPE enum. You can choose to use a software reference driver, a hardware driver or a custom written software driver. Normally you will use the hardware one: D3DDEVTYPE_HAL.
  • focusWindow - the window handle the device is to be associated with
  • behaviourFlags - a combination of flags that controls device creation, important ones are:
    • D3DCREATE_HARDWARE_VERTEXPROCESSING - this means that the video card should handle vertex calculations. This is the fastest method but requires support from the hardware.
    • D3DCREATE_SOFTWARE_VERTEXPROCESSING - this means that Direct3D handles vertex processing itself so no video card acceleration is used. This is obviously slower than using hardware.
    • D3DCREATE_MIXED_VERTEXPROCESSING - this means that both the hardware and software are used for vertex processing. This can be useful if your video card does not support the full set of accelerated processing required.
  • presentationParameters - During your render loop you render your scene and then present it to the display. The members of this structure determine how it is presented.  The presentation parameters structure has a number of optional settings, you can set most of these to the default value of 0 if you are running your game in a window. The important ones are shown below:
    • Windowed - TRUE if you want your game to run in a window or FALSE if you want it to run full screen.
    • SwapEffect - D3DSWAPEFFECT_DISCARD allows the video card to control the swapping of the back buffer and the render target.
    • BackBufferFormat - D3DFMT_UNKNOWN is used when running in a window and means Direct3D will use the current display mode format (whatever bit depth, resolution you currently have your desktop set to).
    • BackBufferCount - how many back buffers to use, normally this will be 1.
    • PresentationInterval - how often the back buffer is copied to the front buffer. D3DPRESENT_INTERVAL_ONE (the default) will cause this to synchronise with the monitor refresh - hence avoiding tearing effects. D3DPRESENT_INTERVAL_IMMEDIATE causes the copy to occur as soon as the card has finished rendering.

You must create a variable to hold the above structure (D3DPRESENT_PARAMETERS). It is good practice to initialise it to 0 to start with as that is the default for most parameters. You can use the function ZeroMemory to set all values to 0. This function takes a pointer to the area of memory as its first parameter then the number of bytes you want to set to zero, which is normally the size of the structure so you can use sizeof to get that value.

D3DPRESENT_PARAMETERS presParams;
ZeroMemory(&presParams,sizeof(presParams));

For running in a window an example is:

presParams.Windowed=TRUE;
presParams.SwapEffect=D3DSWAPEFFECT_DISCARD;
presParams.BackBufferFormat=D3DFMT_UNKNOWN;
presParams.PresentationInterval=D3DPRESENT_INTERVAL_ONE;

Then an example of calling the CreateDevice function is:

HRESULT hr=d3dObject->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &presParams, &d3dDevice);

The final parameter to the CreateDevice call is the address of a device pointer (IDirect3DDevice9*). You declare the pointer and pass the address of it to the CreateDevice call. Direct3D then creates the device object and points your pointer at it.

Note: if you find device creation is failing try changing from hardware vertex processing to software. It may be your video card cannot do hardware vertex processing at all or at least not in a window with your current desktop settings.

Important: the device pointer is just like an instance of a class you may have written, there is nothing mystical about it. It provides a set of functions that you can use to control Direct3D. DirectX uses this pattern over and over again so if you understand how this works the rest should be easy :)

Render Loop

Once you have obtained your device pointer you can use it to display graphics. The render loop will always be the same:

  1. Clear the current contents
  2. Tell the device you are beginning to render
  3. Render your scene
  4. Tell the device you have finished rendering
  5. Present the scene (display it).

The device provides the functions you will use in your render loop.

Clear

The definition of the clear function is:

HRESULT IDirect3DDevice9::Clear(DWORD count, const D3DRECT *pRects, DWORD flags, D3DCOLOR color, float z, DWORD stencil)

The back buffer can be cleared to a set colour. This function is multipurpose and also allows you to clear the stencil and z buffer:  The parameters are:

  • Count - this is used if you wish to clear just some rectangular areas of the back buffer. Set to 0 if you wish to use the whole back buffer.
  • pRects - if you wish to clear just some rectangles you provide the array here or NULL if you want to use the whole back buffer.
  • flags - used to indicate which surface should be cleared. You can combine these flags to clear more than one at the same time. Options are D3DCLEAR_STENCIL, D3DCLEAR_TARGET and D3DCLEAR_ZBUFFER. To clear the screen you should use D3DCLEAR_TARGET.
  • color - this is the colour that you want the target to be cleared to. It is a 32 bit ARGB colour. This is a single colour value, Direct3D provides some macros to convert rgb values into this D3DCOLOR value. One is D3DCOLOR_XRGB(r,g,b) which converts rgb values ranging from 0 to 255 and ignores an alpha value. E.g. for bright red: D3DCOLOR_XRGB(255,0,0)
    z - the value that the z buffer should be cleared to, normally 1.0 (furthest away)
  • stencil - the value that the stencil buffer should be cleared to, normally 0

Example - to clear the back buffer to green :

d3dDevice->Clear(0,NULL,D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,255,0),1.0f,0)

Begin

HRESULT IDirect3DDevice9::BeginScene()

Note: remember these functions are provided by the device so to call them you need to use the device pointer e.g.
gD3dDevice->BeginScene();

End

HRESULT IDirect3DDevice9::EndScene()

Present

Once you have rendered your scene you need to present it. The definition of the function is shown below:

HRESULT IDirect3DDevice9::Present(CONST RECT *pSourceRect, CONST RECT *pDestRect, HWND hDestWindowOverride, CONST RGNDATA *pDirtyRegion );

You can pass in regions of the screen to render, however the default is to use the whole screen so the call is simply:

d3dDevice->Present( NULL, NULL, NULL, NULL );

Debugging it

Once all these calls are in place you should be able to initialise Direct3D and clear the screen to a colour. Note that to get Direct3D debug messages in the output pane you need to have Direct3D set to debug mode (via the DirectX control pane; - now under the All Programs directory). To get the most amount of debug messages you also need to define D3D_DEBUG_INFO. Place this is your project settings under preprocessor definitions for your debug build only.

Becoming a badly behaved Windows Application

Being a well behaved Windows application requires waiting for Windows to send our application messages and only to do work when we receive messages. This is required so other applications can get processor time and Windows runs smoothly. However for games this is not so good. We want our game to run as fast as possible and not have to wait for Windows. So to become badly behaved we need to change the message loop so we do our work whenever there are no Windows messages waiting for us. An example of the changed message loop code is show below. Once you have made this change you will see that when you ALT-TAB away from your application everything runs really slow, this is because we are 'hogging' the operating system time - we are being badly behaved!

Your new message loop in WinMain will look like this:

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.
}

Advanced Note: when you do this you really do hog all the computer resources! In order to give a bit of time to other applications you can do a Sleep(0) each update (allows other threads to carry out essential task). Also when your game is minimised or not in focus (if running in a window) it is a good idea to stop rendering and give plenty of time to other applications. For how to do this see the answer on the Direct3D FAQ page: everything runs slowly...



© 2004-2014 Keith Ditchburn