· This is/was my old 'personal website' (~1997 to 2010-ish); my new 'personal website' is at »
As of Oct 2016 I have decided to add some of the old content back again. Note that some of the info on the site is now outdated.
- David Joffe
Game programming with DirectX
[ Home | Chapter 1 | Chapter 2 | Chapter 3 | Chapter 4 | Chapter 5 | Chapter 6 ]

Chapter 4: A simple Direct3D Retained mode sample

1. Direct3D: An Overview

Over here I'll shove in some basics, like coordinate systems, world and object coordinate systems, etc. For now I'll assume you're at least a little familiar with 3D programming. Blah blah blah, differences between immediate and retained mode, etc etc.

1.1. Devices

Direct3D interfaces with the surface it is rendering to (e.g. screen memory, system memory) using an IDirect3DRMDevice object. More than one type of rendering device can exist and a specific rendering device must be chosen for a scene. For example, there is normally a device for RGB rendering and a device for Mono rendering (these names refer to the lighting model used for rendering. Mono means that only white lights can exist in the scene, while RGB supports colored lights, and is thus slower). Additional devices may be installed that make use of 3D hardware acceleration. It is possible to iterate through the installed D3D devices by enumarating through them (EnumDevices). It is possible to have two different devices rendering to the same surface.

1.2. Viewports

The IDirect3DRMViewport object is used to keep track of how our 3D scene is rendered onto the device. It is possible to have multiple viewports per device, and it is also possible to have a viewport rendering to more than one device. The viewport object keeps track of the camera, front and back clipping fields, field of view etc.

1.3. Frames

A frame in Direct3D is basically used to store an object's position and orientation information, relative to a given frame of reference, which is where the term frame comes from. Frames are positioned relative to other frames, or to the world coordinates. Frames are used to store the positions of objects in the scene as well as other things like lights. OK, so I'm explaining it badly. It's late, I'm tired, I'll revise it soon. To add an object to the scene we have to attach the object to a frame. The object is called a visual in Direct3D, since it represents what the user sees. So, a visual has no meaningful position or orientation information itself, but when attached to a frame, it is transformed when rendered according to the transformation information in the frame. Multiple frames may use the same visual. This can save a lot of time and memory in a situation like, for example, a forest or a small fleet of spacecraft, where you have a bunch of objects that look exactly the same but all exist in different positions and orientations.

Below is a simple ASCII diagram of a single visual attached to two frames which are at different positions:

  /    /| <- Cube (visual)
 /    / |<==========================>[Frame1: (21, 3, 4)]
+----+  |
|    | /<===========================>[Frame2: (-12, 10, -6)]
|    |/

If both of these frames were attached to the scene frame, then our scene would have 2 cubes in it; one at (21, 3, 4) and the other at (-12, 10, -6).

1.4. Materials

Coming sometime.

2. The Direct3D RM Sample

Firstly, heres a screenshot of the small simple sample application we're putting together here.

[Screenshot 2]

3. Setting up global variables

Before we start we'll need a few global variables.

LPDIRECTDRAW pDD; // A DirectDraw object LPDIRECT3DRM pD3DRM; // A Direct3D RM object LPDIRECTDRAWSURFACE pDDSPrimary; // DirectDraw primary surface LPDIRECTDRAWSURFACE pDDSBack; // DirectDraw back surface LPDIRECTDRAWPALETTE pDDPal; // Palette for primary surface LPDIRECTDRAWCLIPPER pClipper; // Clipper for windowed mode LPDIRECT3DRMDEVICE pD3DRMDevice; // A device LPDIRECT3DRMVIEWPORT pViewport; // A viewport LPDIRECT3DRMFRAME pCamera; // A camera LPDIRECT3DRMFRAME pScene; // The scene LPDIRECT3DRMFRAME pCube; // The one and only object in // our scene BOOL bFullScreen; // Are we in full-screen mode? BOOL bAnimating; // Has our animating begun? HWND ddWnd; // HWND of the DDraw window

Note that we need both a DirectDraw object and a Direct3D object to create a Direct3D application. This is because Direct3D works in conjunction with DirectDraw. As before, we need a primary and a back surface for our double-buffering, and a clipper to handle window-clipping in windowed mode. The palette object is still not discusses in this tutorial (yet). We have objects for the device and viewport, and we have frame objects to keep track of the scene and the scene's camera. Also, we have a frame that is used for the object we'll have in this scene.

Here is a routine just to initially flatten these globals:

void InitDirectXGlobals() { pDD = NULL; pD3DRM = NULL; pDDSPrimary = NULL; pDDSBack = NULL; pDDPal = NULL; pClipper = NULL; pD3DRMDevice = NULL; pViewport = NULL; pCamera = NULL; pScene = NULL; pCube = NULL; bFullScreen = FALSE; bAnimating = FALSE; }

4. From 'Initializing the DirectDraw system' to 'Creating the clipper'

These steps all proceed exactly as in the DirectDraw sample, with the exception of the CreateSurface function, where the back surface has to created with the DDSCAPS_3DDEVICE, since it will be used for 3d rendering:

UINT CreatePrimarySurface() { . . . // Create an offscreen surface, specifying 3d device ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE; . . . }

5. Creating the Direct3D Retained Mode object

Now we need to create an IDirect3DRM object. This is achieved, quite simply, by calling the Direct3DRMCreate function.

UINT CreateDirect3DRM() { HRESULT hr; // Create the IDirect3DRM object. hr = Direct3DRMCreate(&pD3DRM); if (FAILED(hr)) { TRACE("Error creating Direct3d RM object\n"); return 1; } return 0; }

6. Creating the device for rendering

We create the device object from the back surface, since this surface is the one we will render to.

UINT CreateDevice() { HRESULT hr; hr = pD3DRM->CreateDeviceFromSurface( NULL, pDD, pDDSBack, &pD3DRMDevice); if (FAILED(hr)) { TRACE("Error %d creating d3drm device\n", int(LOWORD(hr))); return 1; } // success return 0; }

7. Creating the viewport

We do a bit more than just create the viewport here. We create the scene object and the camera object, as well as set the ambient light for the scene, and create a directional light.

UINT CreateViewport() { HRESULT hr; // First create the scene frame hr = pD3DRM->CreateFrame(NULL, &pScene); if (FAILED(hr)) { TRACE("Error creating the scene frame\n"); return 1; } // Next, create the camera as a child of the scene hr = pD3DRM->CreateFrame(pScene, &pCamera); if (FAILED(hr)) { TRACE("Error creating the scene frame\n"); return 2; } // Set the camera to lie somewhere on the negative z-axis, and // point towards the origin pCamera->SetPosition( pScene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(-300.0)); pCamera->SetOrientation( pScene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0)); // create lights LPDIRECT3DRMLIGHT pLightAmbient = NULL; LPDIRECT3DRMLIGHT pLightDirectional = NULL; LPDIRECT3DRMFRAME pLights = NULL; // Create two lights and a frame to attach them to // I haven't quite figured out the CreateLight's second // parameter yet. pD3DRM->CreateFrame(pScene, &pLights); pD3DRM->CreateLight(D3DRMLIGHT_AMBIENT, pD3DRMCreateColorRGB( D3DVALUE(0.3), D3DVALUE(0.3), D3DVALUE(0.3)), &pLightAmbient); pD3DRM->CreateLight(D3DRMLIGHT_DIRECTIONAL, D3DRMCreateColorRGB( D3DVALUE(0.8), D3DVALUE(0.8), D3DVALUE(0.8)), &pLightDirectional); // Orient the directional light pLights->SetOrientation(pScene, D3DVALUE(30.0), D3DVALUE(-20.0), D3DVALUE(50.0), D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0)); // Add ambient light to the scene, and the directional light // to the pLights frame pScene->AddLight(pLightAmbient); pLights->AddLight(pLightDirectional); // Create the viewport on the device hr = pD3DRM->CreateViewport(pD3DRMDevice, pCamera, 10, 10, 300, 220, &pViewport); if (FAILED(hr)) { TRACE("Error creating viewport\n"); return 3; } // set the back clipping field hr = pViewport->SetBack(D3DVAL(5000.0)); // Release the temporary lights created. It seems // they will have been copied for the scene during AddLight pLightAmbient->Release(); pLightDirectional->Release(); // success return 0; }

8. Creating your scene

We need some objects for the scene. Let's create a cube! Wheee! Here is the cube.x file, zipped. Unzip it into the directory from which your app runs.

SetRotation assigns values in radians to a frame that it must rotate around it's local axis each time the frame is rendered using Tick. Direct3D automatically performs this rotation when Tick is called.

// Put stuff into the scene UINT CreateDefaultScene() { HRESULT hr; // Create the frame for the cube hr = pD3DRM->CreateFrame(pScene, &pCube); if (FAILED(hr)) { TRACE("Error creating frame pCube\n"); return 1; } // Load the cube hr = pCube->Load("CUBE.X", NULL, D3DRMLOAD_FROMFILE, NULL, NULL); if (FAILED(hr)) { TRACE("Error [%d] loading cube.x\n", int(LOWORD(hr))); return 2; } // Set cube's position/orientation relative to scene pCube->SetPosition(pScene, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0)); pCube->SetRotation(pScene, D3DVALUE(1.0), D3DVALUE(1.0), D3DVALUE(0.0), D3DVALUE(0.1)); // success return 0; }

9. Putting it all together

Here is the tail-end of the app's InitInstance function:

InitDirectXGlobals(); TRACE("Calling InitDDraw\n"); InitDDraw(); SetMode(); // TRACE("Calling LoadJascPalette\n"); // LoadJascPalette("inspect.pal", 10, 240); TRACE("Calling CreatePrimarySurface\n"); CreatePrimarySurface(); TRACE("Calling CreateClipper\n"); CreateClipper(); // TRACE("Calling AttachPalette\n"); // AttachPalette(pDDPal); TRACE("Calling CreateDirect3DRM\n"); CreateDirect3DRM(); TRACE("Calling CreateDevice\n"); CreateDevice(); TRACE("Calling CreateViewport\n"); CreateViewport(); TRACE("Calling CreateDefaultScene\n"); CreateDefaultScene(); bAnimating = TRUE; return TRUE; }

10. Restoring lost surfaces

Same as the DirectDraw sample:

BOOL CheckSurfaces() { // Check the primary surface if (pDDSPrimary) { if (pDDSPrimary->IsLost() == DDERR_SURFACELOST) { pDDSPrimary->Restore(); return FALSE; } } return TRUE; }

11. The Rendering loop

Same as the DirectDraw sample:

BOOL CD3dRmAppApp::OnIdle(LONG lCount) { CWinApp::OnIdle(lCount); if (bAnimating) { HeartBeat(); Sleep(50); } return TRUE; }

12. The HeartBeat function

BOOL CD3dRmAppApp::HeartBeat() { HRESULT hr; // if (!CheckSurfaces) bForceUpdate = TRUE; // if (bForceUpdate) pViewport->ForceUpdate(10,10,300,220); hr = pD3DRM->Tick(D3DVALUE(1.0)); if (FAILED(hr)) { TRACE("Tick error!\n"); return FALSE; } // Call our routine for flipping the surfaces FlipSurfaces(); // No major errors return TRUE; }

13. Flipping surfaces

Same as for DirectDraw sample.

14. Tracing Direct3D errors

Coming soon(?).

15. Cleaning up

Coming soonish(?).

[ Home | Chapter 1 | Chapter 2 | Chapter 3 | Chapter 4 | Chapter 5 | Chapter 6 ]
Follow @d_joffe