383 lines
14 KiB
NASM
383 lines
14 KiB
NASM
|
;--------------------------------------------------------------------
|
||
|
; Interrupt-driven Blitter Queue
|
||
|
;
|
||
|
; Blitter queues may not be an optimal solution but they add a tool
|
||
|
; to the various ways of intertwining CPU and blitter activity.
|
||
|
;
|
||
|
; As there may be a rather large penalty for interrupt context
|
||
|
; switches (less so if FW_A5_A6_UNTOUCHED is activated),
|
||
|
; this implementation tries to reduce the overhead to a minimum.
|
||
|
;
|
||
|
; There are four basic ways to use the Blitter Queue:
|
||
|
;
|
||
|
; 1) Create and process blits in the same frame:
|
||
|
; - [AddToBlitterQueue]*
|
||
|
; - TerminateBlitterQueue
|
||
|
; - TriggerBlitterQueue
|
||
|
; - Do more CPU based stuff
|
||
|
; - JoinBlitterQueue
|
||
|
; - VSyncWithTask
|
||
|
;
|
||
|
; 2) Create stuff for the next frame, no queue double buffering:
|
||
|
; - TriggerBlitterQueue
|
||
|
; - Do CPU based stuff
|
||
|
; - JoinBlitterQueue
|
||
|
; - [AddToBlitterQueue]*
|
||
|
; - TerminateBlitterQueue
|
||
|
; - VSyncWithTask
|
||
|
;
|
||
|
; 3) Create stuff for the next frame, double buffered queue:
|
||
|
; - TriggerBlitterQueue (the last one)
|
||
|
; - [AddToBlitterQueue]* (the current one)
|
||
|
; - TerminateBlitterQueue (the current one)
|
||
|
; - Do more CPU based stuff
|
||
|
; - JoinBlitterQueue
|
||
|
; - VSyncWithTask
|
||
|
;
|
||
|
; 4) Create and run stuff in parallel with the current frame:
|
||
|
; - [AppendToRunningBlitterQueue, do more CPU based stuff]*
|
||
|
; - TerminateBlitterQueue
|
||
|
; - JoinBlitterQueue
|
||
|
; - VSyncWithTask
|
||
|
;
|
||
|
; 5) Create and run stuff in parallel with the current frame (alternate):
|
||
|
; - [AddToBlitterQueue]* (makes sense if there is not much to prepare)
|
||
|
; - TriggerBlitterQueue
|
||
|
; - [AppendToRunningBlitterQueue, do more CPU based stuff]*
|
||
|
; - TerminateBlitterQueue
|
||
|
; - JoinBlitterQueue
|
||
|
; - VSyncWithTask
|
||
|
;
|
||
|
; Each blit is stored in a BlitterQueueNode with consists of a
|
||
|
; linking pointer (singly linked list) and the pointer to routine
|
||
|
; for setting up the next blit.
|
||
|
; Optional parameters should follow after this field and are
|
||
|
; available to the callee via a0.
|
||
|
;
|
||
|
; When called, a5/a6 are filled with standard values ($dff000 and
|
||
|
; framework base address). a0 points to bq_Data,
|
||
|
; a1 holds the address of the routine stored in bq_Routine.
|
||
|
;
|
||
|
; Your routine may trash a0/a1/d0, all other registers must be
|
||
|
; preserved! The Z flag at exit determines whether the blit should
|
||
|
; be executed synchronously (blitter hogging will be activated).
|
||
|
; This makes sense if the blit is very short and a context switch
|
||
|
; is not reasonable. Except for a blit of 1024x1024 pixels,
|
||
|
; setting bltsize as a last instruction before RTS will clear Z
|
||
|
; and make the routine asynchronous. If you want it synchroneously,
|
||
|
; the last instruction before RTS could be a moveq.l #0,d0.
|
||
|
; In this case you must ensure the blitter is NOT running when
|
||
|
; your routine exits (e.g. with a blitter wait).
|
||
|
;
|
||
|
; Usually your routine MUST start the blitter -- the only exception
|
||
|
; is when setting the Z condition code at the exit of the routine.
|
||
|
; This can be used e.g. for common setup (like for line drawing)
|
||
|
; to reduce the amount of work done in a series of blits.
|
||
|
; You can also issue a BLTWAIT as the last "instruction" of your
|
||
|
; routine to make it a synchronous blit (no context switch).
|
||
|
;
|
||
|
; If you are absolutely sure that no routine of your main code loop
|
||
|
; will modify a5 or a6 while the blitter queue is running, you may
|
||
|
; turn on the FW_A5_A6_UNTOUCHED switch, which will reduce the
|
||
|
; context switch time even further by not saving/loading/restoring
|
||
|
; these two registers during the interrupt.
|
||
|
; (Otherwise, disabling interrupts for the periods where you need to
|
||
|
; modify a5/a6 may be an option, if the routines don't take too long)
|
||
|
;
|
||
|
; Amiga effects are supposed to be one-frame :) Your blitter queue
|
||
|
; must start its last blit before the vertical blank. Otherwise,
|
||
|
; havoc will happen.
|
||
|
; If you plan to use blitter queues that span several frames,
|
||
|
; please be aware that you need to call SetBlitterQueueMultiFrame,
|
||
|
; which is slightly slower because it will check which interrupt
|
||
|
; occurred. Otherwise please call SetBlitterQueueSingleFrame once.
|
||
|
;
|
||
|
; Blitter queues may NOT be combined with Blitter tasks for obvious
|
||
|
; reasons, but having background tasks and calling VSyncWithTask
|
||
|
; is perfectly fine, IF you make sure that you have joined the queue
|
||
|
; with JoinBlitterQueue before calling VSyncWithTask.
|
||
|
;
|
||
|
; To start a new blit, it takes around 3/4 of a raster line in ideal
|
||
|
; conditions. Calculate with an overhead of one rasterline per blit.
|
||
|
;
|
||
|
; Advanced use:
|
||
|
; You can modify fw_BlitterQueueReadPtr(a6) within a routine for
|
||
|
; branching and looping etc. by changing the next queue node to be
|
||
|
; executed. If you want to terminate the queue, don't set it to
|
||
|
; zero directly, but point it to a node with bq_Next set to zero
|
||
|
; and an empty (not null!) routine. Note that this only works
|
||
|
; reliably for blitter queues that are not extended while running!
|
||
|
|
||
|
;--------------------------------------------------------------------
|
||
|
; Adds a node to blitter queue
|
||
|
;
|
||
|
; This call will not start (trigger) the queue!
|
||
|
; If the queue is already being processed, it should append the
|
||
|
; node for later execution -- however, due to race conditions
|
||
|
; it is better to use AppendToBlitterQueue, which will also
|
||
|
; again trigger the queue if it ran out of blits.
|
||
|
;
|
||
|
; Each BlitterQueueNode should be entered with an empty bq_Next
|
||
|
; field (bq_Next may be random if at least the LAST node has it
|
||
|
; set to zero and the queue is not yet running!) and
|
||
|
; a bq_Routine pointer to a routine to be called.
|
||
|
;
|
||
|
; Note: there is also a macro ADD_TO_BLITTER_QUEUE to inline the
|
||
|
; adding for even more speed, but in this case the queue
|
||
|
; must not be empty!
|
||
|
;
|
||
|
; In: a0 = BlitterQueueNode
|
||
|
; Trashes: a1
|
||
|
;
|
||
|
fw_AddToBlitterQueue:
|
||
|
lea fw_BlitterQueueWritePtr(a6),a1
|
||
|
tst.l (a1)
|
||
|
beq.s .first
|
||
|
move.l (a1),a1
|
||
|
PUTMSG 50,<"Add BQ Next %p after %p">,a0,a1
|
||
|
move.l a0,(a1) ; bq_Next
|
||
|
move.l a0,fw_BlitterQueueWritePtr(a6)
|
||
|
rts
|
||
|
.first PUTMSG 50,<"Add BQ Init %p First">,a0
|
||
|
move.l a0,(a1)+ ; fw_BlitterQueueWritePtr
|
||
|
move.l a0,(a1)+ ; fw_BlitterQueueHeadPtr
|
||
|
rts
|
||
|
|
||
|
;--------------------------------------------------------------------
|
||
|
; Appends a blitter node to a previously triggered queue
|
||
|
;
|
||
|
; Given a running blitter queue, this call will attempt to add a node
|
||
|
; to it.
|
||
|
; If the queue has run out of stuff to do, it will retrigger the
|
||
|
; queue automatically.
|
||
|
; If the last blit was still running, it will resume interrupt driven
|
||
|
; blitting (in fact, this also works with running blits that were not
|
||
|
; started from a blitter queue!).
|
||
|
;
|
||
|
; This call is slightly more complex than AddToBlitterQueue, so
|
||
|
; choose wisely.
|
||
|
|
||
|
; In: a0 = BlitterQueueNode
|
||
|
; Trashes: a0/a1/d0
|
||
|
;
|
||
|
fw_AppendToRunningBlitterQueue:
|
||
|
PUTMSG 50,<"Append Head/Write/Read %p %p %p">,fw_BlitterQueueHeadPtr(a6),fw_BlitterQueueWritePtr(a6),fw_BlitterQueueReadPtr(a6)
|
||
|
pea .restints(pc)
|
||
|
DISABLE_INTS
|
||
|
tst.l fw_BlitterQueueHeadPtr(a6)
|
||
|
bne.s fw_AddToBlitterQueue
|
||
|
tst.l fw_BlitterQueueReadPtr(a6)
|
||
|
bne.s fw_AddToBlitterQueue
|
||
|
bsr.s fw_AddToBlitterQueue
|
||
|
btst #DMAB_BLTDONE-8,dmaconr(a5)
|
||
|
beq.s fw_TriggerBlitterQueue
|
||
|
PUTMSG 50,<"Install IRQ %p %p %p">,fw_BlitterQueueHeadPtr(a6),fw_BlitterQueueWritePtr(a6),fw_BlitterQueueReadPtr(a6)
|
||
|
move.w #INTF_BLIT,intreq(a5) ; clear pending blitter int
|
||
|
|
||
|
move.l fw_VBR(a6),a1
|
||
|
move.l fw_BlitterQueueIRQ(a6),$6c(a1)
|
||
|
move.w #INTF_SETCLR|INTF_BLIT,intena(a5) ; enable blitter int
|
||
|
lea fw_BlitterQueueHeadPtr(a6),a1
|
||
|
move.l (a1)+,(a1) ; fw_BlitterQueueHeadPtr -> fw_BlitterQueueReadPtr
|
||
|
clr.l -(a1) ; fw_BlitterQueueHeadPtr
|
||
|
addq.w #4,sp
|
||
|
btst #DMAB_BLTDONE-8,dmaconr(a5)
|
||
|
bne.s .restints
|
||
|
move.w #INTF_SETCLR|INTF_BLIT,intreq(a5) ; set pending blitter int to avoid race condition
|
||
|
.restints
|
||
|
ENABLE_INTS
|
||
|
rts
|
||
|
|
||
|
;--------------------------------------------------------------------
|
||
|
; Terminates the blitter queue list
|
||
|
;
|
||
|
; Makes sure that the adding new nodes to will not contribute to a
|
||
|
; running blitter queue. Must be called before starting a new queue.
|
||
|
;
|
||
|
fw_TerminateBlitterQueue:
|
||
|
clr.l fw_BlitterQueueWritePtr(a6)
|
||
|
rts
|
||
|
|
||
|
;--------------------------------------------------------------------
|
||
|
; Setup the blitter queue for multiple frames
|
||
|
;
|
||
|
; Selects the slightly slower interrupt routine that allows spilling
|
||
|
; of interrupt driven blits across a VBL.
|
||
|
;
|
||
|
; Either this function or SetBlitterQueueSingleFrame must be called
|
||
|
; at least once!
|
||
|
;
|
||
|
; NOTE: Even if you have a multi-frame blitter queue set up, you may
|
||
|
; NOT use VSyncWithTask unless you made sure the queue has been
|
||
|
; processed by calling JoinBlitterQueue. You still can call VSync
|
||
|
; of course.
|
||
|
;
|
||
|
; Trashes: a0
|
||
|
;
|
||
|
fw_SetBlitterQueueMultiFrame:
|
||
|
lea fw_blitter_queue_multiframe_irq(pc),a0
|
||
|
move.l a0,fw_BlitterQueueIRQ(a6)
|
||
|
rts
|
||
|
|
||
|
;--------------------------------------------------------------------
|
||
|
; Setup the blitter queue for multiple frames
|
||
|
;
|
||
|
; Selects the slightly faster interrupt routine that does not care
|
||
|
; about a vertical blank interrupt and may crash or do fancy stuff
|
||
|
; if the blit before the last spills into the next frame.
|
||
|
;
|
||
|
; Either this function or SetBlitterQueueMultiFrame must be called
|
||
|
; at least once!
|
||
|
;
|
||
|
; Trashes: a0
|
||
|
;
|
||
|
fw_SetBlitterQueueSingleFrame:
|
||
|
lea fw_blitter_queue_irq(pc),a0
|
||
|
move.l a0,fw_BlitterQueueIRQ(a6)
|
||
|
rts
|
||
|
|
||
|
;--------------------------------------------------------------------
|
||
|
; Triggers execution of the blitter queue.
|
||
|
;
|
||
|
; Starts the current queue if it is not empty.
|
||
|
; Warning: You MUST make sure the blitter is NOT busy when calling
|
||
|
; this. It is up to you if you want to start the blitter with
|
||
|
; hogging or not (you can control this in your routines as well,
|
||
|
; of course!). Blitter hogging on makes only sense if you will have
|
||
|
; blits with IDLE frames!
|
||
|
;
|
||
|
; NOTE! You MUST join the blitter queue before waiting for the next
|
||
|
; vertical blank because a running interrupt driven blitter queue
|
||
|
; is not compatible with multitasking!
|
||
|
;
|
||
|
; Trashes: a0/a1/d0
|
||
|
;
|
||
|
fw_TriggerBlitterQueue:
|
||
|
PUTMSG 50,<"Trigger Head/Write/Read %p %p %p">,fw_BlitterQueueHeadPtr(a6),fw_BlitterQueueWritePtr(a6),fw_BlitterQueueReadPtr(a6)
|
||
|
move.l fw_BlitterQueueHeadPtr(a6),d0
|
||
|
bne.s .cont
|
||
|
PUTMSG 50,<"BQ empty">
|
||
|
rts
|
||
|
.cont
|
||
|
clr.l fw_BlitterQueueHeadPtr(a6)
|
||
|
move.l d0,a0
|
||
|
fw_TriggerCustomBlitterQueue:
|
||
|
PUTMSG 50,<"Exe BQ %p">,a0
|
||
|
move.l (a0)+,fw_BlitterQueueReadPtr(a6)
|
||
|
beq.s .onlyone
|
||
|
|
||
|
move.w #INTF_BLIT,intena(a5) ; disable blitter int
|
||
|
move.w #INTF_BLIT,intena(a5) ; disable blitter int
|
||
|
|
||
|
.allsyncloop
|
||
|
move.w #INTF_BLIT,intreq(a5) ; clear pending blitter int
|
||
|
|
||
|
move.l (a0)+,a1
|
||
|
jsr (a1)
|
||
|
bne.s .activate
|
||
|
move.l fw_BlitterQueueReadPtr(a6),a0
|
||
|
move.l (a0)+,fw_BlitterQueueReadPtr(a6)
|
||
|
bne.s .allsyncloop
|
||
|
.lastwasalsosync
|
||
|
.onlyone
|
||
|
cmp.l fw_BlitterQueueWritePtr(a6),d0
|
||
|
bne.s .noconflict
|
||
|
PUTMSG 50,<"Trigger caught up">
|
||
|
clr.l fw_BlitterQueueWritePtr(a6)
|
||
|
.noconflict
|
||
|
PUTMSG 50,<"BQ Trigger Last!">
|
||
|
move.l (a0)+,a1
|
||
|
jmp (a1)
|
||
|
.activate
|
||
|
PUTMSG 50,<"Activate!">
|
||
|
move.l fw_VBR(a6),a0
|
||
|
move.l fw_BlitterQueueIRQ(a6),$6c(a0)
|
||
|
move.w #INTF_SETCLR|INTF_BLIT,intena(a5) ; enable blitter int
|
||
|
rts
|
||
|
|
||
|
;--------------------------------------------------------------------
|
||
|
; Makes the blitter queue synchronous again -- no longer causes
|
||
|
; interrupts and will restore normal multitasking operation.
|
||
|
;
|
||
|
; The last blit might still be running when this function returns!
|
||
|
;
|
||
|
; Trashes: a0/a1/d0
|
||
|
;
|
||
|
fw_JoinBlitterQueue:
|
||
|
PUTMSG 50,<"Join Head/Write/Read %p %p %p">,fw_BlitterQueueHeadPtr(a6),fw_BlitterQueueWritePtr(a6),fw_BlitterQueueReadPtr(a6)
|
||
|
move.w #INTF_INTEN|INTF_BLIT,intena(a5) ; disable main and blitter int
|
||
|
move.w #INTF_INTEN|INTF_BLIT,intena(a5) ; disable main and blitter int
|
||
|
tst.l fw_BlitterQueueReadPtr(a6)
|
||
|
beq.s .done
|
||
|
PUTMSG 50,<"Joining blitter queue">
|
||
|
move.l fw_VBR(a6),a0
|
||
|
move.l fw_DefaultIRQ(a6),$6c(a0)
|
||
|
ENABLE_INTS
|
||
|
.retry
|
||
|
move.l fw_BlitterQueueReadPtr(a6),a0
|
||
|
PUTMSG 50,<"BQ Next %p">,a0
|
||
|
move.l (a0)+,fw_BlitterQueueReadPtr(a6)
|
||
|
beq.s .last
|
||
|
pea .retry(pc)
|
||
|
.last
|
||
|
move.l (a0)+,a1 ; bq_Routine
|
||
|
BLTHOGON
|
||
|
BLTWAIT
|
||
|
BLTHOGOFF
|
||
|
jmp (a1)
|
||
|
.done
|
||
|
ENABLE_INTS
|
||
|
rts
|
||
|
|
||
|
;--------------------------------------------------------------------
|
||
|
|
||
|
fw_blitter_queue_multiframe_irq:
|
||
|
btst #INTB_VERTB,$dff000+intreqr+1
|
||
|
bne fw_vblank_standard_irq
|
||
|
fw_blitter_queue_irq:
|
||
|
PUTMSG 50,<"BQINT %lx">,$dff000+intenar
|
||
|
IF FW_COPPER_IRQ_SUPPORT
|
||
|
btst #INTB_COPER,$dff000+intreqr+1
|
||
|
bne fw_copper_irq
|
||
|
ENDC
|
||
|
IF FW_A5_A6_UNTOUCHED
|
||
|
PUSHM d0/a0/a1
|
||
|
ELSE
|
||
|
PUSHM d0/a0/a1/a5/a6
|
||
|
lea $dff000,a5
|
||
|
move.l fw_BasePtr(pc),a6
|
||
|
ENDC
|
||
|
.retry
|
||
|
move.w #INTF_BLIT,intreq(a5) ; acknowledge the blitter irq.
|
||
|
move.l fw_BlitterQueueReadPtr(a6),a0
|
||
|
PUTMSG 50,<"BQ Next Head/Write/Read %p %p %p">,fw_BlitterQueueHeadPtr(a6),fw_BlitterQueueWritePtr(a6),a0
|
||
|
move.l (a0)+,fw_BlitterQueueReadPtr(a6)
|
||
|
beq.s .last
|
||
|
move.l (a0)+,a1 ; bq_Routine
|
||
|
jsr (a1)
|
||
|
beq.s .retry
|
||
|
; this has some issues regarding starvation e.g. if you're spamming
|
||
|
; lots of hogging zero-idle blits across VBL, e.g. music code will not execute in time.
|
||
|
;btst #DMAB_BLTDONE-8,dmaconr(a5)
|
||
|
;beq.s .retry
|
||
|
.finished
|
||
|
POPM NOBUMP
|
||
|
nop
|
||
|
rte
|
||
|
.last
|
||
|
PUTMSG 50,<"BQ Last!">
|
||
|
move.w #INTF_BLIT,intena(a5) ; disable blitter int
|
||
|
lea -4(a0),a1
|
||
|
cmp.l fw_BlitterQueueWritePtr(a6),a1
|
||
|
bne.s .noconflict
|
||
|
PUTMSG 50,<"Caught up">
|
||
|
clr.l fw_BlitterQueueWritePtr(a6)
|
||
|
.noconflict
|
||
|
move.l (a0)+,a1 ; bq_Routine
|
||
|
jsr (a1)
|
||
|
move.l fw_VBR(a6),a0
|
||
|
move.l fw_DefaultIRQ(a6),$6c(a0)
|
||
|
POPM
|
||
|
nop
|
||
|
rte
|