🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Untitled

posted in DruinkJournal
Published April 29, 2009
Advertisement
Hooray, I seem to be able to access my web space, albeit very slowly. So here's tutorial #5:



Overview

As usual, this tutorial builds on the previous tutorial, and covers the ID3DXFont and ID3DXSprite classes, drawing an animating sprite, and a new timer class. Additionally, there's a handy debug assert macro, which can be used for performing some debug build sanity checks.
It's assumed that you've read and completely understood the previous articles, and only the differences in code are shown for brevity. The source code (Downloadable at the end of the page) is standalone and contains the code from the previous article as well as this.
As always, if you have any questions or comments about this tutorial, feel free to E-mail me.

The Timer Class

It's all well and good to use GetTickCount() for basic timing, but it's not particularly accurate, and doesn't allow us to pause and restart the timer. So, we'll start off by creating the FPSTimer class; a class used primarily for framerate independent movement. We'll start with the class header:
//=================================================================================================// FPSTimer.h - A simple frames per second timer / counter.// The FPS timer times from the end of one frame to the end of the next, and averages the times// for the last few times to smooth out judders.//=================================================================================================#ifndef FPSTIMER_H_INCLUDED#define FPSTIMER_H_INCLUDED#include class FPSTimer{public:	FPSTimer();	~FPSTimer();	// [Re]start the timer	void Start();	bool IsStarted() const { return m_dwStartTime!=0; }	// Pause / unpause timer	void Pause();	void Unpause();	bool IsPaused() const { return m_dwPauseTime!=0; }	// Mark the end of each frame	void EndFrame();	// Get the current frame time	float GetFrameTime() const { return m_fAveragedFrameTime; }	// Get the current time in milliseconds	DWORD GetCurrTimeMs() const;private:	static const size_t ms_nAveragedFrames = 15;	DWORD m_dwStartTime;	DWORD m_dwFrames;	DWORD m_dwTotalFrames;	DWORD m_dwPauseTime;	float m_fFrameTimes[ms_nAveragedFrames];	size_t m_nValidTimes;	size_t m_nNextTime;	float m_fAveragedFrameTime;};#endif // FPSTIMER_H_INCLUDED
The functions should all be self explanatory; there's a function to start the timer and check if it's started, functions for pausing and unpausing, the EndFrame function, to notify the timer that a frame has elapsed, and a few accessors.
The timer works by averaging the time taken to render the last 15 frames, and using that as the frame time, which can be accessed with the GetFrameTime function. The timer can be started, but not stopped. This is because there's (currently) no need to stop the timer, just to pause it and you can always start the timer again to reset it, there's no need to stop it first. The only reason the Start function exists is to avoid the need to pause the timer as soon as it is created (So as to not measure the startup time as part of the first frame).
We'll move on to the top of the source file:
//=================================================================================================// FPSTimer.cpp - A simple frames per second timer / counter.//=================================================================================================#include "FPSTimer.h"#include // Link to winmm.lib for the time*() functions#pragma comment(lib, "winmm.lib")// If a frame takes longer than this, clamp it to this timestatic const DWORD s_dwMaxFrameTimeInMs = 100;//=================================================================================================FPSTimer::FPSTimer() :	m_dwStartTime(0),	m_dwFrames(0),	m_dwTotalFrames(0),	m_dwPauseTime(0),	m_nValidTimes(0),	m_nNextTime(0),	m_fAveragedFrameTime(0.0f){	memset(m_fFrameTimes, 0, sizeof(m_fFrameTimes));	// Set highest resolution timer possible	TIMECAPS caps;	timeGetDevCaps(∩︀, sizeof(caps));	timeBeginPeriod(caps.wPeriodMin);}FPSTimer::~FPSTimer(){}
I've chosen to use the Windows multimedia library for timing, since it provides accurate values (Usually to the nearest millisecond), it's quick to read the time, and it's stable. To use the multimedia library, we have to include mmsystem.h, and link to winmm.lib. We also define an upper limit on how long a frame should take, so that a single long frame doesn't mess things up. This is useful for debugging, since if you break to the debugger (By hitting a breakpoint for instance), you'll end up with that one frame taking several seconds or longer.
The constructor initialises all of the variables, as is always good practice, and then calls timeGetDevCaps to get the capabilities of the timer. This is then used to set the timer resolution to the highest supported, with the timeBeginPeriod function.
Next up is the Start, Pause and Unpause functions:
void FPSTimer::Start(){	m_dwStartTime = timeGetTime();	m_dwFrames = 0;	m_dwTotalFrames = 0;	m_dwPauseTime = 0;	m_nValidTimes = 0;	m_nNextTime = 0;	memset(m_fFrameTimes, 0, sizeof(m_fFrameTimes));	m_fAveragedFrameTime = 0.0f;}//=================================================================================================void FPSTimer::Pause(){	m_dwPauseTime = timeGetTime();}void FPSTimer::Unpause(){	// Adjust sample start time by time spent paused	DWORD dwCurrTime = timeGetTime();	DWORD dwPausedTime = dwCurrTime - m_dwPauseTime;	m_dwStartTime += dwPausedTime;	m_dwPauseTime = 0;}
Start() uses timeGetTime - the multimedia libraries version of GetTickCount to record the current time. It then resets the timer state so that a timer can be reset by calling Start() again.
Likewise, Pause() is very lightweight, and only records the time at which it's called. When Unpause() is called, it measures how long the timer has been paused, and uses that to artificially offset the start time. This has the effect of removing the pause delay in the frame time calculations.
The GetCurrTimeMs() function is handy to have, and lets us remove our reliance on GetTickCount completely. All it does is return the value of timeGetTime:
DWORD FPSTimer::GetCurrTimeMs() const{	return timeGetTime();}
The real work for the timer happens in the EndFrame() function. This function should be called by the application code at the end of every frame. Here's that code:
void FPSTimer::EndFrame(){	// If we're paused, don't update frame time	if(IsPaused())		return;	// Increase the number of frames in the sample	++m_dwFrames;	++m_dwTotalFrames;	// If this frame took less than 1ms, we can't measure it, so we have to run multiple frames	// and find how many we can do in a >0ms.	DWORD dwCurrTime = timeGetTime();	DWORD dwElapsed = dwCurrTime - m_dwStartTime;	if(dwElapsed == 0)		return;	// If the frame took exceptionally long, clamp it to a sane value. This also helps to	// smooth out judders slightly	if(dwElapsed > s_dwMaxFrameTimeInMs)		dwElapsed = s_dwMaxFrameTimeInMs;	// Get frame time, and reset number of frames in this sample	float fFrameTime = dwElapsed / (m_dwFrames * 1000.0f);	m_dwFrames = 0;	// Store frame time in averaged array	m_fFrameTimes[m_nNextTime] = fFrameTime;	++m_nNextTime;	if(m_nNextTime >= ms_nAveragedFrames)		m_nNextTime = 0;	if(m_nNextTime > m_nValidTimes)		m_nValidTimes = m_nNextTime;	// Calculate average frame time	m_fAveragedFrameTime = 0.0f;	for(size_t i=0; i		m_fAveragedFrameTime += m_fFrameTimes;	m_fAveragedFrameTime /= (float)m_nValidTimes;	// Reset frame start time	m_dwStartTime = dwCurrTime;}
The first thing the function does is check if the timer is paused, and bails out if it is. Obviously we don't want to time anything while the timer is paused. Next, we increase the number of frames that have been timed, and check how much time has elapsed since this point in the last frame. If a the elapsed time is less than one millisecond, then we can't measure it - we know it took less than a millisecond, but we don't know if it took 0.9ms or 0.0001ms. In this case, we keep timing until we get a value of at least one millisecond, and then divide the elapsed time by the number of frames we timed.
Once we have the frame time for this frame, we add it to the array of frame times. We use this as a circular buffer; when we get to the end of the buffer we start overwriting the first entries, so we remove the old values. Finally, we recalculate the new averaged frame time, which is the value that will be returned to the application when requested.

The Assert Macro

We might as well get this out of the way just now, and I figured that now was a good time to include it in the project. An assert is a function used for detecting programmer errors, which only does anything in a debug build. If you have a function which takes a percentage value for instance, it probably only makes sense for that function to accept values between 0 and 100. Rather than just assuming that the data will be valid, and causing errors or crashes if bad data is fed in, it's a good idea to stick an assert in, so that you can catch the error. In a release build, the assert should vanish and the function should just take the bad data. If a function is likely to fail in a release build, then an assert is not a good idea, since it'll fail silently. For instance, you shouldn't check that IDirect3D9::CreateDevice succeeds with an assert, because if it fails in a release build you'll want to know about it!
Here's what the Assert macro looks like:
// A few things about this assert:// First, we need to use the Visual C++ specific __pragma directive to disable warning 4127, which// is "warning C4127: conditional expression is constant", and then reset it to its default state// after the assert macro is expanded.//// Second, the statement is wrapped in a do { ... } while(0) loop, which will always mean the loop// runs once. This is to allow you to embed asserts in single line if() ... else() statements. If// the dummy loop was not there, then the if statement in the macro would interfere with the if// statement in the calling code. Try removing the "do {" and "} while(0)" lines and then compiling// the following code and you'll get a somewhat confusing "error C2181: illegal else without// matching if" error://    if(m_pDevice)//       Assert(false, "Device exists");//    else//       Assert(false, "Device doesn't exist");//// Thirdly, you can add your own printf()-style parameters to show up in the assert message. For// example://    Assert(!m_pDevice, "Device pointer is valid! Address = 0x%08x, Num = %d", m_pDevice, 42);#ifdef _DEBUG#	define Assert(exp, msg, ...)		__pragma(warning(disable:4127))		do {			if (!(exp))				DbgAssertFailed(#exp, __FILE__, __LINE__, __FUNCTION__, msg, __VA_ARGS__);		} while(0)		__pragma(warning(default:4127))#else#	define Assert(exp, msg, ...) ((void)0)#endif

GDNet edit: Backslashes removed at the ends of lines in the macro since the source tags screw them up.
I won't say much, since the comment pretty much says it all. The code compiles to nothing in a non-debug build (The ((void)0) statement is just "nothing"), and in debug builds when the assert statement fails, we call the DbgAssertFailed function with the expression that failed, current file, line number and function, a message, and optional printf style argument list. So, let's look at what the DbgAssertFailed function does:
void DbgAssertFailed(const char* szExp, const char* szFile, unsigned int nLine,	const char* szFunc, const char* szMsg, ...){	// Trim path from filename	char szBuff[1024];	if(!szFile) szFile = "??";	const char* szRealFile = strrchr(szFile,'\\');	if(!szRealFile) szRealFile = szFile;	else szRealFile++;	// Build vaargs	va_list arg;	va_start(arg, szMsg);	_vsnprintf_s(szBuff, sizeof(szBuff), sizeof(szBuff), szMsg, arg);	va_end(arg);	// Compose string	char szOutput[1536];	sprintf_s(szOutput, sizeof(szOutput), "Assertion failed:\n"		"%s\n"		"\n"		"File: %s:%d (%s)\n"		"Expression:\n"		"%s\n", szBuff, szRealFile, nLine, szFunc, szExp);	// Log assert then display messagebox	OutputDebugStringA(szOutput);	// Only allow one message box to be active at once - if an assert is put in a window message	// loop or other callback, we might get multiple message boxes.	static bool bMessageBoxActive = false;	if(!bMessageBoxActive)	{		strcat_s(szOutput, sizeof(szOutput), "\nDebug?\n\n");		bMessageBoxActive = true;		if(MessageBoxA(NULL, szOutput, "Assertion failed!", MB_YESNO|MB_ICONEXCLAMATION) == IDYES)		{			__debugbreak();		}		bMessageBoxActive = false;	}}
The source file name passed in by the __FILE__ macro is usually a full path rather than a single filename. To tidy the message up, we strip off everything up to the last backslash, leaving us with the filename. Once we've got the filename, we use the _vsnprintf_s function to build the message buffer from the printf style arguments, then build the whole message together, including the printf style arguments, filename, line number, function, and the expression that failed. Finally, we call the non-Unicode version of OutputDebugString explicitly (Since the arguments are only non-Unicode strings) to output the message to the debuggers output window, and show a Windows message box with the message too. We need to do a little bit of messing around here, since the function could be recursive if the assert is in a window message loop, since MessageBox enters its own little message loop internally, which will cause messages for the window to be processed. If the user selects "Yes" from the message box, we call the __debugbreak function to force a break to the debugger.

The Sprite Class

Ideally we want to be able to support more than one sprite on the screen at one time, even if we don't need it right now. As such, we shouldn't stick all of the variables for the sprite in the main D3DWindow class, since that'd mean we'd have to duplicate the variables or turn them into an array to support more than one sprite; which is pretty ugly. To get around this, we'll wrap all of the sprite-related variables in a class called - oddly enough - Sprite.
The sprite class should keep a hold of an IDirect3DTexture9 interface, should manage animation, should keep track of the sprites velocity and position, and should maintain a bounding rectangle for the sprite to bounce off of. Let's see what the header looks like for this class:
//=================================================================================================// Sprite.h - A simple class to encapsulate a sprite. A sprite uses a texture, but does not own// it. The sprite will increase the reference count on the texture to make sure it is not released// when the sprite is using it.// Sprites are animated, and will run through "frames" on the texture in a left-to-right,// top-to-bottom manner.// Sprites obey the Rule of Three, and can be copied, assigned, stored by value in a std::vector,// and so on.//=================================================================================================#ifndef SPRITE_H_INCLUDED#define SPRITE_H_INCLUDED#include class Sprite{public:	Sprite();	Sprite(LPDIRECT3DTEXTURE9 pTexture, const D3DXVECTOR2& vFrameSize, float fSecondsPerFrame);	Sprite(const Sprite& rhs);	~Sprite();	Sprite& operator=(const Sprite& rhs);	// Release and reset this sprite	void Reset();	// Move this sprite instantly	void SetPosition(const D3DXVECTOR2& vPos);	// Set the velocity of this sprite in pixels per second	void SetVelocity(const D3DXVECTOR2& vVel);	// Set the bounding rectangle for this sprite. It will bounce when it hits the edge	void SetBoundingRect(const RECT& rc);	// Set the texture and animation frame size, speed and number of frames. If	// nNumFrames is -1, then the number of frames is calculated from the texture	// size.	void SetTexture(LPDIRECT3DTEXTURE9 pTexture, const D3DXVECTOR2& vFrameSize,		float fSecondsPerFrame, int nNumFrames=-1);	// Update this sprite	void Tick(float fElapsedTime);	// Draw the sprite using the passed in sprite renderer	void Draw(ID3DXSprite* pRenderer);private:	// Per-frame information	D3DXVECTOR2 m_vPos;	D3DXVECTOR2 m_vVel;	UINT m_nFrame;	float m_fElapsedFrameTime;	// Stored constructor parameters	LPDIRECT3DTEXTURE9 m_pTexture;	D3DXVECTOR2 m_vFrameSize;	UINT m_nFramesX;	UINT m_nFramesTotal;	float m_fSecondsPerFrame;	RECT m_rcBounds;};#endif // SPRITE_H_INCLUDED
We start with the default constructor, a constructor taking some parameters (which we'll cover in just a moment), a copy constructor and operator=() and a destructor. The copy constructor and operator=() makes it possible for us to copy the sprite around, which also makes it possible to store by value (rather than by pointer) in a STL container like a std::vector. Following that we have a function to reset the sprite, and some mutators to set the sprites position, velocity, bounding rectangle and texture (With associated animation information). Finally we have the Tick function which will be called every frame by the owner of the sprite to update its animation and position, and the Draw function to draw the sprite using the specified ID3DXSprite interface (Which we'll also cover shortly).
Now we know what the header looks like, on to the source code. I'm not actually going to paste the source code for the constructors, destructors, operator=() and some of the Set*() functions. It's all very similar, and if you're interested you can take a look at the source code provided. I'll go over the SetTexture function though (Which is called by the constructor taking parameters):
void Sprite::SetTexture(LPDIRECT3DTEXTURE9 pTexture, const D3DXVECTOR2& vFrameSize,	float fSecondsPerFrame, int nNumFrames){	// Release any existing texture	if(m_pTexture)		m_pTexture->Release();	m_pTexture = NULL;	m_nFrame = 0;	m_fElapsedFrameTime = 0.0f;	m_nFramesX = 0;	m_vFrameSize.x = 0.0f;	m_vFrameSize.y = 0.0f;	m_nFramesTotal = 0;	if(pTexture)	{		// Get the texture size		D3DSURFACE_DESC desc;		HRESULT hResult = pTexture->GetLevelDesc(0, &desc);		if(SUCCEEDED(hResult))		{			m_vFrameSize = vFrameSize;			m_nFramesX = (UINT)(desc.Width / vFrameSize.x);			UINT nFramesY = (UINT)(desc.Height / vFrameSize.y);			m_nFramesTotal = m_nFramesX * nFramesY;		}		if(nNumFrames >= 0)			m_nFramesTotal = nNumFrames;		// Increase the reference count on the texture so it doesn't get Release()d when we're		// still using it.		m_pTexture = pTexture;		pTexture->AddRef();		m_fSecondsPerFrame = fSecondsPerFrame;	}}
Since the sprite should keep a hold of the texture it's using, we need to make sure we correctly AddRef and Release it. Up until now, it's been safe to assume that once you create a D3D object you own it, and you free it by calling its Release function. That's somewhat true. In reality, each COM object (D3D is built on top of COM) keeps a reference count internally. When you create an object, it is given a reference count of 1, and when you call Release on an object, its reference count is decreased by 1. When the reference count reaches zero, the object is destroyed (Internally that's often done by a call to delete this;). There's also the AddRef function, which increases the reference count of an object by 1. The idea is that when you get a COM object you didn't create, and you want to hold on to it, you increase its reference count. That way, if the other code decides that it's done with the object and calls Release on it, the object just lowers its reference count to 1 and is not destroyed (Which is good, since we still have a pointer to it!). The D3D device actually does this internally - when you call a function like SetVertexBuffer, the device increases the reference count on the object you pass it, so it doesn't end up with a bad pointer if you then decide to release it.
So anyway, we start the function by releasing any texture we currently hold, and then reset all of our variables. If we have a valid texture pointer passed (This could be a good place for an assert if you don't want to allow sprites to not have a texture), we call IDirect3DTexture9::GetLevelDesc. This function gets the description for a level in the texture (Remember that a texture is made up of multiple mip-map levels), which includes its width and height, along with some other information. We're particularly interested in the width and height of the texture. Since the SetTexture function takes in the size of one frame of animation, we need to know the dimensions of the texture to know how many frames of animation are on the texture. We record the number of frames horizontally and the total number of frames, which will give us enough information to pick out the region of the texture containing any frame number easily.
If a valid number of frames were specified, then we override the frame count we determined from the texture size, and use that instead. Finally, we store the texture and animation speed, and call AddRef on the texture to keep hold of it.

The Tick function

Now we know how to initialise a sprite, we can look at how to update it. The sprite's Tick function is responsible for updating its animation and its position. Let's look at the start of the Tick function:
void Sprite::Tick(float fElapsedTime){	// Update animation	if(m_fSecondsPerFrame > 0.00001f)	{		m_fElapsedFrameTime += fElapsedTime;		while(m_fElapsedFrameTime >= m_fSecondsPerFrame)		{			++m_nFrame;			m_fElapsedFrameTime -= m_fSecondsPerFrame;			if(m_nFrame >= m_nFramesTotal)				m_nFrame = 0;		}	}
First we check if the sprite's animation time is reasonable - if it was set to zero, then we'd end up infinitely changing the animation frame. If the animation time is reasonable, we update the elapsed frame time, and when the time exceeds m_fSecondsPerFrame, we change to the next frame, looping back to zero when we overflow. That'll keep the m_nFrame variable ticking over at the correct speed, updating the current animation frame. The while loop is required for when the animation is running faster than the frame rate - if we only advanced at most once per frame, then we could end up with animations feeling laggy when the game slows down, which is best to avoid wherever possible.
With the animation out of the way, we can look at updating the sprite's position:
	// Update position	m_vPos += m_vVel * fElapsedTime;	// Handle bounding volume rebound	if(!IsRectEmpty(&m_rcBounds))	{		int nWidth = m_rcBounds.right - m_rcBounds.left;		int nHeight = m_rcBounds.bottom - m_rcBounds.top;		while(m_vPos.x+m_vFrameSize.x > m_rcBounds.right || m_vPos.x < m_rcBounds.left)		{			float xMax = m_vPos.x+m_vFrameSize.x;			if(xMax > m_rcBounds.right)			{				m_vPos.x = (float)m_rcBounds.right - (xMax - nWidth) - m_vFrameSize.x;				m_vVel.x = -m_vVel.x;			}			else if(m_vPos.x < m_rcBounds.left)			{				m_vPos.x = (float)m_rcBounds.left;				m_vVel.x = -m_vVel.x;			}		}		while(m_vPos.y+m_vFrameSize.y > m_rcBounds.bottom || m_vPos.y < m_rcBounds.top)		{			float yMax = m_vPos.y+m_vFrameSize.y;			if(yMax > m_rcBounds.bottom)			{				m_vPos.y = (float)m_rcBounds.bottom - (yMax - nHeight) - m_vFrameSize.y;				m_vVel.y = -m_vVel.y;			}			else if(m_vPos.y < m_rcBounds.top)			{				m_vPos.y = (float)m_rcBounds.top;				m_vVel.y = -m_vVel.y;			}		}	}}
The first line is your standard time-dependant movement - we update the position of the sprite based on the sprite's velocity and the elapsed time, you might recognise that from high school physics; distance = speed x time.
After we've updated the position of the sprite, we perform collision detection. First, we make sure that the sprite's bounding box isn't empty. If it is, then we'd end up with an infinite loop like with the animation update. The collision detection is pretty self explanatory, we test the X axis and then the Y axis independently, and if the sprite has moved outside the bounding box, we correct it as if it had bounced off the side.

The Draw function

The sprite's Tick function is used to update the logic, and the sprite's Draw function is used to actually draw it. The draw function is pretty short, so let's look at it here:
void Sprite::Draw(ID3DXSprite* pRenderer){	if(!m_pTexture)		return;	// Determine the X,Y coordinates of the frame to render	UINT nFrameX = m_nFrame % m_nFramesX;	UINT nFrameY = m_nFrame / m_nFramesX;	// Get the rectangular region of the texture for this frame	RECT rcTexture;	rcTexture.left = (long)(nFrameX * m_vFrameSize.x);	rcTexture.top = (long)(nFrameY * m_vFrameSize.y);	rcTexture.right = (long)(rcTexture.left + m_vFrameSize.x) - 1;	rcTexture.bottom = (long)(rcTexture.top + m_vFrameSize.y) - 1;	// Draw this sprite	D3DXVECTOR3 vPos = D3DXVECTOR3(m_vPos.x, m_vPos.y, 0.0f);	pRenderer->Draw(m_pTexture, &rcTexture, NULL, &vPos, D3DCOLOR_XRGB(255, 255, 255));}
There's not that much to it really - first, if there's no texture loaded then this sprite isn't valid, so we bail out. Otherwise, we determine the X and Y cell position for the animation frame, and convert that to a rectangle on the texture. Finally, we draw the sprite with the ID3DXSprite::Draw function.

ID3DXSprite

The ID3DXSprite classes name is a little misleading. It doesn't really represent a single sprite, it represents an interface for rendering any number of sprites. There's a bit of misleading information on the Internet, claiming that ID3DXSprite is slow and you're better off writing your own. That's completely untrue. There are only two reasons you shouldn't be using ID3DXSprite. First, if you can't - your engine is cross-platform and you can't use ID3DXSprite, for example it's running on the Xbox 360 where there's no ID3DXSprite class. Second, if you require features that ID3DXSprite doesn't provide.
ID3DXSprite is plenty fast, and you'll have difficulty writing anything faster. In fact, the only way you'd manage to write anything faster is if you're able to batch the quads for drawing sprites with triangles from other models (And if you don't know what I mean by that, you're not going to manage to do it :-)). Internally, ID3DXSprite contains a static index buffer and a dynamic vertex buffer (Dynamic resources will be covered in a later article). When you call ID3DXSprite::Draw, the quad is added to the vertex buffer (The indices will always be the same for any vertices). When you call ID3DXSprite::End or ID3DXSprite::Flush, the vertex buffer is unlocked (And re-locked in the case of Flush), and IDirect3DDevice9::DrawIndexedPrimitive is called to actually do the drawing.
There are quite a lot of state changes that take place so that ID3DXSprite is likely to work in most if not all device setups, but they're all put in a state block (Which may be covered in a later tutorial), so the performance impact is minimal.

The New D3DWindow

Now that we've covered all of the utility classes and functions, we can look at the changes to the main window.
First up, we've got rid of the m_dwStartTime variable - now that we have a real timer, we don't need this variable. The m_timer variable is the Timer class instance, and we've now got a rotation angle stored as a member variable too (Used for the spinning cube). The timer is started at the end of the D3DWindow::Create function, as the last thing before entering the main loop.
To use the timer properly, we need to call Timer::EndFrame at the end of each frame, so there's a call to that at the end of the D3DWindow::Tick function. Note that this should be called here and not at the end of D3DWindow::DrawFrame, since multiple frames could be drawn in one update (When moving the window for instance), and we don't want the logic to update in that time - when you draw the window, we don't want that to cause the game logic to change. We also have two new functions called from WinMain, D3DWindow::BeginMessagePump and D3DWindow::EndMessagePump. These functions pause and unpause the timer respectively. This is so that when the main window is moved or resized, we don't try to update the game logic. We also put a bit of a sanity check in the D3DWindow::WndProc function, so we make sure that the WinMain function calls those two functions correctly.

Creating the sprite and font

Since we're now measuring the frame rate with the timer, we need to make sure that we're not limiting the speed of the app by the refresh rate. Up until now, we've been using D3DPRESENT_INTERVAL_DEFAULT for the presentation interval. That means "Wait for a vertical blank before presenting the backbuffer", which limits the frame rate to the refresh rate of the monitor. To measure maximum speed, we want to just present the backbuffer instantly, not waiting for a vertical blank. That can cause "tearing", where the top half of the screen shows the last frame, and the bottom half shows the new frame (Or worse if the app is running particularly fast). To disable v-sync, we set the PresentationInterval member of the D3DPRESENT_PARAMETERS to D3DPRESENT_INTERVAL_IMMEDIATE.

The font
We want to actually display the frame rate on the screen, and to do that we need to render text (Nit-pick: Oh ok, you could render coloured bars or something to represent frame rate). The ID3DXFont class is a helper class similar to ID3DXSprite which is used for rendering text. Let's look at how we create a font, in the D3DWindow::InitD3DDevice class.
// Create the fontHDC hDC = GetDC(m_hWnd);int nHeight = -MulDiv(s_nFontSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);ReleaseDC(m_hWnd, hDC);hResult = D3DXCreateFont(m_pDevice, nHeight, 0, FW_NORMAL, 0, FALSE, DEFAULT_CHARSET,	OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, s_szFontFace,	&m_pFont);if(FAILED(hResult)){	// Error handling	return false;}
The first three lines are a bit confusing. ID3DXFont, like GDI fonts have a size measured in "logical device units", not point size. We need to convert to device units from point size. To do that, we use this formula, which requires an HDC. We don't have access to the device context that ID3DXFont uses internally, but we can get the device context for our window, which is good enough.
Once we have the font size in logical units, we can call D3DXCreateFont to actually create the font. There's not really much to say here. There are a lot of parameters, but they're all documented.

The sprite

First of all, we need to create the ID3DXSprite interface. That's done in a similar way to the font, by a call to D3DXCreateSprite. This function is a lot simpler, with only two parameters - the device to render sprites to, and the output sprite pointer.
After that, we load the texture for the sprite with a call to D3DXCreateTextureFromFileEx, and set the Sprite instance up with the texture, a velocity and bounding rectangle. Here's the code for all that:
// Create our sprite rendererhResult = D3DXCreateSprite(m_pDevice, &m_pSprite);if(FAILED(hResult)){	// Error handling	return false;}// Load the texture for the sprite and then give up control of it - the sprite now owns it.LPDIRECT3DTEXTURE9 pSpriteTexture;hResult = D3DXCreateTextureFromFileEx(m_pDevice, L"Sprite.png", D3DX_DEFAULT, D3DX_DEFAULT,	D3DX_DEFAULT, 0, fmtTextureSelected, D3DPOOL_MANAGED, D3DX_FILTER_NONE, D3DX_DEFAULT,	0, NULL, NULL, &pSpriteTexture);if(FAILED(hResult)){	// Error handling	return false;}// Setup sprite texture, velocity and bounding rectRECT rcSpriteBounds;rcSpriteBounds.top = rcSpriteBounds.left = 0;rcSpriteBounds.right = sizeBackBuffer.cx;rcSpriteBounds.bottom = sizeBackBuffer.cy;m_sprite.SetTexture(pSpriteTexture, D3DXVECTOR2(64.0f, 64.0f), 0.05f, 10);m_sprite.SetVelocity(D3DXVECTOR2(sizeBackBuffer.cx / 2.0f, sizeBackBuffer.cy/2.0f));m_sprite.SetBoundingRect(rcSpriteBounds);pSpriteTexture->Release();

Rendering

To improve performance, ID3DXSprite expects you to make a call to ID3DXSprite::Begin to say that you're starting to render sprites. There are a few parameters you can pass to this function, a full list can be found at the documentation for D3DXSPRITE. In our case, we want alpha blending and we want to sort by texture for best performance, so we pass D3DXSPRITE_ALPHABLEND | D3DXSPRITE_SORT_TEXTURE as the flags.
ID3DXFont also renders the text you tell it using sprites. You don't have to specify a sprite interface for it to use, but if you don't then every time you draw text, it'll internally call ID3DXSprite::Begin and ID3DXSprite::End, which causes a draw call for each chunk of text, which is bad for performance. We have a sprite interface already, so we can just pass that in to ID3DXFont::DrawText.
Let's see what all that code looks like:
// Begin sprite renderinghResult = m_pSprite->Begin(D3DXSPRITE_ALPHABLEND | D3DXSPRITE_SORT_TEXTURE);if(SUCCEEDED(hResult)){	// Draw our sprite	m_sprite.Draw(m_pSprite);	// Draw the current FPS	if(m_timer.GetFrameTime() > 0.0f)	{		m_pFont->DrawText(m_pSprite, m_strFPS.c_str(), -1, NULL, DT_LEFT | DT_WORDBREAK,			D3DCOLOR_XRGB(255, 255, 255));	}	// End sprite rendering	m_pSprite->End();}
There's nothing too exciting there; we begin sprite rendering, draw our animating sprite, and then see if the frame time is > 0ms. If it is, then we've drawn at least one frame, so we have a usable FPS count, which we then feed into ID3DXFont::DrawText. Finally, we call ID3DXSprite::End to signal that we're done rendering sprites.
The parameters to ID3DXFont::DrawText are similar to the GDI DrawText function, since that's how it's implemented internally anyway. One addition is the colour parameter which is the colour of text to render (obviously).

Updating the Sprite and FPS

Nearly done! The next function to look at is the UpdateLogic() function, which is used to call the sprite's Tick() function, update the current FPS text, and so on. Here it is:
void D3DWindow::UpdateLogic(){	// Update FPS counter output every so often	DWORD dwCurrTime = m_timer.GetCurrTimeMs();	if(dwCurrTime - m_dwLastFPSUpdateTime >= s_dwFrameRateUpdatePeriod &&		m_timer.GetFrameTime() > 0)	{		std::wstringstream str;		str << L"FPS: " << 1.0f / m_timer.GetFrameTime();		m_strFPS = str.str();		m_dwLastFPSUpdateTime = dwCurrTime;	}	// Update our sprite	m_sprite.Tick(m_timer.GetFrameTime());	// Update rotation of cube	float fTimeDifferenceInSeconds = m_timer.GetFrameTime();	m_fRotationAngleInDegrees += fTimeDifferenceInSeconds * 180.0f;}
There's nothing that exciting here again; first we see if it's time to update the FPS text string; if we update it every frame, then it'll be constantly changing and difficult to read, so it's better to only update it every 500ms or so. After that, we tick our sprite to update its animation and position, and then we update the rotation angle for our cube.

Handling a Lost Device

Finally, we have a little extra work to do in our OnLostDevice and OnResetDevice functions. Both ID3DXSprite and [/msdn]ID3DXFont[/msdn] have OnLostDevice and OnResetDevice functions that need called when the device is lost and when it's just been reset. That's because both contain dynamic resources (Which need to be in D3DPOOL_DEFAULT), and so these resources need Released and re-created.
I should make a confession here; I forgot about a place that you need to call OnLostDevice and OnResetDevice in the last tutorial, and that's in ToggleFullscreen, so in the source code these calls have been added around the IDirect3DDevice9::Reset call.

Conclusion

You should now be able to see your spinning, textured, coloured cube, with an animating sprite bouncing around the screen, and an FPS counter like in the screenshot below:
Tutorial 5 screenshot


Exercises

Here are a few exercises you can try if you want:
  1. Play around with the parameters to ID3DXFont::DrawText to draw the FPS counter in a different colour, position, alignment, and so on.
  2. Add a second sprite going at a different velocity or starting in a different position.
  3. Add 100 sprites going at different speeds and starting at different positions.
  4. Try some different sprite images out, with different numbers of animation frames and speeds.

Source Code

The source code for this article is available Here with project files for Microsoft Visual Studio 2008 and 2005.



It's also mirrored on My Site.

As always, all feedback is appreciated!


I don't have anything particular planned for the next tutorial, but I was thinking of doing one or all of these:
1. How to write a proxy d3d9.dll to see what D3DX objects are doing, what other apps are doing, and to render on top of other apps.
2. Video textures with DirectShow or something - This question comes up fairly frequently in the DirectX forum and I never know how to do it, so this would be useful for myself.
3. Writing a truetype font renderer - I already have this from my own engine, it'd just be a case of tidying up the code a bit and then profiling it against ID3DXFont.
4. Using PIX - something else I keep telling others to do when I don't really know exactly how to use it myself...

Anyhoo, back to work...
Previous Entry Untitled
Next Entry Untitled
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement
Advertisement