Direct3D Immediate-Mode Tutorial
by Colin McCartney

To create a 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.

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 3D 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 which don't match the current display depth.
  2. Discard any devices which can't do Gouraud-shaded triangles.
  3. If a hardware device is found which matches 1) and 2) use it. However, if we are running in debug mode we will skip hardware.
  4. Otherwise favour Mono/Ramp mode software renderers over RGB ones as, at least until MMX is widespread, Mono will be faster.

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, not 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 Direct3DDevice.

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 that the palette entries are 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/3D 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/3D 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 have 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 3D 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 of organizing 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 which 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 it 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 peform 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 3D scene, just as you might suspect. 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 which 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 just clear the entire device surface and z-buffer. You should probably implement a more efficient clearing mechanism in your application.

The RenderScene function must be called once and once only 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 keeping trying until we succeed or we 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 fullscreen 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 3D 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 clean-up; 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 it 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 vice versa.

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 just 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; }

Discuss this article in the forums


Date this article was posted to GameDev.net: 9/23/1999
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
Direct3D

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!