mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 21:27:52 -04:00
232 lines
7.6 KiB
C++
232 lines
7.6 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(DmaVagCmd->voice);
|
||
|
goto hack;
|
||
|
}
|
||
|
|
||
|
// mark as paused manually and unpause to start playback.
|
||
|
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
|