diff --git a/include/functions.h b/include/functions.h index 5d94726469..8a19801ff7 100644 --- a/include/functions.h +++ b/include/functions.h @@ -19,7 +19,7 @@ void cleararena(void); void bootproc(void); void Main_ThreadEntry(void* arg); void Idle_ThreadEntry(void* arg); -void ViConfig_UpdateVi(u32 mode); +void ViConfig_UpdateVi(u32 black); void ViConfig_UpdateBlack(void); s32 DmaMgr_CompareName(const char* name1, const char* name2); s32 DmaMgr_DmaRomToRam(u32 rom, u32 ram, u32 size); @@ -977,7 +977,7 @@ void MapMark_Draw(PlayState* play); void PreNmiBuff_Init(PreNmiBuff* this); void PreNmiBuff_SetReset(PreNmiBuff* this); u32 PreNmiBuff_IsResetting(PreNmiBuff* this); -void MsgEvent_SendNullTask(void); +void Sched_FlushTaskQueue(void); f32 OLib_Vec3fDist(Vec3f* a, Vec3f* b); f32 OLib_Vec3fDistXZ(Vec3f* a, Vec3f* b); f32 OLib_ClampMinDist(f32 val, f32 min); @@ -1563,10 +1563,10 @@ void THA_Dt(TwoHeadArena* tha); void func_800C3C20(void); void func_800C3C80(AudioMgr* audioMgr); void AudioMgr_HandleRetrace(AudioMgr* audioMgr); -void AudioMgr_HandlePRENMI(AudioMgr* audioMgr); +void AudioMgr_HandlePreNMI(AudioMgr* audioMgr); void AudioMgr_ThreadEntry(void* arg0); void AudioMgr_Unlock(AudioMgr* audioMgr); -void AudioMgr_Init(AudioMgr* audioMgr, void* stack, OSPri pri, OSId id, SchedContext* sched, IrqMgr* irqMgr); +void AudioMgr_Init(AudioMgr* audioMgr, void* stack, OSPri pri, OSId id, Scheduler* sched, IrqMgr* irqMgr); void TitleSetup_InitImpl(GameState* gameState); void TitleSetup_Destroy(GameState* gameState); void TitleSetup_Init(GameState* gameState); @@ -1630,24 +1630,6 @@ void PadMgr_HandlePreNMI(PadMgr* padmgr); // fault.c (actual bug in game), and the compiler notices and won't compile it // void PadMgr_RequestPadData(PadMgr* padmgr, Input* inputs, s32 mode); void PadMgr_Init(PadMgr* padmgr, OSMesgQueue* serialEventQueue, IrqMgr* irqMgr, OSId id, OSPri priority, void* stack); -void Sched_SwapFrameBuffer(CfbInfo* cfbInfo); -void func_800C84E4(SchedContext* sc, CfbInfo* cfbInfo); -void Sched_HandleReset(SchedContext* sc); -void Sched_HandleStart(SchedContext* sc); -void Sched_QueueTask(SchedContext* sc, OSScTask* task); -void Sched_Yield(SchedContext* sc); -OSScTask* func_800C89D4(SchedContext* sc, OSScTask* task); -s32 Sched_Schedule(SchedContext* sc, OSScTask** sp, OSScTask** dp, s32 state); -void func_800C8BC4(SchedContext* sc, OSScTask* task); -u32 Sched_IsComplete(SchedContext* sc, OSScTask* task); -void Sched_RunTask(SchedContext* sc, OSScTask* spTask, OSScTask* dpTask); -void Sched_HandleEntry(SchedContext* sc); -void Sched_HandleRetrace(SchedContext* sc); -void Sched_HandleRSPDone(SchedContext* sc); -void Sched_HandleRDPDone(SchedContext* sc); -void Sched_SendEntryMsg(SchedContext* sc); -void Sched_ThreadEntry(void* arg); -void Sched_Init(SchedContext* sc, void* stack, OSPri priority, UNK_TYPE arg3, UNK_TYPE arg4, IrqMgr* irqMgr); void SpeedMeter_InitImpl(SpeedMeter* this, u32 arg1, u32 y); void SpeedMeter_Init(SpeedMeter* this); void SpeedMeter_Destroy(SpeedMeter* this); @@ -2261,7 +2243,7 @@ void guMtxF2L(MtxF* m1, Mtx* m2); // ? __ll_to_f(?); // ? __ull_to_d(?); // ? __ull_to_f(?); -u32* osViGetCurrentFramebuffer(void); +void* osViGetCurrentFramebuffer(void); s32 __osSpSetPc(void* pc); f32 absf(f32); void* __osMemset(void* dest, s32 val, size_t len); diff --git a/include/sched.h b/include/sched.h new file mode 100644 index 0000000000..48ecc7b245 --- /dev/null +++ b/include/sched.h @@ -0,0 +1,70 @@ +#ifndef SCHED_H +#define SCHED_H + +#include "ultra64.h" +#include "irqmgr.h" + +#define OS_SC_NEEDS_RDP 0x0001 // Task uses the RDP +#define OS_SC_NEEDS_RSP 0x0002 // Task uses the RSP +#define OS_SC_DRAM_DLIST 0x0004 // Unimplemented +#define OS_SC_PARALLEL_TASK 0x0010 // Unimplemented +#define OS_SC_LAST_TASK 0x0020 // Unimplemented +#define OS_SC_SWAPBUFFER 0x0040 // Swap framebuffer when done + +#define OS_SC_DP OS_SC_NEEDS_RDP // Task is using the RDP +#define OS_SC_SP OS_SC_NEEDS_RSP // Task is using the RSP +#define OS_SC_YIELD 0x0010 // Task has been asked to yield +#define OS_SC_YIELDED 0x0020 // Task has yielded + +#define OS_SC_RCP_MASK (OS_SC_NEEDS_RDP | OS_SC_NEEDS_RSP) +#define OS_SC_TYPE_MASK (OS_SC_NEEDS_RDP | OS_SC_NEEDS_RSP | OS_SC_DRAM_DLIST) + +typedef struct { + /* 0x00 */ u16* framebuffer; // current framebuffer + /* 0x04 */ u16* swapBuffer; // framebuffer to swap to + /* 0x08 */ OSViMode* viMode; + /* 0x0C */ u32 viFeatures; + /* 0x10 */ u8 unk_10; // set to 0, never read + /* 0x11 */ s8 updateRate; // how many VIs should elapse before next swap + /* 0x12 */ s8 updateTimer; // counts down (in VIs) from updateRate to 0, swaps the framebuffer at 0 + /* 0x14 */ f32 xScale; + /* 0x18 */ f32 yScale; +} CfbInfo; // size = 0x1C + +typedef struct OSScTask { + /* 0x00 */ struct OSScTask* next; + /* 0x04 */ u32 state; + /* 0x08 */ u32 flags; + /* 0x0C */ CfbInfo* framebuffer; // The original libultra OSScTask had void* here, it would point directly to a framebuffer + /* 0x10 */ OSTask list; + /* 0x50 */ OSMesgQueue* msgQueue; // Notification queue, will receive a message when the task completes + /* 0x54 */ OSMesg msg; + /* 0x58 */ OSTime startTime; // These last two fields are a guess based on the original libultra OSScTask and padding in other structures, they are unused. + /* 0x60 */ OSTime totalTime; +} OSScTask; // size = 0x68 + +typedef struct { + /* 0x0000 */ OSMesgQueue interruptQueue; + /* 0x0018 */ OSMesg interruptMsgBuf[8]; + /* 0x0038 */ OSMesgQueue cmdQueue; // queue for receiving OSScTask pointers + /* 0x0050 */ OSMesg cmdMsgBuf[8]; + /* 0x0070 */ OSThread thread; + /* 0x0220 */ OSScTask* audioListHead; + /* 0x0224 */ OSScTask* gfxListHead; + /* 0x0228 */ OSScTask* audioListTail; + /* 0x022C */ OSScTask* gfxListTail; + /* 0x0230 */ OSScTask* curRSPTask; // task currently using the RSP + /* 0x0234 */ OSScTask* curRDPTask; // task currently using the RDP + /* 0x0238 */ s32 retraceCount; + /* 0x023C */ s32 doAudio; + /* 0x0240 */ CfbInfo* curBuf; // current framebuffer (taken from buffer 1) + /* 0x0244 */ CfbInfo* pendingSwapBuf1; // buffer 1 (next buffer) + /* 0x0220 */ CfbInfo* pendingSwapBuf2; // buffer 2 (always NULL) + /* 0x0220 */ s32 isFirstSwap; + /* 0x0250 */ IrqMgrClient irqClient; +} Scheduler; // size = 0x258 + +void Sched_Notify(Scheduler* sc); +void Sched_Init(Scheduler* sc, void* stack, OSPri priority, UNK_TYPE arg3, UNK_TYPE arg4, IrqMgr* irqMgr); + +#endif diff --git a/include/variables.h b/include/variables.h index 18d0950a4a..bcd4a309ca 100644 --- a/include/variables.h +++ b/include/variables.h @@ -15,7 +15,7 @@ extern u8 osAppNMIBuffer[0x40]; extern s8 D_80009430; extern u32 gDmaMgrVerbose; extern u32 gDmaMgrDmaBuffSize; -extern vu8 gViConfigUseDefault; +extern vu8 gViConfigBlack; extern u8 gViConfigAdditionalScanLines; extern u32 gViConfigFeatures; extern f32 gViConfigXScale; @@ -184,7 +184,7 @@ extern PauseMapMarksData* gLoadedPauseMarkDataTable; extern s32 gTrnsnUnkState; extern Color_RGBA8_u32 D_801614B0; extern PreNmiBuff* gAppNmiBufferPtr; -extern SchedContext gSchedContext; +extern Scheduler gScheduler; extern PadMgr gPadMgr; extern u32 gSegments[NUM_SEGMENTS]; extern volatile OSTime D_8016A520; diff --git a/include/z64.h b/include/z64.h index 69537ced3e..c4e2e83c52 100644 --- a/include/z64.h +++ b/include/z64.h @@ -35,6 +35,7 @@ #include "irqmgr.h" #include "padmgr.h" #include "fault.h" +#include "sched.h" #define SCREEN_WIDTH 320 #define SCREEN_HEIGHT 240 @@ -116,30 +117,6 @@ typedef struct { /* 0x000C */ Gfx* d; } TwoHeadGfxArena; // size = 0x10 -typedef struct { - /* 0x00 */ u16* fb1; - /* 0x04 */ u16* swapBuffer; - /* 0x08 */ OSViMode* viMode; - /* 0x0C */ u32 features; - /* 0x10 */ u8 unk_10; - /* 0x11 */ s8 updateRate; - /* 0x12 */ s8 updateRate2; - /* 0x13 */ u8 unk_13; - /* 0x14 */ f32 xScale; - /* 0x18 */ f32 yScale; -} CfbInfo; // size = 0x1C - -typedef struct OSScTask { - /* 0x00 */ struct OSScTask* next; - /* 0x04 */ u32 state; - /* 0x08 */ u32 flags; - /* 0x0C */ CfbInfo* framebuffer; - /* 0x10 */ OSTask list; - /* 0x50 */ OSMesgQueue* msgQueue; - /* 0x54 */ OSMesg msg; - /* 0x58 */ char unk_58[0x10]; -} OSScTask; // size = 0x68 - typedef struct GraphicsContext { /* 0x0000 */ Gfx* polyOpaBuffer; // Pointer to "Zelda 0" /* 0x0004 */ Gfx* polyXluBuffer; // Pointer to "Zelda 1" @@ -150,7 +127,6 @@ typedef struct GraphicsContext { /* 0x0038 */ OSMesg msgBuff[0x08]; /* 0x0058 */ OSMesgQueue* schedMsgQueue; /* 0x005C */ OSMesgQueue queue; - /* 0x0074 */ char unk_074[0x04]; /* 0x0078 */ OSScTask task; /* 0x00E0 */ char unk_0E0[0xD0]; /* 0x01B0 */ Gfx* workBuffer; @@ -1580,63 +1556,18 @@ typedef struct { /* 0x10 */ u32 data[1]; } Yaz0Header; // size = 0x10 ("data" is not part of the header) -// == Previously sched.h - -#define OS_SC_NEEDS_RDP 0x0001 -#define OS_SC_NEEDS_RSP 0x0002 -#define OS_SC_DRAM_DLIST 0x0004 -#define OS_SC_PARALLEL_TASK 0x0010 -#define OS_SC_LAST_TASK 0x0020 -#define OS_SC_SWAPBUFFER 0x0040 - -#define OS_SC_RCP_MASK 0x0003 -#define OS_SC_TYPE_MASK 0x0007 - typedef struct { - /* 0x0000 */ u16* curBuffer; - /* 0x0004 */ u16* nextBuffer; -} FrameBufferSwap; - -typedef struct { - /* 0x0000 */ OSMesgQueue interruptQueue; - /* 0x0018 */ OSMesg interruptMsgBuf[8]; - /* 0x0038 */ OSMesgQueue cmdQueue; - /* 0x0050 */ OSMesg cmdMsgBuf[8]; - /* 0x0070 */ OSThread thread; - /* 0x0220 */ OSScTask* audioListHead; - /* 0x0224 */ OSScTask* gfxListHead; - /* 0x0228 */ OSScTask* audioListTail; - /* 0x022C */ OSScTask* gfxListTail; - /* 0x0230 */ OSScTask* curRSPTask; - /* 0x0234 */ OSScTask* curRDPTask; - /* 0x0238 */ s32 retraceCnt; - /* 0x023C */ s32 doAudio; - /* 0x0240 */ CfbInfo* curBuf; - /* 0x0244 */ CfbInfo* pendingSwapBuf1; - /* 0x0220 */ CfbInfo* pendingSwapBuf2; - /* 0x0220 */ UNK_TYPE4 unk_24C; - /* 0x0250 */ IrqMgrClient irqClient; -} SchedContext; // size = 0x258 - -// ======================== - -#define OS_SC_DP 0x0001 -#define OS_SC_SP 0x0002 -#define OS_SC_YIELD 0x0010 -#define OS_SC_YIELDED 0x0020 - -typedef struct { - /* 0x0000 */ IrqMgr* irqMgr; - /* 0x0004 */ SchedContext* sched; - /* 0x0008 */ OSScTask audioTask; - /* 0x0070 */ AudioTask* rspTask; - /* 0x0074 */ OSMesgQueue interruptQueue; - /* 0x008C */ OSMesg interruptMsgBuf[8]; - /* 0x00AC */ OSMesgQueue taskQueue; - /* 0x00C4 */ OSMesg taskMsgBuf[1]; - /* 0x00C8 */ OSMesgQueue lockQueue; - /* 0x00E0 */ OSMesg lockMsgBuf[1]; - /* 0x00E8 */ OSThread thread; + /* 0x0000 */ IrqMgr* irqMgr; + /* 0x0004 */ Scheduler* sched; + /* 0x0008 */ OSScTask audioTask; + /* 0x0070 */ AudioTask* rspTask; + /* 0x0074 */ OSMesgQueue interruptQueue; + /* 0x008C */ OSMesg interruptMsgBuf[8]; + /* 0x00AC */ OSMesgQueue taskQueue; + /* 0x00C4 */ OSMesg taskMsgBuf[1]; + /* 0x00C8 */ OSMesgQueue lockQueue; + /* 0x00E0 */ OSMesg lockMsgBuf[1]; + /* 0x00E8 */ OSThread thread; } AudioMgr; // size = 0x298 struct ArenaNode; @@ -1823,7 +1754,6 @@ typedef struct { /* 0x14 */ u8* dhtPtr[4]; /* 0x24 */ void* imageData; /* 0x28 */ u32 mode; // 0 if Y V0 is 1 and 2 if Y V0 is 2 - /* 0x2C */ char unk_2C[4]; /* 0x30 */ OSScTask scTask; /* 0x98 */ OSMesgQueue mq; /* 0xB0 */ OSMesg msg; diff --git a/spec b/spec index 53f05f9981..503150a295 100644 --- a/spec +++ b/spec @@ -337,7 +337,7 @@ beginseg include "build/src/code/z_map_mark.o" include "build/src/code/z_moji.o" include "build/src/code/z_prenmi_buff.o" - include "build/src/code/z_msgevent.o" + include "build/src/code/z_nulltask.o" include "build/src/code/z_olib.o" include "build/src/code/z_onepointdemo.o" include "build/src/code/z_map_exp.o" diff --git a/src/boot/idle.c b/src/boot/idle.c index f4e3715d7f..8e72b82562 100644 --- a/src/boot/idle.c +++ b/src/boot/idle.c @@ -10,7 +10,7 @@ OSViMode gViConfigMode; u8 D_80013960; s8 D_80009430 = 1; -vu8 gViConfigUseDefault = 1; +vu8 gViConfigBlack = true; u8 gViConfigAdditionalScanLines = 0; u32 gViConfigFeatures = OS_VI_DITHER_FILTER_ON | OS_VI_GAMMA_OFF; f32 gViConfigXScale = 1.0; @@ -75,8 +75,8 @@ void Idle_ThreadEntry(void* arg) { D_80009430 = 1; osViSetMode(&gViConfigMode); - ViConfig_UpdateVi(1); - osViBlack(1); + ViConfig_UpdateVi(true); + osViBlack(true); osViSwapBuffer(0x803DA80); //! @bug Invalid vram address (probably intended to be 0x803DA800) osCreatePiManager(OS_PRIORITY_PIMGR, &gPiMgrCmdQueue, sPiMgrCmdBuff, ARRAY_COUNT(sPiMgrCmdBuff)); StackCheck_Init(&sMainStackInfo, sMainStack, STACK_TOP(sMainStack), 0, 0x400, "main"); diff --git a/src/boot/viconfig.c b/src/boot/viconfig.c index 4c6830c997..38942097f3 100644 --- a/src/boot/viconfig.c +++ b/src/boot/viconfig.c @@ -2,16 +2,21 @@ #include "vt.h" // this should probably go elsewhere but right now viconfig.o is the only object between idle and z_std_dma -OSPiHandle* gCartHandle = 0; +OSPiHandle* gCartHandle = NULL; + +void ViConfig_UpdateVi(u32 black) { + if (black) { + // Black the screen on next call to ViConfig_UpdateBlack, skip most VI configuration -void ViConfig_UpdateVi(u32 mode) { - if (mode != 0) { osSyncPrintf(VT_COL(YELLOW, BLACK) "osViSetYScale1(%f);\n" VT_RST, 1.0f); if (osTvType == OS_TV_PAL) { osViSetMode(&osViModePalLan1); } + // Reset the VI y scale. The VI y scale is different between NTSC (1.0) and PAL (0.833) + // and should be reset to 1.0 during PreNMI to ensure there are no issues when restarting. + // (see section 30.4.3 VI Processing with PreNMI Events in the N64 Programming Manual) osViSetYScale(1.0f); } else { osViSetMode(&gViConfigMode); @@ -34,13 +39,13 @@ void ViConfig_UpdateVi(u32 mode) { } } - gViConfigUseDefault = mode; + gViConfigBlack = black; } void ViConfig_UpdateBlack(void) { - if (gViConfigUseDefault != 0) { - osViBlack(1); + if (gViConfigBlack) { + osViBlack(true); } else { - osViBlack(0); + osViBlack(false); } } diff --git a/src/code/audioMgr.c b/src/code/audioMgr.c index ccef4dbb27..840b254900 100644 --- a/src/code/audioMgr.c +++ b/src/code/audioMgr.c @@ -24,7 +24,7 @@ void AudioMgr_HandleRetrace(AudioMgr* audioMgr) { audioMgr->audioTask.msg = NULL; osSendMesg(&audioMgr->sched->cmdQueue, (OSMesg)&audioMgr->audioTask, OS_MESG_BLOCK); - Sched_SendEntryMsg(audioMgr->sched); + Sched_Notify(audioMgr->sched); } D_8016A550 = osGetTime(); @@ -42,7 +42,7 @@ void AudioMgr_HandleRetrace(AudioMgr* audioMgr) { audioMgr->rspTask = rspTask; } -void AudioMgr_HandlePRENMI(AudioMgr* audioMgr) { +void AudioMgr_HandlePreNMI(AudioMgr* audioMgr) { // "Audio manager received OS_SC_PRE_NMI_MSG" osSyncPrintf("オーディオマネージャが OS_SC_PRE_NMI_MSG を受け取りました\n"); Audio_PreNMI(); @@ -71,13 +71,13 @@ void AudioMgr_ThreadEntry(void* arg0) { case OS_SC_RETRACE_MSG: break; case OS_SC_PRE_NMI_MSG: - AudioMgr_HandlePRENMI(audioMgr); + AudioMgr_HandlePreNMI(audioMgr); break; } } break; case OS_SC_PRE_NMI_MSG: - AudioMgr_HandlePRENMI(audioMgr); + AudioMgr_HandlePreNMI(audioMgr); break; } } @@ -87,7 +87,7 @@ void AudioMgr_Unlock(AudioMgr* audioMgr) { osRecvMesg(&audioMgr->lockQueue, NULL, OS_MESG_BLOCK); } -void AudioMgr_Init(AudioMgr* audioMgr, void* stack, OSPri pri, OSId id, SchedContext* sched, IrqMgr* irqMgr) { +void AudioMgr_Init(AudioMgr* audioMgr, void* stack, OSPri pri, OSId id, Scheduler* sched, IrqMgr* irqMgr) { bzero(audioMgr, sizeof(AudioMgr)); audioMgr->sched = sched; diff --git a/src/code/graph.c b/src/code/graph.c index 868506c7d2..f70663b590 100644 --- a/src/code/graph.c +++ b/src/code/graph.c @@ -218,7 +218,7 @@ void Graph_TaskSet00(GraphicsContext* gfxCtx) { task->yield_data_size = sizeof(gGfxSPTaskYieldBuffer); scTask->next = NULL; - scTask->flags = OS_SC_RCP_MASK | OS_SC_SWAPBUFFER | OS_SC_LAST_TASK; + scTask->flags = OS_SC_NEEDS_RSP | OS_SC_NEEDS_RDP | OS_SC_SWAPBUFFER | OS_SC_LAST_TASK; if (SREG(33) & 1) { SREG(33) &= ~1; scTask->flags &= ~OS_SC_SWAPBUFFER; @@ -229,10 +229,10 @@ void Graph_TaskSet00(GraphicsContext* gfxCtx) { scTask->msg = NULL; cfb = &sGraphCfbInfos[sGraphCfbInfoIdx++]; - cfb->fb1 = gfxCtx->curFrameBuffer; + cfb->framebuffer = gfxCtx->curFrameBuffer; cfb->swapBuffer = gfxCtx->curFrameBuffer; cfb->viMode = gfxCtx->viMode; - cfb->features = gfxCtx->viFeatures; + cfb->viFeatures = gfxCtx->viFeatures; cfb->xScale = gfxCtx->xScale; cfb->yScale = gfxCtx->yScale; cfb->unk_10 = 0; @@ -243,10 +243,10 @@ void Graph_TaskSet00(GraphicsContext* gfxCtx) { if (1) {} - gfxCtx->schedMsgQueue = &gSchedContext.cmdQueue; + gfxCtx->schedMsgQueue = &gScheduler.cmdQueue; - osSendMesg(&gSchedContext.cmdQueue, (OSMesg)scTask, OS_MESG_BLOCK); - Sched_SendEntryMsg(&gSchedContext); + osSendMesg(&gScheduler.cmdQueue, (OSMesg)scTask, OS_MESG_BLOCK); + Sched_Notify(&gScheduler); } void Graph_Update(GraphicsContext* gfxCtx, GameState* gameState) { diff --git a/src/code/irqmgr.c b/src/code/irqmgr.c index d29be50823..39e17b8ed1 100644 --- a/src/code/irqmgr.c +++ b/src/code/irqmgr.c @@ -2,18 +2,20 @@ * @file irqmgr.c * * This file implements a manager for forwarding three key system interrupt events to - * registered clients. The architecture of this system appears to be derived in part from - * the libultra sched module. + * registered clients. + * Together with sched.c, these systems implement the libultra video and task scheduling + * model from the libultra "sched" module, with improved functionality in the handling of + * Pre-NMI related events. * * The interrupts the IRQ manager deals with are: - * - VI Retrace + * - Vertical Retrace * This event is sent to the IRQ manager by the OS VI manager which only supports * the forwarding of VI events to a single message queue. The IRQ manager will - * forward these events to every registered client. VI retrace events are received - * when the Video Interface has reached the start of the vertical blanking interval, - * happening at approximately 60Hz on NTSC and 50Hz on PAL. Many threads sit idle - * until a VI Retrace event wakes them up, at which point they will perform their - * task and then return to idle to await the next retrace. + * forward these events to every registered client. Vertical retrace events are + * received when the Video Interface has reached the start of the vertical blanking + * interval, happening at approximately 60Hz on NTSC and 50Hz on PAL. Many threads + * sit idle until a vertical retrace event wakes them up, at which point they will + * perform their task and then return to idle to await the next retrace. * * - Pre-NMI * This event is sent to the IRQ manager by the OS Interrupt Handler when the reset @@ -27,6 +29,8 @@ * is not to be confused with the hardware NMI interrupt signalled when the CPU is * to fully reset, as by the time that interrupt is received there is no time left * to do anything. + * + * @see sched.c */ #include "global.h" #include "vt.h" @@ -183,7 +187,7 @@ void IrqMgr_CheckStacks(void) { } } -void IrqMgr_HandlePRENMI450(IrqMgr* irqMgr) { +void IrqMgr_HandlePreNMI450(IrqMgr* irqMgr) { u64 nmi = IRQ_RESET_STATUS_NMI; // required to match gIrqMgrResetStatus = nmi; @@ -195,7 +199,7 @@ void IrqMgr_HandlePRENMI450(IrqMgr* irqMgr) { IrqMgr_SendMesgToClients(irqMgr, (OSMesg)&irqMgr->nmiMsg); } -void IrqMgr_HandlePRENMI480(IrqMgr* irqMgr) { +void IrqMgr_HandlePreNMI480(IrqMgr* irqMgr) { u32 result; // Schedule a PRENMI500 message to be handled in 20ms @@ -212,13 +216,15 @@ void IrqMgr_HandlePRENMI480(IrqMgr* irqMgr) { } } -void IrqMgr_HandlePRENMI500(IrqMgr* irqMgr) { +void IrqMgr_HandlePreNMI500(IrqMgr* irqMgr) { IrqMgr_CheckStacks(); } /** - * Runs on each VI retrace, measures the time elapsed between the first and second VI retrace - * and dispatches VI retrace messages to each registered Irq Client + * Runs on each vertical retrace + * + * Measures the time elapsed between the first and second vertical retrace and + * dispatches vertical retrace messages to each registered Irq Client */ void IrqMgr_HandleRetrace(IrqMgr* irqMgr) { if (gIrqMgrRetraceTime == 0) { @@ -257,20 +263,20 @@ void IrqMgr_ThreadEntry(void* arg) { osSyncPrintf("PRENMI450_MSG\n"); // "Scheduler: Receives PRENMI450 message" osSyncPrintf("スケジューラ:PRENMI450メッセージを受信\n"); - IrqMgr_HandlePRENMI450(irqMgr); + IrqMgr_HandlePreNMI450(irqMgr); break; case IRQ_PRENMI480_MSG: osSyncPrintf("PRENMI480_MSG\n"); // "Scheduler: Receives PRENMI480 message" osSyncPrintf("スケジューラ:PRENMI480メッセージを受信\n"); - IrqMgr_HandlePRENMI480(irqMgr); + IrqMgr_HandlePreNMI480(irqMgr); break; case IRQ_PRENMI500_MSG: osSyncPrintf("PRENMI500_MSG\n"); // "Scheduler: Receives PRENMI500 message" osSyncPrintf("スケジューラ:PRENMI500メッセージを受信\n"); exit = true; - IrqMgr_HandlePRENMI500(irqMgr); + IrqMgr_HandlePreNMI500(irqMgr); break; default: // "Unexpected message received" diff --git a/src/code/main.c b/src/code/main.c index f2a1340f64..441ae7e5e2 100644 --- a/src/code/main.c +++ b/src/code/main.c @@ -6,7 +6,7 @@ s32 gScreenHeight = SCREEN_HEIGHT; u32 gSystemHeapSize = 0; PreNmiBuff* gAppNmiBufferPtr; -SchedContext gSchedContext; +Scheduler gScheduler; PadMgr gPadMgr; IrqMgr gIrqMgr; u32 gSegments[NUM_SEGMENTS]; @@ -80,13 +80,12 @@ void Main(void* arg) { osSyncPrintf("タスクスケジューラの初期化\n"); // "Initialize the task scheduler" StackCheck_Init(&sSchedStackInfo, sSchedStack, STACK_TOP(sSchedStack), 0, 0x100, "sched"); - Sched_Init(&gSchedContext, STACK_TOP(sSchedStack), THREAD_PRI_SCHED, D_80013960, 1, &gIrqMgr); + Sched_Init(&gScheduler, STACK_TOP(sSchedStack), THREAD_PRI_SCHED, D_80013960, 1, &gIrqMgr); IrqMgr_AddClient(&gIrqMgr, &irqClient, &irqMgrMsgQueue); StackCheck_Init(&sAudioStackInfo, sAudioStack, STACK_TOP(sAudioStack), 0, 0x100, "audio"); - AudioMgr_Init(&gAudioMgr, STACK_TOP(sAudioStack), THREAD_PRI_AUDIOMGR, THREAD_ID_AUDIOMGR, &gSchedContext, - &gIrqMgr); + AudioMgr_Init(&gAudioMgr, STACK_TOP(sAudioStack), THREAD_PRI_AUDIOMGR, THREAD_ID_AUDIOMGR, &gScheduler, &gIrqMgr); StackCheck_Init(&sPadMgrStackInfo, sPadMgrStack, STACK_TOP(sPadMgrStack), 0, 0x100, "padmgr"); PadMgr_Init(&gPadMgr, &sSerialEventQueue, &gIrqMgr, THREAD_ID_PADMGR, THREAD_PRI_PADMGR, STACK_TOP(sPadMgrStack)); diff --git a/src/code/rcp_utils.c b/src/code/rcp_utils.c index d9fc9cd858..5c14c39f98 100644 --- a/src/code/rcp_utils.c +++ b/src/code/rcp_utils.c @@ -3,6 +3,7 @@ #define printSpStatus(x, name) \ if (x & SP_STATUS_##name) \ osSyncPrintf(#name " ") + #define printDpStatus(x, name) \ if (x & DPC_STATUS_##name) \ osSyncPrintf(#name " ") @@ -44,9 +45,11 @@ void RcpUtils_PrintRegisterStatus(void) { osSyncPrintf("\n"); } -void RcpUtils_Reset() { +void RcpUtils_Reset(void) { RcpUtils_PrintRegisterStatus(); + // Flush the RDP pipeline and freeze clock counter osDpSetStatus(DPC_SET_FREEZE | DPC_SET_FLUSH); + // Halt the RSP, disable interrupt on break and set "task done" signal __osSpSetStatus(SP_SET_HALT | SP_SET_SIG2 | SP_CLR_INTR_BREAK); RcpUtils_PrintRegisterStatus(); } diff --git a/src/code/sched.c b/src/code/sched.c index cdf22616e4..1ab5e99f4f 100644 --- a/src/code/sched.c +++ b/src/code/sched.c @@ -1,32 +1,77 @@ +/** + * @file sched.c + * + * This file implements a cooperative scheduler for managing tasks that run on the RSP and RDP + * asynchronously such as graphics and audio processing. Tasks are prepared and sent to it from + * other threads, where it is placed in a queue until the necessary resources are available. Tasks + * are usually ran in the order they are received, with one exception described below. Tasks can + * also request, through flags, whether the scheduler should swap the active framebuffer once the + * task completes. + * Together with irqmgr.c, these systems implement the libultra video and task scheduling model from + * the libultra "sched" module. Notably, the original sched module supports a wider range of ways to + * communicate with the RDP, while the Zelda 64 implementation only allows the RSP microcode to send + * commands to the RDP. The Zelda 64 implementation also has more complex behavior involving the + * framebuffers. + * + * There are four task types supported: + * + * M_NULTASK + * "NULL" tasks. + * Tasks of this type don't perform any operations, it can be used to "flush" the task queue. Threads + * can wait for this task to complete to ensure there are no more tasks queued in the scheduler. + * + * M_GFXTASK + * Graphics Processing tasks. + * Only these tasks can make use of the RDP. + * + * M_AUDTASK + * Audio Processing tasks. + * These tasks have a higher "priority" than other tasks. If an audio task is enqueued and another + * task is currently running, the scheduler will signal to the running task that it should "yield" + * the RSP to the audio task. The running task will save its current state and stop running, allowing + * the scheduler to send the audio task. This ensures that audio data is always available to be consumed + * by the audio DAC even if another task such as graphics is running slow, avoiding undesirable sound + * artifacts. This is the meaning of "cooperative" scheduler, the current task must acknowledge the + * yield request rather than be immediately interrupted as it would be in a preemptive scheduler. + * + * M_NJPEGTASK + * JPEG to RGBA16 decoding tasks. + * + * @see irqmgr.c + */ #include "global.h" #define RSP_DONE_MSG 667 #define RDP_DONE_MSG 668 -#define ENTRY_MSG 670 +#define NOTIFY_MSG 670 // original name: ENTRY_MSG -// data vs32 sLogScheduler = false; -// bss OSTime sRSPGFXStartTime; OSTime sRSPAudioStartTime; OSTime sRSPOtherStartTime; OSTime sRDPStartTime; -void Sched_SwapFrameBuffer(CfbInfo* cfbInfo) { +/** + * Set the current framebuffer to the swapbuffer pointed to by the provided cfb + */ +void Sched_SwapFrameBufferImpl(CfbInfo* cfbInfo) { u16 width; LogUtils_CheckValidPointer("cfbinfo->swapbuffer", cfbInfo->swapBuffer, "../sched.c", 340); + if (cfbInfo->swapBuffer != NULL) { + // Register the swapbuffer to display on next VI osViSwapBuffer(cfbInfo->swapBuffer); - cfbInfo->updateRate2 = cfbInfo->updateRate; + cfbInfo->updateTimer = cfbInfo->updateRate; if (sLogScheduler) { osSyncPrintf("osViSwapBuffer %08x %08x %08x\n", osViGetCurrentFramebuffer(), osViGetNextFramebuffer(), - (cfbInfo != NULL ? cfbInfo->swapBuffer : NULL)); + (cfbInfo != NULL) ? cfbInfo->swapBuffer : NULL); } - width = cfbInfo->viMode != NULL ? cfbInfo->viMode->comRegs.width : (u32)gScreenWidth; - Fault_SetFrameBuffer(cfbInfo->swapBuffer, width, 0x10); + + width = (cfbInfo->viMode != NULL) ? cfbInfo->viMode->comRegs.width : (u32)gScreenWidth; + Fault_SetFrameBuffer(cfbInfo->swapBuffer, width, 16); if (HREG(80) == 0xD && HREG(95) != 0xD) { HREG(81) = 0; @@ -46,39 +91,41 @@ void Sched_SwapFrameBuffer(CfbInfo* cfbInfo) { HREG(95) = 0xD; } if (HREG(80) == 0xD && HREG(81) == 2) { - osViSetSpecialFeatures(HREG(82) != 0 ? OS_VI_GAMMA_ON : OS_VI_GAMMA_OFF); - osViSetSpecialFeatures(HREG(83) != 0 ? OS_VI_DITHER_FILTER_ON : OS_VI_DITHER_FILTER_OFF); - osViSetSpecialFeatures(HREG(84) != 0 ? OS_VI_GAMMA_DITHER_ON : OS_VI_GAMMA_DITHER_OFF); - osViSetSpecialFeatures(HREG(85) != 0 ? OS_VI_DIVOT_ON : OS_VI_DIVOT_OFF); + osViSetSpecialFeatures((HREG(82) != 0) ? OS_VI_GAMMA_ON : OS_VI_GAMMA_OFF); + osViSetSpecialFeatures((HREG(83) != 0) ? OS_VI_DITHER_FILTER_ON : OS_VI_DITHER_FILTER_OFF); + osViSetSpecialFeatures((HREG(84) != 0) ? OS_VI_GAMMA_DITHER_ON : OS_VI_GAMMA_DITHER_OFF); + osViSetSpecialFeatures((HREG(85) != 0) ? OS_VI_DIVOT_ON : OS_VI_DIVOT_OFF); } } cfbInfo->unk_10 = 0; } -void func_800C84E4(SchedContext* sc, CfbInfo* cfbInfo) { - if (sc->unk_24C != 0) { - sc->unk_24C = 0; +void Sched_SwapFrameBuffer(Scheduler* sc, CfbInfo* cfbInfo) { + if (sc->isFirstSwap) { + sc->isFirstSwap = false; if (gIrqMgrResetStatus == IRQ_RESET_STATUS_IDLE) { - ViConfig_UpdateVi(0); + ViConfig_UpdateVi(false); } } - - Sched_SwapFrameBuffer(cfbInfo); + Sched_SwapFrameBufferImpl(cfbInfo); } -void Sched_HandleReset(SchedContext* sc) { +void Sched_HandlePreNMI(Scheduler* sc) { OSTime now; if (sc->curRSPTask != NULL) { now = osGetTime(); if (sc->curRSPTask->framebuffer == NULL) { + // audio and jpeg tasks end up in here LOG_TIME("(((u64)(now - audio_rsp_start_time)*(1000000LL/15625LL))/((62500000LL*3/4)/15625LL))", OS_CYCLES_TO_USEC(now - sRSPAudioStartTime), "../sched.c", 421); } else if (OS_CYCLES_TO_USEC(now - sRSPGFXStartTime) > 1000000 || OS_CYCLES_TO_USEC(now - sRDPStartTime) > 1000000) { + // More than 1 second since the RSP or RDP tasks began, halt the RSP and RDP RcpUtils_Reset(); + // Manually send RSP/RDP done messages to the scheduler interrupt queue if appropriate if (sc->curRSPTask != NULL) { LOG_TIME("(((u64)(now - graph_rsp_start_time)*(1000000LL/15625LL))/((62500000LL*3/4)/15625LL))", OS_CYCLES_TO_USEC(now - sRSPGFXStartTime), "../sched.c", 427); @@ -93,11 +140,15 @@ void Sched_HandleReset(SchedContext* sc) { } } -void Sched_HandleStart(SchedContext* sc) { - ViConfig_UpdateVi(1); +void Sched_HandleNMI(Scheduler* sc) { + // black the screen and reset the VI y scale just in time for NMI reset + ViConfig_UpdateVi(true); } -void Sched_QueueTask(SchedContext* sc, OSScTask* task) { +/** + * Enqueue a task to either the audio task list or the gfx task list + */ +void Sched_QueueTask(Scheduler* sc, OSScTask* task) { s32 type = task->list.t.type; ASSERT((type == M_AUDTASK) || (type == M_GFXTASK) || (type == M_NJPEGTASK) || (type == M_NULTASK), @@ -109,18 +160,22 @@ void Sched_QueueTask(SchedContext* sc, OSScTask* task) { // "You have entered an audio task" osSyncPrintf("オーディオタスクをエントリしました\n"); } + // Add to audio queue if (sc->audioListTail != NULL) { sc->audioListTail->next = task; } else { sc->audioListHead = task; } sc->audioListTail = task; - sc->doAudio = 1; + + // Set audio flag + sc->doAudio = true; } else { if (sLogScheduler) { - osSyncPrintf("グラフタスクをエントリしました\n"); // "Entered graph task" + // "Entered graph task" + osSyncPrintf("グラフタスクをエントリしました\n"); } - + // Add to graphics queue if (sc->gfxListTail != NULL) { sc->gfxListTail->next = task; } else { @@ -129,15 +184,17 @@ void Sched_QueueTask(SchedContext* sc, OSScTask* task) { sc->gfxListTail = task; } task->next = NULL; - task->state = task->flags & (OS_SC_NEEDS_RDP | OS_SC_NEEDS_RSP); + task->state = task->flags & OS_SC_RCP_MASK; } -void Sched_Yield(SchedContext* sc) { +void Sched_Yield(Scheduler* sc) { if (!(sc->curRSPTask->state & OS_SC_YIELD)) { + // Not already been asked to yield ASSERT(sc->curRSPTask->list.t.type != M_AUDTASK, "sc->curRSPTask->list.t.type != M_AUDTASK", "../sched.c", 496); sc->curRSPTask->state |= OS_SC_YIELD; + // Send yield request osSpTaskYield(); if (sLogScheduler) { @@ -146,11 +203,16 @@ void Sched_Yield(SchedContext* sc) { } } -OSScTask* func_800C89D4(SchedContext* sc, OSScTask* task) { +/** + * Check if the framebuffer the gfx task wants to use is allowed + */ +OSScTask* Sched_GfxTaskFramebufferValid(Scheduler* sc, OSScTask* task) { if (task == NULL) { return NULL; } + // If there are pending swaps, wait until there are none (within 2 VI) + if (sc->pendingSwapBuf1 != NULL) { if (0) { ASSERT(sc->pendingSwapBuf1 != NULL, "sc->pending_swapbuffer1", "../sched.c", UNK_LINE); @@ -165,48 +227,83 @@ OSScTask* func_800C89D4(SchedContext* sc, OSScTask* task) { return NULL; } - if ((sc->pendingSwapBuf2 != NULL ? sc->pendingSwapBuf2->swapBuffer : NULL) == task->framebuffer->fb1) { + // If the task's framebuffer is one of the pending swaps or NULL. + // In conjunction with the above, these checks are redundant as the pending swap buffers will only be + // NULL here, so these could have been simplified to checks for the task's framebuffer being non-NULL. + + if (((sc->pendingSwapBuf2 != NULL) ? sc->pendingSwapBuf2->swapBuffer : NULL) == task->framebuffer->framebuffer) { return NULL; } - if ((sc->pendingSwapBuf1 != NULL ? sc->pendingSwapBuf1->swapBuffer : NULL) == task->framebuffer->fb1) { + if (((sc->pendingSwapBuf1 != NULL) ? sc->pendingSwapBuf1->swapBuffer : NULL) == task->framebuffer->framebuffer) { return NULL; } - if (osViGetCurrentFramebuffer() == (u32*)task->framebuffer->fb1) { + // If the task's framebuffer is the current framebuffer, abort + + if (osViGetCurrentFramebuffer() == task->framebuffer->framebuffer) { return NULL; } return task; } -s32 Sched_Schedule(SchedContext* sc, OSScTask** sp, OSScTask** dp, s32 state) { - s32 ret = state; +/** + * Schedules the next tasks to run on the RSP and RDP + * + * @param sc Scheduler + * @param spTaskOut Next task to run on the RSP + * @param dpTaskOut Next task to run on the RDP + * @param state Bits containing whether the RSP and RDP are currently in use + * @return Bits containing whether the RSP and RDP will be in use after starting the next tasks + */ +s32 Sched_Schedule(Scheduler* sc, OSScTask** spTaskOut, OSScTask** dpTaskOut, s32 state) { + s32 nextState = state; OSScTask* gfxTask = sc->gfxListHead; OSScTask* audioTask = sc->audioListHead; - if (sc->doAudio && (ret & OS_SC_SP)) { - *sp = audioTask; - ret &= ~OS_SC_SP; - sc->doAudio = 0; + if (sc->doAudio && (state & OS_SC_SP)) { + // Audio Task, RSP is available + + // Return next audio task + *spTaskOut = audioTask; + // RSP required + nextState &= ~OS_SC_SP; + //! @bug If there is more than one audio task in the queue at any time, unsetting doAudio here + //! will cause only one task to be processed until a new audio task is enqueued. In practice, audio + //! tasks are sent infrequently enough that there are never two audio tasks in the queue. + sc->doAudio = false; + // Advance task queue sc->audioListHead = sc->audioListHead->next; if (sc->audioListHead == NULL) { sc->audioListTail = NULL; } } else if (gfxTask != NULL) { - if (gfxTask->state & OS_SC_YIELDED || !(gfxTask->flags & OS_SC_NEEDS_RDP)) { - if (ret & OS_SC_SP) { - *sp = gfxTask; - ret &= ~OS_SC_SP; + // GFX Task + + if ((gfxTask->state & OS_SC_YIELDED) || !(gfxTask->flags & OS_SC_NEEDS_RDP)) { + // If this is a yielded GFX task, or the RDP is not needed for this GFX task + + if (state & OS_SC_SP) { + // If the RSP is available, return next graphics task + + *spTaskOut = gfxTask; + // RSP required + nextState &= ~OS_SC_SP; + // Advance task queue sc->gfxListHead = sc->gfxListHead->next; if (sc->gfxListHead == NULL) { sc->gfxListTail = NULL; } } - } else if (ret == (OS_SC_SP | OS_SC_DP)) { - if (gfxTask->framebuffer == NULL || func_800C89D4(sc, gfxTask) != NULL) { - *sp = *dp = gfxTask; - ret &= ~(OS_SC_SP | OS_SC_DP); + } else if (state == (OS_SC_SP | OS_SC_DP)) { + // Both the RSP and RDP are available, check requested framebuffer + if (gfxTask->framebuffer == NULL || Sched_GfxTaskFramebufferValid(sc, gfxTask) != NULL) { + // Return next graphics task + *spTaskOut = *dpTaskOut = gfxTask; + // RSP and RDP both required + nextState &= ~(OS_SC_SP | OS_SC_DP); + // Advance task queue sc->gfxListHead = sc->gfxListHead->next; if (sc->gfxListHead == NULL) { sc->gfxListTail = NULL; @@ -214,41 +311,65 @@ s32 Sched_Schedule(SchedContext* sc, OSScTask** sp, OSScTask** dp, s32 state) { } } } - return ret; + return nextState; } -void func_800C8BC4(SchedContext* sc, OSScTask* task) { +/** + * Sets the next framebuffer to the framebuffer associated to `task`. + * If there is no current buffer or it is time to swap, this buffer will be swapped to + * immediately, otherwise it will be swapped to later in Sched_HandleRetrace. + * + * @see Sched_HandleRetrace + */ +void Sched_SetNextFramebufferFromTask(Scheduler* sc, OSScTask* task) { if (sc->pendingSwapBuf1 == NULL) { sc->pendingSwapBuf1 = task->framebuffer; LogUtils_CheckValidPointer("sc->pending_swapbuffer1", sc->pendingSwapBuf1, "../sched.c", 618); - if ((sc->curBuf == NULL) || (sc->curBuf->updateRate2 < 1)) { - func_800C84E4(sc, task->framebuffer); + if (sc->curBuf == NULL || sc->curBuf->updateTimer <= 0) { + Sched_SwapFrameBuffer(sc, task->framebuffer); } } } -u32 Sched_IsComplete(SchedContext* sc, OSScTask* task) { +/** + * Checks if the task is done, i.e. it is no longer running on either the RSP or RDP. + * If so, send a message to the task's message queue if there is one, and swap the framebuffer + * if required. + */ +u32 Sched_TaskComplete(Scheduler* sc, OSScTask* task) { + // Check that the task has released both the RSP and RDP. For graphics tasks that use both, + // the RSP will typically finish before the RDP, as the RSP can halt while the RDP is still + // working through the command buffer. if (!(task->state & (OS_SC_DP | OS_SC_SP))) { + // Send a message to the notify queue if there is one if (task->msgQueue != NULL) { osSendMesg(task->msgQueue, task->msg, OS_MESG_BLOCK); } + // Swap the framebuffer if needed if (task->flags & OS_SC_SWAPBUFFER) { - func_800C8BC4(sc, task); + Sched_SetNextFramebufferFromTask(sc, task); } - - return 1; + return true; } - - return 0; + return false; } -void Sched_RunTask(SchedContext* sc, OSScTask* spTask, OSScTask* dpTask) { +/** + * Runs the next tasks. The scheduler doesn't support running RDP tasks without + * passthrough via the RSP, if there is no RSP task to run then the RDP task will + * also do nothing. + */ +void Sched_RunTask(Scheduler* sc, OSScTask* spTask, OSScTask* dpTask) { ASSERT(sc->curRSPTask == NULL, "sc->curRSPTask == NULL", "../sched.c", 663); + + // If there is no RSP task there's nothing to do. if (spTask != NULL) { if (spTask->list.t.type == M_NULTASK) { + // NULTASK is a sync/flush operation, clear current RSP and RDP tasks + // and unset flags for this task if (spTask->flags & OS_SC_NEEDS_RSP) { spTask->state &= ~OS_SC_SP; sc->curRSPTask = NULL; @@ -257,14 +378,17 @@ void Sched_RunTask(SchedContext* sc, OSScTask* spTask, OSScTask* dpTask) { spTask->state &= ~OS_SC_DP; sc->curRDPTask = NULL; } - Sched_IsComplete(sc, spTask); + // Finalize + Sched_TaskComplete(sc, spTask); return; } spTask->state &= ~(OS_SC_YIELD | OS_SC_YIELDED); + // Write back data cache and load the OSTask into the RSP osWritebackDCacheAll(); osSpTaskLoad(&spTask->list); + // Begin profiling timers if (spTask->list.t.type == M_AUDTASK) { sRSPAudioStartTime = osGetTime(); } else if (spTask->list.t.type == M_GFXTASK) { @@ -273,14 +397,19 @@ void Sched_RunTask(SchedContext* sc, OSScTask* spTask, OSScTask* dpTask) { sRSPOtherStartTime = osGetTime(); } + // Run RSP osSpTaskStartGo(&spTask->list); + if (sLogScheduler) { osSyncPrintf( "%08d:osSpTaskStartGo(%08x) %s\n", (u32)OS_CYCLES_TO_USEC(osGetTime()), &spTask->list, (spTask->list.t.type == M_AUDTASK ? "AUDIO" : (spTask->list.t.type == M_GFXTASK ? "GRAPH" : "OTHER"))); } + + // Set currently running RSP task sc->curRSPTask = spTask; + // If the task also uses the RDP, set current running RDP task if (spTask == dpTask && sc->curRDPTask == NULL) { sc->curRDPTask = dpTask; sRDPStartTime = sRSPGFXStartTime; @@ -288,17 +417,26 @@ void Sched_RunTask(SchedContext* sc, OSScTask* spTask, OSScTask* dpTask) { } } -void Sched_HandleEntry(SchedContext* sc) { +/** + * Runs when the scheduler has received a notification, either from another thread or + * on VI Retrace. Tasks that have been sent to it will be enqueued onto the audio or + * gfx task queue and one may be ran if the RSP is available. + */ +void Sched_HandleNotification(Scheduler* sc) { OSScTask* nextRSP = NULL; OSScTask* nextRDP = NULL; s32 state; OSScTask* task = NULL; + // Enqueue any tasks sent by other threads while (osRecvMesg(&sc->cmdQueue, (OSMesg*)&task, OS_MESG_NOBLOCK) != -1) { Sched_QueueTask(sc, task); } - if (sc->doAudio != 0 && sc->curRSPTask != NULL) { + // If an audio task has been enqueued and there is currently an RSP task running, + // signal to the currently running task to yield the RSP so that the audio task may + // be ran as soon as possible. + if (sc->doAudio && sc->curRSPTask != NULL) { if (sLogScheduler) { osSyncPrintf("[YIELD B]"); } @@ -306,7 +444,8 @@ void Sched_HandleEntry(SchedContext* sc) { return; } - state = ((sc->curRSPTask == 0) * 2) | (sc->curRDPTask == 0); + // Run next task in the queue if there is one and the necessary resources are available + state = ((sc->curRSPTask == NULL) << 1) | (sc->curRDPTask == NULL); if (Sched_Schedule(sc, &nextRSP, &nextRDP, state) != state) { Sched_RunTask(sc, nextRSP, nextRDP); } @@ -315,42 +454,51 @@ void Sched_HandleEntry(SchedContext* sc) { } } -void Sched_HandleRetrace(SchedContext* sc) { +void Sched_HandleRetrace(Scheduler* sc) { if (sLogScheduler) { osSyncPrintf("%08d:scHandleRetrace %08x\n", (u32)OS_CYCLES_TO_USEC(osGetTime()), osViGetCurrentFramebuffer()); } ViConfig_UpdateBlack(); - sc->retraceCnt++; + sc->retraceCount++; - if (osViGetCurrentFramebuffer() == (u32*)(sc->pendingSwapBuf1 != NULL ? sc->pendingSwapBuf1->swapBuffer : NULL)) { + // Retrace handlers run after VI context swap. The last swap buffer may now be the current buffer. + if (osViGetCurrentFramebuffer() == ((sc->pendingSwapBuf1 != NULL) ? sc->pendingSwapBuf1->swapBuffer : NULL)) { if (sc->curBuf != NULL) { sc->curBuf->unk_10 = 0; } - if (sc->pendingSwapBuf1 != NULL) { sc->pendingSwapBuf1->unk_10 = 0; } + // Advance buffers sc->curBuf = sc->pendingSwapBuf1; sc->pendingSwapBuf1 = sc->pendingSwapBuf2; sc->pendingSwapBuf2 = NULL; } + if (sc->curBuf != NULL) { - if (sc->curBuf->updateRate2 > 0) { - sc->curBuf->updateRate2--; + // Swap the framebuffer when the update timer runs out + if (sc->curBuf->updateTimer > 0) { + sc->curBuf->updateTimer--; } - if ((sc->curBuf->updateRate2 <= 0) && (sc->pendingSwapBuf1 != NULL)) { - func_800C84E4(sc, sc->pendingSwapBuf1); + if (sc->curBuf->updateTimer <= 0 && sc->pendingSwapBuf1 != NULL) { + Sched_SwapFrameBuffer(sc, sc->pendingSwapBuf1); } } + if (sLogScheduler) { osSyncPrintf("%08x %08x %08x %d\n", osViGetCurrentFramebuffer(), osViGetNextFramebuffer(), - sc->pendingSwapBuf1 != NULL ? sc->pendingSwapBuf1->swapBuffer : NULL, - sc->curBuf != NULL ? sc->curBuf->updateRate2 : 0); + (sc->pendingSwapBuf1 != NULL) ? sc->pendingSwapBuf1->swapBuffer : NULL, + (sc->curBuf != NULL) ? sc->curBuf->updateTimer : 0); } - Sched_HandleEntry(sc); + + // Run the notification handler to enqueue any waiting tasks and possibly run one + Sched_HandleNotification(sc); } -void Sched_HandleRSPDone(SchedContext* sc) { +/** + * RSP has signalled that the task has either completed or yielded. + */ +void Sched_HandleRSPDone(Scheduler* sc) { OSScTask* curRSPTask; OSScTask* nextRSP = NULL; OSScTask* nextRDP = NULL; @@ -358,6 +506,7 @@ void Sched_HandleRSPDone(SchedContext* sc) { ASSERT(sc->curRSPTask != NULL, "sc->curRSPTask", "../sched.c", 819); + // Task profiling if (sc->curRSPTask->list.t.type == M_AUDTASK) { gRSPAudioTotalTime += osGetTime() - sRSPAudioStartTime; } else if (sc->curRSPTask->list.t.type == M_GFXTASK) { @@ -366,16 +515,21 @@ void Sched_HandleRSPDone(SchedContext* sc) { gRSPOtherTotalTime += osGetTime() - sRSPOtherStartTime; } + // Clear current RSP task curRSPTask = sc->curRSPTask; sc->curRSPTask = NULL; + if (sLogScheduler) { - osSyncPrintf("RSP DONE %d %d", curRSPTask->state & 0x10, osSpTaskYielded(&curRSPTask->list)); + osSyncPrintf("RSP DONE %d %d", curRSPTask->state & OS_SC_YIELD, osSpTaskYielded(&curRSPTask->list)); } - if (curRSPTask->state & OS_SC_YIELD && osSpTaskYielded(&curRSPTask->list)) { + + if ((curRSPTask->state & OS_SC_YIELD) && osSpTaskYielded(&curRSPTask->list)) { if (sLogScheduler) { osSyncPrintf("[YIELDED]\n"); } + // Task yielded, set yielded state curRSPTask->state |= OS_SC_YIELDED; + // Add it to the front of the queue curRSPTask->next = sc->gfxListHead; sc->gfxListHead = curRSPTask; if (sc->gfxListTail == NULL) { @@ -385,10 +539,12 @@ void Sched_HandleRSPDone(SchedContext* sc) { if (sLogScheduler) { osSyncPrintf("[NOT YIELDED]\n"); } + // Task has completed on the RSP, unset RSP flag and check if the task is fully complete curRSPTask->state &= ~OS_SC_SP; - Sched_IsComplete(sc, curRSPTask); + Sched_TaskComplete(sc, curRSPTask); } + // Run next task in the queue if there is one and the necessary resources are available state = ((sc->curRSPTask == NULL) << 1) | (sc->curRDPTask == NULL); if (Sched_Schedule(sc, &nextRSP, &nextRDP, state) != state) { Sched_RunTask(sc, nextRSP, nextRDP); @@ -398,19 +554,31 @@ void Sched_HandleRSPDone(SchedContext* sc) { } } -void Sched_HandleRDPDone(SchedContext* sc) { +/** + * RDP has signalled task done upon reaching a DPFullSync command + */ +void Sched_HandleRDPDone(Scheduler* sc) { OSScTask* curTask; OSScTask* nextRSP = NULL; OSScTask* nextRDP = NULL; s32 state; + // Task profiling gRDPTotalTime = osGetTime() - sRDPStartTime; + + // Sanity check ASSERT(sc->curRDPTask != NULL, "sc->curRDPTask", "../sched.c", 878); ASSERT(sc->curRDPTask->list.t.type == M_GFXTASK, "sc->curRDPTask->list.t.type == M_GFXTASK", "../sched.c", 879); + + // Clear current RDP task curTask = sc->curRDPTask; sc->curRDPTask = NULL; + + // Task has completed on the RDP, unset RDP flag and check if the task is fully complete curTask->state &= ~OS_SC_DP; - Sched_IsComplete(sc, curTask); + Sched_TaskComplete(sc, curTask); + + // Run next task in the queue if there is one and the necessary resources are available state = ((sc->curRSPTask == NULL) << 1) | (sc->curRDPTask == NULL); if (Sched_Schedule(sc, &nextRSP, &nextRDP, state) != state) { Sched_RunTask(sc, nextRSP, nextRDP); @@ -420,17 +588,24 @@ void Sched_HandleRDPDone(SchedContext* sc) { } } -void Sched_SendEntryMsg(SchedContext* sc) { +/** + * Called by other threads in order to wake the scheduler up immediately to enqueue and + * possibly run a task that has been sent to the task queue. Otherwise, any pending tasks + * will be enqueued on next vertical retrace. + * + * Original name: osScKickEntryMsg + */ +void Sched_Notify(Scheduler* sc) { if (sLogScheduler) { osSyncPrintf("osScKickEntryMsg\n"); } - osSendMesg(&sc->interruptQueue, (OSMesg)ENTRY_MSG, OS_MESG_BLOCK); + osSendMesg(&sc->interruptQueue, (OSMesg)NOTIFY_MSG, OS_MESG_BLOCK); } void Sched_ThreadEntry(void* arg) { OSMesg msg = NULL; - SchedContext* sc = (SchedContext*)arg; + Scheduler* sc = (Scheduler*)arg; while (true) { if (sLogScheduler) { @@ -438,14 +613,15 @@ void Sched_ThreadEntry(void* arg) { osSyncPrintf("%08d:待機中\n", (u32)OS_CYCLES_TO_USEC(osGetTime())); } + // Await interrupt messages, either from the OS, IrqMgr, or another thread osRecvMesg(&sc->interruptQueue, &msg, OS_MESG_BLOCK); switch ((s32)msg) { - case ENTRY_MSG: + case NOTIFY_MSG: if (sLogScheduler) { osSyncPrintf("%08d:ENTRY_MSG\n", (u32)OS_CYCLES_TO_USEC(osGetTime())); } - Sched_HandleEntry(sc); + Sched_HandleNotification(sc); continue; case RSP_DONE_MSG: if (sLogScheduler) { @@ -461,22 +637,24 @@ void Sched_ThreadEntry(void* arg) { continue; } switch (((OSScMsg*)msg)->type) { - case 1: + case OS_SC_RETRACE_MSG: Sched_HandleRetrace(sc); continue; - case 4: - Sched_HandleReset(sc); + case OS_SC_PRE_NMI_MSG: + Sched_HandlePreNMI(sc); continue; - case 3: - Sched_HandleStart(sc); + case OS_SC_NMI_MSG: + Sched_HandleNMI(sc); continue; } } } -void Sched_Init(SchedContext* sc, void* stack, OSPri priority, UNK_TYPE arg3, UNK_TYPE arg4, IrqMgr* irqMgr) { - bzero(sc, sizeof(SchedContext)); - sc->unk_24C = 1; +void Sched_Init(Scheduler* sc, void* stack, OSPri priority, UNK_TYPE arg3, UNK_TYPE arg4, IrqMgr* irqMgr) { + bzero(sc, sizeof(Scheduler)); + sc->isFirstSwap = true; + + // Create message queues for receiving interrupt events and tasks osCreateMesgQueue(&sc->interruptQueue, sc->interruptMsgBuf, ARRAY_COUNT(sc->interruptMsgBuf)); osCreateMesgQueue(&sc->cmdQueue, sc->cmdMsgBuf, ARRAY_COUNT(sc->cmdMsgBuf)); osSetEventMesg(OS_EVENT_SP, &sc->interruptQueue, RSP_DONE_MSG); diff --git a/src/code/z_jpeg.c b/src/code/z_jpeg.c index 2df2f57cc9..9e8925264c 100644 --- a/src/code/z_jpeg.c +++ b/src/code/z_jpeg.c @@ -60,8 +60,8 @@ void Jpeg_ScheduleDecoderTask(JpegContext* ctx) { ctx->scTask.framebuffer = NULL; ctx->scTask.list = sJpegTask; - osSendMesg(&gSchedContext.cmdQueue, (OSMesg)&ctx->scTask, OS_MESG_BLOCK); - Sched_SendEntryMsg(&gSchedContext); // osScKickEntryMsg + osSendMesg(&gScheduler.cmdQueue, (OSMesg)&ctx->scTask, OS_MESG_BLOCK); + Sched_Notify(&gScheduler); osRecvMesg(&ctx->mq, NULL, OS_MESG_BLOCK); } @@ -251,7 +251,7 @@ s32 Jpeg_Decode(void* data, void* zbuffer, void* work, u32 workSize) { 527); osCreateMesgQueue(&ctx.mq, &ctx.msg, 1); - MsgEvent_SendNullTask(); + Sched_FlushTaskQueue(); curTime = osGetTime(); diff = curTime - time; diff --git a/src/code/z_msgevent.c b/src/code/z_msgevent.c deleted file mode 100644 index 5cca1c7179..0000000000 --- a/src/code/z_msgevent.c +++ /dev/null @@ -1,19 +0,0 @@ -#include "global.h" - -void MsgEvent_SendNullTask(void) { - OSScTask task; - OSMesgQueue queue; - OSMesg msg; - u32 pad2[1]; - - task.next = NULL; - task.flags = OS_SC_RCP_MASK; - task.msgQueue = &queue; - task.msg = NULL; - task.framebuffer = NULL; - task.list.t.type = M_NULTASK; - osCreateMesgQueue(task.msgQueue, &msg, 1); - osSendMesg(&gSchedContext.cmdQueue, (OSMesg)&task, OS_MESG_BLOCK); - Sched_SendEntryMsg(&gSchedContext); - osRecvMesg(&queue, NULL, OS_MESG_BLOCK); -} diff --git a/src/code/z_nulltask.c b/src/code/z_nulltask.c new file mode 100644 index 0000000000..1f7dae689e --- /dev/null +++ b/src/code/z_nulltask.c @@ -0,0 +1,27 @@ +#include "global.h" + +/** + * Blocks the current thread until all queued scheduler tasks have completed. + */ +void Sched_FlushTaskQueue(void) { + OSScTask task; + OSMesgQueue queue; + OSMesg msg; + + // Prepare a "NULL" task + task.next = NULL; + task.flags = OS_SC_NEEDS_RDP | OS_SC_NEEDS_RSP; + task.msgQueue = &queue; + task.msg = NULL; + task.framebuffer = NULL; + task.list.t.type = M_NULTASK; + osCreateMesgQueue(task.msgQueue, &msg, 1); + + // Send it to and wake up the scheduler + osSendMesg(&gScheduler.cmdQueue, (OSMesg)&task, OS_MESG_BLOCK); + Sched_Notify(&gScheduler); + + // Wait until the task has been processed, indicating that no task is + // running and the task queue is now empty. + osRecvMesg(&queue, NULL, OS_MESG_BLOCK); +} diff --git a/src/code/z_play.c b/src/code/z_play.c index 76b3010f24..ae65bcc5f7 100644 --- a/src/code/z_play.c +++ b/src/code/z_play.c @@ -1183,7 +1183,7 @@ void Play_Draw(PlayState* this) { PreRender_SetValues(&this->pauseBgPreRender, SCREEN_WIDTH, SCREEN_HEIGHT, gfxCtx->curFrameBuffer, gZBuffer); if (R_PAUSE_MENU_MODE == 2) { - MsgEvent_SendNullTask(); + Sched_FlushTaskQueue(); PreRender_Calc(&this->pauseBgPreRender); R_PAUSE_MENU_MODE = 3; } else if (R_PAUSE_MENU_MODE >= 4) { diff --git a/src/code/z_prenmi.c b/src/code/z_prenmi.c index 9f0d3ad587..3276e60d2a 100644 --- a/src/code/z_prenmi.c +++ b/src/code/z_prenmi.c @@ -17,7 +17,7 @@ void PreNMI_Update(PreNMIContext* this) { } if (this->timer == 0) { - ViConfig_UpdateVi(1); + ViConfig_UpdateVi(true); func_80092320(this); return; } diff --git a/src/libultra/io/sptaskyield.c b/src/libultra/io/sptaskyield.c index a78b7f9ad0..ec7a248bbb 100644 --- a/src/libultra/io/sptaskyield.c +++ b/src/libultra/io/sptaskyield.c @@ -1,5 +1,5 @@ #include "global.h" void osSpTaskYield(void) { - __osSpSetStatus(SP_STATUS_SIG3); + __osSpSetStatus(SP_SET_SIG0); } diff --git a/src/libultra/io/vigetcurrframebuf.c b/src/libultra/io/vigetcurrframebuf.c index f0e4c74437..2093645d91 100644 --- a/src/libultra/io/vigetcurrframebuf.c +++ b/src/libultra/io/vigetcurrframebuf.c @@ -1,10 +1,10 @@ #include "global.h" -u32* osViGetCurrentFramebuffer(void) { +void* osViGetCurrentFramebuffer(void) { register u32 prevInt = __osDisableInt(); - u32* var1 = __osViCurr->buffer; + void* buffer = __osViCurr->buffer; __osRestoreInt(prevInt); - return var1; + return buffer; }