Simple DX8 Framework

Home | Up | Search | X-Zone News | Services | Book Support | Links | Feedback | Smalltalk MT | The Scrapyard | FAQ | Technical Articles

 

Written by Robert Dunlop
Microsoft DirectX MVP

Introduction

This page provides source code for a framework for developing graphics applications using DirectX 8.  The framework provides a quick starting point, while not imposing any high level functionality.

The heart of the framework is the DXContext class, which features:
bullet Initialization in full screen or windowed mode
bullet Automatic detection of valid screen and depth buffer formats 
bulletBuilt in message loop 
bulletWorks with developer supplied frame function 
bulletPer-frame callback of developer supplied frame function 
bulletProvides sub-microsecond frame duration to callback using QPC 
bulletFallback timer for processors lacking performance counter 

Using the Framework

To use the framework, you will need to write a minimum of three functions:

  1. A WinMain() function, which is the entry point of the program.  This function will create an instance of the DXContext class and initialize it.  Control is taken over by DXContext when it's Run() member is called, and returns after the window has been destroyed.  Delete the DXContext instance after it returns from Run(). 
  2. A callback to be called to render each frame.  Pass a pointer to this function to the DXContext::Run() function.  When called by the framework, a float will be passed that contains the number of seconds since the last frame started (i.e. the last time this function was called).  Note that you can change the callback function at any point by calling DXContext::SetRenderFunc(). 
  3. A window procedure to handle messages.  A pointer to this function must be provided when calling DXContext::Init().  At a minimum, the procedure must handle the WM_DESTROY message, by calling DXContext::Cleanup(), followed by PostQuitMessage(0). 

The framework is written under Visual C++ 6.0, and has not yet been compiled on other compilers.  There is no guarantee of fitness for other compilers, and may require modification.  If anyone adapts this for  another compiler, I'd love to hear about it, and possibly post it here with your permission.  Direct this or other comments to rdunlop@mvps.org.

YOURPROGRAM.CPP

#include "stdafx.h"

DXContext *lpDX=NULL;

static char szClass[] = "YourProgramClass";
static char szCaption[] = "Your Program";

void render_frame(float elapsed)
{
    lpDX->lpDevice->Clear(0,NULL,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,0x00000000,1.0f,0);
    if (D3D_OK==lpDX->lpDevice->BeginScene()) {
        // Place your rendering code here
        lpDX->lpDevice->EndScene();
    }
    lpDX->lpDevice->Present(NULL,NULL,NULL,NULL);
}

void Cleanup()
{
    if (lpDX)
        lpDX->Cleanup();
}

LRESULT CALLBACK 
WindowProc(HWND hWnd, unsigned uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_DESTROY:

            Cleanup();
            PostQuitMessage(0);
            break;

        case WM_SIZE:

            // clear or set activity flag to reflect focus

            if (lpDX) {
                if( SIZE_MAXHIDE==wParam || SIZE_MINIMIZED==wParam )
                    lpDX->active = FALSE;
                else
                    lpDX->active = TRUE;
            }
            break;

        case WM_KEYUP: 

            switch (wParam) { 

                case VK_ESCAPE:

                    // exit the program on escape

                    DestroyWindow(hWnd);
                    break;
            } 
            break;

        default:
            return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }

    return 0L;
}

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
    // create application handler and link to our WindowProc

    lpDX=new DXContext(hInstance,szClass,szCaption,WindowProc);
    if (!lpDX)
        return FALSE;

    // check for error

    if (lpDX->lastErr!=S_OK) {
        delete lpDX;
        return FALSE;
    }

    // initialize full screen graphics to 640 x 480 x 32 bit, with 2 back buffers

    if (!lpDX->Init(640,480,16,2,TRUE)) {
        delete lpDX;
        return FALSE;
    }

    // run the game loop, passing pointer to our rendering routine

    lpDX->Run(render_frame);

    SafeDelete(lpDX);

    return TRUE;
} 

STDAFX.H


#include <windows.h>
#include <mmsystem.h>
#include <d3d8.h>
#include <d3dx8.h>
#include <d3dx8mesh.h>
#include "dxcontext.h"

#define SafeRelease(x) if (x) {x->Release(); x=NULL;}
#define SafeDelete(x) if (x) {delete x; x=NULL;} 

DXCONTEXT.H


class DXContext 
{
    public:
        void SetRenderFunc(void (*ptr)(float));
        BOOL TestDepth(D3DFORMAT fmt);
        BOOL TestFormat(D3DFORMAT fmt);
        void (*renderPtr)(float);
        DXContext(HINSTANCE hInstance,LPCSTR pClass,LPCSTR pCaption,WNDPROC pProc);
        ~DXContext();
        BOOL Init(WORD wWidth,WORD wHeight,WORD wDepth,WORD wBackBuffers,BOOL bWindowed);
        int Run(void (*ptr)(float));
        void Cleanup();
        HRESULT hRes;
        HRESULT lastErr;
        D3DFORMAT format;
        D3DFORMAT zFormat;
        WORD height;
        WORD width;
        LPDIRECT3D8 lpD3D;
        LPDIRECT3DDEVICE8 lpDevice;
        BOOL active;
        HWND hwnd;
};

#define DXC_ERR_REGWIN -2
#define DXC_ERR_CREATEWIN -3
#define DXC_ERR_CREATE3D -4
#define DXC_ERR_GETFORMAT -5
#define DXC_ERR_FORMAT -6
#define DXC_ERR_CREATEDEV -7

DXCONTEXT.CPP

#include "stdafx.h"
#include "DXContext.h"

#pragma comment(lib,"winmm.lib")
#pragma comment(lib,"d3d8.lib")
#ifdef _DEBUG
    #pragma comment(lib, "d3dx8d.lib")
#else
    #pragma comment(lib, "d3dx8.lib")
#endif

DXContext:: DXContext(HINSTANCE hInstance,LPCSTR pClass,LPCSTR pCaption,WNDPROC pProc)
{
    WNDCLASS wc;

    // clear the error register

    lastErr=S_OK;

    // clear the active flag

    active=FALSE;

    // Set up and register window class

    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC) pProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = sizeof(DWORD);
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = pClass;
    if (!RegisterClass(&wc)) {
        lastErr=-2;
        return;
    }

    // Get dimensions of display

    int ScreenWidth = GetSystemMetrics(SM_CXSCREEN);
    int ScreenHeight = GetSystemMetrics(SM_CYSCREEN);
    hwnd = CreateWindow(pClass, // class
                        pCaption, // caption
                        WS_OVERLAPPEDWINDOW, // style 
                        0, // left
                        0, // top
                        ScreenWidth, // width
                        ScreenHeight, // height
                        NULL, // parent window
                        NULL, // menu 
                        hInstance, // instance
                        NULL); // parms
    if (!hwnd) {
        lastErr=-3;
        return;
    }
}

DXContext::~DXContext()
{
    // call cleanup in case program did not
    // the cleanup routine will only release objects once, despite repeated calls 

    Cleanup();
}

void DXContext::Cleanup()
{
    // clear active flag

    active=FALSE;

    // release 3D interfaces

    SafeRelease(lpDevice);
    SafeRelease(lpD3D);
}

BOOL DXContext::TestFormat(D3DFORMAT fmt)
{
    if (D3D_OK==lpD3D->CheckDeviceType(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,fmt,fmt,FALSE))
        return TRUE;
    return FALSE;
}

BOOL DXContext::TestDepth(D3DFORMAT fmt)
{
    if (D3D_OK!=lpD3D->CheckDeviceFormat(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,format,
                                         D3DUSAGE_DEPTHSTENCIL,D3DRTYPE_SURFACE,fmt))
        return FALSE;
    if (D3D_OK!=lpD3D->CheckDepthStencilMatch(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,
                                              format,format,fmt))
        return FALSE;
    return TRUE;
}

#define DXTestFmt(x) if (TestFormat(x)) {format=x; break;}
#define DXTestDepth(x) if (TestDepth(x)) zFormat=x

BOOL DXContext::Init(WORD wWidth,WORD wHeight,WORD wDepth,WORD wBackBuffers,BOOL bWindowed)
{
    // save screen parameters

    width=wWidth;
    height=wHeight;

    SetWindowPos(hwnd,HWND_TOP,0,0,width,height,SWP_SHOWWINDOW);

    lpD3D=Direct3DCreate8(D3D_SDK_VERSION);
    if (!lpD3D) {
        lastErr=-4;
        return FALSE;
    }

    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp,sizeof(d3dpp));
    if (bWindowed) {
        d3dpp.Windowed=TRUE;
        D3DDISPLAYMODE d3ddm;
        if( FAILED( hRes=lpD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm ) ) ) {
            lastErr=-5;
            return FALSE;
        }
        format=d3dpp.BackBufferFormat=d3ddm.Format; 
    } else {
        d3dpp.Windowed=FALSE;
        switch (wDepth) {
            case 32:
                DXTestFmt(D3DFMT_A8R8G8B8);
                DXTestFmt(D3DFMT_X8R8G8B8);
            case 16:
                DXTestFmt(D3DFMT_R5G6B5);
                DXTestFmt(D3DFMT_X1R5G5B5);
            default:
                format=D3DFMT_UNKNOWN;
        }
        if (format==D3DFMT_UNKNOWN) {
            lastErr=-6;
            return FALSE;
        }
        d3dpp.BackBufferFormat=format;
        d3dpp.FullScreen_RefreshRateInHz=D3DPRESENT_RATE_DEFAULT;
        d3dpp.FullScreen_PresentationInterval=D3DPRESENT_INTERVAL_ONE;
    }
    d3dpp.BackBufferWidth=width;
    d3dpp.BackBufferHeight=height;
    d3dpp.BackBufferCount=wBackBuffers;
    d3dpp.SwapEffect = D3DSWAPEFFECT_FLIP;
    zFormat=D3DFMT_UNKNOWN;
    DXTestDepth(D3DFMT_D32);
    else DXTestDepth(D3DFMT_D32);
    else DXTestDepth(D3DFMT_D24S8);
    else DXTestDepth(D3DFMT_D24X4S4);
    else DXTestDepth(D3DFMT_D24X8);
    else DXTestDepth(D3DFMT_D16);
    else DXTestDepth(D3DFMT_D15S1);
    else DXTestDepth(D3DFMT_D16_LOCKABLE);
    if (zFormat!=D3DFMT_UNKNOWN) {
        d3dpp.EnableAutoDepthStencil=TRUE;
        d3dpp.AutoDepthStencilFormat=zFormat;
    }
    if( FAILED( hRes=lpD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd,
                                          D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                          &d3dpp,&lpDevice ) ) ) {

        lastErr=-7;
        return FALSE;
    }    

    // return success

    return TRUE;
}

int DXContext::Run(void (*ptr)(float))
{
    renderPtr=ptr;

    LONGLONG cur_time; // current time
    BOOL perf_flag=FALSE; // flag determining which timer to use
    LONGLONG last_time=0; // time of previous frame
    float time_elapsed; // time since previous frame
    float time_scale; // scaling factor for time

    // select timer, get current time, and calculate time scale

    if (QueryPerformanceFrequency((LARGE_INTEGER *) &cur_time)) {
        time_scale=1.0f/cur_time;
        QueryPerformanceCounter((LARGE_INTEGER *) &last_time);
        perf_flag=TRUE;
    } else {
        time_scale=0.001f;
        last_time=timeGetTime();
    }

    // set status as active

    active=TRUE;

    // Now we're ready to recieve and process Windows messages.

    BOOL bGotMsg;
    MSG msg;
    PeekMessage( &msg, NULL, 0U, 0U, PM_NOREMOVE );

    while( WM_QUIT != msg.message ) {
        if (!active) {
            bGotMsg=GetMessage(&msg, NULL, 0U, 0U);
            if( bGotMsg ) {
                TranslateMessage( &msg );
                DispatchMessage( &msg );
            }
        } else {
            bGotMsg = PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE );
            if( bGotMsg ) {
                TranslateMessage( &msg );
                DispatchMessage( &msg );
            } else {

                // use the appropriate method to get time 
                // and calculate elapsed time since last frame

                if (perf_flag) 
                    QueryPerformanceCounter((LARGE_INTEGER *) &cur_time);
                else 
                    cur_time=timeGetTime();

                // calculate elapsed time

                time_elapsed=(cur_time-last_time)*time_scale;

                // save frame time

                last_time=cur_time;

                if (renderPtr)
                    renderPtr(time_elapsed);
            }
        }
    }

    // return final message

    return msg.wParam;
}

void DXContext::SetRenderFunc(void (__cdecl *ptr)(float))
{
    renderPtr=ptr;
}
 

This site, created by DirectX MVP Robert Dunlop and aided by the work of other volunteers, provides a free on-line resource for DirectX programmers.

Special thanks to WWW.MVPS.ORG, for providing a permanent home for this site.

Visitors Since 1/1/2000: Hit Counter
Last updated: 07/26/05.