; CMLINE 2.0  Public domain by Celso Minnitti Jr. October 29, 1994.
;
; CMLINE is a heavily optimized line drawing routine that has the
; fastest possible inner loop for the 386+ family.

_TEXT         segment para public 'CODE'
         assume cs:_TEXT, ds:_TEXT
         org    100h
         .386

MY_X1         EQU    100
MY_Y1         EQU    100
MY_X2         EQU    149
MY_Y2         EQU    109
MY_COLOR EQU    14

start:        mov  ax,13h              ;enter mode 13h
         int  10h            ;320x200 w/ 256 colors

         push MY_COLOR
         push MY_Y2
         push MY_X2
         push MY_Y1
         push MY_X1
         call _line
         add  sp,10

         mov  ah,0           ;wait for a key
         int  16h            ;

         mov  ax,3           ;return to mode 3
         int  10h            ;80x25 16/colors
         int  20h            ;exit to DOS

align 16
;-----------------------------------------------------------------------
; void line(int x1, int y1, int x2, int y2, int color);
;
; Draws a line from (x1,y1) to (x2,y2) including endpoints.
;
; This routine is heavily optimized for Pentium and 486s CPUs...
;-----------------------------------------------------------------------

_line         PROC NEAR
@@x1          EQU  [bp+4]
@@y1          EQU  [bp+6]
@@x2          EQU  [bp+8]
@@y2          EQU  [bp+10]
@@color  EQU  [bp+12]
         push bp
         mov  bp,sp
         push eax            ;
         push ecx            ;
         push edx            ;save all registers that we
         push bx             ;are going to use
         push esi            ;
         push di             ;
         push ds             ;

                xor     ecx,ecx
         mov  ax,[@@y1]
         mul  [_screenWidth]
         mov  di,ax
         mov  cx,word ptr [@@x2]
         add  di,[@@x1]      ;di = y1*_screenWidth + x1
         mov     ax,1        ;xinc = 1
         sub  cx,[@@x1]      ;deltaX = x2 - x1
         jnc  line_1
         neg  ax             ;if (x1 < x2) xinc = -1
         neg  cx             ;deltaX

line_1:  mov  si,[@@y2]
         mov     bx,[_screenWidth]       ;yinc = 320
         sub  si,[@@y1]      ;deltaY = y2 - y1
         jnc  line_2
         neg  bx             ;if (y1 < y2) yinc = -320
         neg  si             ;deltaY

line_2:  cmp  cx,si               ;is deltaX >= deltaY?
         jae  line_3
         xchg ax,bx               ;swap(xinc, yinc)
         xchg cx,si               ;swap(deltaX, deltaY)

line_3:  mov  word ptr [line_xyinc+2],ax ; guess what this does...
         mov  word ptr [line_yxinc+2],bx ;

         inc  cx             ;deltaX++
         mov  ax,si
         shl  esi,16
         inc  ax             ;ax = deltaY+1
         xor  edx,edx        ;
         shl  eax,16
         div  ecx            ;(deltaY << 16) / deltaX
         test edx,edx        ;if (remainder != 0) ax++
         jz   line_4         ;
         inc  ax             ;

line_4:  test ax,ax               ;if (quotient == 0)
         jnz  line_5              ; ax = si = -1
         dec     ax                      ;ax = -1
         dec     si                      ;si = -1

line_5:  mov  word ptr [line_xyloop+2],ax
         mov  al,[@@color]

         mov  ds,[_vidseg]
         jmp  short line_xyseg    ;flush prefetch queue to avoid
                             ;problems with self-modifying
                             ;code...

;Because ($ & 15) < 8 no align 16 is necessary... See your pentium
; notes...
; !!!! Beatiful line inner loop !!!!        ;          486 clocks....
line_xyseg:   mov  [di],al        ;          1 cycle
line_xyinc:   add  di,0ff00h      ;1,-1,320,-320  1 cy
line_xyloop:  add  si,0ff00h      ;          1 cy
         jnc  line_xyseg          ;          3,1 cy

line_yxinc:   add  di,0ff00h      ;320,-320,1,-1  1
         sub  esi,10000h          ;          1
         jnc  line_xyseg          ;          3,1

         pop  ds             ;
         pop  di             ;
         pop  esi            ;
         pop  bx             ;restore registers
         pop  edx            ;
         pop  ecx            ;
         pop  eax            ;
         pop  bp             ;
         ret
_line         ENDP
_vidseg  dw   0a000h
_screenWidth  dw   320
_TEXT         ends
end             start
