Merge pull request #4490 from wwylele/teakra-new
audio: implement DSP LLE
This commit is contained in:
		
							
								
								
									
										15
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
|   | ||||
							
								
								
									
										3
									
								
								externals/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								externals/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
|   | ||||
							
								
								
									
										1
									
								
								externals/teakra
									
									
									
									
										vendored
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								externals/teakra
									
									
									
									
										vendored
									
									
										Submodule
									
								
							 Submodule externals/teakra added at e6ea0eae65
									
								
							| @@ -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() | ||||
|  | ||||
|   | ||||
| @@ -43,6 +43,13 @@ void DspInterface::OutputFrame(StereoFrame16& frame) { | ||||
|     fifo.Push(frame.data(), frame.size()); | ||||
| } | ||||
|  | ||||
| void DspInterface::OutputSample(std::array<s16, 2> 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) { | ||||
|   | ||||
| @@ -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<Service::DSP::DSP_DSP> dsp) = 0; | ||||
|  | ||||
|     /// Loads the DSP program | ||||
|     virtual void LoadComponent(const std::vector<u8>& 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<s16, 2> sample); | ||||
|  | ||||
| private: | ||||
|     void FlushResidualStretcherAudio(); | ||||
|   | ||||
| @@ -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<u8> PipeRead(DspPipe pipe_number, u32 length); | ||||
|     std::size_t GetPipeReadableSize(DspPipe pipe_number) const; | ||||
|     void PipeWrite(DspPipe pipe_number, const std::vector<u8>& 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<u8> DspHle::Impl::PipeRead(DspPipe pipe_number, u32 length) { | ||||
|     const std::size_t pipe_index = static_cast<std::size_t>(pipe_number); | ||||
|  | ||||
| @@ -342,8 +368,16 @@ void DspHle::Impl::AudioTickCallback(s64 cycles_late) { | ||||
| DspHle::DspHle(Memory::MemorySystem& memory) : impl(std::make_unique<Impl>(*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<u8> DspHle::PipeRead(DspPipe pipe_number, u32 length) { | ||||
| @@ -366,4 +400,19 @@ void DspHle::SetServiceToInterrupt(std::weak_ptr<DSP_DSP> dsp) { | ||||
|     impl->SetServiceToInterrupt(std::move(dsp)); | ||||
| } | ||||
|  | ||||
| void DspHle::LoadComponent(const std::vector<u8>& 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 | ||||
|   | ||||
| @@ -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<u8> PipeRead(DspPipe pipe_number, u32 length) override; | ||||
|     std::size_t GetPipeReadableSize(DspPipe pipe_number) const override; | ||||
|     void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) override; | ||||
| @@ -34,6 +35,9 @@ public: | ||||
|  | ||||
|     void SetServiceToInterrupt(std::weak_ptr<Service::DSP::DSP_DSP> dsp) override; | ||||
|  | ||||
|     void LoadComponent(const std::vector<u8>& buffer) override; | ||||
|     void UnloadComponent() override; | ||||
|  | ||||
| private: | ||||
|     struct Impl; | ||||
|     friend struct Impl; | ||||
|   | ||||
							
								
								
									
										490
									
								
								src/audio_core/lle/lle.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										490
									
								
								src/audio_core/lle/lle.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,490 @@ | ||||
| // Copyright 2018 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <array> | ||||
| #include <atomic> | ||||
| #include <thread> | ||||
| #include <teakra/teakra.h> | ||||
| #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<u8>& raw); | ||||
|  | ||||
|     struct Header { | ||||
|         std::array<u8, 0x100> signature; | ||||
|         std::array<u8, 0x4> 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<u8, 0x20> sha256; | ||||
|         }; | ||||
|         std::array<Segment, 10> segments; | ||||
|     }; | ||||
|     static_assert(sizeof(Header) == 0x300); | ||||
|  | ||||
|     struct Segment { | ||||
|         std::vector<u8> data; | ||||
|         SegmentType memory_type; | ||||
|         u32 target; | ||||
|     }; | ||||
|  | ||||
|     std::vector<Segment> segments; | ||||
|     bool recv_data_on_start; | ||||
| }; | ||||
|  | ||||
| Dsp1::Dsp1(const std::vector<u8>& 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<u8>(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<u8>(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<u64>(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<bool> loaded = false; | ||||
|  | ||||
|     const bool multithread; | ||||
|     std::thread teakra_thread; | ||||
|     Common::Barrier teakra_slice_barrier{2}; | ||||
|     std::atomic<bool> 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<u8>& data) { | ||||
|         PipeStatus pipe_status = GetPipeStatus(pipe_index, PipeDirection::CPUtoDSP); | ||||
|         bool need_update = false; | ||||
|         const u8* buffer_ptr = data.data(); | ||||
|         u16 bsize = static_cast<u16>(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<u16>(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<u8> ReadPipe(u8 pipe_index, u16 bsize) { | ||||
|         PipeStatus pipe_status = GetPipeStatus(pipe_index, PipeDirection::DSPtoCPU); | ||||
|         bool need_update = false; | ||||
|         std::vector<u8> 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<u16>(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<u8>& 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<u8>(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<u8> DspLle::PipeRead(DspPipe pipe_number, u32 length) { | ||||
|     return impl->ReadPipe(static_cast<u8>(pipe_number), static_cast<u16>(length)); | ||||
| } | ||||
|  | ||||
| std::size_t DspLle::GetPipeReadableSize(DspPipe pipe_number) const { | ||||
|     return impl->GetPipeReadableSize(static_cast<u8>(pipe_number)); | ||||
| } | ||||
|  | ||||
| void DspLle::PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) { | ||||
|     impl->WritePipe(static_cast<u8>(pipe_number), buffer); | ||||
| } | ||||
|  | ||||
| std::array<u8, Memory::DSP_RAM_SIZE>& DspLle::GetDspMemory() { | ||||
|     return impl->teakra.GetDspMemory(); | ||||
| } | ||||
|  | ||||
| void DspLle::SetServiceToInterrupt(std::weak_ptr<Service::DSP::DSP_DSP> 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<DspPipe>(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<DspPipe>(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<u16>(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<DspPipe>(pipe)); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     impl->teakra.SetRecvDataHandler(2, [ProcessPipeEvent]() { ProcessPipeEvent(true); }); | ||||
|     impl->teakra.SetSemaphoreHandler([ProcessPipeEvent]() { ProcessPipeEvent(false); }); | ||||
| } | ||||
|  | ||||
| void DspLle::LoadComponent(const std::vector<u8>& buffer) { | ||||
|     impl->LoadComponent(buffer); | ||||
| } | ||||
|  | ||||
| void DspLle::UnloadComponent() { | ||||
|     impl->UnloadComponent(); | ||||
| } | ||||
|  | ||||
| DspLle::DspLle(Memory::MemorySystem& memory, bool multithread) | ||||
|     : impl(std::make_unique<Impl>(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<s16, 2> sample) { OutputSample(sample); }); | ||||
| } | ||||
| DspLle::~DspLle() = default; | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										35
									
								
								src/audio_core/lle/lle.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/audio_core/lle/lle.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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<u8> PipeRead(DspPipe pipe_number, u32 length) override; | ||||
|     std::size_t GetPipeReadableSize(DspPipe pipe_number) const override; | ||||
|     void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) override; | ||||
|  | ||||
|     std::array<u8, Memory::DSP_RAM_SIZE>& GetDspMemory() override; | ||||
|  | ||||
|     void SetServiceToInterrupt(std::weak_ptr<Service::DSP::DSP_DSP> dsp) override; | ||||
|  | ||||
|     void LoadComponent(const std::vector<u8>& buffer) override; | ||||
|     void UnloadComponent() override; | ||||
|  | ||||
| private: | ||||
|     struct Impl; | ||||
|     std::unique_ptr<Impl> impl; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -155,6 +155,9 @@ void Config::ReadValues() { | ||||
|         static_cast<u16>(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); | ||||
|   | ||||
| @@ -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 = | ||||
|   | ||||
| @@ -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"); | ||||
|   | ||||
| @@ -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<float>(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) { | ||||
|   | ||||
| @@ -17,6 +17,23 @@ | ||||
|       <string>Audio</string> | ||||
|      </property> | ||||
|      <layout class="QVBoxLayout"> | ||||
|       <item> | ||||
|        <layout class="QHBoxLayout" name="horizontalLayout_emulation"> | ||||
|         <property name="bottomMargin"> | ||||
|          <number>0</number> | ||||
|         </property> | ||||
|         <item> | ||||
|          <widget class="QLabel" name="label_emulation"> | ||||
|           <property name="text"> | ||||
|            <string>Emulation:</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item> | ||||
|          <widget class="QComboBox" name="emulation_combo_box"/> | ||||
|         </item> | ||||
|        </layout> | ||||
|       </item> | ||||
|       <item> | ||||
|        <layout class="QHBoxLayout"> | ||||
|         <item> | ||||
|   | ||||
| @@ -79,9 +79,14 @@ public: | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     std::size_t Generation() const { | ||||
|         std::unique_lock<std::mutex> 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 | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
| #include <utility> | ||||
| #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<ARM_DynCom>(*this, USER32MODE); | ||||
|     } | ||||
|  | ||||
|     dsp_core = std::make_unique<AudioCore::DspHle>(*memory); | ||||
|     if (Settings::values.enable_dsp_lle) { | ||||
|         dsp_core = std::make_unique<AudioCore::DspLle>(*memory, | ||||
|                                                        Settings::values.enable_dsp_lle_multithread); | ||||
|     } else { | ||||
|         dsp_core = std::make_unique<AudioCore::DspHle>(*memory); | ||||
|     } | ||||
|  | ||||
|     dsp_core->SetSink(Settings::values.sink_id, Settings::values.audio_device_id); | ||||
|     dsp_core->EnableStretching(Settings::values.enable_audio_stretching); | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <utility> | ||||
| #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<SharedPtr<Thread>>& WaitObject::GetWaitingThreads() const { | ||||
|     return waiting_threads; | ||||
| } | ||||
|  | ||||
| void WaitObject::SetHLENotifier(std::function<void()> callback) { | ||||
|     hle_notifier = std::move(callback); | ||||
| } | ||||
|  | ||||
| } // namespace Kernel | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <functional> | ||||
| #include <vector> | ||||
| #include <boost/smart_ptr/intrusive_ptr.hpp> | ||||
| #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<SharedPtr<Thread>>& GetWaitingThreads() const; | ||||
|  | ||||
|     /// Sets a callback which is called when the object becomes available | ||||
|     void SetHLENotifier(std::function<void()> callback); | ||||
|  | ||||
| private: | ||||
|     /// Threads waiting for this object to become available | ||||
|     std::vector<SharedPtr<Thread>> waiting_threads; | ||||
|  | ||||
|     /// Function to call when this object becomes available | ||||
|     std::function<void()> hle_notifier; | ||||
| }; | ||||
|  | ||||
| // Specialization of DynamicObjectCast for WaitObjects | ||||
|   | ||||
| @@ -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<u32>(); | ||||
|  | ||||
|     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<u32>(0); | ||||
|         break; | ||||
|     case AudioCore::DspState::Off: | ||||
|     case AudioCore::DspState::Sleeping: | ||||
|         rb.Push<u32>(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<u32>(); | ||||
|  | ||||
|     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<u16>(); | ||||
|  | ||||
|     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<u16>(); | ||||
|  | ||||
|     const DspPipe pipe = static_cast<DspPipe>(channel); | ||||
|     const u16 pipe_readable_size = static_cast<u16>(Core::DSP().GetPipeReadableSize(pipe)); | ||||
|     const u16 pipe_readable_size = static_cast<u16>(system.DSP().GetPipeReadableSize(pipe)); | ||||
|  | ||||
|     std::vector<u8> 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<u32>(); | ||||
|  | ||||
|     const DspPipe pipe = static_cast<DspPipe>(channel); | ||||
|     const u16 pipe_readable_size = static_cast<u16>(Core::DSP().GetPipeReadableSize(pipe)); | ||||
|     const u16 pipe_readable_size = static_cast<u16>(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<u16>(); | ||||
|  | ||||
|     const DspPipe pipe = static_cast<DspPipe>(channel); | ||||
|     const u16 pipe_readable_size = static_cast<u16>(Core::DSP().GetPipeReadableSize(pipe)); | ||||
|     const u16 pipe_readable_size = static_cast<u16>(system.DSP().GetPipeReadableSize(pipe)); | ||||
|  | ||||
|     std::vector<u8> 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<u8> 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<u32>(); | ||||
|     preset_semaphore = rp.Pop<u16>(); | ||||
|  | ||||
|     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() { | ||||
|   | ||||
| @@ -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<Kernel::Event> semaphore_event; | ||||
|     u16 preset_semaphore = 0; | ||||
|  | ||||
|     Kernel::SharedPtr<Kernel::Event> interrupt_zero = nullptr; /// Currently unknown purpose | ||||
|     Kernel::SharedPtr<Kernel::Event> interrupt_one = nullptr;  /// Currently unknown purpose | ||||
|   | ||||
| @@ -79,6 +79,8 @@ void LogSettings() { | ||||
|     LogSetting("Layout_Factor3d", Settings::values.factor_3d); | ||||
|     LogSetting("Layout_LayoutOption", static_cast<int>(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); | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Ben
					Ben