Tricks of the Windows Game Programming Gurus:Advanced DirectDraw and Bitmapped Graphics

All ITKnowledge Source Code Search Tips Advanced Search |

| Tricks of the Windows Game Programming Gurus by Andre LaMothe Sams, Macmillan Computer Publishing ISBN: 0672313618 Pub Date: 09/27/99

Search this book:

Previous | Table of Contents | Next

Although the function seems reasonably simple, it’s extremely inefficient. There are a number of optimizations that you can take advantage of. The first problem is that the function locks and unlocks the sent surface each time. This is totally unacceptable. Locking/unlocking can take hundreds of microseconds on some video cards, and maybe even longer. The bottom line is that in a game loop, you should lock a surface once, do all the manipulation you’re going to do with it, and unlock it when you’re done, as shown in Figure 7.3. That way you don’t have to keep locking/unlocking, zeroing out memory, etc. For example, the memory fill of the `DDSURFACEDESC2` structure probably takes longer than the pixel plot! Not to mention that the function isn’t inline and the function overhead is probably killing you.

Figure 7.3 DirectDraw surfaces should be locked as little as possible.

These are the types of things that a game programmer needs to keep in mind. You aren’t writing a word processor program here—you need speed! Here’s another version of the function with a little bit of optimization, but it can still be 10 times faster:

    inline void Plot_Pixel_Fast16(int x, int y,

                                  int red, int green, int blue,

                                  USHORT *video_buffer, int lpitch)

    {

    // this function plots a pixel in 16-bit color mode

    // assuming that the caller already locked the surface

    // and is sending a pointer and byte pitch to it

    // first build up color WORD

    USHORT pixel = _RGB16BIT565(red,green,blue);

    // write the data

    video_buffer[x + y*(lpitch >> 1)] = pixel;

    } // end Plot_Pixel_Fast16

I still don’t like the multiply and shift, but this new version isn’t bad. You can get rid of both the multiply and shift with a couple of tricks. First, the shift is needed because `lPitch` is memory width in bytes. However, because you’re assuming that the caller already locked the surface and queried the memory pointer and pitch from the surface, it’s a no-brainer to add one more step to the process to compute a `WORD` or 16-bit strided version of `lpitch`, like this:

    int lpitch16 = (lpitch >> 1);

Basically, `lpitch16` is now the number of 16-bit `WORD`s that make up a video line. With this new value, you can rewrite the functions once again, like this:

    inline void Plot_Pixel_Faster16(int x, int y,

                                    int red, int green, int blue,

                                    USHORT *video_buffer, int lpitch16)

    {

    // this function plots a pixel in 16-bit color mode

    // assuming that the caller already locked the surface

    // and is sending a pointer and byte pitch to it

    // first build up color WORD

    USHORT pixel = _RGB16BIT565(red,green,blue);

    // write the data

    video_buffer[x + y*lpitch16] = pixel;

    } // end Plot_Pixel_Faster16

That’s getting there! The function is inline and has a single multiply, addition, and memory access. Not bad, but it could be better! The final optimization is to use a huge lookup table to get rid of the multiply, but this may not be needed because integer multiplies are getting down to single cycles on newer Pentium X architectures. It is a way to speed things up, however.

On the other hand, you can get rid of the multiply by using a number of shift-adds. For example, assuming a perfectly linear memory mode (without any extra stride per line), you know that it’s exactly 1,280 bytes from one video line to another in a 640×480 16-bit mode. Therefore, you need to multiply y by 640 because the array access will use automatic pointer arithmetic and scale anything in the `[]` array operator by a factor of 2 (2 bytes per `USHORT WORD`). Anyway, here’s the math:

    y*640 = y*512 + y*128

512 is equal to 29, and 128 is equal to 27. Therefore, if you were to shift y to the left 9 times and then add that to y shifted to the left 7 times, the result should be equivalent to y*640, or mathematically:

    y*640 = y*512 + y*128

          = (y << 9) + (y << 7)

That’s it! If you aren’t familiar with this trick, take a look at Figure 7.4. Basically, shifting any binary-encoded number to the right is the same as dividing by 2 and shifting to the left is the same as multiplying by 2. Furthermore, multiple shifts accumulate. Hence, you can use this property to perform very fast multiplication on numbers that are powers of 2. However, if the numbers aren’t powers of 2, you can always break them into a sum of products that are—as in the previous case.

Figure 7.4 Using binary shifting to multiply and divide.

> Note: You’ll see a lot more of these tricks when you get to the Chapter 11, “Algorithms, Data Structures, Memory Management, and Multithreading.”

Previous | Table of Contents | Next

Copyright © Macmillan Computer Publishing, Inc.

Products | Contact Us | About Us | Privacy | Ad Info | Home
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.