mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 21:27:52 -04:00
d5951c2b11
Normally, when they allocate a VagCmd, they do a bunch of stuff to clear all the status bits and reset things in particular the InitVAGCmd function does a lot ![image](https://github.com/open-goal/jak-project/assets/48171810/9b355020-ad37-496c-9438-2f8d34f24e0a) but for the stereo command, they do a lot less: ![image](https://github.com/open-goal/jak-project/assets/48171810/12a36712-0e68-4377-a6be-3bde82c2aa15) Which means that the new_stereo_command can just have random status bits left over from whatever the last user had. we seem to end up in a state where byte21 is set, and this causes everything else to be wrong and off-by-one dma transfer. My guess is that the original game avoided this bug due to lucky timing that I don't understand. I think the fix of just clearing byte21 is ok because there's no way that the old value of the byte is useful after the command is repurposed.
234 lines
7.7 KiB
C++
234 lines
7.7 KiB
C++
#include "dma.h"
|
|
|
|
#include "game/overlord/jak2/vag.h"
|
|
#include "game/sound/sdshim.h"
|
|
#include "game/sound/sndshim.h"
|
|
|
|
namespace jak2 {
|
|
|
|
// This file has SPU DMA functions. Unlike Jak 1, they run SPU DMA in the background, and it doesn't
|
|
// work correctly if we assume instant DMA. We end up running the DMA finished interrupt handler
|
|
// in the loop of ISO thread - this makes sure that DMA appears to finish pretty quickly, and before
|
|
// any more loading stuff happens (so loads will never "wait" for the fake simulated dma).
|
|
|
|
s32 SpuDmaStatus = 0; //! set to 1 when SPU DMA is in progress
|
|
VagCmd* DmaVagCmd; //! VagCmd currently doing SPU DMA
|
|
VagCmd* DmaStereoVagCmd; //! VagCmd for the stereo sibling SPU DMA
|
|
int pending_dma = 0; //! PC-port addition to indicate that "dma is running"
|
|
|
|
constexpr int kDmaDelay = 10;
|
|
|
|
void dma_init_globals() {
|
|
SpuDmaStatus = 0;
|
|
DmaVagCmd = nullptr;
|
|
DmaStereoVagCmd = nullptr;
|
|
pending_dma = 0;
|
|
}
|
|
|
|
/*!
|
|
* Interrupt handler for DMA completion.
|
|
*/
|
|
int SpuDmaIntr(int, void*) {
|
|
if (SpuDmaStatus != 1) {
|
|
return 0;
|
|
}
|
|
|
|
if (!DmaVagCmd) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!DmaStereoVagCmd) {
|
|
// not stereo mode, just set status bits
|
|
if ((DmaVagCmd->num_processed_chunks & 1U) == 0) {
|
|
DmaVagCmd->sb_even_buffer_dma_complete = 1;
|
|
} else {
|
|
DmaVagCmd->sb_odd_buffer_dma_complete = 1;
|
|
}
|
|
} else {
|
|
// in stereo mode, we need to do two DMA transfers. The first one starts the second stereo one.
|
|
if (DmaStereoVagCmd->xfer_size != 0) {
|
|
// pick the appropriate double buffer
|
|
s16 chan;
|
|
u8* iop_ptr;
|
|
int spu_addr;
|
|
if ((DmaStereoVagCmd->num_processed_chunks & 1U) == 0) {
|
|
chan = DmaVagCmd->dma_chan;
|
|
iop_ptr = DmaStereoVagCmd->dma_iop_mem_ptr;
|
|
spu_addr = DmaStereoVagCmd->spu_stream_dma_mem_addr;
|
|
} else {
|
|
chan = DmaVagCmd->dma_chan;
|
|
iop_ptr = DmaStereoVagCmd->dma_iop_mem_ptr;
|
|
spu_addr = DmaStereoVagCmd->spu_stream_dma_mem_addr + 0x2000;
|
|
}
|
|
|
|
// clear the pending transfer, so we don't try to start it again
|
|
int old_xfer = DmaStereoVagCmd->xfer_size;
|
|
DmaStereoVagCmd->xfer_size = 0;
|
|
DmaStereoVagCmd->dma_iop_mem_ptr = nullptr;
|
|
|
|
// start the transfer!
|
|
pending_dma = kDmaDelay;
|
|
sceSdVoiceTrans((int)chan, 0, iop_ptr, spu_addr, old_xfer);
|
|
// and return. This will get called again on completion
|
|
return 0;
|
|
}
|
|
|
|
// if we made it here, both transfers are done, so just toggle the buffers.
|
|
if ((DmaVagCmd->num_processed_chunks & 1U) == 0) {
|
|
DmaVagCmd->sb_even_buffer_dma_complete = 1;
|
|
DmaStereoVagCmd->sb_even_buffer_dma_complete = 1;
|
|
} else {
|
|
DmaVagCmd->sb_odd_buffer_dma_complete = 1;
|
|
DmaStereoVagCmd->sb_odd_buffer_dma_complete = 1;
|
|
}
|
|
}
|
|
|
|
// if we just finished the first upload, start playing! I'm not entirely sure if this is playing
|
|
// sound yet, or if we're just looping and waiting for the second upload to actually start.
|
|
// (we don't set the appropriate volumes here, so this is probably not the real playback)
|
|
if (DmaVagCmd->num_processed_chunks == 1) {
|
|
int pitch = CalculateVAGPitch(DmaVagCmd->pitch1, DmaVagCmd->unk_256_pitch2);
|
|
if (!DmaStereoVagCmd) {
|
|
DmaVagCmd->spu_addr_to_start_playing = 0;
|
|
sceSdSetAddr(((s16)DmaVagCmd->voice) | 0x2040, DmaVagCmd->spu_stream_dma_mem_addr + 0x30);
|
|
sceSdSetParam((u16)DmaVagCmd->voice | 0x300, 0xf);
|
|
sceSdSetParam((u16)DmaVagCmd->voice | 0x400, 0x1fc0);
|
|
sceSdSetParam((u16)DmaVagCmd->voice | 0x200, pitch);
|
|
|
|
// start playback!
|
|
sceSdkey_on_jak2_voice(DmaVagCmd->voice);
|
|
} else {
|
|
// same for stereo, but we start both voices.
|
|
DmaVagCmd->spu_addr_to_start_playing = 0;
|
|
DmaStereoVagCmd->spu_addr_to_start_playing = 0;
|
|
|
|
sceSdSetAddr((u16)DmaVagCmd->voice | 0x2040, DmaVagCmd->spu_stream_dma_mem_addr + 0x30);
|
|
sceSdSetAddr((u16)DmaStereoVagCmd->voice | 0x2040,
|
|
DmaStereoVagCmd->spu_stream_dma_mem_addr + 0x30);
|
|
sceSdSetParam((u16)DmaVagCmd->voice | 0x300, 0xf);
|
|
sceSdSetParam((u16)DmaStereoVagCmd->voice | 0x300, 0xf);
|
|
sceSdSetParam((u16)DmaVagCmd->voice | 0x400, 0x1fc0);
|
|
sceSdSetParam((u16)DmaStereoVagCmd->voice | 0x400, 0x1fc0);
|
|
sceSdSetParam((u16)DmaVagCmd->voice | 0x200, pitch);
|
|
sceSdSetParam((u16)DmaStereoVagCmd->voice | 0x200, pitch);
|
|
|
|
sceSdkey_on_jak2_voice(DmaVagCmd->voice);
|
|
sceSdkey_on_jak2_voice(DmaStereoVagCmd->voice);
|
|
}
|
|
} else if (DmaVagCmd->num_processed_chunks == 2) {
|
|
// on the second chunk's DMA finish, start playing by unpausing.
|
|
|
|
// set playing flag
|
|
DmaVagCmd->sb_playing = 1;
|
|
if (DmaStereoVagCmd) {
|
|
DmaStereoVagCmd->sb_playing = 1;
|
|
}
|
|
|
|
// if we paused since the first, kill the voice.
|
|
if (DmaVagCmd->sb_paused) {
|
|
if (!DmaStereoVagCmd) {
|
|
sceSdSetParam((u16)DmaVagCmd->voice | 0x200, 0);
|
|
} else {
|
|
sceSdSetParam((u16)DmaStereoVagCmd->voice | 0x200, 0);
|
|
sceSdSetParam((u16)DmaVagCmd->voice | 0x200, 0);
|
|
sceSdkey_off_jak2_voice(DmaStereoVagCmd->voice);
|
|
}
|
|
sceSdkey_off_jak2_voice(DmaVagCmd->voice);
|
|
goto hack;
|
|
}
|
|
|
|
// mark as paused manually and unpause to start playback.
|
|
printf("start playing from dma unpause\n");
|
|
DmaVagCmd->sb_paused = 1;
|
|
UnPauseVAG(DmaVagCmd, 0);
|
|
hack:;
|
|
}
|
|
|
|
// now that we're done, mark it as not in use and remove from DmaVagCmd.
|
|
DmaVagCmd->safe_to_change_dma_fields = 1;
|
|
if (DmaStereoVagCmd) {
|
|
DmaStereoVagCmd->safe_to_change_dma_fields = 1;
|
|
}
|
|
DmaVagCmd = nullptr;
|
|
DmaStereoVagCmd = nullptr;
|
|
|
|
cleanup:
|
|
// sceSdSetTransIntrHandler(param_1, nullptr, nullptr);
|
|
// if (-1 < param_1) {
|
|
// snd_FreeSPUDMA(param_1);
|
|
// }
|
|
SpuDmaStatus = 0;
|
|
return 0;
|
|
}
|
|
|
|
/*!
|
|
* Call any pending DMA transfer complete interrupt handlers.
|
|
*/
|
|
void spu_dma_hack() {
|
|
if (pending_dma) {
|
|
pending_dma--;
|
|
if (!pending_dma) {
|
|
SpuDmaIntr(0, nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Despite the name, this does not sync and just starts dma.
|
|
*/
|
|
bool DMA_SendToSPUAndSync(uint8_t* iop_mem,
|
|
int size_one_side,
|
|
int spu_addr,
|
|
VagCmd* cmd,
|
|
int /*disable_intr*/) {
|
|
int chan;
|
|
int ret;
|
|
if ((SpuDmaStatus == 0) && (chan = snd_GetFreeSPUDMA(), chan != -1)) {
|
|
DmaVagCmd = cmd;
|
|
if (cmd) {
|
|
auto* sibling = cmd->stereo_sibling;
|
|
// mark as in-use by dma.
|
|
cmd->safe_to_change_dma_fields = 0;
|
|
DmaStereoVagCmd = sibling;
|
|
if (sibling) {
|
|
sibling->dma_iop_mem_ptr = iop_mem + size_one_side;
|
|
sibling->xfer_size = size_one_side;
|
|
sibling->num_processed_chunks = cmd->num_processed_chunks;
|
|
cmd->dma_chan = chan;
|
|
}
|
|
}
|
|
SpuDmaStatus = 1;
|
|
|
|
// note: I've bypassed the way the dma interrupts work for jak 2.
|
|
// here, the interrupt handler set is removed, and we instead set a pending_dma flag.
|
|
// the actual interrupt will be run from iso.cpp, in the isothread loop.
|
|
// sceSdSetTransIntrHandler(chan, SpuDmaIntr, 0);
|
|
pending_dma = kDmaDelay; // added
|
|
int sz = sceSdVoiceTrans((int)(short)chan, 0, iop_mem, spu_addr, size_one_side);
|
|
// if (disable_intr == 1) {
|
|
// CpuResumeIntr(local_28[0]);
|
|
//}
|
|
ret = int(size_one_side + 0x3fU & 0xffffffc0) <= sz;
|
|
} else {
|
|
ret = false;
|
|
// if (disable_intr == 1) {
|
|
// CpuResumeIntr(local_28[0]);
|
|
ret = false;
|
|
//}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void DmaCancelThisVagCmd(VagCmd* param_1) {
|
|
if (DmaVagCmd == param_1) {
|
|
sceSdSetTransIntrHandler(DmaVagCmd->dma_chan, nullptr, nullptr);
|
|
if (-1 < DmaVagCmd->dma_chan) {
|
|
snd_FreeSPUDMA(DmaVagCmd->dma_chan);
|
|
}
|
|
DmaVagCmd = nullptr;
|
|
DmaStereoVagCmd = nullptr;
|
|
SpuDmaStatus = 0;
|
|
}
|
|
}
|
|
|
|
} // namespace jak2
|