Hamazing/source/framework/framework_tasks.asm
chrisly42 9c48c11cd1 Big "squashed" update to latest version of Framework.
- 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
2024-09-15 17:43:33 +02:00

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