Better DSP scheduling

This commit is contained in:
wheremyfoodat 2024-02-18 22:22:00 +02:00
parent f58354af06
commit 33eb096ef8
8 changed files with 44 additions and 27 deletions

View file

@ -7,20 +7,30 @@
#include "helpers.hpp"
#include "logger.hpp"
#include "memory.hpp"
#include "scheduler.hpp"
// The DSP core must have access to the DSP service to be able to trigger interrupts properly
class DSPService;
namespace Audio {
// There are 160 stereo samples in 1 audio frame, so 320 samples total
static constexpr u64 samplesInFrame = 160;
// 1 frame = 4096 DSP cycles = 8192 ARM11 cycles
static constexpr u64 cyclesPerFrame = samplesInFrame * 8192;
// For LLE DSP cores, we run the DSP for N cycles at a time, every N*2 arm11 cycles since the ARM11 runs twice as fast
static constexpr u64 lleSlice = 16384;
class DSPCore {
protected:
Memory& mem;
Scheduler& scheduler;
DSPService& dspService;
MAKE_LOG_FUNCTION(log, dspLogger)
public:
enum class Type { Null, Teakra };
DSPCore(Memory& mem, DSPService& dspService) : mem(mem), dspService(dspService) {}
DSPCore(Memory& mem, Scheduler& scheduler, DSPService& dspService) : mem(mem), scheduler(scheduler), dspService(dspService) {}
virtual void reset() = 0;
virtual void runAudioFrame() = 0;
@ -36,5 +46,5 @@ namespace Audio {
virtual void setSemaphoreMask(u16 value) = 0;
};
std::unique_ptr<DSPCore> makeDSPCore(DSPCore::Type type, Memory& mem, DSPService& dspService);
std::unique_ptr<DSPCore> makeDSPCore(DSPCore::Type type, Memory& mem, Scheduler& scheduler, DSPService& dspService);
} // namespace Audio

View file

@ -20,7 +20,7 @@ namespace Audio {
void resetAudioPipe();
public:
NullDSP(Memory& mem, DSPService& dspService) : DSPCore(mem, dspService) {}
NullDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService) : DSPCore(mem, scheduler, dspService) {}
void reset() override;
void runAudioFrame() override {}

View file

@ -65,14 +65,22 @@ namespace Audio {
bool signalledData;
bool signalledSemaphore;
// Run 1 slice of DSP instructions
void runSlice() {
if (running) {
teakra.Run(Audio::lleSlice);
}
}
public:
TeakraDSP(Memory& mem, DSPService& dspService);
TeakraDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService);
void reset() override;
// Run 1 slice of DSP instructions and schedule the next audio frame
void runAudioFrame() override {
if (running) {
teakra.Run(16384);
}
runSlice();
scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::lleSlice * 2);
}
u8* getDspMemory() override { return teakra.GetDspMemory().data(); }

View file

@ -50,7 +50,6 @@ struct Scheduler {
// Clear any pending events
events.clear();
addEvent(Scheduler::EventType::VBlank, arm11Clock / 60);
addEvent(Scheduler::EventType::RunDSP, 16384 * 2);
// Add a dummy event to always keep the scheduler non-empty
addEvent(EventType::Panic, std::numeric_limits<u64>::max());

View file

@ -3,16 +3,16 @@
#include "audio/null_core.hpp"
#include "audio/teakra_core.hpp"
std::unique_ptr<Audio::DSPCore> Audio::makeDSPCore(DSPCore::Type type, Memory& mem, DSPService& dspService) {
std::unique_ptr<Audio::DSPCore> Audio::makeDSPCore(DSPCore::Type type, Memory& mem, Scheduler& scheduler, DSPService& dspService) {
std::unique_ptr<DSPCore> core;
switch (type) {
case DSPCore::Type::Null: core = std::make_unique<NullDSP>(mem, dspService); break;
case DSPCore::Type::Teakra: core = std::make_unique<TeakraDSP>(mem, dspService); break;
case DSPCore::Type::Null: core = std::make_unique<NullDSP>(mem, scheduler, dspService); break;
case DSPCore::Type::Teakra: core = std::make_unique<TeakraDSP>(mem, scheduler, dspService); break;
default:
Helpers::warn("Invalid DSP core selected!");
core = std::make_unique<NullDSP>(mem, dspService);
core = std::make_unique<NullDSP>(mem, scheduler, dspService);
break;
}

View file

@ -34,7 +34,8 @@ struct Dsp1 {
Segment segments[10];
};
TeakraDSP::TeakraDSP(Memory& mem, DSPService& dspService) : DSPCore(mem, dspService), pipeBaseAddr(0), running(false) {
TeakraDSP::TeakraDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService)
: DSPCore(mem, scheduler, dspService), pipeBaseAddr(0), running(false) {
// Set up callbacks for Teakra
Teakra::AHBMCallback ahbm;
@ -117,7 +118,6 @@ void TeakraDSP::reset() {
// https://github.com/citra-emu/citra/blob/master/src/audio_core/lle/lle.cpp
void TeakraDSP::writeProcessPipe(u32 channel, u32 size, u32 buffer) {
Helpers::warn("Teakra: Write process pipe");
size &= 0xffff;
PipeStatus status = getPipeStatus(channel, PipeDirection::CPUtoDSP);
@ -167,7 +167,7 @@ void TeakraDSP::writeProcessPipe(u32 channel, u32 size, u32 buffer) {
if (needUpdate) {
updatePipeStatus(status);
while (!teakra.SendDataIsEmpty(2)) {
runAudioFrame();
runSlice();
}
teakra.SendData(2, status.slot);
@ -175,7 +175,6 @@ void TeakraDSP::writeProcessPipe(u32 channel, u32 size, u32 buffer) {
}
std::vector<u8> TeakraDSP::readPipe(u32 channel, u32 peer, u32 size, u32 buffer) {
Helpers::warn("Teakra: Read pipe");
size &= 0xffff;
PipeStatus status = getPipeStatus(channel, PipeDirection::DSPtoCPU);
@ -216,7 +215,7 @@ std::vector<u8> TeakraDSP::readPipe(u32 channel, u32 peer, u32 size, u32 buffer)
if (needUpdate) {
updatePipeStatus(status);
while (!teakra.SendDataIsEmpty(2)) {
runAudioFrame();
runSlice();
}
teakra.SendData(2, status.slot);
@ -273,20 +272,20 @@ void TeakraDSP::loadComponent(std::vector<u8>& data, u32 programMask, u32 dataMa
for (int i = 0; i < 3; i++) {
do {
while (!teakra.RecvDataIsReady(i)) {
runAudioFrame();
runSlice();
}
} while (teakra.RecvData(i) != 1);
}
}
printf("DSP::LoadComponent: Semaphore value: %X\n", teakra.GetSemaphore());
// Retrieve the pipe base address
while (!teakra.RecvDataIsReady(2)) {
runAudioFrame();
runSlice();
}
pipeBaseAddr = teakra.RecvData(2);
// Schedule next DSP event
scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::lleSlice * 2);
loaded = true;
}
@ -296,17 +295,19 @@ void TeakraDSP::unloadComponent() {
return;
}
loaded = false;
// Stop scheduling DSP events
scheduler.removeEvent(Scheduler::EventType::RunDSP);
// Wait for SEND2 to be ready, then send the shutdown command to the DSP
while (!teakra.SendDataIsEmpty(2)) {
runAudioFrame();
runSlice();
}
teakra.SendData(2, 0x8000);
// Wait for shutdown to be acknowledged
while (!teakra.RecvDataIsReady(2)) {
runAudioFrame();
runSlice();
}
// Read the value and discard it, completing shutdown

View file

@ -125,7 +125,7 @@ void GPUService::registerInterruptRelayQueue(u32 messagePointer) {
void GPUService::requestInterrupt(GPUInterrupt type) {
// HACK: Signal DSP events on GPU interrupt for now until we have the DSP since games need DSP events
// Maybe there's a better alternative?
// kernel.signalDSPEvents();
//kernel.signalDSPEvents();
if (sharedMem == nullptr) [[unlikely]] { // Shared memory hasn't been set up yet
return;

View file

@ -26,7 +26,7 @@ Emulator::Emulator()
{
DSPService& dspService = kernel.getServiceManager().getDSP();
dsp = Audio::makeDSPCore(Audio::DSPCore::Type::Teakra, memory, dspService);
dsp = Audio::makeDSPCore(Audio::DSPCore::Type::Teakra, memory, scheduler, dspService);
dspService.setDSPCore(dsp.get());
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
@ -151,7 +151,6 @@ void Emulator::pollScheduler() {
case Scheduler::EventType::UpdateTimers: kernel.pollTimers(); break;
case Scheduler::EventType::RunDSP: {
scheduler.addEvent(Scheduler::EventType::RunDSP, time + 16384 * 2);
dsp->runAudioFrame();
break;
}