mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-06-03 12:27:21 +12:00
Merge branch 'master' into specialized-shaderz
This commit is contained in:
commit
58da6ea8a4
97 changed files with 2168 additions and 501 deletions
5
include/android_utils.hpp
Normal file
5
include/android_utils.hpp
Normal file
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
namespace AndroidUtils {
|
||||
int openDocument(const char* directory, const char* mode);
|
||||
}
|
66
include/audio/dsp_core.hpp
Normal file
66
include/audio/dsp_core.hpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "ring_buffer.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 {
|
||||
using Samples = Common::RingBuffer<s16, 1024>;
|
||||
|
||||
protected:
|
||||
Memory& mem;
|
||||
Scheduler& scheduler;
|
||||
DSPService& dspService;
|
||||
|
||||
Samples sampleBuffer;
|
||||
bool audioEnabled = false;
|
||||
|
||||
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 ~DSPCore() {}
|
||||
|
||||
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);
|
||||
|
||||
Samples& getSamples() { return sampleBuffer; }
|
||||
virtual void setAudioEnabled(bool enable) { audioEnabled = enable; }
|
||||
};
|
||||
|
||||
std::unique_ptr<DSPCore> makeDSPCore(DSPCore::Type type, Memory& mem, Scheduler& scheduler, DSPService& dspService);
|
||||
} // namespace Audio
|
31
include/audio/miniaudio_device.hpp
Normal file
31
include/audio/miniaudio_device.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "miniaudio.h"
|
||||
#include "ring_buffer.hpp"
|
||||
|
||||
class MiniAudioDevice {
|
||||
using Samples = Common::RingBuffer<ma_int16, 1024>;
|
||||
static constexpr ma_uint32 sampleRate = 32768; // 3DS sample rate
|
||||
static constexpr ma_uint32 channelCount = 2; // Audio output is stereo
|
||||
|
||||
ma_context context;
|
||||
ma_device_config deviceConfig;
|
||||
ma_device device;
|
||||
ma_resampler resampler;
|
||||
Samples* samples = nullptr;
|
||||
|
||||
bool initialized = false;
|
||||
bool running = false;
|
||||
|
||||
std::vector<std::string> audioDevices;
|
||||
public:
|
||||
MiniAudioDevice();
|
||||
// If safe is on, we create a null audio device
|
||||
void init(Samples& samples, bool safe = false);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
};
|
46
include/audio/null_core.hpp
Normal file
46
include/audio/null_core.hpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#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) {}
|
||||
~NullDSP() override {}
|
||||
|
||||
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
|
104
include/audio/teakra_core.hpp
Normal file
104
include/audio/teakra_core.hpp
Normal file
|
@ -0,0 +1,104 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
|
||||
#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?
|
||||
bool signalledData;
|
||||
bool signalledSemaphore;
|
||||
|
||||
uint audioFrameIndex = 0; // Index in our audio frame
|
||||
std::array<s16, 160 * 2> audioFrame;
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
// Run 1 slice of DSP instructions
|
||||
void runSlice() {
|
||||
if (running) {
|
||||
teakra.Run(Audio::lleSlice);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
TeakraDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService);
|
||||
~TeakraDSP() override {}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void setAudioEnabled(bool enable) override;
|
||||
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
|
|
@ -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,11 +16,15 @@ 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;
|
||||
bool usePortableBuild = false;
|
||||
|
||||
bool audioEnabled = false;
|
||||
bool vsyncEnabled = true;
|
||||
|
||||
bool chargerPlugged = true;
|
||||
// Default to 3% battery to make users suffer
|
||||
int batteryPercentage = 3;
|
||||
|
|
|
@ -17,7 +17,6 @@ class CPU;
|
|||
class MyEnvironment final : public Dynarmic::A32::UserCallbacks {
|
||||
public:
|
||||
u64 ticksLeft = 0;
|
||||
u64 totalTicks = 0;
|
||||
Memory& mem;
|
||||
Kernel& kernel;
|
||||
Scheduler& scheduler;
|
||||
|
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
#include "PICA/gpu.hpp"
|
||||
#include "audio/dsp_core.hpp"
|
||||
#include "audio/miniaudio_device.hpp"
|
||||
#include "cheats.hpp"
|
||||
#include "config.hpp"
|
||||
#include "cpu.hpp"
|
||||
|
@ -41,10 +44,13 @@ class Emulator {
|
|||
GPU gpu;
|
||||
Memory memory;
|
||||
Kernel kernel;
|
||||
Crypto::AESEngine aesEngine;
|
||||
Cheats cheats;
|
||||
std::unique_ptr<Audio::DSPCore> dsp;
|
||||
Scheduler scheduler;
|
||||
|
||||
Crypto::AESEngine aesEngine;
|
||||
MiniAudioDevice audioDevice;
|
||||
Cheats cheats;
|
||||
|
||||
// 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
|
||||
// And so the user can still use the keyboard to control the analog
|
||||
|
@ -71,6 +77,7 @@ class Emulator {
|
|||
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
|
||||
Discord::RPC discordRpc;
|
||||
#endif
|
||||
void setAudioEnabled(bool enable);
|
||||
void updateDiscord();
|
||||
|
||||
// Keep the handle for the ROM here to reload when necessary and to prevent deleting it
|
||||
|
@ -128,6 +135,7 @@ class Emulator {
|
|||
ServiceManager& getServiceManager() { return kernel.getServiceManager(); }
|
||||
LuaManager& getLua() { return lua; }
|
||||
Scheduler& getScheduler() { return scheduler; }
|
||||
Memory& getMemory() { return memory; }
|
||||
|
||||
RendererType getRendererType() const { return config.rendererType; }
|
||||
Renderer* getRenderer() { return gpu.getRenderer(); }
|
||||
|
|
|
@ -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();
|
||||
};
|
|
@ -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 {
|
||||
|
|
|
@ -36,6 +36,7 @@ struct NCCH {
|
|||
};
|
||||
|
||||
u64 partitionIndex = 0;
|
||||
u64 programID = 0;
|
||||
u64 fileOffset = 0;
|
||||
|
||||
bool isNew3DS = false;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#include <string>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "memory.hpp"
|
||||
|
||||
// The kinds of events that can cause a Lua call.
|
||||
// Frame: Call program on frame end
|
||||
|
@ -11,6 +10,8 @@ enum class LuaEvent {
|
|||
Frame,
|
||||
};
|
||||
|
||||
class Emulator;
|
||||
|
||||
#ifdef PANDA3DS_ENABLE_LUA
|
||||
extern "C" {
|
||||
#include <lauxlib.h>
|
||||
|
@ -30,9 +31,9 @@ class LuaManager {
|
|||
public:
|
||||
// For Lua we must have some global pointers to our emulator objects to use them in script code via thunks. See the thunks in lua.cpp as an
|
||||
// example
|
||||
static Memory* g_memory;
|
||||
static Emulator* g_emulator;
|
||||
|
||||
LuaManager(Memory& mem) { g_memory = &mem; }
|
||||
LuaManager(Emulator& emulator) { g_emulator = &emulator; }
|
||||
|
||||
void close();
|
||||
void initialize();
|
||||
|
@ -51,7 +52,7 @@ class LuaManager {
|
|||
#else // Lua not enabled, Lua manager does nothing
|
||||
class LuaManager {
|
||||
public:
|
||||
LuaManager(Memory& mem) {}
|
||||
LuaManager(Emulator& emulator) {}
|
||||
|
||||
void close() {}
|
||||
void initialize() {}
|
||||
|
|
|
@ -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;
|
||||
|
@ -275,12 +275,16 @@ private:
|
|||
// File handle for reading the loaded ncch
|
||||
IOFile CXIFile;
|
||||
|
||||
std::optional<u64> getProgramID();
|
||||
|
||||
u8* getDSPMem() { return dspRam; }
|
||||
u8* getDSPDataMem() { return &dspRam[DSP_DATA_MEMORY_OFFSET]; }
|
||||
u8* getDSPCodeMem() { return &dspRam[DSP_CODE_MEMORY_OFFSET]; }
|
||||
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);
|
||||
|
|
111
include/ring_buffer.hpp
Normal file
111
include/ring_buffer.hpp
Normal file
|
@ -0,0 +1,111 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
/// SPSC ring buffer
|
||||
/// @tparam T Element type
|
||||
/// @tparam capacity Number of slots in ring buffer
|
||||
template <typename T, std::size_t capacity>
|
||||
class RingBuffer {
|
||||
/// A "slot" is made of a single `T`.
|
||||
static constexpr std::size_t slot_size = sizeof(T);
|
||||
// T must be safely memcpy-able and have a trivial default constructor.
|
||||
static_assert(std::is_trivial_v<T>);
|
||||
// Ensure capacity is sensible.
|
||||
static_assert(capacity < std::numeric_limits<std::size_t>::max() / 2);
|
||||
static_assert((capacity & (capacity - 1)) == 0, "capacity must be a power of two");
|
||||
// Ensure lock-free.
|
||||
static_assert(std::atomic_size_t::is_always_lock_free);
|
||||
|
||||
public:
|
||||
/// Pushes slots into the ring buffer
|
||||
/// @param new_slots Pointer to the slots to push
|
||||
/// @param slot_count Number of slots to push
|
||||
/// @returns The number of slots actually pushed
|
||||
std::size_t push(const void* new_slots, std::size_t slot_count) {
|
||||
const std::size_t write_index = m_write_index.load();
|
||||
const std::size_t slots_free = capacity + m_read_index.load() - write_index;
|
||||
const std::size_t push_count = std::min(slot_count, slots_free);
|
||||
|
||||
const std::size_t pos = write_index % capacity;
|
||||
const std::size_t first_copy = std::min(capacity - pos, push_count);
|
||||
const std::size_t second_copy = push_count - first_copy;
|
||||
|
||||
const char* in = static_cast<const char*>(new_slots);
|
||||
std::memcpy(m_data.data() + pos, in, first_copy * slot_size);
|
||||
in += first_copy * slot_size;
|
||||
std::memcpy(m_data.data(), in, second_copy * slot_size);
|
||||
|
||||
m_write_index.store(write_index + push_count);
|
||||
|
||||
return push_count;
|
||||
}
|
||||
|
||||
std::size_t push(std::span<const T> input) { return push(input.data(), input.size()); }
|
||||
|
||||
/// Pops slots from the ring buffer
|
||||
/// @param output Where to store the popped slots
|
||||
/// @param max_slots Maximum number of slots to pop
|
||||
/// @returns The number of slots actually popped
|
||||
std::size_t pop(void* output, std::size_t max_slots = ~std::size_t(0)) {
|
||||
const std::size_t read_index = m_read_index.load();
|
||||
const std::size_t slots_filled = m_write_index.load() - read_index;
|
||||
const std::size_t pop_count = std::min(slots_filled, max_slots);
|
||||
|
||||
const std::size_t pos = read_index % capacity;
|
||||
const std::size_t first_copy = std::min(capacity - pos, pop_count);
|
||||
const std::size_t second_copy = pop_count - first_copy;
|
||||
|
||||
char* out = static_cast<char*>(output);
|
||||
std::memcpy(out, m_data.data() + pos, first_copy * slot_size);
|
||||
out += first_copy * slot_size;
|
||||
std::memcpy(out, m_data.data(), second_copy * slot_size);
|
||||
|
||||
m_read_index.store(read_index + pop_count);
|
||||
|
||||
return pop_count;
|
||||
}
|
||||
|
||||
std::vector<T> pop(std::size_t max_slots = ~std::size_t(0)) {
|
||||
std::vector<T> out(std::min(max_slots, capacity));
|
||||
const std::size_t count = Pop(out.data(), out.size());
|
||||
out.resize(count);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// @returns Number of slots used
|
||||
[[nodiscard]] std::size_t size() const { return m_write_index.load() - m_read_index.load(); }
|
||||
|
||||
/// @returns Maximum size of ring buffer
|
||||
[[nodiscard]] constexpr std::size_t Capacity() const { return capacity; }
|
||||
|
||||
private:
|
||||
// It is important to align the below variables for performance reasons:
|
||||
// Having them on the same cache-line would result in false-sharing between them.
|
||||
// TODO: Remove this ifdef whenever clang and GCC support
|
||||
// std::hardware_destructive_interference_size.
|
||||
#if defined(__cpp_lib_hardware_interference_size) && !defined(__ANDROID__)
|
||||
alignas(std::hardware_destructive_interference_size) std::atomic_size_t m_read_index{0};
|
||||
alignas(std::hardware_destructive_interference_size) std::atomic_size_t m_write_index{0};
|
||||
#else
|
||||
alignas(128) std::atomic_size_t m_read_index{0};
|
||||
alignas(128) std::atomic_size_t m_write_index{0};
|
||||
#endif
|
||||
|
||||
std::array<T, capacity> m_data;
|
||||
};
|
||||
|
||||
} // namespace Common
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
};
|
|
@ -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; }
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue