;*DDK*************************************************************************/
;
; COPYRIGHT    Copyright (C) 1995 IBM Corporation
;
;    The following IBM OS/2 WARP source code is provided to you solely for
;    the purpose of assisting you in your development of OS/2 WARP device
;    drivers. You may use this code in accordance with the IBM License
;    Agreement provided in the IBM Device Driver Source Kit for OS/2. This
;    Copyright statement may not be removed.;
;*****************************************************************************/

        PAGE    ,132
        TITLE   COMPMAC.INC
;/*****************************************************************************
;*
;* SOURCE FILE NAME = COMPMAC.INC
;*
;* DESCRIPTIVE NAME = Macros for DCAF compression code
;*
;*
;* VERSION      V2.0
;*
;* DATE         04/22/92
;*
;* DESCRIPTION  Contains useful macros for the DCAF compression code.
;*
;* FUNCTIONS
;*
;* NOTES        NONE
;*
;* STRUCTURES   NONE
;*
;* EXTERNAL REFERENCES
;*
;*              NONE
;*
;* EXTERNAL FUNCTIONS
;*
;*
;* CHANGE ACTIVITY =
;*   DATE      FLAG        APAR   CHANGE DESCRIPTION
;*   --------  ----------  -----  --------------------------------------
;*   mm/dd/yy  @Vr.mpppxx  xxxxx  xxxxxxx
;*   04/22/92                     Created for DCAF 1.3
;*   07/01/92                     Ported to 32-bit for DCAF 2.0
;*
;*****************************************************************************/

;----------------------------------------------------------------------;
; count_repeating_data counts the number of consecutive repeating
; data fields.
;
; Parameters:
;       non_repeating_data - label to jump to if no repeating data found
;
;       field_size - size of fields (8 or 16 bits)
;
;
; Entry:
;       esi -> source data
;       edi -> destination buffer (value not used here, but must
;                  be preserved)
;       cbBytesToCompress = source data bytes remaining
;
; Exit:
;       ecx = number of consecutive repeating data fields
;
; Preserved:
;       esi
;       edi
;       cbBytesToCompress
;
;----------------------------------------------------------------------;
count_repeating_data macro non_repeating_data,field_size
local run_to_end

        ; initialise the values for the @FLD8 and @FLD16 macros
        INITFIELDSIZE        field_size

        ; Load up the max number of source bytes to compare
        mov     ecx,cbBytesToCompress

        ; Convert bytes to data fields
@FLD16 <shr     ecx,1>

        ; Check that we have at least 3 fields remaining
        ; (the minimum required for a repeating run)
        cmp     ecx, 2

        ; Skip out now if we can
        jle     non_repeating_data

        ; Do a quick check to see if we have repeating data
        mov     ax, [esi]
@FLD8  <cmp     al, ah>
@FLD16 <cmp     ax, [esi+2]>

        ; Exit now if first two fields differ
        jne     non_repeating_data

        ; We have at least a pair of matching data fields.
        ; Use a super-fast "scas" to see how many more there are.
        ; For this, we need [edi] to point to the source data
        ; (which is a shame, as we already have [esi] pointing there).

        push    edi             ; Save edi on stack

        ; Load source pointer into edi.
        mov     edi,esi

        ; Save current value of ecx in ebx
        mov     ebx,ecx

@FLD8  <repe    scasb>
@FLD16 <repe    scasw>

        ; Unless we have reached the end of the string the count
        ; was decremented once too many
        jz      run_to_end
        inc     ecx

run_to_end:

        ; Now calculate how many matches were found.

        ; ebx contains the number of fields remaining before the scan
        ; ecx contains the number of remaining fields after the scan
        sub     ebx,ecx
        mov     ecx,ebx

        pop     edi             ; Restore edi from stack

        ; If we only had two fields the same in a row, we do not have
        ; a run
        cmp     ecx, 2
        jle     non_repeating_data

endm

;----------------------------------------------------------------------;
; count_non_repeating_data counts the number of data fields before we
; have 3 fields that are repeating
;
; Entry:
;       esi -> source data
;       edi -> destination buffer (value not used here, but must
;              be preserved)
;       cbBytesToCompress = source data bytes remaining
;
; Exit:
;       ecx = number of fields before repeating data
;
; Preserved:
;       esi
;       edi
;       cbBytesToCompress
;
;----------------------------------------------------------------------;
count_non_repeating_data macro   field_size
local   no_more_runs
local   no_more_runs_quick_exit
local   run_found
local   cnrd_exit
local   third_field_different
local   repeat_scan

        ; initialise the values for the @FLD8 and @FLD16 macros
        INITFIELDSIZE        field_size

        ; Load up the max number of source bytes to compare
        mov     ecx,cbBytesToCompress

        ; Convert bytes to data fields
@FLD16 <shr     ecx,1>

;------------------------------------------------------------
;  We find the next repeating data by using a cmps instruction
;  with the destination one field further on than the source
;
;  This will find us the first pair of matching fields.
;  We can then check the third one against these fields to
;  see if we have enough in a row to be a run.
;------------------------------------------------------------

        ; Find max no. of comparisons to do
        dec     ecx

        ; Skip out now if we can
        jz      no_more_runs_quick_exit

        ; store the destination and source pointers as we are about to use them
        ; note don't need to push ds as the source segment will not be changed

        pushem  edi,esi

        ; set the destination pointer to be one field on from the source
        mov     edi, esi
@FLD8  <inc     edi>
@FLD16 <add     edi,2>

repeat_scan:
        ; find a pair of repeating fields

@FLD16 <repne   cmpsw>
@FLD8  <repne   cmpsb>

        ; See if we reached the end of the fields
        jcxz    no_more_runs

        ; see if we have 3 in a row
        ; esi points to the second field of the pair, edi points to the
        ; third field if they are the same we have 3 in a row
@FLD16 <cmpsw>
@FLD8  <cmpsb>
        jnz     third_field_different

        ; we have found the next run!

        ; The number of fields before the run is given by
        ; cbBytesToCompress - ecx - 2
        ; as ecx started 1 less than the number of fields and has been
        ; decremented once too often by the cmps instuction
        mov     eax, cbBytesToCompress
        sub     eax, ecx
        sub     eax, 2
        mov     ecx, eax
        jmp     run_found

third_field_different:
        ; although we found a pair, the third field was different,
        ; we want to be able to carry on the CMPS as if we had not stopped
        ; The extra CMPS to check the third field will not have decremented
        ; ecx, so we do that here
        dec     ecx
        jmp     repeat_scan

no_more_runs_quick_exit:
        ; The rest of the line consists of non-repeating data
        ; and we have not pushed the source and destination pointers
        mov     ecx,cbBytesToCompress
@FLD16 <shr     ecx,1>
        jmp     cnrd_exit

no_more_runs:
        ; The rest of the line consists of non-repeating data
        mov     ecx,cbBytesToCompress
@FLD16 <shr     ecx,1>

run_found:
        ; restore the source and destination pointers to their original
        ; values
        popem  edi,esi

cnrd_exit:
endm

;----------------------------------------------------------------------;
; write_repeating_cell is a macro to fill in a repeating cell
; (count and data) in the destination buffer.
;
; Parameters:
;       field_size       - the size of the data cells (8 or 16)
;
; Entry:
;       edi -> destination buffer
;       esi -> source buffer
;       ecx =  count of data fields
;
; Exit:
;       edi -> next free pos in destination buffer
;
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
write_repeating_cell macro     field_size
local   write_another_rpt_cell
local   rd_size_ok

        ; initialise the values for the @FLD8 and @FLD16 macros
        INITFIELDSIZE        field_size

        ; Store the original total count in ebx
        mov     ebx,ecx

        ; If using 8-bit data fields the maximum run length is 127 bytes.
        ; If the run is longer than this, we will have to write multiple
        ; run length/data cells.
        ; During the following loop:
        ;    eax - "clipped" run length
        ;    ecx - total remaining data fields to process

write_another_rpt_cell:
        ; Get length of run into eax, clipping to maximum run length if
        ; necessary.
        mov     eax,ecx
@FLD8  <cmp     eax,CD_MAX_8BIT_RUN_LENGTH>; See if we exceed max run length
@FLD8  <jle     rd_size_ok>             ; Skip forward if not
@FLD8  <mov     eax,CD_MAX_8BIT_RUN_LENGTH>; Set length of this run to max
rd_size_ok:

        ; Store length of run in output buffer
@FLD8  <stosb>
@FLD16 <stosw>

        ; Fetch data value from source, and write to output buffer
        ; Note that the "movs" will increment the source pointer, which
        ; we do not really want, so we immediately adjust it back.
        ; The alternative would be a "mov al/ax,[esi], stosb/stosw",
        ; but this would trash our count in al/ax!
@FLD8  <movsb>
@FLD16 <movsw>

        ; Adjust source pointer back to its original value.
        ; If the data is 16-bit then we have to decrement it twice to
        ; move back a word.
@FLD8  <dec     esi>
@FLD16 <sub     esi, 2>

@FLD8  <sub     ecx,CD_MAX_8BIT_RUN_LENGTH>; Adjust total count
@FLD8  <jg      write_another_rpt_cell> ; Loop back if more cells needed


        ; Convert data fields into bytes
@FLD16 <shl     ebx,1>

        ; Update cbBytesToCompress by the length of the run
        sub     cbBytesToCompress,ebx

        ; Update source pointer
        add     esi,ebx
endm

;----------------------------------------------------------------------;
; write_non_repeating_cell is a macro to fill in a complete non-repeating
; cell (count+data) in the destination buffer.
;
; Parameters:
;       field_size - size of cell - must be 8 or 16
;
; Entry:
;       edi -> destination buffer
;       esi -> source buffer
;       ecx =  count of data fields
;
; Exit:
;       edi -> next free pos in destination buffer
;
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
write_non_repeating_cell macro     field_size
local   write_another_nrpt_cell
local   nrd_size_ok


        ; initialise the values for the @FLD8 and @FLD16 macros
        INITFIELDSIZE        field_size

        ; Store the original total count in ebx
        mov     ebx,ecx

        ; If using 8-bit data fields the maximum run length is 127 bytes.
        ; If the run is longer than this, we will have to write multiple
        ; run length/data cells.
        ; During the following loop:
        ;    eax - "clipped" run length
        ;    ecx - total remaining data fields to process

write_another_nrpt_cell:
        ; Get length of run into eax, clipping to maximum run length if
        ; necessary.
        mov     eax,ecx
@FLD8  <cmp     eax,CD_MAX_8BIT_RUN_LENGTH>; See if we exceed max run length
@FLD8  <jle     nrd_size_ok>            ; Skip forward if not
@FLD8  <mov     eax,CD_MAX_8BIT_RUN_LENGTH>; Set length of this run to max

nrd_size_ok:

        ; Set top bit to indicate that this is non-repeating data
@FLD8  <or      al,CD_NON_REPEATING_DATA_FLAG_8>
@FLD16 <or      ax,CD_NON_REPEATING_DATA_FLAG_16>

        ; Store length of run in output buffer
@FLD8  <stosb>
@FLD16 <stosw>

        ; Clear top bit to get back the true data count
@FLD8  <and     al,not CD_NON_REPEATING_DATA_FLAG_8>
@FLD16 <and     ax,not CD_NON_REPEATING_DATA_FLAG_16>

        ; Copy the non-repeating data from source to destination.
        ; For this we need:
        ;   - the field count in ecx (which is currently in eax)
        ;   - esi -> source data (already set up)
        ;   - edi -> dest buffer (already set up)
        xchg    eax,ecx         ; Move eax into ecx, saving ecx value

        ; Move it!
@FLD8  <rep     movsb>
@FLD16 <rep     movsw>

@FLD8  <mov     ecx,eax>                ; Restore ecx value
@FLD8  <sub     ecx,CD_MAX_8BIT_RUN_LENGTH>; Adjust total count
@FLD8  <jg      write_another_nrpt_cell>; Loop back if more cells needed


        ; Convert data fields into bytes
@FLD16 <shl     ebx,1>

        ; Update cbBytesToCompress
        sub     cbBytesToCompress,ebx
endm


;----------------------------------------------------------------------;
; write_dup_scanline_cell is a macro to fill in a duplicate scanline cell
; in the destination buffer.
; Note the number of duplicates is set to 1
;
; Parameters:
;       field_size       - the size of the data cells (8 or 16)
;
; Entry:
;       edi -> destination buffer
;
; Exit:
;       edi -> next free pos in destination buffer
;
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
write_dup_scanline_cell macro     field_size

        ; initialise the values for the @FLD8 and @FLD16 macros
        INITFIELDSIZE        field_size

        ; A duplicate scan cell is a zero followed by a count
        ; The count is set to one here and adjusted if more
        ; duplicate scans are found later
        ; 
        ; Therefore store the bytes  00 01     if cell is 8 bits wide
        ;                        or  0000 0001 if cell is 16 bits wide

@FLD8  <mov     eax, 0100h>
@FLD8  <stosw>
@FLD8  <sub     cbFreeBytesInDestBuffer, 2>

@FLD16 <mov     eax, 00010000h>
@FLD16 <stosd>
@FLD16 <sub     cbFreeBytesInDestBuffer, 4>
endm

;----------------------------------------------------------------------;
; write_dup_scanline_pair_cell is a macro to fill in a duplicate scanline pair
; cell in the destination buffer.
; Note the number of duplicates is set to 1
;
; Parameters:
;       field_size       - the size of the data cells (8 or 16)
;
; Entry:
;       edi -> destination buffer
;
; Exit:
;       edi -> next free pos in destination buffer
;
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
write_dup_scanline_pair_cell macro     field_size

        ; initialise the values for the @FLD8 and @FLD16 macros
        INITFIELDSIZE        field_size

        ; A duplicate scanline pair is two zero fields followed by a count
        ; The count is set to one here and adjusted if more duplicate
        ; scan pairs are found later
        ; 
        ; Therefore store the bytes 00 00 01        if cell is 8 bits wide
        ;                        or 0000 0000 0001  if cell is 16 bits wide


        xor     eax, eax        ; zero out accumulator

        ; store the two zero fields
@FLD8  <stosw>
@FLD16 <stosd>

       ; initialise the count
        inc     eax

       ; store the count
@FLD8  <stosb>
@FLD16 <stosw>

        ; update the bytes free in buffer
@FLD8  <sub     cbFreeBytesInDestBuffer, 3>
@FLD16 <sub     cbFreeBytesInDestBuffer, 6>

endm





;----------------------------------------------------------------------;
; Macro to compress a row of data.
;
; Parameters:
;       field_size - Specifies the size of a single data field.
;                    Must be either 8 or 16 (bits).
;
; Entry:
;   edi -> next free position in destination buffer
;   esi -> first byte in current source row
;   ecx =  length of row in bytes
;
; Exit:
;   edi -> updated next free position in destination buffer
;   esi -> byte after end of current source row
;   ecx =  0
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
compress_row_local_vars  macro
        cbBytesToCompress :DWORD
endm

compress_row  macro   field_size
local   main_loop
local   non_rpting_data
local   end_loop

        ; initialise the values for the @FLD8 and @FLD16 macros
        INITFIELDSIZE       &field_size

        ; Store source data length
        mov     cbBytesToCompress,ecx

main_loop:
        ; Count consecutive repeating data fields.
        ; The count is returned in ecx.
        count_repeating_data    non_rpting_data, field_size

        ; Write out the repeating data cell
        write_repeating_cell field_size

        jmp     end_loop

non_rpting_data:

        ; Count how many fields we have before the next repeating data
        count_non_repeating_data  field_size

        ; Write out the non-repeating data cell
        write_non_repeating_cell  field_size

end_loop:
        ; Check whether there are more source bytes to compress
        mov     ecx,cbBytesToCompress
        or      ecx,ecx
        jnz     main_loop
endm


;----------------------------------------------------------------------;
; Macro which appends the correct data field size (either 8 or 16)
; onto the end of the supplied argument.
;
; This macro is very dependent upon which adapter is being used.
;    VGA always uses 8- bit fields (always 4bpp)
;    BGA always uses 16-bit fields (always 8bpp)
;    XGA may use either 8 or 16-bit fields (depending on the bits/pel)
;
; Parameters:
;    instruction        The instruction to which "8" or "16" will
;                       be appended. This will typically be a
;                       "write_cell" macro.
;
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
append_data_size  macro      instruction

       instruction 8
endm


;----------------------------------------------------------------------;
; Macro which compares two rows of data
;
; Parameters:
;
; Entry:
;   edi -> row 1
;   esi -> row 2
;   ecx =  length of each row in bytes
;
; Exit:
;   edi -> ????
;   esi -> ????
;   ecx =  ????
;   Zero flag set to indicate if rows were the same or not
;
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
compare_rows macro
local   compare_next_plane
local   compare_rows_exit
local   compare_plane_same

        ; On VGA each plane must match for the row to be identical

        ; Store count
        mov     ebx, ecx

        ; Initialise the plane selection
        plane_select_initialise

compare_next_plane:

        ; Select the next plane
        plane_select_next

        ; Compare this plane
        repe    cmpsb

        ; If the plane was different exit with zero flag clear
        jnz     compare_rows_exit

compare_plane_same:
        ; restore count and move pointers back to the start
        mov     ecx, ebx
        sub     edi, ecx
        sub     esi, ecx

        ; That plane matched, move onto next one
        plane_select_repeat compare_next_plane

        ; All planes matched!!!! - set zero flag and exit
        xor     eax, eax
compare_rows_exit:

endm

;----------------------------------------------------------------------;
; Macro which moves the pointer onto the next source row
; This will jump to an exit point if we go past the last row
;
; Parameters:
;    exit_label         The place to jump to if the last row is past
;
;----------------------------------------------------------------------;
inc_source_row macro exit_label

        ; Increment the row count
        inc     yCurrentRow

        ; Exit if we have processed the last row
        mov     eax, yEndRow
        cmp     eax, yCurrentRow
        je      exit_label

        ; adjust the source pointer onto the next scan line
        add     esi, cbBytesPerScanline

endm

;----------------------------------------------------------------------;
; Macro which moves the pointer on by two source rows
; This will jump to an exit point if we go past the last row
;
; This is functionally equivalent to
;
;       inc_source_row  exit_label
;       inc_source_row  exit_label
;
; Parameters:
;    exit_label         The place to jump to if the last row is past
;
; Note that if the jump is made to the exit label, the yCurrentRow
; could have been incremented one row too far - this is not a problem
; as the yCurrentRow is only needed again if the buffer was not large
; enough.
; This jump will only occur when we have finished without the buffer
; running out.
;
;----------------------------------------------------------------------;
inc_source_row_twice macro exit_label

        ; Update the current row number
        add     yCurrentRow, 2

        ; Exit if we have processed the last row
        mov     eax, yEndRow
        cmp     eax, yCurrentRow
        jle     exit_label

        ; Move the source pointer two rows further on
        mov     eax, cbBytesPerScanline
        shl     eax, 1
        add     esi, eax

endm

;----------------------------------------------------------------------;
; Macro which increments the 'count' field within a cell
;
; If the cell size is 8, there is a maximum of 255 imposed on the
; cell count, if this is exceeded, the duplicate cell is repeated
; to allow for the extra lines
;
; Parameters:
;       cell_type - the type of cell (<dup_scan> or <dup_scan_pair>)
;
;       field_size - Specifies the size of a single data field.
;                    Must be either 8 or 16 (bits).
;
; Entry:
;   edi -> next free byte in destination buffer
;          (so cell count is in the preceding BYTE/WORD
;
; Exit:
;   edi -> next free byte in destination buffer (updated if required)
;
;----------------------------------------------------------------------;
inc_cell_count  macro   cell_type, field_size
local   no_count_wrap

        ; check parameters
ifdif <cell_type>, <dup_scan>
ifdif <cell_type>, <dup_scan_pair>
.err cell_type invalid
endif
endif

        ; Initialise the values for the @FLD8 and @FLD16 macros
        INITFIELDSIZE        field_size

        ; Increment the count (the count field is the field just
        ; before the next free place in the destination)
@FLD16 <inc     word ptr [edi-2]>
@FLD8  <inc     byte ptr [edi-1]>

        ; See if we wrapped around (only appropiate with 8 bit fields)
@FLD8  <jnz     no_count_wrap>

        ; The count has wrapped, set it back to the maximum
        ; value and repeat the cell
@FLD8  <dec     byte ptr [edi-1]>
ifidn <cell_type>,<dup_scan>
@FLD8  <write_dup_scanline_cell 8>
else
@FLD8  <write_dup_scanline_pair_cell 8>
endif

no_count_wrap:
endm
