chrisly42
9c48c11cd1
- Bugfix: WaitForFrame was completely broken. Now also caters for race-condition that would have waited one extra frame. - Bugfix: InitPart would overwrite innocent memory (reported by Gigabates and Losso) - Bugfix: Palette LERP had wrong bias. - Removed extra paths in include statement, use default include paths instead - Added Raspberry Casket no-jitter background calc mode (FW_MUSIC_PLAYER_CHOICE = 6) - Updated Raspberry Casket to V2.0 presto branch (WIP) - Removed fw_FrameCounterLong, use fw_FrameCounter-2 for debug purposes - Support for blue noise palette LERPing (like in Is Real). Provide your own blue noise table (4 KB), stuff it into fw_BlueNoiseTablePtr, set FW_PALETTE_LERP_SUPPORT to 2 - Music tick routine is now replaceable during runtime (fw_MusicTickRoutine) - Support for softints and audio interrupts - LMB exit can also be disabled dynamically when using FW_LMB_EXIT_SUPPORT = 2 and fw_DisableLMBExit != 0 - Added LSP Micro support and LSP Nano (custom format that uses note pitches instead of periods) - Minor other things
506 lines
17 KiB
NASM
506 lines
17 KiB
NASM
;--------------------------------------------------------------------
|
|
; Initialize the multitasking environment.
|
|
;
|
|
fw_InitTasks:
|
|
lea fw_Tasks(a6),a1
|
|
NEWLIST a1
|
|
lea fw_backgroundtask_restore_irq(pc),a0
|
|
move.l a0,fw_MultitaskingIRQ(a6)
|
|
IF FW_BLITTERTASK_MT_SUPPORT
|
|
lea fw_blitter_task_irq(pc),a0
|
|
move.l a0,fw_BlitterTaskIRQ(a6)
|
|
ENDC
|
|
rts
|
|
|
|
;--------------------------------------------------------------------
|
|
; Adds a task to the list of background tasks.
|
|
;
|
|
; LN_NAME(a1) can be populated for debugging reasons
|
|
; LN_PRI(a1) from -128 (low) to +127 (high) describes the priority
|
|
; of the task. Unless a task yields, no lower priority task will be
|
|
; processed.
|
|
; Exception: For FW_ROUNDROBIN_MT_SUPPORT=1, if LN_PRI is negative,
|
|
; low prio tasks will be scheduled round robin, if the task did not yield.
|
|
;
|
|
; Note that every task will only be scheduled at most once every frame.
|
|
; May also be called from subtask.
|
|
; Note: All tasks must preserve a5/a6 until exit!
|
|
;
|
|
; In : a0: start address of routine
|
|
; a1: FrameworkTask structure (with length of ft_SIZEOF)
|
|
; All registers will be initial registers for subtask.
|
|
; Out: All registers are unchanged.
|
|
;
|
|
fw_AddTask:
|
|
PUTMSG 10,<"%d: AddTask(%p,%p,%s)">,fw_FrameCounter-2(a6),a0,a1,LN_NAME(a1)
|
|
tst.l ft_USP(a1)
|
|
bne fw_Error
|
|
IF FW_TOP_BOTTOM_MEM_SECTIONS
|
|
move.w fw_MainMemDirection(a6),ft_MemDirection(a1)
|
|
ENDC
|
|
move.l a2,-(sp)
|
|
lea ft_StackEnd(a1),a2
|
|
exg a2,sp ; temporarily swap stack pointers
|
|
move.l a1,-(sp) ; keep node for cleanup
|
|
pea .cleanup(pc) ; routine to call after RTS from backgroundtask
|
|
move.l a0,-(sp) ; background task to jump to
|
|
clr.w -(sp) ; initial ccr
|
|
movem.l d0-d7/a0-a6,-(sp) ; initial register dump from caller
|
|
move.l sp,ft_USP(a1)
|
|
exg a2,sp
|
|
move.l (sp)+,(8+2)*4(a2) ; restore a2 to stack
|
|
move.l (8+2)*4(a2),a2 ; restore a2 to register
|
|
lea fw_Tasks(a6),a0
|
|
DISABLE_INTS
|
|
bsr fw_EnqueueNode
|
|
ENABLE_INTS
|
|
rts
|
|
|
|
.cleanup
|
|
;move.l fw_BasePtr(pc),a6
|
|
move.l (sp)+,a1 ; suppress M68kDeadWrite used by REMOVE
|
|
PUTMSG 10,<"%d: background task %p (%s) finished">,fw_FrameCounter-2(a6),a1,LN_NAME(a1)
|
|
|
|
.waitforsafearea
|
|
move.l #$1ff00,d0
|
|
and.l vposr(a5),d0
|
|
cmp.l #307<<8,d0 ; we are too short before VBL, it's not safe to disable ints
|
|
bgt .waitforsafearea
|
|
|
|
DISABLE_INTS
|
|
clr.l ft_USP(a1) ; mark task finished
|
|
REMOVE
|
|
lea fw_Tasks(a6),a1
|
|
IFEMPTY a1,fw_KillTaskContext ; wait for doom
|
|
move.l LN_SUCC(a1),a0 ; hand over to next task
|
|
PUTMSG 40,<"Continuing to task %p (%s)">,a0,LN_NAME(a0)
|
|
move.l a0,fw_BackgroundTask(a6)
|
|
move.l ft_USP(a0),sp
|
|
ENABLE_INTS
|
|
movem.l (sp)+,d0-d7/a0-a6
|
|
rtr
|
|
|
|
;--------------------------------------------------------------------
|
|
|
|
fw_EnqueueNode:
|
|
move.b LN_PRI(a1),d1
|
|
move.l (a0),d0
|
|
.next
|
|
move.l d0,a0
|
|
move.l (a0),d0
|
|
beq.s .done
|
|
cmp.b LN_PRI(a0),d1
|
|
ble.s .next
|
|
.done
|
|
move.l LN_PRED(a0),d0
|
|
move.l a1,LN_PRED(a0)
|
|
move.l a0,(a1)
|
|
move.l d0,LN_PRED(a1)
|
|
move.l d0,a0
|
|
move.l a1,(a0)
|
|
rts
|
|
|
|
;--------------------------------------------------------------------
|
|
|
|
fw_KillTaskContext:
|
|
PUTMSG 50,<"KillTaskContext">
|
|
move.l fw_VBR(a6),a0
|
|
move.l fw_DefaultIRQ(a6),$6c(a0)
|
|
clr.l fw_BackgroundTask(a6) ; make sure we don't have a stray pointer
|
|
move.l fw_PrimaryUSP(a6),sp ; primary USP from before
|
|
clr.l fw_PrimaryUSP(a6)
|
|
move.w fw_MainCurrentFrame(a6),d0
|
|
ENABLE_INTS
|
|
btst #DMAB_BLTDONE-8,dmaconr(a5)
|
|
beq.s .loop
|
|
BLTHOGON
|
|
.loop cmp.w fw_FrameCounter(a6),d0
|
|
beq.s .loop
|
|
rts
|
|
|
|
;--------------------------------------------------------------------
|
|
; Waits for next vertical blank and allows multitasking to happen.
|
|
;
|
|
; Note: Also checks left mouse button for exit if configured.
|
|
;
|
|
; In : -
|
|
; Out: All registers except for a5 and a6 may be trashed.
|
|
;
|
|
fw_VSyncWithTask:
|
|
move.l #$1ff00,d0
|
|
and.l vposr(a5),d0
|
|
cmp.l #FW_MAX_VPOS_FOR_BG_TASK<<8,d0 ; if we're too late, don't continue background task
|
|
bgt fw_VSync
|
|
|
|
IF FW_LMB_EXIT_SUPPORT
|
|
IFEQ FW_LMB_EXIT_SUPPORT-2
|
|
tst.w fw_DisableLMBExit(a6)
|
|
bne.s .noabort
|
|
ENDC
|
|
btst #6,$bfe001
|
|
beq .abortdemo
|
|
.noabort
|
|
ENDC
|
|
|
|
lea fw_Tasks(a6),a1
|
|
IFEMPTY a1,fw_VSync
|
|
SUCC a1,a1 ; take first task from list
|
|
; context switch takes a few hundred cycles (with idle DMA)
|
|
|
|
move.w fw_FrameCounter(a6),fw_MainCurrentFrame(a6)
|
|
.switch
|
|
PUTMSG 50,<"TaskSwitch %p">,a1
|
|
move.l sp,fw_PrimaryUSP(a6) ; store old stackpointer (pointing to RTS address)
|
|
move.l fw_VBR(a6),a0
|
|
move.l fw_MultitaskingIRQ(a6),$6c(a0)
|
|
PUTMSG 40,<"Switching to task %p (%s)">,a1,LN_NAME(a1)
|
|
move.l a1,fw_BackgroundTask(a6)
|
|
move.l ft_USP(a1),sp
|
|
movem.l (sp)+,d0-d7/a0-a6 ; restore context, 132 cycles (another >132 cycles in interrupt)
|
|
PUTMSG 50,<"RTR %p">,sp
|
|
rtr
|
|
|
|
IF FW_LMB_EXIT_SUPPORT
|
|
.abortdemo
|
|
move.l fw_DemoAbortStackPointer(a6),d0
|
|
beq.s .noabort
|
|
move.l d0,sp
|
|
rts
|
|
ENDC
|
|
|
|
;--------------------------------------------------------------------
|
|
; Allows the current task to switch CPU to the next task in queue.
|
|
;
|
|
; Usually may not be called from tasks, except if FW_YIELD_FROM_MAIN_TOO
|
|
; is set (then it will just call VSyncWithTask).
|
|
;
|
|
; Preserves all registers!
|
|
;
|
|
fw_Yield:
|
|
IF FW_YIELD_FROM_MAIN_TOO
|
|
tst.l fw_PrimaryUSP(a6)
|
|
bne.s .switch
|
|
; So we're not in a background task (method should actually not be used this way)
|
|
PUTMSG 50,<"Simple yield %p">,a7
|
|
PUSHM d0-d7/a0-a4
|
|
bsr fw_VSyncWithTask
|
|
POPM
|
|
rts
|
|
ENDC
|
|
|
|
.switch
|
|
PUSHM d0/a0
|
|
move.l #$1ff00,d0
|
|
and.l vposr(a5),d0
|
|
cmp.l #307<<8,d0 ; it's too close to VBL, we cannot safely disable ints
|
|
ble .cont
|
|
POPM NOBUMP
|
|
rts
|
|
.cont
|
|
DISABLE_INTS
|
|
move.l fw_BackgroundTask(a6),a0
|
|
TSTNODE a0,a0
|
|
POPM
|
|
bne.s .doswitch
|
|
PUTMSG 50,<"No more tasks">
|
|
subq.w #2,sp ; ccr can be anything
|
|
movem.l d0-d7/a0-a6,-(sp)
|
|
move.l fw_BackgroundTask(a6),a0
|
|
move.l sp,ft_USP(a0)
|
|
|
|
bra fw_KillTaskContext
|
|
|
|
.doswitch
|
|
; return address already on stack
|
|
subq.w #2,sp ; ccr can be anything
|
|
PUSHM d0-d7/a0-a6
|
|
move.l fw_BackgroundTask(a6),a0
|
|
move.l sp,ft_USP(a0)
|
|
SUCC a0,a0
|
|
PUTMSG 40,<"Yield to task %p (%s)">,a0,LN_NAME(a0)
|
|
move.l a0,fw_BackgroundTask(a6)
|
|
move.l ft_USP(a0),sp
|
|
ENABLE_INTS
|
|
POPM
|
|
rtr
|
|
|
|
;--------------------------------------------------------------------
|
|
; Makes sure the given task is finished.
|
|
;
|
|
; May only be called from main.
|
|
;
|
|
; In: a1 = task structure
|
|
;
|
|
; May trash all registers except a1
|
|
;
|
|
fw_WaitUntilTaskFinished:
|
|
.retry tst.l ft_USP(a1)
|
|
bne.s .wait
|
|
rts
|
|
.wait PUSHM a1
|
|
bsr fw_VSyncWithTask
|
|
POPM
|
|
bra.s .retry
|
|
|
|
;--------------------------------------------------------------------
|
|
; Removes the task from list if it was still running
|
|
;
|
|
; May only be called from main.
|
|
;
|
|
; In: a1 = task structure
|
|
; Trashes: a0/a1
|
|
fw_RemTask:
|
|
tst.l ft_USP(a1)
|
|
bne.s .remove
|
|
rts
|
|
.remove PUTMSG 10,<"%d: Removing still running task %p (%s)">,fw_FrameCounter-2(a6),a1,LN_NAME(a1)
|
|
clr.l ft_USP(a1)
|
|
REMOVE
|
|
rts
|
|
|
|
;--------------------------------------------------------------------
|
|
|
|
fw_backgroundtask_restore_irq:
|
|
PUTMSG 50,<"MTINT %lx">,$dff000+intenar
|
|
IF FW_COPPER_IRQ_SUPPORT
|
|
btst #INTB_COPER,$dff000+intreqr+1
|
|
bne fw_copper_irq
|
|
ENDC
|
|
move.l a6,-(sp) ; save a6, we need a spare register
|
|
move.l usp,a6 ; get USP
|
|
PUTMSG 50,<"USP %p">,a6
|
|
move.l 4+2(sp),-(a6) ; store PC
|
|
move.w 4(sp),-(a6) ; store SR
|
|
move.l (sp)+,-(a6) ; store a6 in stack frame
|
|
movem.l d0-d7/a0-a5,-(a6) ; store the rest of the registers
|
|
move.l a6,a0
|
|
|
|
move.l fw_BasePtr(pc),a6
|
|
move.l fw_BackgroundTask(a6),a1
|
|
move.l a0,ft_USP(a1) ; save USP for background task
|
|
IF FW_ROUNDROBIN_MT_SUPPORT
|
|
tst.b LN_PRI(a1)
|
|
bpl.s .tasklocked
|
|
move.l a1,a2
|
|
|
|
; REMOVE
|
|
move.l (a2)+,a0
|
|
move.l (a2),a2 ; LN_PRED
|
|
move.l a0,(a2)
|
|
move.l a2,LN_PRED(a0)
|
|
|
|
lea fw_Tasks+LN_PRED(a6),a0
|
|
; ADDTAIL
|
|
move.l LN_PRED(a0),d0
|
|
move.l a1,LN_PRED(a0)
|
|
exg d0,a0
|
|
movem.l d0/a0,(a1)
|
|
move.l a1,(a0)
|
|
.tasklocked
|
|
ENDC
|
|
|
|
clr.l fw_BackgroundTask(a6)
|
|
|
|
move.l fw_PrimaryUSP(a6),a0 ; primary USP from before
|
|
move.l (a0)+,2(sp) ; store return PC to exception frame (keep SR unchanged)
|
|
move.l a0,usp ; restore primary USP (now at position before calling the vblank wait)
|
|
clr.l fw_PrimaryUSP(a6)
|
|
|
|
PUTMSG 50,<"Restoring USP %p">,a0
|
|
move.l fw_VBR(a6),a0
|
|
move.l fw_DefaultIRQ(a6),$6c(a0)
|
|
|
|
lea $dff000,a5
|
|
move.w #INTF_VERTB,intreq(a5) ; acknowledge the vbl-irq.
|
|
move.w #INTF_VERTB,intreq(a5) ; acknowledge the vbl-irq.
|
|
addq.w #1,fw_FrameCounter(a6)
|
|
|
|
IF (FW_MUSIC_SUPPORT&FW_VBL_MUSIC_IRQ)
|
|
move.l fw_MusicTickRoutine(a6),d0
|
|
beq.s .skipmus
|
|
move.l d0,a0
|
|
jsr (a0) ; IRQ must maintain d4-d7/a4
|
|
.skipmus
|
|
ENDC
|
|
|
|
IF FW_VBL_IRQ_SUPPORT
|
|
move.l fw_VBlankIRQ(a6),d0
|
|
beq.s .novbl1
|
|
move.l d0,a0
|
|
; IRQ may destroy everything here (except a5/a6)
|
|
jsr (a0)
|
|
.novbl1
|
|
ENDC
|
|
nop
|
|
rte
|
|
|
|
;--------------------------------------------------------------------
|
|
; Allows to run a specific task in parallel to the blitter.
|
|
;
|
|
; Make sure the blitter has been started and will not terminate within
|
|
; a few cycles (otherwise it might lose the interrupt).
|
|
;
|
|
; Due to the overhead, this routine is best used for
|
|
; - larger blits with idle cycles
|
|
; - larger blits with no idle cycles, but with blitter hogging off and
|
|
; slow CPU operations such as multiplications or division.
|
|
;
|
|
; Returns from this call, if either a blitter interrupt or VBL has
|
|
; occurred or the task finished execution. Thus you will still need to
|
|
; make sure that the blitter has finished before starting another blit.
|
|
; The task may be continued during another running blitter operation
|
|
; using YieldToBlitterTask. The task can be also finished ("joined")
|
|
; synchroneously using FinishBlitterTask.
|
|
;
|
|
; Compared to a blitter queue or normal background task, this allows more
|
|
; fine grained control over the operations to perform during specific blits.
|
|
; It also has little complexity overhead, works well when combining large
|
|
; (with bg task) and small (without separate task) blits.
|
|
;
|
|
; Although it might be possible to use this across VBL, it is not recommended.
|
|
; A VBL will interrupt the blitter task early even if the blitter is still
|
|
; running for some more time.
|
|
;
|
|
; Note: The task must preserve a5/a6 until exit! This task may not Yield!
|
|
; May only be called from the main task!
|
|
;
|
|
; Trashes all registers except for a5/a6
|
|
;
|
|
IF FW_BLITTERTASK_MT_SUPPORT
|
|
fw_AddAndRunBlitterTask:
|
|
DISABLE_INTS
|
|
move.w #INTF_BLIT,intreq(a5) ; clear the blitter-irq.
|
|
move.l a1,fw_BackgroundTask(a6)
|
|
|
|
.switch
|
|
move.l sp,fw_PrimaryUSP(a6) ; store old stackpointer (pointing to RTS address)
|
|
PUTMSG 50,<"Blitter TaskSwitch %p">,a1
|
|
lea ft_StackEnd(a1),sp
|
|
clr.l -(sp) ; a little space for the task stackpointer
|
|
pea .cleanup(pc) ; routine to call after RTS from backgroundtask
|
|
move.l a0,-(sp) ; background task to jump to
|
|
|
|
move.l fw_VBR(a6),a0
|
|
move.l fw_BlitterTaskIRQ(a6),$6c(a0)
|
|
move.w #INTF_SETCLR|INTF_INTEN|INTF_BLIT,intena(a5) ; enable interrupts, including blitter int
|
|
rts
|
|
|
|
.cleanup
|
|
move.w #INTF_INTEN|INTF_BLIT,intena(a5) ; disable interrupts, including blitter int
|
|
move.w #INTF_INTEN|INTF_BLIT,intena(a5) ; disable interrupts, including blitter int
|
|
; FIXME do we have a race condition here? What happens if the blitter finishes right before this instruction?
|
|
;move.l fw_BasePtr(pc),a6
|
|
move.l fw_PrimaryUSP(a6),d0
|
|
bne.s .irqcleanup
|
|
move.l (sp)+,sp ; we're coming from FinishBlitterTask
|
|
ENABLE_INTS
|
|
rts
|
|
.irqcleanup
|
|
PUTMSG 30,<"%d: blitter task finished">,fw_FrameCounter-2(a6)
|
|
move.l fw_VBR(a6),a0
|
|
move.l fw_DefaultIRQ(a6),$6c(a0)
|
|
clr.l fw_BackgroundTask(a6) ; make sure we don't have a stray pointer
|
|
move.l d0,sp ; switch to primary task
|
|
clr.l fw_PrimaryUSP(a6)
|
|
move.w #INTF_BLIT,intena(a5) ; disable blitter int again
|
|
ENABLE_INTS
|
|
rts
|
|
|
|
;--------------------------------------------------------------------
|
|
; Allows the current task to switch CPU to the current blitter task.
|
|
;
|
|
; Returns from this call immediately if the task was already finished.
|
|
; May return later if either a blitter interrupt or VBL has
|
|
; occurred or the task finished execution.
|
|
; The task can be also finished synchroneously using FinishBlitterTask.
|
|
;
|
|
; May only be called from the main task!
|
|
;
|
|
; In: a0 = task
|
|
; Trashes all registers except for a5/a6
|
|
;
|
|
fw_YieldToBlitterTask:
|
|
move.l fw_BackgroundTask(a6),d0
|
|
beq.s .noswitch
|
|
DISABLE_INTS
|
|
move.w #INTF_BLIT,intreq(a5) ; clear the blitter-irq.
|
|
move.l sp,fw_PrimaryUSP(a6) ; store old stackpointer (pointing to RTS address)
|
|
move.l d0,a0
|
|
move.l ft_USP(a0),sp
|
|
PUTMSG 40,<"Yielding to Blitter task %p (%s)">,a0,LN_NAME(a0)
|
|
move.l fw_VBR(a6),a0
|
|
move.l fw_BlitterTaskIRQ(a6),$6c(a0)
|
|
movem.l (sp)+,d0-d7/a0-a6
|
|
move.w #INTF_SETCLR|INTF_INTEN|INTF_BLIT,intena(a5) ; enable interrupts, including blitter int
|
|
rtr
|
|
.noswitch
|
|
PUTMSG 50,<"Blitter task not available">
|
|
rts
|
|
|
|
;--------------------------------------------------------------------
|
|
; Finish Blitter parallel task outside of blitter action.
|
|
;
|
|
; Returns from this call, immediately if the task was already finished.
|
|
; Otherwise, runs the task until the end.
|
|
;
|
|
; May only be called from the main task!
|
|
;
|
|
; Trashes all registers except for a5/a6
|
|
;
|
|
fw_FinishBlitterTask:
|
|
DISABLE_INTS
|
|
move.l fw_BackgroundTask(a6),d0
|
|
bne.s .doswitch
|
|
PUTMSG 50,<"Blitter task not available">
|
|
ENABLE_INTS
|
|
rts
|
|
|
|
.doswitch
|
|
move.l d0,a0
|
|
PUTMSG 50,<"Restoring Blitter Task %p">,a0
|
|
clr.l fw_BackgroundTask(a6)
|
|
move.l sp,ft_StackEnd-4(a0)
|
|
move.l ft_USP(a0),sp
|
|
ENABLE_INTS
|
|
movem.l (sp)+,d0-d7/a0-a6
|
|
rtr
|
|
|
|
;--------------------------------------------------------------------
|
|
|
|
fw_blitter_task_irq:
|
|
PUTMSG 50,<"BLINT %lx">,$dff000+intenar
|
|
IF FW_COPPER_IRQ_SUPPORT
|
|
btst #INTB_COPER,$dff000+intreqr+1
|
|
bne fw_copper_irq
|
|
ENDC
|
|
move.l a6,-(sp) ; save a6, we need a spare register
|
|
move.l usp,a6 ; get USP
|
|
PUTMSG 50,<"USP %p">,a6
|
|
move.l 4+2(sp),-(a6) ; store PC
|
|
move.w 4(sp),-(a6) ; store SR
|
|
move.l (sp)+,-(a6) ; store a6 in stack frame
|
|
movem.l d0-d7/a0-a5,-(a6) ; store the rest of the registers
|
|
move.l a6,a0
|
|
|
|
move.l fw_BasePtr(pc),a6
|
|
move.l fw_BackgroundTask(a6),a1
|
|
move.l a0,ft_USP(a1) ; save USP for background task
|
|
|
|
move.l fw_PrimaryUSP(a6),a0 ; primary USP from before
|
|
move.l (a0)+,2(sp) ; store return PC to exception frame (keep SR unchanged)
|
|
move.l a0,usp ; restore primary USP (now at position before calling the vblank wait)
|
|
clr.l fw_PrimaryUSP(a6)
|
|
|
|
lea $dff000,a5
|
|
move.w #INTF_BLIT,intreq(a5) ; acknowledge the blitter irq.
|
|
move.w #INTF_BLIT,intena(a5) ; disable the blitter irq
|
|
|
|
PUTMSG 50,<"Restoring USP %p">,a0
|
|
move.l fw_VBR(a6),a0
|
|
move.l fw_DefaultIRQ(a6),$6c(a0)
|
|
|
|
nop
|
|
rte
|
|
|
|
ENDC
|