Topic : Palettes and Pixels in DirectDraw
Author : Joseph Farrell
Page : << Previous 3  Next >>
Go to page :


a particular pixel. We'll get back to it in just a minute. There's one thing I want to get out of the way first. When you're done plotting pixels, you need to unlock the surface that you locked. The prototype for IDirectDrawSurface7::Unlock() is this:

HRESULT Unlock(LPRECT lpRect);

The parameter is the same RECT you passed to Lock(). All right, let's plot some pixels.

Plotting Pixels
The first thing is to typecast the pointer we got from the Lock() function. Logically, we're going to want a pointer that's the same size as the pixels we're writing. So we want a UCHAR* for 8-bit color depth, a USHORT* for 16-bit, and a UINT* for 32-bit. But what about 24-bit? Since there's no 24-bit data type, we'll need to use a UCHAR* for this also, and do things a little differently.

We should also convert the lPitch member so it's in the same units as our pointer. Remember, when we first retrieve lPitch from the DDSURFACEDESC2 structure, it's in bytes. For 16-bit mode, we should divide it by 2 to get the pitch in terms of USHORTs, and for 32-bit mode, we divide by 4 to get it in terms of UINTs.

Let's stop for a second and look at some example code. Suppose we are in 32-bit mode and want to lock the primary surface for plotting pixels. Here's what we would do:

// declare and initialize structure
DDSURFACEDESC2 ddsd;
INIT_DXSTRUCT(ddsd);

// lock the surface
lpddsPrimary->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL);

// now convert the pointer and the pitch
UINT* buffer = (UINT*)ddsd.lpSurface;
UINT nPitch = ddsd.lPitch >> 2;


Now let me go ahead and show you a pixel-plotting function, and then I'll explain it in detail:

inline void PlotPixel32(int x, int y, UINT color, UINT *buffer, int nPitch)
{
    buffer[y*nPitch + x] = color;
}


All right, let's take it apart. First of all, notice that I have declared it as an inline function. This is to eliminate the overhead of passing all the parameters every time we want to do something as simple as plotting one pixel. Now, the only line in the function locates the pixel of interest and sets it to the color we want. Note that the color is just one value, not one each for red, green, and blue, so we'll need to use our RGB_32BIT() macro to create the color for this function.

The formula used to locate the pixel at location (x, y) is y*nPitch + x. This makes sense because nPitch is the number of UINTs in a line. Multiplying that by the y value yields the correct row, and then we just need to add x to locate the correct column. That's all you need to know! Pretty simple, hey? Let me show you a couple of functions for plotting pixels in 16-bit and 8-bit modes, because they're very similar:

inline void PlotPixel8(int x, int y, UCHAR color, UCHAR* buffer, int byPitch)
{
    buffer[y*byPitch + x] = color;
}

inline void PlotPixel16(int x, int y, USHORT color, USHORT* buffer, int nPitch)
{
    buffer[y*nPitch + x] = color;
}


The only things different about these functions are the data types used for the parameters. Also remember that for the 8-bit version, the pitch is in bytes, and for the 16-bit version, the pitch is in USHORTs. That just leaves one mode left, and that's 24-bit. Since there's no data type that's 24 bits, we need to pass and write values for red, green, and blue individually. The function might look like this:

inline void PlotPixel24(int x, int y, UCHAR r, UCHAR g, UCHAR b, UCHAR* buffer, int byPitch)
{
    int index = y*byPitch + x*3;

    buffer[index] = r;
    buffer[index+1] = g;
    buffer[index+2] = b;
}


As you can see, this is going to be a bit slower because we have an extra multiply, and three memory writes. You can speed it up a little bit by replacing x*3 with (x+x+x) or (x<<1)+x, but that probably won't do a lot for you. Still, it can't hurt to throw it in for good measure. Now you can see why working with 24-bit color is a bit of a pain.

Notes on Speed
There are a few things you should do to make sure this all goes as quickly as possible. First of all, locking a surface is not the fastest thing in the world, so you should try to keep the number of times you lock a surface per frame to a minimum. For many things, including a simple pixel-plotting demo, one lock per frame is all you'll need.

Second, let's say you're working in 640x480x16. The pitch will almost always be 1,280 bytes. You still need to take into consideration that it might not always be that way, but you may want to add some optimized code to take advantage of the fact that the pitch will almost always be 1,280. More specifically, you can use a pixel-plotting function that uses bit shifts instead of multiplication to locate the correct pixel. The function we were using earlier used this line of code:

buffer[y*nPitch + x] = color;

We can speed that up if we know nPitch is going to be 640 (since nPitch is in USHORTs, not bytes). 640 isn't a power of two, but 512 is 2^9, and 128 is 2^7. And guess what? 512 + 128 = 640. :) Convenient, hey? The line above will execute just a tad faster if we use this instead:

buffer[(y<<9) + (y<<7) + x] = color;

Most resolutions you'll use have widths that are either powers of two, or a sum of two powers of two. No such luck if you're in 800x600 (512 + 256 + 32 = 800), but it's a good trick to keep in mind. Bit shifts are some of the fastest operations you could ask for.

Finally, if you are going to be using two functions -- one using multiplication, and the other using bit-shifts -- keep the comparison outside of the plotting loop. That is, don't do this:

for (x=0; x<1000; x++)
{
    if (nPitch == 640)
        PlotPixelFast16();
    else
        PlotPixel16();
}


Putting the decision statement inside the loop is just detracting from the advantage you gain with the fast function. Do it this way instead:

if (nPitch == 640)
{
    for (x=0; x<1000; x++)
        PlotPixelFast16( parameters );
}
else
{
    for (x=0; x<1000; x++)
        PlotPixel16( parameters );
}


Make sense? Whenever you've got a big for loop, keep as much as you can on the outside of it. There's no sense in making a calculation a thousand times that's going to come up the same way every time. :) Also, if you're going to be plotting pixels in such a way that the locations are in a pattern, like a horizontal or vertical line (or even a diagonal one), there's no need to re-calculate the location every time. Look at this example, for drawing a line of randomly colored pixels:

for (x=0; x<640; x++)
    PlotPixel16(x, 50, color, buffer, pitch);

The function is re-calculating the correct line every time, when all you need to do is advance the pointer by one each time. This is a faster way of doing it:

// get the address of the line
USHORT* temp = &buffer[50*pitch];

// plot the pixels
for (x=0; x<640; x++)
{
    *temp = color;
    temp++;
}


None of these things are going to make a huge difference in your program's speed unless you're plotting thousands and thousands of pixels each frame -- which you might be doing -- but even if not, it's always good policy to make any little optimization you can find, no matter how insignificant it seems.

Considering how long the previous articles in this series have been, stopping this one here would seem like kind of a rip-off, wouldn't it? Now that we know how to plot pixels, let's take a look at some of the things you can use it for.

Fading Out
One of the most commonly used screen transitions in games is the fade to black, or the fade in from black. Each one is achieved in the same manner. You simply draw your frame, then apply some sort of transformation that alters the brightness of the image. To fade out, you decrease the brightness from 100% to 0%, and to fade in, you increase it from 0% to 100%. If you're working in a palettized mode, you've got it easy! You just change the colors in your palette, and the result shows up onscreen. If you're working in RGB mode, you have to consider other methods.

Now, I'll say right here that manually fading the screen has some better alternatives. You can use

Page : << Previous 3  Next >>