Graphics with Direct Memory Access
Drawing pictures on the screen using the bios interrupts is all very easy, but when push comes to shove, its also very, very slow as the bios routines are built to cope with every graphics mode. A faster way of plotting pixels is to directly place the bits in video memory, using, for example, a move instruction. This is very, very fast. but it does limit you to the resolution for which the routine was written.
The simplest video modes for demostrating this principle, is that of mode 19, (13h) which has 320 x 200 pixels and uses 256 colours. 256 is 2^8, meaning that the colour value takes up exactly one byte per pixel. This makes the actual setting of pixels very easy, move the colour value into the appropriate memory byte. The video memory for this mode begins at memory address A000h, and the pixels are then linearly in memory row by row. The memory location to write to for pixel (x,y) is A000h + (y * 320) + x.
Unfortunately, this involves a previously unencountered complication. With the programs we have written so far, the data and program have all been placed in the one segment. However, the graphics memory is not within that segment, so we need a segment offset. This offset is the start of video memory. To access an address we now use two parts, the segment (stored in the es register) and the offset (stored in the di register). The whole address is referenced es:[di].
Drawing horizontal and vertical lines in this mode is easy: to draw a horizontal line, simply fill all memory addresses from the starting point to the end point; to draw a vertical line, add 320 to the current pixel position and this gives the next point. Below is a sample program demostrating this.
jmp start
;==============================
; Draws a horiz and vert line
;==============================
startaddr dw 0a000h ;start of video memory
colour db 1
;==============================
start:
mov ah,00
mov al,19
int 10h ;switch to 320x200 mode
;=============================
horiz:
mov es, startaddr ;put segment address in es
mov di, 32000 ;row 101 (320 * 100)
add di, 75 ;column 76
mov al,colour ;cannot do mem-mem copy so use reg
mov cx, 160 ;loop counter
hplot:
mov es:[di],al ;set pixel to colour
inc di ;move to next pixel
loop hplot
vert:
mov di, 16000 ;row 51 (320 * 50)
add di, 160 ;column 161
mov cx, 100 ;loop counter
vplot:
mov es:[di],al
add di, 320 ;mov down a pixel
loop vplot
;=============================
keypress:
mov ah,00
int 16h ;await keypress
end:
mov ah,00
mov al,03
int 10h
mov ah,4ch
mov al,00 ;terminate program
int 21h
|
That, basically is all there is to it. Note how for switching to and from graphics mode we still use the int calls. This is because the change only occurs generally once per program and so, unlike pixel plotting is not a bottle-neck.
The primary use of assembler for graphics is frequently to embed the code in a higher level language. Given below, then is an implementation of a few basic graphics primatives created in assembler but embedded withing pascal functions (these will work with Borland/Inprise’s Turbo Pascal Compilers or can be easily converted to equivalent C/C++ functions).
const
vga : word = $A000;
var
oldmode : byte;
Procedure setMCGA; assembler;
{Sets the graphics mode, including saving
the previous graphics mode}
asm
mov ax,0F00h
int 10h
mov oldmode,al
mov ax,0013h
int 10h
end;
Procedure settext; assembler;
{Returns the program to the graphics
mode it was previous in}
asm
mov ah,00h
mov al,oldmode
int 10h
end;
procedure putpixel( x,y : word; colour : byte);
{sets the pixel at (x,y) to the
colour given by colour.
Calculations are done using shifts
and additions not multiplications}
begin
if (x>319) or (y>199) then exit;
asm
push ds {save these two registers...}
push di {...by putting values on stack}
mov ax,y
shl ax,1 {ax=y*2}
mov bx,ax {bx=y*2}
shl ax,2 {ax=y*8}
add ax,bx {ax=(y*8)+(y*2)=y*10}
shl ax,5 {ax=(y*10)*2^5=y*320}
mov bx,x {ax has y offset, bx has x}
add ax,bx {add the offsets}
mov di,ax {di now has currect offset}
mov ah,colour
mov ds,vga {es now has segment}
mov ds:[di],ah {plot the pixel}
pop di {restore reg values}
pop ds
end;
end;
|