News
DirectX
Links
Contact Me

In Association with Amazon.com
In Association with Amazon.ca

$5 via PayPal


Direct3D Lesson 1



Direct3D Lesson 1:Your First DirectX Window
// 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));

}