
VERSIONSTRING equ "Egfx v2.01"

!
! Filename:     egfx.asm
! Module name:  Egfx
! Description:  enhanced-mode video driver
!
! Commands:
!   string QueryVersion () - retrieve version string
!   SetMode (byte numColors, byte refreshRate, byte bufferRezID) - turn
;                                                  egfx mode on or off
!   RestoreMode () - turn egfx mode on without initialization
!   CheckBufferSpace (byte width, byte height) - check for enough memory
!   byte CreateBuffer (byte width, byte height) - create gfx buffer
!   special ScriptInline (byte[] script) - execute gfx script
!   ScriptResource (byte rezID) - execute gfx script stored as resource
!   StartAutoScript (byte rezID, word speed) - periodically execute script
!   StopAutoScript () - turn off auto-script
!
! IPC:
!   IPC_Gfx_SetModeOff
!
! Timers:
!   TIMER_Egfx - timer for auto-scripting
!
! Hooks:
!   HOOK_GFX - graphics hook
!
! Comments:
!   Enhanced-mode video is a significant improvement over standard mode.
!   It allows an arbitrarily sized video buffer and number of colors,
!   and provides functions for drawing objects in a variety of drawing
!   modes.  It also supports "scripts" for quickly excecuting a number
!   of graphics commands.
!
! History:
!   bv 9/21/98	- initial revision
!   bv 7/19/99	- finally converted to cokeOS v3 module,
!		  added multiple buffers, AUTOMOVE_SCRIPT, other stuff...
!   bv 7/28/99	- added RestoreMode command

	include <coke/coke.inc>
	include <coke/egfx.inc>

; constants

HEADER_xsize16		equ 0
HEADER_xsize		equ 1
HEADER_ysize16		equ 2
HEADER_ysize		equ 3
HEADER_bufferptr	equ 4
HEADER_bufferptrlo	equ 5
HEADER_bufsize		equ 6
HEADER_bufsizelo	equ 7
HEADER_bufend		equ 8
HEADER_bufendlo		equ 9
HEADER_CursorX0		equ 10
HEADER_CursorX1		equ 11
HEADER_CursorX2		equ 12
HEADER_CursorX3		equ 13
HEADER_CursorY0		equ 14
HEADER_CursorY1		equ 15
HEADER_CursorY2		equ 16
HEADER_CursorY3		equ 17
HEADERSIZE		equ 18

; direct page variables

egfx_spt		equ DIRPAGE_Egfx
egfx_basecount		equ DIRPAGE_Egfx+60
egfx_column		equ DIRPAGE_Egfx+61
egfx_scanline		equ DIRPAGE_Egfx+62
egfx_numcolors		equ DIRPAGE_Egfx+64
egfx_baseline		equ DIRPAGE_Egfx+65

; the following do not NEED to go in the direct page, but I allocated
; the space, so I might as well use it.

egfx_xcursor		equ DIRPAGE_Egfx+66
egfx_ycursor		equ DIRPAGE_Egfx+67
egfx_cursorptr		equ DIRPAGE_Egfx+68
egfx_bufferptr		equ DIRPAGE_Egfx+70
egfx_bufsize		equ DIRPAGE_Egfx+72
egfx_bufend		equ DIRPAGE_Egfx+74
egfx_xsize16		equ DIRPAGE_Egfx+76
egfx_xsize		equ DIRPAGE_Egfx+77
egfx_ysize16		equ DIRPAGE_Egfx+78
egfx_ysize		equ DIRPAGE_Egfx+79


; EntryPoint

EntryPoint	subroutine
	cmpa	#IPC_Coke_Hello
	beq	.init
	cmpa	#IPC_Coke_Goodbye
	beq	.free
	cmpa	#IPC_Gfx_SetModeOff
	beq	.off
	sec
	rts
.init
	ldx	#CommandTable		; install command table
	jsr	Coke_InstallCommandTable
	ldaa	#ID_Egfx		; register IPC
	ldx	#EntryPoint
	jsr	Coke_RegisterIPC
	jsr	InitEgfx		; init memory stuff
	clc
	rts		
.free
	ldx	#CommandTable		; remove commands
	jsr	Coke_RemoveCommandTable
	ldaa	#TIMER_Egfx		; remove timer
	jsr	Coke_RemoveTimer
	ldx	#RefreshHookEnd		; remove the hook
	ldaa	#HOOK_GFX
	jsr	Coke_RemoveHook
	ldaa	#ID_Doodad		; remove IPC
	jsr	Coke_UnregisterIPC
	clc
	rts
.off
	psha
	pshx

	tst	egfx_active		; are we active?
	beq	.notactive
	ldaa	#HOOK_GFX		; if so, remove the hook
	ldx	#RefreshHookEnd
	jsr	Coke_RemoveHook
	clr 	egfx_active		; ain't active no more, eh?
.notactive
	pulx
	pula
	clc
	rts


; InitEgfx initializes stuff... it does not start up the video mode.

InitEgfx subroutine
	ldaa	#OPCODE_JMPEXT		; prepare the vectors
	staa	CurrentDmodeVec		; pixel draw vector
	staa	AutoMoveVec		; automove vector
	staa	PanVec			; pan vector
	clr	egfx_ysize16		; clear hibyte of ysize
	clr	egfx_xsize16		; clear hibyte of xsize
	clr	egfx_active		; not active
	clr	egfx_modeset		; never set mode
	rts


; Command_SetMode (byte numColors, byte refreshRate, byte rezID)

Command_SetMode subroutine
	jsr	Coke_GetChar		; get numColors
	staa	egfx_stash1
	jsr	Coke_GetChar		; get refreshRate
	staa	egfx_stash2
	jsr	Coke_GetChar		; get rezID
	staa	egfx_stash3

	tst	egfx_stash1		; is numcolors zero?
	bne	.turnon			; if not, turn on the mode
					; otherwise, turn off
	tst	egfx_active		; are we on?
	beq	.offdone		; if not, do nothing 
	ldaa	#HOOK_GFX		; if turning off, remove hook
	ldx	#RefreshHookEnd
	jsr	Coke_RemoveHook
	clr	egfx_active		; clear active flag
.offdone
	ldaa	#COKE_ACK		; all good
	rts

.turnon
	ldab	egfx_stash3		; make sure the resource ID is valid
	jsr	Resource_GetResource
	bcs	.badresource

	ldaa	#IPC_Gfx_SetModeOff	; turn off all other graphics modes
	ldab	#IPCFLAG_Broadcast	; send to all
	jsr	Coke_CallIPC

	ldaa	egfx_stash1		; get numColors
	deca
	bne	.goodcolors		; colors=1 is not allowed
	inca				; so make it 2
.goodcolors 
	staa	egfx_numcolors		; numcolors= number of colors - 1
	ldab	egfx_stash2		; get refresh rate
	mul				; multiply by numcolors
	xgdx				; put into x
	ldd	#8333			; get divisor
	idiv				; quotient in x, remainder in d
	stx	gfxdelay		; that's the new delay value

	ldab	egfx_stash3		; get the buffer rezID
	jsr	SetBuffer		; set the buffer
	ldaa	#$FF			; set initial baseline to zero
	staa	egfx_baseline
	ldaa	#1			; reset mux counter
	staa	egfx_basecount
	ldaa	#DMODE_COPY		; set the drawing mode to copy
	jsr	SetDrawingMode	
	clra				; put zero in a and b
	clrb		
	staa	egfx_delta		; set delta to zero
	staa	egfx_column		; reset the mux column
	staa	egfx_color		; set inital color to zero (baseline)
	jsr	CursorSet		; put the cursor in the top left
	jsr	CursorStore		; put it in the store value too
	jsr	ScreenSet		; set the screen to the top left
	jsr	SetAutoMove		; turn off automove
	jsr	SetPan			; turn off panning

	ldaa	#1	
	staa	egfx_active		; say that we're on
	staa	egfx_modeset		; say that we set the mode
	ldaa	#HOOK_GFX		; install our hook function
	ldx	#RefreshHookEnd
	jsr	Coke_InstallHook

	ldaa	#COKE_ACK
	rts

.badresource
	ldaa	#COKE_FAIL
	ldab	#ERROR_Coke_BadResourceID
	rts


; Command_RestoreMode ()

Command_RestoreMode subroutine
	ldaa	egfx_modeset		; was the mode ever set?
	beq	.badset			; if not, can't do this
	ldaa	egfx_active		; are we already active?
	bne	.done			; if so, we have nothing to do.

	ldaa	#IPC_Gfx_SetModeOff	; turn off all other graphics modes
	ldab	#IPCFLAG_Broadcast	; send to all
	jsr	Coke_CallIPC

	ldaa	#1	
	staa	egfx_active		; say that we're on
	ldaa	#HOOK_GFX		; install our hook function
	ldx	#RefreshHookEnd
	jsr	Coke_InstallHook
.done
	ldaa	#COKE_ACK
	rts
.badset
	ldab	#ERROR_Egfx_ModeNeverSet
	ldaa	#COKE_FAIL
	rts	


; Command_CheckBufferSpace (byte width, byte height)

Command_CheckBufferSpace subroutine
	jsr	Coke_GetChar		; get width
	tab				; put in b
	jsr	Coke_GetChar		; get height
	mul				; put 'em together
	addd	#HEADERSIZE		; add header size
	bcs	.bad
	std	egfx_stash1		; store it
	jsr	Resource_GetFreeSpace	; get free memory in d
	cpd	egfx_stash1		; compare it
	bhs	.bad
	ldaa	#COKE_ACK
	rts
.bad
	ldaa	#COKE_FAIL
	ldab	#ERROR_Coke_NotEnoughMemory
	rts


; Command_CreateBuffer (byte width, byte height)

Command_CreateBuffer subroutine
	jsr	Coke_GetChar		; get width
	staa	egfx_stash1
	tab				; put in b
	jsr	Coke_GetChar		; get height
	staa	egfx_stash2
	mul				; put 'em together
	addd	#HEADERSIZE		; add header size
	bcs	.bad
	jsr	Resource_CreateResource	; allocate some memory (ID in b)
	bcs	.bad
	stab	egfx_stash3		; store rez ID
	jsr	Resource_GetResource	; get resource pointer in x

	ldaa	egfx_stash1		; get width
	staa	HEADER_xsize,x		; store it in buffer header
	ldab	egfx_stash2		; get height
	stab	HEADER_ysize,x		; store it in buffer header
	mul				; put product in d
	std	HEADER_bufsize,x	; store size
	stx	egfx_stash1		; store header pointer
	ldd	egfx_stash1		; put it into d
	addd	#HEADERSIZE		; get pointer to actual buffer
	std	HEADER_bufferptr,x	; store that
	addd	HEADER_bufsize,x	; add the buffer size
	std	HEADER_bufend,x		; store the end of the buffer
	clra				; clear out other stuff
	staa	HEADER_xsize16,x
	staa	HEADER_ysize16,x
	staa	HEADER_CursorX0,x
	staa	HEADER_CursorX1,x
	staa	HEADER_CursorX2,x
	staa	HEADER_CursorX3,x
	staa	HEADER_CursorY0,x
	staa	HEADER_CursorY1,x
	staa	HEADER_CursorY2,x
	staa	HEADER_CursorY3,x

	ldaa	#COKE_QUERY		; return a query response
	ldab	#1			; one byte long
	ldx	#egfx_stash3		; rez ID
	rts
.bad
	ldaa	#COKE_FAIL
	ldab	#ERROR_Coke_NotEnoughMemory
	rts


; Command_StartAutoScript (byte rezID, word speed)

Command_StartAutoScript subroutine
	jsr	Coke_GetChar		; get rezID
	staa	egfx_stash1
	jsr	Coke_GetChar		; get lobyte speed
	tab
	jsr	Coke_GetChar		; get hibyte speed
	xgdy				; put speed in y

	ldab	egfx_stash1		; use rezID as timer parameter
	jsr	Resource_GetResource	; but first check its valid
	bcs	.bad
	ldaa	#TIMER_Egfx		; timer ID
	ldx	#TimerAutoScript	; timer function pointer
	jsr	Coke_SetTimer		; set that timer

	ldaa	#COKE_ACK		; we done
	rts
.bad
	ldaa	#COKE_FAIL
	ldab	#ERROR_Coke_BadResourceID
	rts


; Command_StopAutoScript ()

Command_StopAutoScript subroutine
	ldaa	#TIMER_Egfx		; timer ID
	jsr	Coke_RemoveTimer	; remove the timer
	ldaa	#COKE_ACK
	rts


; Command_ScriptResource (byte rezID)

Command_ScriptResource subroutine
	jsr	Coke_GetChar		; get rezID
	tab				; put in b
	jsr	Resource_GetResource	; get the resource pointer
	bcs	.bad
	jsr	ExecuteScript		; do it
	ldaa	#COKE_ACK
	rts
.bad
	ldaa	#COKE_FAIL
	ldab	#ERROR_Coke_BadResourceID
	rts


; Command_ScriptInline (byte[] script)

Command_ScriptInline subroutine
	ldx	#0
	jsr	ExecuteScript
	ldaa	#COKE_ACK
	rts


; TimerAutoScript is the timer function that does the autoscript.
; It accepts the script id as the parameter in a.

TimerAutoScript subroutine
	pshb
	tab				; put resource ID in b
	jsr	ScriptResource		; do the script
	pulb
	rts



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 
;  scripting functions


ExecuteScript subroutine
	psha
	pshb
	pshx
	pshy

	ldy	egfx_scriptpointer	; get the previous script pointer
	pshy				; and stash it, in case this is nested
	stx	egfx_scriptpointer	; store new script pointer

.scriptloop
	jsr	GetNextChar		; get next command
	tsta				; is it zero?
	beq	.done			; if so, we're done here

	tab				; put it in b
	pshb				; stash it for later
	andb	#$F0			; only want high nibble
	lsrb				; get high nibble times two in b
	lsrb
	lsrb
	ldx	#FunctionTable		; put pointer to first table in x
	abx				; add offset to table pointer

	ldd	0,x			; get pointer to second table
	xgdx				; put it in x

	pulb				; get back command char
	andb	#$0F			; only want low nibble
	cmpb	0,x			; compare to num of functions in table
	bhs	.done			; if function doesn't exist, quit now!

	aslb				; put lo nibble times two in b
	incb				; plus one, for the function count
	abx				; add it to table pointer
	ldd	0,x			; get function pointer
	xgdx				; put it in x

	jsr	0,x			; do the function
	bra	.scriptloop		; do some more!
	
.done
	pulx
	stx	egfx_scriptpointer	; restore the old script pointer

	puly
	pulx
	pulb
	pula
	rts	



; GetNextChar gets the next character in the script.  If scriptpointer
; is not null, it gets it from that and updates the pointer.  If scriptpointer
; is null, it calls Coke_GetChar.
; char is returned in a.

GetNextChar subroutine
	pshx
	ldx	egfx_scriptpointer	; get current script pointer
	beq	.nullptr		; if the ptr is 0, do the serial thang
	ldaa	0,x			; otherwise, get the char
	inx				; update the pointer
	stx	egfx_scriptpointer	; store it
	pulx
	rts				; and return
.nullptr
	jsr	Coke_GetChar		; get something from the serial port
	pulx
	rts


; The Param functions are used to set up the proper registers for the
; Egfx call, using the parameters in the script.

Param_None subroutine		; no parameters
	rts

Param_A subroutine		; one parameter in the a register
	jmp	GetNextChar

Param_B subroutine		; one parameter in the b register
	jsr	GetNextChar
	tab
	rts

Param_AB subroutine		; first parameter in a, second in b
	jsr	GetNextChar
	psha
	jsr	GetNextChar
	tab
	pula
	rts

Param_XX subroutine		; two byte parameter in x, low byte first
	jsr	GetNextChar
	tab
	jsr	GetNextChar
	xgdx
	rts

Param_XAB subroutine		; one byte parameter in x, then a, then b
	jsr	GetNextChar
	tab
	clra
	xgdx
	jsr	GetNextChar
	psha
	jsr	GetNextChar
	tab
	pula
	rts

Param_XXAB subroutine		; two bytes in x, then one in a and one in b
	jsr	GetNextChar
	tab
	jsr	GetNextChar
	xgdx
	jsr	GetNextChar
	psha
	jsr	GetNextChar
	tab
	pula
	rts	

Param_XYAB subroutine		; byte in x, y, a, and b
	jsr	GetNextChar
	tab
	clra
	xgdx
	jsr	GetNextChar
	tab
	clra
	xgdy
	jsr	GetNextChar
	psha
	jsr	GetNextChar
	tab
	pula
	rts	


; FunctionTable is a table to the graphics functions.  It actually
; is a double table.  Use the high nibble of the code to look up a pointer
; to another table, and then use the low nibble to look up the actual function
; within that table.  The first entry of each second table is a byte that
; indicates how many functions are in that table, so we can terminate
; on illegal function codes. 

FunctionTable subroutine
	dc.w	.dummy		; $0
	dc.w	.script		; $1
	dc.w	.settings	; $2
	dc.w	.cursor		; $3
	dc.w	.drawing	; $4
	dc.w	.font		; $5
	dc.w	.buffer		; $6
	dc.w	.screen		; $7
	dc.w	.dummy		; $8
	dc.w	.dummy		; $9
	dc.w	.dummy		; $A
	dc.w	.dummy		; $B
	dc.w	.dummy		; $C
	dc.w	.dummy		; $D
	dc.w	.dummy		; $E
	dc.w	.dummy		; $F

.dummy
	dc.b	0
.script
	dc.b	1
	dc.w	Egfx_ScriptResource
.settings
	dc.b	7
	dc.w    Egfx_SetBuffer
	dc.w	Egfx_SetColor
	dc.w	Egfx_SetDelta
	dc.w	Egfx_SetBaseline
	dc.w	Egfx_SetDrawingMode
	dc.w	Egfx_SetAutoMove
	dc.w	Egfx_SetPan
.cursor
	dc.b	9
	dc.w	Egfx_CursorSet
	dc.w	Egfx_Xset
	dc.w	Egfx_Yset
	dc.w	Egfx_Xjump
	dc.w	Egfx_Yjump
	dc.w	Egfx_CursorStore
	dc.w	Egfx_CursorStoreNum
	dc.w	Egfx_CursorRestore
	dc.w	Egfx_CursorRestoreNum
.drawing
	dc.b	6
	dc.w	Egfx_Skip
	dc.w	Egfx_Dot
	dc.w	Egfx_Xline
	dc.w	Egfx_Yline
	dc.w	Egfx_Frame
	dc.w	Egfx_Rect
.font
	dc.b	0
;	dc.w	Egfx_UseFontResource
;	dc.w	Egfx_Char
;	dc.w	Egfx_CharLine

.buffer
	dc.b	1
	dc.w	Egfx_Wipe
;	dc.w	Egfx_Dump
;	dc.w	Egfx_DumpResource
;	dc.w	Egfx_DumpRect
;	dc.w	Egfx_DumpRectResource
;	dc.w	Egfx_MoveRect
.screen
	dc.b	5
	dc.w	Egfx_ScreenSet
	dc.w	Egfx_RangeSet
	dc.w	Egfx_Pan
	dc.w	Egfx_PanThis
	dc.w	Egfx_PanRange


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;  graphics functions

	include	"egfx_core.asm"


; version stuff

	_versionstuff

; command table

CommandTable subroutine
	dc.b	CMD_Egfx_QueryVersion
	dc.w	Command_QueryVersion
	dc.b	CMD_Egfx_SetMode
	dc.w	Command_SetMode
	dc.b	CMD_Egfx_RestoreMode
	dc.w	Command_RestoreMode
	dc.b	CMD_Egfx_CreateBuffer
	dc.w	Command_CreateBuffer
	dc.b	CMD_Egfx_ScriptInline
	dc.w	Command_ScriptInline
	dc.b	CMD_Egfx_ScriptResource
	dc.w	Command_ScriptResource
	dc.b	CMD_Egfx_StartAutoScript
	dc.w	Command_StartAutoScript
	dc.b	CMD_Egfx_StopAutoScript
	dc.w	Command_StopAutoScript

	dc.b	0


; data segment

egfx_color		ds 1
egfx_delta		ds 1
egfx_scriptpointer	ds 2
egfx_active		ds 1
egfx_headerptr		ds 2
egfx_automovescript	ds 1
egfx_hpt		ds 60
egfx_stash1		ds 1
egfx_stash2		ds 1
egfx_stash3		ds 1
egfx_modeset		ds 1

CurrentDmodeVec		jmp DmodeNone
AutoMoveVec		jmp MoveNone
PanVec			jmp PanNone
