summaryrefslogtreecommitdiff
path: root/src/code
diff options
context:
space:
mode:
Diffstat (limited to 'src/code')
-rw-r--r--src/code/audioMgr.c10
-rw-r--r--src/code/graph.c12
-rw-r--r--src/code/irqmgr.c38
-rw-r--r--src/code/main.c7
-rw-r--r--src/code/rcp_utils.c5
-rw-r--r--src/code/sched.c358
-rw-r--r--src/code/z_jpeg.c6
-rw-r--r--src/code/z_msgevent.c19
-rw-r--r--src/code/z_nulltask.c27
-rw-r--r--src/code/z_play.c2
-rw-r--r--src/code/z_prenmi.c2
11 files changed, 340 insertions, 146 deletions
diff --git a/src/code/audioMgr.c b/src/code/audioMgr.c
index ccef4dbb2..840b25490 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 868506c7d..f70663b59 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 d29be5082..39e17b8ed 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 f2a1340f6..441ae7e5e 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 d9fc9cd85..5c14c39f9 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 cdf22616e..1ab5e99f4 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 2df2f57cc..9e8925264 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 5cca1c717..000000000
--- 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 000000000..1f7dae689
--- /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 76b3010f2..ae65bcc5f 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 9f0d3ad58..3276e60d2 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;
}