From ebd19dec99d9809a669f63294745d7c8facc6d31 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Thu, 31 Aug 2023 15:09:15 +0100 Subject: Rework ADSP into a wrapper for apps --- src/audio_core/adsp/adsp.cpp | 18 ++ src/audio_core/adsp/adsp.h | 50 +++++ .../adsp/apps/audio_renderer/audio_renderer.cpp | 215 +++++++++++++++++++++ .../adsp/apps/audio_renderer/audio_renderer.h | 115 +++++++++++ .../adsp/apps/audio_renderer/command_buffer.h | 23 +++ .../apps/audio_renderer/command_list_processor.cpp | 108 +++++++++++ .../apps/audio_renderer/command_list_processor.h | 120 ++++++++++++ src/audio_core/adsp/mailbox.h | 69 +++++++ 8 files changed, 718 insertions(+) create mode 100644 src/audio_core/adsp/adsp.cpp create mode 100644 src/audio_core/adsp/adsp.h create mode 100644 src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp create mode 100644 src/audio_core/adsp/apps/audio_renderer/audio_renderer.h create mode 100644 src/audio_core/adsp/apps/audio_renderer/command_buffer.h create mode 100644 src/audio_core/adsp/apps/audio_renderer/command_list_processor.cpp create mode 100644 src/audio_core/adsp/apps/audio_renderer/command_list_processor.h create mode 100644 src/audio_core/adsp/mailbox.h (limited to 'src/audio_core/adsp') diff --git a/src/audio_core/adsp/adsp.cpp b/src/audio_core/adsp/adsp.cpp new file mode 100644 index 000000000..0580990f5 --- /dev/null +++ b/src/audio_core/adsp/adsp.cpp @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/adsp/adsp.h" +#include "core/core.h" + +namespace AudioCore::ADSP { + +ADSP::ADSP(Core::System& system, Sink::Sink& sink) { + audio_renderer = + std::make_unique(system, system.ApplicationMemory(), sink); +} + +AudioRenderer::AudioRenderer& ADSP::AudioRenderer() { + return *audio_renderer.get(); +} + +} // namespace AudioCore::ADSP diff --git a/src/audio_core/adsp/adsp.h b/src/audio_core/adsp/adsp.h new file mode 100644 index 000000000..bd5bcc63b --- /dev/null +++ b/src/audio_core/adsp/adsp.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/adsp/apps/audio_renderer/audio_renderer.h" +#include "common/common_types.h" + +namespace Core { +class System; +} // namespace Core + +namespace AudioCore { +namespace Sink { +class Sink; +} + +namespace ADSP { + +/** + * Represents the ADSP embedded within the audio sysmodule. + * This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot. + * + * The kernel will run the apps you write for it, Nintendo have the following: + * + * Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all + * audio samples end up, and we skip it entirely, since we have very different backends and + * mixing is implicitly handled by the OS (but also due to lack of research/simplicity). + * + * AudioRenderer - Receives command lists generated by the audio render + * system on the host, processes them, and sends the samples to Gmix. + * + * OpusDecoder - Contains libopus, and decodes Opus audio packets into raw pcm data. + * + * Communication between the host and ADSP is done through mailboxes, and mapping of shared memory. + */ +class ADSP { +public: + explicit ADSP(Core::System& system, Sink::Sink& sink); + ~ADSP() = default; + + AudioRenderer::AudioRenderer& AudioRenderer(); + +private: + /// AudioRenderer app + std::unique_ptr audio_renderer{}; +}; + +} // namespace ADSP +} // namespace AudioCore diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp new file mode 100644 index 000000000..3da342ea3 --- /dev/null +++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp @@ -0,0 +1,215 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "audio_core/adsp/apps/audio_renderer/audio_renderer.h" +#include "audio_core/audio_core.h" +#include "audio_core/common/common.h" +#include "audio_core/sink/sink.h" +#include "common/logging/log.h" +#include "common/microprofile.h" +#include "common/thread.h" +#include "core/core.h" +#include "core/core_timing.h" + +MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97)); + +namespace AudioCore::ADSP::AudioRenderer { + +AudioRenderer::AudioRenderer(Core::System& system_, Core::Memory::Memory& memory_, + Sink::Sink& sink_) + : system{system_}, memory{memory_}, sink{sink_} {} + +AudioRenderer::~AudioRenderer() { + Stop(); +} + +void AudioRenderer::Start() { + CreateSinkStreams(); + + mailbox.Initialize(AppMailboxId::AudioRenderer); + + main_thread = std::jthread([this](std::stop_token stop_token) { Main(stop_token); }); + + mailbox.Send(Direction::DSP, {Message::InitializeOK, {}}); + if (mailbox.Receive(Direction::Host).msg != Message::InitializeOK) { + LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " + "message response from ADSP!"); + return; + } + running = true; +} + +void AudioRenderer::Stop() { + if (!running) { + return; + } + + mailbox.Send(Direction::DSP, {Message::Shutdown, {}}); + if (mailbox.Receive(Direction::Host).msg != Message::Shutdown) { + LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " + "message response from ADSP!"); + } + main_thread.request_stop(); + main_thread.join(); + + for (auto& stream : streams) { + if (stream) { + stream->Stop(); + sink.CloseStream(stream); + stream = nullptr; + } + } + running = false; +} + +void AudioRenderer::Signal() { + signalled_tick = system.CoreTiming().GetGlobalTimeNs().count(); + Send(Direction::DSP, {Message::Render, {}}); +} + +void AudioRenderer::Wait() { + auto received = Receive(Direction::Host); + if (received.msg != Message::RenderResponse) { + LOG_ERROR(Service_Audio, + "Did not receive the expected render response from the AudioRenderer! Expected " + "{}, got {}", + Message::RenderResponse, received.msg); + } +} + +void AudioRenderer::Send(Direction dir, MailboxMessage message) { + mailbox.Send(dir, std::move(message)); +} + +MailboxMessage AudioRenderer::Receive(Direction dir, bool block) { + return mailbox.Receive(dir, block); +} + +void AudioRenderer::SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept { + command_buffers[session_id] = buffer; +} + +u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept { + return command_buffers[session_id].remaining_command_count; +} + +void AudioRenderer::ClearRemainCommandCount(s32 session_id) noexcept { + command_buffers[session_id].remaining_command_count = 0; +} + +u64 AudioRenderer::GetRenderingStartTick(s32 session_id) const noexcept { + return (1000 * command_buffers[session_id].render_time_taken_us) + signalled_tick; +} + +void AudioRenderer::CreateSinkStreams() { + u32 channels{sink.GetDeviceChannels()}; + for (u32 i = 0; i < MaxRendererSessions; i++) { + std::string name{fmt::format("ADSP_RenderStream-{}", i)}; + streams[i] = + sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render); + streams[i]->SetRingSize(4); + } +} + +void AudioRenderer::Main(std::stop_token stop_token) { + static constexpr char name[]{"AudioRenderer"}; + MicroProfileOnThreadCreate(name); + Common::SetCurrentThreadName(name); + Common::SetCurrentThreadPriority(Common::ThreadPriority::High); + + // TODO: Create buffer map/unmap thread + mailbox + // TODO: Create gMix devices, initialize them here + + if (mailbox.Receive(Direction::DSP).msg != Message::InitializeOK) { + LOG_ERROR(Service_Audio, + "ADSP Audio Renderer -- Failed to receive initialize message from host!"); + return; + } + + mailbox.Send(Direction::Host, {Message::InitializeOK, {}}); + + // 0.12 seconds (2,304,000 / 19,200,000) + constexpr u64 max_process_time{2'304'000ULL}; + + while (!stop_token.stop_requested()) { + auto received{mailbox.Receive(Direction::DSP)}; + switch (received.msg) { + case Message::Shutdown: + mailbox.Send(Direction::Host, {Message::Shutdown, {}}); + return; + + case Message::Render: { + if (system.IsShuttingDown()) [[unlikely]] { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + mailbox.Send(Direction::Host, {Message::RenderResponse, {}}); + continue; + } + std::array buffers_reset{}; + std::array render_times_taken{}; + const auto start_time{system.CoreTiming().GetGlobalTimeUs().count()}; + + for (u32 index = 0; index < MaxRendererSessions; index++) { + auto& command_buffer{command_buffers[index]}; + auto& command_list_processor{command_list_processors[index]}; + + // Check this buffer is valid, as it may not be used. + if (command_buffer.buffer != 0) { + // If there are no remaining commands (from the previous list), + // this is a new command list, initialize it. + if (command_buffer.remaining_command_count == 0) { + command_list_processor.Initialize(system, command_buffer.buffer, + command_buffer.size, streams[index]); + } + + if (command_buffer.reset_buffer && !buffers_reset[index]) { + streams[index]->ClearQueue(); + buffers_reset[index] = true; + } + + u64 max_time{max_process_time}; + if (index == 1 && command_buffer.applet_resource_user_id == + command_buffers[0].applet_resource_user_id) { + max_time = max_process_time - render_times_taken[0]; + if (render_times_taken[0] > max_process_time) { + max_time = 0; + } + } + + max_time = std::min(command_buffer.time_limit, max_time); + command_list_processor.SetProcessTimeMax(max_time); + + if (index == 0) { + streams[index]->WaitFreeSpace(stop_token); + } + + // Process the command list + { + MICROPROFILE_SCOPE(Audio_Renderer); + render_times_taken[index] = + command_list_processor.Process(index) - start_time; + } + + const auto end_time{system.CoreTiming().GetGlobalTimeUs().count()}; + + command_buffer.remaining_command_count = + command_list_processor.GetRemainingCommandCount(); + command_buffer.render_time_taken_us = end_time - start_time; + } + } + + mailbox.Send(Direction::Host, {Message::RenderResponse, {}}); + } break; + + default: + LOG_WARNING(Service_Audio, + "ADSP AudioRenderer received an invalid message, msg={:02X}!", + received.msg); + break; + } + } +} + +} // namespace AudioCore::ADSP::AudioRenderer diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h new file mode 100644 index 000000000..b225e10fb --- /dev/null +++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h @@ -0,0 +1,115 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "audio_core/adsp/apps/audio_renderer/command_buffer.h" +#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" +#include "audio_core/adsp/mailbox.h" +#include "common/common_types.h" +#include "common/polyfill_thread.h" +#include "common/reader_writer_queue.h" +#include "common/thread.h" + +namespace Core { +class System; +namespace Timing { +struct EventType; +} +namespace Memory { +class Memory; +} +class System; +} // namespace Core + +namespace AudioCore { +namespace Sink { +class Sink; +} + +namespace ADSP::AudioRenderer { + +enum Message : u32 { + Invalid = 0x00, + MapUnmap_Map = 0x01, + MapUnmap_MapResponse = 0x02, + MapUnmap_Unmap = 0x03, + MapUnmap_UnmapResponse = 0x04, + MapUnmap_InvalidateCache = 0x05, + MapUnmap_InvalidateCacheResponse = 0x06, + MapUnmap_Shutdown = 0x07, + MapUnmap_ShutdownResponse = 0x08, + InitializeOK = 0x16, + RenderResponse = 0x20, + Render = 0x2A, + Shutdown = 0x34, +}; + +/** + * The AudioRenderer application running on the ADSP. + */ +class AudioRenderer { +public: + explicit AudioRenderer(Core::System& system, Core::Memory::Memory& memory, Sink::Sink& sink); + ~AudioRenderer(); + + /** + * Start the AudioRenderer. + * + * @param mailbox The mailbox to use for this session. + */ + void Start(); + + /** + * Stop the AudioRenderer. + */ + void Stop(); + + void Signal(); + void Wait(); + + void Send(Direction dir, MailboxMessage message); + MailboxMessage Receive(Direction dir, bool block = true); + + void SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept; + u32 GetRemainCommandCount(s32 session_id) const noexcept; + void ClearRemainCommandCount(s32 session_id) noexcept; + u64 GetRenderingStartTick(s32 session_id) const noexcept; + +private: + /** + * Main AudioRenderer thread, responsible for processing the command lists. + */ + void Main(std::stop_token stop_token); + + /** + * Creates the streams which will receive the processed samples. + */ + void CreateSinkStreams(); + + /// Core system + Core::System& system; + /// Memory + Core::Memory::Memory& memory; + /// The output sink the AudioRenderer will use + Sink::Sink& sink; + /// The active mailbox + Mailbox mailbox; + /// Main thread + std::jthread main_thread{}; + /// The current state + std::atomic running{}; + std::array command_buffers{}; + /// The command lists to process + std::array command_list_processors{}; + /// The streams which will receive the processed samples + std::array streams{}; + u64 signalled_tick{0}; +}; + +} // namespace ADSP::AudioRenderer +} // namespace AudioCore diff --git a/src/audio_core/adsp/apps/audio_renderer/command_buffer.h b/src/audio_core/adsp/apps/audio_renderer/command_buffer.h new file mode 100644 index 000000000..3fd1b09dc --- /dev/null +++ b/src/audio_core/adsp/apps/audio_renderer/command_buffer.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::ADSP::AudioRenderer { + +struct CommandBuffer { + // Set by the host + CpuAddr buffer{}; + u64 size{}; + u64 time_limit{}; + u64 applet_resource_user_id{}; + bool reset_buffer{}; + // Set by the DSP + u32 remaining_command_count{}; + u64 render_time_taken_us{}; +}; + +} // namespace AudioCore::ADSP::AudioRenderer diff --git a/src/audio_core/adsp/apps/audio_renderer/command_list_processor.cpp b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.cpp new file mode 100644 index 000000000..acbc9100c --- /dev/null +++ b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.cpp @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h" +#include "audio_core/renderer/command/command_list_header.h" +#include "audio_core/renderer/command/commands.h" +#include "common/settings.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/memory.h" + +namespace AudioCore::ADSP::AudioRenderer { + +void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size, + Sink::SinkStream* stream_) { + system = &system_; + memory = &system->ApplicationMemory(); + stream = stream_; + header = reinterpret_cast(buffer); + commands = reinterpret_cast(buffer + sizeof(Renderer::CommandListHeader)); + commands_buffer_size = size; + command_count = header->command_count; + sample_count = header->sample_count; + target_sample_rate = header->sample_rate; + mix_buffers = header->samples_buffer; + buffer_count = header->buffer_count; + processed_command_count = 0; +} + +void CommandListProcessor::SetProcessTimeMax(const u64 time) { + max_process_time = time; +} + +u32 CommandListProcessor::GetRemainingCommandCount() const { + return command_count - processed_command_count; +} + +void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) { + commands = reinterpret_cast(buffer + sizeof(Renderer::CommandListHeader)); + commands_buffer_size = size; +} + +Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const { + return stream; +} + +u64 CommandListProcessor::Process(u32 session_id) { + const auto start_time_{system->CoreTiming().GetGlobalTimeUs().count()}; + const auto command_base{CpuAddr(commands)}; + + if (processed_command_count > 0) { + current_processing_time += start_time_ - end_time; + } else { + start_time = start_time_; + current_processing_time = 0; + } + + std::string dump{fmt::format("\nSession {}\n", session_id)}; + + for (u32 index = 0; index < command_count; index++) { + auto& command{*reinterpret_cast(commands)}; + + if (command.magic != 0xCAFEBABE) { + LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}", + command.magic); + return system->CoreTiming().GetGlobalTimeUs().count() - start_time_; + } + + auto current_offset{CpuAddr(commands) - command_base}; + + if (current_offset + command.size > commands_buffer_size) { + LOG_ERROR(Service_Audio, + "Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}", + commands_buffer_size, + CpuAddr(commands) + command.size - sizeof(Renderer::CommandListHeader)); + return system->CoreTiming().GetGlobalTimeUs().count() - start_time_; + } + + if (Settings::values.dump_audio_commands) { + command.Dump(*this, dump); + } + + if (!command.Verify(*this)) { + break; + } + + if (command.enabled) { + command.Process(*this); + } else { + dump += fmt::format("\tDisabled!\n"); + } + + processed_command_count++; + commands += command.size; + } + + if (Settings::values.dump_audio_commands && dump != last_dump) { + LOG_WARNING(Service_Audio, "{}", dump); + last_dump = dump; + } + + end_time = system->CoreTiming().GetGlobalTimeUs().count(); + return end_time - start_time_; +} + +} // namespace AudioCore::ADSP::AudioRenderer diff --git a/src/audio_core/adsp/apps/audio_renderer/command_list_processor.h b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.h new file mode 100644 index 000000000..9d6fe1851 --- /dev/null +++ b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.h @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/common/common.h" +#include "audio_core/renderer/command/command_list_header.h" +#include "common/common_types.h" + +namespace Core { +namespace Memory { +class Memory; +} +class System; +} // namespace Core + +namespace AudioCore { +namespace Sink { +class SinkStream; +} + +namespace Renderer { +struct CommandListHeader; +} + +namespace ADSP::AudioRenderer { + +/** + * A processor for command lists given to the AudioRenderer. + */ +class CommandListProcessor { +public: + /** + * Initialize the processor. + * + * @param system - The core system. + * @param buffer - The command buffer to process. + * @param size - The size of the buffer. + * @param stream - The stream to be used for sending the samples. + */ + void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream); + + /** + * Set the maximum processing time for this command list. + * + * @param time - The maximum process time. + */ + void SetProcessTimeMax(u64 time); + + /** + * Get the remaining command count for this list. + * + * @return The remaining command count. + */ + u32 GetRemainingCommandCount() const; + + /** + * Set the command buffer. + * + * @param buffer - The buffer to use. + * @param size - The size of the buffer. + */ + void SetBuffer(CpuAddr buffer, u64 size); + + /** + * Get the stream for this command list. + * + * @return The stream associated with this command list. + */ + Sink::SinkStream* GetOutputSinkStream() const; + + /** + * Process the command list. + * + * @param session_id - Session ID for the commands being processed. + * + * @return The time taken to process. + */ + u64 Process(u32 session_id); + + /// Core system + Core::System* system{}; + /// Core memory + Core::Memory::Memory* memory{}; + /// Stream for the processed samples + Sink::SinkStream* stream{}; + /// Header info for this command list + Renderer::CommandListHeader* header{}; + /// The command buffer + u8* commands{}; + /// The command buffer size + u64 commands_buffer_size{}; + /// The maximum processing time allotted + u64 max_process_time{}; + /// The number of commands in the buffer + u32 command_count{}; + /// The target sample count for output + u32 sample_count{}; + /// The target sample rate for output + u32 target_sample_rate{}; + /// The mixing buffers used by the commands + std::span mix_buffers{}; + /// The number of mix buffers + u32 buffer_count{}; + /// The number of processed commands so far + u32 processed_command_count{}; + /// The processing start time of this list + u64 start_time{}; + /// The current processing time for this list + u64 current_processing_time{}; + /// The end processing time for this list + u64 end_time{}; + /// Last command list string generated, used for dumping audio commands to console + std::string last_dump{}; +}; + +} // namespace ADSP::AudioRenderer +} // namespace AudioCore diff --git a/src/audio_core/adsp/mailbox.h b/src/audio_core/adsp/mailbox.h new file mode 100644 index 000000000..c31b73717 --- /dev/null +++ b/src/audio_core/adsp/mailbox.h @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/bounded_threadsafe_queue.h" +#include "common/common_types.h" + +namespace AudioCore::ADSP { + +enum class AppMailboxId : u32 { + Invalid = 0, + AudioRenderer = 50, + AudioRendererMemoryMapUnmap = 51, +}; + +enum class Direction : u32 { + Host, + DSP, +}; + +struct MailboxMessage { + u32 msg; + std::span data; +}; + +class Mailbox { +public: + void Initialize(AppMailboxId id_) { + Reset(); + id = id_; + } + + AppMailboxId Id() const noexcept { + return id; + } + + void Send(Direction dir, MailboxMessage&& message) { + auto& queue = dir == Direction::Host ? host_queue : adsp_queue; + queue.EmplaceWait(std::move(message)); + } + + MailboxMessage Receive(Direction dir, bool block = true) { + auto& queue = dir == Direction::Host ? host_queue : adsp_queue; + MailboxMessage t; + if (block) { + queue.PopWait(t); + } else { + queue.TryPop(t); + } + return t; + } + + void Reset() { + id = AppMailboxId::Invalid; + MailboxMessage t; + while (host_queue.TryPop(t)) { + } + while (adsp_queue.TryPop(t)) { + } + } + +private: + AppMailboxId id{0}; + Common::SPSCQueue host_queue; + Common::SPSCQueue adsp_queue; +}; + +} // namespace AudioCore::ADSP -- cgit v1.2.3