Part Overview
Chapter 5: Timing, Direct Input, and Animation and Sprites Overview
This chapter covers several small topics. We show how to use the performance counter for computing the frames per second and for updating game objects based on time (animation); we also show how to initialize Direct Input for immediate mode keyboard and mouse input; and in addition, we show how to draw 2D sprites using the ID3Dxsprite interface. It is also the last chapter before we dive into 3D graphics programming.
Objectives:
To learn how to use the Win32 performance counter functions for obtaining high-resolution timer readings.
To find out how to compute the time that elapses between frames, how to compute the frames being rendered per second, and how to compute the time spent processing a frame.
To become familiar with basic Direct Input concepts, and how to use immediate mode Direct Input to obtain input from the mouse and keyboard.
To discover how to draw 2D images with the ID3DXSprite interface, and how to animate these images over time.
5.1 The Performance Timer
For accurate time measurements, we use the performance timer (or performance counter). To use the Win32 functions for querying the performance timer, we must
#include<windows.h>.
The performance timer measures time in units called counts. We obtain the current time value (measured in counts) of the performance timer with the QueryPerformanceCounter function like so:
__int64 prevTimeStamp = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&prevTimeStamp);
Observe that this function returns the current time value through its parameter, which is a 64-bit integer value.
Note The values returned by the QueryPerformanceCounter function are not particularly interesting in and of themselves. What we do is get the current time value using QueryPerformanceCounter, and then get the current time value a little later using QueryPerformanceCounter again. Then the time (in counts) that elapsed between those two time calls is just the difference. The following better illustrates the idea:
__int64 A = 0;
Now this "counts" unit is not particularly meaningful to us; we would like to convert it to something that makes more sense to us, such as seconds. Fortunately, there is another API function, QueryPerformanceFrequency , which returns the performance timer's frequency (counts per second) so that we can convert time readings from units of counts to units of seconds. The function is invoked as follows:
__int64 cntsPerSec = 0;
QueryPerformanceFrequency((LARGE_INTEGER*)&cntsPerSec);
Then the number of seconds per count is just the reciprocal of the counts per second:
float secsPerCnt = 1.0f / (float)cntsPerSec;
Thus, to convert a counts time reading valuelnCounts to seconds, we just multiply it by the conversion factor secsPerCnt (which tells us how many seconds [or fraction of a second] there are in one count):
valueInSecs = valueInCounts * secsPerCnt;
Note The function QueryPerformanceFrequency returns a BOOL type; specifically, true is returned if the performance timer exists and false otherwise.
Pentiums and above have the performance timer, so it can be assumed to exist. Still, if you do not want to make that assumption, you can fall back to the Win32 multimedia timer function timeGetTime .
5.1.1 Time Differential between Frames
Computing the time differential between frames (i.e., the elapsed time between frames) is easy. Let t be the time returned by the performance counter during the ith frame and let ti-1, be the time returned by the performance counter during the previous frame. Then the time that elapsed between the ti-1, reading and the ti reading is just: ∆t = ti - ti-1. For real-time rendering, we typically require at least 30 frames per second (and we usually have much higher rates); thus, ∆t = ti - ti-1 tends to be a relatively small number.
In our application framework, we compute the time differential, ∆t = ti - ti-1, every frame and pass this value into our updateScene method. In this way, our application can update the game state with respect to time. Our application framework's modified run method now looks like this (new code lines bolded):
int D3DApp::run()
QueryPerformanceCounter((LARGE_INTEGER*)&currTimeStamp);
float dt = (currTimeStamp - prevTimeStamp)*secsPerCnt;
}
It is common for games and graphics applications to measure the number of frames being rendered per second (FPS). To do this, we simply count the number of frames processed (and store it in a variable n) over some specified time period t. Then, the average FPS over the time period t is fpsavg = n/t. If we set t = 1, then fpsavg = n/1 = n. In our code, we use t = 1 since it avoids a division, and moreover, one second gives a pretty good average — it is not too long and not too short.
The code to compute the FPS is provided as a method of the Gfxstats class (which we discuss in the next subsection) as follows:
void GfxStats::update(float dt)
This method would be called every frame to count the frame.
In addition to computing the FPS, the above code also computes the number of milliseconds it takes, on average, to process a frame:
mMilliSecPerFrame = 1000.0f / mFPS;
Note The seconds per frame is just the reciprocal of the FPS, but we multiply by 1000 ms /1 s to convert from seconds to milliseconds (recall that there are 1000 ms per second).
The idea behind this line is to compute the time, in milliseconds, it takes to render a frame; this is a different quantity than FPS (but observe this value can be derived from the FPS). In actuality, the time it takes to render a frame is more useful than the FPS, as we may directly see the increase/decrease in time it takes to render a frame as we modify our scene. On the other hand, the FPS does not immediately tell us the increase/decrease in time as we modify our scene. Moreover, as Robert Dunlop [Dunlop03] points out in his article "FPS Versus Frame Time," due to the non-linearity of the FPS curve, using the FPS can give misleading results. For example, consider situation (1): Suppose our application is running at 1000 FPS, taking 1 ms (millisecond) to render a frame. If the frame rate drops to 250 FPS, then it takes 4 ms to render a frame. Now consider situation (2): Suppose that our application is running at 100 FPS, taking 10 ms to render a frame. If the frame rate drops to about 76.9 FPS, then it takes about 13 ms to render a frame. In both situations, the rendering per frame increased by 3 ms, and thus both represent the same increase in time it takes to render a frame. Reading the FPS is not as straightforward. The drop from 1000 FPS to 250 FPS seems much more drastic than the drop from 100 FPS to 76.9 FPS; however, as we have just shown, they actually represent the same increase in time it takes to render a frame.
5.1.3 Graphics Stats Demo
To facilitate calculating and drawing the FPS and milliseconds per frame, we define and implement a graphics stats class called Gfxstats:
class GfxStats
DWORD mNumTris;
DWORD mNumVertices;
};
Observe that the class contains an ID3DXFont object for displaying the statistics data, and it has members for storing the FPS and milliseconds per frame. A pointer to an ID3DXFont interface is obtained in the constructor just like it was in the Hello Direct3D demo from the previous chapter. We saw how the update method was implemented in the preceding subsection. The display method simply wraps the ID3DXFont::DrawText method to output the information:
void GfxStats::display() {
// Make static so memory is not allocated every frame.
static char buffer[256];
sprintf(buffer, "Frames Per Second = %.2f\n"
"Milliseconds Per Frame = %.4f\n"
"Triangle Count = %d\n"
"Vertex Count = %d", mFPS, mMilliSecPerFrame, mNumTris, mNumVertices);
RECT R = {5, 5, 0, 0};
HR(mFont - >DrawText(0, buffer, - 1, &R, DT_NOCLIP, D3DCOLOR_XRSB(0,0,0)));
}
Note that the DT_NOCLIP flag means that the text will not be clipped by the formatting rectangle (i.e., the text can go outside the rectangle bounds and not be chopped off). Thus, the dimensions of the formatting rectangle are not needed — we really only need to specify the upper-left corner of the formatting rectangle to specify the position of the text in screen space.
To make the class more useful, it also keeps track of the overall scene triangle and vertex count; as we will learn in Chapter 6, our 3D worlds are composed of triangles and vertices. The triangle count and vertex count can be modified with the following methods:
void GfxStats::addVertices(DWORD n) { mNumVertices += n;}
void GfxStats::subVertices(DWORD n) { mNumVertices - = n;}
void GfxStats::addTriangles(DWORD n) { mNumTris += n; } void GfxStats::subTriangles(DWORD n) { mNumTris - = n; } void GfxStats::setTriCount(DWORD n) { mNumTris = n; } void GfxStats::setVertexCount(DWORD n){ mNumVertices = n; }
Figure 5.1 shows a screenshot of the demo application illustrating the GfxStats class. It is rather trivial, and very similar to the Hello Direct3D demo, except that it computes the FPS and milliseconds per frame. Consequently, it also illustrates the performance timer functions. Be sure to familiarize yourself with the source code of the demo before moving on to the next chapter.
Figure 5.1: A screenshot of the demo stats screen.
5.2 Direct Input Primer
We know that via the Win32 API message system, we can obtain mouse and keyboard input. However, wouldn't it be faster to bypass the message system and work directly with the input drivers ? This is exactly what Direct Input allows us to do. For the purposes of this book, we only use the keyboard and mouse, but Direct Input can work with joysticks, game controllers (including force feedback ones), and other game input devices; see the DirectX SDK documentation for details.
As we work through our discussion of Direct Input, we implement a Directlnput class at the same time. For reference, the class is defined as follows:
class DirectInput {
public:
DirectInput(DWORD keyboardCoopFlags, DWORD mouseCoopFlags);
~DirectInput();
DirectInput(const DirectInput& rhs);
DirectInput& operator=(const DirectInput& rhs);
private:
IDirectInput8* mDInput;
IDirectInputDevice8* mKeyboard;
char mKeyboardState[256];
IDirectInputDevice8* mMouse;
DIMOUSESTATE2 mMouseState;
};
The methods and data members will become evident as you work through this section.
Note that this class is designed to be instantiated only once. For convenient access, we declare the following global variable in DirectInput h:
extern DirectInput* gDInput;
This variable should be made to point to the one and only instantiation of DirectInput; in this book's demos, we do this in Winmain:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR cmdLine, int showCmd)
Note The DirectInput class is defined and implemented in Directlnputh/.cpp. To employ Direct Input, we need to #include <dinput.h> and link dinput&lib.
In this book, we use immediate mode Direct Input, which looks at a snapshot of the input devices frequently. Another technique is buffered input, which records the state of the input devices over a period of time in a data buffer.
5.2.1 Interfaces
We concern ourselves with two Direct Input interfaces:
IDirectInputDevice8: This interface is Direct Input's analog to the IDirect3DDevice9 interface; that is, this interface is our software representation of a physical input device (mouse, keyboard, joystick) and it allows us to communicate with that input device. For example, through this interface we find out which keys are currently down or how the mouse position is changing.
IDirectInput8: This interface is Direct Input's analog to the IDirect3D9 interface; through it we can enumerate input devices on the system, and it is used for obtaining pointers to IDirectlnputDevice8 objects. Since we restrict ourselves to only the mouse and keyboard, we do not concern ourselves with input device enumeration (you would use enumeration to look for joysticks, game controllers, etc.). Moreover, we assume every computer that runs our applications has at least a mouse and keyboard.
A pointer to an IDirectInput8 object can be obtained with the DirectInput8Create function, which is invoked like so:
HR(DirectInput8Create(
The first parameter is a handle to the application instance, which we can get from the D3DApp::getAppInst method (recall we keep a global pointer, gd3dApp, to the one and only application instance). The second parameter specifies the Direct Input version number. Before including <dinput.h>, you should define the symbol DIRECTINPUT VERSION by:
#define DIRECTINPUT_VERSION 0x0800,
which indicates to use version 8.0. For parameter three, we specify a unique interface ID of the Direct Input interface we want to create — this will always be IID_IDirectInput8. Via the fourth parameter, the function returns a pointer to the newly created IDirectInput8 object (provided the function returned successfully). Finally, we don't need the last parameter and can specify null.
Note The latest version of the Direct Input interfaces is 8.0 — the Direct Input interfaces were never augmented to version 9.0.
5.2.2 Initializing the Keyboard and Mouse
There are four basic steps to initializing the keyboard and mouse with Direct Input:
1. Create the device.
2. Set the data format.
3. Set the cooperative level.
4. Acquire the device.