mirror of
https://github.com/open-goal/jak-project
synced 2026-08-07 02:06:59 -04:00
266b423efa
In Jak 3, the default PC settings file would have a language of 255 because it runs before the first settings update. This would cause the game to crash the second time it is started. I also added this simple imgui window to see the names of streams in the "SPU" memory, which has been useful for debugging  --------- Co-authored-by: water111 <awaterford1111445@gmail.com>
625 lines
21 KiB
C++
625 lines
21 KiB
C++
#include "dma.h"
|
|
|
|
#include "common/log/log.h"
|
|
#include "common/util/Assert.h"
|
|
|
|
#include "game/overlord/jak3/basefile.h"
|
|
#include "game/overlord/jak3/overlord.h"
|
|
#include "game/overlord/jak3/vag.h"
|
|
#include "game/sce/iop.h"
|
|
#include "game/sound/sdshim.h"
|
|
#include "game/sound/sndshim.h"
|
|
|
|
#define VOICE_BIT(voice) (1 << ((voice) >> 1))
|
|
|
|
namespace jak3 {
|
|
OverlordStreamMemory g_overlord_stream_memory;
|
|
using namespace iop;
|
|
namespace {
|
|
|
|
// most recent call to voice_trans_wrapper's arguments
|
|
u32 g_voiceTransMode = 0;
|
|
u32 g_voiceTransSize = 0;
|
|
s16 g_voiceTransChannel = 0;
|
|
const void* g_voiceTransAddr = nullptr;
|
|
u32 g_voiceTransSpuAddr = 0;
|
|
|
|
// if we've started a transfer recently
|
|
bool g_voiceTransRunning = false;
|
|
// when that transfer was started
|
|
u32 g_voiceTransTime = 0;
|
|
|
|
// despite the name, this is really an indicator that the SPU streaming system is waiting
|
|
// for a SPU interrupt on completion.
|
|
bool g_bSpuDmaBusy = false;
|
|
int g_nSpuDmaChannel = 0;
|
|
|
|
ISO_VAGCommand* g_pDmaVagCmd = nullptr;
|
|
ISO_VAGCommand* g_pDmaStereoVagCmd = nullptr;
|
|
|
|
int g_nSpuDmaChunks = 0;
|
|
|
|
std::array<DmaQueueEntry, 16> g_aSpuDmaQueue;
|
|
int g_nSpuDmaQueueHead = 0;
|
|
int g_nSpuDmaQueueTail = 0;
|
|
int g_nSpuDmaQueueCount = 0;
|
|
|
|
struct DmaInterruptHandlerHack {
|
|
s32 chan = 0;
|
|
sceSdTransIntrHandler cb = nullptr;
|
|
void* data;
|
|
int countdown = 0;
|
|
bool pending = false;
|
|
} g_DmaInterruptHack;
|
|
|
|
const char* g_current_stream_name = 0;
|
|
|
|
} // namespace
|
|
|
|
void jak3_overlord_init_globals_dma() {
|
|
g_voiceTransMode = 0;
|
|
g_voiceTransSize = 0;
|
|
g_voiceTransChannel = 0;
|
|
g_voiceTransAddr = nullptr;
|
|
g_voiceTransSpuAddr = 0;
|
|
g_voiceTransRunning = false;
|
|
g_voiceTransTime = 0;
|
|
g_bSpuDmaBusy = false;
|
|
g_nSpuDmaChannel = 0;
|
|
g_pDmaVagCmd = nullptr;
|
|
g_pDmaStereoVagCmd = nullptr;
|
|
g_nSpuDmaChunks = 0;
|
|
g_aSpuDmaQueue = {};
|
|
g_nSpuDmaQueueHead = 0;
|
|
g_nSpuDmaQueueCount = 0;
|
|
g_nSpuDmaQueueTail = 0;
|
|
g_DmaInterruptHack = {};
|
|
}
|
|
|
|
// The DMA callback hack below is used to defer dma completion "interrupts" until the next run
|
|
// of the ISO Thread. This avoids re-entry type problems where the original design would set off
|
|
// a dma transfer in the completion handler of the previous transfer, and expect a few instructions
|
|
// to run after.
|
|
|
|
void uninstall_dma_intr() {
|
|
g_DmaInterruptHack = {};
|
|
}
|
|
|
|
void set_dma_intr_handler_hack(s32 chan, sceSdTransIntrHandler cb, void* data) {
|
|
ASSERT(!g_DmaInterruptHack.cb);
|
|
g_DmaInterruptHack.chan = chan;
|
|
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--;
|
|
if (g_DmaInterruptHack.countdown == 0) {
|
|
int chan = g_DmaInterruptHack.chan;
|
|
void* data = g_DmaInterruptHack.data;
|
|
g_DmaInterruptHack = {};
|
|
SPUDmaIntr(chan, data);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* This function is used to set up a DMA transfer to SPU DMA.
|
|
*
|
|
* This wrapper was added very close to the end of Jak 3's development.
|
|
*
|
|
* I believe it basically checks for dma transfers that are somehow "dropped", and retries them.
|
|
* Since I don't think our IOP framework will ever do this, we have an assert if the dropped logic
|
|
* ever goes off.
|
|
*/
|
|
int voice_trans_wrapper(s16 chan, u32 mode, const void* iop_addr, u32 spu_addr, u32 size) {
|
|
// remember the transfer settings. If there's a transfer in progress, so we can't start here,
|
|
// we'll use these to start the transfer later.
|
|
g_voiceTransMode = mode;
|
|
g_voiceTransSize = size;
|
|
g_voiceTransChannel = chan;
|
|
g_voiceTransAddr = iop_addr;
|
|
g_voiceTransSpuAddr = spu_addr;
|
|
|
|
if (g_voiceTransRunning) {
|
|
// I claim this should never happen, and this is their workaround for a bug.
|
|
ASSERT_NOT_REACHED();
|
|
return -0xd2; // busy
|
|
} else {
|
|
g_voiceTransRunning = true;
|
|
g_voiceTransTime = GetSystemTimeLow();
|
|
|
|
switch (spu_addr) {
|
|
case 0x5040:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 0, 0);
|
|
break;
|
|
case 0x7040:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 0, 1);
|
|
break;
|
|
case 0x9080:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 1, 0);
|
|
break;
|
|
case 0xb080:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 1, 1);
|
|
break;
|
|
case 0xd0c0:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 2, 0);
|
|
break;
|
|
case 0xf0c0:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 2, 1);
|
|
break;
|
|
case 0x11100:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 3, 0);
|
|
break;
|
|
case 0x13100:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 3, 1);
|
|
break;
|
|
case 0x15140:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 4, 0);
|
|
break;
|
|
case 0x17140:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 4, 1);
|
|
break;
|
|
case 0x19180:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 5, 0);
|
|
break;
|
|
case 0x1b180:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 5, 1);
|
|
break;
|
|
}
|
|
return sceSdVoiceTrans(chan, mode, iop_addr, spu_addr, size);
|
|
}
|
|
}
|
|
|
|
OverlordStreamMemory::OverlordStreamMemory() {
|
|
for (auto& x : infos) {
|
|
for (auto& y : x) {
|
|
y.idx = 0;
|
|
strcpy(y.name.chars, "Uninitialized");
|
|
}
|
|
}
|
|
}
|
|
|
|
void OverlordStreamMemory::update_name(const char* input, int stream, int side) {
|
|
auto& info = infos[stream][side];
|
|
if (!input) {
|
|
strcpy(info.name.chars, "???");
|
|
info.idx = 0;
|
|
} else {
|
|
if (strcmp(input, info.name.chars) == 0) {
|
|
info.idx++;
|
|
} else {
|
|
info.idx = 0;
|
|
strncpy(info.name.chars, input, 48);
|
|
info.name.chars[47] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
u32 read_rate_calc(u32 pitch) {
|
|
u64 pitch1 = (pitch >> 3);
|
|
u64 mult_result = pitch1 * 0x2492'4925ull;
|
|
return mult_result >> 32;
|
|
}
|
|
|
|
/*!
|
|
* The worst function of all time - the SPU DMA completion interrupt.
|
|
*/
|
|
int SPUDmaIntr(int channel, void* userdata) {
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr enter! {} 0x{:x}", channel, (u64)userdata);
|
|
|
|
if (!g_bSpuDmaBusy) {
|
|
// we got an interrupt, but weren't expecting it, or no longer have the need for the data.
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr exit - not busy");
|
|
return 0;
|
|
}
|
|
|
|
if (channel != g_nSpuDmaChannel) {
|
|
// interrupt was for the wrong channel, somehow.
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr exit - not our channel ??");
|
|
return 0;
|
|
}
|
|
|
|
// since we're in the completion handler, we know that there is no voice trans (SPU DMA) running.
|
|
g_voiceTransRunning = false;
|
|
|
|
// This next block will handle updating the playback command that triggered this dma:
|
|
if (g_pDmaVagCmd) {
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDma for cmd {}", g_pDmaVagCmd->name);
|
|
if (!g_pDmaStereoVagCmd) {
|
|
// non-stereo audio
|
|
|
|
// set a flag to indicate even/odd number of chunks have been dma'd
|
|
if ((g_nSpuDmaChunks & 1) == 0) {
|
|
g_pDmaVagCmd->flags.dma_complete_even_chunk_count = 1;
|
|
} else {
|
|
g_pDmaVagCmd->flags.dma_complete_odd_chunk_count = 1;
|
|
}
|
|
} else {
|
|
// stereo audio. This requires two uploads, one for left/right audio. If we've finished the
|
|
// first, start the second one here:
|
|
if (g_pDmaStereoVagCmd->xfer_size) {
|
|
// parameters for second upload
|
|
int chan = g_pDmaVagCmd->dma_chan;
|
|
const u8* iop_addr = g_pDmaStereoVagCmd->dma_iop_mem_ptr;
|
|
int size = g_pDmaStereoVagCmd->xfer_size;
|
|
|
|
// SPU addr - toggle the buffer based on stereo side:
|
|
// TODO: better explanation of why this picks the correct buffer.
|
|
int spu_addr;
|
|
if ((g_nSpuDmaChunks & 1) == 0) {
|
|
spu_addr = g_pDmaStereoVagCmd->stream_sram;
|
|
} else {
|
|
spu_addr = g_pDmaStereoVagCmd->stream_sram + 0x2000;
|
|
}
|
|
|
|
// these lines reordered to possibly support immediate dma completion callback??
|
|
|
|
// clear flag so we know not to transfer the next part
|
|
g_pDmaStereoVagCmd->xfer_size = 0;
|
|
g_pDmaStereoVagCmd->dma_iop_mem_ptr = nullptr;
|
|
|
|
// start next transfer
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr starting stereo sibling transfer");
|
|
set_dma_intr_handler_hack(g_nSpuDmaChannel, SPUDmaIntr, userdata);
|
|
voice_trans_wrapper(chan, 0, iop_addr, spu_addr, size);
|
|
return 0;
|
|
}
|
|
|
|
// second stereo upload completed - update double-buffering flags
|
|
if ((g_nSpuDmaChunks & 1) == 0) {
|
|
g_pDmaVagCmd->flags.dma_complete_even_chunk_count = 1;
|
|
g_pDmaStereoVagCmd->flags.dma_complete_even_chunk_count = 1;
|
|
} else {
|
|
g_pDmaVagCmd->flags.dma_complete_odd_chunk_count = 1;
|
|
g_pDmaStereoVagCmd->flags.dma_complete_odd_chunk_count = 1;
|
|
}
|
|
}
|
|
|
|
// if this is the first chunk, we'll start the actual audio here:
|
|
// lg::warn("----------> interrupt with chunks {}\n", g_nSpuDmaChunks);
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks count {}", g_nSpuDmaChunks);
|
|
|
|
if (g_nSpuDmaChunks == 0) {
|
|
// compute pitch/playback rate
|
|
int pitch = CalculateVAGPitch(g_pDmaVagCmd->pitch1, g_pDmaVagCmd->pitch_cmd);
|
|
ASSERT(pitch == (pitch & 0xffff));
|
|
|
|
// inform the ISO system how fast we're reading
|
|
if (g_pDmaVagCmd->m_pBaseFile) {
|
|
// unlike actual playback, this is done with the pitch1 value from the file itself - so if
|
|
// we speed up/slow down stuff in debug, it won't change streaming modes
|
|
const int pitch_from_file =
|
|
CalculateVAGPitch(g_pDmaVagCmd->pitch1_file, g_pDmaVagCmd->pitch_cmd);
|
|
int rate = g_pDmaStereoVagCmd ? pitch_from_file * 0x2ee : pitch_from_file * 0x177;
|
|
g_pDmaVagCmd->m_pBaseFile->m_ReadRate = read_rate_calc(rate);
|
|
}
|
|
|
|
// start!
|
|
u32 voice_mask = 0;
|
|
if (!g_pDmaStereoVagCmd) {
|
|
// forget any previous spu address
|
|
g_pDmaVagCmd->current_spu_address = 0;
|
|
|
|
static_assert(SD_VA_SSA == 0x2040);
|
|
static_assert(SD_S_KOFF == 0x1600);
|
|
static_assert(SD_S_KON == 0x1500);
|
|
|
|
static_assert(SD_VP_ADSR1 == 0x300);
|
|
static_assert(SD_VP_ADSR2 == 0x400);
|
|
static_assert(SD_VP_PITCH == 0x200);
|
|
|
|
// before touching SPU2 hardware, wait for voice safety:
|
|
BlockUntilVoiceSafe(g_pDmaVagCmd->voice, 0x900);
|
|
|
|
// set address and ADSR settings
|
|
sceSdSetAddr(g_pDmaVagCmd->voice | SD_VA_SSA, g_pDmaVagCmd->stream_sram + 0x30);
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR1, 0xff);
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR2, 0x1fc0);
|
|
if (g_pDmaVagCmd->flags.paused) {
|
|
pitch = 0;
|
|
}
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, pitch);
|
|
voice_mask = VOICE_BIT(g_pDmaVagCmd->voice);
|
|
} else {
|
|
// forget any previous spu address
|
|
g_pDmaVagCmd->current_spu_address = 0;
|
|
g_pDmaStereoVagCmd->current_spu_address = 0;
|
|
|
|
// wait for voices to be safe to adjust
|
|
BlockUntilVoiceSafe(g_pDmaVagCmd->voice, 0x900);
|
|
BlockUntilVoiceSafe(g_pDmaStereoVagCmd->voice, 0x900);
|
|
|
|
// set voice params
|
|
sceSdSetAddr(g_pDmaVagCmd->voice | SD_VA_SSA, g_pDmaVagCmd->stream_sram + 0x30);
|
|
sceSdSetAddr(g_pDmaStereoVagCmd->voice | SD_VA_SSA, g_pDmaStereoVagCmd->stream_sram + 0x30);
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR1, 0xff);
|
|
sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_ADSR1, 0xff);
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR2, 0x1fc0);
|
|
sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_ADSR2, 0x1fc0);
|
|
if (g_pDmaVagCmd->flags.paused) {
|
|
pitch = 0;
|
|
}
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, pitch);
|
|
sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_PITCH, pitch);
|
|
voice_mask = VOICE_BIT(g_pDmaVagCmd->voice) | VOICE_BIT(g_pDmaStereoVagCmd->voice);
|
|
}
|
|
|
|
// do key-on or key-off
|
|
if (g_pDmaVagCmd->flags.paused) {
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 0, key off");
|
|
BlockUntilAllVoicesSafe();
|
|
sceSdSetSwitch(SD_S_KOFF | (g_pDmaVagCmd->voice & 1), voice_mask);
|
|
} else {
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 0, key on");
|
|
BlockUntilAllVoicesSafe();
|
|
sceSdSetSwitch(SD_S_KON | (g_pDmaVagCmd->voice & 1), voice_mask);
|
|
}
|
|
|
|
// remember the time of the key-on/off. This is used to avoid sending voice commands
|
|
// quickly, which somehow confuses the sound hardware.
|
|
auto sys_time = GetSystemTimeLow();
|
|
MarkVoiceKeyedOnOff(g_pDmaVagCmd->voice, sys_time);
|
|
if (g_pDmaStereoVagCmd) {
|
|
MarkVoiceKeyedOnOff(g_pDmaStereoVagCmd->voice, sys_time);
|
|
}
|
|
} else if (g_nSpuDmaChunks == 1) {
|
|
g_pDmaVagCmd->flags.saw_chunks1 = 1;
|
|
if (g_pDmaStereoVagCmd) {
|
|
g_pDmaStereoVagCmd->flags.saw_chunks1 = 1;
|
|
}
|
|
|
|
if (g_pDmaVagCmd->flags.paused) {
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 1, pausing");
|
|
u32 voice_mask = 0;
|
|
if (!g_pDmaStereoVagCmd) {
|
|
// pause by setting pitches to 0
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, 0);
|
|
BlockUntilVoiceSafe(VOICE_BIT(g_pDmaVagCmd->voice), 0x900);
|
|
voice_mask = VOICE_BIT(g_pDmaVagCmd->voice);
|
|
} else {
|
|
sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_PITCH, 0);
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, 0);
|
|
BlockUntilVoiceSafe(VOICE_BIT(g_pDmaVagCmd->voice), 0x900);
|
|
BlockUntilVoiceSafe(VOICE_BIT(g_pDmaStereoVagCmd->voice), 0x900);
|
|
voice_mask = VOICE_BIT(g_pDmaVagCmd->voice) | VOICE_BIT(g_pDmaStereoVagCmd->voice);
|
|
}
|
|
|
|
// switch off
|
|
BlockUntilAllVoicesSafe();
|
|
sceSdSetSwitch(SD_S_KOFF | (g_pDmaVagCmd->voice & 1), voice_mask);
|
|
auto sys_time = GetSystemTimeLow();
|
|
MarkVoiceKeyedOnOff(g_pDmaVagCmd->voice, sys_time);
|
|
if (g_pDmaStereoVagCmd) {
|
|
MarkVoiceKeyedOnOff(g_pDmaStereoVagCmd->voice, sys_time);
|
|
}
|
|
} else {
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 1, unpausing by call to UnPauseVAG");
|
|
g_pDmaVagCmd->flags.paused = 1;
|
|
UnPauseVAG(g_pDmaVagCmd);
|
|
}
|
|
}
|
|
|
|
// now that we've processed the command from this interrupt, mark it as safe to modify
|
|
g_pDmaVagCmd->safe_to_modify_dma = 1;
|
|
if (g_pDmaStereoVagCmd) {
|
|
g_pDmaStereoVagCmd->safe_to_modify_dma = 1;
|
|
}
|
|
// and forget it!
|
|
g_pDmaVagCmd = nullptr;
|
|
g_pDmaStereoVagCmd = nullptr;
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr dma handling of VAG cmd is complete");
|
|
}
|
|
|
|
// release ref on this page. (interestingly, not a dma ref...)
|
|
if (userdata) {
|
|
CPage* page = (CPage*)userdata;
|
|
int ret = page->ReleaseRef();
|
|
ASSERT(ret >= 0);
|
|
}
|
|
|
|
// now - see if we have another queued dma transfer
|
|
ASSERT(g_nSpuDmaQueueCount >= 0);
|
|
if (g_nSpuDmaQueueCount == 0) {
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr dma queue is empty, disabling interrupt");
|
|
// we're done!
|
|
// set_dma_intr_handler_hack(channel, nullptr, nullptr);
|
|
uninstall_dma_intr();
|
|
// if (-1 < channel) {
|
|
// snd_FreeSPUDMA(channel);
|
|
// }
|
|
g_bSpuDmaBusy = false;
|
|
} else {
|
|
ovrld_log(LogCategory::SPU_DMA_STR,
|
|
"SPUDmaIntr dma queue is not empty, preparing to run {} ({} pending)",
|
|
g_nSpuDmaQueueHead, g_nSpuDmaQueueCount);
|
|
// nope, more dma to run
|
|
auto* next_xfer = &g_aSpuDmaQueue[g_nSpuDmaQueueHead];
|
|
|
|
// set up the next interrupt handler
|
|
set_dma_intr_handler_hack(channel, SPUDmaIntr, next_xfer->user_data);
|
|
|
|
// args for the dma transfer
|
|
int next_chan = channel;
|
|
int next_mode = 0;
|
|
const void* next_iop = next_xfer->iop_mem;
|
|
u32 next_spu = next_xfer->spu_addr;
|
|
u32 next_length = next_xfer->length;
|
|
|
|
// load up the commands to handle
|
|
g_pDmaVagCmd = next_xfer->command;
|
|
g_pDmaStereoVagCmd = nullptr;
|
|
if (g_pDmaVagCmd) {
|
|
g_pDmaStereoVagCmd = g_pDmaVagCmd->stereo_sibling;
|
|
}
|
|
g_nSpuDmaChunks = next_xfer->num_isobuffered_chunks;
|
|
|
|
// advance the queue!
|
|
g_nSpuDmaQueueCount = g_nSpuDmaQueueCount + -1;
|
|
g_nSpuDmaQueueHead = g_nSpuDmaQueueHead + 1;
|
|
if (0xf < g_nSpuDmaQueueHead) {
|
|
g_nSpuDmaQueueHead = 0;
|
|
}
|
|
|
|
// start the next one!
|
|
// set_dma_intr_handler_hack(g_nSpuDmaChannel, SPUDmaIntr, userdata);
|
|
voice_trans_wrapper(next_chan, next_mode, next_iop, next_spu, next_length);
|
|
}
|
|
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr exit - end of function");
|
|
return 0;
|
|
}
|
|
|
|
/*!
|
|
* Start DMA to EE.
|
|
*/
|
|
void DMA_SendToEE(void* ee_dest,
|
|
const void* iop_src,
|
|
u32 length,
|
|
void callback(void*),
|
|
void* callback_arg) {
|
|
ASSERT(iop_src);
|
|
ASSERT(ee_dest);
|
|
ASSERT(((uintptr_t)iop_src & 3) == 0);
|
|
ASSERT(((uintptr_t)ee_dest & 0xf) == 0);
|
|
ASSERT(length < 0xffff0);
|
|
sceSifDmaData cmd; // DMA settings
|
|
|
|
// setup command
|
|
cmd.mode = 0;
|
|
cmd.data = iop_src;
|
|
cmd.addr = ee_dest;
|
|
cmd.size = length;
|
|
|
|
// instant DMA
|
|
// ovrld_log(LogCategory::EE_DMA, "DMA_SendToEE: 0x{:x}, size {}", (u64)ee_dest, length);
|
|
sceSifSetDma(&cmd, 1);
|
|
|
|
// for now, we'll do the callback here, but I bet it will cause problems
|
|
if (callback) {
|
|
callback(callback_arg);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Start DMA transfer to SPU. Despite the name, this does not actually "sync" - the transfer will
|
|
* be ongoing. If there is an ongoing transfer when this is called, the transfer will be queued.
|
|
*/
|
|
int DMA_SendToSPUAndSync(const u8* iop_mem,
|
|
int length,
|
|
int spu_addr,
|
|
ISO_VAGCommand* cmd,
|
|
void* user_data) {
|
|
// CpuSuspendIntr(local_28);
|
|
int ret = 1;
|
|
bool defer = false;
|
|
|
|
ovrld_log(LogCategory::SPU_DMA_STR,
|
|
"DMA to SPU requested for {}, {} bytes to 0x{:x}, currently busy? {}",
|
|
cmd ? cmd->name : "NO-CMD", length, spu_addr, g_bSpuDmaBusy);
|
|
if (cmd) {
|
|
g_current_stream_name = cmd->name;
|
|
} else {
|
|
const static char* unknown = "unknown";
|
|
g_current_stream_name = unknown;
|
|
}
|
|
|
|
if (g_bSpuDmaBusy == 0) {
|
|
// not busy, we can actually start dma now.
|
|
g_nSpuDmaChannel = snd_GetFreeSPUDMA();
|
|
if (g_nSpuDmaChannel == -1) {
|
|
return 0;
|
|
}
|
|
// set globals for DMA processing
|
|
if (cmd) {
|
|
g_nSpuDmaChunks = cmd->num_isobuffered_chunks;
|
|
g_pDmaStereoVagCmd = cmd->stereo_sibling;
|
|
g_pDmaVagCmd = cmd;
|
|
}
|
|
|
|
} else {
|
|
// busy, need to queue the dma
|
|
ASSERT(g_nSpuDmaQueueCount <= (int)g_aSpuDmaQueue.size());
|
|
|
|
// set values:
|
|
g_aSpuDmaQueue[g_nSpuDmaQueueTail].length = length;
|
|
g_aSpuDmaQueue[g_nSpuDmaQueueTail].spu_addr = spu_addr;
|
|
g_aSpuDmaQueue[g_nSpuDmaQueueTail].user_data = user_data;
|
|
g_aSpuDmaQueue[g_nSpuDmaQueueTail].num_isobuffered_chunks =
|
|
cmd ? cmd->num_isobuffered_chunks : 0;
|
|
g_aSpuDmaQueue[g_nSpuDmaQueueTail].command = cmd;
|
|
g_aSpuDmaQueue[g_nSpuDmaQueueTail].iop_mem = iop_mem;
|
|
g_nSpuDmaQueueCount = g_nSpuDmaQueueCount + 1;
|
|
g_nSpuDmaQueueTail = g_nSpuDmaQueueTail + 1;
|
|
if (0xf < g_nSpuDmaQueueTail) {
|
|
g_nSpuDmaQueueTail = 0;
|
|
}
|
|
defer = true;
|
|
}
|
|
|
|
// set up the stereo command
|
|
if (cmd) {
|
|
cmd->safe_to_modify_dma = 0;
|
|
auto* stereo = cmd->stereo_sibling;
|
|
if (stereo) {
|
|
stereo->num_isobuffered_chunks = cmd->num_isobuffered_chunks;
|
|
stereo->dma_iop_mem_ptr = iop_mem + length;
|
|
cmd->dma_chan = g_nSpuDmaChannel;
|
|
stereo->xfer_size = length;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
/*!
|
|
* Run a dma transfer that was delayed or dropped.
|
|
*/
|
|
void RunDeferredVoiceTrans() {
|
|
// only if there's a currently happening transfer.
|
|
if (g_voiceTransRunning) {
|
|
if (GetSystemTimeLow() - g_voiceTransTime > 0x384000) {
|
|
ovrld_log(LogCategory::WARN, "DeferredVoiceTrans has detected hung dma... expect problems.");
|
|
// original game also check sceSdVoiceTransStatus here, we'll possibly need to mess with this
|
|
// if we delay dma completion interrupts...
|
|
g_voiceTransRunning = false;
|
|
voice_trans_wrapper(g_voiceTransChannel, g_voiceTransMode, g_voiceTransAddr,
|
|
g_voiceTransSpuAddr, g_voiceTransSize);
|
|
}
|
|
}
|
|
}
|
|
} // namespace jak3
|