From a3e8f0f91a60258068cf2aa90ed719dc15bb7041 Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Sat, 14 Dec 2024 12:36:24 -0500 Subject: [PATCH] [Jak3] Adjust overlord SPU dma to avoid audio hangs (#3804) 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 --- game/overlord/jak3/dma.cpp | 27 ++++++++++++++++++++++++++- game/overlord/jak3/iso.cpp | 2 +- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/game/overlord/jak3/dma.cpp b/game/overlord/jak3/dma.cpp index e194d01aa7..5fced6b50c 100644 --- a/game/overlord/jak3/dma.cpp +++ b/game/overlord/jak3/dma.cpp @@ -48,6 +48,7 @@ struct DmaInterruptHandlerHack { sceSdTransIntrHandler cb = nullptr; void* data; int countdown = 0; + bool pending = false; } g_DmaInterruptHack; } // namespace @@ -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--; @@ -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; } diff --git a/game/overlord/jak3/iso.cpp b/game/overlord/jak3/iso.cpp index 92129036c4..3e7418d913 100644 --- a/game/overlord/jak3/iso.cpp +++ b/game/overlord/jak3/iso.cpp @@ -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);