Direct3D Immediate Mode Tutorial

Microsoft Corporation

October 16, 1997

Click to copy the d3dex1src sample file associated with this technical article.

Click to copy the d3dex1exec sample file associated with this technical article.

Summary: To create a Microsoft® Windows®-based Direct3D® Immediate Mode application, you create DirectDraw® and Direct3D objects, set render states, fill execute buffers, and execute those buffers. This section includes a simple Immediate Mode application that draws a single, rotating, Gouraud-shaded triangle. (Please note: you will need to have the current version of DirectX® installed in order to view the triangle.) The triangle is drawn in a window whose size is fixed. For code clarity, we have chosen not to address a number of issues in this sample. For example, full-screen operation, resizing the window, and texture-mapping are not included. Furthermore, we have not included some optimizations when their inclusion would have made the code more obscure. Code comments highlight the places in which we did not implement a common optimization.

Contents

Definitions, Prototypes, and Globals
Enumerating Direct3D Devices
Creating Objects and Interfaces
Creating the Scene
Filling the Execute Buffer
Animating the Scene
Rendering
Working with Matrices
Restoring and Redrawing
Releasing Objects
Error Checking
Converting Bit Depths
Main Window Procedure
WinMain Function

Definitions, Prototypes, and Globals

This section contains the definitions, function prototypes, global variables, constants, and other structural underpinnings for the Imsample.c code sample.

Header and Includes

/***********************************************************************
 *
 * File :       imsample.c
 *
 * Author :     Colin D. C. McCartney
 *
 * Date :       1/7/97
 *
 * Version :    V1.1
 *
 **********************************************************************/

/***********************************************************************
 *
 * Include files
 *
 **********************************************************************/

#define  INITGUID
#include <windows.h>
#include <math.h>
#include <assert.h>
#include <ddraw.h>
#include <d3d.h>

#include "resource.h"

Constants

// Class name for this application's window class.
#define WINDOW_CLASSNAME      "D3DSample1Class"
// Title for the application's window.
#define WINDOW_TITLE          "D3D Sample 1"
// String to be displayed when the application is paused.
#define PAUSED_STRING         "Paused"
// Half height of the view window.
#define HALF_HEIGHT           D3DVAL(0.5)
// Front and back clipping planes.
#define FRONT_CLIP            D3DVAL(1.0)
#define BACK_CLIP             D3DVAL(1000.0)
// Fixed window size.
#define WINDOW_WIDTH          320
#define WINDOW_HEIGHT         200
// Maximum length of the chosen device name and description of the
// chosen Direct3D device.
#define MAX_DEVICE_NAME       256
#define MAX_DEVICE_DESC       256
// Amount to rotate per frame.
#define M_PI                  3.14159265359
#define M_2PI                 6.28318530718
#define ROTATE_ANGLE_DELTA    (M_2PI / 300.0)
// Execute buffer contents
#define NUM_VERTICES          3
#define NUM_INSTRUCTIONS      6
#define NUM_STATES            7
#define NUM_PROCESSVERTICES   1
#define NUM_TRIANGLES         1

Macros

// Extract the error code from an HRESULT.

#define CODEFROMHRESULT(hRes) ((hRes) & 0x0000FFFF)

#ifdef _DEBUG
#define ASSERT(x)       assert(x)
#else
#define ASSERT(x)
#endif

// Used to keep the compiler from issuing warnings about any unused 
// parameters.

#define USE_PARAM(x)    (x) = (x)

Global Variables

// Application instance handle (set in WinMain).

static HINSTANCE               hAppInstance              = NULL;

// Running in debug mode?

static BOOL                    fDebug                    = FALSE;

// Is the application active?

static BOOL                    fActive                   = TRUE;

// Has the application been suspended?

static BOOL                    fSuspended                = FALSE;

// DirectDraw interfaces

static LPDIRECTDRAW            lpdd                      = NULL;
static LPDIRECTDRAWSURFACE     lpddPrimary               = NULL;
static LPDIRECTDRAWSURFACE     lpddDevice                = NULL;
static LPDIRECTDRAWSURFACE     lpddZBuffer               = NULL;
static LPDIRECTDRAWPALETTE     lpddPalette               = NULL;

// Direct3D interfaces

static LPDIRECT3D              lpd3d                     = NULL;
static LPDIRECT3DDEVICE        lpd3dDevice               = NULL;
static LPDIRECT3DMATERIAL      lpd3dMaterial             = NULL;
static LPDIRECT3DMATERIAL      lpd3dBackgroundMaterial   = NULL;
static LPDIRECT3DVIEWPORT      lpd3dViewport             = NULL;
static LPDIRECT3DLIGHT         lpd3dLight                = NULL;
static LPDIRECT3DEXECUTEBUFFER lpd3dExecuteBuffer        = NULL;

// Direct3D handles

static D3DMATRIXHANDLE         hd3dWorldMatrix           = 0;
static D3DMATRIXHANDLE         hd3dViewMatrix            = 0;
static D3DMATRIXHANDLE         hd3dProjMatrix            = 0;
static D3DMATERIALHANDLE       hd3dSurfaceMaterial       = 0;
static D3DMATERIALHANDLE       hd3dBackgroundMaterial    = 0;

// Globals used for selecting the Direct3D device. They are
// globals because this makes it easy for the enumeration callback
// function to read and write from them.

static BOOL                    fDeviceFound              = FALSE;
static DWORD                   dwDeviceBitDepth          = 0;
static GUID                    guidDevice;
static char                    szDeviceName[MAX_DEVICE_NAME];
static char                    szDeviceDesc[MAX_DEVICE_DESC];
static D3DDEVICEDESC           d3dHWDeviceDesc;
static D3DDEVICEDESC           d3dSWDeviceDesc;

// The screen coordinates of the client area of the window. This
// rectangle defines the destination into which we blit to update
// the client area of the window with the results of the 3-D rendering.

static RECT                    rDstRect;

// This rectangle defines the portion of the rendering target surface
// into which we render. The top-left coordinates of this rectangle
// are always zero; the right and bottom coordinates give the size of 
// the viewport.

static RECT                    rSrcRect;

// Angle of rotation of the world matrix.

static double                  dAngleOfRotation          = 0.0;

// Predefined transformations.

static D3DMATRIX d3dWorldMatrix =
{
    D3DVAL( 1.0), D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 1.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 1.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 1.0)
};

static D3DMATRIX d3dViewMatrix =
{
    D3DVAL( 1.0), D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 1.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 1.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 5.0), D3DVAL( 1.0)
};

static D3DMATRIX d3dProjMatrix =
{
    D3DVAL( 2.0), D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 2.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 1.0), D3DVAL( 1.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL(-1.0), D3DVAL( 0.0)
};

Function Prototypes

static void
ReportError(HWND hwnd, int nMessage,HRESULT hRes);
static void 
FatalError(HWND hwnd, int nMessage, HRESULT hRes);
static DWORD 
BitDepthToFlags(DWORD dwBitDepth);
static DWORD 
FlagsToBitDepth(DWORD dwFlags);
static void
SetPerspectiveProjection(LPD3DMATRIX lpd3dMatrix,
double      dHalfHeight,
double      dFrontClipping,
double      dBackClipping);
static void
SetRotationAboutY(LPD3DMATRIX lpd3dMatrix,
double      dAngleOfRotation);
static HRESULT
CreateDirect3D(HWND hwnd);
static HRESULT
ReleaseDirect3D(void);
static HRESULT
CreatePrimary(HWND hwnd);
static HRESULT
RestorePrimary(void);
static HRESULT
ReleasePrimary(void);
static HRESULT WINAPI 
EnumDeviceCallback
(LPGUID         lpGUID, 
LPSTR           lpszDeviceDesc,
LPSTR           lpszDeviceName,
LPD3DDEVICEDESC lpd3dHWDeviceDesc,
LPD3DDEVICEDESC lpd3dSWDeviceDesc,
LPVOID          lpUserArg);
static HRESULT
ChooseDevice(void);
static HRESULT
CreateDevice(DWORD dwWidth, DWORD dwHeight);
static HRESULT
RestoreDevice(void);
static HRESULT
ReleaseDevice(void);
static LRESULT
RestoreSurfaces(void);
static HRESULT
FillExecuteBuffer(void);
static HRESULT
CreateScene(void);
static HRESULT
ReleaseScene(void);
static HRESULT
AnimateScene(void);
static HRESULT
UpdateViewport(void);
static HRESULT
RenderScene(void);
static HRESULT
DoFrame(void);
static void
PaintSuspended(HWND hwnd, HDC hdc);
static LRESULT
OnMove(HWND hwnd, int x, int y);
static LRESULT
OnSize(HWND hwnd, int w, int h);
static LRESULT
OnPaint(HWND hwnd, HDC hdc, LPPAINTSTRUCT lpps);
static LRESULT
OnIdle(HWND hwnd);
LRESULT CALLBACK
WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam);
Int PASCAL
WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCommandLine, int cmdShow);

Enumerating Direct3D Devices

The first thing a Direct3D application should do is enumerate the available Direct3D device drivers. The most important API element in this job is IDirect3D::EnumDevices.

This section contains the ChooseDevice function that selects among the available Direct3D devices and the EnumDeviceCallback function that implements the selection mechanism.

This sample application does not demonstrate the enumeration of display modes, which you will need to do if your application supports full-screen rendering modes. To enumerate the display modes, call the IDirectDraw3::EnumDisplayModes method.

Enumeration Callback Function

The EnumDeviceCallback function is invoked for each Direct3D device installed on the system. For each device, we retrieve its identifying GUID, a name and description, a description of its hardware and software capabilities, and an unused user argument.

The EnumDeviceCallback function uses the following algorithm to choose an appropriate Direct3D device:

  1. Discard any devices that don't match the current display depth.

  2. Discard any devices that can't do Gouraud-shaded triangles.

  3. If a hardware device is found that meets the criteria specified in items 1 and 2 above, use it. However, if we are running in debug mode we will skip hardware.

  4. Otherwise, favor Mono/Ramp mode software renderers over RGB ones because Mono will be faster, at least until MMX is widespread.

This callback function is invoked by the ChooseDevice enumeration function, which is described in Enumeration Function.

Note that the first parameter passed to this callback function, lpGUID, is NULL for the primary device. All other devices should have a non-NULL pointer. You should consider saving the actual GUID for the device you choose, rather than the pointer to the GUID, in case the pointer is accidentally corrupted.

static HRESULT WINAPI
EnumDeviceCallback(LPGUID          lpGUID, 
                   LPSTR           lpszDeviceDesc,
                   LPSTR           lpszDeviceName,
                   LPD3DDEVICEDESC lpd3dHWDeviceDesc,
                   LPD3DDEVICEDESC lpd3dSWDeviceDesc,
                   LPVOID          lpUserArg)
{
    BOOL            fIsHardware;
    LPD3DDEVICEDESC lpd3dDeviceDesc;

        // Call the USE_PARAM macro on the unused parameter to
        // avoid compiler warnings.

    USE_PARAM(lpUserArg);

        // If there is no hardware support, the color model is zero.
    
    fIsHardware     = (0 != lpd3dHWDeviceDesc->dcmColorModel);
    lpd3dDeviceDesc = (fIsHardware ? lpd3dHWDeviceDesc :
                                     lpd3dSWDeviceDesc);

        // If we are in debug mode and this is a hardware device,
        // skip it.
    
    if (fDebug && fIsHardware)
        return D3DENUMRET_OK;

        // Does the device render at the depth we want?
    
    if (0 == (lpd3dDeviceDesc->dwDeviceRenderBitDepth & 
              dwDeviceBitDepth))
    {
                // If not, skip this device.
        
        return D3DENUMRET_OK;
    }

        // The device must support Gouraud-shaded triangles.
    
    if (D3DCOLOR_MONO == lpd3dDeviceDesc->dcmColorModel)
    {
        if (!(lpd3dDeviceDesc->dpcTriCaps.dwShadeCaps & 
              D3DPSHADECAPS_COLORGOURAUDMONO))
        {
                        // No Gouraud shading. Skip this device.
            
            return D3DENUMRET_OK;
        }
    }
    else
    {
        if (!(lpd3dDeviceDesc->dpcTriCaps.dwShadeCaps & 
              D3DPSHADECAPS_COLORGOURAUDRGB))
        {
                        // No Gouraud shading. Skip this device.
            
            return D3DENUMRET_OK;
        }
    }

    if (!fIsHardware && fDeviceFound && 
           (D3DCOLOR_RGB == lpd3dDeviceDesc->dcmColorModel))
    {
                // If this is software RGB and we already have found 
                // a software monochromatic renderer, we are not 
                // interested. Skip this device.
        
        return D3DENUMRET_OK;
    }

        // This is a device we are interested in. Save the details.
    
    fDeviceFound = TRUE;
    CopyMemory(&guidDevice, lpGUID, sizeof(GUID));
    strcpy(szDeviceDesc, lpszDeviceDesc);
    strcpy(szDeviceName, lpszDeviceName);
    CopyMemory(&d3dHWDeviceDesc, lpd3dHWDeviceDesc, 
               sizeof(D3DDEVICEDESC));
    CopyMemory(&d3dSWDeviceDesc, lpd3dSWDeviceDesc, 
               sizeof(D3DDEVICEDESC));

        // If this is a hardware device, we have found 
        // what we are looking for.
    
    if (fIsHardware)
        return D3DENUMRET_CANCEL;

        // Otherwise, keep looking.
    
    return D3DENUMRET_OK;
}

Enumeration Function

The ChooseDevice function invokes the EnumDeviceCallback function, which is described in Enumeration Callback Function.

static HRESULT
ChooseDevice(void)
{
    DDSURFACEDESC ddsd;
    HRESULT       hRes;

    ASSERT(NULL != lpd3d);
    ASSERT(NULL != lpddPrimary);

        // Since we are running in a window, we will not be changing the 
        // screen depth; therefore, the pixel format of the rendering 
        // target must match the pixel format of the current primary 
        // surface. This means that we need to determine the pixel 
        // format of the primary surface.
    
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    hRes = lpddPrimary->lpVtbl->GetSurfaceDesc(lpddPrimary, &ddsd);
    if (FAILED(hRes))
        return hRes;

    dwDeviceBitDepth = 
        BitDepthToFlags(ddsd.ddpfPixelFormat.dwRGBBitCount);

        // Enumerate the devices and pick one.

    fDeviceFound = FALSE;
    hRes = lpd3d->lpVtbl->EnumDevices(lpd3d, EnumDeviceCallback, 
                                      &fDeviceFound);
    if (FAILED(hRes))
        return hRes;

    if (!fDeviceFound)
    {
                // No suitable device was found. We cannot allow 
                // device-creation to continue.

        return DDERR_NOTFOUND;
    }

    return DD_OK;
}

Creating Objects and Interfaces

This section contains functions that create the primary DirectDraw surface, a DirectDrawClipper object, a Direct3D object, and a Direct3D device.

Creating the Primary Surface and Clipper Object

The CreatePrimary function creates the primary surface (representing the desktop) and creates and attaches a clipper object. If necessary, this function also creates a palette.

static HRESULT
CreatePrimary(HWND hwnd)
{
    HRESULT             hRes;
    DDSURFACEDESC       ddsd;
    LPDIRECTDRAWCLIPPER lpddClipper;
    HDC                 hdc;
    int                 i;
    PALETTEENTRY        peColorTable[256];

    ASSERT(NULL != hwnd);
    ASSERT(NULL != lpdd);
    ASSERT(NULL == lpddPrimary);
    ASSERT(NULL == lpddPalette);

    // Create the primary surface.
    
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize         = sizeof(ddsd);
    ddsd.dwFlags        = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
    hRes = lpdd->lpVtbl->CreateSurface(lpdd, &ddsd, &lpddPrimary, NULL);
    if (FAILED(hRes))
        return hRes;

    // Create the clipper. We bind the application's window to the
    // clipper and attach it to the primary. This ensures that when we
    // blit from the rendering surface to the primary we don't write
    // outside the visible region of the window.
    
    hRes = DirectDrawCreateClipper(0, &lpddClipper, NULL);
    if (FAILED(hRes))
        return hRes;
    hRes = lpddClipper->lpVtbl->SetHWnd(lpddClipper, 0, hwnd);
    if (FAILED(hRes))
    {
        lpddClipper->lpVtbl->Release(lpddClipper);
        return hRes;
    }
    hRes = lpddPrimary->lpVtbl->SetClipper(lpddPrimary, lpddClipper);
    if (FAILED(hRes))
    {
        lpddClipper->lpVtbl->Release(lpddClipper);
        return hRes;
    }

    // We release the clipper interface after attaching it to the 
    // surface because we don't need to use it again. The surface 
    // holds a reference to the clipper when it has been attached. 
    // The clipper will therefore be released automatically when the 
    // surface is released.
    
    lpddClipper->lpVtbl->Release(lpddClipper);

    // If the primary surface is palettized, the device will be, too. 
    // (The device surface must have the same pixel format as the 
    // current primary surface if we want to double buffer with 
    // DirectDraw.) Therefore, if the primary surface is palettized, we 
    // need to create a palette and attach it to the primary surface 
    // (and to the device surface when we create it).
    
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    hRes = lpddPrimary->lpVtbl->GetSurfaceDesc(lpddPrimary, &ddsd);
    if (FAILED(hRes))
        return hRes;
    if (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8)
    {
    // Initializing the palette correctly is essential. Since we are
    // running in a window, we must not change the top ten and bottom 
    // ten static colors. Therefore, we copy them from the system 
    // palette and mark them as read only (D3DPAL_READONLY). The middle 
    // 236 entries are free for use by Direct3D, so we mark them free 
    // (D3DPAL_FREE).

    // NOTE: In order for the palette entries to be correctly
    // allocated, it is essential that the free entries are
    // also marked reserved to GDI (PC_RESERVED).

    // NOTE: We don't need to specify the palette caps flag
    // DDPCAPS_INITIALIZE. This flag is obsolete. CreatePalette
    // must be given a valid palette-entry array and always
    // initializes from it.
        
        hdc = GetDC(NULL);
        GetSystemPaletteEntries(hdc, 0, 256, peColorTable);
        ReleaseDC(NULL, hdc);

        for (i = 0; i < 10; i++)
            peColorTable[i].peFlags = D3DPAL_READONLY;
        for (i = 10; i < 246; i++)
            peColorTable[i].peFlags = D3DPAL_FREE | PC_RESERVED;
        for (i = 246; i < 256; i++)
            peColorTable[i].peFlags = D3DPAL_READONLY;
        hRes = lpdd->lpVtbl->CreatePalette(lpdd,
            DDPCAPS_8BIT, peColorTable, &lpddPalette, NULL);

        if (FAILED(hRes))
            return hRes;

        hRes = lpddPrimary->lpVtbl->SetPalette(lpddPrimary, 
                lpddPalette);
            return hRes;
    }

    return DD_OK;
}

Creating the Direct3D Object

The CreateDirect3D function creates the DirectDraw (Direct3D) driver objects and retrieves the COM interfaces for communicating with these objects. This function calls three crucial methods: DirectDrawCreate, to create the DirectDraw object; IDirectDraw3::SetCooperativeLevel, to determine whether the application will run in full-screen or windowed mode; and IDirectDraw::QueryInterface to retrieve a pointer to the Direct3D interface.

static HRESULT
CreateDirect3D(HWND hwnd)
{
    HRESULT hRes;

    ASSERT(NULL == lpdd);
    ASSERT(NULL == lpd3d);

    // Create the DirectDraw/3-D driver object and get the DirectDraw
    // interface to that object.
    
    hRes = DirectDrawCreate(NULL, &lpdd, NULL);
    if (FAILED(hRes))
        return hRes;

    // Since we are running in a window, set the cooperative level to 
    // normal. Also, to ensure that the palette is realized correctly,
    // we need to pass the window handle of the main window.
    
    hRes = lpdd->lpVtbl->SetCooperativeLevel(lpdd, hwnd, DDSCL_NORMAL);
    if (FAILED(hRes))
        return hRes;

        // Retrieve the Direct3D interface to the DirectDraw/3-D driver 
        // object.
    
    hRes = lpdd->lpVtbl->QueryInterface(lpdd, &IID_IDirect3D, &lpd3d);
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

Creating the Direct3D Device

The CreateDevice function creates an instance of the Direct3D device we chose earlier, using the specified width and height.

This function handles all aspects of the device creation, including choosing the surface-memory type, creating the device surface, creating the z-buffer (if necessary), and attaching the palette (if required). If you create a z-buffer, you must do so before creating an IDirect3DDevice interface.

static HRESULT
CreateDevice(DWORD dwWidth, DWORD dwHeight)
{
    LPD3DDEVICEDESC lpd3dDeviceDesc;
    DWORD           dwDeviceMemType;
    DWORD           dwZBufferMemType;
    DDSURFACEDESC   ddsd;
    HRESULT         hRes;
    DWORD           dwZBufferBitDepth;

    ASSERT(NULL != lpdd);
    ASSERT(NULL != lpd3d);
    ASSERT(NULL != lpddPrimary);
    ASSERT(NULL == lpddDevice);
    ASSERT(NULL == lpd3dDevice);

    // Determine the kind of memory (system or video) from which the 
    // device surface should be allocated.
    
    if (0 != d3dHWDeviceDesc.dcmColorModel)
    {
        lpd3dDeviceDesc = &d3dHWDeviceDesc;

        // Device has a hardware rasterizer. Currently this means that
        // the device surface must be in video memory.
        
        dwDeviceMemType  = DDSCAPS_VIDEOMEMORY;
        dwZBufferMemType = DDSCAPS_VIDEOMEMORY;
    }
    else
    {
        lpd3dDeviceDesc = &d3dSWDeviceDesc;

        // Device has a software rasterizer. We will let DirectDraw
        // decide where the device surface resides unless we are
        // running in debug mode, in which case we will force it into
        // system memory. For a software rasterizer, the z-buffer should
        // always go into system memory. A z-buffer in video memory will
        // seriously degrade the application's performance.
        
        dwDeviceMemType  = (fDebug ? DDSCAPS_SYSTEMMEMORY : 0);
        dwZBufferMemType = DDSCAPS_SYSTEMMEMORY;
    }

    // Create the device surface. The pixel format will be identical
    // to that of the primary surface, so we don't need to explicitly 
    // specify it. We do need to explicitly specify the size, memory 
    // type and capabilities of the surface.
    
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize         = sizeof(ddsd);
    ddsd.dwFlags        = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
    ddsd.dwWidth        = dwWidth;
    ddsd.dwHeight       = dwHeight;
    ddsd.ddsCaps.dwCaps = DDSCAPS_3DDEVICE | DDSCAPS_OFFSCREENPLAIN | 
                          dwDeviceMemType;
    hRes = lpdd->lpVtbl->CreateSurface(lpdd, &ddsd, &lpddDevice, NULL);
    if (FAILED(hRes))
        return hRes;

    // If we have created a palette, we have already determined that
    // the primary surface (and hence the device surface) is palettized. 
    // Therefore, we should attach the palette to the device surface. 
    // (The palette is already attached to the primary surface.)
    
    if (NULL != lpddPalette)
    {
        hRes = lpddDevice->lpVtbl->SetPalette(lpddDevice, lpddPalette);
        if (FAILED(hRes))
            return hRes;
    }

    // We now determine whether or not we need a z-buffer and, if 
    // so, its bit depth. 
    
    if (0 != lpd3dDeviceDesc->dwDeviceZBufferBitDepth)
    {
        // The device supports z-buffering. Determine the depth. We
        // select the lowest supported z-buffer depth to save memory.
        // (Accuracy is not too important for this sample.)
        
        dwZBufferBitDepth = 
            FlagsToBitDepth(lpd3dDeviceDesc->dwDeviceZBufferBitDepth);

        // Create the z-buffer.
        
        ZeroMemory(&ddsd, sizeof(ddsd));
        ddsd.dwSize            = sizeof(ddsd);
        ddsd.dwFlags           = DDSD_CAPS   |
                                 DDSD_WIDTH  |
                                 DDSD_HEIGHT |
                                 DDSD_ZBUFFERBITDEPTH;
        ddsd.ddsCaps.dwCaps    = DDSCAPS_ZBUFFER | dwZBufferMemType;
        ddsd.dwWidth           = dwWidth;
        ddsd.dwHeight          = dwHeight;
        ddsd.dwZBufferBitDepth = dwZBufferBitDepth;
        hRes = lpdd->lpVtbl->CreateSurface(lpdd, &ddsd, &lpddZBuffer,
                                           NULL);
        if (FAILED(hRes))
            return hRes;

                // Attach it to the rendering target.
        
        hRes = lpddDevice->lpVtbl->AddAttachedSurface(lpddDevice, 
                                                      lpddZBuffer);
        if (FAILED(hRes))
            return hRes;
    }

    // Now all the elements are in place: the device surface is in the 
    // correct memory type; a z-buffer has been attached with the 
    // correct depth and memory type; and a palette has been attached, 
    // if necessary. Now we can query for the Direct3D device we chose 
    // earlier.
    
    hRes = lpddDevice->lpVtbl->QueryInterface(lpddDevice,
                                   &guidDevice, &lpd3dDevice);
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

Creating the Scene

The CreateScene function creates the elements making up the 3-D scene. In this sample, the scene consists of a single light, the viewport, the background and surface materials, the three transformation matrices, and the execute buffer holding the state changes and drawing primitives.

static HRESULT
CreateScene(void)
{
    HRESULT              hRes;
    D3DMATERIAL          d3dMaterial;
    D3DLIGHT             d3dLight;
    DWORD                dwVertexSize;
    DWORD                dwInstructionSize;
    DWORD                dwExecuteBufferSize;
    D3DEXECUTEBUFFERDESC d3dExecuteBufferDesc;
    D3DEXECUTEDATA       d3dExecuteData;

    ASSERT(NULL != lpd3d);
    ASSERT(NULL != lpd3dDevice);
    ASSERT(NULL == lpd3dViewport);
    ASSERT(NULL == lpd3dMaterial);
    ASSERT(NULL == lpd3dBackgroundMaterial);
    ASSERT(NULL == lpd3dExecuteBuffer);
    ASSERT(NULL == lpd3dLight);
    ASSERT(0  == hd3dWorldMatrix);
    ASSERT(0  == hd3dViewMatrix);
    ASSERT(0  == hd3dProjMatrix);

    // Create the light.
    
    hRes = lpd3d->lpVtbl->CreateLight(lpd3d, &lpd3dLight, NULL);
    if (FAILED(hRes))
        return hRes;

    ZeroMemory(&d3dLight, sizeof(d3dLight));
    d3dLight.dwSize = sizeof(d3dLight);
    d3dLight.dltType = D3DLIGHT_POINT;
    d3dLight.dcvColor.dvR    = D3DVAL( 1.0);
    d3dLight.dcvColor.dvG    = D3DVAL( 1.0);
    d3dLight.dcvColor.dvB    = D3DVAL( 1.0);
    d3dLight.dcvColor.dvA    = D3DVAL( 1.0);
    d3dLight.dvPosition.dvX  = D3DVAL( 1.0);
    d3dLight.dvPosition.dvY  = D3DVAL(-1.0);
    d3dLight.dvPosition.dvZ  = D3DVAL(-1.0);
    d3dLight.dvAttenuation0  = D3DVAL( 1.0);
    d3dLight.dvAttenuation1  = D3DVAL( 0.1);
    d3dLight.dvAttenuation2  = D3DVAL( 0.0);
    hRes = lpd3dLight->lpVtbl->SetLight(lpd3dLight, &d3dLight);
    if (FAILED(hRes))
        return hRes;

    // Create the background material.
    
    hRes = lpd3d->lpVtbl->CreateMaterial(lpd3d, 
        &lpd3dBackgroundMaterial, NULL);
    if (FAILED(hRes))
        return hRes;

    ZeroMemory(&d3dMaterial, sizeof(d3dMaterial));
    d3dMaterial.dwSize = sizeof(d3dMaterial);
    d3dMaterial.dcvDiffuse.r  = D3DVAL(0.0);
    d3dMaterial.dcvDiffuse.g  = D3DVAL(0.0);
    d3dMaterial.dcvDiffuse.b  = D3DVAL(0.0);
    d3dMaterial.dcvAmbient.r  = D3DVAL(0.0);
    d3dMaterial.dcvAmbient.g  = D3DVAL(0.0);
    d3dMaterial.dcvAmbient.b  = D3DVAL(0.0);
    d3dMaterial.dcvSpecular.r = D3DVAL(0.0);
    d3dMaterial.dcvSpecular.g = D3DVAL(0.0);
    d3dMaterial.dcvSpecular.b = D3DVAL(0.0);
    d3dMaterial.dvPower       = D3DVAL(0.0);

    // Since this is the background material, we don't want a ramp to be 
    // allocated. (We will not be smooth-shading the background.)
    
    d3dMaterial.dwRampSize    = 1;
    
    hRes = lpd3dBackgroundMaterial->lpVtbl->SetMaterial
        (lpd3dBackgroundMaterial, &d3dMaterial);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dBackgroundMaterial->lpVtbl->GetHandle
        (lpd3dBackgroundMaterial, lpd3dDevice, &hd3dBackgroundMaterial);
    if (FAILED(hRes))
        return hRes;

    // Create the viewport.
    // The viewport parameters are set in the UpdateViewport function, 
    // which is called in response to WM_SIZE.
    
    hRes = lpd3d->lpVtbl->CreateViewport(lpd3d, &lpd3dViewport, NULL);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dDevice->lpVtbl->AddViewport(lpd3dDevice, lpd3dViewport);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dViewport->lpVtbl->SetBackground(lpd3dViewport, 
        hd3dBackgroundMaterial);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dViewport->lpVtbl->AddLight(lpd3dViewport, lpd3dLight);
    if (FAILED(hRes))
        return hRes;

    // Create the matrices.
    
    hRes = lpd3dDevice->lpVtbl->CreateMatrix(lpd3dDevice, 
        &hd3dWorldMatrix);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dDevice->lpVtbl->SetMatrix(lpd3dDevice, hd3dWorldMatrix, 
        &d3dWorldMatrix);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dDevice->lpVtbl->CreateMatrix(lpd3dDevice, 
        &hd3dViewMatrix);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dDevice->lpVtbl->SetMatrix(lpd3dDevice, hd3dViewMatrix, 
        &d3dViewMatrix);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dDevice->lpVtbl->CreateMatrix(lpd3dDevice, 
        &hd3dProjMatrix);
    if (FAILED(hRes))
        return hRes;
    SetPerspectiveProjection(&d3dProjMatrix, HALF_HEIGHT, FRONT_CLIP, 
        BACK_CLIP);
    hRes = lpd3dDevice->lpVtbl->SetMatrix(lpd3dDevice, hd3dProjMatrix,
        &d3dProjMatrix);
    if (FAILED(hRes))
        return hRes;

    // Create the surface material.
    
    hRes = lpd3d->lpVtbl->CreateMaterial(lpd3d, &lpd3dMaterial, NULL);
    if (FAILED(hRes))
        return hRes;
    ZeroMemory(&d3dMaterial, sizeof(d3dMaterial));
    d3dMaterial.dwSize = sizeof(d3dMaterial);

    // Base green with white specular.
    
    d3dMaterial.dcvDiffuse.r  = D3DVAL(0.0);
    d3dMaterial.dcvDiffuse.g  = D3DVAL(1.0);
    d3dMaterial.dcvDiffuse.b  = D3DVAL(0.0);
    d3dMaterial.dcvAmbient.r  = D3DVAL(0.0);
    d3dMaterial.dcvAmbient.g  = D3DVAL(0.4);
    d3dMaterial.dcvAmbient.b  = D3DVAL(0.0);
    d3dMaterial.dcvSpecular.r = D3DVAL(1.0);
    d3dMaterial.dcvSpecular.g = D3DVAL(1.0);
    d3dMaterial.dcvSpecular.b = D3DVAL(1.0);
    d3dMaterial.dvPower       = D3DVAL(20.0);
    d3dMaterial.dwRampSize    = 16;
    
    hRes = lpd3dMaterial->lpVtbl->SetMaterial(lpd3dMaterial, 
        &d3dMaterial);
    if (FAILED(hRes))
        return hRes;

    hRes = lpd3dMaterial->lpVtbl->GetHandle(lpd3dMaterial, lpd3dDevice, 
        &hd3dSurfaceMaterial);
    if (FAILED(hRes))
        return hRes;

    // Build the execute buffer.
    
    dwVertexSize      = (NUM_VERTICES        * sizeof(D3DVERTEX));
    dwInstructionSize = (NUM_INSTRUCTIONS    * sizeof(D3DINSTRUCTION)) +
                        (NUM_STATES          * sizeof(D3DSTATE))       +
                        (NUM_PROCESSVERTICES * 
                                        sizeof(D3DPROCESSVERTICES))    +
                        (NUM_TRIANGLES      * sizeof(D3DTRIANGLE));
    dwExecuteBufferSize = dwVertexSize + dwInstructionSize;
    ZeroMemory(&d3dExecuteBufferDesc, sizeof(d3dExecuteBufferDesc));
    d3dExecuteBufferDesc.dwSize       = sizeof(d3dExecuteBufferDesc);
    d3dExecuteBufferDesc.dwFlags      = D3DDEB_BUFSIZE;
    d3dExecuteBufferDesc.dwBufferSize = dwExecuteBufferSize;
    hRes = lpd3dDevice->lpVtbl->CreateExecuteBuffer(lpd3dDevice,
        &d3dExecuteBufferDesc, &lpd3dExecuteBuffer, NULL);
    if (FAILED(hRes))
        return hRes;

    // Fill the execute buffer with the required vertices, state
    // instructions, and drawing primitives.
    
    hRes = FillExecuteBuffer();
    if (FAILED(hRes))
        return hRes;

    // Set the execute data so Direct3D knows how many vertices are in 
    // the buffer and where the instructions start.
    
    ZeroMemory(&d3dExecuteData, sizeof(d3dExecuteData));
    d3dExecuteData.dwSize = sizeof(d3dExecuteData);
    d3dExecuteData.dwVertexCount       = NUM_VERTICES;
    d3dExecuteData.dwInstructionOffset = dwVertexSize;
    d3dExecuteData.dwInstructionLength = dwInstructionSize;
    hRes = lpd3dExecuteBuffer->lpVtbl->SetExecuteData
        (lpd3dExecuteBuffer, &d3dExecuteData);
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

Filling the Execute Buffer

The FillExecuteBuffer function fills the single execute buffer used in this sample with all the vertices, transformations, light and render states, and drawing primitives necessary to draw our triangle.

Note   This is not the most efficient way to organize the execute buffer. For best performance, you should minimize state changes. In this sample, we submit the execute buffer for each frame in the animation loop and no state in the buffer is modified. The only thing we modify is the world matrix (its contents—not its handle). Therefore, it would be more efficient to extract all the static state instructions into a separate execute buffer that we would issue once only at startup and, from then on, simply execute a second execute buffer with vertices and triangles.

However, because this sample is more concerned with clarity than performance, it uses only one execute buffer and resubmits the buffer in its entirety for each frame.

static HRESULT
FillExecuteBuffer(void)
{
    HRESULT              hRes;
    D3DEXECUTEBUFFERDESC d3dExeBufDesc;
    LPD3DVERTEX          lpVertex;
    LPD3DINSTRUCTION     lpInstruction;
    LPD3DPROCESSVERTICES lpProcessVertices;
    LPD3DTRIANGLE        lpTriangle;
    LPD3DSTATE           lpState;

    ASSERT(NULL != lpd3dExecuteBuffer);
    ASSERT(0  != hd3dSurfaceMaterial);
    ASSERT(0  != hd3dWorldMatrix);
    ASSERT(0  != hd3dViewMatrix);
    ASSERT(0  != hd3dProjMatrix);

    // Lock the execute buffer.
    
    ZeroMemory(&d3dExeBufDesc, sizeof(d3dExeBufDesc));
    d3dExeBufDesc.dwSize = sizeof(d3dExeBufDesc);
    hRes = lpd3dExecuteBuffer->lpVtbl->Lock(lpd3dExecuteBuffer, 
        &d3dExeBufDesc);
    if (FAILED(hRes))
        return hRes;

    // For purposes of illustration, we fill the execute buffer by 
    // casting a pointer to the execute buffer to the appropriate data 
    // structures.
    
    lpVertex = (LPD3DVERTEX)d3dExeBufDesc.lpData;

    // First vertex.
    
    lpVertex->dvX  = D3DVAL( 0.0); // Position in model coordinates      
    lpVertex->dvY  = D3DVAL( 1.0);
    lpVertex->dvZ  = D3DVAL( 0.0);
    lpVertex->dvNX = D3DVAL( 0.0); // Normalized illumination normal     
    lpVertex->dvNY = D3DVAL( 0.0);
    lpVertex->dvNZ = D3DVAL(-1.0);
    lpVertex->dvTU = D3DVAL( 0.0); // Texture coordinates (not used) 
    lpVertex->dvTV = D3DVAL( 1.0);
    lpVertex++;

    // Second vertex.
    
    lpVertex->dvX  = D3DVAL( 1.0); // Position in model coordinates      
    lpVertex->dvY  = D3DVAL(-1.0);
    lpVertex->dvZ  = D3DVAL( 0.0);
    lpVertex->dvNX = D3DVAL( 0.0); // Normalized illumination normal     
    lpVertex->dvNY = D3DVAL( 0.0);
    lpVertex->dvNZ = D3DVAL(-1.0);
    lpVertex->dvTU = D3DVAL( 1.0); // Texture coordinates (not used)
    lpVertex->dvTV = D3DVAL( 1.0);
    lpVertex++;

    // Third vertex.
    
    lpVertex->dvX  = D3DVAL(-1.0); // Position in model coordinates      
    lpVertex->dvY  = D3DVAL(-1.0);
    lpVertex->dvZ  = D3DVAL( 0.0);
    lpVertex->dvNX = D3DVAL( 0.0); // Normalized illumination normal     
    lpVertex->dvNY = D3DVAL( 0.0);
    lpVertex->dvNZ = D3DVAL(-1.0);
    lpVertex->dvTU = D3DVAL( 1.0); // Texture coordinates (not used)
    lpVertex->dvTV = D3DVAL( 0.0);
    lpVertex++;

    // Transform state - world, view and projection.
    
    lpInstruction = (LPD3DINSTRUCTION)lpVertex;
    lpInstruction->bOpcode = D3DOP_STATETRANSFORM;
    lpInstruction->bSize   = sizeof(D3DSTATE);
    lpInstruction->wCount  = 3U;
    lpInstruction++;
    lpState = (LPD3DSTATE)lpInstruction;
    lpState->dtstTransformStateType = D3DTRANSFORMSTATE_WORLD;
    lpState->dwArg[0] = hd3dWorldMatrix;
    lpState++;
    lpState->dtstTransformStateType = D3DTRANSFORMSTATE_VIEW;
    lpState->dwArg[0] = hd3dViewMatrix;
    lpState++;
    lpState->dtstTransformStateType = D3DTRANSFORMSTATE_PROJECTION;
    lpState->dwArg[0] = hd3dProjMatrix;
    lpState++;

    // Lighting state.
    
    lpInstruction = (LPD3DINSTRUCTION)lpState;
    lpInstruction->bOpcode = D3DOP_STATELIGHT;
    lpInstruction->bSize   = sizeof(D3DSTATE);
    lpInstruction->wCount  = 2U;
    lpInstruction++;
    lpState = (LPD3DSTATE)lpInstruction;
    lpState->dlstLightStateType = D3DLIGHTSTATE_MATERIAL;
    lpState->dwArg[0] = hd3dSurfaceMaterial;
    lpState++;
    lpState->dlstLightStateType = D3DLIGHTSTATE_AMBIENT;
    lpState->dwArg[0] = RGBA_MAKE(128, 128, 128, 128);
    lpState++;

    // Render state.
    
    lpInstruction = (LPD3DINSTRUCTION)lpState;
    lpInstruction->bOpcode = D3DOP_STATERENDER;
    lpInstruction->bSize = sizeof(D3DSTATE);
    lpInstruction->wCount = 3U;
    lpInstruction++;
    lpState = (LPD3DSTATE)lpInstruction;
    lpState->drstRenderStateType = D3DRENDERSTATE_FILLMODE;
    lpState->dwArg[0] = D3DFILL_SOLID;
    lpState++;
    lpState->drstRenderStateType = D3DRENDERSTATE_SHADEMODE;
    lpState->dwArg[0] = D3DSHADE_GOURAUD;
    lpState++;
    lpState->drstRenderStateType = D3DRENDERSTATE_DITHERENABLE;
    lpState->dwArg[0] = TRUE;
    lpState++;

    // The D3DOP_PROCESSVERTICES instruction tells the driver what to
    // do with the vertices in the buffer. In this sample, we want
    // Direct3D to perform the entire pipeline on our behalf, so
    // the instruction is D3DPROCESSVERTICES_TRANSFORMLIGHT.
    
    lpInstruction = (LPD3DINSTRUCTION)lpState;
    lpInstruction->bOpcode = D3DOP_PROCESSVERTICES;
    lpInstruction->bSize   = sizeof(D3DPROCESSVERTICES);
    lpInstruction->wCount  = 1U;
    lpInstruction++;
    lpProcessVertices = (LPD3DPROCESSVERTICES)lpInstruction;
    lpProcessVertices->dwFlags    = D3DPROCESSVERTICES_TRANSFORMLIGHT;
    lpProcessVertices->wStart     = 0U;           // First source vertex
    lpProcessVertices->wDest      = 0U;
    lpProcessVertices->dwCount    = NUM_VERTICES; // Number of vertices 
    lpProcessVertices->dwReserved = 0;
    lpProcessVertices++;

    // Draw the triangle.
    
    lpInstruction = (LPD3DINSTRUCTION)lpProcessVertices;
    lpInstruction->bOpcode = D3DOP_TRIANGLE;
    lpInstruction->bSize   = sizeof(D3DTRIANGLE);
    lpInstruction->wCount  = 1U;
    lpInstruction++;
    lpTriangle = (LPD3DTRIANGLE)lpInstruction;
    lpTriangle->wV1    = 0U; 
    lpTriangle->wV2    = 1U;
    lpTriangle->wV3    = 2U;
    lpTriangle->wFlags = D3DTRIFLAG_EDGEENABLETRIANGLE;
    lpTriangle++;

    // Stop execution of the buffer.
    
    lpInstruction = (LPD3DINSTRUCTION)lpTriangle;
    lpInstruction->bOpcode = D3DOP_EXIT;
    lpInstruction->bSize   = 0;
    lpInstruction->wCount  = 0U;

    // Unlock the execute buffer.
    
    lpd3dExecuteBuffer->lpVtbl->Unlock(lpd3dExecuteBuffer);

    return DD_OK;
}

Animating the Scene

The animation in this sample is simply a rotation about the y-axis. All we need to do is build a rotation matrix and set the world matrix to that new rotation matrix.

We don't need to modify the execute buffer in any way to perform this rotation. We simply set the matrix and resubmit the execute buffer.

static HRESULT
AnimateScene(void)
{
    HRESULT hRes;

    ASSERT(NULL != lpd3dDevice);
    ASSERT(0  != hd3dWorldMatrix);

    // We rotate the triangle by setting the world transform to a
    // rotation matrix.
    
    SetRotationAboutY(&d3dWorldMatrix, dAngleOfRotation);
    dAngleOfRotation += ROTATE_ANGLE_DELTA;
    hRes = lpd3dDevice->lpVtbl->SetMatrix(lpd3dDevice,
        hd3dWorldMatrix, &d3dWorldMatrix);
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

Rendering

This section contains functions that render the entire scene and render a single frame.

Rendering the Scene

The RenderScene function renders the 3-D scene, just as you might expect. The fundamental task performed by this function is submitting the single execute buffer used by this sample. However, the function also clears the back and z-buffers and demarcates the start and end of the scene (which, in this case, is a single execute).

When you clear the back and z-buffers, it's safe to specify the z-buffer clear flag even if we don't have an attached z-buffer. Direct3D will simply discard the flag if no z-buffer is being used.

For maximum efficiency, we only want to clear those regions of the device surface and z-buffer that we actually rendered to in the last frame. This is the purpose of the array of rectangles and count passed to this function. It is possible to query Direct3D for the regions of the device surface that were rendered to by that execute. The application can then accumulate those rectangles and clear only those regions. However this is a very simple sample and so, for simplicity, we will clear the entire device surface and z-buffer. You should probably implement a more efficient clearing mechanism in your own application.

The RenderScene function must be called once and only once for every frame of animation. If you have multiple execute buffers comprising a single frame, you must have one call to the IDirect3DDevice::BeginScene method before submitting those execute buffers. If you have more than one device being rendered in a single frame (for example, a rear-view mirror in a racing game), call the IDirect3DDevice::BeginScene and IDirect3DDevice::EndScene methods once for each device.

When the RenderScene function returns DD_OK, the scene will have been rendered and the device surface will hold the contents of the rendering.

static HRESULT
RenderScene(void)
{
    HRESULT hRes;
    D3DRECT d3dRect;

    ASSERT(NULL != lpd3dViewport);
    ASSERT(NULL != lpd3dDevice);
    ASSERT(NULL != lpd3dExecuteBuffer);

    // Clear both back and z-buffer.
    
    d3dRect.lX1 = rSrcRect.left;
    d3dRect.lX2 = rSrcRect.right;
    d3dRect.lY1 = rSrcRect.top;
    d3dRect.lY2 = rSrcRect.bottom;
    hRes = lpd3dViewport->lpVtbl->Clear(lpd3dViewport, 1, &d3dRect,
        D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER);
    if (FAILED(hRes))
        return hRes;

    // Start the scene.
    
    hRes = lpd3dDevice->lpVtbl->BeginScene(lpd3dDevice);
    if (FAILED(hRes))
        return hRes;

    // Submit the execute buffer.
     
    // We want Direct3D to clip the data on our behalf, so we specify
    // D3DEXECUTE_CLIPPED.
    
    hRes = lpd3dDevice->lpVtbl->Execute(lpd3dDevice, lpd3dExecuteBuffer,
        lpd3dViewport, D3DEXECUTE_CLIPPED);
    if (FAILED(hRes))
    {
        lpd3dDevice->lpVtbl->EndScene(lpd3dDevice);
        return hRes;
    }

        // End the scene.
    
    hRes = lpd3dDevice->lpVtbl->EndScene(lpd3dDevice);
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

Rendering a Single Frame

The DoFrame function renders and shows a single frame. This involves rendering the scene and blitting the result to the client area of the application window on the primary surface.

This function handles lost surfaces by attempting to restore the application's surfaces and then retrying the rendering. It is called by the OnMove function (discussed in Redrawing on Window Movement), the OnSize function (discussed in Redrawing on Window Resizing), and the OnPaint function (discussed in Repainting the Client Area).

static HRESULT
DoFrame(void)
{
    HRESULT hRes;

    // We keep trying until we succeed or fail for a reason
    // other than DDERR_SURFACELOST.
    
    while (TRUE)
    {
        hRes = RenderScene();
        if (SUCCEEDED(hRes))
        {
            hRes = lpddPrimary->lpVtbl->Blt(lpddPrimary, &rDstRect,
                lpddDevice, &rSrcRect, DDBLT_WAIT, NULL);
            if (SUCCEEDED(hRes))  // If it worked. 
                return hRes;
        }
        while (DDERR_SURFACELOST == hRes)  // Restore lost surfaces
            hRes = RestoreSurfaces();
        if (FAILED(hRes))  // handle other failure cases
            return hRes;
    }
}

Working with Matrices

This section contains two functions that work with matrices: the SetPerspectiveProjection function, which sets a given matrix to the appropriate values for the front and back clipping planes, and the SetRotationAboutY function, which sets a matrix to a rotation about the y-axis. 

Setting the Perspective Transformation

The SetPerspectiveProjection function sets the given matrix to a perspective transform for the given half-height and front- and back-clipping planes. This function is called as part of the CreateScene function, documented in Creating the Scene.

static void
SetPerspectiveProjection(LPD3DMATRIX lpd3dMatrix,
                         double      dHalfHeight,
                         double      dFrontClipping,
                         double      dBackClipping)
{
    double dTmp1;
    double dTmp2;

    ASSERT(NULL != lpd3dMatrix);

    dTmp1 = dHalfHeight / dFrontClipping;
    dTmp2 = dBackClipping / (dBackClipping - dFrontClipping);

    lpd3dMatrix->_11 =  D3DVAL(2.0);
    lpd3dMatrix->_12 =  D3DVAL(0.0);
    lpd3dMatrix->_13 =  D3DVAL(0.0);
    lpd3dMatrix->_14 =  D3DVAL(0.0);
    lpd3dMatrix->_21 =  D3DVAL(0.0);
    lpd3dMatrix->_22 =  D3DVAL(2.0);
    lpd3dMatrix->_23 =  D3DVAL(0.0);
    lpd3dMatrix->_24 =  D3DVAL(0.0);
    lpd3dMatrix->_31 =  D3DVAL(0.0);
    lpd3dMatrix->_32 =  D3DVAL(0.0);
    lpd3dMatrix->_33 =  D3DVAL(dTmp1 * dTmp2);
    lpd3dMatrix->_34 =  D3DVAL(dTmp1);
    lpd3dMatrix->_41 =  D3DVAL(0.0);
    lpd3dMatrix->_42 =  D3DVAL(0.0);
    lpd3dMatrix->_43 =  D3DVAL(-dHalfHeight * dTmp2);
    lpd3dMatrix->_44 =  D3DVAL(0.0);
}

Setting a Rotation Transformation

The SetRotationAboutY function sets the given matrix to a rotation about the y-axis, using the specified number of radians. This function is called as part of the AnimateScene function, documented in Animating the Scene.

static void
SetRotationAboutY(LPD3DMATRIX lpd3dMatrix, double dAngleOfRotation)
{
    D3DVALUE dvCos;
    D3DVALUE dvSin;

    ASSERT(NULL != lpd3dMatrix);

    dvCos = D3DVAL(cos(dAngleOfRotation));
    dvSin = D3DVAL(sin(dAngleOfRotation));

    lpd3dMatrix->_11 =  dvCos;
    lpd3dMatrix->_12 =  D3DVAL(0.0);
    lpd3dMatrix->_13 = -dvSin;
    lpd3dMatrix->_14 =  D3DVAL(0.0);
    lpd3dMatrix->_21 =  D3DVAL(0.0);
    lpd3dMatrix->_22 =  D3DVAL(1.0);
    lpd3dMatrix->_23 =  D3DVAL(0.0);
    lpd3dMatrix->_24 =  D3DVAL(0.0);
    lpd3dMatrix->_31 =  dvSin;
    lpd3dMatrix->_32 =  D3DVAL(0.0);
    lpd3dMatrix->_33 =  dvCos;
    lpd3dMatrix->_34 =  D3DVAL(0.0);
    lpd3dMatrix->_41 =  D3DVAL(0.0);
    lpd3dMatrix->_42 =  D3DVAL(0.0);
    lpd3dMatrix->_43 =  D3DVAL(0.0);
    lpd3dMatrix->_44 =  D3DVAL(1.0);
}

Restoring and Redrawing

This section contains functions that restore objects and surfaces that may have been lost while the application is running.

Restoring the Direct3D Device

The RestoreDevice function restores lost video memory for the device surface and z-buffer.

static HRESULT
RestoreDevice(void)
{
    HRESULT hRes;

    if (NULL != lpddZBuffer)
    {
        hRes = lpddZBuffer->lpVtbl->Restore(lpddZBuffer);
        if (FAILED(hRes))
            return hRes;
    }

    if (NULL != lpddDevice)
    {
        hRes = lpddDevice->lpVtbl->Restore(lpddDevice);
        if (FAILED(hRes))
            return hRes;
    }

    return DD_OK;
}

Restoring the Primary Surface

The RestorePrimary function attempts to restore the video memory allocated for the primary surface. This function will be invoked by a DirectX function returning DDERR_SURFACELOST due to a mode switch or full-screen DOS box invalidating video memory.

static HRESULT
RestorePrimary(void)
{
    ASSERT(NULL != lpddPrimary);

    return lpddPrimary->lpVtbl->Restore(lpddPrimary);
}

Restoring All Surfaces

The RestoreSurfaces function attempts to restore all the surfaces used by the application.

static LRESULT
RestoreSurfaces(void)
{
    HRESULT hRes;

    hRes = RestorePrimary();
    if (FAILED(hRes))
        return hRes;

    hRes = RestoreDevice();
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

Redrawing on Window Movement

static LRESULT
OnMove(HWND hwnd, int x, int y)
{
    int     xDelta;
    int     yDelta;
    HRESULT hRes;

    // No action if the device has not yet been created or if we are
    // suspended.
    
    if ((NULL != lpd3dDevice) && !fSuspended)
    {
        // Update the destination rectangle for the new client position.
        
        xDelta = x - rDstRect.left;
        yDelta = y - rDstRect.top;

        rDstRect.left   += xDelta;
        rDstRect.top    += yDelta;
        rDstRect.right  += xDelta;
        rDstRect.bottom += yDelta;

        // Repaint the client area.
        
        hRes = DoFrame();
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_RENDERSCENE, hRes);
            return 0L;
        }
    }

    return 0L;
}

Redrawing on Window Resizing

static LRESULT
OnSize(HWND hwnd, int w, int h)
{
    HRESULT       hRes;
    DDSURFACEDESC ddsd;

        // Nothing to do if we are suspended.
    
    if (!fSuspended)
    {
        // Update the source and destination rectangles (used by the
        // blit that shows the rendering in the client area).
        
        rDstRect.right  = rDstRect.left + w;
        rDstRect.bottom = rDstRect.top  + h;
        rSrcRect.right  = w;
        rSrcRect.bottom = h;

        if (NULL != lpd3dDevice)
        {
            // Although we already have a device, we need to be sure it 
            // is big enough for the new window client size.
             
            // Because the window in this sample has a fixed size, it 
            // should never be necessary to handle this case. This code 
            // will be useful when we make the application resizable.
            
            ZeroMemory(&ddsd, sizeof(ddsd));
            ddsd.dwSize = sizeof(ddsd);
            hRes = lpddDevice->lpVtbl->GetSurfaceDesc(lpddDevice, 
                &ddsd);
            if (FAILED(hRes))
            {
                FatalError(hwnd, IDS_ERRMSG_DEVICESIZE, hRes);
                return 0L;
            }
    
            if ((w > (int)ddsd.dwWidth) || (h > (int)ddsd.dwHeight))
            {
                // The device is too small. We need to shut it down
                // and rebuild it.

                // Execute buffers are bound to devices, so when 
                // we release the device we must release the execute
                // buffer.
                
                ReleaseScene();
                ReleaseDevice();
            }
        }

        if (NULL == lpd3dDevice)
        {
            // No Direct3D device yet. This is either because this is 
            // the first time through the loop or because we discarded 
            // the existing device because it was not big enough for the 
            // new window client size.
            
            hRes = CreateDevice((DWORD)w, (DWORD)h);
            if (FAILED(hRes))
            {
                FatalError(hwnd, IDS_ERRMSG_CREATEDEVICE, hRes);
                return 0L;
            }
            hRes = CreateScene();
            if (FAILED(hRes))
            {
                FatalError(hwnd, IDS_ERRMSG_BUILDSCENE, hRes);
                return 0L;
            }
        }

        hRes = UpdateViewport();
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_UPDATEVIEWPORT, hRes);
            return 0L;
        }

        // Render at the new size and show the results in the window's
        // client area.
        
        hRes = DoFrame();
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_RENDERSCENE, hRes);
            return 0L;
        }
    }

    return 0L;
}

Repainting the Client Area

The OnPaint function repaints the client area, when required. Notice that it calls the DoFrame function to do much of the work, even though DoFrame rerenders the scene as well as blitting the result to the primary surface. Although the rerendering is not necessary, for this simple sample this inefficiency does not matter. In your application, you should rerender only when the scene changes.

For more information about the DoFrame function, see Rendering a Single Frame. The DoFrame function renders and shows a single frame. This involves rendering the scene and blitting the result to the client area of the application window on the primary surface.

static LRESULT
OnPaint(HWND hwnd, HDC hdc, LPPAINTSTRUCT lpps)
{
    HRESULT hRes;

    USE_PARAM(lpps);

    if (fActive && !fSuspended && (NULL != lpd3dDevice))
    {
        hRes = DoFrame();
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_RENDERSCENE, hRes);
            return 0L;
        }
    }
    else
    {
        // Show the suspended image if we are not active or suspended or
        // if we have not yet created the device.
        
        PaintSuspended(hwnd, hdc);
    }

    return 0L;
}

Updating the Viewport

The UpdateViewport function updates the viewport in response to a change in window size. This ensures that we render at a resolution that matches the client area of the target window.

static HRESULT
UpdateViewport(void)
{
    D3DVIEWPORT d3dViewport;

    ASSERT(NULL != lpd3dViewport);

    ZeroMemory(&d3dViewport, sizeof(d3dViewport));
    d3dViewport.dwSize   = sizeof(d3dViewport);
    d3dViewport.dwX      = 0;
    d3dViewport.dwY      = 0;
    d3dViewport.dwWidth  = (DWORD)rSrcRect.right;
    d3dViewport.dwHeight = (DWORD)rSrcRect.bottom;
    d3dViewport.dvScaleX = D3DVAL((float)d3dViewport.dwWidth / 2.0);
    d3dViewport.dvScaleY = D3DVAL((float)d3dViewport.dwHeight / 2.0);
    d3dViewport.dvMaxX   = D3DVAL(1.0);
    d3dViewport.dvMaxY   = D3DVAL(1.0);
    return lpd3dViewport->lpVtbl->SetViewport(lpd3dViewport, 
        &d3dViewport);
}

Releasing Objects

This section contains functions that release objects when they are no longer needed. 

Releasing the Direct3D Object

The ReleaseDirect3D function releases the DirectDraw (Direct3D) driver object.

static HRESULT
ReleaseDirect3D(void)
{
    if (NULL != lpd3d)
    {
        lpd3d->lpVtbl->Release(lpd3d);
        lpd3d = NULL;
    }
    if (NULL != lpdd)
    {
        lpdd->lpVtbl->Release(lpdd);
        lpdd = NULL;
    }

    return DD_OK;
}

Releasing the Direct3D Device

The ReleaseDevice function releases the Direct3D device and its associated surfaces.

static HRESULT
ReleaseDevice(void)
{
    if (NULL != lpd3dDevice)
    {
        lpd3dDevice->lpVtbl->Release(lpd3dDevice);
        lpd3dDevice = NULL;
    }
    if (NULL != lpddZBuffer)
    {
        lpddZBuffer->lpVtbl->Release(lpddZBuffer);
        lpddZBuffer = NULL;
    }
    if (NULL != lpddDevice)
    {
        lpddDevice->lpVtbl->Release(lpddDevice);
        lpddDevice = NULL;
    }

    return DD_OK;
}

Releasing the Primary Surface

The ReleasePrimary function releases the primary surface and its attached clipper and palette.

static HRESULT
ReleasePrimary(void)
{
    if (NULL != lpddPalette)
    {
        lpddPalette->lpVtbl->Release(lpddPalette);
        lpddPalette = NULL;
    }
    if (NULL != lpddPrimary)
    {
        lpddPrimary->lpVtbl->Release(lpddPrimary);
        lpddPrimary = NULL;
    }

    return DD_OK;
}

Releasing the Objects in the Scene

The ReleaseScene function releases all the objects making up the 3-D scene.

static HRESULT
ReleaseScene(void)
{
    if (NULL != lpd3dExecuteBuffer)
    {
        lpd3dExecuteBuffer->lpVtbl->Release(lpd3dExecuteBuffer);
        lpd3dExecuteBuffer = NULL;
    }
    if (NULL != lpd3dBackgroundMaterial)
    {
        lpd3dBackgroundMaterial->
            lpVtbl->Release(lpd3dBackgroundMaterial);
        lpd3dBackgroundMaterial = NULL;
    }
    if (NULL != lpd3dMaterial)
    {
        lpd3dMaterial->lpVtbl->Release(lpd3dMaterial);
        lpd3dMaterial = NULL;
    }
    if (0 != hd3dWorldMatrix)
    {
        lpd3dDevice->lpVtbl->DeleteMatrix(lpd3dDevice, hd3dWorldMatrix);
        hd3dWorldMatrix = 0;
    }
    if (0 != hd3dViewMatrix)
    {
        lpd3dDevice->lpVtbl->DeleteMatrix(lpd3dDevice, hd3dViewMatrix);
        hd3dViewMatrix = 0;
    }
    if (0 != hd3dProjMatrix)
    {
        lpd3dDevice->lpVtbl->DeleteMatrix(lpd3dDevice, hd3dProjMatrix);
        hd3dProjMatrix = 0;
    }
    if (NULL != lpd3dLight)
    {
        lpd3dLight->lpVtbl->Release(lpd3dLight);
        lpd3dLight = NULL;
    }
    if (NULL != lpd3dViewport)
    {
        lpd3dViewport->lpVtbl->Release(lpd3dViewport);
        lpd3dViewport = NULL;
    }

    return DD_OK;
}

Error Checking

This section contains functions that help you check for and report errors. 

Checking for Active Status

static LRESULT
OnIdle(HWND hwnd)
{
    HRESULT hRes;

    // Only animate if we are the foreground app, we aren't suspended,
    // and we have completed initialization.
    
    if (fActive && !fSuspended && (NULL != lpd3dDevice))
    {
        hRes = AnimateScene();
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_ANIMATESCENE, hRes);
            return 0L;
        }

        hRes = DoFrame();
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_RENDERSCENE, hRes);
            return 0L;
        }
    }

    return 0L;
}

Reporting Standard Errors

The ReportError function displays a message box to report an error.

static void
ReportError(HWND hwnd, int nMessage, HRESULT hRes)
{
    HDC  hdc;
    char szBuffer[256];
    char szMessage[128];
    char szError[128];
    int  nStrID;

    // Turn the animation loop off.
    
    fSuspended = TRUE;

    // Get the high-level error message.
    
    LoadString(hAppInstance, nMessage, szMessage, sizeof(szMessage));

    // We issue sensible error messages for common run-time errors. For
    // errors which are internal or coding errors, we simply issue an
    // error number (they should never occur).
    
    switch (hRes)
    {
        case DDERR_EXCEPTION:        nStrID = IDS_ERR_EXCEPTION;        break;
        case DDERR_GENERIC:          nStrID = IDS_ERR_GENERIC;          break;
        case DDERR_OUTOFMEMORY:      nStrID = IDS_ERR_OUTOFMEMORY;      break;
        case DDERR_OUTOFVIDEOMEMORY: nStrID = IDS_ERR_OUTOFVIDEOMEMORY; break;
        case DDERR_SURFACEBUSY:      nStrID = IDS_ERR_SURFACEBUSY;      break;
        case DDERR_SURFACELOST:      nStrID = IDS_ERR_SURFACELOST;      break;
        case DDERR_WRONGMODE:        nStrID = IDS_ERR_WRONGMODE;        break;
        default:                     nStrID = IDS_ERR_INTERNALERROR;    break;
    }
    LoadString(hAppInstance, nStrID, szError, sizeof(szError));

    // Show the "paused" display.
    
    hdc = GetDC(hwnd);
    PaintSuspended(hwnd, hdc);
    ReleaseDC(hwnd, hdc);

    // Convert the error code into a string.
    
    wsprintf(szBuffer, "%s\n%s (Error #%d)", szMessage, szError, 
        CODEFROMHRESULT(hRes));
    MessageBox(hwnd, szBuffer, WINDOW_TITLE, MB_OK | MB_APPLMODAL);
    fSuspended = FALSE;
}

Reporting Fatal Errors

The FatalError function displays a message box to report an error message and then destroys the window. The function does not perform any cleanup; this is done when the application receives the WM_DESTROY message sent by the DestroyWindow function.

static void
FatalError(HWND hwnd, int nMessage, HRESULT hRes)
{
    ReportError(hwnd, nMessage, hRes);
    fSuspended = TRUE;

    DestroyWindow(hwnd);
}

Displaying a Notification String

The PaintSuspended function draws a notification string in the client area whenever the application is suspended—for example, when the application is in the background or is handling an error.

static void
PaintSuspended(HWND hwnd, HDC hdc)
{
    HPEN     hOldPen;
    HBRUSH   hOldBrush;
    COLORREF crOldTextColor;
    int      oldMode;
    int      x;
    int      y;
    SIZE     size;
    RECT     rect;
    int      nStrLen;

    // Black background.
    
    hOldPen   = SelectObject(hdc, GetStockObject(NULL_PEN));
    hOldBrush = SelectObject(hdc, GetStockObject(BLACK_BRUSH));

    // White text.
    
    oldMode = SetBkMode(hdc, TRANSPARENT);
    crOldTextColor = SetTextColor(hdc, RGB(255, 255, 255));

    GetClientRect(hwnd, &rect);

    // Clear the client area.
    
    Rectangle(hdc, rect.left, rect.top, rect.right + 1, rect.bottom + 1);

    // Draw the string centered in the client area.
    
    nStrLen = strlen(PAUSED_STRING);
    GetTextExtentPoint32(hdc, PAUSED_STRING, nStrLen, &size);
    x = (rect.right  - size.cx) / 2;
    y = (rect.bottom - size.cy) / 2;
    TextOut(hdc, x, y, PAUSED_STRING, nStrLen);

    SetTextColor(hdc, crOldTextColor);
    SetBkMode(hdc, oldMode);

    SelectObject(hdc, hOldBrush);
    SelectObject(hdc, hOldPen);
}

Converting Bit Depths

This section contains functions that convert bit depths into flags and the reverse.

Converting a Bit Depth into a Flag

The BitDepthToFlags function is used by the ChooseDevice enumeration function to convert a bit depth into the appropriate DirectDraw bit-depth flag. For more information, see Enumeration Function

static DWORD
BitDepthToFlags(DWORD dwBitDepth)
{
    switch (dwBitDepth)
    {
        case  1: return DDBD_1;
        case  2: return DDBD_2;
        case  4: return DDBD_4;
        case  8: return DDBD_8;
        case 16: return DDBD_16;
        case 24: return DDBD_24;
        case 32: return DDBD_32;
        default: return 0;     
    }
}

Converting a Flag into a Bit Depth

The FlagsToBitDepth function is used by the CreateDevice function to convert bit-depth flags to an actual bit count. It selects the smallest bit count in the mask if more than one flag is present. For more information, see Creating the Direct3D Device.

static DWORD
FlagsToBitDepth(DWORD dwFlags)
{
    if (dwFlags & DDBD_1)
        return 1;
    else if (dwFlags & DDBD_2)
        return 2;
    else if (dwFlags & DDBD_4)
        return 4;
    else if (dwFlags & DDBD_8)
        return 8;
    else if (dwFlags & DDBD_16)
        return 16;
    else if (dwFlags & DDBD_24)
        return 24;
    else if (dwFlags & DDBD_32)
        return 32;
    else
        return 0; 
}

Main Window Procedure

LRESULT CALLBACK
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HDC         hdc;
    PAINTSTRUCT ps;
    LRESULT     lResult;
    HRESULT     hRes;
    char        szBuffer[128];

    switch (msg)
    {
        case WM_CREATE:
            hRes = CreateDirect3D(hwnd);
            if (FAILED(hRes))
            {
                ReportError(hwnd, IDS_ERRMSG_CREATEDEVICE, hRes);
                ReleaseDirect3D();
                return -1L;
            }

            hRes = CreatePrimary(hwnd);
            if (FAILED(hRes))
            {
                ReportError(hwnd, IDS_ERRMSG_INITSCREEN, hRes);
                ReleasePrimary();
                ReleaseDirect3D();
                return -1L;
            }

            hRes = ChooseDevice();
            if (FAILED(hRes))
            {
                ReportError(hwnd, IDS_ERRMSG_NODEVICE, hRes);
                ReleasePrimary();
                ReleaseDirect3D();
                return -1L;
            }

            // Update the title to show the name of the chosen device.
            
            wsprintf(szBuffer, "%s: %s", WINDOW_TITLE, szDeviceName);
            SetWindowText(hwnd, szBuffer);

            return 0L;

        case WM_MOVE:
            return OnMove(hwnd, (int)LOWORD(lParam), 
                (int)HIWORD(lParam));

        case WM_SIZE:
            return OnSize(hwnd, (int)LOWORD(lParam), 
                (int)HIWORD(lParam));

        case WM_ERASEBKGND:
        // Our rendering fills the entire viewport so we won't bother
        // erasing the background.
            
            return 1L;

        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);

            lResult = OnPaint(hwnd, hdc, &ps);

            EndPaint(hwnd, &ps);
            return lResult;

        case WM_ACTIVATEAPP:
            fActive = (BOOL)wParam;
            if (fActive && !fSuspended && (NULL != lpddPalette))
            {
                // Realizing the palette using DirectDraw is different
                // from GDI. To realize the palette, we call SetPalette
                // each time our application is activated.
                 
                // NOTE: DirectDraw recognizes that the new palette 
                // is the same as the old one and so does not increase 
                // the reference count of the palette.
                
                hRes = lpddPrimary->lpVtbl->SetPalette(lpddPrimary,
                    lpddPalette);
                if (FAILED(hRes))
                {
                    FatalError(hwnd, IDS_ERRMSG_REALIZEPALETTE, hRes);
                    return 0L;
                }

            }
            else
            {
                // If we have been deactived, invalidate to show 
                // the suspended display.
                
                InvalidateRect(hwnd, NULL, FALSE);
            }
            return 0L;

        case WM_KEYUP:
            // We use the escape key as a quick way of 
            // getting out of the application.
            
            if (VK_ESCAPE == (int)wParam)
            {
                DestroyWindow(hwnd);
                return 0L;
            }
            break;

        case WM_CLOSE:
            DestroyWindow(hwnd);
            return 0L;

        case WM_DESTROY:
            // All cleanup is done here when terminating normally or
            // shutting down due to an error.
            
            ReleaseScene();
            ReleaseDevice();
            ReleasePrimary();
            ReleaseDirect3D();

            PostQuitMessage(0);
            return 0L;
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

WinMain Function

int PASCAL
WinMain(HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR     lpszCommandLine,
        int       cmdShow)
{
    WNDCLASS wndClass;
    HWND     hwnd;
    MSG      msg;

    USE_PARAM(hPrevInstance);

    // Record the instance handle.
    
    hAppInstance = hInstance;

    // Very simple command-line processing. We only have one
    // option -- debug -- so we will simply assume that, if anything was
    // specified on the command line, the user wants debug mode. 
    // (In debug mode there is no hardware and all surfaces are 
    // explicitly in system memory.)
    
    if (0 != *lpszCommandLine)
        fDebug = TRUE;

    // Register the window class.
    
    wndClass.style         = 0;
    wndClass.lpfnWndProc   = WndProc;
    wndClass.cbClsExtra    = 0;
    wndClass.cbWndExtra    = 0;
    wndClass.hInstance     = hInstance;
    wndClass.hIcon         = LoadIcon(hAppInstance, 
        MAKEINTRESOURCE(IDI_APPICON));
    wndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wndClass.hbrBackground = GetStockObject(WHITE_BRUSH);
    wndClass.lpszMenuName  = NULL;
    wndClass.lpszClassName = WINDOW_CLASSNAME;

    RegisterClass(&wndClass);

    // Create the main window of the instance.
    
    hwnd = CreateWindow(WINDOW_CLASSNAME,
                        WINDOW_TITLE,
                        WS_OVERLAPPED | WS_SYSMENU,
                        CW_USEDEFAULT, CW_USEDEFAULT,
                        WINDOW_WIDTH, WINDOW_HEIGHT,
                        NULL,
                        NULL,
                        hInstance,
                        NULL);

    ShowWindow(hwnd, cmdShow);
    UpdateWindow(hwnd);

    // The main message dispatch loop.
     
    // NOTE: For simplicity, we handle the message loop with a
    // simple PeekMessage scheme. This might not be the best
    // mechanism for a real application (a separate render worker
    // thread might be better). 
    
    while (TRUE)
    {
        if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
        {
            // Message pending. If it's QUIT then exit the message
            // loop. Otherwise, process the message.
            
            if (WM_QUIT == msg.message)
            {
                break;
            }
            else
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        else
        {
            // Animate the scene.
            
            OnIdle(hwnd);
        }
    }

    return msg.wParam;
}