Tutorial

To create a Microsoft® Windows®-based, Direct3D® Retained Mode application, you set up the features of two different environments: the devices, viewports, and color capabilities of the Windows environment, and the models, textures, lights, and positions of the virtual environment. This section is a tutorial that contains all the code for a simple Retained Mode application. The following illustration is a single frame from the running animation:

Single frame from running animation

The tutorial is divided into the following sections:

Making the Helworld Sample

This tutorial includes all the code for a working Direct3D Retained Mode application. You can copy the code from this tutorial into a single .c file and then compile and run it if the following conditions are met:

Most of the code responsible for 3-D effects in this sample has been broken out into discrete functions so that you can modify parts of the system incrementally in your own experiments. You should look at the samples in the DirectX SDK for implementations of more complicated features of Direct3D.

The following topics describe some of the concerns that apply to the overall development of a simple Direct3D Retained Mode application.

Limitations of the Sample

This tutorial contains the Helworld.c example code, which creates a sphere, applies a texture to it, and rotates it in a window. This is the only C source file required to build this application. The only other files you need are Sphere3.x, a mesh file that ships in the Media directory of the DirectX SDK; and Tutor.bmp, a bitmap you supply. (Note that you must have the Direct3D header files in your include path and link to the Winmm.lib, D3drm.lib, and Ddraw.lib static libraries.)

This tutorial is a simplified version of the Globe sample that is part of the DirectX SDK. The Globe sample, like all the Direct3D Retained Mode samples in the SDK, requires the inclusion of a file named Rmmain.cpp and a number of header files. In Helworld.c, the relevant parts of Rmmain.cpp have been converted to C from C++ and integrated into the source code.

The code shown in this tutorial should not be mistaken for production code. The only possible user interactions with the program are starting it, stopping it, and minimizing the window while it is running. Most of the error checking has been removed for clarity. The purpose of this example is analogous to the purpose of the beginning program that prints "Hello, world!" on the screen: to produce output with as little confusion as possible.

DirectDraw Windowed Mode

Nearly all Direct3D applications will use DirectDraw® to display their graphics on the screen. These applications will either use DirectDraw's exclusive (full-screen) mode or nonexclusive (windowed) mode. The code in this documentation uses windowed mode. Although the full-screen mode offers some benefits in performance and convenience, it is much easier to debug code written in windowed mode. Most developers will write their code in windowed mode and port it to full-screen mode late in the development cycle, when most of the bugs have been worked out.

Definitions and Global Variables

Below are the first lines of the Helworld.c example. You won't need any other C source files to build this application, but be sure to give your file the .c extension rather than .cpp. The C compiler assumes a file with the .cpp extension contains C++ code and handles the lpVtbl pointer and the additional this parameter for you. See Accessing COM Objects by Using C in the DirectX SDK documentation for more details. Because this tutorial is written in C it explicitly includes the lpVtbl pointer and adds the initial this parameter. The compiler generates many compile errors on the same file if it has the .cpp extension.

Notice that INITGUID must be defined prior to all other includes and defines. This is a crucial point that is sometimes missed by developers who are new to DirectX.

/////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 1996 Microsoft Corporation. All Rights Reserved.
//
//  File: Helworld.c
//
//  Simplified Direct3D Retained Mode example, based on 
//  the "Globe" SDK sample.
//
/////////////////////////////////////////////////////////////////////

#define INITGUID           // Must precede other defines and includes

#include <windows.h>
#include <malloc.h>        // Required by memset call
#include <d3drmwin.h>

#define MAX_DRIVERS 5           // Maximum D3D drivers expected  

// Global variables

LPDIRECT3DRM lpD3DRM;           // Direct3DRM object 
LPDIRECTDRAWCLIPPER lpDDClipper;// DirectDrawClipper object 

struct _myglobs {
    LPDIRECT3DRMDEVICE dev;     // Direct3DRM device 
    LPDIRECT3DRMVIEWPORT view;  // Direct3DRM viewport through which 
                                // the scene is viewed
    LPDIRECT3DRMFRAME scene;    // Master frame in which others are 
                                // placed 
    LPDIRECT3DRMFRAME camera;   // Frame describing the user's POV 

    GUID DriverGUID[MAX_DRIVERS];     // GUIDs of available D3D drivers 
    char DriverName[MAX_DRIVERS][50]; // Names of available D3D drivers 
    int  NumDrivers;                  // Number of available D3D drivers 
    int  CurrDriver;                  // Number of D3D driver currently
                                      // being used 

    BOOL bQuit;                 // Program is about to terminate 
    BOOL bInitialized;          // All D3DRM objects are initialized 
    BOOL bMinimized;            // Window is minimized 

    int BPP;                    // Bit depth of the current display mode

} myglobs;

// Function prototypes.
 
static BOOL InitApp(HINSTANCE, int);
long FAR PASCAL WindowProc(HWND, UINT, WPARAM, LPARAM);
static BOOL EnumDrivers(HWND win);
static HRESULT WINAPI enumDeviceFunc(LPGUID lpGuid, 
    LPSTR lpDeviceDescription, LPSTR lpDeviceName, 
    LPD3DDEVICEDESC lpHWDesc, LPD3DDEVICEDESC lpHELDesc, 
    LPVOID lpContext);
static DWORD BPPToDDBD(int bpp);
static BOOL CreateDevAndView(LPDIRECTDRAWCLIPPER lpDDClipper, 
    int driver, int width, int height);
static BOOL SetRenderState(void);
static BOOL RenderLoop(void);
static BOOL MyScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view, 
    LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera);
void MakeMyFrames(LPDIRECT3DRMFRAME lpScene, LPDIRECT3DRMFRAME lpCamera,
    LPDIRECT3DRMFRAME * lplpLightFrame1, 
    LPDIRECT3DRMFRAME * lplpWorld_frame);
void MakeMyLights(LPDIRECT3DRMFRAME lpScene, LPDIRECT3DRMFRAME lpCamera, 
    LPDIRECT3DRMFRAME lpLightFrame1, 
    LPDIRECT3DRMLIGHT * lplpLight1, LPDIRECT3DRMLIGHT * lplpLight2);
void SetMyPositions(LPDIRECT3DRMFRAME lpScene, 
    LPDIRECT3DRMFRAME lpCamera, LPDIRECT3DRMFRAME lpLightFrame1, 
    LPDIRECT3DRMFRAME lpWorld_frame);
void MakeMyMesh(LPDIRECT3DRMMESHBUILDER  * lplpSphere3_builder);
void MakeMyWrap(LPDIRECT3DRMMESHBUILDER  sphere3_builder, 
                LPDIRECT3DRMWRAP * lpWrap);
void AddMyTexture(LPDIRECT3DRMMESHBUILDER  lpSphere3_builder, 
                  LPDIRECT3DRMTEXTURE * lplpTex);
static void CleanUp(void);

Windows Setup and Initialization

This section describes the standard setup and initialization functions in a Windows program, as implemented in the Helworld.c sample code.

The WinMain Function

The WinMain function in Helworld.c has only a few lines of code that are unique to an application that uses DirectDraw and Direct3D Retained Mode. The InitApp and CleanUp functions are standard parts of a Windows program, although in the case of Helworld, they perform some unique tasks. The most important call in WinMain, from the perspective of Direct3D, is the call to the RenderLoop function. RenderLoop is responsible for drawing each new frame of the animation. For more information about the RenderLoop function, see The Rendering Loop.

/////////////////////////////////////////////////////////////////////
// 
// WinMain
// Initializes the application and enters a message loop.
// The message loop renders the scene until a quit message is received.
//
/////////////////////////////////////////////////////////////////////
 
int PASCAL
WinMain (HINSTANCE this_inst, HINSTANCE prev_inst, LPSTR cmdline, 
    int cmdshow)
{
    MSG     msg;
    HACCEL  accel = NULL;
    int     failcount = 0;  // Number of times RenderLoop has failed 

    prev_inst;
    cmdline;

    // Create the window and initialize all objects needed to begin 
    // rendering.
     
    if (!InitApp(this_inst, cmdshow))
        return 1;

    while (!myglobs.bQuit) {
        
        // Monitor the message queue until there are no pressing
        // messages.
         
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            if (!TranslateAccelerator(msg.hwnd, accel, &msg)) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        
        // If the app is not minimized, not about to quit, and D3DRM has
        // been initialized, begin rendering.
         
        if (!myglobs.bMinimized && !myglobs.bQuit && 
             myglobs.bInitialized) {

            // Attempt to render a frame. If rendering fails more than 
            // twice, abort execution.
                 
            if (!RenderLoop())
                ++failcount;
            if (failcount > 2) {
                CleanUp();
                break;
            }
        }
    }
    return msg.wParam;
}

The InitApp Function

The initialization function (InitApp) in Helworld.c creates the window class and main application window, just as in most Windows-based applications. After this, however, InitApp does some work that is unique to applications using DirectDraw and Direct3D.

Next, InitApp retrieves the current display's bits per pixel. This information is used to help set the rendering quality for the application. For more information, see Setting the Render State.

InitApp then uses the locally defined EnumDrivers function to determine which Direct3D drivers are available and to choose one. For more information about enumerating drivers, see Enumerating Device Drivers.

Next, the code uses the Direct3DRMCreate function to create an IDirect3DRM interface. It uses this interface in the calls to IDirect3DRM::CreateFrame and IDirect3DRMFrame::SetPosition that create the scene and camera frames, and position the camera in the scene.

A DirectDrawClipper object makes it simple to manage the clipping planes that control which parts are visible of a 3-D scene. Helworld.c uses the DirectDrawCreateClipper function to create an IDirectDrawClipper interface, and then uses the IDirectDrawClipper::SetHWnd method to set the window handle that obtains the clipping information.

Now the InitApp function uses the locally defined CreateDevAndView function to create the Direct3D device and viewport. For more information about this function, see Creating the Device and Viewport.

When the entire supporting structure of a Direct3D application has been put into place, the details of the 3-D scene can be constructed. The MyScene function does this. For more information about MyScene, see Creating the Device and Viewport.

Finally, just as in a standard initialization function, InitApp displays and updates the window.

/////////////////////////////////////////////////////////////////////
// 
// InitApp
// Creates window and initializes all objects necessary to begin 
// rendering.
//
/////////////////////////////////////////////////////////////////////
 
static BOOL
InitApp(HINSTANCE this_inst, int cmdshow)
{
    HWND win;
    HDC hdc;
    WNDCLASS wc;
    RECT rc;

    // Set up and register the window class.
     
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = sizeof(DWORD);
    wc.hInstance = this_inst;
    wc.hIcon = LoadIcon(this_inst, "AppIcon");
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = NULL;        
    wc.lpszClassName = "D3DRM Example";
    if (!RegisterClass(&wc))
        return FALSE;
    
    // Initialize the global variables.
     
    memset(&myglobs, 0, sizeof(myglobs));

    // Create the window.
     
    win =
        CreateWindow
        (   "D3DRM Example",            // Class 
            "Hello World (Direct3DRM)", // Title bar 
            WS_VISIBLE | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
                 WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
            CW_USEDEFAULT,              // Init. x pos 
            CW_USEDEFAULT,              // Init. y pos 
            300,                        // Init. x size 
            300,                        // Init. y size 
            NULL,                       // Parent window 
            NULL,                       // Menu handle 
            this_inst,                  // Program handle 
            NULL                        // Create parms 
        );
    if (!win)
        return FALSE;
   
    // Record the current display bits-per-pixel.
     
    hdc = GetDC(win);
    myglobs.BPP = GetDeviceCaps(hdc, BITSPIXEL);
    ReleaseDC(win, hdc);
   
    // Enumerate the D3D drivers and select one.
     
    if (!EnumDrivers(win))
        return FALSE;
   
    // Create the D3DRM object and the D3DRM window object.
     
    lpD3DRM = NULL;
    Direct3DRMCreate(&lpD3DRM);
    
    // Create the master scene frame and camera frame.
     
    lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, NULL, &myglobs.scene);
    lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, myglobs.scene, 
        &myglobs.camera);
    myglobs.camera->lpVtbl->SetPosition(myglobs.camera, myglobs.scene, 
        D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0));

    // Create a DirectDrawClipper object and associate the 
    // window with it.
     
    DirectDrawCreateClipper(0, &lpDDClipper, NULL);
    lpDDClipper->lpVtbl->SetHWnd(lpDDClipper, 0, win);
    
    // Create the D3DRM device by using the selected D3D driver.
     
    GetClientRect(win, &rc);
    if (!CreateDevAndView(lpDDClipper, myglobs.CurrDriver, rc.right, 
            rc.bottom)) {
        return FALSE;
    }
    
    // Create the scene to be rendered.
     
    if (!MyScene(myglobs.dev, myglobs.view, myglobs.scene, 
            myglobs.camera))
        return FALSE;
    
    myglobs.bInitialized = TRUE;  // Initialization completed
    
    // Display the window.
     
    ShowWindow(win, cmdshow);
    UpdateWindow(win);

    return TRUE;
}

The Main Window Procedure

The Helworld.c sample has a very simple main window procedure—after all, the application allows practically no user interaction.

When the window procedure receives a WM_DESTROY message, it uses the CleanUp function, just as you would expect.

When it receives a WM_ACTIVATE message, it retrieves an IDirect3DRMWinDevice interface and uses the IDirect3DRMWinDevice::HandleActivate method to ensure that the colors are correct in the active rendering window. Similarly, the function reacts to a WM_PAINT message by using the IDirect3DRMWinDevice::HandlePaint method.

/////////////////////////////////////////////////////////////////////
//
// WindowProc
// Main window message handler.
//
/////////////////////////////////////////////////////////////////////
 
LONG FAR PASCAL WindowProc(HWND win, UINT msg, 
    WPARAM wparam, LPARAM lparam)
{
    RECT r;
    PAINTSTRUCT ps;
    LPDIRECT3DRMWINDEVICE lpD3DRMWinDev;

    switch (msg)    {
        
    case WM_DESTROY:
        CleanUp();
        break;

    case WM_ACTIVATE:
        {
       
        // Create a Windows-specific Direct3D Retained Mode window device to handle this
        // message.
         
        if (!myglobs.dev)
            break;
        myglobs.dev->lpVtbl->QueryInterface(myglobs.dev, 
            &IID_IDirect3DRMWinDevice, (void **) &lpD3DRMWinDev);
        lpD3DRMWinDev->lpVtbl->HandleActivate(lpD3DRMWinDev, 
            (WORD) wparam);
        lpD3DRMWinDev->lpVtbl->Release(lpD3DRMWinDev);
        }
        break;

    case WM_PAINT:
        if (!myglobs.bInitialized || !myglobs.dev)
            return DefWindowProc(win, msg, wparam, lparam);
       
        // Create a Windows-specific Direct3D Retained Mode window device to handle this
        // message.
         
        if (GetUpdateRect(win, &r, FALSE)) {
            BeginPaint(win, &ps);
            myglobs.dev->lpVtbl->QueryInterface(myglobs.dev, 
                &IID_IDirect3DRMWinDevice, (void **) &lpD3DRMWinDev);
            lpD3DRMWinDev->lpVtbl->HandlePaint(lpD3DRMWinDev, 
                ps.hdc);
            lpD3DRMWinDev->lpVtbl->Release(lpD3DRMWinDev);
            EndPaint(win, &ps);
        }
        break;
    default:
        return DefWindowProc(win, msg, wparam, lparam);
    }
    return 0L;
}

Enumerating Device Drivers

Applications that use Direct3D always enumerate the available drivers and choose the one that best matches their needs. The following sections each describe one of the functions that perform this task:

The EnumDrivers Function

The EnumDrivers function is used by the InitApp function just before InitApp creates the application's scene and camera.

The IDirect3D Component Object Model (COM) interface is really an interface to a DirectDraw object, so the first thing this enumeration function does is use the DirectDrawCreate function to create a DirectDrawObject. Then EnumDrivers uses the QueryInterface method to create an IDirect3D interface. Notice that the C implementation of QueryInterface requires that you pass the address of the interface identifier as the second parameter, not simply the constant itself (like in the C++ implementation).

The enumeration is handled by the IDirect3D::EnumDevices method, which depends on the locally defined enumDeviceFunc callback function. For more information about this callback function, see The enumDeviceFunc Callback Function.

Notice that IDirect3D::EnumDevices is a Direct3D method, not a Direct3DRM method; there is no enumeration method in the Retained Mode API. This is a good example of the natural use of both Retained Mode and Immediate Mode methods in a single application.

/////////////////////////////////////////////////////////////////////
//
// EnumDrivers
// Enumerate the available D3D drivers and choose one.
//
/////////////////////////////////////////////////////////////////////
 
static BOOL
EnumDrivers(HWND win)
{
    LPDIRECTDRAW lpDD;
    LPDIRECT3D lpD3D;
    HRESULT rval;

    // Create a DirectDraw object and query for the Direct3D interface 
    // to use to enumerate the drivers.
     
    DirectDrawCreate(NULL, &lpDD, NULL);
    rval = lpDD->lpVtbl->QueryInterface(lpDD, &IID_IDirect3D, 
        (void**) &lpD3D);
    if (rval != DD_OK) {
        lpDD->lpVtbl->Release(lpDD);
        return FALSE;
    }
    
    // Enumerate the drivers, setting CurrDriver to -1 to initialize the
    // driver selection code in enumDeviceFunc.
     
    myglobs.CurrDriver = -1;
    lpD3D->lpVtbl->EnumDevices(lpD3D, enumDeviceFunc, 
        &myglobs.CurrDriver);
    
    // Ensure at least one valid driver was found.
     
    if (myglobs.NumDrivers == 0) {
        return FALSE;
    }
    lpD3D->lpVtbl->Release(lpD3D);
    lpDD->lpVtbl->Release(lpDD);
    
    return TRUE;
}

The enumDeviceFunc Callback Function

The enumDeviceFunc callback function is of the D3DENUMDEVICESCALLBACK type, as defined in the D3dcaps.h header file. The system uses this function with identifiers and names for each Direct3D driver in the system, as well as the hardware and emulated capabilities of the driver.

The callback function uses the dcmColorModel member of the D3DDEVICEDESC structure to determine whether to examine the hardware or emulated driver description. If the member has been filled by the hardware description, the function consults the hardware description.

Next, the callback function determines whether the driver being enumerated can render in the current bit depth. If not, the function returns D3DENUMRET_OK to skip the rest of the process for this driver and continue the enumeration with the next driver. The callback function uses the locally defined BPPToDDBD function to compare the reported bit depth against the bits-per-pixel retrieved by the call to the GetDeviceCaps function in the InitApp function. (BPPToDDBD stands for bits-per-pixel to DirectDraw bit-depth.) For the code for this function, see The BPPToDDBD Helper Function.

If the driver being enumerated passes a few simple tests, other parts of D3DDEVICEDESC are examined. The callback function will choose hardware over software emulation, and monochromatic lighting capabilities over RGB lighting capabilities.

/////////////////////////////////////////////////////////////////////
//
// enumDeviceFunc
// Callback function that records each usable D3D driver's name 
// and GUID. Chooses a driver and sets *lpContext to this driver.
//
/////////////////////////////////////////////////////////////////////
 
static HRESULT
WINAPI enumDeviceFunc(LPGUID lpGuid, LPSTR lpDeviceDescription, 
    LPSTR lpDeviceName, LPD3DDEVICEDESC lpHWDesc, 
    LPD3DDEVICEDESC lpHELDesc, LPVOID lpContext)
{
    static BOOL hardware = FALSE; // Current start driver is hardware 
    static BOOL mono = FALSE;     // Current start driver is mono light 
    LPD3DDEVICEDESC lpDesc;
    int *lpStartDriver = (int *)lpContext;
   
    // Decide which device description should be consulted.
     
    lpDesc = lpHWDesc->dcmColorModel ? lpHWDesc : lpHELDesc;
    
    // If this driver cannot render in the current display bit-depth, 
    // skip it and continue with the enumeration.
     
    if (!(lpDesc->dwDeviceRenderBitDepth & BPPToDDBD(myglobs.BPP)))
        return D3DENUMRET_OK;
    
    // Record this driver's name and GUID.
     
    memcpy(&myglobs.DriverGUID[myglobs.NumDrivers], lpGuid, 
        sizeof(GUID));
    lstrcpy(&myglobs.DriverName[myglobs.NumDrivers][0], lpDeviceName);
   
    // Choose hardware over software, RGB lights over mono lights.
     
    if (*lpStartDriver == -1) {
        
        // This is the first valid driver.
         
        *lpStartDriver = myglobs.NumDrivers;
        hardware = lpDesc == lpHWDesc ? TRUE : FALSE;
        mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE;
    } else if (lpDesc == lpHWDesc && !hardware) {
        
        // This driver is hardware and the start driver is not.
         
        *lpStartDriver = myglobs.NumDrivers;
        hardware = lpDesc == lpHWDesc ? TRUE : FALSE;
        mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE;
    } else if ((lpDesc == lpHWDesc && hardware ) || 
               (lpDesc == lpHELDesc && !hardware)) {
        if (lpDesc->dcmColorModel == D3DCOLOR_MONO && !mono) {
            
            // This driver and the start driver are the same type, and 
            // this driver is mono whereas the start driver is not.
             
            *lpStartDriver = myglobs.NumDrivers;
            hardware = lpDesc == lpHWDesc ? TRUE : FALSE;
            mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE;
        }
    }
    myglobs.NumDrivers++;
    if (myglobs.NumDrivers == MAX_DRIVERS)
        return (D3DENUMRET_CANCEL);
    return (D3DENUMRET_OK);
}

BPPToDDBD Helper Function

The enumDeviceFunc callback function uses the BPPToDDBD helper function to convert the stored bits-per-pixel the current device supports to a form that can be compared against the bit depth for the driver being enumerated. For more information about enumDeviceFunc, see The enumDeviceFunc Callback Function.

/////////////////////////////////////////////////////////////////////
//
// BPPToDDBD
// Converts bits-per-pixel to a DirectDraw bit-depth flag.
//
/////////////////////////////////////////////////////////////////////

static DWORD
BPPToDDBD(int bpp)
{
    switch(bpp) {
        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;
    }
}

Setting up the 3-D Environment

This section describes the code in Helworld.c that establishes the 3-D environment. The following sections describe the two functions that perform this task:

These functions do not populate the 3-D environment with objects, frames, and lights. That is done by the MyScene function and the functions it uses. For information about filling up the 3-D environment, see Creating the Scene.

Creating the Device and Viewport

The Direct3D device and viewport are created as part of the application's initialization. After creating a DirectDrawClipper object, the InitApp function uses CreateDevAndView, passing as parameters the DirectDrawClipper object, the driver that was chosen, and the dimensions of the client rectangle.

The CreateDevAndView function uses the IDirect3DRM::CreateDeviceFromClipper method to create a Direct3DRM device, using the driver that was selected during the enumeration process. It uses this IDirect3DRMDevice interface to retrieve the device's width and height, by using IDirect3DRMDevice::GetWidth and IDirect3DRMDevice::GetHeight methods. After it has retrieved this information, it uses the IDirect3DRM::CreateViewport method to retrieve the IDirect3DRMViewport interface.

When CreateDevAndView has used the IDirect3DRMViewport::SetBack method to set the back clipping plane of the viewport, it uses the locally defined SetRenderState function. SetRenderState is described in the next section, Setting the Render State.

/////////////////////////////////////////////////////////////////////
//
// CreateDevAndView
// Create the D3DRM device and viewport with the given D3D driver and 
// with the specified size.
// 
/////////////////////////////////////////////////////////////////////
 
static BOOL
CreateDevAndView(LPDIRECTDRAWCLIPPER lpDDClipper, int driver, 
    int width, int height)
{
    HRESULT rval;

    // Create the D3DRM device from this window by using the specified 
    // D3D driver.
     
    lpD3DRM->lpVtbl->CreateDeviceFromClipper(lpD3DRM, lpDDClipper, 
        &myglobs.DriverGUID[driver], width, height, &myglobs.dev);
    
    // Create the D3DRM viewport by using the camera frame. Set the 
    // background depth to a large number. The width and height
    // might have been slightly adjusted, so get them from the device.
     
    width = myglobs.dev->lpVtbl->GetWidth(myglobs.dev);
    height = myglobs.dev->lpVtbl->GetHeight(myglobs.dev);
    rval = lpD3DRM->lpVtbl->CreateViewport(lpD3DRM, myglobs.dev, 
        myglobs.camera, 0, 0, width, height, &myglobs.view);
    if (rval != D3DRM_OK) {
        myglobs.dev->lpVtbl->Release(myglobs.dev);
        return FALSE;
    }
    rval = myglobs.view->lpVtbl->SetBack(myglobs.view, D3DVAL(5000.0));
    if (rval != D3DRM_OK) {
        myglobs.dev->lpVtbl->Release(myglobs.dev);
        myglobs.view->lpVtbl->Release(myglobs.view);
        return FALSE;
    }
    
    // Set the render quality, fill mode, lighting state,
    // and color shade info.
     
    if (!SetRenderState())
        return FALSE;
    return TRUE;
}

Setting the Render State

Direct3D is a state machine; applications set up the state of the lighting, rendering, and transformation modules and then pass data through them. This architecture is integral to Immediate Mode, but it is partially hidden by the Retained Mode API. The SetRenderState function is a simple way to set the rendering state for a Retained Mode application.

First, SetRenderState uses the IDirect3DRMDevice::SetQuality method, specifying that the lights are on, that the fill mode is solid, and that Gouraud shading should be used. At this point, applications that need to change the dithering mode or texture quality can use the IDirect3DRMDevice::SetDither or IDirect3DRMDevice::SetTextureQuality methods.

/////////////////////////////////////////////////////////////////////
//
// SetRenderState
// Set the render quality and shade information.
//
/////////////////////////////////////////////////////////////////////
 
BOOL
SetRenderState(void)
{
    HRESULT rval;
    
    // Set the render quality (light toggle, fill mode, shade mode).
     
    rval = myglobs.dev->lpVtbl->SetQuality(myglobs.dev, 
        D3DRMLIGHT_ON | D3DRMFILL_SOLID | D3DRMSHADE_GOURAUD);
    if (rval != D3DRM_OK) {
        return FALSE;
    }
    
    // If you want to change the dithering mode, use SetDither here.

    // If you want a texture quality other than D3DRMTEXTURE_NEAREST
    // (the default value), use SetTextureQuality here.
        
   return TRUE;
}

Rendering Loop

The WinMain function uses the RenderLoop function to draw each new frame of the animation. The RenderLoop function performs a few simple tasks:

/////////////////////////////////////////////////////////////////////
//
// RenderLoop
// Clear the viewport, render the next frame, and update the window.
//
/////////////////////////////////////////////////////////////////////
 
static BOOL
RenderLoop()
{
    HRESULT rval;
   
    // Tick the scene.
     
    rval = myglobs.scene->lpVtbl->Move(myglobs.scene, D3DVAL(1.0));
    if (rval != D3DRM_OK) {
        return FALSE;
    }
    
    // Clear the viewport.
     
    rval = myglobs.view->lpVtbl->Clear(myglobs.view);
    if (rval != D3DRM_OK) {
        return FALSE;
    }
   
    // Render the scene to the viewport.
     
    rval = myglobs.view->lpVtbl->Render(myglobs.view, myglobs.scene);
    if (rval != D3DRM_OK) {
        return FALSE;
    }
    
    // Update the window.
     
    rval = myglobs.dev->lpVtbl->Update(myglobs.dev);
    if (rval != D3DRM_OK) {
        return FALSE;
    }
    return TRUE;
}

Creating the Scene

After setting up the 3-D environment—choosing a device driver, creating the 3-D device and viewport, setting the rendering state, and so on—Helworld.c uses a series of functions to populate this 3-D environment with objects, frames, and lights:

The MyScene Function

The MyScene function in Helworld.c corresponds to the BuildScene function that is implemented in all the Direct3D samples in the DirectX SDK. This is where all the work occurs that displays unique objects with unique textures and lighting effects.

The MyScene function uses a series of locally defined functions that set up the separate features of the scene that is being created. These functions are:

When these functions have set up the visual object, MyScene uses the IDirect3DRMFrame::AddVisual method to add the object to the environment's world frame. After adding the visual object, MyScene no longer needs the interfaces it has created, so it uses the Release method repeatedly to release them all.

/////////////////////////////////////////////////////////////////////
//
// MyScene
// Uses the functions that create the frames, lights, mesh, and
// texture. Releases all interfaces on completion.
//
/////////////////////////////////////////////////////////////////////

BOOL
MyScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
    LPDIRECT3DRMFRAME lpScene, LPDIRECT3DRMFRAME lpCamera)
{
    LPDIRECT3DRMFRAME lpLightframe1 = NULL;
    LPDIRECT3DRMFRAME lpWorld_frame = NULL;
    LPDIRECT3DRMLIGHT lpLight1      = NULL;
    LPDIRECT3DRMLIGHT lpLight2      = NULL;
    LPDIRECT3DRMTEXTURE lpTex       = NULL;
    LPDIRECT3DRMWRAP lpWrap         = NULL;
    LPDIRECT3DRMMESHBUILDER  lpSphere3_builder = NULL;

    MakeMyFrames(lpScene, lpCamera, &lpLightframe1, &lpWorld_frame);
    MakeMyLights(lpScene, lpCamera, lpLightframe1, &lpLight1,
        &lpLight2);
    SetMyPositions(lpScene, lpCamera, lpLightframe1, lpWorld_frame);
    MakeMyMesh(&lpSphere3_builder);
    MakeMyWrap(lpSphere3_builder, &lpWrap);
    AddMyTexture(lpSphere3_builder, &lpTex);

    // If you need to create a material (for example, to create
    // a shiny surface), use CreateMaterial and SetMaterial here.

    // Now that the visual object has been created, add it
    // to the world frame.

    lpWorld_frame->lpVtbl->AddVisual(lpWorld_frame, 
        (LPDIRECT3DRMVISUAL) lpSphere3_builder);

    lpLightframe1->lpVtbl->Release(lpLightframe1);
    lpWorld_frame->lpVtbl->Release(lpWorld_frame);
    lpSphere3_builder->lpVtbl->Release(lpSphere3_builder);
    lpLight1->lpVtbl->Release(lpLight1);
    lpLight2->lpVtbl->Release(lpLight2);
    lpTex->lpVtbl->Release(lpTex);
    lpWrap->lpVtbl->Release(lpWrap);

    return TRUE;
}

The MakeMyFrames Function

The MyScene function uses the MakeMyFrames function to create the frames for the directional light and the world frame used in Helworld.c. MakeMyFrames does this work by using the IDirect3DRM::CreateFrame method.

/////////////////////////////////////////////////////////////////////
//
// MakeMyFrames
// Create frames used in the scene.
//
/////////////////////////////////////////////////////////////////////

void MakeMyFrames(LPDIRECT3DRMFRAME lpScene, LPDIRECT3DRMFRAME lpCamera,
    LPDIRECT3DRMFRAME * lplpLightFrame1,
    LPDIRECT3DRMFRAME * lplpWorld_frame)
{
    lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, lpScene, lplpLightFrame1);
    lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, lpScene, lplpWorld_frame);
}

The MakeMyLights Function

The MyScene function uses the MakeMyLights function to create the directional and ambient lights used in Helworld.c. MakeMyLights uses the IDirect3DRM::CreateLightRGB and IDirect3DRMFrame::AddLight methods to create a bright directional light and add it to a light frame, and to create a dim ambient light and add it to the entire scene. (Ambient lights are always associated with an entire scene.)

/////////////////////////////////////////////////////////////////////
//
// MakeMyLights
// Create lights used in the scene.
//
/////////////////////////////////////////////////////////////////////

void MakeMyLights(LPDIRECT3DRMFRAME lpScene, LPDIRECT3DRMFRAME lpCamera, 
    LPDIRECT3DRMFRAME lpLightFrame1, 
    LPDIRECT3DRMLIGHT * lplpLight1, LPDIRECT3DRMLIGHT * lplpLight2)
{
    lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_DIRECTIONAL, 
        D3DVAL(0.9), D3DVAL(0.9), D3DVAL(0.9), lplpLight1);
                          
    lpLightFrame1->lpVtbl->AddLight(lpLightFrame1, *lplpLight1);

    lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_AMBIENT, 
        D3DVAL(0.1), D3DVAL(0.1), D3DVAL(0.1), lplpLight2);
    
    lpScene->lpVtbl->AddLight(lpScene, *lplpLight2);
}

The SetMyPositions Function

The MyScene function uses the SetMyPositions function to set the positions and orientations of the frames used in Helworld.c. SetMyPositions does this work by using the IDirect3DRMFrame::SetPosition and IDirect3DRMFrame::SetOrientation methods. The IDirect3DRMFrame::SetRotation method imparts a spin to the frame to which the sphere will be added.

/////////////////////////////////////////////////////////////////////
//
// SetMyPositions
// Set the positions and orientations of the light, camera, and 
// world frames. Establish a rotation for the globe.
//
/////////////////////////////////////////////////////////////////////

void SetMyPositions(LPDIRECT3DRMFRAME lpScene, 
    LPDIRECT3DRMFRAME lpCamera, LPDIRECT3DRMFRAME lpLightFrame1, 
    LPDIRECT3DRMFRAME lpWorld_frame)
{

    lpLightFrame1->lpVtbl->SetPosition(lpLightFrame1, lpScene, 
        D3DVAL(2), D3DVAL(0.0), D3DVAL(22));

    lpCamera->lpVtbl->SetPosition(lpCamera, lpScene, 
        D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0));
    lpCamera->lpVtbl->SetOrientation(lpCamera, lpScene, 
        D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1), 
        D3DVAL(0.0), D3DVAL(1), D3DVAL(0.0));
                                        
    lpWorld_frame->lpVtbl->SetPosition(lpWorld_frame, lpScene, 
        D3DVAL(0.0), D3DVAL(0.0), D3DVAL(15));
    lpWorld_frame->lpVtbl->SetOrientation(lpWorld_frame, lpScene, 
        D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1),
        D3DVAL(0.0), D3DVAL(1), D3DVAL(0.0));
                                
    lpWorld_frame->lpVtbl->SetRotation(lpWorld_frame, lpScene, 
        D3DVAL(0.0), D3DVAL(0.1), D3DVAL(0.0), D3DVAL(0.05));
}

The MakeMyMesh Function

The MyScene function uses the MakeMyMesh function to load and set the spherical mesh used in Helworld.c. MakeMyMesh uses the IDirect3DRM::CreateMeshBuilder method to create an IDirect3DRMMeshBuilder interface. Then it uses the IDirect3DRMMeshBuilder::Load, IDirect3DRMMeshBuilder::Scale, and IDirect3DRMMeshBuilder::SetColorRGB methods to prepare the mesh represented by the Sphere3.x file. (The Sphere3.x file is included in the DirectX SDK, as part of the media provided for use with the sample code.)

/////////////////////////////////////////////////////////////////////
//
// MakeMyMesh
// Create MeshBuilder object, load, scale, and color the mesh.
//
/////////////////////////////////////////////////////////////////////

void MakeMyMesh(LPDIRECT3DRMMESHBUILDER  * lplpSphere3_builder)
{
    lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, lplpSphere3_builder);

    (*lplpSphere3_builder)->lpVtbl->Load(*lplpSphere3_builder, 
        "sphere3.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL);

    (*lplpSphere3_builder)->lpVtbl->Scale(*lplpSphere3_builder,
        D3DVAL(2), D3DVAL(2), D3DVAL(2));
      
    // Set sphere to white to avoid unexpected texture-blending results.

    (*lplpSphere3_builder)->lpVtbl->SetColorRGB(*lplpSphere3_builder, 
        D3DVAL(1), D3DVAL(1), D3DVAL(1));
}

The MakeMyWrap Function

The MyScene function uses the MakeMyWrap function to create and apply texture coordinates to the sphere loaded by the MakeMyMesh function. MakeMyWrap uses the IDirect3DRMMeshBuilder::GetBox method to retrieve the bounding box that contains the sphere and uses the dimensions of that bounding box in a call to the IDirect3DRM::CreateWrap method, which creates a cylindrical texture wrap and retrieves the IDirect3DRMWrap interface. A call to the IDirect3DRMWrap::Apply method applies the texture coordinates to the sphere.

/////////////////////////////////////////////////////////////////////
//
// MakeMyWrap
// Creates and applies wrap for texture.
//
/////////////////////////////////////////////////////////////////////

void MakeMyWrap(LPDIRECT3DRMMESHBUILDER  sphere3_builder, 
                LPDIRECT3DRMWRAP * lpWrap)
{
    D3DVALUE miny, maxy, height;
    D3DRMBOX box;

    sphere3_builder->lpVtbl->GetBox(sphere3_builder, &box);

    maxy = box.max.y;
    miny = box.min.y;
    height = maxy - miny;

    lpD3DRM->lpVtbl->CreateWrap
        (lpD3DRM, D3DRMWRAP_CYLINDER, NULL,
        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(1.0),
        D3DVAL(0.0), D3DDivide(miny, height),
        D3DVAL(1.0), D3DDivide(D3DVAL(1.0), height),
        lpWrap);

    (*lpWrap)->lpVtbl->Apply(*lpWrap, (LPDIRECT3DRMOBJECT)
        sphere3_builder);

}

The AddMyTexture Function

The MyScene function uses the AddMyTexture function to load a texture and associate it with the sphere. AddMyTexture uses the IDirect3DRM::LoadTexture method to load a bitmap called Tutor.bmp, and then it uses the IDirect3DRMMeshBuilder::SetTexture method to associate the bitmap with the sphere.

Bitmap used as a texture on the sphere

/////////////////////////////////////////////////////////////////////
//
// AddMyTexture
// Creates and applies wrap for texture.
//
/////////////////////////////////////////////////////////////////////

void AddMyTexture(LPDIRECT3DRMMESHBUILDER  lpSphere3_builder, 
    LPDIRECT3DRMTEXTURE * lplpTex)
{
    lpD3DRM->lpVtbl->LoadTexture(lpD3DRM, "tutor.bmp", lplpTex);

    // If you need a color depth other than the default (16), 
    // use IDirect3DRMTexture::SetShades here.

    lpSphere3_builder->lpVtbl->SetTexture(lpSphere3_builder, *lplpTex);

}

Cleaning Up

Helworld.c uses the CleanUp function when it receives a WM_DESTROY message or after several consecutive unsuccessful attempts to use the RenderLoop function.

/////////////////////////////////////////////////////////////////////
//
// CleanUp
// Release all D3DRM objects and set the bQuit flag.
//
/////////////////////////////////////////////////////////////////////

void
CleanUp(void)
{
    myglobs.bInitialized = FALSE;
    myglobs.scene->lpVtbl->Release(myglobs.scene);
    myglobs.camera->lpVtbl->Release(myglobs.camera);
    myglobs.view->lpVtbl->Release(myglobs.view);
    myglobs.dev->lpVtbl->Release(myglobs.dev);
    lpD3DRM->lpVtbl->Release(lpD3DRM);
    lpDDClipper->lpVtbl->Release(lpDDClipper);

    myglobs.bQuit = TRUE;
}

Top of Page Top of Page
© 1999 Microsoft and/or its suppliers. All rights reserved. Terms of Use.