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 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(){}
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;}
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();}
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;}
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 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
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; }}
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; } }
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; } } }}
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));}
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;}
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();}
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;}
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:![Cube & Friends Tutorial 5 screenshot](http://members.gamedev.net/EvilSteve/JournalStuff2/D3DTutorial05/Screenshot.png)
Exercises
Here are a few exercises you can try if you want:- Play around with the parameters to ID3DXFont::DrawText to draw the FPS counter in a different colour, position, alignment, and so on.
- Add a second sprite going at a different velocity or starting in a different position.
- Add 100 sprites going at different speeds and starting at different positions.
- 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...