*
* last modified 30/08/95
*
* AMIGA QDOS SCREEN ACCELERATOR for 32 bit AGA machines
* Version 3.21 by Simon N Goodwin, with thanks to Mark
* J Swift for scanning a listing when I lost the source!
* Listing assumes TABs set to 11 chars
*
qdos	equr	a0	Qdos screen pointer
Plane1	equr	a1	Least significant bitplane
Plane2	equr	a2
Plane3	equr	a3	MODE 8 only
table	equr	a4	Byte translator
*
* Interrupt servers must preserve A5, A6 and A7 (!)
* but all other registers are up for grabs.
*
* Bitplane accumulator registers, added in v3.11
*
RED	equr	d4
BLUE	equr	d5
GREEN	equr	d6
*
* Amiga hardware equates
*
COLOR00	EQU	$DFF180
COLOR01	EQU	$DFF182
COLOR02	EQU	$DFF184
COLOR03	EQU	$DFF186
COLOR04	EQU	$DFF188
COLOR05	EQU	$DFF18A
COLOR06	EQU	$DFF18C
COLOR07	EQU	$DFF18E
*
* QDOS equates
*
BP.INIT	equ	$110
CA.GTINT	equ	$112
BV.CHRIX	equ	$11A
*
* QDOS hardware equates
*
MC_STAT	equ	$18063
*
* Default addresses for Qdos and Amiga screen images
*
qlbase	equ	$20000	Base of first QL screen
qlsize	equ	$8000	Size of each QL screen
bp1base	equ	$10000	Base of first Amiga bitplane
bp3base	equ	$12000	Base of BLUE bitplane in MODE 8
bp2base	equ	$14000	Base of second bitplane
*
* Offsets of variables in interrupt linkage
*
link	equ	0	Interrupt link comes first
vector	equ	4	Offset of code address
marker	equ	8	Offset of "ACE3" signature
prefix	equ	12	Number of bytes so far
*
* These variables follow MARKER and are relative to PREFIX
*
limit	equ	0	Count fields down from this
count	equ	1	Current field in sequence
busy	equ	2	Set during interrupt handler
missed	equ	3	(Counted interrupts ignored)
chunks	equ	4	Number of 2K chunks at each pass
soFar	equ	5	Number of 2K chunks already done
smode	equ	6	current screen mode
varSize	equ	prefix+8	Total bytes of linkage
*
*  ROM header
*
BASE:
	dc.l	$4AFB0001		ROM recognition code
	dc.w	define-BASE	BASIC procs
	dc.w	ROM_START-BASE
	dc.b	0,30,'ACE screen accelerator v3.21 ',$A
*
*  start of ROM code
*
ROM_START:
	movem.l	d0-d3/a0-a3,-(a7)

	lea	BASE(pc),a0
	cmp.l	#$1000000,a0	FAST RAM?
	bmi	ROM_EXIT		No, user decides

	moveq	#0,d0
	trap	#1

	move.b	161(a0),d0 	Check CPU version
	cmp.b	#$30,d0		< 030, user decides
	bcs.s	ROM_EXIT

	bsr	mblit_on

ROM_EXIT:
	movem.l	(a7)+,d0-d3/a0-a3
	rts
*
* This is the routine that redraws the whole screen
* On entry A3 -> Linkage-8; A6 -> Qdos variables
*
AceVars	equ	prefix+8		A3 offset to LIMIT
*
redraw	btst	#7,$1813E
	beq	mblit_off

	tst.b	busy+AceVars(a3)	Prevent re-entrancy
	beq.s	update
*
* Code to work out the number of interrupts MISSED (while ACE is
* busy) commented out for version 3.16 as Qdos can't cope with
* missed interrupts anyway, and I needed the variable to set the
* proportion of the screen to be updated (1..16, as SCR_PRIORITY)
*
* ignore	addq.b	#1,missed+AceVars(a3)
skip	rts
*
update
*	move.b	missed+AceVars(a3),d1
*	beq.s	limit_ok		No interrupts missed
*
*	addq.b	#1,d1		Count one that worked
*	clr.b	missed+AceVars(a3)	Keep checking
*	cmp.b	limit+AceVars(a3),d1	Can the CPU keep up?
*	bls.s	limit_ok		There is enough time
*
*	move.b	d1,limit+AceVars(a3)	Increase limit
limit_ok	subq.b	#1,count+AceVars(a3)
	bne.s	skip		The time is NOT nigh
*
	st	busy+AceVars(a3)	Flag critical region
	move.b	limit+AceVars(a3),count+AceVars(a3)
	lea.l	bp1base,Plane1
	lea.l	bp2base,Plane2
	lea.l	qlbase+qlsize,qdos	Point at screen 2
	move.b	MC_STAT,d2 	Check if it's in use
	bmi.s	chkmod
*
	lea.l	-qlsize(qdos),qdos	Wind back to screen 1
*
* Check if mode has changed
*
chkmod
	cmp.b	smode+AceVars(a3),d2
	beq.s	chksho
*
	bsr	ace_cop
	move.b	d2,smode+AceVars(a3)
*
chksho
	btst	#1,d2		check blanking bit
	bne	done		and exit if set
*
* Work out which chunk needs to be updated next, and adjust pointers
*
based	move.w	#2048/8,d3 	Iterations per 2K chunk
	moveq	#0,d4
	move.b	chunks+AceVars(a3),d4
	moveq	#0,d5
	move.b	soFar+AceVars(a3),d5
*
* Advance input and output pointers past data previously processed
*
	move.l	d5,d6
	lsl.l	#8,d6
	add.l	d6,d6		* 512 for output offsets
	add.l	d6,Plane1
	add.l	d6,qdos
	add.l	d6,Plane2
	add.l	d6,qdos		Qdos gets scanned fastest
	add.l	d6,qdos
	add.l	d6,qdos		We'll need D6 again later
*
* Work out where we're going to end up
*
	add.b	d4,d5
	cmp.b	#16,d5		Up to end of screen?
	bcs.s	nextChunk
*
	sub.b	#16,d5		Compute potential overrun
	sub.b	d5,d4		Do that much less, then
	clr.b	d5		Restart at the beginning
*
nextChunk	move.b	d5,soFar+AceVars(a3)
	mulu.w	d4,d3		D3 is loop count
	subq.w	#1,d3		Adjust for DBRA
*
* If we're running on a CPU with a data cache, we should disable cache
* allocation while redrawing the screen to avoid swamping it with data
* that we do not intend to re-use, and wiping out MODE 8 table data.
*
	move.b	161(a6),d0 	Check CPU version
	cmp.b	#$30,d0
	bcs.s	decached		No data cache before 68030
*
* If we're in MODE 8 we need to prime the data cache with our byteTable
*
	 btst	#3,d2		MODE 8 ?
	 beq.s	noPrime
*
	lea.l     byteTable(pc),table
	moveq     #256/4-1,d1
primer	tst.l     (table)+		Get it all into the cache
	dbra      d1,primer
*
noPrime	cmp.b     #$30,d0		Is this a 68030?
	bne.s     after030
*
* 68030 data cache disabling code
*
	dc.w      $4E7A,$1002 	MOVEC CACR,D1
	move.l    d1,d7		Save old setting
	beq.s     decached
*
	bset      #9,d1		Disable data allocation
	dc.w      $4E7B,$1002 	MOVEC D1,CACR
	bra.s     decached
*
* 68040/060 data cache disabling code; leaves prior DTT0 in D7
*
after030	dc.w      $4E7A,$7006 	MOVEC DTT0,D7
	move.l    #$C060,d1		Don't cache 0..16 Mb
	dc.w      $4E7B,$1006 	MOVEC D1,DTT0
*
* Check the mode to determine the format of the Qdos screen
*
decached	btst      #3,d2		Mode 8 ?
	bne.s     mode8
*
* Algorithmic version, reads 8 bytes and unscrambles them to 2 bitplanes
* Input read to D0 and D5 goes to D2 and D3; D4 scratch, D6 counts loops
*
mode4	move.w    d3,d1
	add.l     d6,Plane1		MODE 4 outputs 2 for 1
	add.l     d6,Plane2
next8	movem.l   (qdos)+,d0/d5	Scrambled long words: ABCD EFGH
	move.l    d0,d2		Set A in high byte of result
	lsl.l     #8,d0		Align BC in the high word
	move.l    d2,d4		Save low byte in D for later
	swap      d0		Low word is now BC
	swap      d2		CDAB
	move.l    d0,d3		Set up B, but in low word
	move.b    d4,d3		Set up D, again low for now
	move.b    d0,d2		AC set in low word
*
* So far we have set AC and BD in the low word of our output registers
* Next step is to work out the other word, and get both words in order
*
	move.l	d5,d4		Save H for later
	swap	d5		GHEF
	swap	d2		AC??
	move.w	d5,d2		Set E in place
	move.l	d4,d5		EFGH
	lsr.l	#8,d5		0EFG
	move.b	d5,d2		Set G in place
	move.l	d2,(Plane2)+	Store ACEG green bits
	swap	d3		BD??
	move.w	d5,d3		BDF?
	move.b	d4,d3		BDFH
	move.l	d3,(Plane1)+	Write out red bits
	dbra	d1,next8
*
	bra	CacheIn
*
* MODE 8 support - added in version 3.11
*
mode8
	lea.l     byteTable(pc),table	Point to start of table
*
	moveq	#0,d0		Ensure byte values are unsigned longs
	move.l	a3,-(a7)		** fix? **
	lea.l	bp3base,Plane3	Extra plane pointer for eight colours
	add.l	d6,Plane3
*
next2	move.l	(qdos)+,d1
*
* Convert two Qdos screen bytes into three packed nybbles for the bitplanes
*
	rol.l	#8,d1		Move MSB (green byte) to LSB
	move.b	d1,d0		Make it an unsigned long value
	moveq	#15,d2		Mask for low nybble
	and.b	0(table,d0.l),d2	Get four packed green bits
	lsl.l	#4,GREEN		Make room
	or.b	d2,GREEN
	rol.l	#8,d1		Get blue and red byte to LSB
	move.b	d1,d0
	moveq	#15,d2		Mask for packed red bits
	move.b	0(table,d0.l),d0
	lsl.l	#4,RED		Make room
	and.b	d0,d2		Extract red bits
	lsl.l	#4,BLUE		Make room
	lsr.b	#4,d0		Move blue pack to low nybble
	or.b	d2,RED
	or.b	d0,BLUE
*
* Now do the next two Qdos bytes, from the other half of D1
*
	rol.l	#8,d1		Move MSB (green byte) to LSB
	move.b	d1,d0		Make it an unsigned long value
	moveq	#15,d2		Mask for low nybble
	and.b	0(table,d0.l),d2	Get four packed green bits
	lsl.l	#4,GREEN		Make room
	or.b	d2,GREEN
	rol.l	#8,d1		Get blue and red byte to LSB
	move.b	d1,d0
	moveq	#15,d2		Mask for packed red bits
	move.b	0(table,d0.l),d0
	lsl.l	#4,RED		Make room
	and.b	d0,d2		Extract red bits
	lsl.l	#4,BLUE		Make room
	lsr.b	#4,d0		Move blue pack to low nybble
	or.b	d2,RED
	or.b	d0,BLUE
*
* Pick up another Qdos long word and repeat to get a whole word for each bitplane
*
	move.l	(qdos)+,d1
*
* Optimisation to speed up black areas of the screen. Overhead is just this test
*
	beq.s	blackOut		Skip prevarication
*
	rol.l	#8,d1		Move MSB (green byte) to LSB
	move.b	d1,d0		Make it an unsigned long value
	moveq	#15,d2		Mask for low nybble
	and.b	0(table,d0.l),d2	Get four packed green bits
	lsl.l	#4,GREEN		Make room
	or.b	d2,GREEN
	rol.l	#8,d1		Get blue and red byte to LSB
	move.b	d1,d0
	moveq	#15,d2		Mask for packed red bits
	move.b	0(table,d0.l),d0
	lsl.l	#4,RED		Make room
	and.b	d0,d2		Extract red bits
	lsl.l	#4,BLUE		Make room
	lsr.b	#4,d0		Move blue pack to low nybble
	or.b	d2,RED
	or.b	d0,BLUE
*
* Time to sort out the remaining Qdos word
*
	rol.l	#8,d1		Move MSB (green byte) to LSB
	move.b	d1,d0		Make it an unsigned long value
	moveq	#15,d2		Mask for low nybble
	and.b	0(table,d0.l),d2	Get four packed green bits
	lsl.l	#4,GREEN		Make room
	or.b	d2,GREEN
*
* If D3 is even we've got long words to write out
*
	btst	#0,d3
	bne.s	noWrite
*
* These three nybbles complete long bitplane values which must be stored
*
	move.l	GREEN,(Plane3)+
	rol.l	#8,d1		Get blue and red byte to LSB
	move.b	d1,d0
	moveq	#15,d2		Mask for packed red bits
	move.b	0(table,d0.l),d0
	lsl.l	#4,RED		Make room
	and.b	d0,d2		Extract red bits
	or.b	d2,RED
	move.l	RED,(Plane2)+
	lsl.l	#4,BLUE		Make room
	lsr.b	#4,d0		Move blue pack to low nybble
	or.b	d0,BLUE
	move.l	BLUE,(Plane1)+
	bra.s	CarryOn
*
* A quick hack to save time if even input long words are zero (8 black pixels)
*
blackOut	lsl.l	#8,GREEN
	lsl.l	#8,RED
	lsl.l	#8,BLUE
*
* If D3 is even we've got long words to write out
*
	btst	#0,d3
	bne.s	CarryOn		Go round again for the next word
*
	move.l	BLUE,(Plane1)+
	move.l	RED,(Plane2)+
	move.l	GREEN,(Plane3)+
	bra.s	CarryOn
*
* Continue to accumulate in registers as we have not yet got long values
*
noWrite	rol.l	#8,d1		Get blue and red byte to LSB
	move.b	d1,d0
	moveq	#15,d2		Mask for packed red bits
	move.b	0(table,d0.l),d0
	lsl.l	#4,RED		Make room
	and.b	d0,d2		Extract red bits
	lsl.l	#4,BLUE		Make room
	lsr.b	#4,d0		Move blue pack to low nybble
	or.b	d2,RED
	or.b	d0,BLUE
CarryOn	dbra	d3,next2
	move.l	(a7)+,a3		** fix? **
*
CacheIn	move.b	161(a6),d1 	Check for data cache
	cmp.b	#$30,d1		68030?
	bcs.s	done
*
	beq.s	enable030
*
* Revert to prior setting of DTT0 on 68040 or 68060
*
	dc.w	$4E7B,$7006	MOVEC D7,DTT0
	bra.s	done
*
* Revert to prior setting of CACR on 68030
*
enable030	dc.w	$4E7B,$7002	MOVEC D7,CACR
done	clr.b	busy+AceVars(a3)
	rts
*
* sub to change copper list to suit ace
*
ace_cop
	bset	#7,$1813E		disable screen blitter
	move.w	#$0000,COLOR00
	move.b	MC_STAT,d2
	btst	#3,d2
	bne.s	ace_cop8
*
ace_cop4
	move.w	#76,99974		DDFSTART
	move.w	#204,99978 	DDFSTOP
	move.w	#-4,99982		Modulo
	move.w	#-4,99986		Modulo
	move.w	#$A200,100002
	btst	#1,d2
	bne	blnkit
	move.w	#$0F00,COLOR01
	move.w	#$00F0,COLOR02
	move.w	#$0FFF,COLOR03
	rts
ace_cop8
	move.w	#72,99974		DDFSTART
	move.w	#200,99978 	DDFSTOP
	move.w	#-2,99982		Modulo
	move.w	#-2,99986		Modulo
	move.w	#$3200,100002
	btst	#1,d2
	bne.s	blnkit
	move.w	#$000F,COLOR01
	move.w	#$0F00,COLOR02
	move.w	#$0F0F,COLOR03
	move.w	#$00F0,COLOR04
	move.w	#$00FF,COLOR05
	move.w	#$0FF0,COLOR06
	move.w	#$0FFF,COLOR07
	rts
*
blnkit
	move.w	#$0000,COLOR01
	move.w	#$0000,COLOR02
	move.w	#$0000,COLOR03
	rts
*
* sub to change copper list to suit blitter
*
blt_cop
	bclr	#7,$1813E		enable screen blitter
	move.w	#76,99974		DDFSTART
	move.w	#204,99978 	DDFSTOP
	move.w	#-4,99982		Modulo
	move.w	#-4,99986		Modulo
	move.w	#$A200,100002
	move.w	#$0000,COLOR00
	move.w	#$0FFF,COLOR03
*
	move.b	MC_STAT,d2
	btst	#1,d2
	bne.s	blnkit
	btst	#3,d2
	bne.s	blt_cop8
*
blt_cop4
	move.w	#$0F00,COLOR01
	move.w	#$00F0,COLOR02
	rts
*
blt_cop8
	move.w	#$0708,COLOR01
	move.w	#$00FA,COLOR02
	rts
*
* ACE_ON asks is it already on? if so, return no error
*
mblit_on	bsr.s	find_pos2
	beq.s	it_worked		Already on
*
* Otherwise, allocate linkage area in common heap
*
	moveq	#varSize,d1
	moveq	#0,d2		Owned by SuperBASIC
	moveq	#24,d0		MT.ALCHP trap key
	trap	#1
	tst.l	d0		Did we get it?
	bne.s	oops
*
	bsr	ace_cop
	move.b	d2,prefix+smode(a0)
*
	lea.l	redraw,a2
	move.l	a2,vector(a0)	Provide code address
	move.l	d7,marker(a0)	Add signature
	move.b	#2,prefix+limit(a0)	Update every other field
	move.b	#1,prefix+count(a0)
	move.b	#0,prefix+missed(a0)	Not currently used...
	move.b	#0,prefix+busy(a0)
	move.b	#8,prefix+chunks(a0)	Scan 8 x 2K per update
	move.b	#0,prefix+soFar(a0)
	moveq	#28,d0		Set up MT.LPOLL trap
trap1_out	trap	#1
it_worked	moveq	#0,d0
	rts
*
find_pos2	bsr.s	find_pos		Extra call for trapping
oops	rts
*
* ACE_OFF is pretty simple too
*
mblit_off	bsr.s	find_pos2		Is our interrupt link
	bmi.s	it_worked		If not, return at once
*
* Remove interrupt server and deallocate linkage memory
*
	bsr	blt_cop
*
not_opened lea.l	-prefix(a4),a0
	moveq	#29,d0		MT.RPOLL trap key
	trap	#1
	moveq	#25,d0		MT.RECHP trap key
	bra.s	trap1_out
*
bad_param	moveq	#-15,d0		ERR.BP code
no_luck	rts
*
setrate	move.w	CA.GTINT,a2	Integer fetch vector
	jsr	(a2)
	bne.s	no_luck
*
	subq.w	#2,d3
	bne.s	bad_param
*
* Check first parameter, number of 2K chunks to update each time
*
	move.w	0(a1,a6.l),d4
	ble.s	bad_param
*
	cmp.w	#16,d4
	bhi.s	bad_param
*
* Check second parameter, delay between updates in fields
*
	tst.b	2(a1,a6.l)
	bne.s	bad_param		Reject if <0 or >255
*
	move.b	3(a1,a6.l),d3
	beq.s	bad_param		Trap zero (aka 256!)
*
	bsr.s	find_pos
*
	move.b	d3,limit(a4)
	move.b	d4,chunks(a4)
	move.b	#1,count(a4)	Respond next field
	bra.s	it_worked
*
* ACE_STEP% returns the current setting of CHUNKS to SuperBASIC
*
step	bsr.s	find_pos
	moveq	#0,d4
	move.b	chunks(a4),d4
	bra.s	chk_integer
*
* ACE_RATE% returns the current setting of LIMIT to SuperBASIC
*
rate	bsr.s	find_pos
	moveq	#0,d4
	move.b	limit(a4),d4
*
* Return the integer in D4.W to SuperBASIC via the RI stack
*
chk_integer moveq	#2,d1		Number of bytes needed
	move.w	BV.CHRIX,a2	Find the BV.CHRIX vector
	jsr	(a2)		Allocate RI space
	subq.l	#2,$58(a6) 	Update BV.RIP
ret_integer move.l	$58(a6),a1 	Get BV.RIP
	move.w	d4,0(a1,a6.l)	Stack the result
	moveq	#3,d4		Type = 16 bit Integer
no_error	moveq	#0,d0		No run-time error
	rts
*
* FIND_POS points A4 at ACE variables in the interrupt list
*
* LONG link address
* LONG code address
* LONG "ACE3" marker
* BYTE count
* BYTE limit
*
* Result in A4, "ACEn" marker in D7; job ID in D1, uses D0
* Returns NOT FOUND to prior caller if the server is absent
*
find_pos	moveq	#0,d0		MT.INF
	trap	#1
	move.l	#'ACE3',d7
	lea.l	60(a0),a4		Locate polled list
find_loop	move.l	(a4),d0
	beq.s	not_found		Report to prior caller
*
	movea.l	d0,a4
	cmp.l	marker(a4),d7	Check signature
	bne.s	find_loop
*
found_it	lea.l	prefix(a4),a4	Point at the data
	rts
*
not_found	addq.l	#4,a7		Discard return address
	moveq	#-7,d0		ERR.NF report code
	rts
*
* Mode 8 byte conversion table, generated by ACE3_BAS
*
* Index:  0..255	A B C D E F G H
* Result: 0..255	B D F H A C E G
*
byteTable	dc.b	0,16,1,17,32,48,33,49
	dc.b	2,18,3,19,34,50,35,51
	dc.b	64,80,65,81,96,112,97,113
	dc.b	66,82,67,83,98,114,99,115
	dc.b	4,20,5,21,36,52,37,53
	dc.b	6,22,7,23,38,54,39,55
	dc.b	68,84,69,85,100,116,101,117
	dc.b	70,86,71,87,102,118,103,119
	dc.b	128,144,129,145,160,176,161,177
	dc.b	130,146,131,147,162,178,163,179
	dc.b	192,208,193,209,224,240,225,241
	dc.b	194,210,195,211,226,242,227,243
	dc.b	132,148,133,149,164,180,165,181
	dc.b	134,150,135,151,166,182,167,183
	dc.b	196,212,197,213,228,244,229,245
	dc.b	198,214,199,215,230,246,231,247
	dc.b	8,24,9,25,40,56,41,57
	dc.b	10,26,11,27,42,58,43,59
	dc.b	72,88,73,89,104,120,105,121
	dc.b	74,90,75,91,106,122,107,123
	dc.b	12,28,13,29,44,60,45,61
	dc.b	14,30,15,31,46,62,47,63
	dc.b	76,92,77,93,108,124,109,125
	dc.b	78,94,79,95,110,126,111,127
	dc.b	136,152,137,153,168,184,169,185
	dc.b	138,154,139,155,170,186,171,187
	dc.b	200,216,201,217,232,248,233,249
	dc.b	202,218,203,219,234,250,235,251
	dc.b	140,156,141,157,172,188,173,189
	dc.b	142,158,143,159,174,190,175,191
	dc.b	204,220,205,221,236,252,237,253
	dc.b	206,222,207,223,238,254,239,255
*
* SuperBASIC extension details for the BP.INIT vector
*
define	dc.w	3+1		Three procedures
	dc.w	mblit_off-*
	dc.b	7,'ACE_OFF'
	dc.w	mblit_on-*
	dc.b	6,'ACE_ON'
	dc.w	setrate-*
	dc.b	12,'ACE_PRIORITY'	Long name hence +1
	dc.w	0
*
	dc.w	2		Two functions
	dc.w	rate-*
	dc.b	9,'ACE_RATE%'
	dc.w	step-*
	dc.b	9,'ACE_STEP%'
	dc.w	0
*
	end
