// These are all the includes we need. One for the basic Windows stuff
// (which we will use as rarely as possible, I'm not a fan of the Win32
// API) and one for Direct3D 8.
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <D3DX8.h>
// This is causes the d3d8.lib to be linked in, the same thing can be accomplished by
// adding it to your compiler's link list (Project->Settings->Link in VC++),
// but I prefer this method.
#pragma comment(lib,"d3d8.lib")
#pragma comment(lib,"d3dx8.lib")
// Forward declarations for all of our functions, see their definitions for more detail
void FatalError(const char *error_msg);
void FatalError(HRESULT hr,const char *error_msg);
void ask_fullscreen(void);
LRESULT CALLBACK default_window_proc(HWND hwnd,UINT msg,WPARAM wparam,LPARAM lparam);
void init_window(void);
void kill_window(void);
void init_d3d(void);
void kill_d3d(void);
void init_scene(void);
void kill_scene(void);
void message_pump(void);
D3DFORMAT find_16bit_mode(void);
void render(void);
void NOP(HINSTANCE p_prev_instance,LPSTR p_cmd_line,int p_show);
// The name of our application. Used for window titles, MessageBox titles and
// error reporting
const char g_app_name[]="DirectX 8 Tutorial 1";
// Our screen/window sizes. A better app would allow the user to choose the
// sizes. I'll do that in a later tutorial, for now this is good enough.
const int g_width=640;
const int g_height=480;
// A global flag to determine if we're windowed (false) or full-screen(true)
bool g_fullscreen=true;
// A global handle to our main window, initializing pointers to NULL can save you
// a lot of hassle in the future.
HWND g_main_window=NULL;
// A global handle to our 'instance'. This is needed in various places by the Windows API.
HINSTANCE g_instance;
// Our global flag to track whether we should quit or not. When it becomes true, we clean
// up and exit.
bool g_app_done=false;
// Our main Direct3D interface, it doesn't do much on it's own, but all the more commonly
// used interfaces are created by it. It's the first D3D object you create, and the last
// one you release.
LPDIRECT3D8 g_D3D=NULL;
// The D3DDevice is your main rendering interface. It represents the display and all of its
// capabilities. When you create, modify, or render any type of resource, you will likely
// do it through this interface.
IDirect3DDevice8 *g_d3d_device=NULL;
// WinMain is the first function called by Windows when our app is run. It's the entry
// point of our application.
int APIENTRY WinMain(HINSTANCE p_instance,HINSTANCE p_prev_instance,LPSTR p_cmd_line,int p_show){
// Set our global instance handle so we don't have to pass it around
g_instance=p_instance;
//This function exists to quiet compiler warnings, see its definition for more detail
NOP(p_prev_instance,p_cmd_line,p_show);
// Prompt the user, Full Screen? Windowed? Cancel?
ask_fullscreen();
// Build our window. Cover the screen if full-screen, otherwise make a standard window
init_window();
//Build the D3D objects we'll require
init_d3d();
//One-time preparation of objects and other stuff required for rendering
init_scene();
//Loop until the user aborts (closes the window or hits a key)
while(!g_app_done){
message_pump(); //Check for window messages
render(); //Draw our incredibly cool graphics
}
//Free all of our objects and other resources
kill_scene();
//Clean up all of our Direct3D objects
kill_d3d();
//Close down our window
kill_window();
//Exit happily
return 0;
}
// Procedure: NOP
// Whazzit:This procedure does nothing. If set to a high warning level
// (which I like to do) the compiler will complain because the
// parameters passed into WinMain are never used. The purpose
// of this procedure is to make it think that they are used, so
// it doesn't complain.
void NOP(HINSTANCE p_prev_instance,LPSTR p_cmd_line,int p_show){
p_prev_instance=p_prev_instance;
p_cmd_line=p_cmd_line;
p_show=p_show;
}
// Procedure: message_pump
// Whazzit:Checks the message queue to see if any windows messages
// (window is closing, window needs repainting, etc)
// are waiting and if there are, the messages are dispatched
// to our message handler.
void message_pump(void){
MSG msg;
if(PeekMessage(&msg, NULL, 0, 0,PM_REMOVE)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// Function:init_d3d
// Whazzit:Sets up Direct3D and creates the device. The device is create differently
// if we're full-screen as opposed to running in a desktop window.
void init_d3d(void){
HRESULT hr;
D3DPRESENT_PARAMETERS d3dpp;
D3DDISPLAYMODE display_mode;
//Create Direct3D8, this is the first thing you have to do in any D3D8 program
//Always pass D3D_SDK_VERSION to the function.
g_D3D = Direct3DCreate8( D3D_SDK_VERSION );
if(!g_D3D ){
FatalError("Error getting Direct3D");
}
//Get the current(desktop) display mode. This is really only needed if
//we're running in a window.
hr=g_D3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,&display_mode);
if(FAILED(hr)){
FatalError(hr,"Error getting display mode\n");
}
//Clear out our D3DPRESENT_PARAMETERS structure. Even though we're going
//to set virtually all of it members, it's good practice to zero it out first.
ZeroMemory(&d3dpp,sizeof(d3dpp));
//Whether we're full-screen or windowed these are the same.
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // Throw away previous frames, we don't need them
d3dpp.hDeviceWindow = g_main_window; //This is our main (and only) window
d3dpp.BackBufferCount= 1; //We only need a single back buffer
// BackBufferWidth/Height have to be set for full-screen apps, these values are
//used (along with BackBufferFormat) to determine the display mode.
//They aren't needed in windowed mode since the size of the window will be used.
// BackBufferFormat is the pixel format we want. In windowed mode we use the same
//format as the desktop (which we found by using GetAdapterDisplayMode() above).
//In full-screen we need to find a pixel format we like, see find_16bit_mode()
//below for more details.
if(g_fullscreen){
d3dpp.Windowed = FALSE;
d3dpp.BackBufferWidth = g_width;
d3dpp.BackBufferHeight = g_height;
d3dpp.BackBufferFormat = find_16bit_mode();
}else{
d3dpp.Windowed = TRUE;
d3dpp.BackBufferFormat = display_mode.Format;
}
//After filling in our D3DPRESENT_PARAMETERS structure, we're ready to create our device.
//Most of the options in how the device is created are set in the D3DPRESENT_PARAMETERS
//structure.
hr=g_D3D->CreateDevice(D3DADAPTER_DEFAULT, //The default adapter, on a multimonitor system
//there can be more than one.
//Use hardware acceleration rather than the software renderer
D3DDEVTYPE_HAL,
//Our Window
g_main_window,
//Process vertices in software. This is slower than in hardware,
//But will work on all graphics cards.
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
//Our D3DPRESENT_PARAMETERS structure, so it knows what we want to build
&d3dpp,
//This will be set to point to the new device
&g_d3d_device);
if(FAILED(hr)){
FatalError(hr,"Error creating device\n");
}
}
// Function:kill_d3d
// Whazzit:Releases all of our D3D resources in the opposite order from their creation.
// Note-Since we initially set the pointers to be NULL, we can safely test them
// for a non-NULL state and we know if they've been created. Thus we never Release
// something we didn't create (which causes bad things to happen).
void kill_d3d(void){
if(g_d3d_device){
g_d3d_device->Release();
g_d3d_device=NULL;
}
if(g_D3D){
g_D3D->Release();
g_D3D=NULL;
}
}
// Function:init_scene
// Whazzit:Prepare any objects required for rendering. In this tutorial we have nothing to do here.
void init_scene(void){
}
// Function:kill_scene
// Whazzit:Clean up any objects we required for rendering. In this tutorial we have nothing to do here.
void kill_scene(void){
}
// Function:find_16bit_mode
// Whazzit:Tests a couple of 16-bit modes to see if they are supported. Virtually every graphics
// card in existance will support one of these 2 formats.
D3DFORMAT find_16bit_mode(void){
HRESULT hr;
//CheckDeviceType() is used to verify that a Device can support a particular display mode.
//First we test for R5G6B5. All 16-bits are used in this format giving us a full 64K worth
//worth of colours
hr=g_D3D->CheckDeviceType(D3DADAPTER_DEFAULT, //Test the primary display device, this is
//necessary because systems can have add-on cards
//or multi-monitor setups
D3DDEVTYPE_HAL, //This states that we want support for this mode
//in hardware rather than emulated in software
D3DFMT_R5G6B5, //The is the primary (viewable) buffer format
D3DFMT_R5G6B5, //This is the back (drawable) buffer format
FALSE); //Is this windowed mode? Nope
if(SUCCEEDED(hr)){
return D3DFMT_R5G6B5;
}
//Next try X1R5G5B5. Since 1 bit is wasted it's technically a 15-bit mode and only
//provides 32K colours, though you'd be hard pressed to tell the difference between
//15- & 16-bit modes.
hr=g_D3D->CheckDeviceType(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,D3DFMT_X1R5G5B5,D3DFMT_X1R5G5B5,FALSE);
if(SUCCEEDED(hr)){
return D3DFMT_X1R5G5B5;
}
//This is a freaky card. Complain and bail out.
FatalError("Couldn't find a decent mode\n");
//Won't actually hit this line since FatalError() kills us, but it makes the compiler happy.
return (D3DFORMAT)NULL;
}
// Function:ask_fullscreen
// Whazzit:Ask the user if they would like to run in full-screen or windowed mode or if they
// would like to Cancel (abort).
void ask_fullscreen(void){
int full_result;
full_result=MessageBox(NULL,"Would you like to run in fullscreen mode?",g_app_name,
MB_YESNOCANCEL|MB_ICONQUESTION);
switch(full_result){
case IDCANCEL: //User hit 'Cancel' button, so we quit
MessageBox(NULL,"User Abort",g_app_name,MB_OK);
exit(5);
break;
case IDNO: //User hit 'No' button, run in a window
g_fullscreen=false;
break;
case IDYES: //User hit 'Yes' button, run full-screen
g_fullscreen=true;
break;
case 0: //Error! Couldn't open dialog box
OutputDebugString("Couldn't open MessageBox, dying");
exit(10);
break;
}
}
// Function: render
// Whazzit:Clears the screen to a pseudo-random colour and then presents the results.
// If we were doing any real drawing, it would go in this function between
// the BeginScene() & EndScene().
void render(void){
static unsigned char red=0,green=0,blue=0;
//These will safely overflow when the values go over 255, wrapping back to 0.
red++;
green+=2;
blue+=3;
//Clear the buffer to our new colour.
g_d3d_device->Clear(0, //Number of rectangles to clear, we're clearing everything so set it to 0
NULL, //Pointer to the rectangles to clear, NULL to clear whole display
D3DCLEAR_TARGET, //What to clear. We don't have a Z Buffer or Stencil Buffer
D3DCOLOR_XRGB(red,green,blue), //Colour to clear to
1.0f, //Value to clear ZBuffer to, doesn't matter since we don't have one
0 ); //Stencil clear value, again, we don't have one, this value doesn't matter
//Notify the device that we're ready to render
if(SUCCEEDED(g_d3d_device->BeginScene())){
//Put cool stuff here
//Notify the device that we're finished rendering for this frame
g_d3d_device->EndScene();
}
//Show the results
g_d3d_device->Present(NULL, //Source rectangle to display, NULL for all of it
NULL, //Destination rectangle, NULL to fill whole display
NULL, //Target window, if NULL uses device window set in CreateDevice
NULL );//Unused parameter, set it to NULL
}
// Function: init_window
// Whazzit:Registers a window class and then creates our window.
void init_window(void){
ULONG window_width, window_height;
WNDCLASS window_class;
DWORD style;
//Fill in all the fields for the WNDCLASS structure. Window classes
//are a sort of template for window creation. You could create many
//windows using the same window class.
window_class.style = CS_OWNDC;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = g_instance;
window_class.hIcon = LoadIcon(NULL,IDI_APPLICATION);
window_class.hCursor = LoadCursor(NULL,IDC_ARROW);
window_class.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
window_class.lpszMenuName = NULL;
window_class.lpszClassName = "DH Class";
//Here we provide our default window handler, all windows messages
//will be sent to this function.
window_class.lpfnWndProc = default_window_proc;
//Register the class with windows
if(!RegisterClass(&window_class)){
FatalError("Error registering window class");
}
//If we're running full screen, we cover the desktop with our window.
//This isn't necessary, but it provides a smoother transition for the
//user, especially when we're going to change screen modes.
if(g_fullscreen){
window_width=GetSystemMetrics(SM_CXSCREEN);
window_height=GetSystemMetrics(SM_CYSCREEN);
style=WS_POPUP;
}else{
//In windowed mode, we just make the window whatever size we need.
window_width=g_width;
window_height=g_height;
style=WS_OVERLAPPED|WS_SYSMENU;
}
g_main_window=CreateWindow("DH Class",g_app_name,style,
0,0,window_width,window_height,NULL,NULL,
g_instance,NULL);
if(!g_main_window){
FatalError("Error opening window");
}
//The next 3 lines just make sure that our window is visible and has the
//input focus. It's not strictly necessary, but it doesn't hurt to be
//thorough.
ShowWindow(g_main_window,SW_SHOW);
UpdateWindow(g_main_window);
SetFocus(g_main_window);
}
// Function: kill_window
// Whazzit:Closes the window, clean up any waiting messages, and then unregister
// our window class. Note - This is not the standard Win32 way of cleaning
// up your window. The standard way involves putting the clean-up code in
// your window handler, I don't like that method.
void kill_window(void){
//Test if our window is valid
if(g_main_window){
if(!DestroyWindow(g_main_window)){
//We failed to destroy our window, this shouldn't ever happen
MessageBox(NULL,"Destroy Window Failed",g_app_name,MB_OK|MB_ICONERROR|MB_TOPMOST);
}else{
MSG msg;
//Clean up any pending messages
while(PeekMessage(&msg, NULL, 0, 0,PM_REMOVE)){
DispatchMessage(&msg);
}
}
//Set our window handle to NULL just to be safe
g_main_window=NULL;
}
//Unregister our window, if we had opened multiple windows using this
//class, we would have to close all of them before we unregistered the class.
if(!UnregisterClass("DH Class",g_instance)){
MessageBox(NULL,"Unregister Failed",g_app_name,MB_OK|MB_ICONERROR|MB_TOPMOST);
}
}
// Function:FatalError
// Whazzit:Close down all resources, alert the user and quit
void FatalError(const char *error_msg){
kill_scene();
kill_d3d();
kill_window();
//Write our error message out to the debugger (if it's active)
OutputDebugString( error_msg );
OutputDebugString("\n");
MessageBox(NULL, error_msg,g_app_name, MB_OK );
exit(5);
}
// Function:FatalError
// Whazzit:Close down all resources, alert the user and quit
void FatalError(HRESULT hr,const char *error_msg){
char buffer[255];
D3DXGetErrorStringA(hr,buffer,250);
strcat(buffer,"\n");
strcat(buffer,error_msg);
kill_scene();
kill_d3d();
kill_window();
//Write our error message out to the debugger (if it's active)
OutputDebugString( buffer );
OutputDebugString("\n");
MessageBox(NULL, buffer,g_app_name, MB_OK );
exit(5);
}
// Function:default_window_proc
// Whazzit:All Windows messages get passed through this function. We only handle
// a tiny subset of the available messages, all unhandled messages get
// passed through to DefWindowProc() which is part of the Win32 API.
LRESULT CALLBACK default_window_proc(HWND hwnd,UINT msg,WPARAM wparam,LPARAM lparam){
switch(msg){
case WM_KEYDOWN: // A key has been pressed, end the app
g_app_done=true;
return 0;
case WM_CLOSE: //User hit the Close Window button, end the app
g_app_done=true;
return 0;
case WM_DESTROY: //This window is being destroyed, tell Windows we're quitting
PostQuitMessage(0);
return 0;
}
return (DefWindowProc(hwnd,msg,wparam,lparam));
}
|