15,746,110 members
Articles / Multimedia / DirectX
Article
Posted 16 May 2006

99.6K views
74 bookmarked

# DirectX Tutorial Part I: DirectX Dialog Template for fast pixelwise drawing to DirectX surfaces

Rate me:
CDirectXDialog is a base class for dialog classes in which you want to use DirectX.

## Introduction

This is the first part of my article series on DirectX programming: working titled "Where do we come from?". I will give you a brief overview of some techniques which were used back in times where maybe some blueprints of DirectX existed on Mr. Gates' work bench. This will give us a better understanding about the wondrous things under the hood of DirectX and modern graphics hardware. This tutorial consists of six parts, with two major examples of how to use the code provided with this tutorial.

• DirectX tutorial part I: DirectX Dialog Template
• DirectX tutorial part II: Using the `CDirectXDialog` class in a pinball game
• DirectX tutorial part III: Basic 3D mathematics
• DirectX tutorial part IV: Using the `Vector` class in a Vectorballs scroller
• DirectX tutorial part V: Lambert and Gouraud polygon fillers from scratch
• DirectX tutorial part VI: Doing it the Direct3D way...

At the end of this tutorial, you should have a basic knowledge about 3D mathematics, polygon fillers, and about how easy it is to do all these kind of things with DirectX. But first, let us begin our expedition with a glimpse at the DirectX capabilities for fast accessing the surface memory. This will allow us to emulate VGA like drawing to the video memory (remember 0xA000). Our Lambert and Gouraud polygon fillers will only use this functionality to manipulate pixels on screen.

To give you an idea of how fast this pixel routine is, I decided to calculate a mathematical figure (see the screenshot above) which is based on the function, f(x,y) = (x2+y2)2. As you can see, this function is symmetrical to both axis, so it is sufficient to calculate only one quarter and to flip the values to the other ones. But doing this will not prevent us from drawing all of the pixels from any quarter. If you maximize the dialog on an average resolution, this means that you have to draw around 780000 pixels. On my current hardware configuration (Pentium IV, 3GHz,...), this only takes about 72ms. By the way, I've done this maybe 15 years ago on my first computer (it was an Amiga, and only had 7.14MHz). For the default resolution of 320x256=81920pixels, printing the whole image took around 5 minutes (I've done all of the programming in MC6800x0 assembly those times). For hardware enthusiasts, let's calculate the increase of speed: (780000 / 72ms) / (81920 / (5*60000ms)) = 39672.85!!! I think this value is amazing, isn't it?

## Requirements for compilation

You will have to download and install the Microsoft DirectX SDK for compiling the sample projects provided by this tutorial. If the compiler cannot find the appropriate DirectX headers, please take a look at your project settings. You will have to specify where exactly the common includes are located. In my special case, I have installed the DirectX SDK in C:\DXSDK. Therefore, the additional include path of my project settings is C:\DXSDK\Samples\C++\Common\Include.

## Direct Surface Access

The `IDirectDrawSurface7` interface provides a method for obtaining DirectX surface memory for directly manipulating its pixels. The method is called `Lock`, and you will have to take care that you will release the lock when your drawing to the surface memory is finished. The appropriate release method is named `Unlock`. While your application has a lock on the surface memory, no other DirectX unit may have access to it, not even the graphics hardware itself. Therefore, you shouldn't use this method in a real DirectX application, because of the overall pure performance. But in our case, this is not very important. At the end, we are just interested in a straightforward implementation of a pixel based direct drawing routine.

```HRESULT Lock(LPRECT lpDestRect,
LPDDSURFACEDESC2 lpDDSurfaceDesc,
DWORD dwFlags, HANDLE hEvent );
HRESULT UnLock(LPRECT lpRect);```

When you obtain access to the surface memory, the DirectX runtime will return information about that surface in a structure called `DDSURFACEDESC2`. The memory layout of a DirectX surface can vary according to your system's display settings. Therefore, we will have to scrutinize this structure to understand which bytes correspond to which pixel on screen. The surface memory can be divided into a visible and a non-visible area. The width and height of the visible area are determined by the attributes `dwWidth` and `dwHeight` of the `DDSURFACEDESC2` structure. I am not quite sure why there is a non-visible part, but I suppose that it has something to do with horizontal scrolling. By changing the starting address of the surface, you can manipulate which part of the memory is used for each scan line. The effect is that pixels from the non-visible area become visible, which looks as if you are scrolling the surface.

Now, to change the pixels on the surface, we must map the screen coordinates to positions within the surface memory. This mapping can be described through a linear mathematical function:

`memory_position = start_address + x * bytes_per_pixel + y * bytes_per_line`

Every pixel uses some bits in memory, according to your system's display settings. To determine how many bytes are actually occupied by a pixel (variable `bytes_per_pixel`), we can look at the `dwRGBBitCount` attribute of the `DDPIXELFORMAT` structure. The variable `bytes_per_line` corresponds to the `lPitch` attribute of the `DDSURFACEDESC2` structure. But calculating the memory position of a specific pixel is not enough to set the right color for it. To do this, we also have to check which bits belong to which color channel. In 32 bit mode, the implementation is very straightforward. In this case, you can directly put the RGB color into the surface memory, because each pixel is four bytes wide. But in other cases, the mapping is a little more complicated. In 16 bit mode, for instance, each pixel only occupies two bytes of surface memory. This means that, each color channel has less than 8 bits for its representation (only 5 bits to be exact). Therefore, we will have to mask and shift our RGB values to the corresponding pixel format. The only piece of information we need to accomplish this task is the bit mask and bit position of each color channel. The corresponding attributes are `dwRBitMask`, `dwGBitMask`, and `dwBBitMask` of the `DDPIXELFORMAT` structure.

The table below gives you the values of these attributes for the 16 bit mode. As formerly stated, the masks in this mode are 5 bits wide, where the blue color channel occupies the foremost bits and the red color channel the uppermost.

#### Values of the color channel bit mask

AttributeValue
`dwRBitMask`01111100 00000000
`dwGBitMask`00000011 11100000
`dwBBitMask`00000000 00011111

To determine the number of bits in the bit mask, we constantly have to reduce the bit mask by one and apply a logical AND operation. The following operation of the `CDirectXDialog` class performs this calculation.

```int CDirectXDialog::getNumberOfBits(DWORD mask)
{
int nob = 0;
{
nob++;
}
return nob;
}```

Finally, the starting bit of the bit mask can be identified by constantly applying a logical AND operation to the bit mask and a variable bit which is shifted from right to left commencing with the foremost bit. For the sake of completeness, here is the operation for the determination of this starting bit position.

```int CDirectXDialog::getBitMaskPosition(DWORD mask)
{
int pos = 0;
while (!(mask & 1 << pos)) pos++;
return pos;
}```

## Manipulating pixels

The DirectX surface access is completely wrapped by the class `CDirectXDialog`. When you want to access the surface memory, you have to call the operation `BackbufferLock()` of this class. After you have finished drawing, you will have to call `BackbufferUnlock()`. It is very crucial to call these operations pair-wise, that I decided to make them private. Therefore, you cannot directly access these operations. You rather have to use a worker object, which is responsible to lock and unlock the surface. This worker object class is a friend class of `CDirectXDialog`, and is called `CDirectXLockGuard`.

The first issue which is handled in `BackbufferLock()` is the lock of the DirectX surface. Afterwards, the surface is examined to determine the bit mask and bit position of each color channel and, of course, the pitch values. The actual `setPixel` operation of `CDirectXDialog` can be accessed through a member function pointer. In `BackbufferLock()`, this function pointer is initialized according to the current set display mode. The concrete functions for setting a pixel in the DirectX surface are called `CDirectXDialog::setPixelOPTIMIZED` and `CDirectXDialog::setPixelSECURE`. The optimized version only copies the RGB value to the corresponding memory position. The secure version also masks and shifts the RGB color value to the right pixel format, thus being much slower as the optimized one.

```inline void CDirectXDialog::setPixelOPTIMIZED(int x, int y, DWORD color)
{
*(unsigned int*)(backbuffervideodata +
x*x_pitch + y*y_pitch) = color;
}

inline void CDirectXDialog::setPixelSECURE(int x, int y, DWORD color)
{
int offset = x*x_pitch + y*y_pitch;
DWORD Pixel = *(LPDWORD)((DWORD)backbuffervideodata + offset);

Pixel = (Pixel & ~ sDesc.ddpfPixelFormat.dwRBitMask) |
((RGB_GETRED(color) >> (8 - rbits)) << rpos);
Pixel = (Pixel & ~ sDesc.ddpfPixelFormat.dwGBitMask) |
((RGB_GETGREEN(color) >> (8 - gbits)) << gpos);
Pixel = (Pixel & ~ sDesc.ddpfPixelFormat.dwBBitMask) |
((RGB_GETBLUE(color) >> (8 - bbits)) << bpos);

*(unsigned int*)(backbuffervideodata + offset) = Pixel;
}```

## The CDirectXDialog class

`CDirectXDialog` is an abstract class. Therefore, you will have to subclass it and overwrite the pure virtual function `displayFrame()`. This operation is called by the framework each time the dialog has to be redrawn. The circle image of this example is drawn in the `displayFrame()` operation of the `CDlgBackgroundArtDecoDlg` class. Other important and over-writeable operations are.

• `initDirectDraw()`

Called by the framework when the dialog is initialized. You can create and initialize other DirectX resources like other surfaces here.

• `restoreSurfaces()`

Called by the framework when an exception has occurred and the DirectX drawing surface is lost. You will have to re-create and initialize your DirectX resources here.

• `freeDirectXResources()`

Called by the framework when the dialog is going to destroyed. You can release your created resources here.

## Drawing the image

The displayed image is calculated and drawn in the `displayFrame()` operation of the `CDlgBackgroundArtDecoDlg` class. I wanted to examine the speed benefits between a C++ and a straight assembler implementation. Therefore, you can choose which one to activate. If the preprocessor variable `IS_IT_WORTH_IT` is defined, then the drawing is accomplished by the x86 assembler routine. Otherwise, it is done by the C++ implementation. By the way, I am using a member function pointer to call the `setPixel` member function from within an assember code fragment. Refer to my other article, if you want to know more about this topic (How to invoke C++ member operations from inline-assembler code segments).

```void CDlgBackgroundArtDecoDlg::displayFrame()
{
CRect rect; GetClientRect(rect);

int width = rect.Width() / 2;
int height = rect.Height() / 2;

DWORD starttime,stoptime;

starttime = GetTickCount();
{
g_pDisplay.Clear();
CDirectXLockGuard lock(this);

#if defined(IS_IT_WORTH_IT)

setPixelPTR _setPixel = setPixel;
int x, y, c, z = zoom;
_asm
{
mov edx, width
loop1:        mov ebx, height
loop2:        mov eax, edx;    //color = (x*x+y*y)
imul eax, eax;
mov ecx, ebx;
imul ecx, ecx;
imul eax, eax;    //color = color*color;

mov ecx, z;        //zoom
sar eax, cl;

and eax, 0xFF;
mov cl, al;
and cl, 0x80;    //if (color >= 128) color = color - 127;
jz weiter;
xor eax, 0x7F;

weiter:        shl eax, 8+1;

//Backup
mov x, edx;
mov y, ebx;
mov c, eax;

push eax;        //color
mov eax, ebx;    //y
push eax;
mov eax, edx;    //x
push eax;
mov ecx, this;    //this-call of member function pointer
call _setPixel;

mov eax, c;        //color
push eax;
mov eax, y;        //y
push eax;
mov eax, width;    //x
sub eax, x;
push eax;
mov ecx, this;    //this-call of member function pointer
call _setPixel;

mov eax, c;        //color
push eax;
mov eax, height;//y
sub eax, y;
push eax;
mov eax, width;    //x
sub eax, x;
push eax;
mov ecx, this;    //this-call of member function pointer
call _setPixel;

mov eax, c;        //color
push eax;
mov eax, height;//y
sub eax, y;
push eax;
mov eax, x;        //x
push eax;
mov ecx, this;    //this-call of member function pointer
call _setPixel;

mov edx, x;
mov ebx, y;

sub ebx, 1
jge loop2
sub edx, 1
jge loop1
}
#else
for (long x = 0; x < width; x++)
for (long y = 0; y < height; y++)
{
long g = x*x + y*y;
g = g * g;
g = g >> zoom;
g = g & 0xFf;
if (g & 0x80) // if (g > 0x7f) g = 0x7f - g;
g = 0x7f ^ g;
g = g << 1;
(this->*setPixel)(width + x, height + y,RGBA_MAKE(g,0,0,0));
(this->*setPixel)(width - x, height + y,RGBA_MAKE(0,g,0,0));
(this->*setPixel)(width + x, height - y,RGBA_MAKE(0,0,g,0));
(this->*setPixel)(width - x, height - y,RGBA_MAKE(g,g,g,0));
}
#endif
}
stoptime = GetTickCount();

char buffer[128];
sprintf(buffer, "time: %4dms", stoptime - starttime);
g_pTextSurface->DrawText(NULL, buffer, 0, 0, RGB(0,0,0), RGB(255,255,0));
g_pDisplay.Blt(20, 20, g_pTextSurface, NULL);

CDialog::OnPaint();
}```

## What is left to be said?

Some might say, that this article is kind of confusing, because on the one hand, I want to explain techniques which are older than DirectX, on the other hand, I am writing about DirectX. But let me assure you, this is all we need to know about DirectX. The only reason I am using DirectX is, that it is fast, and the pixel drawing routine I presented here corresponds to those in the DOS VGA mode. I could have used Microsoft's GDI to implement the pixel drawing routine, and frankly, I have had a version which was based on GDI. But it was too slow to create fast polygon fillers with it. Therefore, I decided that this is a good tradeoff. Hope that you still find these articles helpful and interesting.

A list of licenses authors might use can be found here

Written By
Chief Technology Officer W3L
Germany
-Since 1th August 2007: Chief Technology Officer of W3L
-2002/08/01-2007/07/31: PhD student
-1997/10/15-2002/07/31: Studied Electrical Engineering and Computer Science

 First Prev Next
 Error message occured when I running the demo exe file btcat123-Mar-11 2:17 btcat1 23-Mar-11 2:17
 Manipulating Display Memory NovaNuker23-May-07 2:04 NovaNuker 23-May-07 2:04
 Re: Manipulating Display Memory Doga Arinir23-May-07 2:20 Doga Arinir 23-May-07 2:20
 Why do you have to use Assembler language to draw the picture ? alexnpl27-Sep-06 17:05 alexnpl 27-Sep-06 17:05
 Re: Why do you have to use Assembler language to draw the picture ? Doga Arinir17-Oct-06 23:11 Doga Arinir 17-Oct-06 23:11
 does not compile with DirectX 9 Rolf Steenge10-Jul-06 23:20 Rolf Steenge 10-Jul-06 23:20
 Re: does not compile with DirectX 9 Prakash Nadar5-Aug-06 22:47 Prakash Nadar 5-Aug-06 22:47
 Re: does not compile with DirectX 9 vows_siu@yahoo.com.hk11-Oct-06 4:38 vows_siu@yahoo.com.hk 11-Oct-06 4:38
 Re: does not compile with DirectX 9 laserbeak4314-Aug-07 21:49 laserbeak43 14-Aug-07 21:49
 5 minutes with an Amiga? Stephane Rodriguez.16-May-06 22:05 Stephane Rodriguez. 16-May-06 22:05
 Re: 5 minutes with an Amiga? Doga Arinir16-May-06 23:41 Doga Arinir 16-May-06 23:41
 Re: 5 minutes with an Amiga? Stephane Rodriguez.17-May-06 21:06 Stephane Rodriguez. 17-May-06 21:06
 Re: 5 minutes with an Amiga? Doga Arinir19-May-06 13:03 Doga Arinir 19-May-06 13:03
 Last Visit: 31-Dec-99 18:00     Last Update: 29-Sep-23 8:23 Refresh 1