; Draws all pixels in the specified column, with the pixel colors
; taken from the specified texture map.  Uses approach of pre-stepping
; 1/2 pixel into the source image and rounding to the nearest source
; pixel at each step, so that texture maps will appear reasonably similar
; at all angles.  This routine is specific to 320-pixel-wide planar
; (non-Chain4) 256-color modes, such as mode X, which is a planar
; (non-chain4) 256-color mode with a resolution of 320x240.
;
; SS must == DS so that the texture map can be accessed via SS:BP.
;
; C near-callable as:
;     void ScanOutLine(EdgeScan * TopEdge, EdgeScan * BottomEdge);
;
; Tested with TASM 3.0.
;

SC_INDEX equ    03c4h   ;Sequence Controller Index
MAP_MASK equ    02h     ;index in SC of Map Mask register
SCREEN_SEG equ  0a000h  ;segment of display memory in mode X
SCREEN_WIDTH equ 80     ;width of screen in bytes from one scan line
                        ; to the next

        .model  small
        .386
        .data
        extrn   _TexMapBits:word, _TexMapWidth:word, _DestX:word
        extrn   _CurrentPageBase:word, _ClipMinX:word
        extrn   _ClipMinY:word, _ClipMaxX:word, _ClipMaxY:word

; Describes the current location and stepping, in both the source and
; the destination, of an edge. Mirrors structure in DRAWTEXP.C.
EdgeScan struc
Direction dw    ?       ;through edge list; 1 for a right edge (forward
                        ; through vertex list), -1 for a left edge (backward
                        ; through vertex list)
RemainingScans dw ?     ;height left to scan out in dest
CurrentEnd dw   ?       ;vertex # of end of current edge
SourceX dd      ?       ;X location in source for this edge
SourceY dd      ?       ;Y location in source for this edge
SourceStepX dd  ?       ;X step in source for Y step in dest of 1
SourceStepY dd  ?       ;Y step in source for Y step in dest of 1
                        ;variables used for all-integer Bresenham's-type
                        ; X stepping through the dest, needed for precise
                        ; pixel placement to avoid gaps
DestY   dw      ?       ;current Y location in dest for this edge
DestYIntStep dw ?       ;whole part of dest Y step per column X step
DestYDirection dw ?     ;-1 or 1 to indicate which way Y steps (left/right)
DestYErrTerm dw ?       ;current error term for dest Y stepping
DestYAdjUp dw   ?       ;amount to add to error term per scan line move
DestYAdjDown dw ?       ;amount to subtract from error term when the
                        ; error term turns over
EdgeScan ends

Parms   struc
          dw      2 dup(?) ;return address & pushed BP
TopEdge  dw      ?        ;pointer to EdgeScan structure for left edge
BottomEdge dw      ?        ;pointer to EdgeScan structure for right edge
Parms   ends

;Offsets from BP in stack frame of local variables.
lSourceX        equ     -4      ;current X coordinate in source image
lSourceY        equ     -8      ;current Y coordinate in source image
lSourceStepX    equ     -12     ;X step in source image for X dest step of 1
lSourceStepY    equ     -16     ;Y step in source image for X dest step of 1
lXAdvanceByOne  equ     -18     ;used to step source pointer 1 pixel
                                ; incrementally in X
lBaseAdvance   equ      -20     ;use to step source pointer minimum number of
                                ; pixels incrementally in X+Y simultaneously
lYAdvanceByOne  equ     -22     ;used to step source pointer 1 pixel
                                ; incrementally in Y
LOCAL_SIZE      equ     22      ;total size of local variables

        .code
        extrn   _FixedMul:near, _FixedDiv:near

        align   2
ToScanDone:
        jmp     ScanDone

        public  _ScanOutLine
        align   2
_ScanOutLine    proc    near
        push    bp              ;preserve caller's stack frame
        mov     bp,sp           ;point to our stack frame
        sub     sp,LOCAL_SIZE   ;allocate space for local variables
        push    si              ;preserve caller's register variables
        push    di

; Nothing to do if destination is fully Y clipped.

        mov     di,[bp].BottomEdge
        mov     si,[di].DestY
        cmp     si,[_ClipMinY]
        jle     short ToScanDone ;bottom edge is to left of clip rect, so done
        mov     bx,[bp].TopEdge
        mov     dx,[bx].DestY
        cmp     dx,[_ClipMaxY]
        jge     short ToScanDone ;top edge is to right of clip rect, so done
        sub     si,dx            ;destination fill height
        jle     short ToScanDone ;null or negative full height, so done

        mov     ax,word ptr [bx].SourceX        ;initial source X coordinate
        mov     word ptr [bp].lSourceX,ax
        mov     ax,word ptr [bx].SourceX+2
        mov     word ptr [bp].lSourceX+2,ax

        mov     ax,word ptr [bx].SourceY        ;initial source Y coordinate
        mov     word ptr [bp].lSourceY,ax
        mov     ax,word ptr [bx].SourceY+2
        mov     word ptr [bp].lSourceY+2,ax

; Calculate source steps that correspond to each 1-pixel destination X step
; (across the destination scan line).

        push    si              ;push dest Y height, in fixedpoint form
        sub     ax,ax
        push    ax              ;push 0 as fractional part of dest Y height
        mov     ax,word ptr [di].SourceX
        sub     ax,word ptr [bp].lSourceX       ;low word of source X width
        mov     dx,word ptr [di].SourceX+2
        sbb     dx,word ptr [bp].lSourceX+2     ;high word of source X width
        push    dx              ;push source X width, in fixedpoint form
        push    ax
        call    _FixedDiv       ;scale source X width to dest Y height
        add     sp,8            ;clear parameters from stack
        mov     word ptr [bp].lSourceStepX,ax   ;remember source X step for
        mov     word ptr [bp].lSourceStepX+2,dx ; 1-pixel destination Y height
        mov     cx,1            ;assume source X advances non-negative
        and     dx,dx           ;which way does source X advance?
        jns     short SourceXNonNeg ;non-negative
        neg     cx              ;negative
        cmp     ax,0            ;is the whole step exactly an integer?
        jz      short SourceXNonNeg ;yes
        inc     dx              ;no, truncate to integer in the direction of
                                ; 0, because otherwise we'll end up with a
                                ; whole step of 1-too-large magnitude
SourceXNonNeg:
        mov     [bp].lXAdvanceByOne,cx  ;amount to add to source pointer to
                                        ; move by one in X
        mov     [bp].lBaseAdvance,dx    ;minimum amount to add to source
                                        ; pointer to advance in X each time
                                        ; the dest advances one in Y

        push    si              ;push dest Y height, in fixedpoint form
        sub     ax,ax
        push    ax              ;push 0 as fractional part of dest Y height
        mov     ax,word ptr [di].SourceY
        sub     ax,word ptr [bp].lSourceY       ;low word of source Y height
        mov     dx,word ptr [di].SourceY+2
        sbb     dx,word ptr [bp].lSourceY+2     ;high word of source Y height
        push    dx              ;push source Y height, in fixedpoint form
        push    ax
        call    _FixedDiv       ;scale source Y height to dest Y height
        add     sp,8            ;clear parameters from stack
        mov     word ptr [bp].lSourceStepY,ax   ;remember source Y step for
        mov     word ptr [bp].lSourceStepY+2,dx ; 1-pixel destination X step
        mov     cx,[_TexMapWidth] ;assume source Y advances non-negative
        and     dx,dx           ;which way does source Y advance?
        jns     short SourceYNonNeg ;non-negative
        neg     cx              ;negative
        cmp     ax,0            ;is the whole step exactly an integer?
        jz      short SourceYNonNeg ;yes
        inc     dx              ;no, truncate to integer in the direction of
                                ; 0, because otherwise we'll end up with a
                                ; whole step of 1-too-large magnitude
SourceYNonNeg:
        mov     [bp].lYAdvanceByOne,cx  ;amount to add to source pointer to
                                        ; move by one in Y
        mov     ax,[_TexMapWidth]       ;minimum distance skipped in source
        imul    dx                      ; image bitmap when Y steps (ignoring
        add     [bp].lBaseAdvance,ax    ; carry from the fractional part);
                                        ; add into total minimum source
                                        ; advance amount

; Advance 1/2 step in the stepping direction, to space scanned pixels evenly
; between the left and right edges. (There's a slight inaccuracy in dividing
; negative numbers by 2 by shifting rather than dividing, but the inaccuracy
; is in the least significant bit, and we'll just live with it.)

        mov     ax,word ptr [bp].lSourceStepX
        mov     dx,word ptr [bp].lSourceStepX+2
        sar     dx,1
        rcr     ax,1
        add     word ptr [bp].lSourceX,ax
        adc     word ptr [bp].lSourceX+2,dx

        mov     ax,word ptr [bp].lSourceStepY
        mov     dx,word ptr [bp].lSourceStepY+2
        sar     dx,1
        rcr     ax,1
        add     word ptr [bp].lSourceY,ax
        adc     word ptr [bp].lSourceY+2,dx

; Clip bottom edge if necessary.

        mov     bx,[di].DestY
        cmp     bx,[_ClipMaxY]
        jl      short BottomEdgeClipped
        mov     bx,[_ClipMaxY]
BottomEdgeClipped:

; Clip top edge if necssary

        mov     si,[bp].TopEdge
        mov     di,[si].DestY
        cmp     di,[_ClipMinY]
        jge     short TopEdgeClipped

; Top clipping is necessary; advance the source accordingly

        neg     di
        add     di,[_ClipMinY]  ;ClipMinY - DestY
                                ;first, advance the source in X
        push    di              ;push ClipMinX - DestX, in fixedpoint form
        sub     ax,ax
        push    ax              ;push 0 as fractional part of ClipMinX-DestX
        push    word ptr [bp].lSourceStepX+2
        push    word ptr [bp].lSourceStepX
        call    _FixedMul       ;total source X stepping in clipped area
        add     sp,8            ;clear parameters from stack
        add     word ptr [bp].lSourceX,ax   ;step the source X past clipping
        adc     word ptr [bp].lSourceX+2,dx
                                ;now advance the source in Y
        push    di              ;push ClipMinX - DestX, in fixedpoint form
        sub     ax,ax
        push    ax              ;push 0 as fractional part of ClipMinX-DestX
        push    word ptr [bp].lSourceStepY+2
        push    word ptr [bp].lSourceStepY
        call    _FixedMul       ;total source Y stepping in clipped area
        add     sp,8            ;clear parameters from stack
        add     word ptr [bp].lSourceY,ax   ;step the source Y past clipping
        adc     word ptr [bp].lSourceY+2,dx

        mov     di,[_ClipMinY]  ;start Y coordinate in dest after clipping
TopEdgeClipped:

; Calculate actual clipped destination drawing height.

        sub     bx,di

; Scan down the destination column, updating the source image position
; accordingly.

; Point to the initial source image pixel, adding 0.5 to both X and Y so that
; we can truncate to integers from now on but effectively get rounding.

        add     word ptr [bp].lSourceY,8000h    ;add 0.5
        mov     ax,word ptr [bp].lSourceY+2
        adc     ax,0
        mul     [_TexMapWidth]   ;initial scan line in source image
        add     word ptr [bp].lSourceX,8000h    ;add 0.5
        mov     si,word ptr [bp].lSourceX+2 ;offset into source scan line
        adc     si,ax            ;initial source offset in source image
        add     si,[_TexMapBits] ;SI points to the initial image pixel

; Point to initial destination pixel.

        mov     ax,SCREEN_WIDTH
        mul     di      ;offset of initial dest scan line
        mov     di,[_DestX]
        mov     cx,di   ;initial destination X
        shr     di,1
        shr     di,1    ;X/4 = offset of pixel in scan line
        add     di,ax   ;offset of pixel in page
        add     di,[_CurrentPageBase] ;offset of pixel in display memory
                        ;DI now points to the first destination pixel

        and     cl,011b ;CL = pixel's plane
        mov     al,MAP_MASK
        mov     dx,SC_INDEX
        mov     ah,01h  ;one plane bit in each nibble, so we'll get carry
                        ; automatically when going from plane 3 to plane 0
        shl     ah,cl   ;set the bit for the first pixel's plane to 1
        out     dx,ax

; If source Y step is negative, change over to working with non-negative
; values.

        cmp     word ptr [bp].lYAdvanceByOne,0
        jge     short SYStepSet
        neg     word ptr [bp].lSourceStepY
        not     word ptr [bp].lSourceY
SYStepSet:

; If source X step is negative, handle with a separate inner loop.

        cmp     word ptr [bp].lXAdvanceByOne,0
        jl      short TexScanRToL

; Handles edges that go left to right through the source texture.

        mov     cx,word ptr [bp].lSourceStepX
        shl     ecx,16          ;upper word of ECX is fractional X advance
        mov     cx,word ptr [bp].lSourceStepY
        shr     cx,1            ;lower 15 bits of ECX are fractional Y
                                ; advance, bit 15 is 0
        mov     dx,[bp].lSourceX
        shl     edx,16
        mov     dx,[bp].lSourceY
        shr     dx,1            ;upper 16 bits are X fractional coord, bit 15
                                ; is 0, lower 15 bits are Y fractional coord
        push    ds              ;preserve normal data segment
        mov     ax,SCREEN_SEG
        mov     ds,ax           ;DS:DI->initial destination pixel
        mov     ax,[bp].lYAdvanceByOne
        push    bp              ;preserve stack frame pointer
                                ;***stack frame not available***
        mov     bp,[bp].lBaseAdvance
        xchg    bp,si           ;SS:BP->initial source texture pixel
                                ;SI = minimum source advance per destination
                                ; step
        inc     bx              ;round pixel-pair count up
        shr     bx,1            ;# of pixel-pairs to texture map
        jc      short TexScanLToRLoop ;even # of pixels
        push    bx              ;preserve pixel-pair count
        sub     di,SCREEN_WIDTH ;preadjust back to compensate for
                                ; built-in offset in odd-pixel code
                                ; being jumped to
        jmp     short TexScanLToRLoopOddEntry ;odd # of pixels

; Inner loop to draw a single texture-mapped vertical column,
; rather than a horizontal scanline. Maxed-out 16-bit version.
;
; At this point:
;       AX = source pointer increment to advance one in Y
;       BX = # of pixel-pairs to draw
;       ECX = fractional Y advance in lower 15 bits of CX,
;             fractional X advance in high word of ECX, bit
;             15 set to 0
;       EDX = fractional source texture Y coordinate in lower
;             15 bits of CX, fractional source texture X coord
;             in high word of ECX, bit 15 set to 0
;       SI = sum of integral X & Y source pointer advances
;       DS:DI = initial destination pointer
;       SS:BP = initial source texture pointer
;       Plane mask set to point to the plane containing this scan

        align   2
TexScanLToRLoop:
        push    bx      ;remember pixel-pair count

        mov     bl,ss:[bp]      ;get texture pixel
        mov     ds:[di],bl      ;set screen pixel

        add     edx,ecx ;advance frac Y in DX,
                        ; frac X in high word of EDX
        adc     bp,si   ;advance source pointer by integral
                        ; X & Y amount, also accounting for
                        ; carry from X fractional addition
        test    dh,80h  ;carry from Y fractional addition?
        jz      short TSLToRL1 ;no
        add     bp,ax   ;yes, advance Y by one
        and     dh,not 80h ;reset the Y fractional carry bit
TSLToRL1:

TexScanLToRLoopOddEntry:

        mov     bl,ss:[bp]              ;get texture pixel
        mov     ds:[di+SCREEN_WIDTH],bl ;set screen pixel

        add     edx,ecx ;advance frac Y in DX,
                        ; frac X in high word of EDX
        adc     bp,si   ;advance source pointer by integral
                        ; X & Y amount, also accounting for
                        ; carry from X fractional addition
        test    dh,80h  ;carry from Y fractional addition?
        jz      short TSLToRL2 ;no
        add     bp,ax   ;yes, advance Y by one
        and     dh,not 80h ;reset the Y fractional carry bit
TSLToRL2:

; Point to the next destination pixel.

        add     di,SCREEN_WIDTH*2

; Continue if there are any more dest pixels to draw.

        pop     bx
        dec     bx
        jnz     short TexScanLToRLoop

        pop     bp              ;restore stack frame pointer
                                ;***stack frame available***
        pop     ds              ;restore normal data segment
        jmp     short ScanDone

; Handles edges that go right to left through the source texture.
        align   2
TexScanRToL:
        mov     cx,word ptr [bp].lSourceStepX
        neg     cx              ;make fractional X advance positive, so we can
                                ; add both X and Y simultaneously
        shl     ecx,16          ;upper word of ECX is fractional X advance
        mov     cx,word ptr [bp].lSourceStepY
        shr     cx,1            ;lower 15 bits of ECX are fractional Y
                                ; advance, bit 15 is 0
        mov     dx,[bp].lSourceX
        not     dx              ;convert fraction X advance to counting-up
                                ; mode, so we can add both X and Y
                                ; simultaneously (since X goes right to left,
                                ; fractional X would normally be advanced
                                ; with subtraction)
        shl     edx,16
        mov     dx,[bp].lSourceY
        shr     dx,1            ;upper 16 bits are X fractional coord, bit 15
                                ; is 0, lower 15 bits are Y fractional coord
        push    ds              ;preserve normal data segment
        mov     ax,SCREEN_SEG
        mov     ds,ax           ;DS:DI->initial destination pixel
        mov     ax,[bp].lYAdvanceByOne
        push    bp              ;preserve stack frame pointer
                                ;***stack frame not available***
        mov     bp,[bp].lBaseAdvance
        xchg    bp,si           ;SS:BP->initial source texture pixel
        neg     si              ;SI = minimum source advance per destination
                                ; step, negated so we can use SBB in the inner
                                ; loop
        inc     bx              ;round pixel-pair count up
        shr     bx,1            ;# of pixel-pairs to texture map
        jc      short TexScanRToLLoop ;even # of pixels
        push    bx              ;preserve pixel-pair count
        sub     di,SCREEN_WIDTH ;preadjust back to compensate for
                                ; built-in offset in odd-pixel code
                                ; being jumped to
        jmp     short TexScanRToLLoopOddEntry ;odd # of pixels

; Inner loop to draw a single texture-mapped vertical column,
; rather than a horizontal scanline. Maxed-out 16-bit version.
;
; At this point:
;       AX = source pointer increment to advance one in Y
;       BX = # of pixel-pairs to draw
;       ECX = fractional Y advance in lower 15 bits of CX,
;             fractional X advance in high word of ECX, bit
;             15 set to 0
;       EDX = fractional source texture Y coordinate in lower
;             15 bits of CX, fractional source texture X coord
;             in high word of ECX, bit 15 set to 0
;       SI = sum of integral X & Y source pointer advances
;       DS:DI = initial destination pointer
;       SS:BP = initial source texture pointer
;       Plane mask set to point to the plane containing this scan

        align   2
TexScanRToLLoop:
        push    bx      ;remember pixel-pair count

        mov     bl,ss:[bp]      ;get texture pixel
        mov     ds:[di],bl      ;set screen pixel

        add     edx,ecx ;advance frac Y in DX,
                        ; frac X in high word of EDX
        sbb     bp,si   ;advance source pointer by integral
                        ; X & Y amount, also accounting for
                        ; carry from X fractional addition
        test    dh,80h  ;carry from Y fractional addition?
        jz      short TSRToLL1 ;no
        add     bp,ax   ;yes, advance Y by one
        and     dh,not 80h ;reset the Y fractional carry bit
TSRToLL1:

TexScanRToLLoopOddEntry:

        mov     bl,ss:[bp]              ;get texture pixel
        mov     ds:[di+SCREEN_WIDTH],bl ;set screen pixel

        add     edx,ecx ;advance frac Y in DX,
                        ; frac X in high word of EDX
        sbb     bp,si   ;advance source pointer by integral
                        ; X & Y amount, also accounting for
                        ; carry from X fractional addition
        test    dh,80h  ;carry from Y fractional addition?
        jz      short TSRToLL2 ;no
        add     bp,ax   ;yes, advance Y by one
        and     dh,not 80h ;reset the Y fractional carry bit
TSRToLL2:

; Point to the next destination pixel.

        add     di,SCREEN_WIDTH*2

; Continue if there are any more dest pixels to draw.

        pop     bx
        dec     bx
        jnz     short TexScanRToLLoop

        pop     bp              ;restore stack frame pointer
                                ;***stack frame available***
        pop     ds              ;restore normal data segment

ScanDone:
        pop     di              ;restore caller's register variables
        pop     si
        mov     sp,bp           ;deallocate local variables
        pop     bp              ;restore caller's stack frame
        ret
_ScanOutLine    endp

        end

