mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-15 06:20:06 +00:00
Merge pull request #4490 from wwylele/teakra-new
audio: implement DSP LLE
This commit is contained in:
commit
4f23d5d69e
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -46,3 +46,6 @@
|
|||||||
[submodule "cpp-jwt"]
|
[submodule "cpp-jwt"]
|
||||||
path = externals/cpp-jwt
|
path = externals/cpp-jwt
|
||||||
url = https://github.com/arun11299/cpp-jwt.git
|
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
|
# The SoundTouch target doesn't export the necessary include paths as properties by default
|
||||||
target_include_directories(SoundTouch INTERFACE ./soundtouch/include)
|
target_include_directories(SoundTouch INTERFACE ./soundtouch/include)
|
||||||
|
|
||||||
|
# Teakra
|
||||||
|
add_subdirectory(teakra)
|
||||||
|
|
||||||
# Xbyak
|
# Xbyak
|
||||||
if (ARCHITECTURE_x86_64)
|
if (ARCHITECTURE_x86_64)
|
||||||
# Defined before "dynarmic" above
|
# Defined before "dynarmic" above
|
||||||
|
1
externals/teakra
vendored
Submodule
1
externals/teakra
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit e6ea0eae656c022d7878ffabc4e016b3e6f0c536
|
@ -14,6 +14,8 @@ add_library(audio_core STATIC
|
|||||||
hle/shared_memory.h
|
hle/shared_memory.h
|
||||||
hle/source.cpp
|
hle/source.cpp
|
||||||
hle/source.h
|
hle/source.h
|
||||||
|
lle/lle.cpp
|
||||||
|
lle/lle.h
|
||||||
interpolate.cpp
|
interpolate.cpp
|
||||||
interpolate.h
|
interpolate.h
|
||||||
null_sink.h
|
null_sink.h
|
||||||
@ -30,7 +32,7 @@ add_library(audio_core STATIC
|
|||||||
create_target_directory_groups(audio_core)
|
create_target_directory_groups(audio_core)
|
||||||
|
|
||||||
target_link_libraries(audio_core PUBLIC common 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)
|
if(SDL2_FOUND)
|
||||||
target_link_libraries(audio_core PRIVATE SDL2)
|
target_link_libraries(audio_core PRIVATE SDL2)
|
||||||
@ -41,4 +43,3 @@ if(ENABLE_CUBEB)
|
|||||||
target_link_libraries(audio_core PRIVATE cubeb)
|
target_link_libraries(audio_core PRIVATE cubeb)
|
||||||
add_definitions(-DHAVE_CUBEB=1)
|
add_definitions(-DHAVE_CUBEB=1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -43,6 +43,13 @@ void DspInterface::OutputFrame(StereoFrame16& frame) {
|
|||||||
fifo.Push(frame.data(), frame.size());
|
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) {
|
void DspInterface::OutputCallback(s16* buffer, std::size_t num_frames) {
|
||||||
std::size_t frames_written;
|
std::size_t frames_written;
|
||||||
if (perform_time_stretching) {
|
if (perform_time_stretching) {
|
||||||
|
@ -32,8 +32,26 @@ public:
|
|||||||
DspInterface& operator=(const DspInterface&) = delete;
|
DspInterface& operator=(const DspInterface&) = delete;
|
||||||
DspInterface& operator=(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`.
|
* 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
|
/// Sets the dsp class that we trigger interrupts for
|
||||||
virtual void SetServiceToInterrupt(std::weak_ptr<Service::DSP::DSP_DSP> dsp) = 0;
|
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.
|
/// Select the sink to use based on sink id.
|
||||||
void SetSink(const std::string& sink_id, const std::string& audio_device);
|
void SetSink(const std::string& sink_id, const std::string& audio_device);
|
||||||
/// Get the current sink
|
/// Get the current sink
|
||||||
@ -79,6 +103,7 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
void OutputFrame(StereoFrame16& frame);
|
void OutputFrame(StereoFrame16& frame);
|
||||||
|
void OutputSample(std::array<s16, 2> sample);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void FlushResidualStretcherAudio();
|
void FlushResidualStretcherAudio();
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "audio_core/sink.h"
|
#include "audio_core/sink.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "common/hash.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
@ -29,6 +30,8 @@ public:
|
|||||||
|
|
||||||
DspState GetDspState() const;
|
DspState GetDspState() const;
|
||||||
|
|
||||||
|
u16 RecvData(u32 register_number);
|
||||||
|
bool RecvDataIsReady(u32 register_number) const;
|
||||||
std::vector<u8> PipeRead(DspPipe pipe_number, u32 length);
|
std::vector<u8> PipeRead(DspPipe pipe_number, u32 length);
|
||||||
std::size_t GetPipeReadableSize(DspPipe pipe_number) const;
|
std::size_t GetPipeReadableSize(DspPipe pipe_number) const;
|
||||||
void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer);
|
void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer);
|
||||||
@ -93,6 +96,29 @@ DspState DspHle::Impl::GetDspState() const {
|
|||||||
return dsp_state;
|
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) {
|
std::vector<u8> DspHle::Impl::PipeRead(DspPipe pipe_number, u32 length) {
|
||||||
const std::size_t pipe_index = static_cast<std::size_t>(pipe_number);
|
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(Memory::MemorySystem& memory) : impl(std::make_unique<Impl>(*this, memory)) {}
|
||||||
DspHle::~DspHle() = default;
|
DspHle::~DspHle() = default;
|
||||||
|
|
||||||
DspState DspHle::GetDspState() const {
|
u16 DspHle::RecvData(u32 register_number) {
|
||||||
return impl->GetDspState();
|
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) {
|
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));
|
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
|
} // namespace AudioCore
|
||||||
|
@ -24,8 +24,9 @@ public:
|
|||||||
explicit DspHle(Memory::MemorySystem& memory);
|
explicit DspHle(Memory::MemorySystem& memory);
|
||||||
~DspHle();
|
~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::vector<u8> PipeRead(DspPipe pipe_number, u32 length) override;
|
||||||
std::size_t GetPipeReadableSize(DspPipe pipe_number) const override;
|
std::size_t GetPipeReadableSize(DspPipe pipe_number) const override;
|
||||||
void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) 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 SetServiceToInterrupt(std::weak_ptr<Service::DSP::DSP_DSP> dsp) override;
|
||||||
|
|
||||||
|
void LoadComponent(const std::vector<u8>& buffer) override;
|
||||||
|
void UnloadComponent() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Impl;
|
struct Impl;
|
||||||
friend 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));
|
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_bottom", 480));
|
||||||
|
|
||||||
// Audio
|
// 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.sink_id = sdl2_config->GetString("Audio", "output_engine", "auto");
|
||||||
Settings::values.enable_audio_stretching =
|
Settings::values.enable_audio_stretching =
|
||||||
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
|
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
|
||||||
|
@ -169,6 +169,15 @@ custom_bottom_bottom =
|
|||||||
swap_screen =
|
swap_screen =
|
||||||
|
|
||||||
[Audio]
|
[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.
|
# Which audio output engine to use.
|
||||||
# auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available)
|
# auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available)
|
||||||
output_engine =
|
output_engine =
|
||||||
|
@ -137,6 +137,9 @@ void Config::ReadValues() {
|
|||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("Audio");
|
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.sink_id = ReadSetting("output_engine", "auto").toString().toStdString();
|
||||||
Settings::values.enable_audio_stretching =
|
Settings::values.enable_audio_stretching =
|
||||||
ReadSetting("enable_audio_stretching", true).toBool();
|
ReadSetting("enable_audio_stretching", true).toBool();
|
||||||
@ -416,6 +419,8 @@ void Config::SaveValues() {
|
|||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("Audio");
|
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("output_engine", QString::fromStdString(Settings::values.sink_id), "auto");
|
||||||
WriteSetting("enable_audio_stretching", Settings::values.enable_audio_stretching, true);
|
WriteSetting("enable_audio_stretching", Settings::values.enable_audio_stretching, true);
|
||||||
WriteSetting("output_device", QString::fromStdString(Settings::values.audio_device_id), "auto");
|
WriteSetting("output_device", QString::fromStdString(Settings::values.audio_device_id), "auto");
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include "audio_core/sink.h"
|
#include "audio_core/sink.h"
|
||||||
#include "audio_core/sink_details.h"
|
#include "audio_core/sink_details.h"
|
||||||
#include "citra_qt/configuration/configure_audio.h"
|
#include "citra_qt/configuration/configure_audio.h"
|
||||||
|
#include "core/core.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "ui_configure_audio.h"
|
#include "ui_configure_audio.h"
|
||||||
|
|
||||||
@ -19,6 +20,11 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
|
|||||||
ui->output_sink_combo_box->addItem(id);
|
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,
|
connect(ui->volume_slider, &QSlider::valueChanged, this,
|
||||||
&ConfigureAudio::setVolumeIndicatorText);
|
&ConfigureAudio::setVolumeIndicatorText);
|
||||||
|
|
||||||
@ -41,6 +47,18 @@ void ConfigureAudio::setConfiguration() {
|
|||||||
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
|
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
|
||||||
ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum());
|
ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum());
|
||||||
setVolumeIndicatorText(ui->volume_slider->sliderPosition());
|
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() {
|
void ConfigureAudio::setOutputSinkFromSinkID() {
|
||||||
@ -85,6 +103,8 @@ void ConfigureAudio::applyConfiguration() {
|
|||||||
.toStdString();
|
.toStdString();
|
||||||
Settings::values.volume =
|
Settings::values.volume =
|
||||||
static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum();
|
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) {
|
void ConfigureAudio::updateAudioDevices(int sink_index) {
|
||||||
|
@ -17,6 +17,23 @@
|
|||||||
<string>Audio</string>
|
<string>Audio</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout">
|
<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>
|
<item>
|
||||||
<layout class="QHBoxLayout">
|
<layout class="QHBoxLayout">
|
||||||
<item>
|
<item>
|
||||||
|
@ -79,9 +79,14 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::size_t Generation() const {
|
||||||
|
std::unique_lock<std::mutex> lk(mutex);
|
||||||
|
return generation;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::condition_variable condvar;
|
std::condition_variable condvar;
|
||||||
std::mutex mutex;
|
mutable std::mutex mutex;
|
||||||
std::size_t count;
|
std::size_t count;
|
||||||
std::size_t waiting = 0;
|
std::size_t waiting = 0;
|
||||||
std::size_t generation = 0; // Incremented once each time the barrier is used
|
std::size_t generation = 0; // Incremented once each time the barrier is used
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include "audio_core/dsp_interface.h"
|
#include "audio_core/dsp_interface.h"
|
||||||
#include "audio_core/hle/hle.h"
|
#include "audio_core/hle/hle.h"
|
||||||
|
#include "audio_core/lle/lle.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
#ifdef ARCHITECTURE_x86_64
|
#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);
|
cpu_core = std::make_unique<ARM_DynCom>(*this, USER32MODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = std::make_unique<AudioCore::DspHle>(*memory);
|
||||||
|
}
|
||||||
|
|
||||||
dsp_core->SetSink(Settings::values.sink_id, Settings::values.audio_device_id);
|
dsp_core->SetSink(Settings::values.sink_id, Settings::values.audio_device_id);
|
||||||
dsp_core->EnableStretching(Settings::values.enable_audio_stretching);
|
dsp_core->EnableStretching(Settings::values.enable_audio_stretching);
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <utility>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/hle/kernel/errors.h"
|
#include "core/hle/kernel/errors.h"
|
||||||
@ -86,10 +87,17 @@ void WaitObject::WakeupAllWaitingThreads() {
|
|||||||
|
|
||||||
thread->ResumeFromWait();
|
thread->ResumeFromWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hle_notifier)
|
||||||
|
hle_notifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<SharedPtr<Thread>>& WaitObject::GetWaitingThreads() const {
|
const std::vector<SharedPtr<Thread>>& WaitObject::GetWaitingThreads() const {
|
||||||
return waiting_threads;
|
return waiting_threads;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WaitObject::SetHLENotifier(std::function<void()> callback) {
|
||||||
|
hle_notifier = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <boost/smart_ptr/intrusive_ptr.hpp>
|
#include <boost/smart_ptr/intrusive_ptr.hpp>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
@ -52,9 +53,15 @@ public:
|
|||||||
/// Get a const reference to the waiting threads list for debug use
|
/// Get a const reference to the waiting threads list for debug use
|
||||||
const std::vector<SharedPtr<Thread>>& GetWaitingThreads() const;
|
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:
|
private:
|
||||||
/// Threads waiting for this object to become available
|
/// Threads waiting for this object to become available
|
||||||
std::vector<SharedPtr<Thread>> waiting_threads;
|
std::vector<SharedPtr<Thread>> waiting_threads;
|
||||||
|
|
||||||
|
/// Function to call when this object becomes available
|
||||||
|
std::function<void()> hle_notifier;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Specialization of DynamicObjectCast for WaitObjects
|
// Specialization of DynamicObjectCast for WaitObjects
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
#include "audio_core/audio_types.h"
|
#include "audio_core/audio_types.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/hash.h"
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/hle/ipc_helpers.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);
|
IPC::RequestParser rp(ctx, 0x01, 1, 0);
|
||||||
const u32 register_number = rp.Pop<u32>();
|
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);
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
rb.Push(system.DSP().RecvData(register_number));
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DEBUG(Service_DSP, "register_number={}", 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);
|
IPC::RequestParser rp(ctx, 0x02, 1, 0);
|
||||||
const u32 register_number = rp.Pop<u32>();
|
const u32 register_number = rp.Pop<u32>();
|
||||||
|
|
||||||
ASSERT_MSG(register_number == 0, "Unknown register_number {}", register_number);
|
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||||
rb.Push(RESULT_SUCCESS);
|
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);
|
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);
|
IPC::RequestParser rp(ctx, 0x07, 1, 0);
|
||||||
const u16 semaphore_value = rp.Pop<u16>();
|
const u16 semaphore_value = rp.Pop<u16>();
|
||||||
|
|
||||||
|
system.DSP().SetSemaphore(semaphore_value);
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(RESULT_SUCCESS);
|
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) {
|
void DSP_DSP::ConvertProcessAddressFromDspDram(Kernel::HLERequestContext& ctx) {
|
||||||
@ -111,7 +93,7 @@ void DSP_DSP::WriteProcessPipe(Kernel::HLERequestContext& ctx) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::DSP().PipeWrite(pipe, buffer);
|
system.DSP().PipeWrite(pipe, buffer);
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
@ -127,11 +109,11 @@ void DSP_DSP::ReadPipe(Kernel::HLERequestContext& ctx) {
|
|||||||
const u16 size = rp.Pop<u16>();
|
const u16 size = rp.Pop<u16>();
|
||||||
|
|
||||||
const DspPipe pipe = static_cast<DspPipe>(channel);
|
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;
|
std::vector<u8> pipe_buffer;
|
||||||
if (pipe_readable_size >= size)
|
if (pipe_readable_size >= size)
|
||||||
pipe_buffer = Core::DSP().PipeRead(pipe, size);
|
pipe_buffer = system.DSP().PipeRead(pipe, size);
|
||||||
else
|
else
|
||||||
UNREACHABLE(); // No more data is in pipe. Hardware hangs in this case; Should never happen.
|
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 u32 peer = rp.Pop<u32>();
|
||||||
|
|
||||||
const DspPipe pipe = static_cast<DspPipe>(channel);
|
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);
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
@ -166,11 +148,11 @@ void DSP_DSP::ReadPipeIfPossible(Kernel::HLERequestContext& ctx) {
|
|||||||
const u16 size = rp.Pop<u16>();
|
const u16 size = rp.Pop<u16>();
|
||||||
|
|
||||||
const DspPipe pipe = static_cast<DspPipe>(channel);
|
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;
|
std::vector<u8> pipe_buffer;
|
||||||
if (pipe_readable_size >= size)
|
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);
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
@ -190,23 +172,27 @@ void DSP_DSP::LoadComponent(Kernel::HLERequestContext& ctx) {
|
|||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
rb.Push(true); /// Pretend that we actually loaded the DSP firmware
|
rb.Push(true);
|
||||||
rb.PushMappedBuffer(buffer);
|
rb.PushMappedBuffer(buffer);
|
||||||
|
|
||||||
// TODO(bunnei): Implement real DSP firmware loading
|
|
||||||
|
|
||||||
std::vector<u8> component_data(size);
|
std::vector<u8> component_data(size);
|
||||||
buffer.Read(component_data.data(), 0, size);
|
buffer.Read(component_data.data(), 0, size);
|
||||||
|
|
||||||
LOG_INFO(Service_DSP, "Firmware hash: {:#018x}",
|
system.DSP().LoadComponent(component_data);
|
||||||
Common::ComputeHash64(component_data.data(), component_data.size()));
|
|
||||||
// Some versions of the firmware have the location of DSP structures listed here.
|
LOG_INFO(Service_DSP, "called size=0x{:X}, prog_mask=0x{:08X}, data_mask=0x{:08X}", size,
|
||||||
if (size > 0x37C) {
|
prog_mask, data_mask);
|
||||||
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);
|
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) {
|
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) {
|
void DSP_DSP::SetSemaphoreMask(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx, 0x17, 1, 0);
|
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);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(RESULT_SUCCESS);
|
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) {
|
void DSP_DSP::GetHeadphoneStatus(Kernel::HLERequestContext& ctx) {
|
||||||
@ -350,7 +336,8 @@ bool DSP_DSP::HasTooManyEventsRegistered() const {
|
|||||||
return number >= max_number_of_interrupt_events;
|
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[] = {
|
static const FunctionInfo functions[] = {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
{0x00010040, &DSP_DSP::RecvData, "RecvData"},
|
{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"},
|
{0x000F0080, &DSP_DSP::GetPipeReadableSize, "GetPipeReadableSize"},
|
||||||
{0x001000C0, &DSP_DSP::ReadPipeIfPossible, "ReadPipeIfPossible"},
|
{0x001000C0, &DSP_DSP::ReadPipeIfPossible, "ReadPipeIfPossible"},
|
||||||
{0x001100C2, &DSP_DSP::LoadComponent, "LoadComponent"},
|
{0x001100C2, &DSP_DSP::LoadComponent, "LoadComponent"},
|
||||||
{0x00120000, nullptr, "UnloadComponent"},
|
{0x00120000, &DSP_DSP::UnloadComponent, "UnloadComponent"},
|
||||||
{0x00130082, &DSP_DSP::FlushDataCache, "FlushDataCache"},
|
{0x00130082, &DSP_DSP::FlushDataCache, "FlushDataCache"},
|
||||||
{0x00140082, &DSP_DSP::InvalidateDataCache, "InvalidateDCache"},
|
{0x00140082, &DSP_DSP::InvalidateDataCache, "InvalidateDCache"},
|
||||||
{0x00150082, &DSP_DSP::RegisterInterruptEvents, "RegisterInterruptEvents"},
|
{0x00150082, &DSP_DSP::RegisterInterruptEvents, "RegisterInterruptEvents"},
|
||||||
@ -393,6 +380,9 @@ DSP_DSP::DSP_DSP(Core::System& system) : ServiceFramework("dsp::DSP", DefaultMax
|
|||||||
|
|
||||||
semaphore_event =
|
semaphore_event =
|
||||||
system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "DSP_DSP::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() {
|
DSP_DSP::~DSP_DSP() {
|
||||||
|
@ -153,6 +153,15 @@ private:
|
|||||||
*/
|
*/
|
||||||
void LoadComponent(Kernel::HLERequestContext& ctx);
|
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
|
* DSP_DSP::FlushDataCache service function
|
||||||
*
|
*
|
||||||
@ -245,7 +254,10 @@ private:
|
|||||||
/// Checks if we are trying to register more than 6 events
|
/// Checks if we are trying to register more than 6 events
|
||||||
bool HasTooManyEventsRegistered() const;
|
bool HasTooManyEventsRegistered() const;
|
||||||
|
|
||||||
|
Core::System& system;
|
||||||
|
|
||||||
Kernel::SharedPtr<Kernel::Event> semaphore_event;
|
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_zero = nullptr; /// Currently unknown purpose
|
||||||
Kernel::SharedPtr<Kernel::Event> interrupt_one = 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_Factor3d", Settings::values.factor_3d);
|
||||||
LogSetting("Layout_LayoutOption", static_cast<int>(Settings::values.layout_option));
|
LogSetting("Layout_LayoutOption", static_cast<int>(Settings::values.layout_option));
|
||||||
LogSetting("Layout_SwapScreen", Settings::values.swap_screen);
|
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_OutputEngine", Settings::values.sink_id);
|
||||||
LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching);
|
LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching);
|
||||||
LogSetting("Audio_OutputDevice", Settings::values.audio_device_id);
|
LogSetting("Audio_OutputDevice", Settings::values.audio_device_id);
|
||||||
|
@ -151,6 +151,8 @@ struct Values {
|
|||||||
u8 factor_3d;
|
u8 factor_3d;
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
|
bool enable_dsp_lle;
|
||||||
|
bool enable_dsp_lle_multithread;
|
||||||
std::string sink_id;
|
std::string sink_id;
|
||||||
bool enable_audio_stretching;
|
bool enable_audio_stretching;
|
||||||
std::string audio_device_id;
|
std::string audio_device_id;
|
||||||
|
Loading…
Reference in New Issue
Block a user