Alright! Well we can all agree that part one to the DirectX intro was boring. all that code and nothing really cool to show for it. This time we'll have something neat to show for it. I'd like to take this opportunity to reiterate that knowledge of Win API and C++ is assumed as well as DirectX/D3D initialization that was covered in the previous tutorial. Here we go!
In this tutorial we will be covering some more indepth functions of DirectX as well as creating a reusable framework for future work. This is the most important part since typing all this crap over and over gets old quick. The idea will be to eventually create your own libraries for repeated use, but we aren't there yet. NOTE: This project has ALOT of code and takes a while to disgest; patience is key here (I was tearing my hair out on a few DX9 changes that my compiler was hating). I compiled this on MSVS 2005 in case you are curious.
Let's first look at the class D3DAPP, which we will use to derive children classes of apps we are currently developing:
CODE
//This is the header file including class declaration
//Some directX framework
#ifndef D3DAPP_H
#define D3DAPP_H
#include "d3dUtil.h"
#include <string>
class D3DApp
{
public:
D3DApp(HINSTANCE hInstance, std::string winCaption, D3DDEVTYPE devType, DWORD requestedVP);
virtual ~D3DApp();
HINSTANCE getAppInst();
HWND getMainWnd();
// Framework methods. Derived client class overrides these methods to
// implement specific application requirements.
virtual bool checkDeviceCaps() { return true; }
virtual void onLostDevice() {}
virtual void onResetDevice() {}
virtual void updateScene(float dt) {}
virtual void drawScene() {}
// Virtual methods
virtual void initMainWindow();
virtual void initDirect3D();
virtual int run();
virtual LRESULT msgProc(UINT msg, WPARAM wParam, LPARAM lParam);
void enableFullScreenMode(bool enable);
bool isDeviceLost();
protected:
// Derived client class can modify these data members in the constructor to
// customize the application.
std::string mMainWndCaption;
D3DDEVTYPE mDevType;
DWORD mRequestedVP;
// Application, Windows, and Direct3D data members.
HINSTANCE mhAppInst;
HWND mhMainWnd;
IDirect3D9* md3dObject;
bool mAppPaused;
D3DPRESENT_PARAMETERS md3dPP;
};
// Globals for convenient access.
extern D3DApp* gd3dApp;
extern IDirect3DDevice9* gd3dDevice;
#endif // D3DAPP_H
This is one of two header files and there are only 2 cpp files, only four files total. Let's take a look at the class.
Why make a class? Well, in short we can keep all the good features we want in a reasonable area so we don't have to reinvent the wheel everytime we sit down to write a D3D app. Like in all directx programs we declare a 'global variable pointer' pointing to the device we are making/using. This allows us to make calls to all of the features directx has. For the purpose of this tutorial the virtual functions can be ignored, they are there in the future if you want to override some directx goodies.
We also have data members to check if we are in windowed or full screen mode (the window can change if you click the button in the right hand corner.)
Now let's look at the 'utility' file:
CODE
// Contains various utility code for DirectX applications, such as clean up
// and debugging code.
#ifndef D3DUTIL_H
#define D3DUTIL_H
// Enable extra D3D debugging in debug builds if using the debug DirectX runtime.
// This makes D3D objects work well in the debugger watch window, but slows down
// performance slightly.
#if defined(DEBUG) | defined(_DEBUG)
#ifndef D3D_DEBUG_INFO
#define D3D_DEBUG_INFO
#endif
#endif
#include <d3d9.h>
#include <d3dx9.h>
#include <dxerr9.h>
#include <string>
#include <sstream>
//===============================================================
// Globals for convenient access.
class D3DApp;
extern D3DApp* gd3dApp;
extern IDirect3DDevice9* gd3dDevice;
//===============================================================
// Clean up
#define ReleaseCOM(x) { if(x){ x->Release();x = 0; } }
//===============================================================
// Debug
#if defined(DEBUG) | defined(_DEBUG)
#ifndef HR
#define HR(x) \
{ \
HRESULT hr = x; \
if(FAILED(hr)) \
{ \
DXTrace(__FILE__, __LINE__, hr, #x, TRUE); \
} \
}
#endif
#else
#ifndef HR
#define HR(x) x;
#endif
#endif
#endif // D3DUTIL_H
Again, its a lot to digest all at once, but the good news most of this isn't necessary to dive in and experiment. We again have global pointers for access to our class and derivatives of that class. The other important thing here is the debugging code. Directx is a damn complicated library and the easier you can make debugging the more fun you'll have. Putting HR in front of a directx/D3D function call will alert you if the call is bad for whatever reason. This can save a lot of time if you are hunting for a bug. Why did it fail, what parameters made it fail...
All this code and still no coolness.

I know, but all this framwork/infrastructure will allow us to do minimal coding in the next two files. Here is the cpp file that creates our instance of the D3DApp class seen above and gives it its methods and data members:
CODE
//A sample directX demo outputting some flashing color text
#include "d3dApp.h"
#include <tchar.h>
#include <crtdbg.h>
//Our application is derived from the D3DAPP class, making setup for a game
//or other program easier in the long run
class HelloD3DApp : public D3DApp
{
public:
HelloD3DApp(HINSTANCE hInstance, std::string winCaption, D3DDEVTYPE devType, DWORD requestedVP);
~HelloD3DApp();
bool checkDeviceCaps();
void onLostDevice();
void onResetDevice();
void updateScene(float dt);
void drawScene();
private:
ID3DXFont* mFont;
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
PSTR cmdLine, int showCmd)
{
// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif
HelloD3DApp app(hInstance, "Hello Direct3D", D3DDEVTYPE_HAL, D3DCREATE_HARDWARE_VERTEXPROCESSING);
gd3dApp = &app;
return gd3dApp->run();
}
HelloD3DApp::HelloD3DApp(HINSTANCE hInstance, std::string winCaption, D3DDEVTYPE devType, DWORD requestedVP)
: D3DApp(hInstance, winCaption, devType, requestedVP)
{
srand(time_t(0));
if(!checkDeviceCaps())
{
MessageBox(0, "checkDeviceCaps() Failed", 0, 0);
PostQuitMessage(0);
}
LOGFONTA font;
font.lfHeight = 80;
font.lfWidth = 40;
font.lfEscapement = 0;
font.lfOrientation = 0;
font.lfWeight = FW_BOLD;
font.lfItalic = true;
font.lfUnderline = false;
font.lfStrikeOut = false;
font.lfCharSet = DEFAULT_CHARSET;
font.lfOutPrecision = OUT_DEFAULT_PRECIS;
font.lfClipPrecision = CLIP_CHARACTER_PRECIS;
font.lfQuality = DEFAULT_QUALITY;
font.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
_tcscpy(font.lfFaceName, _T("Times New Roman"));
HR(D3DXCreateFontIndirect(gd3dDevice, &font, &mFont));
}
HelloD3DApp::~HelloD3DApp()
{
ReleaseCOM(mFont);
}
bool HelloD3DApp::checkDeviceCaps()
{
// Nothing to check.
return true;
}
void HelloD3DApp::onLostDevice()
{
HR(mFont->OnLostDevice());
}
void HelloD3DApp::onResetDevice()
{
HR(mFont->OnResetDevice());
}
void HelloD3DApp::updateScene(float dt)
{
}
void HelloD3DApp::drawScene()
{
HR(gd3dDevice->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(255, 255, 255), 1.0f, 0));
RECT formatRect;
GetClientRect(mhMainWnd, &formatRect);
HR(gd3dDevice->BeginScene());
mFont->DrawText(TEXT("Hello </DIC>!"), -1,
&formatRect, DT_CENTER | DT_VCENTER,
D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256));
HR(gd3dDevice->EndScene());
HR(gd3dDevice->Present(0, 0, 0, 0));
}
Notice in the beginning we derive HelloD3DApp to be from D3DApp class (it inherits all of the functions as well as any new ones you want to put in.) This is the best example of where we don't have to retype all the features of the basic D3DApp into our new class. We simply add the functions that will draw the flashing text and clean up after us. There are other font structs you can use, such as
D3DXFONT_DESC it is really your preference and what you can get to compile, they offer virtually all the same features of text customization.
It is important to note that we call the DrawText method inbetween BeginScene and EndScene renders. This gives us the opportunity to update the backbuffer with the current frame each cycle. If we called it somewhere else, it would not be updated.
Now, I know what you're all saying. WTF?! Still nothing to show for our work! Well here is the boilerplate windows code that will give us our awesome flashing text window (either windowed or full screen your choice):
CODE
#include "d3dApp.h"
D3DApp* gd3dApp = 0;
IDirect3DDevice9* gd3dDevice = 0;
LRESULT CALLBACK
MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Don't start processing messages until the application has been created.
if( gd3dApp != 0 )
return gd3dApp->msgProc(msg, wParam, lParam);
else
return DefWindowProc(hwnd, msg, wParam, lParam);
}
D3DApp::D3DApp(HINSTANCE hInstance, std::string winCaption, D3DDEVTYPE devType, DWORD requestedVP)
{
mMainWndCaption = winCaption;
mDevType = devType;
mRequestedVP = requestedVP;
mhAppInst = hInstance;
mhMainWnd = 0;
md3dObject = 0;
mAppPaused = false;
ZeroMemory(&md3dPP, sizeof(md3dPP));
initMainWindow();
initDirect3D();
}
D3DApp::~D3DApp()
{
ReleaseCOM(md3dObject);
ReleaseCOM(gd3dDevice);
}
HINSTANCE D3DApp::getAppInst()
{
return mhAppInst;
}
HWND D3DApp::getMainWnd()
{
return mhMainWnd;
}
void D3DApp::initMainWindow()
{
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = mhAppInst;
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = "D3DWndClassName";
if( !RegisterClass(&wc) )
{
MessageBox(0, "RegisterClass FAILED", 0, 0);
PostQuitMessage(0);
}
// Default to a window with a client area rectangle of 800x600.
RECT R = {0, 0, 800, 600};
AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
mhMainWnd = CreateWindow("D3DWndClassName", mMainWndCaption.c_str(),
WS_OVERLAPPEDWINDOW, 100, 100, R.right, R.bottom,
0, 0, mhAppInst, 0);
if( !mhMainWnd )
{
MessageBox(0, "CreateWindow FAILED", 0, 0);
PostQuitMessage(0);
}
ShowWindow(mhMainWnd, SW_SHOW);
UpdateWindow(mhMainWnd);
}
void D3DApp::initDirect3D()
{
// Step 1: Create the IDirect3D9 object.
md3dObject = Direct3DCreate9(D3D_SDK_VERSION);
if( !md3dObject )
{
MessageBox(0, "Direct3DCreate9 FAILED", 0, 0);
PostQuitMessage(0);
}
// Step 2: Verify hardware support for specified formats in windowed and full screen modes.
D3DDISPLAYMODE mode;
md3dObject->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &mode);
HR(md3dObject->CheckDeviceType(D3DADAPTER_DEFAULT, mDevType, mode.Format, mode.Format, true));
HR(md3dObject->CheckDeviceType(D3DADAPTER_DEFAULT, mDevType, D3DFMT_X8R8G8B8, D3DFMT_X8R8G8B8, false));
// Step 3: Check for requested vertex processing and pure device.
D3DCAPS9 caps;
HR(md3dObject->GetDeviceCaps(D3DADAPTER_DEFAULT, mDevType, &caps));
DWORD devBehaviorFlags = 0;
if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
devBehaviorFlags |= mRequestedVP;
else
devBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
// If pure device and HW T&L supported
if( caps.DevCaps & D3DDEVCAPS_PUREDEVICE &&
devBehaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING)
devBehaviorFlags |= D3DCREATE_PUREDEVICE;
// Step 4: Fill out the D3DPRESENT_PARAMETERS structure.
md3dPP.BackBufferWidth = 0;
md3dPP.BackBufferHeight = 0;
md3dPP.BackBufferFormat = D3DFMT_UNKNOWN;
md3dPP.BackBufferCount = 1;
md3dPP.MultiSampleType = D3DMULTISAMPLE_NONE;
md3dPP.MultiSampleQuality = 0;
md3dPP.SwapEffect = D3DSWAPEFFECT_DISCARD;
md3dPP.hDeviceWindow = mhMainWnd;
md3dPP.Windowed = true;
md3dPP.EnableAutoDepthStencil = true;
md3dPP.AutoDepthStencilFormat = D3DFMT_D24S8;
md3dPP.Flags = 0;
md3dPP.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
md3dPP.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
// Step 5: Create the device.
HR(md3dObject->CreateDevice(
D3DADAPTER_DEFAULT, // primary adapter
mDevType, // device type
mhMainWnd, // window associated with device
devBehaviorFlags, // vertex processing
&md3dPP, // present parameters
&gd3dDevice)); // return created device
}
int D3DApp::run()
{
MSG msg;
msg.message = WM_NULL;
while(msg.message != WM_QUIT)
{
// If there are Window messages then process them.
if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
// Otherwise, do animation/game stuff.
else
{
// If the application is paused then free some CPU
// cycles to other applications and then continue on
// to the next frame.
if( mAppPaused )
{
Sleep(20);
continue;
}
if( !isDeviceLost() )
{
updateScene(0.0f);
drawScene();
}
}
}
return (int)msg.wParam;
}
LRESULT D3DApp::msgProc(UINT msg, WPARAM wParam, LPARAM lParam)
{
// Is the application in a minimized or maximized state?
static bool minOrMaxed = false;
RECT clientRect = {0, 0, 0, 0};
switch( msg )
{
// WM_ACTIVE is sent when the window is activated or deactivated.
// We pause the game when the main window is deactivated and
// unpause it when it becomes active.
case WM_ACTIVATE:
if( LOWORD(wParam) == WA_INACTIVE )
mAppPaused = true;
else
mAppPaused = false;
return 0;
// WM_SIZE is sent when the user resizes the window.
case WM_SIZE:
if( gd3dDevice )
{
md3dPP.BackBufferWidth = LOWORD(lParam);
md3dPP.BackBufferHeight = HIWORD(lParam);
if( wParam == SIZE_MINIMIZED )
{
mAppPaused = true;
minOrMaxed = true;
}
else if( wParam == SIZE_MAXIMIZED )
{
mAppPaused = false;
minOrMaxed = true;
onLostDevice();
HR(gd3dDevice->Reset(&md3dPP));
onResetDevice();
}
// Restored is any resize that is not a minimize or maximize.
// For example, restoring the window to its default size
// after a minimize or maximize, or from dragging the resize
// bars.
else if( wParam == SIZE_RESTORED )
{
mAppPaused = false;
// Restoration from windowed to fullscreen
if( minOrMaxed && md3dPP.Windowed )
{
onLostDevice();
HR(gd3dDevice->Reset(&md3dPP));
onResetDevice();
}
else
{
//empty on purpose
}
minOrMaxed = false;
}
}
return 0;
// WM_EXITSIZEMOVE is sent when the user releases the resize bars.
// Here we reset everything based on the new window dimensions.
case WM_EXITSIZEMOVE:
GetClientRect(mhMainWnd, &clientRect);
md3dPP.BackBufferWidth = clientRect.right;
md3dPP.BackBufferHeight = clientRect.bottom;
onLostDevice();
HR(gd3dDevice->Reset(&md3dPP));
onResetDevice();
return 0;
// WM_CLOSE is sent when the user presses the 'X' button in the
// caption bar menu.
case WM_CLOSE:
DestroyWindow(mhMainWnd);
return 0;
// WM_DESTROY is sent when the window is being destroyed.
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_KEYDOWN:
if( wParam == VK_ESCAPE )
enableFullScreenMode(false);
else if( wParam == 'F' )
enableFullScreenMode(true);
return 0;
}
return DefWindowProc(mhMainWnd, msg, wParam, lParam);
}
void D3DApp::enableFullScreenMode(bool enable)
{
// Switch to fullscreen mode.
if( enable )
{
// Are we already in fullscreen mode?
if( !md3dPP.Windowed )
return;
int width = GetSystemMetrics(SM_CXSCREEN);
int height = GetSystemMetrics(SM_CYSCREEN);
md3dPP.BackBufferFormat = D3DFMT_X8R8G8B8;
md3dPP.BackBufferWidth = width;
md3dPP.BackBufferHeight = height;
md3dPP.Windowed = false;
// Change the window style to a more fullscreen friendly style.
SetWindowLongPtr(mhMainWnd, GWL_STYLE, WS_POPUP);
SetWindowPos(mhMainWnd, HWND_TOP, 0, 0, width, height, SWP_NOZORDER | SWP_SHOWWINDOW);
}
// Switch to windowed mode.
else
{
// Are we already in windowed mode?
if( md3dPP.Windowed )
return;
RECT R = {0, 0, 800, 600};
AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
md3dPP.BackBufferFormat = D3DFMT_UNKNOWN;
md3dPP.BackBufferWidth = 800;
md3dPP.BackBufferHeight = 600;
md3dPP.Windowed = true;
// Change the window style to a more windowed friendly style.
SetWindowLongPtr(mhMainWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
SetWindowPos(mhMainWnd, HWND_TOP, 100, 100, R.right, R.bottom, SWP_NOZORDER | SWP_SHOWWINDOW);
}
// Reset the device with the changes.
onLostDevice();
HR(gd3dDevice->Reset(&md3dPP));
onResetDevice();
}
bool D3DApp::isDeviceLost()
{
// Get the state of the graphics device.
HRESULT hr = gd3dDevice->TestCooperativeLevel();
if( hr == D3DERR_DEVICELOST )
{
Sleep(20);
return true;
}
// Driver error, exit.
else if( hr == D3DERR_DRIVERINTERNALERROR )
{
MessageBox(0, "Internal Driver Error...Exiting", 0, 0);
PostQuitMessage(0);
return true;
}
// The device is lost but we can reset and restore it.
else if( hr == D3DERR_DEVICENOTRESET )
{
onLostDevice();
HR(gd3dDevice->Reset(&md3dPP));
onResetDevice();
return false;
}
else
return false;
}
First few things, this is a little different then the first tutorials game loop. In theory, it it the same, but I call it here differently, since we are using more D3D functions rather then vanilla directx. The steps taken when first starting the program are numbered for convience. Again, a lot of this you might not even use unless you were designing a commerical app (such as confirming hardware support of various lighting and shading functions), but are nice to have in the event you want to play around with them. you probably have noticed at this point that directx uses a lot of CAPS when it is defining things, rather then numbers or variables. I find this to be a good thing as there is little confusion between DEFAULT_SOMETHING and NOT_DEFAULT.
As long as our device is still valid the game will run and draw a random color of text, which will result in 'flashing' text of all kinds of colors. Even though this last file is my least favorite it is important in the regards that it takes care of all the possible window functions (destroyed, moved, focus loss, etc...) that is annoying to program and if not implemented leads the program to not shut down properly, perhaps causing a memory leak.
The flashing code says:
"Hello </DIC>!"
IMPORTANT: Don't forget to link the following into your code for it to compile:
d3d9.lib
d3dx9.lib
dxguid.lib
DxErr9.lib
dinput8.lib
For compiling issues the file names in order of apperance:
d3dApp.h
d3dUtil.h
HelloDirect3D.cpp
d3dApp.cpp
(I can upload the files later if so desired)
Also, for the debug features, you need the latest Directx SDK installed (debug mode). Hopefully you found this enjoyable. I'll delve into some 3D stuff once I brush up on the subject some more. Happy coding all!
--kya