Skip to content

Commit

Permalink
[Jak3] Adjust overlord SPU dma to avoid audio hangs (#3804)
Browse files Browse the repository at this point in the history
Change jak 3 SPU DMA to run the interrupt handler "immediately" (or at
least before `DMA_SendToSPUAndSync` returns).

This fixes an issue where audio can hang during fast cutscene playback.
I'm hoping it fixes more issues with looping/stuck audio as well, but
this needs more testing.

I originally wanted to do it this way, but thought that it didn't work -
from Jak 2 it seemed like things broke if the DMA was too fast. But, at
least for Jak 3, everything seems to work like this. This will remove a
huge source of non-deterministic timing in audio stuff and hopefully
make things easier to debug. It also means that a large portion of
streaming audio code will never have to run - from the game's point of
view there's always the next SPU buffer available.

If this works well, I might revisit jak 2 as well.

Co-authored-by: water111 <[email protected]>
  • Loading branch information
water111 and water111 authored Dec 14, 2024
1 parent b3cdcf3 commit a3e8f0f
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 2 deletions.
27 changes: 26 additions & 1 deletion game/overlord/jak3/dma.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ struct DmaInterruptHandlerHack {
sceSdTransIntrHandler cb = nullptr;
void* data;
int countdown = 0;
bool pending = false;
} g_DmaInterruptHack;

} // namespace
Expand Down Expand Up @@ -87,10 +88,20 @@ void set_dma_intr_handler_hack(s32 chan, sceSdTransIntrHandler cb, void* data) {
g_DmaInterruptHack.cb = cb;
g_DmaInterruptHack.data = data;
g_DmaInterruptHack.countdown = 10;
g_DmaInterruptHack.pending = true;
}

int SPUDmaIntr(int channel, void* userdata);

void complete_dma_now() {
if (g_DmaInterruptHack.pending) {
int chan = g_DmaInterruptHack.chan;
void* data = g_DmaInterruptHack.data;
g_DmaInterruptHack = {};
SPUDmaIntr(chan, data);
}
}

void dma_intr_hack() {
if (g_DmaInterruptHack.countdown) {
g_DmaInterruptHack.countdown--;
Expand Down Expand Up @@ -498,12 +509,26 @@ int DMA_SendToSPUAndSync(const u8* iop_mem,
}
}

// kick off dma, if we decided not to queue.
// Note on DMA interrupts.
// The DMA completion interrupt handler function may start more DMA transfers.
// If the second transfer's completion interrupt runs before the first transfer's completion
// interrupt returns, things break. This wasn't an issue on the real PS2 since the DMA takes
// longer. On PC, this means that we can't just call the completion handler from the DMA start
// function. Instead, put it at the end of this function.

// kick off dma, if we decided not to queue. This copies data immediately to the SPU buffer, but
// doesn't run the completion interrupt.
if (!defer) {
g_bSpuDmaBusy = true;
set_dma_intr_handler_hack(g_nSpuDmaChannel, SPUDmaIntr, user_data);
voice_trans_wrapper(g_nSpuDmaChannel, 0, iop_mem, spu_addr, length);
}

// run completion interrupts. the interrupt may start another DMA transfer, which should also
// finish here.
while (g_DmaInterruptHack.pending) {
complete_dma_now();
}
return ret;
}

Expand Down
2 changes: 1 addition & 1 deletion game/overlord/jak3/iso.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ u32 ISOThread() {
// ISOFileDef* file_def = nullptr;

while (true) {
dma_intr_hack();
// dma_intr_hack();
// Part 1: Handle incoming messages from the user:

int poll_result = PollMbx((MsgPacket**)&mbx_cmd, g_nISOMbx);
Expand Down

0 comments on commit a3e8f0f

Please sign in to comment.