2025-04-27 07:49:33 -04:00

922 lines
28 KiB
NASM

;---------------------------Module-Header------------------------------;
; Module Name: rleblts.asm
;
; Copyright (c) 1992 Microsoft Corporation
;-----------------------------------------------------------------------;
.386
ifndef DOS_PLATFORM
.model small,c
else
ifdef STD_CALL
.model small,c
else
.model small,pascal
endif; STD_CALL
endif; DOS_PLATFORM
assume ds:FLAT,es:FLAT,ss:FLAT
assume fs:nothing,gs:nothing
.xlist
include stdcall.inc ;calling convention cmacros
include i386\egavga.inc
include i386\strucs.inc
.list
.code
;-------------------------------Macro-----------------------------------;
; RleSetUp
;
; Set the EGA\VGA to write mode 2 (M_COLOR_WRITE). Initialize local vars
;
; Entry:
; None
; Returns:
; ESI = pointer to the source bitmap
;
;-----------------------------------------------------------------------;
RleSetUp macro
cld
mov dx,VGA_BASE + GRAF_ADDR
mov ax,(M_COLOR_WRITE shl 8) + GRAF_MODE
out dx,ax
mov esi,pRleInfo
mov edi,[esi].RLE_prctlTrg
mov eax,[edi].xLeft
mov xStart,eax
mov eax,[esi].RLE_xBegin
mov xCurr,eax
mov eax,[edi].yBottom
dec eax
mov yCurr,eax
mov eax,[esi].RLE_pulTranslate
mov pulXlate,eax
mov eax,[esi].RLE_prctlClip
mov prclClip,eax
mov eax,[esi].RLE_pjSrcBitsMax
mov pjSrcEnd,eax
mov ebx,[esi].RLE_pjTrg
mov pjDst,ebx
mov esi,[esi].RLE_pjSrcBits
endm
;-------------------------------Macro-----------------------------------;
; RleDelta
;
; Handle the Delta case.
;
; Entry:
; ESI = pointer to the horz/vert offset of the source bitmap
; Returns:
; None
;
;-----------------------------------------------------------------------;
RleDelta macro dest
lodsw
movzx ecx,al
add xCurr,ecx ;x adjustment
movzx eax,ah
sub yCurr,eax ;y adjustment
mul lNextScan
sub pjDst,eax
jmp dest
endm
;-------------------------------Macro-----------------------------------;
; RleExit
;
; Prepare the Rle routine to exit. Set the EGA/VGA to default write
; mode. Save the current status in the RleInfo so we can continue
; from where we left off if a complex clipping is encountered.
;
; Entry:
; ESI = pointer pass the first run above the clipping rect
; EBX = yCurr(y value of the first run above the clipping rect)
; ECX = xCurr(x value of the first run above the clipping rect)
; Returns:
; None
;
;-----------------------------------------------------------------------;
RleExit macro
mov dx,VGA_BASE + GRAF_ADDR
mov ax,(M_PROC_WRITE shl 8) + GRAF_MODE
out dx,ax
mov ax,0FF00h + GRAF_BIT_MASK
out dx,ax ;no mask
mov edi,pRleInfo ;update for next call
dec esi
dec esi
mov [edi].RLE_pjSrcBits,esi
mov esi,[edi].RLE_prctlTrg
inc ebx ;make it exclusive
mov [esi].yBottom,ebx
mov eax,pjDst
mov [edi].RLE_pjTrg,eax
mov [edi].RLE_xBegin,ecx
endm
;-------------------------------Macro-----------------------------------;
; Clip_Encoded
;
; Clip the current run in encoded mode against the clipping rect.
;
; Entry:
; AH = pel color of this run
; AL = number of pels of this run
; Returns:
; EAX = left coordinate if no clipping
; ECX = left coordinate of the clipped run
; EBX = right coordinate of the clipped run
;
;-----------------------------------------------------------------------;
Clip_Encoded macro done, loop_start
movzx edx,ah ;EDX = pel color
movzx eax,al ;EAX = number of pels
mov ecx,xCurr ;ECX = left coordinate
add xCurr,eax ;compute new x position
mov ebx,yCurr
; Check if y is inside the clipping range.
mov edi,prclClip
cmp ebx,[edi].yTop
jb done
cmp ebx,[edi].yBottom
jae short loop_start
; Clip to the passed in clip rectangle.
mov eax,ecx
cmp ecx,[edi].xLeft
jae short @F
mov ecx,[edi].xLeft
@@:
mov ebx,xCurr
cmp ebx,[edi].xRight
jbe short @F
mov ebx,[edi].xRight
@@:
cmp ebx,ecx ;clipped out if left and right crossed
jbe short loop_start
endm
;-------------------------------Macro-----------------------------------;
; Draw_Encoded
;
; Write the current encoded run to the EGA/VGA.
;
; Entry:
; EBX = right coordinate (exclusive)
; ECX = left coordinate (inclusive)
; EDX = pel color
; Returns:
; None
;
;-----------------------------------------------------------------------;
Draw_Encoded macro loop_start
call comp_masks ;compute bit masks
shl eax,8 ;save right mask in high word
add edi,pjDst
mov ebx,pulXlate
mov ebx,[ebx][edx*4]
mov dx,VGA_BASE + GRAF_ADDR
or ah,ah ;AH = left mask
jz short @F
mov al,GRAF_BIT_MASK ;Set bitmask for altered bits
out dx,ax
mov al,[edi] ;latch dest (value is don't care)
mov [edi],bl ;write dest, with Bit Mask clipping
inc edi ;update destination pointer
@@:
or ecx,ecx ;ECX = inner loop count
jz short @F
mov ax,0FF00h + GRAF_BIT_MASK
out dx,ax ;no mask
mov al,bl
rep stosb ;write color
@@:
shr eax,8 ;AH = right mask
or ah,ah
jz loop_start
mov al,GRAF_BIT_MASK ;Set bitmask for altered bits
out dx,ax
mov al,[edi] ;latch dest (value is don't care)
mov [edi],bl ;write color
jmp loop_start
endm
;-------------------------------Macro-----------------------------------;
; Clip_Absolute
;
; Clip the current run in absolute mode against the clipping rect.
; Compute initial bit mask and other vars for subsequent loop.
;
; Entry:
; AH = number of pels of this run
; Returns:
; AL = GRAF_BIT_MASK
; AH = bitmap for the first pel
; EBX = pulXlate (pointer to the xlate table)
; CH = 01000001b
; EDI = pointer to destination byte to be altered
; loop_count = loop count (number of pels to be altered)
;
;-----------------------------------------------------------------------;
Clip_Absolute macro done,loop_end,loop_count
movzx eax,ah ;EAX = number of pels
mov ecx,xCurr ;ECX = left coordinate
add xCurr,eax
mov ebx,yCurr
; Check if y is inside the clipping range.
mov edi,prclClip
cmp ebx,[edi].yTop
jb done
cmp ebx,[edi].yBottom
jae loop_end
; Check if x is inside the clipping range.
cmp ecx,[edi].xRight ;check if x is in range
jae loop_end
mov ebx,xCurr ;EBX = right coordinate
cmp ebx,[edi].xLeft ;can I not jump???!!!
jbe loop_end
; Clip to the passed in clip rectangle.
cmp ecx,[edi].xLeft
mov cPreSrcAdv,0
jae short @F
mov eax,[edi].xLeft
sub eax,ecx ;diff of left coor and prclClip->xLeft
mov cPreSrcAdv,eax
add ecx,eax ;ECX = left coordinate = prclClip->xLeft
@@:
xor eax,eax ;diff of right coor and prclClip->xRight
cmp ebx,[edi].xRight
jbe short @F
mov eax,ebx
mov ebx,[edi].xRight ;EBX = right coor = prclClip->xRight
sub eax,ebx
@@:
mov cPostSrcAdv,eax
sub ebx,ecx ;loop count
mov edi,ecx
shr edi,3
add edi,pjDst ;point to the first byte to write
and ecx,00000111b ;Compute bit index for left side
mov ah,080h ;Compute bit mask
shr ah,cl ;AL = first pel bit mask
mov al,GRAF_BIT_MASK
mov loop_count,bl ;loop count < 0FFh
mov ch,01000001b ;for later use
mov ebx,pulXlate
endm
;-------------------------------Macro-----------------------------------;
; RleEOL
;
; Handle the End of line case.
;
; Entry:
; None.
; Returns:
; None.
;
;-----------------------------------------------------------------------;
RleEOL macro loop_start
mov eax,lNextScan
sub pjDst,eax ;adjust pjDst
dec yCurr ;adjust yCurr
mov eax,xStart ;adjust xCurr
mov xCurr,eax
jmp loop_start
endm
;-------------------------------Macro-----------------------------------;
; Set up banking-related variables, and make sure the bank for the
; initial (bottom) scan line is mapped in.
;
; Entry:
; None.
; Returns:
; EBX = pdsurf
; ESI = pRleInfo
; EDI = RLE_prctlClip
;
;-------------------------------Macro-----------------------------------;
RleBankSetUp macro
local map_bank,bank_mapped
mov esi,pRleInfo
mov ebx,[esi].Rle_pdsurfTrg
mov pdsurf,ebx
mov eax,[ebx].dsurf_lNextScan
mov lNextScan,eax
mov edi,[esi].RLE_prctlClip
mov eax,[edi].yTop
mov ulTrueClipTop,eax ;we blt until we reach this scan line
; Map in the bottom scan line of the clip rect.
mov eax,[edi].yBottom ;bottom clip scan, exclusive
dec eax ;make it inclusive
cmp eax,[ebx].dsurf_rcl1WindowClip.yTop
jl short map_bank
cmp eax,[ebx].dsurf_rcl1WindowClip.yBottom
jl short bank_mapped
map_bank:
ptrCall <dword ptr [ebx].dsurf_pfnBankControl>,<ebx,eax,JustifyBottom>
bank_mapped:
endm
;-------------------------------Macro-----------------------------------;
; Set up the screen pointer and clip the clip rect to the current bank.
;
; Entry:
; EBX = pdsurf
; ESI = pRleInfo
; EDI = RLE_prctlClip
; Returns:
; None.
;
;-------------------------------Macro-----------------------------------;
RleBankTop macro
; Convert the screen pointer from an offset within the bitmap to a virtual
; address.
mov eax,[ebx].dsurf_pvBitmapStart
add [esi].RLE_pjTrg,eax
; Set the clip rect top to MAX(top_of_bank,top_of_clip), to confine drawing to
; this bank.
mov eax,[ebx].dsurf_rcl1WindowClip.yTop
cmp ulTrueClipTop,eax
jl short @F
mov eax,ulTrueClipTop
@@:
mov [edi].yTop,eax
endm
;-------------------------------Macro-----------------------------------;
; Check whether we've done the last bank spanned by this clip rect; exit
; if so, map in the bank and continue if not.
;
; Entry:
; None.
; Returns:
; EBX = pdsurf
; ESI = pRleInfo
; EDI = RLE_prctlClip
;
;-------------------------------Macro-----------------------------------;
RleBankBottom macro loop_top,done
; Convert the screen pointer back to an offset within the bitmap.
mov ebx,pdsurf
mov esi,pRleInfo
mov eax,[ebx].dsurf_pvBitmapStart
sub [esi].RLE_pjTrg,eax
; Have we reached the top bank involved in this blt? If so, done; if not, map
; it in and blt the next bank.
mov eax,[ebx].dsurf_rcl1WindowClip.yTop
cmp eax,ulTrueClipTop
jle short &done
dec eax ;map in the bank above the current one
ptrCall <dword ptr [ebx].dsurf_pfnBankControl>,<ebx,eax,JustifyBottom>
mov edi,[esi].RLE_prctlClip ;for RleBankTop
jmp &loop_top
endm
;--------------------------Public-Routine-------------------------------;
; vRle8ToVga
;
; Write an RLE8 bitmap onto the VGA device.
;
; Entry:
; None.
; Returns:
; None
;
;-----------------------------------------------------------------------;
cProc vRle8ToVga,4,< \
uses esi edi ebx, \
pRleInfo: ptr RLEINFO >
local pjDst :ptr
local pulXlate :ptr
local xCurr :dword
local yCurr :dword
local xStart :dword
local cPreSrcAdv :dword
local cPostSrcAdv :dword
local prclClip :ptr RECTL
local pjSrcEnd :ptr
local pdsurf :ptr DEVSURF
local ulTrueClipTop :dword
local lNextScan : dword
RleBankSetUp ;wrap a banking loop around everything
rle8_bank_loop:
RleBankTop
RleSetUp
rle8_loop:
cmp esi,pjSrcEnd
jae rle8_done
lodsw ;fetch a word from src RLE bitmap
or al,al
jz short rle8_escape
rle8_encoded:
Clip_Encoded rle8_done,rle8_loop
Draw_Encoded rle8_loop
rle8_escape:
cmp ah,2 ; First byte is 0, check the second byte.
ja short rle8_absolute
jb rle8_end_of_line_or_bitmap
rle8_delta:
RleDelta rle8_loop
rle8_absolute:
Clip_Absolute rle8_done,rle8_absolute_advance_src,cl
add esi,cPreSrcAdv ;advance esi to point to the clipped run
; I am doing a per pel loop rather than a per byte loop which writes 1 plane
; at a time because it appears to me that most of the time we output 2 or
; 3 pels per encounter of absolute mode. The logic here is simple and we can
; stay with M_COLOR_WRITE mode all the time.
rle8_absolute_loop:
mov dx,VGA_BASE + GRAF_ADDR
out dx,ax ;Set bitmask for altered bit
mov dl,[edi] ;latch dest (value is don't care)
sub edx,edx
mov dl,byte ptr [esi] ;grab color for this pel
inc esi
mov edx,[ebx][edx*4]
mov [edi],dl ;write to destination
ror ah,1 ;rotate bit mask for next pel
cmp ch,ah ;cmp bitmask with 01000001b
adc edi,0 ;advance edi if bitmask is 10000000b
dec cl ;per pel loop
jnz short rle8_absolute_loop
mov eax,cPostSrcAdv
rle8_absolute_advance_src:
add esi,eax ;advance src ptr for clipped out pels
bt esi,0 ;add 1 if not word aligned
adc esi,0
jmp rle8_loop
rle8_end_of_line_or_bitmap:
or ah,ah
jnz short rle8_end_of_bitmap
rle8_end_of_line:
RleEOL rle8_loop
rle8_done:
RleExit
RleBankBottom rle8_bank_loop,rle8_exit ;see if there are more banks
; to do
rle8_end_of_bitmap:
RleExit
rle8_exit:
cRet vRle8ToVga
endProc vRle8ToVga
;--------------------------Public-Routine-------------------------------;
; vRle4ToVga
;
; Write an RLE4 bitmap onto the VGA device.
;
; Entry:
; None
; Returns:
; None
;
;-----------------------------------------------------------------------;
cProc vRle4ToVga,4,< \
uses esi edi ebx, \
pRleInfo: ptr RLEINFO >
local pjDst :ptr
local pulXlate :ptr
local xCurr :dword
local yCurr :dword
local xStart :dword
local cPreSrcAdv :dword
local cPostSrcAdv :dword
local pjDstStart :ptr
local iPels :dword
local xMask :dword
local nPels :byte
local prclClip :ptr RECTL
local pjSrcEnd :ptr
local pdsurf :ptr DEVSURF
local ulTrueClipTop :dword
local lNextScan : dword
RleBankSetUp ;wrap a banking loop around everything
rle4_bank_loop:
RleBankTop
RleSetUp
rle4_loop:
cmp esi,pjSrcEnd
jae rle4_done
lodsw ;fetch a word
or al,al
jz rle4_escape
rle4_encoded:
Clip_Encoded rle4_done,rle4_loop
mov dh,dl ;separate two colors into dl, dh
and dl,0Fh
shr dh,4
cmp dh,dl
jnz short encoded_diff_color
xor dh,dh ;Draw_Encoded use edx as color index
Draw_Encoded rle4_loop
encoded_diff_color:
sub eax,ecx ;-eax = #pels clipped away
bt eax,0
jnc short @F
xchg dl,dh ;xchg colors if odd #pels clipped
@@:
mov iPels,edx ;save color index
sub ebx,ecx ;Compute extent of interval
dec ebx ;Make interval inclusive
mov edi,ecx ;Don't destroy starting X
shr edi,3 ;/8 for byte address
add edi,pjDst
and ecx,00000111b ;Compute bit index for first byte
mov eax,00AAAAAAh ;Compute altered bits mask
shr eax,cl ;AL = left side altered bytes mask
add ebx,ecx ;Compute bit index for last byte
mov ecx,ebx ;(save for inner loop count)
and ecx,00000111b
mov ch,80h
sar ch,cl
and al,ch ;AL = last byte altered bits mask
mov edx,eax
shr edx,1
and dl,ch ;EDX = mask for 2nd pel
mov ecx,ebx
shr ecx,3 ;Compute inner byte count
jnz short comp_byte_dont_combine ;loop count + 1 > 0, check it out
; Only one byte will be affected. Combine first/last masks, set loop count = 0
mov ebx,eax
shl eax,16 ;Will use first byte mask only
and eax,ebx ;AH = first mask. Rest of the bits = 0
mov ebx,edx
shl edx,16 ;Will use first byte mask only
and edx,ebx ;DH = first mask. Rest of the bits = 0
inc ecx ;Fall through to set 0
comp_byte_dont_combine:
dec ecx ;Dec inner loop count (might become 0)
xchg al,ah
ror eax,16 ;EAX = last mask:inter mask:first mask:0
xchg al,ah ;for first pel
xchg dl,dh
ror edx,16 ;EDX = last mask:inter mask:first mask:0
xchg dl,dh ;for second pel
mov pjDstStart,edi
mov xMask,edx ;save mask for the 2nd round
mov ebx,pulXlate
mov edx,iPels
shr edx,8 ;index for the first pel
mov ebx,[ebx][edx*4]
inc bh ;BH = rle4_encoded_diff_color_loop count
inc bh
mov dx,VGA_BASE + GRAF_ADDR
;BL = color for first pel
rle4_encoded_diff_color_loop:
or ah,ah ;AH = first mask
jz short encoded_inner_diff_color
mov al,GRAF_BIT_MASK ;Set bitmask for altered bits
out dx,ax
mov al,[edi] ;latch dest (value is don't care)
mov [edi],bl ;write color
encoded_inner_diff_color:
inc edi ;update destination pointer
shr eax,8 ;AH = internal mask
or cl,cl ;ECX = inner loop count
jz short encoded_last_byte_diff_color
mov ch,cl ;save loop count in ch
;loop count always fit in a byte
mov al,GRAF_BIT_MASK
out dx,ax ;no mask
encoded_loop_diff_color:
mov al,[edi] ;latch dest (value is don't care)
mov [edi],bl ;write color
inc edi
dec cl
jnz short encoded_loop_diff_color
encoded_last_byte_diff_color:
shr eax,8 ;AH = last mask
or ah,ah
jz short encoded_this_run_maybe_done
mov al,GRAF_BIT_MASK ;Set bitmask for altered bits
out dx,ax
encoded_write:
mov al,[edi] ;latch dest (value is don't care)
mov [edi],bl ;write color
encoded_this_run_maybe_done:
dec bh
jz rle4_loop
mov edi,pulXlate
mov eax,iPels ;index of the second pel
xor ah,ah
mov bl,byte ptr [edi][eax*4]
mov edi,pjDstStart
mov eax,xMask ;load mask
mov cl,ch ;loop count
jmp rle4_encoded_diff_color_loop
rle4_escape:
cmp ah,2 ; First byte is 0, check the second byte.
ja short rle4_absolute
jb rle4_end_of_line_or_bitmap
rle4_delta:
RleDelta rle4_loop
rle4_absolute:
Clip_Absolute rle4_done,rle4_absolute_advance_src,nPels
;advance esi to point to the clipped run
mov edx,cPreSrcAdv ;number of pels to advance
shr edx,1 ;number of bytes
jnc short @F
add esi,edx ;odd number of pels to advance
movzx edx,byte ptr [esi] ;grab color for prev and this pels
inc esi
mov iPels,edx
inc nPels ;inc loop count since we start from 1
mov cl,1
jmp short absolute_loop
@@: ;even number of pels to advance
add esi,edx
; I am doing a per pel loop rather than a per byte loop which writes 1 plane
; at a time because it appears to me that most of the time we output 2 or
; 3 pels per encounter of absolute mode. The logic here is simple and we can
; stay with M_COLOR_WRITE mode all the time.
xor cl,cl ;initialize loop count
absolute_loop:
mov dx,VGA_BASE + GRAF_ADDR
out dx,ax ;Set bitmask for altered bit
mov dl,[edi] ;latch dest (value is don't care)
bt ecx,0
jc short @F
movzx edx,byte ptr [esi] ;grab color for this and next pels
inc esi
mov iPels,edx ;save for next pel
shr dl,4
jmp short ready_to_draw
@@:
mov edx,iPels ;second pel
and dl,0Fh ;mask out the second pel
ready_to_draw:
mov edx,[ebx][edx*4] ;look up for VGA color
mov [edi],dl ;write to destination
ror ah,1 ;rotate bit mask for next pel
cmp ch,ah ;cmp bitmask with 01000001b
adc edi,0 ;advance edi if bitmask is 10000000b
inc cl
cmp cl,nPels
jnz short absolute_loop
mov eax,cPostSrcAdv
rle4_absolute_advance_src:
shr eax,1 ;2 pels for one byte
add esi,eax ;advance src ptr for clipped out pels
bt esi,0 ;add 1 if not word aligned
adc esi,0
jmp rle4_loop
rle4_end_of_line_or_bitmap:
or ah,ah
jnz short rle4_end_of_bitmap
rle4_end_of_line:
RleEOL rle4_loop
rle4_done:
RleExit
RleBankBottom rle4_bank_loop,rle4_exit ;see if there are more banks
; to do
rle4_end_of_bitmap:
RleExit
rle4_exit:
cRet vRle4ToVga
endProc vRle4ToVga
;--------------------------Private-Routine------------------------------;
; comp_byte_interval
;
; An interval will be computed for byte boundaries.
;
; A first mask and a last mask will be calculated, and possibly
; combined into the inner loop count. If no first byte exists,
; the start address will be incremented to adjust for it.
;
; Entry:
; EBX = right coordinate (exclusive)
; ECX = left coordinate (inclusive)
; Returns:
; EDI = offset to first byte to be altered in the scan
; ECX = inner loop count
; AL = first byte mask (possibly 0)
; AH = last byte mask (possibly 0)
; Error Returns:
; None
; Registers Preserved:
; ES,BP
; Registers Destroyed:
; AX,BX,CX,DI,FLAGS
; Calls:
; None
;
;-----------------------------------------------------------------------;
comp_masks proc
sub ebx,ecx ;Compute extent of interval
dec ebx ;Make interval inclusive
mov edi,ecx ;Don't destroy starting X
shr edi,3 ;/8 for byte address
and ecx,00000111b ;Compute bit index for left side
mov al,0FFh ;Compute left side altered bits mask
shr al,cl ;AL = left side altered bytes mask
add ebx,ecx ;Compute bit index for right side
mov ecx,ebx ;(save for inner loop count)
and ecx,00000111b
mov ah,80h
sar ah,cl ;AH = right side altered bits mask
shr ebx,3 ;Compute inner byte count
jnz short comp_byte_dont_combine ;loop count + 1 > 0, check it out
; Only one byte will be affected. Combine first/last masks, set loop count = 0
and al,ah ;Will use first byte mask only
xor ah,ah ;Want last byte mask to be 0
inc ebx ;Fall through to set 0
comp_byte_dont_combine:
dec ebx ;Dec inner loop count (might become 0)
; If all pixels in the first byte are altered, combine the first byte into the
; inner loop and clear the first byte mask. Ditto for the last byte mask.
mov ecx,0FFFFFFFFh
cmp al,cl ;Set 'C' if not all pixels 1
sbb ebx,ecx ;If no 'C', sub -1 (add 1), else sub 0
cmp al,cl ;Set 'C' if not all pixels 1
sbb al,cl ;If no 'C', sub -1 (add 1), else sub 0
cmp ah,cl ;Set 'C' if not all pixels 1
sbb ebx,ecx ;If no 'C', sub -1 (add 1), else sub 0
cmp ah,cl ;Set 'C' if not all pixels 1
sbb ah,cl ;If no 'C', sub -1 (add 1), else sub 0
mov ecx,ebx ;Return inner loop count in ECX
ret
comp_masks endp
end