by Michael Fötsch
July 10, 2000 (Last update: January 30, 2001)
This article was originally published at Gary Simmons’ Mr GameMaker.
Table of Contents
Introduction
Sometimes a simple Blt is simply not good enough! How would you blur the screen using Blt? How would you do an emboss effect, a twirl effect, or a fishbowl effect? For all of those, you need to be able to access the surface memory directly, as an array of bytes. That’s where IDirectDrawSurface::Lock comes in.
In the days of DOS and the VGA, programmers used to have full control over the video frame buffer. You knew its address and you didn’t have to think about other apps that you could interfere with. Well, those days are over and I do not miss them. (Hardware blitting is just so much better than 0xA000!) But you can still have a similar level of control. It’s just that you do not know the frame buffer’s address nowadays. Instead, you ask DirectDraw to give it to you. The function to call is IDirectDrawSurface::Lock. Besides giving you the pointer to the surface memory, it ensures that no other application will have access to the surface while you’re working with it. Everything else is not that different from SVGA programming, where you had to interpret pixel formats as well and where people heard about “pitch” for the first time.
In this tutorial, we’ll write a SetPixel routine that will help you understand the concepts behind direct surface access. We’ll shift and mask off bits, we’ll calculate offsets…and finally we’ll implement a special effect that makes use of the Lock method.
Note: This tutorial will get you started with Lock. It is, however, not intended as a complete reference of the Lock method or the involved structures. These topics are covered in sufficient detail in the DirectX SDK docs [1]. I think there’s no use in simply duplicating information, and I hope that’s okay for you as well.
But what’s better than a reference is the source code included with this tutorial that you can readily compile and experiment with.
The SetPixel routine
While you’ll probably never use SetPixel (and GetPixel) in any real-world application (the overhead of the function call makes it too slow), it serves as a good example of direct surface access. To get started, it will show you how to work with DDSURFACEDESC2 and how to interpret the pixel format information.
The function head of our SetPixel routine will look like this:
void SetRGBPixel(int x, int y, D3DCOLOR rgb);
First, we lock the surface for write access:
DDSURFACEDESC2 ddsd;
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
lpDDSBackBuffer->Lock(NULL, &ddsd,
DDLOCK_NOSYSLOCK | DDLOCK_WRITEONLY, NULL);
Next, we calculate the byte offset of the pixel at x,y:
DWORD Offset = y * ddsd.lPitch +
x * (ddsd.ddpfPixelFormat.dwRGBBitCount >> 3);
Note: “dwRGBBitCount >> 3″ is the same as “dwRGBBitCount / 8″. It gives the number of bytes per pixel.
This line introduces an important concept: pitch. Every scanline is lPitch bytes wide. Note that lPitch can be greater than dwWidth*BytesPerPixel. Imagine a surface as follows:

The surface is lPitch bytes wide, but only the white area contains an image.
The gray area is not used, but nevertheless it must be skipped when moving the pointer from one line to the next.
We have the address of the surface memory and we have the offset of the pixel. We also have the RGB color that should be written. But we can’t write “D3DCOLOR rgb” to the surface as is. It must be converted to match the surface’s pixel format first. “Converting” means scaling down the number of bits per color component if necessary and ensuring the correct RGB order (some pixel formats use BGR instead of RGB).
Note: We’ll always read and write DWORDs, regardless of the bit depth of the surface. There’d be a problem when working with 16- and 24-bit pixel formats, though. We’d overwrite a pixel that we don’t want to overwrite. Therefore, we read a DWORD first and combine it with the pixel to write before we actually write it back:

The above figure assumes a 16-bit mode. The left DWORD contains the two pixels that are currently on the surface.
The right DWORD contains the new pixel. Its HIWORD is not used.
These two DWORDs are combined and written back to the surface.
The pixel in the red WORD remains unchanged. It is the one immediately following the “green” pixel on the screen.
This is the code to read a pixel (a DWORD actually; would be 2 pixels in 16-bit mode and 1 1/3 pixels in 24-bit mode):
DWORD Pixel;
Pixel = *((LPDWORD)((DWORD)ddsd.lpSurface+Offset));
This DWORD is now going to be combined with the color contained in D3DCOLOR rgb. The bit masks in ddsd.ddpfPixelFormat tell us how to convert rgb to match the surface’s format. Following figure shows how the bit masks for a 16-bit mode could look like:

In hex format, dwRBitMask==0x1F, dwGBitMask==0x3E0, and dwBBitMask=0x7C00.
The missing bit might be used as an alpha channel, as indicated by dwRGBAlphaBitMask.
As a human being, you can immediately tell how many bits each color component uses (R=5, G=5, B=5) and in which order the color components appear (RGB). The question is, how to code that? The DirectX SDK docs contain an example of how to count the number of bits in a bit mask (see “DDPIXELFORMAT”):
WORD GetNumberOfBits( DWORD dwMask )
{
WORD wBits = 0;
while( dwMask )
{
dwMask = dwMask & ( dwMask - 1 );
wBits++;
}
return wBits;
}
(As long as dwMask contains one or more bits (“while (dwMask)”), the least significant bit of the mask is removed (“dwMask = dwMask & (dwMask-1)”) and the bit counter is incremented. Easy.)
The position of the mask (i.e. of its least-significant bit) can be obtained equally as easy:
WORD GetMaskPos( DWORD dwMask )
{
WORD wPos = 0;
while( !(dwMask & (1 << wPos)) ) wPos++;
return wPos;
}
Let’s calculate the positions and bit counts:
WORD wRBits=GetNumberOfBits(ddsd.ddpfPixelFormat.dwRBitMask);
WORD wGBits=GetNumberOfBits(ddsd.ddpfPixelFormat.dwGBitMask);
WORD wBBits=GetNumberOfBits(ddsd.ddpfPixelFormat.dwBBitMask);
WORD wRPos=GetMaskPos(ddsd.ddpfPixelFormat.dwRBitMask);
WORD wGPos=GetMaskPos(ddsd.ddpfPixelFormat.dwGBitMask);
WORD wBPos=GetMaskPos(ddsd.ddpfPixelFormat.dwBBitMask);
D3DCOLOR rgb is converted and combined with the DWORD on the surface like this:
- Shift the red component of D3DCOLOR rgb to the right so that it uses only wRBits bits (initially, it uses 8 bits.)
- Mask off the red bits of the color currently on the surface
- Shift the converted red component to the position of the red bit mask (wRPos)
- Combine using a bitwise OR
// Step 1:
BYTE NewRed = RGB_GETRED(rgb) >> (8 - NumRBits);
BYTE NewGreen = RGB_GETGREEN(rgb) >> (8 - NumGBits);
BYTE NewBlue = RGB_GETBLUE(rgb) >> (8 - NumBBits);
// Mask off red bits and store NewRed to the DWORD (steps 2-4):
Pixel = (Pixel & ~ddsd.ddpfPixelFormat.dwRBitMask) |
(NewRed << RBitsPos);
// Mask off green bits and store NewGreen:
Pixel = (Pixel & ~ddsd.ddpfPixelFormat.dwGBitMask) |
(NewGreen << GBitsPos);
// Mask off blue bits and store NewBlue:
Pixel = (Pixel & ~ddsd.ddpfPixelFormat.dwBBitMask) |
(NewBlue << BBitsPos);
// Done. We never touched any bit that we shouldn't touch.
Finally, let’s write back the DWORD and unlock the surface:
*((LPDWORD)((DWORD)ddsd.lpSurface+Offset)) = Pixel;
lpDDSBackBuffer->Unlock(NULL);
As I said, you should not make frequent use of such a function in your own code. It can be optimized (by performing some calculations outside of the function, for example), but its speed might still not be satisfying. In the following section (“The flag effect”), you’ll see how you can read and write pixels and yet not use a separate routine for this.
Note: Obviously, this will work only with non-palettized RGB surfaces. Working with 8-bit surfaces is straight-forward: Calculate the offset as seen in the code above and write a BYTE that contains the palette index of the color.
Furthermore, the width of the surface should be a multiple of 4.
Note: A GetPixel function will only read the DWORD and convert the color back to 8:8:8 format:
BYTE Red8Bit = (Pixel & dwRBitMask) << (8 - NumRBits);
...
The flag effect
Now, this can’t have been the advantage of Lock! The real strength of direct surface access is that you can use it to program a number of special effects that you can’t even achieve with D3D and texture stages. Here’s a short list (add everything else that Photoshop can do): Blur, pinch, punch, mosaic, wind, water ripples…and a real-time animated 2D flag effect.

The 2D flag effect in action. (Of course, this looks much better when animated!)
For our flag effect, we copy the bitmap pixel by pixel from its offscreen surface to the back buffer. In a normal blit, pixel 1,1 on the back buffer would correspond to pixel 1,1 in the bitmap. Here, we’ll distort the bitmap according to two moving sine curves. To fill pixel 1,3 on the back buffer, we take pixel 1+sin(3),3+sin(1) from the bitmap. To speed up the whole thing, we pre-calculate a sinus table with 128 entries. (As you’ll see, it’s easiest to work with powers of 2 like 128.)
int SinTable[128];
double DegRad = 57.29578f; // used to convert from degrees to radians
double d = 360.0f/129.0f; // used to get 360 degrees into 128 entries
void CalcSinTable()
{
for (int i=0; i < 128; i++)
SinTable[i] = (int)(sin(d * i / DegRad) * 20);
}
This is the SineEffect code (to be called once per frame):
// Lock the surfaces:
lpDDSBitmap->Lock(NULL, &SrcDesc,
DDLOCK_NOSYSLOCK | DDLOCK_READONLY, NULL);
lpDDSBackBuffer->Lock(NULL, &DestDesc,
DDLOCK_NOSYSLOCK | DDLOCK_WRITEONLY, NULL);
int Phase = timeGetTime / 5; // animate the sine wave slowly
for (int y=0; y < Height; y++)
{
for (int x=0; x < Width; x++)
{
// Calculate position of source pixel:
SrcXDelta = SinTable[(y + Phase) & 127] >> 1;
SrcX = x + SrcXDelta;
SrcYDelta = SinTable[(x + Phase) & 127];
SrcY = y + SrcYDelta;
// If that's off-surface, set to black:
if (SrcY < 0 || SrcY >= SrcDesc.dwHeight ||
SrcX < 0 || SrcX >= SrcDesc.dwWidth)
Pixel = 0;
// Otherwise, read the pixel:
else
Pixel = *((LPDWORD)((DWORD)SrcDesc.lpSurface +
SrcY * SrcDesc.lPitch + SrcX * BytesPerPixel));
// Save pixel and increment dest offset:
*((DWORD*)DestDesc.lpSurface) = Pixel;
(DWORD)DestDesc.lpSurface += BytesPerPixel;
}
// Skip the invisible area of the surface: (see figure)
(DWORD)DestDesc.lpSurface +=
DestDesc.lPitch - Width * BytesPerPixel;
}
lpDDSBitmap->Unlock(NULL);
lpDDSBackBuffer->Unlock(NULL);
Note: You can speed up the function by creating DDSBitmap in system memory. Add the following line to your surface creation code: (The sample uses ddutil.cpp for this. Change the code there.)
...
ddsd.ddsCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
...
Where do we go from here?™
This is only the beginning! I hope you’re not afraid anymore of locking surfaces (in case you ever were). If I wanted to be pathetic, I’d say “You have nothing to lose and everything to gain.” So, compile the Flag sample, experiment with the code, and start creating your own effects. It’s the number of special effects that will set your game apart from others!
References
DirectX Developer Center: http://msdn.microsoft.com/directx
Code archive
Flag sample: winmain.cpp
Sample flag bitmap: flag.bmp
Flickr Photostream
Spotify