jak-project/game/overlord/jak2/dma.cpp

246 lines
8.3 KiB
C++
Raw Normal View History

#include "dma.h"
#include "common/log/log.h"
#include "common/util/string_util.h"
#include "game/overlord/jak2/ssound.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;
// TODO - temporary hack, `channel` being set to -1 is what causes the lifeseed cutscene crash
// narrow down why/where -1 is coming from
// - https://github.com/open-goal/jak-project/issues/2988
if (chan < 0) {
lg::error("SND-ERROR: channel was invalid: {} for command: {}", chan,
DmaStereoVagCmd->name);
return -1;
}
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(SD_VA_SSA | DmaVagCmd->voice, DmaVagCmd->spu_stream_dma_mem_addr + 0x30);
sceSdSetParam(SD_VP_ADSR1 | DmaVagCmd->voice, 0xf);
sceSdSetParam(SD_VP_ADSR2 | DmaVagCmd->voice, 0x1fc0);
sceSdSetParam(SD_VP_PITCH | DmaVagCmd->voice, pitch);
sceSdSetSwitch(SD_S_KON | (DmaVagCmd->voice & 1), VOICE_BIT(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(SD_VA_SSA | DmaVagCmd->voice, DmaVagCmd->spu_stream_dma_mem_addr + 0x30);
sceSdSetAddr(SD_VA_SSA | DmaStereoVagCmd->voice,
DmaStereoVagCmd->spu_stream_dma_mem_addr + 0x30);
sceSdSetParam(SD_VP_ADSR1 | DmaVagCmd->voice, 0xf);
sceSdSetParam(SD_VP_ADSR1 | DmaStereoVagCmd->voice, 0xf);
sceSdSetParam(SD_VP_ADSR2 | DmaVagCmd->voice, 0x1fc0);
sceSdSetParam(SD_VP_ADSR2 | DmaStereoVagCmd->voice, 0x1fc0);
sceSdSetParam(SD_VP_PITCH | DmaVagCmd->voice, pitch);
sceSdSetParam(SD_VP_PITCH | DmaStereoVagCmd->voice, pitch);
sceSdSetSwitch(SD_S_KON | (DmaVagCmd->voice & 1),
VOICE_BIT(DmaVagCmd->voice) | VOICE_BIT(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(SD_VP_PITCH | DmaVagCmd->voice, 0);
} else {
sceSdSetParam(SD_VP_PITCH | DmaStereoVagCmd->voice, 0);
sceSdSetParam(SD_VP_PITCH | DmaVagCmd->voice, 0);
sceSdSetSwitch(SD_S_KOFF | (DmaVagCmd->voice & 1), VOICE_BIT(DmaStereoVagCmd->voice));
}
sceSdSetSwitch(SD_S_KOFF | (DmaVagCmd->voice & 1), VOICE_BIT(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