diff --git a/.gitmodules b/.gitmodules index b7f41e5f6..b0e5a858c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -38,11 +38,14 @@ path = externals/discord-rpc url = https://github.com/discordapp/discord-rpc.git [submodule "externals/libzmq"] - path = externals/libzmq - url = https://github.com/zeromq/libzmq + path = externals/libzmq + url = https://github.com/zeromq/libzmq [submodule "externals/cppzmq"] - path = externals/cppzmq - url = https://github.com/zeromq/cppzmq + path = externals/cppzmq + url = https://github.com/zeromq/cppzmq [submodule "cpp-jwt"] - path = externals/cpp-jwt - url = https://github.com/arun11299/cpp-jwt.git + path = externals/cpp-jwt + url = https://github.com/arun11299/cpp-jwt.git +[submodule "teakra"] + path = externals/teakra + url = https://github.com/wwylele/teakra.git diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index b96bebb7f..55bf4f8c4 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -51,6 +51,9 @@ add_subdirectory(soundtouch) # The SoundTouch target doesn't export the necessary include paths as properties by default target_include_directories(SoundTouch INTERFACE ./soundtouch/include) +# Teakra +add_subdirectory(teakra) + # Xbyak if (ARCHITECTURE_x86_64) # Defined before "dynarmic" above diff --git a/externals/teakra b/externals/teakra new file mode 160000 index 000000000..e6ea0eae6 --- /dev/null +++ b/externals/teakra @@ -0,0 +1 @@ +Subproject commit e6ea0eae656c022d7878ffabc4e016b3e6f0c536 diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index de6079edc..538cfd894 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -14,6 +14,8 @@ add_library(audio_core STATIC hle/shared_memory.h hle/source.cpp hle/source.h + lle/lle.cpp + lle/lle.h interpolate.cpp interpolate.h null_sink.h @@ -30,7 +32,7 @@ add_library(audio_core STATIC create_target_directory_groups(audio_core) target_link_libraries(audio_core PUBLIC common core) -target_link_libraries(audio_core PRIVATE SoundTouch) +target_link_libraries(audio_core PRIVATE SoundTouch teakra) if(SDL2_FOUND) target_link_libraries(audio_core PRIVATE SDL2) @@ -41,4 +43,3 @@ if(ENABLE_CUBEB) target_link_libraries(audio_core PRIVATE cubeb) add_definitions(-DHAVE_CUBEB=1) endif() - diff --git a/src/audio_core/dsp_interface.cpp b/src/audio_core/dsp_interface.cpp index 2beb74343..ca1a5a047 100644 --- a/src/audio_core/dsp_interface.cpp +++ b/src/audio_core/dsp_interface.cpp @@ -43,6 +43,13 @@ void DspInterface::OutputFrame(StereoFrame16& frame) { fifo.Push(frame.data(), frame.size()); } +void DspInterface::OutputSample(std::array sample) { + if (!sink) + return; + + fifo.Push(&sample, 1); +} + void DspInterface::OutputCallback(s16* buffer, std::size_t num_frames) { std::size_t frames_written; if (perform_time_stretching) { diff --git a/src/audio_core/dsp_interface.h b/src/audio_core/dsp_interface.h index aef57db87..e7f996bb6 100644 --- a/src/audio_core/dsp_interface.h +++ b/src/audio_core/dsp_interface.h @@ -32,8 +32,26 @@ public: DspInterface& operator=(const DspInterface&) = delete; DspInterface& operator=(DspInterface&&) = delete; - /// Get the state of the DSP - virtual DspState GetDspState() const = 0; + /** + * Reads data from one of three DSP registers + * @note this function blocks until the data is available + * @param register_number the index of the register to read + * @returns the value of the register + */ + virtual u16 RecvData(u32 register_number) = 0; + + /** + * Checks whether data is ready in one of three DSP registers + * @param register_number the index of the register to check + * @returns true if data is ready + */ + virtual bool RecvDataIsReady(u32 register_number) const = 0; + + /** + * Sets the DSP semaphore register + * @param semaphore_value the value set to the semaphore register + */ + virtual void SetSemaphore(u16 semaphore_value) = 0; /** * Reads `length` bytes from the DSP pipe identified with `pipe_number`. @@ -70,6 +88,12 @@ public: /// Sets the dsp class that we trigger interrupts for virtual void SetServiceToInterrupt(std::weak_ptr dsp) = 0; + /// Loads the DSP program + virtual void LoadComponent(const std::vector& buffer) = 0; + + /// Unloads the DSP program + virtual void UnloadComponent() = 0; + /// Select the sink to use based on sink id. void SetSink(const std::string& sink_id, const std::string& audio_device); /// Get the current sink @@ -79,6 +103,7 @@ public: protected: void OutputFrame(StereoFrame16& frame); + void OutputSample(std::array sample); private: void FlushResidualStretcherAudio(); diff --git a/src/audio_core/hle/hle.cpp b/src/audio_core/hle/hle.cpp index 4fa4145e2..6e7b45278 100644 --- a/src/audio_core/hle/hle.cpp +++ b/src/audio_core/hle/hle.cpp @@ -11,6 +11,7 @@ #include "audio_core/sink.h" #include "common/assert.h" #include "common/common_types.h" +#include "common/hash.h" #include "common/logging/log.h" #include "core/core.h" #include "core/core_timing.h" @@ -29,6 +30,8 @@ public: DspState GetDspState() const; + u16 RecvData(u32 register_number); + bool RecvDataIsReady(u32 register_number) const; std::vector PipeRead(DspPipe pipe_number, u32 length); std::size_t GetPipeReadableSize(DspPipe pipe_number) const; void PipeWrite(DspPipe pipe_number, const std::vector& buffer); @@ -93,6 +96,29 @@ DspState DspHle::Impl::GetDspState() const { return dsp_state; } +u16 DspHle::Impl::RecvData(u32 register_number) { + ASSERT_MSG(register_number == 0, "Unknown register_number {}", register_number); + + // Application reads this after requesting DSP shutdown, to verify the DSP has indeed shutdown + // or slept. + + switch (GetDspState()) { + case AudioCore::DspState::On: + return 0; + case AudioCore::DspState::Off: + case AudioCore::DspState::Sleeping: + return 1; + default: + UNREACHABLE(); + break; + } +} + +bool DspHle::Impl::RecvDataIsReady(u32 register_number) const { + ASSERT_MSG(register_number == 0, "Unknown register_number {}", register_number); + return true; +} + std::vector DspHle::Impl::PipeRead(DspPipe pipe_number, u32 length) { const std::size_t pipe_index = static_cast(pipe_number); @@ -342,8 +368,16 @@ void DspHle::Impl::AudioTickCallback(s64 cycles_late) { DspHle::DspHle(Memory::MemorySystem& memory) : impl(std::make_unique(*this, memory)) {} DspHle::~DspHle() = default; -DspState DspHle::GetDspState() const { - return impl->GetDspState(); +u16 DspHle::RecvData(u32 register_number) { + return impl->RecvData(register_number); +} + +bool DspHle::RecvDataIsReady(u32 register_number) const { + return impl->RecvDataIsReady(register_number); +} + +void DspHle::SetSemaphore(u16 semaphore_value) { + // Do nothing in HLE } std::vector DspHle::PipeRead(DspPipe pipe_number, u32 length) { @@ -366,4 +400,19 @@ void DspHle::SetServiceToInterrupt(std::weak_ptr dsp) { impl->SetServiceToInterrupt(std::move(dsp)); } +void DspHle::LoadComponent(const std::vector& component_data) { + // HLE doesn't need DSP program. Only log some info here + LOG_INFO(Service_DSP, "Firmware hash: {:#018x}", + Common::ComputeHash64(component_data.data(), component_data.size())); + // Some versions of the firmware have the location of DSP structures listed here. + if (component_data.size() > 0x37C) { + LOG_INFO(Service_DSP, "Structures hash: {:#018x}", + Common::ComputeHash64(component_data.data() + 0x340, 60)); + } +} + +void DspHle::UnloadComponent() { + // Do nothing +} + } // namespace AudioCore diff --git a/src/audio_core/hle/hle.h b/src/audio_core/hle/hle.h index 70abddbd5..4ab468331 100644 --- a/src/audio_core/hle/hle.h +++ b/src/audio_core/hle/hle.h @@ -24,8 +24,9 @@ public: explicit DspHle(Memory::MemorySystem& memory); ~DspHle(); - DspState GetDspState() const override; - + u16 RecvData(u32 register_number) override; + bool RecvDataIsReady(u32 register_number) const override; + void SetSemaphore(u16 semaphore_value) override; std::vector PipeRead(DspPipe pipe_number, u32 length) override; std::size_t GetPipeReadableSize(DspPipe pipe_number) const override; void PipeWrite(DspPipe pipe_number, const std::vector& buffer) override; @@ -34,6 +35,9 @@ public: void SetServiceToInterrupt(std::weak_ptr dsp) override; + void LoadComponent(const std::vector& buffer) override; + void UnloadComponent() override; + private: struct Impl; friend struct Impl; diff --git a/src/audio_core/lle/lle.cpp b/src/audio_core/lle/lle.cpp new file mode 100644 index 000000000..e9948d1c2 --- /dev/null +++ b/src/audio_core/lle/lle.cpp @@ -0,0 +1,490 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include "audio_core/lle/lle.h" +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/swap.h" +#include "common/thread.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/lock.h" +#include "core/hle/service/dsp/dsp_dsp.h" + +namespace AudioCore { + +enum class SegmentType : u8 { + ProgramA = 0, + ProgramB = 1, + Data = 2, +}; + +class Dsp1 { +public: + explicit Dsp1(const std::vector& raw); + + struct Header { + std::array signature; + std::array magic; + u32_le binary_size; + u16_le memory_layout; + INSERT_PADDING_BYTES(3); + SegmentType special_segment_type; + u8 num_segments; + union { + BitField<0, 1, u8> recv_data_on_start; + BitField<1, 1, u8> load_special_segment; + }; + u32_le special_segment_address; + u32_le special_segment_size; + u64_le zero; + struct Segment { + u32_le offset; + u32_le address; + u32_le size; + INSERT_PADDING_BYTES(3); + SegmentType memory_type; + std::array sha256; + }; + std::array segments; + }; + static_assert(sizeof(Header) == 0x300); + + struct Segment { + std::vector data; + SegmentType memory_type; + u32 target; + }; + + std::vector segments; + bool recv_data_on_start; +}; + +Dsp1::Dsp1(const std::vector& raw) { + Header header; + std::memcpy(&header, raw.data(), sizeof(header)); + recv_data_on_start = header.recv_data_on_start != 0; + for (u32 i = 0; i < header.num_segments; ++i) { + Segment segment; + segment.data = + std::vector(raw.begin() + header.segments[i].offset, + raw.begin() + header.segments[i].offset + header.segments[i].size); + segment.memory_type = header.segments[i].memory_type; + segment.target = header.segments[i].address; + segments.push_back(std::move(segment)); + } +} + +struct PipeStatus { + u16_le waddress; + u16_le bsize; + u16_le read_bptr; + u16_le write_bptr; + u8 slot_index; + u8 flags; + + static constexpr u16 WrapBit = 0x8000; + static constexpr u16 PtrMask = 0x7FFF; + + bool IsFull() const { + return (read_bptr ^ write_bptr) == WrapBit; + } + + bool IsEmpty() const { + return (read_bptr ^ write_bptr) == 0; + } + + /* + * IsWrapped: Are read and write pointers not in the same pass. + * false: ----[xxxx]---- + * true: xxxx]----[xxxx (data is wrapping around the end) + */ + bool IsWrapped() const { + return (read_bptr ^ write_bptr) >= WrapBit; + } +}; + +static_assert(sizeof(PipeStatus) == 10); + +enum class PipeDirection : u8 { + DSPtoCPU = 0, + CPUtoDSP = 1, +}; + +static u8 PipeIndexToSlotIndex(u8 pipe_index, PipeDirection direction) { + return (pipe_index << 1) + static_cast(direction); +} + +struct DspLle::Impl final { + Impl(bool multithread) : multithread(multithread) { + teakra_slice_event = Core::System::GetInstance().CoreTiming().RegisterEvent( + "DSP slice", [this](u64, int late) { TeakraSliceEvent(static_cast(late)); }); + } + + ~Impl() { + StopTeakraThread(); + } + + Teakra::Teakra teakra; + u16 pipe_base_waddr = 0; + + bool semaphore_signaled = false; + bool data_signaled = false; + + Core::TimingEventType* teakra_slice_event; + std::atomic loaded = false; + + const bool multithread; + std::thread teakra_thread; + Common::Barrier teakra_slice_barrier{2}; + std::atomic stop_signal = false; + std::size_t stop_generation; + + static constexpr u32 DspDataOffset = 0x40000; + static constexpr u32 TeakraSlice = 20000; + + void TeakraThread() { + while (true) { + teakra.Run(TeakraSlice); + teakra_slice_barrier.Sync(); + if (stop_signal) { + if (stop_generation == teakra_slice_barrier.Generation()) + break; + } + } + stop_signal = false; + } + + void StopTeakraThread() { + if (teakra_thread.joinable()) { + stop_generation = teakra_slice_barrier.Generation() + 1; + stop_signal = true; + teakra_slice_barrier.Sync(); + teakra_thread.join(); + } + } + + void RunTeakraSlice() { + if (multithread) { + teakra_slice_barrier.Sync(); + } else { + teakra.Run(TeakraSlice); + } + } + + void TeakraSliceEvent(u64 late) { + RunTeakraSlice(); + u64 next = TeakraSlice * 2; // DSP runs at clock rate half of the CPU rate + if (next < late) + next = 0; + else + next -= late; + Core::System::GetInstance().CoreTiming().ScheduleEvent(next, teakra_slice_event, 0); + } + + u8* GetDspDataPointer(u32 baddr) { + auto& memory = teakra.GetDspMemory(); + return &memory[DspDataOffset + baddr]; + } + + const u8* GetDspDataPointer(u32 baddr) const { + auto& memory = teakra.GetDspMemory(); + return &memory[DspDataOffset + baddr]; + } + + PipeStatus GetPipeStatus(u8 pipe_index, PipeDirection direction) const { + u8 slot_index = PipeIndexToSlotIndex(pipe_index, direction); + PipeStatus pipe_status; + std::memcpy(&pipe_status, + GetDspDataPointer(pipe_base_waddr * 2 + slot_index * sizeof(PipeStatus)), + sizeof(PipeStatus)); + ASSERT(pipe_status.slot_index == slot_index); + return pipe_status; + } + + void UpdatePipeStatus(const PipeStatus& pipe_status) { + u8 slot_index = pipe_status.slot_index; + u8* status_address = + GetDspDataPointer(pipe_base_waddr * 2 + slot_index * sizeof(PipeStatus)); + if (slot_index % 2 == 0) { + std::memcpy(status_address + 4, &pipe_status.read_bptr, sizeof(u16)); + } else { + std::memcpy(status_address + 6, &pipe_status.write_bptr, sizeof(u16)); + } + } + + void WritePipe(u8 pipe_index, const std::vector& data) { + PipeStatus pipe_status = GetPipeStatus(pipe_index, PipeDirection::CPUtoDSP); + bool need_update = false; + const u8* buffer_ptr = data.data(); + u16 bsize = static_cast(data.size()); + while (bsize != 0) { + ASSERT_MSG(!pipe_status.IsFull(), "Pipe is Full"); + u16 write_bend; + if (pipe_status.IsWrapped()) + write_bend = pipe_status.read_bptr & PipeStatus::PtrMask; + else + write_bend = pipe_status.bsize; + u16 write_bbegin = pipe_status.write_bptr & PipeStatus::PtrMask; + ASSERT_MSG(write_bend > write_bbegin, + "Pipe is in inconsistent state: end {:04X} <= begin {:04X}, size {:04X}", + write_bend, write_bbegin, pipe_status.bsize); + u16 write_bsize = std::min(bsize, write_bend - write_bbegin); + std::memcpy(GetDspDataPointer(pipe_status.waddress * 2 + write_bbegin), buffer_ptr, + write_bsize); + buffer_ptr += write_bsize; + pipe_status.write_bptr += write_bsize; + bsize -= write_bsize; + ASSERT_MSG((pipe_status.write_bptr & PipeStatus::PtrMask) <= pipe_status.bsize, + "Pipe is in inconsistent state: write > size"); + if ((pipe_status.write_bptr & PipeStatus::PtrMask) == pipe_status.bsize) { + pipe_status.write_bptr &= PipeStatus::WrapBit; + pipe_status.write_bptr ^= PipeStatus::WrapBit; + } + need_update = true; + } + if (need_update) { + UpdatePipeStatus(pipe_status); + while (!teakra.SendDataIsEmpty(2)) + RunTeakraSlice(); + teakra.SendData(2, pipe_status.slot_index); + } + } + + std::vector ReadPipe(u8 pipe_index, u16 bsize) { + PipeStatus pipe_status = GetPipeStatus(pipe_index, PipeDirection::DSPtoCPU); + bool need_update = false; + std::vector data(bsize); + u8* buffer_ptr = data.data(); + while (bsize != 0) { + ASSERT_MSG(!pipe_status.IsEmpty(), "Pipe is empty"); + u16 read_bend; + if (pipe_status.IsWrapped()) { + read_bend = pipe_status.bsize; + } else { + read_bend = pipe_status.write_bptr & PipeStatus::PtrMask; + } + u16 read_bbegin = pipe_status.read_bptr & PipeStatus::PtrMask; + ASSERT(read_bend > read_bbegin); + u16 read_bsize = std::min(bsize, read_bend - read_bbegin); + std::memcpy(buffer_ptr, GetDspDataPointer(pipe_status.waddress * 2 + read_bbegin), + read_bsize); + buffer_ptr += read_bsize; + pipe_status.read_bptr += read_bsize; + bsize -= read_bsize; + ASSERT_MSG((pipe_status.read_bptr & PipeStatus::PtrMask) <= pipe_status.bsize, + "Pipe is in inconsistent state: read > size"); + if ((pipe_status.read_bptr & PipeStatus::PtrMask) == pipe_status.bsize) { + pipe_status.read_bptr &= PipeStatus::WrapBit; + pipe_status.read_bptr ^= PipeStatus::WrapBit; + } + need_update = true; + } + if (need_update) { + UpdatePipeStatus(pipe_status); + while (!teakra.SendDataIsEmpty(2)) + RunTeakraSlice(); + teakra.SendData(2, pipe_status.slot_index); + } + return data; + } + u16 GetPipeReadableSize(u8 pipe_index) const { + PipeStatus pipe_status = GetPipeStatus(pipe_index, PipeDirection::DSPtoCPU); + u16 size = pipe_status.write_bptr - pipe_status.read_bptr; + if (pipe_status.IsWrapped()) { + size += pipe_status.bsize; + } + return size & PipeStatus::PtrMask; + } + + void LoadComponent(const std::vector& buffer) { + if (loaded) { + LOG_ERROR(Audio_DSP, "Component already loaded!"); + return; + } + + teakra.Reset(); + + Dsp1 dsp(buffer); + auto& dsp_memory = teakra.GetDspMemory(); + u8* program = dsp_memory.data(); + u8* data = dsp_memory.data() + DspDataOffset; + for (const auto& segment : dsp.segments) { + if (segment.memory_type == SegmentType::ProgramA || + segment.memory_type == SegmentType::ProgramB) { + std::memcpy(program + segment.target * 2, segment.data.data(), segment.data.size()); + } else if (segment.memory_type == SegmentType::Data) { + std::memcpy(data + segment.target * 2, segment.data.data(), segment.data.size()); + } + } + + // TODO: load special segment + + Core::System::GetInstance().CoreTiming().ScheduleEvent(TeakraSlice, teakra_slice_event, 0); + + if (multithread) { + teakra_thread = std::thread(&Impl::TeakraThread, this); + } + + // Wait for initialization + if (dsp.recv_data_on_start) { + for (u8 i = 0; i < 3; ++i) { + do { + while (!teakra.RecvDataIsReady(i)) + RunTeakraSlice(); + } while (teakra.RecvData(i) != 1); + } + } + + // Get pipe base address + while (!teakra.RecvDataIsReady(2)) + RunTeakraSlice(); + pipe_base_waddr = teakra.RecvData(2); + + loaded = true; + } + + void UnloadComponent() { + if (!loaded) { + LOG_ERROR(Audio_DSP, "Component not loaded!"); + return; + } + + loaded = false; + + // Send finalization signal via command/reply register 2 + constexpr u16 FinalizeSignal = 0x8000; + while (!teakra.SendDataIsEmpty(2)) + RunTeakraSlice(); + + teakra.SendData(2, FinalizeSignal); + + // Wait for completion + while (!teakra.RecvDataIsReady(2)) + RunTeakraSlice(); + + teakra.RecvData(2); // discard the value + + Core::System::GetInstance().CoreTiming().UnscheduleEvent(teakra_slice_event, 0); + StopTeakraThread(); + } +}; + +u16 DspLle::RecvData(u32 register_number) { + while (!impl->teakra.RecvDataIsReady(register_number)) { + impl->RunTeakraSlice(); + } + return impl->teakra.RecvData(static_cast(register_number)); +} + +bool DspLle::RecvDataIsReady(u32 register_number) const { + return impl->teakra.RecvDataIsReady(register_number); +} + +void DspLle::SetSemaphore(u16 semaphore_value) { + impl->teakra.SetSemaphore(semaphore_value); +} + +std::vector DspLle::PipeRead(DspPipe pipe_number, u32 length) { + return impl->ReadPipe(static_cast(pipe_number), static_cast(length)); +} + +std::size_t DspLle::GetPipeReadableSize(DspPipe pipe_number) const { + return impl->GetPipeReadableSize(static_cast(pipe_number)); +} + +void DspLle::PipeWrite(DspPipe pipe_number, const std::vector& buffer) { + impl->WritePipe(static_cast(pipe_number), buffer); +} + +std::array& DspLle::GetDspMemory() { + return impl->teakra.GetDspMemory(); +} + +void DspLle::SetServiceToInterrupt(std::weak_ptr dsp) { + impl->teakra.SetRecvDataHandler(0, [this, dsp]() { + if (!impl->loaded) + return; + + std::lock_guard lock(HLE::g_hle_lock); + if (auto locked = dsp.lock()) { + locked->SignalInterrupt(Service::DSP::DSP_DSP::InterruptType::Zero, + static_cast(0)); + } + }); + impl->teakra.SetRecvDataHandler(1, [this, dsp]() { + if (!impl->loaded) + return; + + std::lock_guard lock(HLE::g_hle_lock); + if (auto locked = dsp.lock()) { + locked->SignalInterrupt(Service::DSP::DSP_DSP::InterruptType::One, + static_cast(0)); + } + }); + + auto ProcessPipeEvent = [this, dsp](bool event_from_data) { + if (!impl->loaded) + return; + + auto& teakra = impl->teakra; + if (event_from_data) { + impl->data_signaled = true; + } else { + if ((teakra.GetSemaphore() & 0x8000) == 0) + return; + impl->semaphore_signaled = true; + } + if (impl->semaphore_signaled && impl->data_signaled) { + impl->semaphore_signaled = impl->data_signaled = false; + u16 slot = teakra.RecvData(2); + u16 side = slot % 2; + u16 pipe = slot / 2; + ASSERT(pipe < 16); + if (side != static_cast(PipeDirection::DSPtoCPU)) + return; + if (pipe == 0) { + // pipe 0 is for debug. 3DS automatically drains this pipe and discards the data + impl->ReadPipe(pipe, impl->GetPipeReadableSize(pipe)); + } else { + std::lock_guard lock(HLE::g_hle_lock); + if (auto locked = dsp.lock()) { + locked->SignalInterrupt(Service::DSP::DSP_DSP::InterruptType::Pipe, + static_cast(pipe)); + } + } + } + }; + + impl->teakra.SetRecvDataHandler(2, [ProcessPipeEvent]() { ProcessPipeEvent(true); }); + impl->teakra.SetSemaphoreHandler([ProcessPipeEvent]() { ProcessPipeEvent(false); }); +} + +void DspLle::LoadComponent(const std::vector& buffer) { + impl->LoadComponent(buffer); +} + +void DspLle::UnloadComponent() { + impl->UnloadComponent(); +} + +DspLle::DspLle(Memory::MemorySystem& memory, bool multithread) + : impl(std::make_unique(multithread)) { + Teakra::AHBMCallback ahbm; + ahbm.read8 = [&memory](u32 address) -> u8 { + return *memory.GetFCRAMPointer(address - Memory::FCRAM_PADDR); + }; + ahbm.write8 = [&memory](u32 address, u8 value) { + *memory.GetFCRAMPointer(address - Memory::FCRAM_PADDR) = value; + }; + impl->teakra.SetAHBMCallback(ahbm); + impl->teakra.SetAudioCallback([this](std::array sample) { OutputSample(sample); }); +} +DspLle::~DspLle() = default; + +} // namespace AudioCore diff --git a/src/audio_core/lle/lle.h b/src/audio_core/lle/lle.h new file mode 100644 index 000000000..dc3647aa0 --- /dev/null +++ b/src/audio_core/lle/lle.h @@ -0,0 +1,35 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "audio_core/dsp_interface.h" + +namespace AudioCore { + +class DspLle final : public DspInterface { +public: + explicit DspLle(Memory::MemorySystem& memory, bool multithread); + ~DspLle() override; + + u16 RecvData(u32 register_number) override; + bool RecvDataIsReady(u32 register_number) const override; + void SetSemaphore(u16 semaphore_value) override; + std::vector PipeRead(DspPipe pipe_number, u32 length) override; + std::size_t GetPipeReadableSize(DspPipe pipe_number) const override; + void PipeWrite(DspPipe pipe_number, const std::vector& buffer) override; + + std::array& GetDspMemory() override; + + void SetServiceToInterrupt(std::weak_ptr dsp) override; + + void LoadComponent(const std::vector& buffer) override; + void UnloadComponent() override; + +private: + struct Impl; + std::unique_ptr impl; +}; + +} // namespace AudioCore diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 45319a4e1..bb7b0325f 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -155,6 +155,9 @@ void Config::ReadValues() { static_cast(sdl2_config->GetInteger("Layout", "custom_bottom_bottom", 480)); // Audio + Settings::values.enable_dsp_lle = sdl2_config->GetBoolean("Audio", "enable_dsp_lle", false); + Settings::values.enable_dsp_lle_multithread = + sdl2_config->GetBoolean("Audio", "enable_dsp_lle_multithread", false); Settings::values.sink_id = sdl2_config->GetString("Audio", "output_engine", "auto"); Settings::values.enable_audio_stretching = sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index f75af9cc3..b7596d54a 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -169,6 +169,15 @@ custom_bottom_bottom = swap_screen = [Audio] +# Whether or not to enable DSP LLE +# 0 (default): No, 1: Yes +enable_dsp_lle = + +# Whether or not to run DSP LLE on a different thread +# 0 (default): No, 1: Yes +enable_dsp_lle_thread = + + # Which audio output engine to use. # auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available) output_engine = diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 90cabf124..f597a5fa5 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -137,6 +137,9 @@ void Config::ReadValues() { qt_config->endGroup(); qt_config->beginGroup("Audio"); + Settings::values.enable_dsp_lle = ReadSetting("enable_dsp_lle", false).toBool(); + Settings::values.enable_dsp_lle_multithread = + ReadSetting("enable_dsp_lle_multithread", false).toBool(); Settings::values.sink_id = ReadSetting("output_engine", "auto").toString().toStdString(); Settings::values.enable_audio_stretching = ReadSetting("enable_audio_stretching", true).toBool(); @@ -416,6 +419,8 @@ void Config::SaveValues() { qt_config->endGroup(); qt_config->beginGroup("Audio"); + WriteSetting("enable_dsp_lle", Settings::values.enable_dsp_lle, false); + WriteSetting("enable_dsp_lle_multithread", Settings::values.enable_dsp_lle_multithread, false); WriteSetting("output_engine", QString::fromStdString(Settings::values.sink_id), "auto"); WriteSetting("enable_audio_stretching", Settings::values.enable_audio_stretching, true); WriteSetting("output_device", QString::fromStdString(Settings::values.audio_device_id), "auto"); diff --git a/src/citra_qt/configuration/configure_audio.cpp b/src/citra_qt/configuration/configure_audio.cpp index 0e1419017..04211a161 100644 --- a/src/citra_qt/configuration/configure_audio.cpp +++ b/src/citra_qt/configuration/configure_audio.cpp @@ -6,6 +6,7 @@ #include "audio_core/sink.h" #include "audio_core/sink_details.h" #include "citra_qt/configuration/configure_audio.h" +#include "core/core.h" #include "core/settings.h" #include "ui_configure_audio.h" @@ -19,6 +20,11 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) ui->output_sink_combo_box->addItem(id); } + ui->emulation_combo_box->addItem(tr("HLE (fast)")); + ui->emulation_combo_box->addItem(tr("LLE (accurate)")); + ui->emulation_combo_box->addItem(tr("LLE multi-core")); + ui->emulation_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + connect(ui->volume_slider, &QSlider::valueChanged, this, &ConfigureAudio::setVolumeIndicatorText); @@ -41,6 +47,18 @@ void ConfigureAudio::setConfiguration() { ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching); ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum()); setVolumeIndicatorText(ui->volume_slider->sliderPosition()); + + int selection; + if (Settings::values.enable_dsp_lle) { + if (Settings::values.enable_dsp_lle_multithread) { + selection = 2; + } else { + selection = 1; + } + } else { + selection = 0; + } + ui->emulation_combo_box->setCurrentIndex(selection); } void ConfigureAudio::setOutputSinkFromSinkID() { @@ -85,6 +103,8 @@ void ConfigureAudio::applyConfiguration() { .toStdString(); Settings::values.volume = static_cast(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum(); + Settings::values.enable_dsp_lle = ui->emulation_combo_box->currentIndex() != 0; + Settings::values.enable_dsp_lle_multithread = ui->emulation_combo_box->currentIndex() == 2; } void ConfigureAudio::updateAudioDevices(int sink_index) { diff --git a/src/citra_qt/configuration/configure_audio.ui b/src/citra_qt/configuration/configure_audio.ui index b80b10d37..9a3768b2a 100644 --- a/src/citra_qt/configuration/configure_audio.ui +++ b/src/citra_qt/configuration/configure_audio.ui @@ -17,6 +17,23 @@ Audio + + + + 0 + + + + + Emulation: + + + + + + + + diff --git a/src/common/thread.h b/src/common/thread.h index e84db99a0..38532a190 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -79,9 +79,14 @@ public: } } + std::size_t Generation() const { + std::unique_lock lk(mutex); + return generation; + } + private: std::condition_variable condvar; - std::mutex mutex; + mutable std::mutex mutex; std::size_t count; std::size_t waiting = 0; std::size_t generation = 0; // Incremented once each time the barrier is used diff --git a/src/core/core.cpp b/src/core/core.cpp index 95fa69f43..a6af49b41 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -6,6 +6,7 @@ #include #include "audio_core/dsp_interface.h" #include "audio_core/hle/hle.h" +#include "audio_core/lle/lle.h" #include "common/logging/log.h" #include "core/arm/arm_interface.h" #ifdef ARCHITECTURE_x86_64 @@ -188,7 +189,13 @@ System::ResultStatus System::Init(EmuWindow& emu_window, u32 system_mode) { cpu_core = std::make_unique(*this, USER32MODE); } - dsp_core = std::make_unique(*memory); + if (Settings::values.enable_dsp_lle) { + dsp_core = std::make_unique(*memory, + Settings::values.enable_dsp_lle_multithread); + } else { + dsp_core = std::make_unique(*memory); + } + dsp_core->SetSink(Settings::values.sink_id, Settings::values.audio_device_id); dsp_core->EnableStretching(Settings::values.enable_audio_stretching); diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp index d64b7def7..a63cf4c33 100644 --- a/src/core/hle/kernel/wait_object.cpp +++ b/src/core/hle/kernel/wait_object.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include #include "common/assert.h" #include "common/logging/log.h" #include "core/hle/kernel/errors.h" @@ -86,10 +87,17 @@ void WaitObject::WakeupAllWaitingThreads() { thread->ResumeFromWait(); } + + if (hle_notifier) + hle_notifier(); } const std::vector>& WaitObject::GetWaitingThreads() const { return waiting_threads; } +void WaitObject::SetHLENotifier(std::function callback) { + hle_notifier = std::move(callback); +} + } // namespace Kernel diff --git a/src/core/hle/kernel/wait_object.h b/src/core/hle/kernel/wait_object.h index 01fc40a0d..693ae2186 100644 --- a/src/core/hle/kernel/wait_object.h +++ b/src/core/hle/kernel/wait_object.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include "common/common_types.h" @@ -52,9 +53,15 @@ public: /// Get a const reference to the waiting threads list for debug use const std::vector>& GetWaitingThreads() const; + /// Sets a callback which is called when the object becomes available + void SetHLENotifier(std::function callback); + private: /// Threads waiting for this object to become available std::vector> waiting_threads; + + /// Function to call when this object becomes available + std::function hle_notifier; }; // Specialization of DynamicObjectCast for WaitObjects diff --git a/src/core/hle/service/dsp/dsp_dsp.cpp b/src/core/hle/service/dsp/dsp_dsp.cpp index b15e670d3..22eba5ea4 100644 --- a/src/core/hle/service/dsp/dsp_dsp.cpp +++ b/src/core/hle/service/dsp/dsp_dsp.cpp @@ -4,7 +4,6 @@ #include "audio_core/audio_types.h" #include "common/assert.h" -#include "common/hash.h" #include "common/logging/log.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" @@ -24,26 +23,9 @@ void DSP_DSP::RecvData(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x01, 1, 0); const u32 register_number = rp.Pop(); - ASSERT_MSG(register_number == 0, "Unknown register_number {}", register_number); - - // Application reads this after requesting DSP shutdown, to verify the DSP has indeed shutdown - // or slept. - IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); - - switch (Core::DSP().GetDspState()) { - case AudioCore::DspState::On: - rb.Push(0); - break; - case AudioCore::DspState::Off: - case AudioCore::DspState::Sleeping: - rb.Push(1); - break; - default: - UNREACHABLE(); - break; - } + rb.Push(system.DSP().RecvData(register_number)); LOG_DEBUG(Service_DSP, "register_number={}", register_number); } @@ -52,11 +34,9 @@ void DSP_DSP::RecvDataIsReady(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x02, 1, 0); const u32 register_number = rp.Pop(); - ASSERT_MSG(register_number == 0, "Unknown register_number {}", register_number); - IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); - rb.Push(true); /// 0 = not ready, 1 = ready to read + rb.Push(system.DSP().RecvDataIsReady(register_number)); LOG_DEBUG(Service_DSP, "register_number={}", register_number); } @@ -65,10 +45,12 @@ void DSP_DSP::SetSemaphore(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x07, 1, 0); const u16 semaphore_value = rp.Pop(); + system.DSP().SetSemaphore(semaphore_value); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_DSP, "(STUBBED) called, semaphore_value={:04X}", semaphore_value); + LOG_INFO(Service_DSP, "called, semaphore_value={:04X}", semaphore_value); } void DSP_DSP::ConvertProcessAddressFromDspDram(Kernel::HLERequestContext& ctx) { @@ -111,7 +93,7 @@ void DSP_DSP::WriteProcessPipe(Kernel::HLERequestContext& ctx) { break; } - Core::DSP().PipeWrite(pipe, buffer); + system.DSP().PipeWrite(pipe, buffer); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); @@ -127,11 +109,11 @@ void DSP_DSP::ReadPipe(Kernel::HLERequestContext& ctx) { const u16 size = rp.Pop(); const DspPipe pipe = static_cast(channel); - const u16 pipe_readable_size = static_cast(Core::DSP().GetPipeReadableSize(pipe)); + const u16 pipe_readable_size = static_cast(system.DSP().GetPipeReadableSize(pipe)); std::vector pipe_buffer; if (pipe_readable_size >= size) - pipe_buffer = Core::DSP().PipeRead(pipe, size); + pipe_buffer = system.DSP().PipeRead(pipe, size); else UNREACHABLE(); // No more data is in pipe. Hardware hangs in this case; Should never happen. @@ -149,7 +131,7 @@ void DSP_DSP::GetPipeReadableSize(Kernel::HLERequestContext& ctx) { const u32 peer = rp.Pop(); const DspPipe pipe = static_cast(channel); - const u16 pipe_readable_size = static_cast(Core::DSP().GetPipeReadableSize(pipe)); + const u16 pipe_readable_size = static_cast(system.DSP().GetPipeReadableSize(pipe)); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); @@ -166,11 +148,11 @@ void DSP_DSP::ReadPipeIfPossible(Kernel::HLERequestContext& ctx) { const u16 size = rp.Pop(); const DspPipe pipe = static_cast(channel); - const u16 pipe_readable_size = static_cast(Core::DSP().GetPipeReadableSize(pipe)); + const u16 pipe_readable_size = static_cast(system.DSP().GetPipeReadableSize(pipe)); std::vector pipe_buffer; if (pipe_readable_size >= size) - pipe_buffer = Core::DSP().PipeRead(pipe, size); + pipe_buffer = system.DSP().PipeRead(pipe, size); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); rb.Push(RESULT_SUCCESS); @@ -190,23 +172,27 @@ void DSP_DSP::LoadComponent(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); rb.Push(RESULT_SUCCESS); - rb.Push(true); /// Pretend that we actually loaded the DSP firmware + rb.Push(true); rb.PushMappedBuffer(buffer); - // TODO(bunnei): Implement real DSP firmware loading - std::vector component_data(size); buffer.Read(component_data.data(), 0, size); - LOG_INFO(Service_DSP, "Firmware hash: {:#018x}", - Common::ComputeHash64(component_data.data(), component_data.size())); - // Some versions of the firmware have the location of DSP structures listed here. - if (size > 0x37C) { - LOG_INFO(Service_DSP, "Structures hash: {:#018x}", - Common::ComputeHash64(component_data.data() + 0x340, 60)); - } - LOG_WARNING(Service_DSP, "(STUBBED) called size=0x{:X}, prog_mask=0x{:08X}, data_mask=0x{:08X}", - size, prog_mask, data_mask); + system.DSP().LoadComponent(component_data); + + LOG_INFO(Service_DSP, "called size=0x{:X}, prog_mask=0x{:08X}, data_mask=0x{:08X}", size, + prog_mask, data_mask); +} + +void DSP_DSP::UnloadComponent(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x12, 0, 0); + + system.DSP().UnloadComponent(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + + LOG_INFO(Service_DSP, "(STUBBED)"); } void DSP_DSP::FlushDataCache(Kernel::HLERequestContext& ctx) { @@ -282,12 +268,12 @@ void DSP_DSP::GetSemaphoreEventHandle(Kernel::HLERequestContext& ctx) { void DSP_DSP::SetSemaphoreMask(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x17, 1, 0); - const u32 mask = rp.Pop(); + preset_semaphore = rp.Pop(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_DSP, "(STUBBED) called mask=0x{:08X}", mask); + LOG_WARNING(Service_DSP, "(STUBBED) called mask=0x{:04X}", preset_semaphore); } void DSP_DSP::GetHeadphoneStatus(Kernel::HLERequestContext& ctx) { @@ -350,7 +336,8 @@ bool DSP_DSP::HasTooManyEventsRegistered() const { return number >= max_number_of_interrupt_events; } -DSP_DSP::DSP_DSP(Core::System& system) : ServiceFramework("dsp::DSP", DefaultMaxSessions) { +DSP_DSP::DSP_DSP(Core::System& system) + : ServiceFramework("dsp::DSP", DefaultMaxSessions), system(system) { static const FunctionInfo functions[] = { // clang-format off {0x00010040, &DSP_DSP::RecvData, "RecvData"}, @@ -370,7 +357,7 @@ DSP_DSP::DSP_DSP(Core::System& system) : ServiceFramework("dsp::DSP", DefaultMax {0x000F0080, &DSP_DSP::GetPipeReadableSize, "GetPipeReadableSize"}, {0x001000C0, &DSP_DSP::ReadPipeIfPossible, "ReadPipeIfPossible"}, {0x001100C2, &DSP_DSP::LoadComponent, "LoadComponent"}, - {0x00120000, nullptr, "UnloadComponent"}, + {0x00120000, &DSP_DSP::UnloadComponent, "UnloadComponent"}, {0x00130082, &DSP_DSP::FlushDataCache, "FlushDataCache"}, {0x00140082, &DSP_DSP::InvalidateDataCache, "InvalidateDCache"}, {0x00150082, &DSP_DSP::RegisterInterruptEvents, "RegisterInterruptEvents"}, @@ -393,6 +380,9 @@ DSP_DSP::DSP_DSP(Core::System& system) : ServiceFramework("dsp::DSP", DefaultMax semaphore_event = system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "DSP_DSP::semaphore_event"); + + semaphore_event->SetHLENotifier( + [this]() { this->system.DSP().SetSemaphore(preset_semaphore); }); } DSP_DSP::~DSP_DSP() { diff --git a/src/core/hle/service/dsp/dsp_dsp.h b/src/core/hle/service/dsp/dsp_dsp.h index 7226aeeac..5bf00334b 100644 --- a/src/core/hle/service/dsp/dsp_dsp.h +++ b/src/core/hle/service/dsp/dsp_dsp.h @@ -153,6 +153,15 @@ private: */ void LoadComponent(Kernel::HLERequestContext& ctx); + /** + * DSP_DSP::UnloadComponent service function + * Inputs: + * 0 : Header Code[0x00120000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void UnloadComponent(Kernel::HLERequestContext& ctx); + /** * DSP_DSP::FlushDataCache service function * @@ -245,7 +254,10 @@ private: /// Checks if we are trying to register more than 6 events bool HasTooManyEventsRegistered() const; + Core::System& system; + Kernel::SharedPtr semaphore_event; + u16 preset_semaphore = 0; Kernel::SharedPtr interrupt_zero = nullptr; /// Currently unknown purpose Kernel::SharedPtr interrupt_one = nullptr; /// Currently unknown purpose diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 8bdf5689b..2141d57bd 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -79,6 +79,8 @@ void LogSettings() { LogSetting("Layout_Factor3d", Settings::values.factor_3d); LogSetting("Layout_LayoutOption", static_cast(Settings::values.layout_option)); LogSetting("Layout_SwapScreen", Settings::values.swap_screen); + LogSetting("Audio_EnableDspLle", Settings::values.enable_dsp_lle); + LogSetting("Audio_EnableDspLleMultithread", Settings::values.enable_dsp_lle_multithread); LogSetting("Audio_OutputEngine", Settings::values.sink_id); LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching); LogSetting("Audio_OutputDevice", Settings::values.audio_device_id); diff --git a/src/core/settings.h b/src/core/settings.h index 4a6d8aee7..e3bf77a40 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -151,6 +151,8 @@ struct Values { u8 factor_3d; // Audio + bool enable_dsp_lle; + bool enable_dsp_lle_multithread; std::string sink_id; bool enable_audio_stretching; std::string audio_device_id;