Creating a DirectX device
Last updated: 17-06-2006
Project: Game Programming with DirectX 9
Prerequisites: Completion of all the preceding tutorials in this project is required.
Downloads: DirectXCreateDevice.zip
In this tutorial we are going to create a DirectX device.
The source code that comes with this tutorial (see Downloads section above) extends the previous solution.
To see all the tutorials that belong to this project click on the project name above.
In the DirectX environment your hardware (graphical card) is represented by a software entity called device. To keep it simple you use the DirectX device when you want to take advantage of the features provided by your graphical card. The most important feature is drawing your objects on the screen. Since graphical cards are different, your directX device may not support all of the features. In some systems several graphical cards are available. To interact with each one of them you have to create a separate DirectX device.
In this project we will interface with a primary adaptor, which means that if you have multiple graphical cards installed in your system, you will only be able to use the card defined as a primary adaptor. In other words the card that is currently active and displaying your desktop. The provided source code does not perform device enoumeration of any kind and does not allow the user to select a different device during the runtime of the application.
In addition to this tutorial it is also recommended to take a look at the DirectX SDK tutorial 1 "Create Device" (DirectX SDK installation wizard typically installs tutorials and code examples from Microsoft, most of them are fairly advanced) to get a different perspective.
In the DirectX SDK tutorial 1 "Create Device" the D3DCREATE_SOFTWARE_VERTEXPROCESSING constant is specified to use the "software vertrex processing" instead of the "hardware vertex processing" because it assumes that not each computer has a suitable graphical card to work with DirectX.
If one doesn’t have a suitable graphical card (e.g.: GeForce from nVIDIA http://www.nvidia.com/page/products.html or any other card that supports "hardware vertex processing") then my advice is to buy a suitable graphical card before taking it any further.
The advantage of the code we use is that we first check if the system has a suitable graphical card (if it supports "hardware vertex processing"). If it does, we create a DirectX device able to interact with your graphical card and perform "hardware vertex processing", otherwise we create a "software device" simulating interaction with a graphical card by using the "software vertex processing". This special simulation mode is build-in into the DirectX environment. It can only be used for testing and not in real world DirectX applications.
If you use the debugger available in Visual Studio, you may check in which mode your application is running by setting the breakpoint where DirectX device is being initialized. However most of the modern graphical cards support "hardware vertex processing".
Now we can discuss the source code modifications. First of all we include the following header files:
#include<d3d9.h> 
#include<d3dx9.h>
Notice that in order to work with DirectX you also have to reference the following libraries in your Visual Studio C++ Win32 project:
d3d9.lib d3dx9.lib winmm.lib
Notice that these libraries are not referenced by default so you really need to take care to add them in each project if you want to work with DirectX. If you get strange compile errors indicating that DirectX methods can not be found, remember to check if you did reference the DirectX libraries correctly (assuming of course that installation of DirectX SDK and Visual Studio extensions for DirectX was successful).
You do that by right clicking the project name (not the solution name), which is always under the solution name, selecting the Properties > Configuration Properties > Linker > Input and adding the d3d9.lib d3dx9.lib winmm.lib libraries in "Additional Dependencies" field as in the picture above.
We add the following global variables to our program:
//
// Globals
//
LPDIRECT3D9 directX;
LPDIRECT3DDEVICE9 directXDevice;
The first variable is a pointer to Direct3D9. You can see it as our DirectX environment. Notice that "LP" in the type name stands for "long pointer". The second variable is a pointer to a DirectX device we have discussed earlier in this tutorial. This device will represent our primary adaptor and during the initialization process we will determine what kind of vertex processing (hardware or software) this device will be supporting.
Lets examine the following code modifications made inside the WinMain() method:
// Init DirectX objects
initDevice(hwnd);
initCamera();
initScene();

// Message loop
while(msg.message != WM_QUIT){
    if(::PeekMessage(&msg, 0, 0, 0, PM_REMOVE)){
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }
    drawScene();
}

// Cleanup DirectX objects
cleanup();
return msg.wParam;

Before we enter the message loop we call initDevice() method by passing a handle to the current window. After that we call initCamera() and initScene(). We will discuss each method later. Notice that inside the message loop we now call PeekMessage() method instead of GetMessage() like in the previous tutorial. This is because PeekMessage() doesn't wait for a confirmation that a message has been placed in the queue and returns immediately. GetMessage() does not return until the message is placed in the queue and produces delays that we can not afford while rendering DirectX objects, especially during the animation where we must maintain a decent frame rate.
Notice the call to drawScene() method inside the message loop. This is the method that encapsulates rendering of all the objects during a runtime of our application. Finally before exiting our application we call the cleanup() method to clean the memory from DirectX resources.
The source code of the initDevice() method is provided bellow:
void initDevice(HWND myWindow){
    D3DPRESENT_PARAMETERS dxPresentParams;

    directX = Direct3DCreate9(D3D_SDK_VERSION);
    if (directX == NULL){
        ::MessageBox(myWindow, 
        "DirectX runtime library is not installed.", 
        "System error", MB_ICONEXCLAMATION | MB_OK);
    }

    ZeroMemory( &dxPresentParams, sizeof(dxPresentParams) );
    dxPresentParams.Windowed = TRUE;
    dxPresentParams.SwapEffect = D3DSWAPEFFECT_DISCARD;
    dxPresentParams.BackBufferFormat = D3DFMT_UNKNOWN;

    if (FAILED(directX->CreateDevice(
        D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, 
        myWindow, D3DCREATE_HARDWARE_VERTEXPROCESSING, 
        &dxPresentParams, &directXDevice))){
        if (FAILED(directX->CreateDevice(
            D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, 
            myWindow, D3DCREATE_SOFTWARE_VERTEXPROCESSING, 
            &dxPresentParams, &directXDevice))){
            ::MessageBox(myWindow, 
            "CreateDevice() failed", "System error", 
            MB_ICONEXCLAMATION | MB_OK);
        }
    }

    directXDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
    directXDevice->SetRenderState(D3DRS_LIGHTING, false);
}
As you can see we first define the dxPresentParams which is an object representing all the features supported by a DirectX device. Then we create our DirectX environment object, we check if DirectX environment is available (installed in the system), if not we give an error message indicating that. After that we setup selected properties of the dxPresentParams, try to create a DirectX device supporting the "hardware vertex processing". If we fail to create such device, we try to create a "software device" supporting the "software vertex processing" in a simulation mode. Finally we set the properties of device's render state. Notice that at this point the lighting is set to false. Light will be discussed in the Color, material and light tutorial. The following method initiates the camera:
void initCamera(){
    D3DXVECTOR3 eye(0, 0, -30);
    D3DXVECTOR3 target(0, 0, 0);
    D3DXVECTOR3 up(0, 1, 0);
    D3DXMATRIXA16 view;
    D3DXMatrixLookAtLH(&view, &eye, &target, &up);
    directXDevice->SetTransform(D3DTS_VIEW, &view);

    D3DXMATRIX mProjection;
    D3DXMatrixPerspectiveFovLH(&mProjection, D3DX_PI*0.25f, 
    MAIN_WINDOW_WIDTH/MAIN_WINDOW_HEIGHT, 1, 50);
    directXDevice->SetTransform(D3DTS_PROJECTION, &mProjection);
}
The eye of the camera located at (0,0,-30) is "looking at" the target located at (0, 0, 0) which is the center of our 3D world (the numbers represent x, y, z coordinates). Notice that our camera has a matrix associated with it and that it has to be transformed with the SetTransform() method to position the camera in the world. In the next tutorial DirectX build-in primitives: drawing the box we will talk more detailed about positioning, matrices and their transformations.
We then define the projection matrix which is responsible for a perspective view that creates an illusion of depth on the screen. Notice that one of the parameters passed to the D3DXMatrixPerspectiveFovLH() method is the ratio calculated by: MAIN_WINDOW_WIDTH/MAIN_WINDOW_HEIGHT. Please advise the DirectX SDK documentation for details about other parameters. Finally we transform the projection matrix by calling the SetTransform() method of our DirectX device.
The initScene() method is empty since we have no objects to draw yet. We will learn how to draw in the next tutorial.
The following method is responsible for all the drawing we are going to perform during the lifetime of our application:
void drawScene(){     
    directXDevice->Clear(0, NULL, D3DCLEAR_TARGET, 
    0x00000000, 1.0f, 0);  // 0x00000000 = black
    directXDevice->BeginScene();
    // begin scene

    //
    // draw your objects here
    //

    // end scene
    directXDevice->EndScene();
    directXDevice->Present(NULL, NULL, NULL, NULL);
}

Notice that our scene at this tutorial does not contain any objects so nothing will be rendered. We only prepare the scene for drawing by clearing each pixel of the screen to black color defined as 0x00000000. We will talk more about color in the Color, material and light tutorial. All the objects we ever going to draw has to be included between the BeginScene() and the EndScene() methods of our DirectX device. Finally the scene is presented by calling the Present() method.
Bellow is the cleanup() method:
void cleanup(){
    
    //
    // cleanup your objects here
    //

    if(directXDevice){
        directXDevice->Release();
        directXDevice = NULL;
    }
    if(directX){
        directX->Release();
        directX = NULL;
    }
}
As it was mentioned before inside the cleanup() method we release the DirectX resources from memory.
In the next tutorial we will learn about DirectX primitives and will draw a box on the screen.