Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

DirectX 8 Template

0.00/5 (No votes)
26 Sep 2001 1  
A framework for writing apps using DirectX 8

Sample Image - dx8template.jpg

Introduction

This project was written using a same method as is recommended when using the Microsoft DirectX Framework, although there is no library to build in DirectX 8 as there was in DirectX 7, so the files have to be included in the project. These files are now located in the folder mssdk\samples\multimedia\common which includes both a src and a include directory. The best way to use these is to add the include file to your DevStudio path through the tools|options directories tab and the include the source files directly in your project.

One important note here is that DirectX 8 is much stricter about what it will let you do due to its greater hardware dependency. In DirectX 7 I was always able to run a debug app in full screen and then add break points with no problems at all. I could even run the code on a laptop with no 3D card present. This is possible with DirectX 8 but this project runs in a window at five frames a second so it would be hell to try and develop anything serious on it. This means that I'm going to have to recommend that all development takes place in a windowed environment and on a computer with a proper 3D card :(

This project is meant as an introduction to DirectX 8 and the classes in the project are intended to be reused with the minimum of fuss, while the demo code is clearly identified so as to be easily removable.

The Code

The code starts by inheriting from the framework library main class and simply adds a few get and set functions

class CNew3dApp : public CD3DApplication
"LEFT">{

The aim of this class is to override certain function in the base class and provide new implementations of some of the virtual functions. It also provides a few get and set functions to make life a bit easier. The functions that are overridden are,

	/// called first 

	virtual HRESULT OneTimeSceneInit(); 
	/// called next to initialize app objects 

	virtual HRESULT InitDeviceObjects(); 
	virtual HRESULT DeleteDeviceObjects(); 
	/// render the scene 

	virtual HRESULT Render(); 
	/// move the frame 

	virtual HRESULT FrameMove( FLOAT fMove ); 
	virtual HRESULT RestoreSurfaces(); 
	virtual HRESULT FinalCleanup();

These are all pretty obvious when you've been staring at DirectX for a while but I'll just give a brief description of each here.

The virtual function OneTimeSceneInit() Is the first function to be called that the code accesses. This is to allow the code to set up the objects that will be required in the program. This function is used by the program to switch on, none major options in this case turning on the Stats function which will place a screen size read out and the frame rate int the top left hand corner of the screen.

The virtual function InitDeviceObjects is where the main objects for the code are set up. This code is reentrant so if the code is only required to do something once then it is necessary to make sure yourself that it is only done once. I know it seems a strange thing to say when the last function was called OneTimeSceneInit but that function should not be used to change and major aspects such as the screen which is what I am doing here

	if( !bInitialScreen ) 
	{ 
		bInitialScreen = true; 
		if( FAILED( SetScreenSize( 800, 600 ) ) ) 
			return E_FAIL; 
	}

See setting the screen size below fro a description of what this function does.

The Font

If you open the 3dApp.h file you'll notice a private member in the class

	CD3DFont *m_pFont;

This is used in this project to show the stats that appear in the top left hand corner of the screen and if we trace it through the code we can see not only how it works but get a quick reprisal of the way that objects should be treated in the code.

In the constructor the font is initialized

	m_pFont = new CD3DFont( _T( "Arial" ), 12, D3DFONT_BOLD );
In the InitDeviceObjects function where we set up the 3D objects for our scene the font is initialized and associated with the current D3DDevice
	m_pFont->InitDeviceObjects( m_pd3dDevice );

In the DeleteDeviceObjects function the font calls its own DeleteDeviceObjects function. This is done when the device needs to be deleted when the project is closing down.

The next time we see it is in the render function when it draws the application frame and device stats. It then calls its own version of InvalidateObjects in the applications invalidate objects function, which is called when the current devices are changed for instance switching between windows and full screen or between the windowed application and the debugger. Again in RestoreDeviceObjects when the application once again has the main focus the font object calls its own version of the RestoreDeviceObjects function. Finally in the FinalCleanup function the font object is deleted.

While, hopefully, not being the most exciting class you'll ever come across it does provide a useful template for how to design classes to work within the framework provided by DirectX. The CD3DFont class itself has three functions that are of interest apart from its use of the DirectX Framework format. These are,

	HRESULT DrawText( FLOAT x, FLOAT y, DWORD dwColor, TCHAR* strText, DWORD dwFlags=0L );
    	HRESULT DrawTextScaled( FLOAT x, FLOAT y, FLOAT z, FLOAT fXScale, FLOAT fYScale, DWORD 						dwColor, TCHAR* strText, DWORD dwFlags=0L );
    	HRESULT Render3DText( TCHAR* strText, DWORD dwFlags=0L );

In the current code we only use the DrawText function but it might be interesting to look at what the others do in more detail at a later date.

Setting The Screen Size

The SetScreenSize function has had to be completely rewritten in the change to DirectX 8. This is due to the introduction of the notion of adapters and the changes to the way the bit depth works.

HRESULT SetScreenSize(int nHoriz, int nVert, 
                      BOOL bWindowed = FALSE, D3DFORMAT d3dFormat = D3DFMT_X8R8G8B8 );

The final parameter has been changed to a D3DFORMAT parameter which is a value to represent the different types of ( in the case of the render target ) pixel format. We will only be concerned with two values here the D3DFMT_X8R8G8B8 format which is a 32 bit RGB ( Red, Green, Blue )format where eight bits are reserved for each colour and the D3DFORMAT_R5G6B5 format which is a 16 bit RGB format. The code is setup to default to use the 32 bit value.

The rest of the SetScreenSize function is.

HRESULT hResult = S_OK;
int nWidth, nHeight;
// Get access to the newly selected adapter, device, and mode

DWORD dwDevice;
dwDevice  = m_Adapters[m_dwAdapter].dwCurrentDevice;
for( int i=0; i < ( int )m_Adapters[ m_dwAdapter ].devices[ dwDevice ].dwNumModes; i++ )
{
	if( m_Adapters[ m_dwAdapter ].devices[ dwDevice ].modes[ i ].Format == d3dFormat )
	{
		nWidth = ( int )m_Adapters[ m_dwAdapter ].devices[ dwDevice ].modes[ i ].Width;
		nHeight = ( int )m_Adapters[ m_dwAdapter ].devices[ dwDevice ].modes[ i ].Height;
		if( nWidth == nHoriz && nHeight == nVert )
		{
			m_Adapters[ m_dwAdapter ].devices[ dwDevice ].dwCurrentMode = i;
			m_Adapters[ m_dwAdapter ].devices[ dwDevice ].bWindowed = bWindowed;
			m_bWindowed = bWindowed; 
			break;
		}
	}
}
	
/// Release all scene objects that will be re-created for the new device

InvalidateDeviceObjects();
DeleteDeviceObjects();

/// Release display objects, so a new device can be created

if( m_pd3dDevice->Release() > 0L )
{
        return DisplayErrorMsg( D3DAPPERR_NONZEROREFCOUNT, MSGERR_APPMUSTEXIT );
}

/// Inform the display class of the change. It will internally

/// re-create valid surfaces, a d3ddevice, etc.


hResult = Initialize3DEnvironment();
if( FAILED( hResult ) )
        return DisplayErrorMsg( hResult, MSGERR_APPMUSTEXIT );

The function changes the screen size to the requested size and sets it full screen or not depending on the value passed in bWindowed, which must be TRUE if running in debug mode. The main difference is the use of the notion of Adapters which to you and me means video card, though it is possible for a video card to have more than one adapter if it has built in multi monitor support. The devices that each adapter supports are the video modes eg 640/480 in 16 bit, 800/600 in 32 bit, etc., that the card can run.

Loading And Rotation

The object displayed on screen is an .x file that is basically a list of vectors for the DirectX code to draw. This file does also contain texture information but for now just loading it and getting it to the screen correctly should be good enough. The object will be loaded into a D3DMesh class which is located in the header file d3dfile.h. This class will contain all the information needed for manipulating the x file that the code will load.

The mesh is loaded with the code,

	HRESULT hResult = m_pObjectMesh->Create( m_pd3dDevice, _T( "dolphin.X" ) );
	if( FAILED( hResult ) )
		return D3DAPPERR_MEDIANOTFOUND;

This code loads the dolphin.x file although there is nothing to prevent the name of any other file being added here it should be noted that at the moment the file has to be in the current directory and that due to the different sizes of the meshes it is possible that the object loaded will at first be too small. Big, or not visible at all which will mean that some changes to the view will have to be made which will be discussed in a while.

If you look through the code in the 3dapp.cpp file you can see that the control flow of the CD3DMesh is exactly the same as that for the font object in that in each of the overloaded functions a function is called by the m_pObjectMesh object that will keep the object synchronised with the rest of the application.

There are three things that need to be covered now. At the moment we have a computer screen and an object that we want to draw on it in 3d but how does the computer know where to draw the object. Simple we tell it.

    	D3DXMATRIX matWorld;
    	D3DXMATRIX matView;
    	D3DXMATRIX matProj;

	/// Set the Rotation speed

	D3DXMatrixRotationY( &matWorld, timeGetTime()/550.0f );
    	m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
	D3DXMatrixLookAtLH( &matView, &m_vecEye, &vecAt, &vecUp );
	m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
	D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, -1.0f );
	m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );

In the Render function we define three objects of type D3DMATRIX, one for the world one for the view and one for the projection. We then set these matrices the way we want them by calling the DirectX 8 helper functions and finally call SetTransform on the m_pd3dDevice ( which is the drawing device ).

The world matrix ( matWorld ) specifies the way the world will look, here the code tells it to set the matrix to rotate on the y access and because this is the Render function that gets called a number of times a second the code will tell it to rotate a bit more every single time through. Then when the SetTransform function is called any objects in the world will rotate on the y axis. Raising or lowering the value the divides the return of timeGetTime ( the number of seconds since windows started ) will slow down or speed up the rotation of the object respectively.

The view matrix ( matView ) specifies the way that the screen is looking in on the world. This calls the function D3DXMatrixLookAtLH which sets the way the world is looked at in a left handed world. Meaning that the x axis of the screen will be 0 at the leftmost and its highest value at its rightmost point. SetTransform is then called with the D3DTS_VIEW constant to tell DirectX that this is the viewing style that is wanted.

The projection matrix ( matProj ) deals with the way that objects are perceived and scaled with the 3d world created. This is why things shrink when they are moved further away.

Changing The View

When the code for this project is run the object first of all comes from the left hand of the screen and then moves backwards and forwards a bit while sliding slightly to left as it moves forward and slightly to the right as it moves backwards. Well it Doesn't. The object remains perfectly stationary throughout the entire program. The only code that deals with moving the object in this example is the World matrix code that rotates it a little bit every time the render function is called. In fact when the code starts the object is behind you. As you move back and to the left the object comes into view and centers itself in the middle of your screen and then when it appears to move towards you and to your left it is because you are moving forwards and to the right. This is all done in the FrameMove function with the code,

	static bool bForward = true;
	if( bForward )
	{
		if( m_vecEye.z > 1000.0f ) 
			bForward = false;

		m_vecEye.x += 3.0f;
		m_vecEye.z += 10.0f;
	}
	else
	{
		if( m_vecEye.z < 400.0f )
			bForward = true;
		m_vecEye.x -= 3.0f;
		m_vecEye.z -= 10.0f;
	}

The changes are made in the m_vecEye D3DXVECTOR3 ( Note when I say vector I mean a D3DXVECTOR3 which inherits from a Vector and adds some functionality ) which is a class member that stores the eye position of the view when matView matrix is created

	D3DXMatrixLookAtLH( &matView, &m_vecEye, &vecAt, &vecUp );

A vector contains the x, y and z coordinates for that position. With x being the position from the left of the center of the World position with a negative x moving to the left. y being the position from the base of the World position with a positive y value moving towards the top of the screen and a negative y value moving down towards the bottom of the screen and z being the depth. The vectors that are passed to the function above are the eye position which is the screen position within the World, which is currently the center of the screen. The at vector is the position which is the way that the screen is facing and the up position which tells the matrix where up is.

The whole effect is controlled by the value contained in m_vecEye.z value which when it goes above 1000 the view stops and starts to move forwards again until it reaches a value less than 400 where it stops and starts to go backwards again. These values should be experimented with to get the same effects when using different models.

Building The Sample

There should be no problems for any one building the sample as the cpp files are included in the zip file. This has been done due to me adding the line

	#include "stdafx.h" 

In all of the cpp files. This is because I prefer to include MFC and I probably will use some of it some point. It's not done for technical reasons but because this is how I work. You can remove them and use the standard cpp files if you prefer. The only possible problem you should have is the need to include the path to the common header files in your tools\options\directories section of developer studio. These can be found under mssdk\samples\multimedia\common\include.

The demo code will start at

	/// DEMO CODE

and end with

	/// END DEMO CODE

By removing the code between these lines you will be left with a template to start developing you own code.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here