Author : Ian Fleeton
Page : << Previous 4 Next >>
tell DirectDraw that you're happy to use the default refresh rate of the driver and use 0 for the extra flags.
Common Resolutions
640x480x8 is a safe mode to use as your default. Below are a list of the most common resolutions that typical display hardware supports. The number in parentheses is the aspect ratio.
320x200 (1.6)
320x240 (1.33)
512x384 (1.33)
640x480 (1.33)
800x600 (1.33)
1024x768 (1.33)
1280x1024 (1.25)
The trouble with drawing straight onto the screen is that you can see the screen being gradually being built up every frame, and this is not a good effect. We'll be using two flipping surfaces to do our animation. What happens is we draw onto the back surface, and when it is ready we make the back surface the primary surface (the one that is being shown on the monitor). Instead of moving all that screen data, we just swap over some pointers. DirectDraw gives us a function to do that so we can always write to the back surface by using the back pointer with no worries. These will be pointers to DirectDraw Surface interfaces and are defined like this:
// LPDIRECTDRAWSURFACE7 primary;
IDirectDrawSurface7* primary;
// LPDIRECTDRAWSURFACE7 back;
IDirectDrawSurface7* back;
Once again, in other tutorials you may see these names lpddsBack and lpddsPrimary (conforming to Hungarian Notation).
DDSURFACEDESC2 ddsd;
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps =
DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;
r = dd->CreateSurface(&ddsd, &primary, 0);
if(FAILED(r)) { /* Error */ }
ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
r = primary->GetAttachedSurface(&ddsd.ddsCaps, &back);
if(FAILED(r)) { /* Error */ }
Now I better do some explaining, huh? Okay, from the top we have DDSURFACEDESC2. This is a structure containing a lot of data describing the properties of a surface. These structures are used to make function calling quicker, because things would get bogged down if we had to push and pop over a hundred bytes every call. Instead we fill out this structure and pass its pointer to the function. DDSURFACEDESC2 describes many properties, most of which we won't even use. That's what dwFlags is for. dwFlags contains a bitfield of flags telling the function which members of the DDSURFACEDESC2 structure we are intending to use. It is very important to set the dwSize member. DirectX checks the size of its structures to make sure that they are the correct version. If you forget to set the size then the function just plain won't work. This is a good thing to keep in mind when something just fails for no reason.
The important pieces of information for creating our flipping surfaces are the capabilities of the surface and the back buffer count. We are going to have one primary surface and one back buffer. Later on, you might want to use triple buffering which is so easy to implement I'm going to tell you - just change the dwBackBufferCount member to 2 instead of 1. If you're going to play safe with me for a while, just use 1 back buffer. This needs less video memory.
ddsCaps.dwCaps is just something you have to fill out and forget about. For the primary surface we're going to use DDSCAPS_PRIMARYSURFACE, DDSCAPS_FLIP and DDSCAPS_COMPLEX which states that we're about to create a primary surface which is capable of flipping and is complex (because it can be flipped).
After this we call a CreateSurface() to create our primary surface. If that goes okay we then re-use the DDSURFACEDESC2 structure and just change the ddsCaps.dwCaps member to DDSCAPS_BACKBUFFER. To get our back surface, we use primary's IDirectDrawSurface7::GetAttachedSurface() instead of CreateSurface().
You'll want to flip the surfaces at the end of each frame. This code is quite simple but won't handle problems associated with losing control of the video card. I'll complicate this function in a later tutorial :). I have another tutorial hosted on www.gamecoding.co.uk that shows you how to limit the frame rate.
void flip()
{
dd->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN, 0);
primary->Flip(0, DDFLIP_WAIT);
}
WaitForVerticalBlank() is used to synchronise the flipping with the refresh rate of the monitor. Without it, you get an effect called tearing which happens because the monitor is displaying part of the screen with the old frame memory and the remaining part of the screen is filled with the new frame memory.
Flip() does the job. The DDFLIP_WAIT parameter causes the function to keep retrying if the video card is currently busy with other operations (such as blitting).
This is a little useful function that does what CLS does in BASIC except that you won't see the effects until you next call flip().
void clearScreen()
{
DDBLTFX clsBltFX;
ZeroMemory(&clsBltFX, sizeof(clsBltFX));
clsBltFX.dwSize = sizeof(clsBltFX);
back->Blt(0, 0, 0, DDBLT_COLORFILL | DDBLT_WAIT, &clsBltFX);
}
I had to keep my promise and include something practical. Now most of the boring stuff really is out of the way. While you're waiting for the next tutorial I guess I had better give you a SetPixel function. Before you can manipulate the surface memory, though, you'll have to lock it. Once you've finished, you should unlock it again. The purpose of this is to prevent the video card and driver from shifting the memory around as you're working on it. You'll be doing something like this:
lock();
// Some drawing with setPixel()
unlock();
Now let's code these functions.
unsigned char* videoMemory;
long pitch;
void lock()
{
DDSURFACEDESC2 ddsd;
ddsd.dwSize = sizeof(ddsd);
back->Lock(0, &ddsd, DDLOCK_WAIT, 0);
videoMemory = (unsigned char*)ddsd.lpSurface;
pitch = ddsd.lPitch;
}
void unlock()
{
back->Unlock(0);
}
void setPixel(int x, int y, unsigned char c)
{
videoMemory[x + pitch * y] = c;
}
videoMemory is the pointer to the memory we're going to manipulate. Notice it's an unsigned char pointer because we're in 8 bits per pixel mode (that is, one byte/unsigned char per pixel). pitch is a variable which tells us how many bytes we need to add to get to the next line of the surface. You might expect this to just be 640 (the pixel width of the display mode) but this isn't always so.
When you lock the back surface, you can specify a rectangle (RECT) to identify which portion of the memory you want to be locked. However, for most purposes I tend to lock the whole surface, so 0 is used as the first parameter. Instead of sending information with a DDSURFACEDESC2 structure, this time we're actually receiving it. There are a two things we need to know about the back surface - the pitch and the memory address. I'm going to file these away into two globals.
getPixel() can be coded similarly to setPixel() so I'll leave that as an exercise for you.
Always remember to lock and unlock the surfaces properly. Disaster can result if you don't and you might need to reboot!
At the end of your DirectDraw session you should clean up. The DirectDraw documentation says that doing it in response to the WM_DESTROY message is the best time if DirectDraw is used throughout the whole of the game's execution.
To clear up, just call Release() on each of the interface pointers and COM will take care of the rest internally.
if(dd)
{
dd->Release();
if(primary)
primary->Release();
}
Because back was created as part of a flipping chain with a call to GetAttachedSurface() it will be released automatically when you release the primary surface. Users of previous versions of Directx might be looking at this code in shock-horror because I haven't released the interfaces in reverse order. This is quite okay with DirectX 8, though.
There are two common modes of using colour; using a palette look-up and using RGB bit encoding. 8 bits make up just one byte which is small in comparison to 16, 24 and 32 bit modes so 8 bit images can be transferred from system memory (RAM) to video memory (VRAM) very quickly. In these tutorials I am giving you hints on how to write an unaccelerated 2D game. That means that you will be using 8 bit frequently and 16 bit less often and hardly ever using any more than that.
8 bits allows for 256 (2^8) different combinations of colours. However, dividing the 8 bits into an RGB bitfield such as RRRGGGBB gives us quite a few problems. Firstly, at best you only get 2^3 (8) shades of each colour. Also, one of the colours can only have 2 bits allocated to it. That's just 4 shades. So RGB encoding within a single byte is not useful.
A palette solves this. What happens is that 256 true colour descriptions are held on the video card and that each pixel's byte indexes a true colour from the table. It's a bit like one of those colour-by-numbers painting sets really. They give you a palette of a few suitable colours and the picture links
Page : << Previous 4 Next >>