sunlight

First Steps

Introduction

In this part of the tutorial, we will cover:

  • Idle-time processing in message loops
  • DirectDraw initialisation

It's a lot of code and a load of principles, so bear with me... all will become clear!

Previous View Code Download Code Next

Idle-time Processing

When we create our DirectDraw application, we will want to do things slightly differently. Although the Windows event-driven model is still present, games define their own user interface. If you want to use Windows' dialog boxes and menus for games, that's fine. However, most modern games will want something different.

The major difference between your average application and a game is that a game operates even when you aren't doing anything. This background processing is done in the idle time of the process, so that when a message comes through (like keyboard input) it is dealt with quickly, and then control returned to the background task. Doing it this way avoids messy synchronisation problems.

Altering the Message Loop

At present, our message loop is simple.

	while (GetMessage(&msg, NULL, 0, 0))
		DispatchMessage(&msg);

GetMessage waits until a message is received before it does anything. This is obviously not optimal for our application. What we want is to check to see if a message is available. If it is not, we do a bit of our idle processing and go back to check again:

	for (;;)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
		{
			if (!GetMessage(&msg, NULL, 0, 0))
				break;
			DispatchMessage(&msg);
		}
		else
		{
			// idle-time processing
			OnIdle();
		}
	}

When a message is received, PeekMessage returns TRUE. This message is pulled out of the queue with GetMessage (checking to see if we should quit) and then dispatched.

If no messages come in, we call a function to perform our idle-time processing.

Preparing for DirectDraw

To prepare for DirectDraw, we need to pare down our application a little. Remove the WM_COMMAND and WM_PAINT handlers, and all the bitmap loading code.

We're also going to change the properties of our window, because we don't want to see the window artifacts when we start the program. To do this, we will change the window style to WS_POPUP and set the window to fill the screen:

	hWndMain = CreateWindowEx(WS_EX_APPWINDOW,
		"SampleWindowClass", "DirectDraw Sample", WS_POPUP,
		0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN),
		NULL, NULL, hInstance, NULL);

For this tutorial, the code has been in C, compilable with a C++ compiler. This must now change. DirectDraw is accessible from C, but it's far quicker and easier to write in C++. I strongly recommend that you compile all DirectDraw code with a C++ compiler. Don't worry, there's nothing new to learn - we're just doing this to simplify the DirectDraw code. All the rest will remain in C.

You will need:

  • The DirectX 8 SDK, or the latest release of the Platform SDK, from Microsoft. Install the header files, libraries and sample programs.
  • DirectX version 8.
  • Windows 95, 98 or Windows 2000. Sorry, but newer DirectX versions don't function on Windows NT 4.

Add DDRAW.H and DDUTIL.H to your header file includes:

#include <ddraw.h>
#include "ddutil.h"

and add DDRAW.LIB and DXGUID.LIB to your project, too.

Now, you're ready to go.

Initialising DirectDraw

We will need a new function to initialise DirectDraw. This function will first need to call DirectDrawCreateEx to create an IDirectDraw7 object.
// Initialise DirectDraw and go to full screen mode.
void InitDirectDraw()
{
    DirectDrawCreateEx(NULL, (void **)&pDD, IID_IDirectDraw7, NULL);

Next, we must set the co-operative level of the application. The co-operative level defines how much access we need to the video hardware. We want exclusive, full-screen access to the display:

    pDD->SetCooperativeLevel(hWndMain, 
        DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT);

Let's go for an easy resolution, first - 640x480, 16-bit colour.

    pDD->SetDisplayMode(640, 480, 16, 0, 0);

Now we are in full-screen mode, we will need a method of drawing to the screen. We could use the standard Windows GDI calls, but there is a much faster and more convenient method available: DirectDraw surfaces.

Surfaces

A surface simply corresponds to an area of memory (usually video memory). This method gives us direct access to things like the video memory buffer. Naturally enough, for such a resource, co-operation is important. Access to the buffer is regulated through the methods associated with the surface.

The primary surface corresponds to the video memory that goes on the display. We could draw directly to the primary surface, but this tends to cause horrible 'tearing' as the changes we make are visible as we draw them. We will instead use a back buffer. This is a buffer in video memory which can be swapped with the primary surface. This allows us to prepare a frame without showing it until we're done.

We will first get a primary surface object:

    DDSURFACEDESC2  ddsd;

    ZeroMemory(&ddsd, sizeof(DDSURFACEDESC2));
    ddsd.dwSize = sizeof(DDSURFACEDESC2);
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
            DDSCAPS_FLIP |
            DDSCAPS_COMPLEX;
    ddsd.dwBackBufferCount = 2;
    pDD->CreateSurface(&ddsd, &lpPrimary, NULL);

We have specified two back buffers (triple buffering) since this gives us a better average frame rate if the frame drawing time is close to the display refresh rate.

We now want a pointer to the back buffer:

    ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
    lpPrimary->GetAttachedSurface(&ddsd.ddsCaps, &lpBackBuffer);

Note that only one pointer is retrieved, despite there being two back buffers. When Windows swaps the buffers, it also swaps the back buffer pointers so that you can continue to use the back buffer pointer. Thus, we will use the back buffer pointer lpBackBuffer for all our drawing.

We're almost done. We just need a function to close the DirectDraw objects before we do DestroyWindow:

void ExitDirectDraw()
{
	lpPrimary->Release();
	lpPrimary = NULL;

	pDD->Release();
	pDD = NULL;
}

We must call this function when we get a WM_CLOSE message:

	case WM_CLOSE:
		ExitDirectDraw();
		DestroyWindow(hWnd);
		return 0;

We're done. We now have all the steps in place to begin drawing. If you run the program now, you should get a satisfying click from your monitor as it switches to 640x480... and then a blank screen. Press Alt+F4 to close the program.

In the next section, we'll load a bitmap from disk and display it on the screen... and then we'll go on to talk about sprites, which is a topic everyone enjoys.

 

Copyright © David McCabe, 1998 - 2001. All rights reserved.

You will need to download and install the m-math control to display any equations on this Web site. Without this control, you will not see most of the equations. Please do not e-mail me asking why the equations do not display!