: Windows API Tutorials

Implementation

Implementation

As mentioned before, all DirectDraw objects are enclosed in the Direct namespace. The fundamental object, Direct::Draw, is responsible for the initialization and the release of the DirectDraw subsystem. It can also be used to set up the cooperation level with Windows. It's easy to add more methods and options to this class, as the need arises.

namespace Direct { // Direct Draw object
class Draw {
public:
Draw ();
~Draw () {
_pDraw->Release ();
}
void SetCoopNormal (HWND h) {
HRESULT res = _pDraw->SetCooperativeLevel(h, DDSCL_NORMAL);
if (res != DD_OK) throw "Cannot set normal cooperative level";
}
IDirectDraw * operator-> () { return _pDraw; }
private:
IDirectDraw * _pDraw;
};
}
// implementation file
using namespace Direct;
Draw::Draw () {
HRESULT res = ::DirectDrawCreate (0, &_pDraw, 0);
if (res != DD_OK) throw "Cannot create Direct Draw object";
}

DirectDraw is based on COM interfaces, so it makes sense to encapsulate the "interface magic" in a handy template. The Direct::IFace class takes care of reference counting, pointer dereferencing and assignment (see the assignment of surfaces above).

template<class I>
class IFace {
public:
IFace (IFace<I> & i) {
i->AddRef ();
_i = i._i;
}
~IFace () {
if (_i) _i->Release ();
}
void operator = (IFace<I> & i) {
if (i._i) i._i->AddRef ();
if (_i) _i->Release ();
_i = i._i;
}
I * operator-> () { return _i; }
operator I * () { return _i; }
protected:
IFace () : _i (0) {}
protected:
I * _i;
};

A drawing surface is an example of an interface. First we define a generic surface, then we'll specialize it to primary and off-screen surfaces.

class Surface: public IFace<IDirectDrawSurface> {
friend class SurfaceBuf;
protected:
// Locking and unlocking the whole surface
void Lock (SurfaceDesc & desc) {
assert (_i != 0);
HRESULT res;
do res = _i->Lock (0, &desc, 0, 0); while (res == DDERR_WASSTILLDRAWING);
if (res != DD_OK) throw "Cannot lock surface";
}
void Unlock () {
assert (_i != 0);
_i->Unlock (0);
}
public:
Surface () {}
void GetDescription (SurfaceDesc & desc) {
HRESULT res = _i->GetSurfaceDesc (&desc);
if (res != DD_OK) throw "Cannot get surface description";
}
void SetClipper (Clipper & clipper) {
assert (_i != 0);
HRESULT res = _i->SetClipper (clipper);
if (res != DD_OK) throw "Cannot set clipper";
}
void BltFrom (Surface & src, RECT * dstRect = 0, RECT * srcRect = 0) {
assert (_i != 0);
HRESULT res = _i->Blt (dstRect, src._i, srcRect, 0, 0);
if (res != DD_OK) throw "Cannot perform a blt";
}
void Fill (COLORREF color);
};

When you create a surface, it has to be one of the derived ones. The primary surface lets you draw directly on your screen, the off-screen surface lets you prepare a picture off-screen in a format that can be very quickly transferred to the screen.

class PrimarySurface: public Surface {
public:
PrimarySurface () {}
PrimarySurface (Draw & draw) {
Init (draw);
}
void Init (Draw & draw) {
SurfaceDesc desc;
desc.SetCapabilities (DDSCAPS_PRIMARYSURFACE);
HRESULT res = draw->CreateSurface (&desc, &_i, 0);
if (res != DD_OK) throw "Cannot create primary surface";
}
};
class OffScreenSurface: public Surface {
public:
OffScreenSurface () {}
OffScreenSurface (Draw & draw, int width, int height) {
Init (draw, width, height);
}
void Init (Draw & draw, int width, int height) {
SurfaceDesc desc;
desc.SetCapabilities (DDSCAPS_OFFSCREENPLAIN);
desc.SetDimensions (width, height);
HRESULT res = draw->CreateSurface (&desc, &_i, 0);
if (res != DD_OK) throw "Cannot create off-screen surface";
}
};

In order to directly access pixels in a surface, you have to lock it. The class, SurfaceBuf, encapsulates locking and unlocking in its constructor and destructor, so that you don't have to worry about it. It also provides direct access to the buffer through its SetPixel method. Notice the low-level address calculations and color formatting.

class SurfaceBuf {
public:
SurfaceBuf (Surface & surface) : _surface (surface) {
SurfaceDesc desc;
surface.Lock (desc);
_pitch = desc.Pitch ();
_buf = static_cast<unsigned char *> (desc.Buffer ());
_format.Init (desc);
int bpp = _format.BitsPp ();
if (bpp != 16 && bpp != 24 && bpp != 32) {
surface.Unlock ();
throw "Only high color and true color supported";
}
}
~SurfaceBuf () {
_surface.Unlock ();
}
void SetPixel (int x, int y, COLORREF color) {
switch (_format.BitsPp ()) {
case 16:
{
int offset = y * _pitch + x * 2;
unsigned short * p = reinterpret_cast<unsigned short *> ( _buf + offset);
*p &= _format.Mask ();
*p |= static_cast<unsigned short> ( _format.ColorValue16 (color));
}
break;
case 24:
{
int offset = y * _pitch + x * 3;
unsigned long * p = reinterpret_cast<unsigned long *> ( _buf + offset);
*p &= _format.Mask ();
*p |= _format.ColorValue24 (color);
}
break;
case 32:
{
int offset = y * _pitch + x * 4;
unsigned long * p = reinterpret_cast<unsigned long *> ( _buf + offset);
*p &= _format.Mask ();
*p |= _format.ColorValue32 (color);
}
break;
}
}
private:
Surface & _surface;
unsigned char * _buf;
int _pitch;
PixelFormat _format;
};

There is a separate class to deal with the formatting of color values. As you can see, depending on the bits-per-pixel setting of the video card, different color encodings are used. The surface descriptor contains bit masks that are used in this encoding. These masks vary not only between bpp settings, but also between video cards. The most complex is the encoding of the color for the 6-bit setting. In 32-bit mode the card can actually support more colors that can be packed into the standard Windows COLORREF. Here, we're not making use of it, but it would be an interesting area to experiment.

class PixelFormat {
public:
PixelFormat () {}
PixelFormat (SurfaceDesc & desc) {
Init (desc);
}
void Init (SurfaceDesc & desc);
int BitsPp () const { return _bpp; }
unsigned long Mask () const { return _mask; }
unsigned long ColorValue16 (COLORREF color) {
return ( (GetRValue (color) << _redShift) & (_redMask << 16) | (GetGValue (color) << _greenShift) & (_greenMask << 16) | (GetBValue (color) << _blueShift) & (_blueMask << 16)) >> 16;
}
unsigned long ColorValue24 (COLORREF color) {
return (GetRValue (color) << 16) & _redMask | (GetGValue (color) << 8) & _greenMask | GetBValue (color) & _blueMask;
}
unsigned long ColorValue32 (COLORREF color) {
return (GetRValue (color) << 16) & _redMask | (GetGValue (color) << 8) & _greenMask | GetBValue (color) & _blueMask;
}
unsigned long ColorValue (COLORREF color) {
switch (_bpp) {
case 16:
return ColorValue16 (color);
case 24:
return ColorValue24 (color);
case 32:
return ColorValue32 (color);
default:
throw "PixelFormat: only 16, 24 and 32 bits supported";
}
}
private:
int _bpp; // bits per pixel 4, 8, 16, 24, or 32
unsigned long _redMask;
unsigned long _greenMask;
unsigned long _blueMask;
unsigned _redShift;
unsigned _greenShift;
unsigned _blueShift;
unsigned long _mask;
};
unsigned HiBitShift (unsigned val) {
unsigned i = 0;
while (val != 0) {
val >>= 1;
++i;
}
return i;
}
void PixelFormat::Init (SurfaceDesc & desc) {
DDPIXELFORMAT & format = desc.PixelFormat ();
if (format.dwFlags != DDPF_RGB) throw "Direct Draw: Non-RGB formats not supported";
_bpp = format.dwRGBBitCount;
_redMask = format.dwRBitMask;
_greenMask = format.dwGBitMask;
_blueMask = format.dwBBitMask;
_mask = ~(_redMask | _greenMask | _blueMask);
switch (_bpp) {
case 16:
_redShift = HiBitShift (_redMask) + 8;
_greenShift = HiBitShift (_greenMask) + 8;
_blueShift = HiBitShift (_blueMask) + 8;
break;
case 24:
break;
case 32:
break;
default:
throw "Only 16, 24 and 32 bit graphics supported";
}
}

Notice that this tutorial only scratches the surface of DirectX. There are several versions of DirectDraw. There is Direct3D for three-dimensional graphics (although it seems like Open GL might be a better platform). Sound can be dealt with using DirectSound and input devices have their DirectInput. All these subsystems can be encapsulated using similar techniques.


: 0.050. /Cache: 2 / 0