413 lines
17 KiB
Markdown
413 lines
17 KiB
Markdown
------------------------ --------------------------------- --------------------
|
||
[Previous](35-06.html) [Table of Contents](index.html) [Next](36-01.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
**LISTING 35.3 L35-3.ASM**
|
||
|
||
; Fast assembler implementation of Bresenham’s line-drawing algorithm
|
||
; for the EGA and VGA. Works in modes 0Eh, 0Fh, 10h, and 12h.
|
||
; Borland C++ near-callable.
|
||
; Bit mask accumulation technique when |DeltaX| >= |DeltaY|
|
||
; suggested by Jim Mackraz.
|
||
;
|
||
; Assembled with TASM
|
||
;
|
||
; By Michael Abrash
|
||
;
|
||
;****************************************************************
|
||
; C-compatible line-drawing entry point at _EVGALine. *
|
||
; Near C-callable as: *
|
||
; EVGALine(X0, Y0, X1, Y1, Color); *
|
||
;****************************************************************
|
||
;
|
||
|
||
model small
|
||
.code
|
||
|
||
;
|
||
; Equates.
|
||
;
|
||
EVGA_SCREEN_WIDTH_IN_BYTES equ 80 ;memory offset from start of
|
||
; one row to start of next
|
||
; in display memory
|
||
EVGA_SCREEN_SEGMENT equ 0a000h ;display memory segment
|
||
GC_INDEX equ 3ceh ;Graphics Controller
|
||
; Index register port
|
||
SET_RESET_INDEX equ 0 ;indexes of needed
|
||
ENABLE_SET_RESET_INDEX equ 1 ; Graphics Controller
|
||
BIT_MASK_INDEX equ 8 ; registers
|
||
|
||
;
|
||
; Stack frame.
|
||
;
|
||
EVGALineParms struc
|
||
dw ? ;pushed BP
|
||
dw ? ;pushed return address (make double
|
||
; word for far call)
|
||
X0 dw ? ;starting X coordinate of line
|
||
Y0 dw ? ;starting Y coordinate of line
|
||
X1 dw ? ;ending X coordinate of line
|
||
Y1 dw ? ;ending Y coordinate of line
|
||
Color db ? ;color of line
|
||
db ? ;dummy to pad to word size
|
||
EVGALineParms ends
|
||
|
||
;****************************************************************
|
||
; Line drawing macros. *
|
||
;****************************************************************
|
||
|
||
;
|
||
; Macro to loop through length of line, drawing each pixel in turn.
|
||
; Used for case of |DeltaX| >= |DeltaY|.
|
||
; Input:
|
||
; MOVE_LEFT: 1 if DeltaX < 0, 0 else
|
||
; AL: pixel mask for initial pixel
|
||
; BX: |DeltaX|
|
||
; DX: address of GC data register, with index register set to
|
||
; index of Bit Mask register
|
||
; SI: DeltaY
|
||
; ES:DI: display memory address of byte containing initial
|
||
; pixel
|
||
;
|
||
LINE1 macro MOVE_LEFT
|
||
local LineLoop, MoveXCoord, NextPixel, Line1End
|
||
local MoveToNextByte, ResetBitMaskAccumulator
|
||
mov cx,bx ;# of pixels in line
|
||
jcxz Line1End ;done if there are no more pixels
|
||
; (there’s always at least the one pixel
|
||
; at the start location)
|
||
shl si,1 ;DeltaY * 2
|
||
mov bp,si ;error term
|
||
sub bp,bx ;error term starts at DeltaY * 2 - DeltaX
|
||
shl bx,1 ;DeltaX * 2
|
||
sub si,bx ;DeltaY * 2 - DeltaX * 2 (used in loop)
|
||
add bx,si ;DeltaY * 2 (used in loop)
|
||
mov ah,al ;set aside pixel mask for initial pixel
|
||
; with AL (the pixel mask accumulator) set
|
||
; for the initial pixel
|
||
LineLoop:
|
||
;
|
||
; See if it’s time to advance the Y coordinate yet.
|
||
;
|
||
and bp,bp ;see if error term is negative
|
||
js MoveXCoord ;yes, stay at the same Y coordinate
|
||
;
|
||
; Advance the Y coordinate, first writing all pixels in the current
|
||
; byte, then move the pixel mask either left or right, depending
|
||
; on MOVE_LEFT.
|
||
;
|
||
out dx,al ;set up bit mask for pixels in this byte
|
||
xchg byte ptr [di],al
|
||
;load latches and write pixels, with bit mask
|
||
; preserving other latched bits. Because
|
||
; set/reset is enabled for all planes, the
|
||
; value written actually doesn’t matter
|
||
add di,EVGA_SCREEN_WIDTH_IN_BYTES ;increment Y coordinate
|
||
add bp,si ;adjust error term back down
|
||
;
|
||
; Move pixel mask one pixel (either right or left, depending
|
||
; on MOVE_LEFT), adjusting display memory address when pixel mask wraps.
|
||
;
|
||
if MOVE_LEFT
|
||
rol ah,1 ;move pixel mask 1 pixel to the left
|
||
else
|
||
ror ah,1 ;move pixel mask 1 pixel to the right
|
||
endif
|
||
jnc ResetBitMaskAccumulator ;didn’t wrap to next byte
|
||
jmp short MoveToNextByte ;did wrap to next byte
|
||
;
|
||
; Move pixel mask one pixel (either right or left, depending
|
||
; on MOVE_LEFT), adjusting display memory address and writing pixels
|
||
; in this byte when pixel mask wraps.
|
||
;
|
||
MoveXCoord:
|
||
add bp,bx ;increment error term & keep same
|
||
if MOVE_LEFT
|
||
rol ah,1 ;move pixel mask 1 pixel to the left
|
||
else
|
||
ror ah,1 ;move pixel mask 1 pixel to the right
|
||
endif
|
||
jnc NextPixel ;if still in same byte, no need to
|
||
; modify display memory yet
|
||
out dx,al ;set up bit mask for pixels in this byte.
|
||
xchg byte ptr [di],al
|
||
;load latches and write pixels, with bit mask
|
||
; preserving other latched bits. Because
|
||
; set/reset is enabled for all planes, the
|
||
; value written actually doesn’t matter
|
||
MoveToNextByte:
|
||
if MOVE_LEFT
|
||
dec di ;next pixel is in byte to left
|
||
else
|
||
inc di ;next pixel is in byte to right
|
||
endif
|
||
ResetBitMaskAccumulator:
|
||
sub al,al ;reset pixel mask accumulator
|
||
NextPixel:
|
||
or al,ah ;add the next pixel to the pixel mask
|
||
; accumulator
|
||
loop LineLoop
|
||
;
|
||
; Write the pixels in the final byte.
|
||
;
|
||
Line1End:
|
||
out dx,al ;set up bit mask for pixels in this byte
|
||
xchg byte ptr [di],al
|
||
;load latches and write pixels, with bit mask
|
||
; preserving other latched bits. Because
|
||
; set/reset is enabled for all planes, the
|
||
; value written actually doesn’t matter
|
||
endm
|
||
|
||
;
|
||
; Macro to loop through length of line, drawing each pixel in turn.
|
||
; Used for case of DeltaX < DeltaY.
|
||
; Input:
|
||
; MOVE_LEFT: 1 if DeltaX < 0, 0 else
|
||
; AL: pixel mask for initial pixel
|
||
; BX: |DeltaX|
|
||
; DX: address of GC data register, with index register set to
|
||
; index of Bit Mask register
|
||
; SI: DeltaY
|
||
; ES:DI: display memory address of byte containing initial
|
||
; pixel
|
||
;
|
||
LINE2 macro MOVE_LEFT
|
||
local LineLoop, MoveYCoord, ETermAction, Line2End
|
||
mov cx,si ;# of pixels in line
|
||
jcxz Line2End ;done if there are no more pixels
|
||
shl bx,1 ;DeltaX * 2
|
||
mov bp,bx ;error term
|
||
sub bp,si ;error term starts at DeltaX * 2 - DeltaY
|
||
shl si,1 ;DeltaY * 2
|
||
sub bx,si ;DeltaX * 2 - DeltaY * 2 (used in loop)
|
||
add si,bx ;DeltaX * 2 (used in loop)
|
||
;
|
||
; Set up initial bit mask & write initial pixel.
|
||
;
|
||
out dx,al
|
||
xchg byte ptr [di],ah
|
||
;load latches and write pixel, with bit mask
|
||
; preserving other latched bits. Because
|
||
; set/reset is enabled for all planes, the
|
||
; value written actually doesn’t matter
|
||
LineLoop:
|
||
;
|
||
; See if it’s time to advance the X coordinate yet.
|
||
;
|
||
and bp,bp ;see if error term is negative
|
||
jns ETermAction ;no, advance X coordinate
|
||
add bp,si ;increment error term & keep same
|
||
jmp short MoveYCoord ; X coordinate
|
||
ETermAction:
|
||
;
|
||
; Move pixel mask one pixel (either right or left, depending
|
||
; on MOVE_LEFT), adjusting display memory address when pixel mask wraps.
|
||
;
|
||
if MOVE_LEFT
|
||
rol al,1
|
||
sbb di,0
|
||
else
|
||
ror al,1
|
||
adc di,0
|
||
endif
|
||
out dx,al ;set new bit mask
|
||
add bp,bx ;adjust error term back down
|
||
;
|
||
; Advance Y coordinate.
|
||
;
|
||
MoveYCoord:
|
||
add di,EVGA_SCREEN_WIDTH_IN_BYTES
|
||
;
|
||
; Write the next pixel.
|
||
;
|
||
xchg byte ptr [di],ah
|
||
;load latches and write pixel, with bit mask
|
||
; preserving other latched bits. Because
|
||
; set/reset is enabled for all planes, the
|
||
; value written actually doesn’t matter
|
||
;
|
||
loop LineLoop
|
||
Line2End:
|
||
endm
|
||
|
||
;****************************************************************
|
||
; Line drawing routine. *
|
||
;****************************************************************
|
||
|
||
public _EVGALine
|
||
_EVGALine proc near
|
||
push bp
|
||
mov bp,sp
|
||
push si ;preserve register variables
|
||
push di
|
||
push ds
|
||
;
|
||
; Point DS to display memory.
|
||
;
|
||
mov ax,EVGA_SCREEN_SEGMENT
|
||
mov ds,ax
|
||
;
|
||
; Set the Set/Reset and Set/Reset Enable registers for
|
||
; the selected color.
|
||
;
|
||
mov dx,GC_INDEX
|
||
mov al,SET_RESET_INDEX
|
||
out dx,al
|
||
inc dx
|
||
mov al,[bp+Color]
|
||
out dx,al
|
||
dec dx
|
||
mov al,ENABLE_SET_RESET_INDEX
|
||
out dx,al
|
||
inc dx
|
||
mov al,0ffh
|
||
out dx,al
|
||
;
|
||
; Get DeltaY.
|
||
;
|
||
mov si,[bp+Y1] ;line Y start
|
||
mov ax,[bp+Y0] ;line Y end, used later in
|
||
;calculating the start address
|
||
sub si,ax ;calculate DeltaY
|
||
jns CalcStartAddress ;if positive, we’re set
|
||
;
|
||
; DeltaY is negative — swap coordinates so we’re always working
|
||
; with a positive DeltaY.
|
||
;
|
||
mov ax,[bp+Y1] ;set line start to Y1, for use
|
||
; in calculating the start address
|
||
mov dx,[bp+X0]
|
||
xchg dx,[bp+X1]
|
||
mov [bp+X0],dx ;swap X coordinates
|
||
neg si ;convert to positive DeltaY
|
||
;
|
||
; Calculate the starting address in display memory of the line.
|
||
; Hardwired for a screen width of 80 bytes.
|
||
;
|
||
CalcStartAddress:
|
||
shl ax,1 ;Y0 * 2 ;Y0 is already in AX
|
||
shl ax,1 ;Y0 * 4
|
||
shl ax,1 ;Y0 * 8
|
||
shl ax,1 ;Y0 * 16
|
||
mov di,ax
|
||
shl ax,1 ;Y0 * 32
|
||
shl ax,1 ;Y0 * 64
|
||
add di,ax ;Y0 * 80
|
||
mov dx,[bp+X0]
|
||
mov cl,dl ;set aside lower 3 bits of column for
|
||
and cl,7 ; pixel masking
|
||
shr dx,1
|
||
shr dx,1
|
||
shr dx,1 ;get byte address of column (X0/8)
|
||
add di,dx ;offset of line start in display segment
|
||
;
|
||
; Set up GC Index register to point to the Bit Mask register.
|
||
;
|
||
mov dx,GC_INDEX
|
||
mov al,BIT_MASK_INDEX
|
||
out dx,al
|
||
inc dx ;leave DX pointing to the GC Data register
|
||
;
|
||
; Set up pixel mask (in-byte pixel address).
|
||
;
|
||
mov al,80h
|
||
shr al,cl
|
||
;
|
||
; Calculate DeltaX.
|
||
;
|
||
mov bx,[bp+X1]
|
||
sub bx,[bp+X0]
|
||
;
|
||
; Handle correct one of four octants.
|
||
;
|
||
js NegDeltaX
|
||
cmp bx,si
|
||
jb Octant1
|
||
;
|
||
; DeltaX >= DeltaY >= 0.
|
||
;
|
||
LINE1 0
|
||
jmp EVGALineDone
|
||
;
|
||
; DeltaY > DeltaX >= 0.
|
||
;
|
||
Octant1:
|
||
LINE2 0
|
||
jmp short EVGALineDone
|
||
;
|
||
NegDeltaX:
|
||
neg bx ;|DeltaX|
|
||
cmp bx,si
|
||
jb Octant2
|
||
;
|
||
; |DeltaX| >= DeltaY and DeltaX < 0.
|
||
;
|
||
LINE1 1
|
||
jmp short EVGALineDone
|
||
;
|
||
; |DeltaX| < DeltaY and DeltaX < 0.
|
||
;
|
||
Octant2:
|
||
LINE2 1
|
||
;
|
||
EVGALineDone:
|
||
;
|
||
; Restore EVGA state.
|
||
;
|
||
mov al,0ffh
|
||
out dx,al ;set Bit Mask register to 0ffh
|
||
dec dx
|
||
mov al,ENABLE_SET_RESET_INDEX
|
||
out dx,al
|
||
inc dx
|
||
sub al,al
|
||
out dx,al ;set Enable Set/Reset register to 0
|
||
;
|
||
pop ds
|
||
pop di
|
||
pop si
|
||
pop bp
|
||
ret
|
||
_EVGALine endp
|
||
|
||
end
|
||
|
||
An explanation of the workings of the code in Listing 35.3 would be a
|
||
lengthy one, and would be redundant since the basic operation of the
|
||
code in Listing 35.3 is no different from that of the code in Listing
|
||
35.1, although the implementation is much changed due to the nature of
|
||
assembly language and also due to designing for speed rather than for
|
||
clarity. Given that you thoroughly understand the C implementation in
|
||
Listing 35.1, the assembly language implementation in Listing 35.3,
|
||
which is well-commented, should speak for itself.
|
||
|
||
One point I do want to make is that Listing 35.3 incorporates a clever
|
||
notion for which credit is due Jim Mackraz, who described the notion in
|
||
a letter written in response to an article I wrote long ago in the late
|
||
and lamented *Programmer’s Journal*. Jim’s suggestion was that when
|
||
drawing lines for which |**DeltaX**| is greater than |**DeltaY**|, bits
|
||
set to 1 for each of the pixels controlled by a given byte can be
|
||
accumulated in a register, rather than drawing each pixel individually.
|
||
All the pixels controlled by that byte can then be drawn at once, with a
|
||
single access to display memory, when all pixel processing associated
|
||
with that byte has been completed. This approach can save many **OUT**s
|
||
and many display memory reads and writes when drawing nearly-horizontal
|
||
lines, and that’s important because EGAs and VGAs hold the CPU up for a
|
||
considerable period of time on each I/O operation and display memory
|
||
access.
|
||
|
||
All too many PC programmers fall into the high-level-language trap of
|
||
thinking that a good algorithm guarantees good performance. Not so: As
|
||
our two implementations of Bresenham’s algorithm graphically illustrate
|
||
(pun not originally intended, but allowed to stand once recognized),
|
||
truly great PC code requires both a good algorithm *and* a good assembly
|
||
implementation. In Listing 35.3, we’ve got y-oh-my, isn’t it fun?
|
||
|
||
------------------------ --------------------------------- --------------------
|
||
[Previous](35-06.html) [Table of Contents](index.html) [Next](36-01.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
* * * * *
|
||
|
||
Graphics Programming Black Book © 2001 Michael Abrash
|