Merge pull request #410 from wheremyfoodat/dsp

WIP: DSP support
This commit is contained in:
wheremyfoodat 2024-02-19 19:34:05 +00:00 committed by GitHub
commit 093364f615
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 868 additions and 159 deletions

View file

@ -0,0 +1,55 @@
#pragma once
#include <array>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "helpers.hpp"
#include "logger.hpp"
#include "scheduler.hpp"
// The DSP core must have access to the DSP service to be able to trigger interrupts properly
class DSPService;
class Memory;
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, Scheduler& scheduler, DSPService& dspService) : mem(mem), scheduler(scheduler), dspService(dspService) {}
virtual void reset() = 0;
virtual void runAudioFrame() = 0;
virtual u8* getDspMemory() = 0;
virtual u16 recvData(u32 regId) = 0;
virtual bool recvDataIsReady(u32 regId) = 0;
virtual void setSemaphore(u16 value) = 0;
virtual void writeProcessPipe(u32 channel, u32 size, u32 buffer) = 0;
virtual std::vector<u8> readPipe(u32 channel, u32 peer, u32 size, u32 buffer) = 0;
virtual void loadComponent(std::vector<u8>& data, u32 programMask, u32 dataMask) = 0;
virtual void unloadComponent() = 0;
virtual void setSemaphoreMask(u16 value) = 0;
static Audio::DSPCore::Type typeFromString(std::string inString);
static const char* typeToString(Audio::DSPCore::Type type);
};
std::unique_ptr<DSPCore> makeDSPCore(DSPCore::Type type, Memory& mem, Scheduler& scheduler, DSPService& dspService);
} // namespace Audio

View file

@ -0,0 +1,45 @@
#pragma once
#include <array>
#include "audio/dsp_core.hpp"
#include "memory.hpp"
namespace Audio {
class NullDSP : public DSPCore {
enum class DSPState : u32 {
Off,
On,
Slep,
};
// Number of DSP pipes
static constexpr size_t pipeCount = 8;
DSPState dspState;
std::array<std::vector<u8>, pipeCount> pipeData; // The data of each pipe
std::array<u8, Memory::DSP_RAM_SIZE> dspRam;
void resetAudioPipe();
bool loaded = false; // Have we loaded a component?
public:
NullDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService) : DSPCore(mem, scheduler, dspService) {}
void reset() override;
void runAudioFrame() override;
u8* getDspMemory() override { return dspRam.data(); }
u16 recvData(u32 regId) override;
bool recvDataIsReady(u32 regId) override { return true; } // Treat data as always ready
void writeProcessPipe(u32 channel, u32 size, u32 buffer) override;
std::vector<u8> readPipe(u32 channel, u32 peer, u32 size, u32 buffer) override;
// NOPs for null DSP core
void loadComponent(std::vector<u8>& data, u32 programMask, u32 dataMask) override;
void unloadComponent() override;
void setSemaphore(u16 value) override {}
void setSemaphoreMask(u16 value) override {}
};
} // namespace Audio

View file

@ -0,0 +1,99 @@
#pragma once
#include "audio/dsp_core.hpp"
#include "memory.hpp"
#include "swap.hpp"
#include "teakra/teakra.h"
namespace Audio {
class TeakraDSP : public DSPCore {
Teakra::Teakra teakra;
u32 pipeBaseAddr;
bool running; // Is the DSP running?
bool loaded; // Have we finished loading a binary with LoadComponent?
// Get a pointer to a data memory address
u8* getDataPointer(u32 address) { return getDspMemory() + Memory::DSP_DATA_MEMORY_OFFSET + address; }
enum class PipeDirection {
DSPtoCPU = 0,
CPUtoDSP = 1,
};
// A lot of Teakra integration code, especially pipe stuff is based on Citra's integration here:
// https://github.com/citra-emu/citra/blob/master/src/audio_core/lle/lle.cpp
struct PipeStatus {
// All addresses and sizes here refer to byte values, NOT 16-bit values.
u16_le address;
u16_le byteSize;
u16_le readPointer;
u16_le writePointer;
u8 slot;
u8 flags;
static constexpr u16 wrapBit = 0x8000;
static constexpr u16 pointerMask = 0x7FFF;
bool isFull() const { return (readPointer ^ writePointer) == wrapBit; }
bool isEmpty() const { return (readPointer ^ writePointer) == 0; }
// isWrapped: Are read and write pointers in different memory passes.
// true: xxxx]----[xxxx (data is wrapping around the end of memory)
// false: ----[xxxx]----
bool isWrapped() const { return (readPointer ^ writePointer) >= wrapBit; }
};
static_assert(sizeof(PipeStatus) == 10, "Teakra: Pipe Status size is wrong");
static constexpr u8 pipeToSlotIndex(u8 pipe, PipeDirection direction) { return (pipe * 2) + u8(direction); }
PipeStatus getPipeStatus(u8 pipe, PipeDirection direction) {
PipeStatus ret;
const u8 index = pipeToSlotIndex(pipe, direction);
std::memcpy(&ret, getDataPointer(pipeBaseAddr * 2 + index * sizeof(PipeStatus)), sizeof(PipeStatus));
return ret;
}
void updatePipeStatus(const PipeStatus& status) {
u8 slot = status.slot;
u8* statusAddress = getDataPointer(pipeBaseAddr * 2 + slot * sizeof(PipeStatus));
if (slot % 2 == 0) {
std::memcpy(statusAddress + 4, &status.readPointer, sizeof(u16));
} else {
std::memcpy(statusAddress + 6, &status.writePointer, sizeof(u16));
}
}
bool signalledData;
bool signalledSemaphore;
// Run 1 slice of DSP instructions
void runSlice() {
if (running) {
teakra.Run(Audio::lleSlice);
}
}
public:
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 {
runSlice();
scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::lleSlice * 2);
}
u8* getDspMemory() override { return teakra.GetDspMemory().data(); }
u16 recvData(u32 regId) override { return teakra.RecvData(regId); }
bool recvDataIsReady(u32 regId) override { return teakra.RecvDataIsReady(regId); }
void setSemaphore(u16 value) override { teakra.SetSemaphore(value); }
void setSemaphoreMask(u16 value) override { teakra.MaskSemaphore(value); }
void writeProcessPipe(u32 channel, u32 size, u32 buffer) override;
std::vector<u8> readPipe(u32 channel, u32 peer, u32 size, u32 buffer) override;
void loadComponent(std::vector<u8>& data, u32 programMask, u32 dataMask) override;
void unloadComponent() override;
};
} // namespace Audio

View file

@ -1,6 +1,7 @@
#pragma once
#include <filesystem>
#include "audio/dsp_core.hpp"
#include "renderer.hpp"
// Remember to initialize every field here to its default value otherwise bad things will happen
@ -15,6 +16,7 @@ struct EmulatorConfig {
bool shaderJitEnabled = shaderJitDefault;
bool discordRpcEnabled = false;
RendererType rendererType = RendererType::OpenGL;
Audio::DSPCore::Type dspType = Audio::DSPCore::Type::Null;
bool sdCardInserted = true;
bool sdWriteProtected = false;

View file

@ -2,10 +2,12 @@
#include <filesystem>
#include <fstream>
#include <memory>
#include <optional>
#include <span>
#include "PICA/gpu.hpp"
#include "audio/dsp_core.hpp"
#include "cheats.hpp"
#include "config.hpp"
#include "cpu.hpp"
@ -41,9 +43,11 @@ class Emulator {
GPU gpu;
Memory memory;
Kernel kernel;
std::unique_ptr<Audio::DSPCore> dsp;
Scheduler scheduler;
Crypto::AESEngine aesEngine;
Cheats cheats;
Scheduler scheduler;
// Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard
// This is done so when a gamepad is connected, we won't automatically override the 3DS analog stick settings with the gamepad's state

View file

@ -66,15 +66,20 @@ class Kernel {
Handle makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission);
public:
Handle makeEvent(ResetType resetType); // Needs to be public to be accessible to the APT/HID services
Handle makeMutex(bool locked = false); // Needs to be public to be accessible to the APT/DSP services
Handle makeSemaphore(u32 initialCount, u32 maximumCount); // Needs to be public to be accessible to the service manager port
// Needs to be public to be accessible to the APT/HID services
Handle makeEvent(ResetType resetType, Event::CallbackType callback = Event::CallbackType::None);
// Needs to be public to be accessible to the APT/DSP services
Handle makeMutex(bool locked = false);
// Needs to be public to be accessible to the service manager port
Handle makeSemaphore(u32 initialCount, u32 maximumCount);
Handle makeTimer(ResetType resetType);
void pollTimers();
// Signals an event, returns true on success or false if the event does not exist
bool signalEvent(Handle e);
// Run the callback for "special" events that have callbacks
void runEventCallback(Event::CallbackType callback);
void clearEvent(Handle e) {
KernelObject* object = getObject(e, KernelObjectType::Event);
if (object != nullptr) {
@ -240,6 +245,5 @@ public:
ServiceManager& getServiceManager() { return serviceManager; }
void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); }
void signalDSPEvents() { serviceManager.signalDSPEvents(); }
void clearInstructionCache();
};

View file

@ -62,11 +62,19 @@ struct Process {
};
struct Event {
// Some events (for now, only the DSP semaphore events) need to execute a callback when signalled
// This enum stores what kind of callback they should execute
enum class CallbackType : u32 {
None, DSPSemaphore,
};
u64 waitlist; // A bitfield where each bit symbolizes if the thread with thread with the corresponding index is waiting on the event
ResetType resetType = ResetType::OneShot;
CallbackType callback = CallbackType::None;
bool fired = false;
Event(ResetType resetType) : resetType(resetType), waitlist(0) {}
Event(ResetType resetType, CallbackType cb) : resetType(resetType), waitlist(0), callback(cb) {}
};
struct Port {

View file

@ -36,6 +36,7 @@ namespace Log {
static Logger<false> gpuLogger;
static Logger<false> rendererLogger;
static Logger<false> shaderJITLogger;
static Logger<false> dspLogger;
// Service loggers
static Logger<false> acLogger;

View file

@ -100,8 +100,8 @@ namespace KernelMemoryTypes {
class Memory {
u8* fcram;
u8* dspRam;
u8* vram; // Provided to the memory class by the GPU class
u8* dspRam; // Provided to us by Audio
u8* vram; // Provided to the memory class by the GPU class
u64& cpuTicks; // Reference to the CPU tick counter
using SharedMemoryBlock = KernelMemoryTypes::SharedMemoryBlock;
@ -281,6 +281,8 @@ private:
u32 getUsedUserMem() { return usedUserMemory; }
void setVRAM(u8* pointer) { vram = pointer; }
void setDSPMem(u8* pointer) { dspRam = pointer; }
bool allocateMainThreadStack(u32 size);
Regions getConsoleRegion();
void copySharedFont(u8* ptr);

View file

@ -10,7 +10,8 @@ struct Scheduler {
enum class EventType {
VBlank = 0, // End of frame event
UpdateTimers = 1, // Update kernel timer objects
Panic = 2, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX)
RunDSP = 2, // Make the emulated DSP run for one audio frame
Panic = 3, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX)
TotalNumberOfEvents // How many event types do we have in total?
};
static constexpr usize totalNumberOfEvents = static_cast<usize>(EventType::TotalNumberOfEvents);

View file

@ -1,17 +1,12 @@
#pragma once
#include <array>
#include <optional>
#include "audio/dsp_core.hpp"
#include "helpers.hpp"
#include "logger.hpp"
#include "memory.hpp"
#include "result/result.hpp"
namespace DSPPipeType {
enum : u32 {
Debug = 0, DMA = 1, Audio = 2, Binary = 3
};
}
// Circular dependencies!
class Kernel;
@ -19,15 +14,11 @@ class DSPService {
Handle handle = KernelHandles::DSP;
Memory& mem;
Kernel& kernel;
Audio::DSPCore* dsp = nullptr;
MAKE_LOG_FUNCTION(log, dspServiceLogger)
enum class DSPState : u32 {
Off, On, Slep
};
// Number of DSP pipes
static constexpr size_t pipeCount = 8;
DSPState dspState;
// DSP service event handles
using DSPEvent = std::optional<Handle>;
@ -36,10 +27,7 @@ class DSPService {
DSPEvent interrupt0;
DSPEvent interrupt1;
std::array<DSPEvent, pipeCount> pipeEvents;
std::array<std::vector<u8>, pipeCount> pipeData; // The data of each pipe
void resetAudioPipe();
std::vector<u8> readPipe(u32 pipe, u32 size);
u16 semaphoreMask = 0;
DSPEvent& getEventRef(u32 type, u32 pipe);
static constexpr size_t maxEventCount = 6;
@ -67,6 +55,10 @@ public:
DSPService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
void reset();
void handleSyncRequest(u32 messagePointer);
void setDSPCore(Audio::DSPCore* pointer) { dsp = pointer; }
// Special callback that's ran when the semaphore event is signalled
void onSemaphoreEventSignal() { dsp->setSemaphore(semaphoreMask); }
enum class SoundOutputMode : u8 {
Mono = 0,
@ -74,5 +66,8 @@ public:
Surround = 2
};
void signalEvents();
void triggerPipeEvent(int index);
void triggerSemaphoreEvent();
void triggerInterrupt0();
void triggerInterrupt1();
};

View file

@ -105,9 +105,8 @@ class ServiceManager {
void setHIDSharedMem(u8* ptr) { hid.setSharedMem(ptr); }
void setCSNDSharedMem(u8* ptr) { csnd.setSharedMemory(ptr); }
void signalDSPEvents() { dsp.signalEvents(); }
// Input function wrappers
HIDService& getHID() { return hid; }
NFCService& getNFC() { return nfc; }
DSPService& getDSP() { return dsp; }
};