
** ZX Spectrum colour screen to bitplanes "pure software" translator, v2.0
** Richard Carlsson 1993


** ZX Spectrum memory layout:

; -8000  -1  0	+3FFF  +4000	+57FF  +5800	     +5AFF  +5B00  +7FFF
;  |	  |  |	    |  |	    |	|		 |   |	       |
;  --RAM---  --ROM---  Display bitmap	Display attributes   ----RAM----
;
;
;Extension for the bitplane translator:
;
; +8000     +97FF  +9800	 +9AFF	+9B00	       +B2FF
;  |		|   |		     |	 |		   |
;  Copy of bitmap   Copy of attributes	 Bitplane mask cache


;Distance from a display RAM address to the corresponding copy:

SOFTSCR_COPYOFFSET	EQU	$4000


;Three pointers are used to reference these areas:
;	a pointer to the display bitmap: bm_ptr,
;	a pointer to the attribute copy: attr_ptr,
;	and a pointer to the mask cache: mask_cache.
;
;The relation between the attribute pointer and the mask cache is
;as follows:
;
; +5800 +5AFF  +5B00 +7FFF  +8000     +97FF  +9800    +9AFF  +9B00 +B2FF
;  |	    |	|	 |   |		  |   | 	  |   |        |
;  Attributes	---RAM----   Copy of bitmap   Copy of attrs   Mask cache
;
;(The mask cache holds 8 words per attribute word; 16*768/2 = $1800 bytes)
;
;The register mask_cache points to the low end of the mask cache area.
;The register attr_ptr points to *the copy of* the current attribute.
;Thus:	mask_cache - attr_ptr - 2 = offset of current attribute,
;from the last word in the attribute area towards the first.
;Since entries in the cache are per word of attribute data, and ordering
;the cache from higher attribute addresses to lower, we see that the
;offset into the cache is  16 * ((mask_cache - attr_ptr - 2) / 2),
;or simplified,  8 * (mask_cache - attr_ptr) - 16.



	XDEF	softScr_Recalc
	XDEF	softScr_Redraw
	XDEF	softScr_Update
	XDEF	_softScr_InitTable

	XDEF	_softScr_SpBitmap
	XDEF	_softScr_LineModulo
	XDEF	_softScr_Bitplane0
	XDEF	_softScr_Bitplane1
	XDEF	_softScr_Bitplane2
	XDEF	_softScr_Bitplane3



	SECTION DATA

	;The memory pointers:
_softScr_SpBitmap	dc.l	0	;Beginning of Spectrum bitmap
_softScr_Bitplane0	dc.l	0
_softScr_Bitplane1	dc.l	0
_softScr_Bitplane2	dc.l	0
_softScr_Bitplane3	dc.l	0

_softScr_LineModulo	dc.l	0	;MUST be an even number of bytes!

	;Private:
softScr_FlashState	dc.w	0	;nonzero for inverted video



	SECTION CODE

** Register usage:

;(The aliases are picked to match the use in the bitmap update loop.)
;
; Data:
;	d0	bm_data 	bitmap data; also flash flag and row count
;	d1	andm_a		and mask a
;	d2	eorm_a		eor mask a
;	d3	andm_b		and mask b; carries bitmap data
;	d4	eorm_b		eor mask b; carries attribute data
;	d5	line_mod	line modulo (longword, constant)
;	d6	loopc		loop counter; also column counter
;	d7	temp		temporary
; Address:
;	a0	bplp_0		Bitplane 0 pointer
;	a1	bplp_1		Bitplane 1 pointer
;	a2	bplp_2		Bitplane 2 pointer
;	a3	bplp_3		Bitplane 3 pointer
;	a4	bm_ptr		Spectrum bitmap pointer
;	a5	attr_ptr	Spectrum attribute pointer
;	a6	mask_cache	Pointer to base of mask cache

;In the change checking loop, the low byte of bm_data holds the flash
;flag, which is negative for no flash update, zero for true-video update
;and positive for inverted-video update. The bits 10-15 hold the row
;loop counter, and the bits 8-9 hold the block loop counter.

bm_data EQUR	d0
andm_a	EQUR	d1
eorm_a	EQUR	d2
andm_b	EQUR	d3
eorm_b	EQUR	d4
line_mod EQUR	d5
loopc	EQUR	d6
temp	EQUR	d7

bplp_0	EQUR	a0
bplp_1	EQUR	a1
bplp_2	EQUR	a2
bplp_3	EQUR	a3
bm_ptr	EQUR	a4
attr_ptr EQUR	a5
mask_cache EQUR a6



** For the following subroutines, the caller must save any registers
** he wants to preserve (SP not counted).
**   The wanted flash status (normal or inverted video) is passed in
** the d0 register (word); zero for normal and nonzero for inverted.
**   The data fields _softScr_LineModulo, _softScr_SpBitmap and
** _softScr_Bitplane 0-3 must be initialised, and the routine
** _softScr_InitTable must have been called before using these routines.

** Routines to force screen update and/or redrawing of bitplanes:
** If the screen data could be modified while these routines are
** executing (because of DMA, interrupts or task switching), it can
** not be guaranteed that the produced display matches the actual
** screen data.

** Subroutine to completely recalculate the screen, regarding all
** cached information as invalid.

softScr_Recalc
	move.l	_softScr_SpBitmap,a0
	lea	SOFTSCR_COPYOFFSET+$1800(a0),a0  ;attribute copy area
	move.w	#(768/4)-1,d1
.loop	move.l	-SOFTSCR_COPYOFFSET(a0),d2	;Get real attribute.
	not.l	d2			;Make all attribute copies unlike
	move.l	d2,(a0)+		;their corresponding actual values.
	dbf	d1,.loop
	bra	softScr_Update		;d0 unchanged, passed to Update


** Subroutine to redraw all of the bitplanes, assuming that no
** attributes have changed. Useful for restoring the screen quickly
** when a window has been rendered on top of it without automatically
** saving the background graphics.

softScr_Redraw
	move.l	_softScr_SpBitmap,a0
	lea	SOFTSCR_COPYOFFSET(a0),a0	;bitmap copy area
	move.w	#($1800/4)-1,d1
.loop	move.l	-SOFTSCR_COPYOFFSET(a0),d2	;Get real bitmap data.
	not.l	d2			;Make all bitmap copies unlike
	move.l	d2,(a0)+		;their corresponding actual values.
	dbf	d1,.loop
	bra	softScr_Update		;d0 unchanged, passed to Update



** Subroutine to update the portions of the screen that have changed.

SOFTSCR_COLUMNS	EQU	16	;16 words per row
SOFTSCR_ROWS	EQU	8	;8 rows per block (max 64; 6 bits used)
SOFTSCR_BLOCKS	EQU	3	;3 blocks per screen (max 4; 2 bits used)

softScr_Update
	;We can't modify any (data) register through an alias
	;until the value in d0 has been used (or protected).
	tst.w	d0
	sne	d0
	ext.w	d0	;make d0 = $0000 if zero, $FFFF if nonzero
	cmp.w	softScr_FlashState,d0
	beq.s	.noflash

	;Forced update of flash attributes if wanted state
	;and current state differ:
	move.w	d0,softScr_FlashState		;update current state
	bne.s	.inverted
	moveq	#0,bm_data	;set to flash update, normal video
	bra.s	.flash
.inverted
	moveq	#127,bm_data	;set to flash update, inverted video
	bra.s	.flash

.noflash
	moveq	#-1,bm_data	;set to no flash update
.flash
	and.w	#$00FF,bm_data	;clear bits 8-15

	;bits 8-15 of bm_data must all be zero at this point

	;set bits 8-9 to hold -SOFTSCR_BLOCKS (-1 to -4 are possible)
	;the loop will be from negative up until 0
   	add.w	#((4-SOFTSCR_BLOCKS)&3)<<8,bm_data

	;set bits 10-15 to hold SOFTSCR_ROWS-1 (0 to 63 are possible)
	;the loop will be down to -1
   	add.w	#(SOFTSCR_ROWS-1)<<10,bm_data

	moveq	#(SOFTSCR_COLUMNS-1),loopc	;dbf loop (until -1)
	move.l	_softScr_Bitplane0,bplp_0
	move.l	_softScr_Bitplane1,bplp_1
	move.l	_softScr_Bitplane2,bplp_2
	move.l	_softScr_Bitplane3,bplp_3
	move.l	_softScr_SpBitmap,bm_ptr
	lea	SOFTSCR_COPYOFFSET+$1800(bm_ptr),attr_ptr
	lea	SOFTSCR_COPYOFFSET+$1B00(bm_ptr),mask_cache
	move.l	_softScr_LineModulo,line_mod
	bra	softScr_AttrFetch


** ---------------------------------------------------------------------

** Here begins the code for the updater routine. The main entry point
** is the label softScr_AttrFetch.
** The layout of the routine is somewhat chaotic. This is because short
** branches are used as much as possible.


softScr_RowLoop
	;The spectrum bitmap and attribute pointers are already at the
	;next row (the bitmap pointer is adjusted at each new block).
	;The bitplane pointers need to be moved 16 words back and
	;8 lines down:
	move.l	line_mod,temp
	lsl.l	#3,temp 	;temp = 8*line_mod
	lea	-32(bplp_0,temp.l),bplp_0
	lea	-32(bplp_1,temp.l),bplp_1
	lea	-32(bplp_2,temp.l),bplp_2
	lea	-32(bplp_3,temp.l),bplp_3

	moveq	#(SOFTSCR_COLUMNS-1),loopc	;new column count

	;This part had to be moved out of the setup_7 to setup_0
	;range, or short branches could not be used in the bitmap
	;check. The only sacrifice is this extra branch, which is
	;executed at each new character row.
	bra.s	softScr_AttrFetch



** Some setup stubs are placed here so short branches can be used
** by the bitmap check.

softScr_setup7
	lea	$700(bm_ptr),bm_ptr
	move.l	line_mod,temp
	lsl.l	#3,temp
	sub.l	line_mod,temp	;temp=7*line_mod
	adda.l	temp,bplp_0
	adda.l	temp,bplp_1
	adda.l	temp,bplp_2
	adda.l	temp,bplp_3
	moveq	#7,temp
	bra	softScr_BitmapChanged

softScr_setup6
	lea	$600(bm_ptr),bm_ptr
	move.l	line_mod,temp
	add.l	temp,temp
	add.l	line_mod,temp
	add.l	temp,temp	;temp=6*line_mod
	adda.l	temp,bplp_0
	adda.l	temp,bplp_1
	adda.l	temp,bplp_2
	adda.l	temp,bplp_3
	moveq	#6,temp
	bra	softScr_BitmapChanged

softScr_setup5
	lea	$500(bm_ptr),bm_ptr
	move.l	line_mod,temp
	add.l	temp,temp
	add.l	temp,temp
	add.l	line_mod,temp	;temp=5*line_mod
	adda.l	temp,bplp_0
	adda.l	temp,bplp_1
	adda.l	temp,bplp_2
	adda.l	temp,bplp_3
	moveq	#5,temp
	bra	softScr_BitmapChanged



** Attribute change check:

;Special case of attribute fetch for flash updates:

softScr_FlashCheck
	move.w	-SOFTSCR_COPYOFFSET(attr_ptr),eorm_b
	bmi	softScr_HighFlash	;jump if flash bit set in high byte
	tst.b	eorm_b
	bmi	softScr_LowFlash	;jump if flash bit set in low byte

	bra.s	softScr_AttrCheck	;proceed to check if attr has changed


;This is the main entry point for the screen update:

;Important:  The whole of this loop: attribute fetch and check,
;flash check, bitmap check, pointer increment, loop counter decrement
;and jump, should all fit into a 256-byte cache.

;The read attribute (word) is carried in the eorm_b register.
;The bm_data register holds the flash flag and row counter.
;Recall that attr_ptr points to the attribute copy.

softScr_AttrFetch
	tst.b	bm_data		;nonnegative if this is a flash update
	;Branch if flash bits must be checked:
	bpl.s	softScr_FlashCheck

	move.w	-SOFTSCR_COPYOFFSET(attr_ptr),eorm_b

softScr_AttrCheck	;label used when coming from softScr_FlashCheck

	cmp.w	(attr_ptr),eorm_b
	;Update whole character if attr has changed:
	bne	softScr_AttrChanged

	;fall through into softScr_BitmapCheck



** Bitmap change check:

;Principle:  We check from the bottom line to the top, and if a change
;is found, all lines from the changed line to the top will be updated.
;This way, all bitmap and bitplane pointers are back at the top line
;after the update is done. See the updater routine and setup stubs below.
;For speed, we handle two characters at a time, reading and writing words.
;
;Comment:  It is not a good idea to keep checking lines for changes once
;one change is found. The case when many lines need updating would be
;very much slowed down, and not much is gained in any case.

; bm_ptr = top line of current character in Spectrum bitmap
; bplp_x = top line of character in bitplane x, for x in [0..3]

;The read bitmap data will be carried in the andm_b register.

softScr_BitmapCheck
	;It's faster to use offsets than to add $100 after each line.
	;It's also faster not to jump as long as the data compares equal.

bmcheck MACRO
	move.w	(\1*$100)(bm_ptr),andm_b	;can't cmp directly anyway
	cmp.w	((\1*$100)+SOFTSCR_COPYOFFSET)(bm_ptr),andm_b
	bne.s	softScr_setup\1
	ENDM

	bmcheck 7
	bmcheck 6
	bmcheck 5
	bmcheck 4
	bmcheck 3
	bmcheck 2
	bmcheck 1

	move.w	(bm_ptr),andm_b 	;special case: offset is 0
	cmp.w	SOFTSCR_COPYOFFSET(bm_ptr),andm_b
	bne.s	softScr_setup0

;If this point is reached, the bitmap of the two characters was unchanged.


;Skip to next word and repeat until the whole screen is ready:

next_char MACRO 	;this macro also used after bitplane updating
	addq.l	#2,bm_ptr
	addq.l	#2,attr_ptr
	addq.l	#2,bplp_0
	addq.l	#2,bplp_1
	addq.l	#2,bplp_2
	addq.l	#2,bplp_3
	dbf	loopc,softScr_AttrFetch
	ENDM

	next_char	;(macro call)

softScr_NextRow
	sub.w	#(1<<10),bm_data	;decrement the row loop counter
	bcc	softScr_RowLoop		;carry set if counter becomes -1
	bra.s	softScr_NewBlock



** Stubs to set up registers before entering the attribute handling part

;Comment:  Something like this must be done in any case. There must be
;some way to communicate to the updating routine how many lines are to
;be updated, like a count value or an entry point jump address.
;Since we have to go through the Attribute to Bitmap Masks Translation
;(and I don't want eight almost identical copies of that routine), this
;information must be carried in some register. Using a loop counter is a
;good way, because of caching; see the updater routine below.
;
;  Skipping to the line where the update begins and then updating all
;lines to the top line saves some time; see the updater routine.
;  setup 0 is the shortest stub, but the least common. If that is the one
;used, then all bytes in the character have been tested, and only the top
;line had changed (so we might well want it to be short).

softScr_setup4
	lea	$400(bm_ptr),bm_ptr
	move.l	line_mod,temp
	add.l	temp,temp
	add.l	temp,temp	;temp=4*line_mod
	adda.l	temp,bplp_0
	adda.l	temp,bplp_1
	adda.l	temp,bplp_2
	adda.l	temp,bplp_3
	moveq	#4,temp
	bra.s	softScr_BitmapChanged

softScr_setup3
	lea	$300(bm_ptr),bm_ptr
	move.l	line_mod,temp
	add.l	temp,temp
	add.l	line_mod,temp	;temp=3*line_mod
	adda.l	temp,bplp_0
	adda.l	temp,bplp_1
	adda.l	temp,bplp_2
	adda.l	temp,bplp_3
	moveq	#3,temp
	bra.s	softScr_BitmapChanged

softScr_setup2
	lea	$200(bm_ptr),bm_ptr
	move.l	line_mod,temp
	add.l	temp,temp	;temp=2*line_mod
	adda.l	temp,bplp_0
	adda.l	temp,bplp_1
	adda.l	temp,bplp_2
	adda.l	temp,bplp_3
	moveq	#2,temp
	bra.s	softScr_BitmapChanged

softScr_setup1
	lea	$100(bm_ptr),bm_ptr
	adda.l	line_mod,bplp_0
	adda.l	line_mod,bplp_1
	adda.l	line_mod,bplp_2
	adda.l	line_mod,bplp_3
	moveq	#1,temp
	bra.s	softScr_BitmapChanged

softScr_setup0
	;No pointers need to be moved.
	moveq	#0,temp
	bra.s	softScr_BitmapChanged



softScr_NewBlock
	;At this point, bits 10-15 of bm_data are all set (-1).

	add.w	#(1<<8),bm_data	;increment the block counter (negative)
	bcs.s	.end		;carry is set if counter becomes 0

	;The bitmap pointer must be moved 32*8=256 bytes
	;back and one block (32*8*8=2048 bytes) forward.
	lea	2048-256(bm_ptr),bm_ptr

	;The row counter in bits 10-15 of bm_data is still -1.
	;Adding the actual number of rows will set up the counter
	;for another "spin around the block".
	add.w	#(SOFTSCR_ROWS<<10),bm_data

	bra	softScr_RowLoop

.end	rts			;Return to calling routine



;This is the entry point used when a bitmap line change is detected.
;We come here directly from one of the setup stubs, so the attribute
;can not have changed, and neither is this a forced flash update.
;Thus, the cached masks should be valid, and they always reflect the
;current flash status (normal or inverted) for attributes with the
;flash bit set.

;The loop counter value is passed in the temp register, and the read
;bitmap data is passed in the andm_b register.

softScr_BitmapChanged
	swap	loopc		;Store column counter in high word.
	move.w	temp,loopc	;Use low word of loopc for line count.

	swap	bm_data 	;Store flash flag/row count in high word.
	move.w	andm_b,bm_data	;Keep bitmap data in low word.

	move.l	mask_cache,temp 	;This calculation is described
	sub.l	attr_ptr,temp		;at the beginning of the file.
	lsl.l	#3,temp
	movem.l -16(mask_cache,temp),andm_a/eorm_a/andm_b/eorm_b

	bra.s	softScr_UpdateLines



** Bitplane updater routine:

;registers needed:
;	data:
;		bm_data (word)
;		andm_a
;		andm_b
;		eorm_a
;		eorm_b
;		loopc (word)
;		line_mod
;		temp
;	address:
;		bplp_0
;		bplp_1
;		bplp_2
;		bplp_3
;		bm_ptr

;Principle:  We update from lower lines towards the top line, so that when
;we are finished, the bitmap and bitplane pointers are pointing at the
;top line and need no further correction. Otherwise, we would either have
;to save their values before the loop and restore them afterwards (slow)
;or save the number of lines updated so that we could recalculate them
;after the loop (stupid).
;Thus, we calculate the addresses for the first line to update before
;we enter the loop. Nothing needs to be stored and the calculations
;are fast (see the setup stubs above),
;
;Comment:  Using a loop is better than to have duplicate code and several
;entry points, since the loop will fit easily into any code cache.
;Caching will save a lot more time than any code duplication scheme.
;(This isn't really intended for processors lower than 68020 anyway.)

;Upon entry, bm_data has already been read, and all pointers are set to
;update the lowermost line that was changed. The routine then proceeds
;with updating the n lines above as well (n given by low word of loopc).

;softScr_UpdateLines is the entry point for this routine.

softScr_UpdateLoop
	lea	-$100(bm_ptr),bm_ptr	;move to previous bitmap line
	move.w	(bm_ptr),bm_data	;get bitmap data

	;Move bitplane pointers to previous line:
	;  One reason not to have a statically compiled line modulo and
	;use "lea -modulo(bpl_x),bpl_x" is that it would take two words
	;per instruction, rather than one for "suba.l ...", so with
	;caching and all, adda has greater potential for fast execution.
	;  Another reason is that the dynamical way is simply more
	;dynamical - the screen size could easily be set to any width,
	;without having to recompile the program.
	suba.l	line_mod,bplp_0
	suba.l	line_mod,bplp_1
	suba.l	line_mod,bplp_2
	suba.l	line_mod,bplp_3

softScr_UpdateLines
	move.w	bm_data,SOFTSCR_COPYOFFSET(bm_ptr)  ;update bitmap copy

	;See layout of masks in registers below.

	move.w	bm_data,temp	;First bitplanes 0 and 1:
	swap	temp		;Make both high and low word
	move.w	bm_data,temp	;hold a copy of the bitmap data.

	and.l	andm_a,temp	;Mask out the bitplane data.
	eor.l	eorm_a,temp

	move.w	temp,(bplp_1)	;Write the data to the bitplanes
	move.w	bm_data,temp	;and set up new copies of the
	swap	temp		;bitmap data for planes 2 and 3.
	move.w	temp,(bplp_0)
	move.w	bm_data,temp

	and.l	andm_b,temp
	eor.l	eorm_b,temp

	move.w	temp,(bplp_3)
	swap	temp
	move.w	temp,(bplp_2)


	;The counter is 0 for a single line.
	;Only the low word is affected.
	dbf	loopc,softScr_UpdateLoop

	swap	loopc		;Restore column counter.
	swap	bm_data 	;Restore flash flag/row count.

	next_char		;Move to next character (macro call)

	;In case the current row ended:
	bra	softScr_NextRow


** Attributes to bitplane masks translation:

;Mask calculation:
;
;Suppose that there is a translation from the attribute (paper, ink and
;brightness) for a certain character, to a "Background" and a "Foreground"
;bitplane mapping, with one bit for each bitplane, respectively.
;Then, depending on the state of bit x in each mapping, one of four
;possible byte values are written into bitplane x:
;
;  Background	 Foreground    Bitplane
;      0	     0		  0
;      0	     1		  b
;      1	     0		 ~b
;      1	     1		 $FF
;
;where b is the byte value in the corresponding position in the
;ZX Spectrum bitmap, and ~b is the complement of that value.
;
;We see that the written value can be expressed as:
;  (b AND and_mask) EOR eor_mask
;where the masks are calculated as follows:
;
;  Background	 Eor-mask
;      0	    0
;      1	   $FF
;
;  Background EOR Foreground	And-mask
;	       0		   0
;	       1		  $FF


;Layout of masks in registers:
;
;	andm_a: high word	low word
;		andm_0		andm_1
;
;	eorm_a: high word	low word
;		eorm_0		eorm_1
;
;	andm_b: high word	low word
;		andm_2		andm_3
;
;	eorm_b: high word	low word
;		eorm_2		eorm_3


;Layout of masks in the tables: Each entry consists of 4 words.
;
; Word: 0	1	2	3
;	e0 a0	e1 a1	e2 a2	e3 a3
;
;where e0 is "eor mask 0", and a0 is "and mask 0". These are byte-sized.
;
;For two attribute bytes X and Y read as the word XY, for each byte
;the flash bit is cleared and the entry indexed by the byte is read.
;For each entry, words 0 and 1 are read as a single long word and
;words 2 and 3 as another.
;
;Call the first longword given by X "Ax", and that given by Y "Ay".
;They will have the following contents:
;	Ax:	e0x a0x e1x a1x
;	Ay:	e0y a0y e1y a1y
;
;We want to form the andm_a and eorm_a from these two long words.
;	Aa:	a0x a0y a1x a1y
;	Ea:	e0x e0y e1x e1y
;
;We see that	Aa = ( ( ShiftLeft(Ax,8) EOR Ay ) AND $FF00FF00 ) EOR Ay
;and		Ea = ( ( ShiftRight(Ay,8) EOR Ax ) AND $00FF00FF ) EOR Ax
;
;Correspondingly, we can form Ab and Eb from Bx and By, that is the second
;long words read from the table.


;Mask caching:
;
;The mask longwords (Aa, Ea, Ab and Eb above) are stored in the "mask
;cache" and are reused if only the bitmap has changed. This saves quite
;a lot of time, since the mask calculations are a bit slow, and in general
;a character position will have the same attribute through several updates
;even if the bitmap changes.


;Registers needed:
;	data:
;		temp
;		andm_a
;		andm_b
;		eorm_a
;		eorm_b
;	address:
;		mask_cache


;Special handling of attributes with flash bit set:

;If the flash bit was set in the high byte (the low byte was not tested):

softScr_HighFlash
	swap	loopc		;store column count in high word
	move.w	bm_data,loopc	;put flash flag/row count in low word
	;Now bm_data can be used as scratch.

	move.l	mask_cache,bm_data	;Protect the mask_cache register

	;XY is stored in low word of eorm_b
	move.w	eorm_b,temp
	andi.w	#$007F,temp	;keep lower 7 bits of Y

	lea	softScr_InvTable(PC),mask_cache

	tst.b	eorm_b		;test attribute of low byte
	bpl.s	.no_inv_L	;skip if not a flash attribute
	tst.b	loopc		;test flash flag
	beq.s	.no_inv_L	;no inversion if zero
	move.b	(mask_cache,temp.w),temp	;swap ink and paper
.no_inv_L
	lsl.w	#3,temp 	;get index into table (8-byte entries)

	lsr.w	#8,eorm_b	;move X to low byte
	andi.w	#$007F,eorm_b	;keep lower 7 bits of X
	tst.b	loopc		;test flash flag
	beq.s	.no_inv_H	;no inversion if zero
	move.b	(mask_cache,eorm_b.w),eorm_b	;swap ink and paper
.no_inv_H
	lsl.w	#3,eorm_b	;get index into table (8-byte entries)

	bra.s	softScr_FromHigh


;If the flash bit was reset in the high byte and set in the low byte:

softScr_LowFlash
	swap	loopc		;store column count in high word
	move.w	bm_data,loopc	;put flash flag/row count in low word
	;Now bm_data can be used as scratch.

	move.l	mask_cache,bm_data	;Protect the mask_cache register

	;XY is stored in low word of eorm_b
	move.w	eorm_b,temp
	andi.w	#$007F,temp	;keep lower 7 bits of Y

	tst.b	loopc		;test flash flag
	beq.s	.no_inv 	;no inversion if zero
	lea	softScr_InvTable(PC),mask_cache
	move.b	(mask_cache,temp.w),temp	;swap ink and paper
.no_inv
	bra.s	softScr_FromLow


;This is the entry point used when a changed attribute is discovered.
;The attributes (word) should already be read into the register eorm_b.
;Recall that attr_ptr points to the attribute copy.

;We must look at the attribute flash bits and the current flash state to
;find out how to map the attribute information.

softScr_AttrChanged
	move.w	eorm_b,(attr_ptr)	;update the attribute copy
	bmi.s	softScr_HighFlash ;branch if first byte has flash set
	tst.b	eorm_b
	bmi.s	softScr_LowFlash ;branch if second byte has flash set

	swap	loopc		;store column count in high word
	move.w	bm_data,loopc	;put flash flag/row count in low word
	;Now bm_data can be used as scratch.

	move.l	mask_cache,bm_data	;Protect the mask_cache register

	;XY is stored in low word of eorm_b
	move.w	eorm_b,temp
	andi.w	#$007F,temp	;keep lower 7 bits of Y

softScr_FromLow ;label used when coming from softScr_LowFlash
	lsl.w	#3,temp 	;get index into table (8-byte entries)

	andi.w	#$7F00,eorm_b	;keep lower 7 bits of X
	lsr.w	#5,eorm_b	;get index into table (8-byte entries)

	;Now eorm_b holds the index for X and temp the index for Y.

softScr_FromHigh	;label used when coming from softScr_HighFlash
	;Use the mask_cache register as table base pointer
	lea	softScr_MaskTable(PC),mask_cache

	;Read Ax and Bx from mask table to andm_a and andm_b
	movem.l (mask_cache,eorm_b.w),andm_a/andm_b

	;Read Ay and By from mask table to eorm_a and eorm_b
	movem.l (mask_cache,temp.w),eorm_a/eorm_b

	movea.l bm_data,mask_cache	;Restore the mask_cache register


	move.l	andm_a,temp	;protect Ax
	lsl.l	#8,andm_a	;shift Ax 8 bits left
	eor.l	eorm_a,andm_a	;eor result with Ay
	andi.l	#$FF00FF00,andm_a
	eor.l	eorm_a,andm_a	;andm_a is now (a0x a0y a1x a1y)

	lsr.l	#8,eorm_a	;shift Ay 8 bits right
	eor.l	temp,eorm_a	;eor result with Ax
	andi.l	#$00FF00FF,eorm_a
	eor.l	temp,eorm_a	;eorm_a is now (e0x e0y e1x e1y)

	move.l	andm_b,temp	;protect Bx
	lsl.l	#8,andm_b	;shift Bx 8 bits left
	eor.l	eorm_b,andm_b	;eor result with By
	andi.l	#$FF00FF00,andm_b
	eor.l	eorm_b,andm_b	;andm_b is now (a2x a2y a3x a3y)

	lsr.l	#8,eorm_b	;shift By 8 bits right
	eor.l	temp,eorm_b	;eor result with Bx
	andi.l	#$00FF00FF,eorm_b
	eor.l	temp,eorm_b	;eorm_b is now (e2x e2y e3x e3y)

	;Store the masks in the cache
	move.l	mask_cache,temp 	;This calculation is described
	sub.l	attr_ptr,temp		;at the beginning of the file.
	lsl.l	#3,temp
	movem.l andm_a/eorm_a/andm_b/eorm_b,-16(mask_cache,temp)

	move.w	loopc,bm_data	;flash flag/row count back into bm_data
	swap	bm_data 	;store flash flag/row count in high word


	;Lastly, set up for update of all 8 lines:

	lea	$700(bm_ptr),bm_ptr
	move.w	(bm_ptr),bm_data	;bitmap word must be preread

	move.l	line_mod,temp
	lsl.l	#3,temp
	sub.l	line_mod,temp	;temp=7*line_mod
	adda.l	temp,bplp_0
	adda.l	temp,bplp_1
	adda.l	temp,bplp_2
	adda.l	temp,bplp_3
	move.w	#7,loopc	;don't destroy high word of loopc!

	bra	softScr_UpdateLines


softScr_InvTable	;table to swap ink and paper
	;Bright bit reset:
	dc.b	%000000,%001000,%010000,%011000
	dc.b	%100000,%101000,%110000,%111000
	dc.b	%000001,%001001,%010001,%011001
	dc.b	%100001,%101001,%110001,%111001
	dc.b	%000010,%001010,%010010,%011010
	dc.b	%100010,%101010,%110010,%111010
	dc.b	%000011,%001011,%010011,%011011
	dc.b	%100011,%101011,%110011,%111011
	dc.b	%000100,%001100,%010100,%011100
	dc.b	%100100,%101100,%110100,%111100
	dc.b	%000101,%001101,%010101,%011101
	dc.b	%100101,%101101,%110101,%111101
	dc.b	%000110,%001110,%010110,%011110
	dc.b	%100110,%101110,%110110,%111110
	dc.b	%000111,%001111,%010111,%011111
	dc.b	%100111,%101111,%110111,%111111
	;Bright bit set:
	dc.b	%1000000,%1001000,%1010000,%1011000
	dc.b	%1100000,%1101000,%1110000,%1111000
	dc.b	%1000001,%1001001,%1010001,%1011001
	dc.b	%1100001,%1101001,%1110001,%1111001
	dc.b	%1000010,%1001010,%1010010,%1011010
	dc.b	%1100010,%1101010,%1110010,%1111010
	dc.b	%1000011,%1001011,%1010011,%1011011
	dc.b	%1100011,%1101011,%1110011,%1111011
	dc.b	%1000100,%1001100,%1010100,%1011100
	dc.b	%1100100,%1101100,%1110100,%1111100
	dc.b	%1000101,%1001101,%1010101,%1011101
	dc.b	%1100101,%1101101,%1110101,%1111101
	dc.b	%1000110,%1001110,%1010110,%1011110
	dc.b	%1100110,%1101110,%1110110,%1111110
	dc.b	%1000111,%1001111,%1010111,%1011111
	dc.b	%1100111,%1101111,%1110111,%1111111

softScr_MaskTable	;128 entries, 2 longwords per entry:
		;  eor0 and0 eor1 and1 eor2 and2 eor3 and3
	ds.l	(128*2)

softScr_ColourMap
	;16 byte-sized entries. Colours 0-7 in entries 0-7,
	;bright colours 0-7 in entries 8-15.
	;For each entry, a bit is set if the corresponding
	;bitplane is used for that colour. Only bits 0-3 are used.
	;The colour code bit order in the ZX Spectrum is GRB (not RGB),
	;giving a colour sequence of Black (0), Blue, Red, Magenta,
	;Green, Cyan, Yellow and White (7).

	;This table is almost an identity mapping (letting the bitplane
	;order coincide with the colour code bit order), with the
	;exception of "plain black" being mapped onto "bright black".
	;This makes colour register 0 free for use as the border colour.

	dc.b	%1000,%0001,%0010,%0011,%0100,%0101,%0110,%0111
	dc.b	%1000,%1001,%1010,%1011,%1100,%1101,%1110,%1111


	EVEN

** Subroutine to initialise the mask table, using the mapping in
** the softScr_ColourMap table. The mask calculation is described above.
** The routine protects all registers it uses, and can be called
** directly from a C program. Prototype: "void softScr_InitTable(void)".

_softScr_InitTable
	movem.l a0-a1/d0-d4,-(SP)
	lea	softScr_MaskTable(PC),a0
	lea	softScr_ColourMap(PC),a1	;plain colours
	bsr.s	.calc
	lea	8+softScr_ColourMap(PC),a1	;bright colours
	bsr.s	.calc
	movem.l (SP)+,a0-a1/d0-d4
	rts

.calc	;Each call calculates 64 entries using a1 as colour map base.
	moveq	#7,d0	;paper counter (from 7 to 0)
.p_loop
	moveq	#7,d1	;ink counter (from 7 to 0)
	moveq	#7,d2
	sub.w	d0,d2		;colour indexes must go from 0 to 7
	move.b	(a1,d2.w),d2	;get background bitplane map
.i_loop
	moveq	#7,d3
	sub.w	d1,d3		;colour indexes must go from 0 to 7
	move.b	(a1,d3.w),d3	;get foreground bitplane map

	eor.b	d2,d3	;background EOR foreground (gives and-mask)
	move.b	d2,d4	;copy of background (gives eor-mask)

	lsr.b	#1,d4
	scs	(a0)+	;eor 0
	lsr.b	#1,d3
	scs	(a0)+	;and 0
	lsr.b	#1,d4
	scs	(a0)+	;eor 1
	lsr.b	#1,d3
	scs	(a0)+	;and 1
	lsr.b	#1,d4
	scs	(a0)+	;eor 2
	lsr.b	#1,d3
	scs	(a0)+	;and 2
	lsr.b	#1,d4
	scs	(a0)+	;eor 3
	lsr.b	#1,d3
	scs	(a0)+	;and 3

	dbf	d1,.i_loop
	dbf	d0,.p_loop
	rts

** --------------------------------------------------------------------
