Creating an Object-Oriented Framework

  CU_Framework.zip (24.2 KiB, 5,558 hits)

When working with your own projects, it is extremely helpful to put together a framework that you can reuse again and again in future projects. This saves both time and energy since you don’t have to write the same code that you have already written half a dozen times. Using the knowledge from the previous tutorials, we will put together a framework so all future projects will be easier to write. The framework that we will write is loosely based on the sample framework that ships with the SDK. However, our framework will be a completely stripped down version of the sample framework so we can focus on the bare essentials. It will be a lot less complicated, but still function reasonably well.

The idea behind our framework is to hide the code that is common to every application using the framework. This includes window creation, DirectX initialization, fullscreen toggles, etc. Since all that code remains the same in every application, we shouldn’t have to fuss with it let alone see it. Instead, we will modify an application-specific class whose functions will be called by the framework whenever necessary.

To begin with, our framework will need to support 2 basic types of resource methods: resource creation methods and resource disposal methods. The resource creation and disposal methods will need to account for which memory pool the resource is created in. For simplicity’s sake, we will categorize a resource as being either a D3DPOOL_MANAGED resource or a D3DPOOL_DEFAULT resource. Managed resources are backed up by system memory and do not need to be recreated unless the device is destroyed. Default resources, on the other hand, are not backed up so default resources need to be recreated whenever the device is reset. Keeping all this in mind, we will write Managed resource creation and disposal methods (OnCreateDevice and OnDestroyDevice) and Default resource creation and disposing methods (OnResetDevice and OnLostDevice). We will also add functions to handle application initialization, frame updates and frame renders, and key presses.

To make things easier (for me), I have already implemented the handling of lost devices into the framework. This is so I would not have to add more common code to the framework later on. Since I haven’t gone over lost devices yet, you can safely ignore the lost device code, which is located in the OnRenderFrame function of the framework. I will go over lost devices in a later tutorial. For this tutorial, focus on the overall design of the framework.

First up is a class to encapsulate the Direct3D Device:/p>

#ifndef CGRAPHICS_H
#define CGRAPHICS_H 

#include "stdafx.h" 

class CGraphics {
 public:
  CGraphics();
  ~CGraphics() { Release(); }
  BOOL Initialize(HWND hWnd, BOOL bWindowed);
  void Release();
  BOOL Reset();
  LPDIRECT3D9 GetD3D() { return m_pD3D9; }
  LPDIRECT3DDEVICE9 GetDevice() { return m_pDevice; } 

  BOOL Windowed; 

 private:
  BOOL BuildPresentParameters();
  HWND m_hWnd;
  HINSTANCE m_hInstance;
  LPDIRECT3D9 m_pD3D9;
  LPDIRECT3DDEVICE9 m_pDevice;
  D3DCAPS9 m_caps;
  D3DPRESENT_PARAMETERS m_pp;
  D3DDISPLAYMODE m_displayMode;
}; 

#endif

This is the header file for the class, CGraphics, which encapsulates all things IDirect3DDevice9-related. CGraphics will store such things as the IDirect3D9 and IDirect3DDevice9 objects, PRESENT_PARAMETERS, D3DCAPS9, and D3DDISPLAYMODE structures, as well as some Win32 constructs like HINSTANCE and HWND.

#include “..\include\stdafx.h”
#include “..\include\CGraphics.h”

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Default constructor
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
CGraphics::CGraphics() Toggle

{

m_pD3D9 = NULL;
m_pDevice = NULL;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Cleans up resources
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGraphics::Release() Toggle

{

SAFE_RELEASE( m_pDevice );
SAFE_RELEASE( m_pD3D9 );

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Initializes Direct3D.
Parameters:
[in] hWnd – Handle to the window
[in] windowed – TRUE for windowed mode, FALSE for fullscreen mode
Returns: TRUE on success. FALSE on failure
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BOOL CGraphics::Initialize( HWND hWnd, BOOL windowed ) Toggle

{

Windowed = windowed;
m_hWnd = hWnd;
SAFE_RELEASE( m_pD3D9 );
SAFE_RELEASE( m_pDevice );
m_pD3D9 = Direct3DCreate9( D3D_SDK_VERSION );
if ( !m_pD3D9 )
{

SHOWERROR( “Direct3DCreate9() – Failed”, __FILE__, __LINE__ );
return FALSE;

}

m_pD3D9->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &m_displayMode );
m_pD3D9->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &m_caps );

// Check for hardware T&L
DWORD vertexProcessing = 0;
if ( m_caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
{

vertexProcessing = D3DCREATE_HARDWARE_VERTEXPROCESSING;
// Check for pure device
if ( m_caps.DevCaps & D3DDEVCAPS_PUREDEVICE )
{

vertexProcessing |= D3DCREATE_PUREDEVICE;

}

}
else
{

vertexProcessing = D3DCREATE_SOFTWARE_VERTEXPROCESSING;

}

if ( !BuildPresentParameters() )
{

SAFE_RELEASE( m_pD3D9 );
return FALSE;

}

HRESULT hresult = 0;
// Create the device
hresult = m_pD3D9->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hWnd, vertexProcessing, &m_pp, &m_pDevice );
if ( FAILED( hresult ) )
{

SAFE_RELEASE( m_pD3D9 );
SHOWERROR( “CreateDevice() – Failed”, __FILE__, __LINE__ );
return FALSE;

}
return TRUE;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Builds the D3DPRESENT_PARAMETERS structure.
Returns: TRUE on success. FALSE on failure
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BOOL CGraphics::BuildPresentParameters() Toggle

{

ZeroMemory( &m_pp, sizeof(m_pp) );
D3DFORMAT adapterFormat = (Windowed) ? m_displayMode.Format : D3DFMT_X8R8G8B8;
if ( SUCCEEDED( m_pD3D9->CheckDeviceFormat( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, adapterFormat, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D24S8 ) ) )
{

m_pp.AutoDepthStencilFormat = D3DFMT_D24S8;

}
else if ( SUCCEEDED( m_pD3D9->CheckDeviceFormat( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, adapterFormat, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D24X8 ) ) )
{

m_pp.AutoDepthStencilFormat = D3DFMT_D24X8;

}
else if ( SUCCEEDED( m_pD3D9->CheckDeviceFormat( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, adapterFormat, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D16 ) ) )
{

m_pp.AutoDepthStencilFormat = D3DFMT_D16;

}
else
{

SHOWERROR( “Unable to find valid depth format”, __FILE__, __LINE__ );
return FALSE;

}

m_pp.BackBufferWidth = (Windowed) ? 0 : m_displayMode.Width;
m_pp.BackBufferHeight = (Windowed) ? 0 : m_displayMode.Height;
m_pp.BackBufferFormat = adapterFormat;
m_pp.BackBufferCount = 1;
m_pp.MultiSampleType = D3DMULTISAMPLE_NONE;
m_pp.MultiSampleQuality = 0;
m_pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
m_pp.hDeviceWindow = m_hWnd;
m_pp.Windowed = Windowed;
m_pp.EnableAutoDepthStencil = TRUE;
m_pp.FullScreen_RefreshRateInHz = (Windowed) ? 0 : m_displayMode.RefreshRate;
m_pp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

return TRUE;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Resets the device
Returns: TRUE on success. FALSE on failure
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BOOL CGraphics::Reset() Toggle

{

if ( m_pDevice )
{

if ( !BuildPresentParameters() )
{

return FALSE;

}
m_pDevice->Reset( &m_pp );

}

return TRUE;

}

As you can see, the CGraphics class accomplishes all the initialization code that we performed in the last tutorial. The only difference is that it is all wrapped up in its own class. The Reset function is called whenever the window is resized, the device is lost, or we toggle between window and fullscreen mode. To reset the device, we call IDirect3DDevice9::Reset. It is necessary to reset the device in these cases because the device is only configured for one specific size and format. When either of these change, we must reset the device to account for the new size or format.

Now we will move on to the main framework class, CFramework.

#ifndef CFRAMEWORK_H
#define CFRAMEWORK_H

#include “stdafx.h”
#include “CGraphics.h”

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Interface that the main game application must implement
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
class CBaseApp
{
public:
virtual ~CBaseApp() {}
virtual void Release() = 0;
virtual void OnCreateDevice( LPDIRECT3DDEVICE9 pDevice ) = 0;
virtual void OnResetDevice( LPDIRECT3DDEVICE9 pDevice ) = 0;
virtual void OnLostDevice() = 0;
virtual void OnDestroyDevice() = 0;
virtual void OnUpdateFrame( LPDIRECT3DDEVICE9 pDevice ) = 0;
virtual void OnRenderFrame( LPDIRECT3DDEVICE9 pDevice ) = 0;
virtual void OnKeyDown( WPARAM wParam ) = 0;
};

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Main framework class
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
class CFramework
{
public:
CFramework( CBaseApp* pGameApp );
~CFramework() { Release(); }
BOOL Initialize( char* title, HINSTANCE hInstance, int width, int height, BOOL windowed = TRUE );
void Run();
void Release();
void ToggleFullscreen();
static LRESULT CALLBACK StaticWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );

private:
LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );
void OnCreateDevice();
void OnResetDevice();
void OnLostDevice();
void OnDestroyDevice();
void OnUpdateFrame();
void OnRenderFrame();

HWND m_hWnd;
HINSTANCE m_hInstance;
BOOL m_active;
int m_windowWidth;
int m_windowHeight;
WINDOWPLACEMENT m_wp;

CGraphics* m_pGraphics;
CBaseApp* m_pGameApp;
};

#endif

First up is the CBaseApp class. Everytime we create a new application, we will need to create a class that inherits_ from CBaseApp. The CFramework class will call the methods found in CBaseApp to interact with our application. As you can see, the methods in CBaseApp are the methods I mentioned above such as the resource creation and disposal methods, and the frame update and render methods.

The CFramework class is the main control center of the application. It has its own instance of CGraphics and CBaseApp as well as some state variables.

#include “..\include\stdafx.h”
#include “..\include\CFramework.h”

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Default constructor
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
CFramework::CFramework( CBaseApp* pGameApp ) Toggle
{
   m_pGameApp = pGameApp;
   m_active = TRUE;
   m_pGraphics = new CGraphics();
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Destructor
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CFramework::Release() Toggle
{
   SAFE_RELEASE( m_pGraphics );
   OnLostDevice();
   OnDestroyDevice();
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Creates the window and initializes DirectX Graphics.
Parameters:
[in] szTitle – Title of the window
[in] hInstance – Application instance.
[in] iWidth – Window width.
[in] iHeight – Window height.
[in] bWindowed – Window mode (TRUE). Fullscreen mode (FALSE).
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BOOL CFramework::Initialize( char* title, HINSTANCE hInstance, int width, int height, BOOL windowed ) Toggle
{
   m_hInstance = hInstance;
   m_windowWidth = width;
   m_windowHeight = height;

   // Define the window
   WNDCLASSEX wcex;
   wcex.cbSize         = sizeof( WNDCLASSEX );
   wcex.style          = CS_DBLCLKS;
   wcex.lpfnWndProc    = (WNDPROC)CFramework::StaticWndProc;
   wcex.cbClsExtra     = 0;
   wcex.cbWndExtra     = 0;
   wcex.hInstance      = hInstance;
   wcex.hIcon          = LoadIcon( hInstance, MAKEINTRESOURCE( IDI_CUNIT ) );
   wcex.hCursor        = LoadCursor( NULL, IDC_ARROW );
   wcex.hbrBackground  = (HBRUSH)GetStockObject( BLACK_BRUSH );
   wcex.lpszMenuName   = NULL;
   wcex.lpszClassName  = title;
   wcex.hIconSm        = LoadIcon( hInstance, MAKEINTRESOURCE( IDI_CUNIT ) );

   // Register the window
   RegisterClassEx( &wcex );

   // Create the window
   m_hWnd = CreateWindow( title, title,  windowed ? WS_OVERLAPPEDWINDOW : WS_EX_TOPMOST, CW_USEDEFAULT,
       0, width, height, NULL, NULL, hInstance, this );
   
   // Adjust to desired size
   RECT rect = { 0, 0, width, height };
   AdjustWindowRect( &rect, GetWindowLong( m_hWnd, GWL_STYLE ), FALSE );
   SetWindowPos( m_hWnd, HWND_TOP, 0, 0, rect.right – rect.left, rect.bottom – rect.top,
       SWP_NOZORDER | SWP_NOMOVE  );

   ShowWindow( m_hWnd, SW_SHOW );
   UpdateWindow( m_hWnd );

   // Initialize Direct3D
   if ( !m_pGraphics->Initialize( m_hWnd, windowed ) )
   {
       return FALSE;
   }

   
   OnCreateDevice();
   OnResetDevice();

   return TRUE;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Runs the application
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CFramework::Run() Toggle
{
   MSG msg;
   while ( 1 )
   {
       // Did we recieve a message, or are we idling ?
       if ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
       {
           if ( msg.message == WM_QUIT)
           {
               break;
           }
           TranslateMessage( &msg );
           DispatchMessage ( &msg );
       }
       else
       {
           // Advance Game Frame.
           if ( m_pGraphics->GetDevice() != NULL && m_active )
           {
               OnUpdateFrame();
               OnRenderFrame();
           }
       }
   }
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Called after the device is created. Create D3DPOOL_MANAGED resources here.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CFramework::OnCreateDevice() Toggle
{
   if ( m_pGameApp != NULL && m_pGraphics != NULL )
   {
       m_pGameApp->OnCreateDevice( m_pGraphics->GetDevice() );
   }
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Called after the device is reset. Create D3DPOOL_DEFAULT resources here.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CFramework::OnResetDevice() Toggle
{
   if ( m_pGameApp != NULL && m_pGraphics != NULL )
   {
       m_pGameApp->OnResetDevice( m_pGraphics->GetDevice() );
   }
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Called when the device is lost. Release D3DPOOL_DEFAULT resources here.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CFramework::OnLostDevice() Toggle
{
   if ( m_pGameApp != NULL )
   {
       m_pGameApp->OnLostDevice();
   }
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Called after the device is destroyed. Release D3DPOOL_MANAGED resources here.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CFramework::OnDestroyDevice() Toggle
{
   if ( m_pGameApp != NULL )
   {
       m_pGameApp->OnDestroyDevice();
   }
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Updates the current frame.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CFramework::OnUpdateFrame() Toggle
{
   if ( m_pGameApp != NULL && m_pGraphics != NULL )
   {
       m_pGameApp->OnUpdateFrame( m_pGraphics->GetDevice() );
   }
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Renders the current frame.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CFramework::OnRenderFrame() Toggle
{
   if ( !m_active || (m_pGraphics->GetDevice() == NULL) )
   {
       return;
   }

   // Check for lost device
   HRESULT result = m_pGraphics->GetDevice()->TestCooperativeLevel();
   if ( FAILED( result ) )
   {
       if ( result == D3DERR_DEVICELOST )
       {
           Sleep( 50 );
           return;
       }
       else
       {
           OnLostDevice();
           if ( m_pGraphics->Reset() == D3DERR_DEVICELOST )
           {
               // Device is lost still
               Sleep( 50 );
               return;
           }
           else
           {
               OnResetDevice();
           }
       }
   }

   if ( m_pGameApp != NULL )
   {
       m_pGameApp->OnRenderFrame( m_pGraphics->GetDevice() );
   }
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Toggles between fullscreen and windowed mode.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CFramework::ToggleFullscreen() Toggle
{
   if ( m_pGraphics == NULL || !m_active )
   {
       return;
   }

   m_pGraphics->Windowed = !m_pGraphics->Windowed;

   // Set new window style
   if ( m_pGraphics->Windowed )
   {
       // Going to windowed mode
       SetWindowLongPtr( m_hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW );
   }
   else
   {
       // Save current location/size
       ZeroMemory( &m_wp, sizeof( WINDOWPLACEMENT ) );
       m_wp.length = sizeof( WINDOWPLACEMENT );
       GetWindowPlacement( m_hWnd, &m_wp );

       // Going to fullscreen mode
       SetWindowLongPtr( m_hWnd, GWL_STYLE, WS_EX_TOPMOST );

       // Hide the window to avoid animation of blank windows
       ShowWindow( m_hWnd, SW_HIDE );
   }

   // Reset the Device
   OnLostDevice();
   m_pGraphics->Reset();
   OnResetDevice();

   if ( m_pGraphics->Windowed )
   {
       // Going to windowed mode
       // Restore the window location/size
       SetWindowPlacement( m_hWnd, &m_wp );
   }

   // Make the window visible
   if ( !IsWindowVisible( m_hWnd ) )
   {
       ShowWindow( m_hWnd, SW_SHOW );
   }
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Event handler. Routes messages to appropriate instance.
Parameters:
[in] hWnd – Unique handle to the window.
[in] message – Incoming message.
[in] wParam – Parameter of the message (unsigned int).
[in] lParam – Parameter of the message (long).
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
LRESULT CALLBACK CFramework::StaticWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) Toggle
{
   if ( msg == WM_CREATE )
   {
       SetWindowLongPtr( hWnd, GWLP_USERDATA, (LONG)((CREATESTRUCT *)lParam)->lpCreateParams );
   }

   CFramework *targetApp = (CFramework*)GetWindowLongPtr( hWnd, GWLP_USERDATA );

   if ( targetApp )
   {
       return targetApp->WndProc( hWnd, msg, wParam, lParam );
   }

   return DefWindowProc( hWnd, msg, wParam, lParam );
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Application event handler.
Parameters:
[in] hWnd – Unique handle to the window.
[in] message – Incoming message.
[in] wParam – Parameter of the message (unsigned int).
[in] lParam – Parameter of the message (long).
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
LRESULT CALLBACK CFramework::WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) Toggle
{
   switch ( message )
   {
   case WM_DESTROY:
       PostQuitMessage( 0 );
       return 0;
   case WM_PAINT:
       if ( m_pGraphics->GetDevice() )
       {
           OnUpdateFrame();
           OnRenderFrame();
       }
       ValidateRect( hWnd, 0 );
       return 0;
   case WM_SIZE:
       if ( wParam == SIZE_MINIMIZED )
       {
           // Disable application on minimized
           m_active = FALSE;
       }
       else
       {
           m_active = TRUE;
           m_windowWidth = LOWORD( lParam );
           m_windowHeight = HIWORD( lParam );
           if ( m_pGraphics->GetDevice() )
           {
               OnLostDevice();
               m_pGraphics->Reset();
               OnResetDevice();
               OnUpdateFrame();
               OnRenderFrame();
           }
       }
       return 0;
   case WM_KEYDOWN:
       // Send keystrokes to application to handle
       if ( m_pGameApp != NULL )
       {
           m_pGameApp->OnKeyDown( wParam );
       }
       return 0;
   }
   return DefWindowProc( hWnd, message, wParam, lParam );
}

The CFramework class contains a method for each stage of the application’s execution. In each of the On* methods, CFramework calls the corresponding method of the same of its CBaseApp instance, which will be implemented in our application-specific class. You can also see the Win32 initialization code as well as the message loop code that we created in the previous tutorials.

Another feature to notice is that there are two window procedures: WndProc and StaticWndProc. When we fill out the WNDCLASSEX.lpfnWndProc member of the window class, we need to specify a pointer to a function that has a specific function declaration:

LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )

When the program is compiled, another parameter is added to all non-static member functions, a this pointer, which changes the function declaration so it is incompatible with what is required for the lpfnWndProc member. Static functions on the other hand, do not receive this extra parameter, which is why we set the lpfnWndProc member to StaticWndProc. However, static functions can only access static member variables. Since all the other variables are non-static, we need a way to access them.

If you look at the CreateWindow function, the last parameter, lpParam, is defined as a “Pointer to a value to be passed to the window through the CREATESTRUCT structure passed in the lpParam parameter of the WM_CREATE message.” So we can store any type of pointer we want here, such as a this pointer. This pointer (no pun intended), which will be accessible during a WM_CREATE message, could be used to send messages meant for our application to a non-static window procedure, which would allow us to access the non-static data of our class, CFramework. But if this pointer is only accessible during a WM_CREATE message, we have to store it with the window when the WM_CREATE message arrives so that all future messages will find their way to our non-static window procedure. Luckily, all windows have a set of attributes that hold information about the window. One of these attributes is a user-defined attribute, which means we can store whatever we want at that spot. We can store our this pointer in the user-defined attribute using the SetWindowLongPtr function with the GWLP_USERDATA offset flag. With the this pointer now stored with our window, we can access it in all subsequent messages with the GetWindowLongPtr function. Once the pointer is retreived, we can cast the pointer to a CFramework pointer and access all the non-static functions of the class, such as the non-static window procedure. Using this, we route all messages to their corresponding non-static window procedure.

CFramework also has a method to toggle between windowed and fullscreen mode, which is always a good thing to have. With our framework squared away, we can move on to our application-specific class.

#ifndef CGAMEAPP_H
#define CGAMEAPP_H

#include “stdafx.h”
#include “CFramework.h”

class CGameApp : public CBaseApp
{
public:
   CGameApp();
   ~CGameApp() { Release(); }
   void SetFramework( CFramework* pFramework );
   BOOL Initialize();
   virtual void Release();
   virtual void OnCreateDevice( LPDIRECT3DDEVICE9 pDevice );
   virtual void OnResetDevice( LPDIRECT3DDEVICE9 pDevice );
   virtual void OnLostDevice();
   virtual void OnDestroyDevice();
   virtual void OnUpdateFrame( LPDIRECT3DDEVICE9 pDevice );
   virtual void OnRenderFrame( LPDIRECT3DDEVICE9 pDevice );
   virtual void OnKeyDown( WPARAM wParam );

private:
   CFramework* m_pFramework;
};
#endif

The CGameApp class inherits from the CBaseApp class that was shown above. This is necessary because CFramework will call the methods found in CBaseApp to interact with our program. CGameApp contains an instance of CFramework, which we will use to perform such operations as toggling to fullscreen mode and getting some state variables.

#include “..\include\stdafx.h”
#include “..\include\CGameApp.h”

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Default constructor
Parameters:
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
CGameApp::CGameApp() Toggle
{
   m_pFramework = NULL;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Clean up resources
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::Release() Toggle
{
   SAFE_RELEASE( m_pFramework );
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Sets the CFramework instnace of the application.
Parameters:
[in] pFramework – Pointer to a CFramework instance
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::SetFramework( CFramework* pFramework ) Toggle
{
   m_pFramework = pFramework;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Initialize application-specific resources and states here.
Returns: TRUE on success, FALSE on failure
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BOOL CGameApp::Initialize() Toggle
{
   return TRUE;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary:
This callback function will be called immediately after the Direct3D device has been created. This is
the best location to create D3DPOOL_MANAGED resources. Resources created here should be released in
the OnDestroyDevice callback.
Parameters:
[in] pDevice – Pointer to a DIRECT3DDEVICE9 instance
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnCreateDevice( LPDIRECT3DDEVICE9 pDevice ) Toggle
{
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary:
This callback function will be called immediately after the Direct3D device has been created. This is
the best location to create D3DPOOL_DEFAULT resources. Resources created here should be released in
the OnLostDevice callback.
Parameters:
[in] pDevice – Pointer to a DIRECT3DDEVICE9 instance
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnResetDevice( LPDIRECT3DDEVICE9 pDevice ) Toggle
{
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary:
This callback function will be called immediately after the Direct3D device has entered a lost state
and before IDirect3DDevice9::Reset is called. Resources created in the OnResetDevice callback should
be released here, which generally includes all D3DPOOL_DEFAULT resources.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnLostDevice() Toggle
{
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary:
This callback function will be called immediately after the Direct3D device has been destroyed.
Resources created in the OnCreateDevice callback should be released here, which generally includes
all D3DPOOL_MANAGED resources.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnDestroyDevice() Toggle
{
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Updates the current frame.
Parameters:
[in] pDevice – Pointer to a DIRECT3DDEVICE9 instance
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnUpdateFrame( LPDIRECT3DDEVICE9 pDevice ) Toggle
{
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Renders the current frame.
Parameters:
[in] pDevice – Pointer to a DIRECT3DDEVICE9 instance
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnRenderFrame( LPDIRECT3DDEVICE9 pDevice ) Toggle
{
   pDevice->Clear( 0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB( 0, 0, 200 ), 1.0f, 0 );
   pDevice->BeginScene();

   // Render scene here

   pDevice->EndScene();
   pDevice->Present( 0, 0, 0, 0 );
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Responds to key presses
Parameters:
[in] wParam – Key down argument
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnKeyDown( WPARAM wParam ) Toggle
{
   switch ( wParam )
   {
   case VK_ESCAPE:
       PostQuitMessage( 0 );
       break;
   case VK_F1:
       if ( m_pFramework != NULL )
       {
           m_pFramework->ToggleFullscreen();
       }
       break;
   }
}

CGameApp is the class that we will modify each time we create a new project. It contains all the resource creation and disposal methods mentioned earlier as well as the frame update and rendering methods. Most of the methods are empty because we’re not doing anything besides create the framework. However, you can see in the OnRenderFrame method how to render your geometry. You can also see in the OnKeyDown method how to respond to keypresses. In the above code, we’ll quit the program by pressing escape and we’ll toggle between windowed and fullscreen mode by pressing F1.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Application entry point
Parameters:
[in] hInstance – Application instance
[in] hPrevInstance – Junk
[in] lpCmdLine – Command line arguments
[in] nCmdShow – Window display flags
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow )
{
CGameApp* pApplication = new CGameApp();
CFramework* pFramework = new CFramework( (CBaseApp*)pApplication );

pApplication->SetFramework( pFramework );

// Initialize any application resources
if ( !pApplication->Initialize() )
{
return 0;
}

// Initialize the Framework
if ( !pFramework->Initialize( “Creating a Framework”, hInstance, 640, 480, TRUE ) )
{
return 0;
}

// Rock and roll
pFramework->Run();

// Clean up resources
SAFE_RELEASE( pApplication );

return 0;
}

With everything now ready to go, our WinMain function now looks like the above code. We just need to initialize CGameApp and CFramework, and then call CFramework::Run.

Three cheers for OOP.

2 thoughts on “Creating an Object-Oriented Framework”

Quak Quak May 30, 2011 at 11:37 am

Hello,
I really like the rest of your framework, but you forget to release the directx properly. There are a lot of unfreed memory. I suggest you to switch to the debug version of Direct3D 9.

Ian August 4, 2011 at 1:18 pm

Great tutorial! I’ve been looking for information on DirectX game architecture. This has been very helpful.

Comments are closed.

Leave A Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

<code> For inline code.
[sourcecode] For multiline code.