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; |