Direct Buffer Writing

Top  Previous  Next

Direct buffer writing, also known as direct memory access, gives you the power to directly modify a buffers contents using a memory pointer. This is an advanced feature and should not be attempted unless you really understand everything presented here. Writing beyond the confines of a DirectX buffer can and will crash your program, or destabilize the system.

A DirectX buffer is an area of memory either on the video card or in system ram. The benefits of direct buffer writing include increased speed, creating custom drawing commands, and special effects.

The buffer is comprised of scan lines equal to the vertical size of the created screen. The length of each scan line in bytes is called the buffer pitch. The pitch will be the number of bytes required to store the horizontal pixel information plus some overhead used by DirectX. The length in bytes of a pixel is determined by the screens bits per pixel (bpp).

8 bpp =  1 byte per pixel

16 bpp =  2 bytes per pixel

24 bpp = 3 bytes per pixel

32 bpp = 4 bytes per pixel

I mentioned the overhead used by DirectX because its important to remember that the length of one scan line is not simply the horizontal size * bytes per pixel, you must have the actual pitch to perform direct buffer writing.

Before writing or reading to a buffer can happen it must be locked first. Use the LOCKBUFFER command to lock the buffer and the UNLOCKBUFFER command when you are finished with direct buffer writing. To obtain the pitch of the buffer use GETBUFFERPITCH. Use GETBUFFERPOINTER to retrieve the starting memory location of the buffer. The buffer pointer must be DEFined as type POINTER ahead of time.
 

DEF pBuffer as POINTER
LOCKBUFFER
pitch = GETBUFFERPITCH
pBuffer = GETBUFFERPOINTER

Do not store the pitch and buffer between calls to lock the buffer. DirectX is free to change buffer geometry any time it sees fit. Once the pitch and buffer pointer is obtained a simple calculation is used to determine the location of the pixel in question. You can use direct pointer math, or assign to a temporary pointer.

pTemp = pBuffer + (x * bytes per pixel) + (y * pitch)

Now comes the question of what to write to the memory location. DirectX stores pixel formats differently depending on the video card. Never assume a particular format, such as BGR, is used. To correctly determine a color value to store use the RGBToScreen function. RGBToScreen takes an RGB color and converts it to the pixel format used by the current screen. It is not needed for 8 bpp screens which only store a palette index as a byte.

Once a color is correctly converted use pointer dereferencing to store the pixel into the buffer. The type casting of the pointer depends on the bpp of the screen. 32bpp = UINT, 16bpp = WORD, 8bpp = CHAR.  24bpp is a bit more difficult which we will cover separately.
 

'Write a pixel to a 32 bpp screen.
#<UINT>pTemp = RGBToScreen(255,0,0)
 
'Write a pixel to a 16 bpp screen
#<WORD>pTemp = RGBToScreen(0,0,255)
 
'Write a pixel to an 8 bit screen. Uses a palette index instead
#<CHAR>pTemp = 1

For a 24 bpp screen you must use two writes instead of one. First a WORD sized write and then a CHAR sized write. Because of this a 24bpp screen will always be the lowest performer of all the screen modes.
 

'Write a pixel to a 24 bpp screen
col = RGBToScreen(255,0,255)
#<WORD>pTemp = col & 0xFFFF
pTemp += 2
#<CHAR>pTemp = (col >> 16) & 0x00FF

 

Complete example of direct buffer writing:

CONST width = 640
CONST height = 480
IF CREATESCREEN(width,height,16) < 0
    MESSAGEBOX 0, "Error: Couldn't create a screen\nBe sure you have DirectX 7.0 or greater","Error"
    END
ENDIF
'just to show the speed
DEF fps as INT
'The pointer to the buffer
DEF pBuffer as POINTER
 
DO
    LOCKBUFFER
        pitch = GetBufferPitch
        pBuffer = GetBufferPointer
        FOR y = 0 to height-1
            FOR x = 0 to width-1
                #<WORD>pBuffer[x] = RAND(0xFFFF)
            NEXT x
        pBuffer += pitch
        NEXT Y
    UNLOCKBUFFER
    WRITETEXT 0,0, "Press ESC to close" + " FPS=" + STR$(fps)
    fps = FLIP 1
UNTIL KEYDOWN(0x01)
CLOSESCREEN
END

The example above creates a static pattern like you would see on a TV with no input signal. For the example we didn't use any specific colors but just a random range of values from 0 to 0xFFFF.

The real power of direct buffer writing comes when combined with inline assembly to create very fast drawing routines.

Direct sprite writing

The 2D command set also allows modifying sprites directly. Sprites are also stored on a DirectX buffer and can be locked for direct manipulation of the sprites pixels. LOCKSPRITE, GETSPRITEPITCH, GETSPRITEPOINTER, and UNLOCKSPRITE commands are used in the same manner as their buffer counterparts:
 

LOCKSPRITE sprite
pitch = GETSPRITEPITCH sprite
pBuffer = GETSPRITEPOINTER sprite
   '... do something with the buffer
UNLOCKSPRITE sprite

The bpp for a sprite will be the same as the created screen. As with direct buffer writing you must be sure not to overwrite a sprites buffer.

Creating a blank sprite

To create a blank sprite for direct sprite buffer writing use the CREATESPRITE function. The CreateSprite function returns a pointer to a freshly created sprite of the width and height specified. The frames parameter specifies how many image frames you plan on creating. Calculate this by using total width / frame width.  It must work out to be evenly divisible. For example suppose we want frames that are 64 x 64 and a total of 5 frames. The total width would be 5 * 64 or 320 pixels

newSprite = CREATESPRITE(320, 64, 5)

Always check the return value of CreateSprite before attempting to lock and write to it.