Saturday, December 6, 2008

Maze Sample : Article 2 - D3D10Maze-Base

D3D10Maze-Base

More on DXUT

I want to spend another minute or two on DXUT, and list the files contained in a typical sample project and their basic purpose.

Image 1 below shows the DXUT subfolder in the Base project. These include DXUT, DXUTCamera, DXUTenum, DXUTgui, DXUTmisc, DXUTres, DXUTsettingsdlg, SDKmesh, and SDKmisc; .h header files and .cpp source files.


















Here is a quick description of what the file pairs ( .cpp, .h ) do for us:

DXUT: callbacks, device settings, etc

DXcamera: arcball controller and cameras

DXUTenum: device enumeration

DXUTgui: 3D UI elements

DXUTmisc: controllers, timers, etc

DXUTres: create resources from memory

DXUTsettingsdlg: device settings dialog support

SDKmesh: support for “SDKMesh” files

SDKmisc: resource cache, fonts, helpers

The samples make heavy use of macro V_RETURN, defined in DXUT.h, which wraps checking an HRESULT for success and failure. I am assuming all readers are familiar with COM and SAFE_RELEASE and SAFE_DELETE.

Now that we have defined the DXUT Framework as our basis of discussion and have grounding in what it gives and where to look, let’s get started with what I am calling the project baseline.
Lets cover the D3D10 callback methods, then WinMain, then the D3D9 callback methods.

Base Application Project

Globals-D3D10

ID3DX10Font* g_pFont10 = NULL;
ID3DX10Sprite* g_pSprite10 = NULL;
CDXUTTextHelper* g_pTxtHelper = NULL;


Callbacks-D3D10

We initialize the UI elements required to support the frame rate counter in OnD3D10CreateDevice as follows:

//------------------------------------------------------------------
// Create D3D10 resources that aren't dependant on the back buffer
//------------------------------------------------------------------
HRESULT CALLBACK OnD3D10CreateDevice( ID3D10Device* pd3dDevice,
const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc,
void* pUserContext )
{
HRESULT hr;

V_RETURN( D3DX10CreateFont( pd3dDevice, 15, 0, FW_BOLD, 1, FALSE,
DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH FF_DONTCARE,
L"Arial", &g_pFont10 ) );

V_RETURN( D3DX10CreateSprite( pd3dDevice, 512, &g_pSprite10 ) );

g_pTxtHelper = new CDXUTTextHelper( NULL, NULL, g_pFont10, g_pSprite10,

15 );

return S_OK;
}


That includes a font, a sprite or billboard to render the font on, and a TextHelper object that DXUT provides to ease dealing with text. CDXUTTextHelper is declared in SDKMisc.h and defined SDKMisc. Cpp.

We handle any device-lifetime objects in OnD3D10ResizedSwapChains with:

//------------------------------------------------------------------
// Create any D3D10 resources that depend on the back buffer
//------------------------------------------------------------------
HRESULT CALLBACK OnD3D10ResizedSwapChain(ID3D10Device* pd3dDevice,
IDXGISwapChain* pSwapChain,
const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc,
void* pUserContext )
{
HRESULT hr;

// Setup the camera's projection parameters
float fAspectRatio = pBackBufferSurfaceDesc->Width / ( FLOAT )pBackBufferSurfaceDesc->Height;

return S_OK;
}


For now, there is no code in OnFrameMove, it’s just a shell.

//------------------------------------------------------------------
// Handle updates to the scene regardless of which D3D API is used
//------------------------------------------------------------------
void CALLBACK OnFrameMove( double fTime, float fElapsedTime,

void* pUserContext )
{

}


That will change soon enough. And we should note the lack of a device parameter to OnFrameMove, the framework uses a single OnFrameMove for both D3D10 and D3D9 which has some implications I will cover later.

In OnD3D10FrameRender we render the frame rate using helper method RenderText10 which hides the use of the DXUTTextHelper object:

//------------------------------------------------------------------
// Render the help and statistics text
//------------------------------------------------------------------
void RenderText10()
{
g_pTxtHelper->Begin();
g_pTxtHelper->SetInsertionPos( 2, 0 );
g_pTxtHelper->SetForegroundColor( D3DXCOLOR( 1.0f, 1.0f, 0.0f, 1.0f ) );
g_pTxtHelper->DrawTextLine( DXUTGetFrameStats( true ) );
g_pTxtHelper->DrawTextLine( DXUTGetDeviceStats( ) );
g_pTxtHelper->End();
}


Note the use of DXUTGetFrameStats and DXUTGetDeviceStats to retrieve and display the frame rate and device information. And OnD3D10FrameRender uses that as follows:

//------------------------------------------------------------------
// Render the scene using the D3D10 device
//------------------------------------------------------------------
void CALLBACK OnD3D10FrameRender( ID3D10Device* pd3dDevice,
double fTime, float fElapsedTime,
void* pUserContext )
{
// Clear render target and the depth stencil
float ClearColor[4] = { 0.176f, 0.196f, 0.667f, 0.0f };
pd3dDevice->ClearRenderTargetView( DXUTGetD3D10RenderTargetView(), ClearColor );
pd3dDevice->ClearDepthStencilView( DXUTGetD3D10DepthStencilView(), D3D10_CLEAR_DEPTH,1.0,0 );

//render stats
RenderText10();
}

Here you see the clearing of the render target and depth/stencil buffers and then the render of the FPS text and Device text using helper method RenderText10.

Now its time to clean up. In this simplistic example, OnD3D10ReleasingSwapChain does nothing:

//------------------------------------------------------------------
// Release D3D10 resources created in OnD3D10ResizedSwapChain
//------------------------------------------------------------------
void CALLBACK OnD3D10ReleasingSwapChain( void* pUserContext )
{
}


In OnD3D10DestroyDevice the font, sprite, and texthelper objects are released:

//------------------------------------------------------------------
// Release D3D10 resources created in OnD3D10CreateDevice
//------------------------------------------------------------------
void CALLBACK OnD3D10DestroyDevice( void* pUserContext )
{
//HUD
SAFE_RELEASE( g_pFont10 );
SAFE_RELEASE( g_pSprite10 );
SAFE_DELETE( g_pTxtHelper );
}


Handlers

Next we have a message handler, MsgProc. It currently does nothing:

//------------------------------------------------------------
// Handle messages to the application
//------------------------------------------------------------
LRESULT CALLBACK MsgProc( HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam,
bool* pbNoFurtherProcessing,
void* pUserContext )

{
return 0;

}

There are two other handlers that may come up, OnKeyboard and OnMouse. They too are currently shells:

//--------------------------------------------------------------------------------------
// Handle key presses
//--------------------------------------------------------------------------------------
void CALLBACK OnKeyboard( UINT nChar, bool bKeyDown, bool bAltDown, void* pUserContext )
{
}


//--------------------------------------------------------------------------------------
// Handle mouse button presses
//--------------------------------------------------------------------------------------
void CALLBACK OnMouse( bool bLeftButtonDown, bool bRightButtonDown, bool bMiddleButtonDown,
bool bSideButton1Down, bool bSideButton2Down, int nMouseWheelDelta,
int xPos, int yPos, void* pUserContext )
{
}

WinMain

The next big component of the sample is the WinMain method:

//------------------------------------------------------------------
// Initialize everything and go into a render loop
//------------------------------------------------------------------
int WINAPI wWinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow )
{
// Enable run-time memory check for debug builds.
#if defined(DEBUG) defined(_DEBUG)
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF _CRTDBG_LEAK_CHECK_DF );
#endif

// DXUT will create and use the best device (either D3D9 or D3D10)
// that is available on the system depending on which D3D callbacks are set below

// Set general DXUT callbacks
DXUTSetCallbackFrameMove( OnFrameMove );
DXUTSetCallbackKeyboard( OnKeyboard );
DXUTSetCallbackMouse( OnMouse );
DXUTSetCallbackMsgProc( MsgProc );
DXUTSetCallbackDeviceChanging( ModifyDeviceSettings );
DXUTSetCallbackDeviceRemoved( OnDeviceRemoved );

// Set the D3D9 DXUT callbacks. Remove these sets if the app doesn't need to support D3D9
DXUTSetCallbackD3D9DeviceAcceptable( IsD3D9DeviceAcceptable );
DXUTSetCallbackD3D9DeviceCreated( OnD3D9CreateDevice );
DXUTSetCallbackD3D9DeviceReset( OnD3D9ResetDevice );
DXUTSetCallbackD3D9FrameRender( OnD3D9FrameRender );
DXUTSetCallbackD3D9DeviceLost( OnD3D9LostDevice );
DXUTSetCallbackD3D9DeviceDestroyed( OnD3D9DestroyDevice );

// Set the D3D10 DXUT callbacks. Remove these sets if the app doesn't need to support D3D10
DXUTSetCallbackD3D10DeviceAcceptable( IsD3D10DeviceAcceptable );
DXUTSetCallbackD3D10DeviceCreated( OnD3D10CreateDevice );
DXUTSetCallbackD3D10SwapChainResized( OnD3D10ResizedSwapChain );
DXUTSetCallbackD3D10FrameRender( OnD3D10FrameRender );
DXUTSetCallbackD3D10SwapChainReleasing( OnD3D10ReleasingSwapChain );
DXUTSetCallbackD3D10DeviceDestroyed( OnD3D10DestroyDevice );

// Perform any application-level initialization here

//start main processing
// Parse the command line, show msgboxes on error, no extra command line params
DXUTInit( true, true, NULL );
DXUTSetCursorSettings( true, true ); // Show the cursor and clip it when in full screen
DXUTCreateWindow( L"D3D10Maze-Base" );
DXUTCreateDevice( true, 800, 600 );
DXUTMainLoop(); // Enter into the DXUT render loop

// Perform any application-level cleanup here

return DXUTGetExitCode();
}


Now that we have that basic structure defined, let’s go back and define the D3D9 code ( globals and callbacks ) and finish off our discussions of this “skeleton” app.

Globals-D3D9

The D3D9 code is very similar but shares the DXUTTextHelper object and the global declarations reflect that:

ID3DXFont* g_pFont9 = NULL;
ID3DXSprite* g_pSprite9 = NULL;

extern CDXUTTextHelper* g_pTxtHelper;


Callbacks-D3D9

In the OnD3D9CreateDevice method we handle long-lived resource creation:

//------------------------------------------------------------------
// Create D3D9 resources (D3DPOOL_MANAGED)
// and aren't tied to the back buffer size
//------------------------------------------------------------------
HRESULT CALLBACK OnD3D9CreateDevice( IDirect3DDevice9* pd3dDevice,
const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
void* pUserContext )
{
HRESULT hr;

V_RETURN( D3DXCreateFont( pd3dDevice, 15, 0, FW_BOLD, 1, FALSE, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH FF_DONTCARE,
L"Arial", &g_pFont9 ) );

return S_OK;
}


For OnD3D9ResetDevice we create any resources that do not live through a device reset:

//------------------------------------------------------------------
// Create D3D9 resources (D3DPOOL_DEFAULT)
// or that are tied to the back buffer size
//------------------------------------------------------------------
HRESULT CALLBACK OnD3D9ResetDevice( IDirect3DDevice9* pd3dDevice,
const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
void* pUserContext )
{
HRESULT hr;

if( g_pFont9 ) V_RETURN( g_pFont9->OnResetDevice() );

V_RETURN( D3DXCreateSprite( pd3dDevice, &g_pSprite9 ) );
g_pTxtHelper = new CDXUTTextHelper( g_pFont9, g_pSprite9, NULL, NULL, 15 );

float fAspectRatio = pBackBufferSurfaceDesc->Width / ( FLOAT )pBackBufferSurfaceDesc->Height;

return S_OK;
}


Here we see a bit of difference in how D3D9 and D3D10 treat objects and object lifetimes. So we “refresh” the font by calling its OnResetDevice method and create the Sprite and CDXUTTextHelper here instead of OnD3D9CreateDevice. Remember, that, it’s going to come up again.

The DXUT framework shares one OnFrameMove for both D3D9 and D3D10. That means if there is any device specific time-based processing you need to perform it in the OnD3D9FrameRender method.

In OnD3D9FrameRender we render the frame rate using helper method RenderText9 which hides the use of the DXUTTextHelper object:

//------------------------------------------------------------------
// Render the help and statistics text
//------------------------------------------------------------------
void RenderText()
{
g_pTxtHelper->Begin();
g_pTxtHelper->SetInsertionPos( 2, 0 );
g_pTxtHelper->SetForegroundColor( D3DXCOLOR( 1.0f, 1.0f, 0.0f, 1.0f ) );
g_pTxtHelper->DrawTextLine( DXUTGetFrameStats( true ) );
g_pTxtHelper->DrawTextLine( DXUTGetDeviceStats( ) );
g_pTxtHelper->End();
}


OnD3D9FrameRender is similar to the D3D10 version with just the changes required by D3D9:

//------------------------------------------------------------------
// Render the scene using the D3D9 device
//------------------------------------------------------------------
void CALLBACK OnD3D9FrameRender( IDirect3DDevice9* pd3dDevice,
double fTime, float fElapsedTime,
void* pUserContext )
{
HRESULT hr;

// Clear the render target and the zbuffer
V( pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET D3DCLEAR_ZBUFFER,
D3DCOLOR_ARGB( 0, 45, 50, 170 ),
1.0f, 0 ) );

// Render the scene
if( SUCCEEDED( pd3dDevice->BeginScene() ) )
{
//HUD
RenderText();

V( pd3dDevice->EndScene() );
}
}


In OnD3D9LostDevice we call the font OnLostDevice method, release the sprite, and delete the texthelper:

//------------------------------------------------------------------
// Release D3D9 resources created in the OnD3D9ResetDevice callback
//------------------------------------------------------------------
void CALLBACK OnD3D9LostDevice( void* pUserContext )
{
//HUD
if( g_pFont9 ) g_pFont9->OnLostDevice();
SAFE_RELEASE( g_pSprite9 );
SAFE_DELETE( g_pTxtHelper );
}

Now we have one more D3D9 method, OnD3D9DestroyDevice:

//------------------------------------------------------------------
// Release D3D9 resources created in the OnD3D9CreateDevice callback
//------------------------------------------------------------------
void CALLBACK OnD3D9DestroyDevice( void* pUserContext )
{
//HUD
SAFE_RELEASE( g_pFont9 );
}


And we are done.

Here is a screenshot of the app running under D3D10:



And D3D9:



Here is a zip folder containing the project.

It has been built using VS 2008.

It has been run on both a desktop PC with a 9800GX2 and a laptop PC with an 8400M. It should work just fine on an ATI part, though, that is just the hardware they gave me and is not any implied endorsement.

Conclusion

There you have it, an app about nothing, to misquote Seinfeld.

This is an important step in this series nonetheless. This app handles entering and leaving full screen, resizing, and closing correctly. The FPS counter displays the frame rate. It responds to the forcexxx command line parameters. Plus the app auto-detects D3D10 or D3D9 support and picks the current runtime.

Even though it really appears to do nothing, it actually does quite a bit.

The next article will add the simple skybox. This one came out relatively quickly; I don’t want to promise a repeat of that so we will see how fast I can turn it around. Stay tuned!

PS:

I apologize for the indentation of the source, it appears Blogger doesnt like leading blanks, and is cutting my tabs or spaces.

3 comments:

said...

:-) Great post.
I think beginners will be happy to find those articles.

I'll wait to be at home to download this code, because mediafire is blocked by my IT Services @ Work.

said...

Foogoo,

thanks. do you have a better hosting service you could point me to?

Phil

said...

This does not really matter, i can wait to be at home to download your files.
If you really want to change, Skydrive works with our ITS security policies ;-)

This new blog template is far better than the previsous one.

Blogger is not really friendly with code quotes. I don't think you can obtain a better result than that.

Guillaume