User Tools

Site Tools


Direct screen memory access

by Richard Russell, March 2007

Normally, your BASIC program doesn't have direct access to its screen memory, i.e. the contents of the bitmap holding the text and graphics output from the program. The only ways to write to that bitmap are by using BASIC statements and commands (e.g. PRINT, PLOT, *MDISPLAY etc.) or by means of Windows API functions that use the @memhdc% system variable (e.g. “SYS “LineTo”, @memhdc%, 1000, 1000”). This is perfectly acceptable for most applications, but occasionally you may prefer to have direct memory access to the bitmap. An example might be an arcade-style game in which some special animation cannot straightforwardly (or quickly enough) be achieved in BASIC or by using the Windows API.

It is possible to set up your program so that you can access the bitmap memory. This allows you to write directly to that memory, for example using custom assembler code, to achieve effects that would otherwise be impractical. The first step is to decide what format you wish the bitmap to have; typical choices would be 8 bits-per-pixel (a paletted format in which each pixel contains an index to a colour table) or 24 bits-per-pixel (an RGB format containing true colour values). Other formats you might consider using are 16 bits-per-pixel or 32 bits-per-pixel.

The choice of format typically depends on a trade-off between the number of different colours available (in 8 bits-per-pixel mode the total number of different colours is 256) and speed (in 24 bits-per-pixel mode three times as much memory is required to hold a bitmap of the same dimensions).

The first step is to define the chosen bitmap format:

        DIM BITMAPINFOHEADER{Size%, Width%, Height%, Planes{l&,h&}, BitCount{l&,h&}, \
        \                    Compression%, SizeImage%, XPelsPerMeter%, YPelsPerMeter%, \
        \                    ClrUsed%, ClrImportant%}
        DIM bmi{Header{} = BITMAPINFOHEADER{}, Palette%(255)}
        bmi.Header.Size% = DIM(BITMAPINFOHEADER{})
        bmi.Header.Width% = @vdu%!208
        bmi.Header.Height% = @vdu%!212
        bmi.Header.Planes.l& = 1
        bmi.Header.BitCount.l& = 8

Here an 8 bits-per-pixel bitmap has been defined; if you want a different format set the value of bmi.Header.BitCount.l& accordingly. The code assumes that you want the bitmap to be the same size as the current window (as obtained from the system variables @vdu%!208 and @vdu%!212); if you require a different width or height set the structure members accordingly.

Because the selected mode is paletted a colour table containing an appropriate set of colours must be created:

        FOR I% = 0 TO 255
          r% = I%
          g% = I%
          b% = I%
          bmi.Palette%(I%) = b% + (g% << 8) + (r% << 16)

Here, for simplicity, a grey-scale has been created, where each palette entry contains the same values for red, green and blue. In practice it is more likely that your program will need a selection of colours. You might need to store the colours in, for example, DATA statements.

Now the bitmap format and colour table have been defined, you can create the bitmap itself:

        SYS "CreateDIBSection", @memhdc%, bmi{}, 0, ^bits%, 0, 0 TO hbitmap%
        IF hbitmap% = 0 ERROR 100, "Couldn't create DIBSection"
        SYS "SelectObject", @memhdc%, hbitmap% TO oldhbm%
        SYS "DeleteObject", oldhbm%

On successful completion of this code the variable bits% will contain the address in memory of the bitmap, hence ?bits% would be the pixel value of the bottom left pixel in the window (i.e. it is a bottom up bitmap).

It is most likely that you will want to 'draw' into the bitmap using assembler code, but for the purposes of illustration here is some very simple BASIC code which draws a diagonal line:

        bytesperpixel% = bmi.Header.BitCount.l& DIV 8
        bytesperline% = ((bmi.Header.Width% * bytesperpixel%) + 3) AND -4
        FOR I% = 0 TO 99
          x% = I%
          y% = I%
          c% = 0
          bits%?(y% * bytesperline% + x% * bytesperpixel%) = c%
        SYS "InvalidateRect", @hwnd%, 0, 0

The variable bytesperpixel% contains the number of bytes comprising each pixel (in this example 1) and the variable bytesperline% contains the number of bytes in one line (row) of the bitmap, which must always be rounded up to a multiple of 4.

The InvalidateRect is necessary to cause the screen to be updated from the new bitmap contents. For best performance you should invalidate only the smallest rectangle containing the changed pixels, but for convenience the code shown invalidates the entire window. If you do want to invalidate only a rectangle use code similar to the following:

        DIM rc{l%, t%, r%, b%}
        REM Load rectangle dimensions here (left, top, right, bottom)
        SYS "InvalidateRect", @hwnd%, rc{}, 0

If you are performing some kind of animation, it is quite likely that you will want to force an immediate screen refresh:


You can still use the majority of the standard BASIC or Windows API methods of writing to the output bitmap, in addition to the direct memory access provided by this technique. For example if you want to output text you can use PRINT.

Note that this technique may not work succesfully if the PC's display is itself set to a paletted (e.g. 256 colour) mode.

direct_20screen_20memory_20access.txt · Last modified: 2018/04/13 15:01 by richardrussell