Rework ADSP into a wrapper for apps

This commit is contained in:
Kelebek1
2023-08-31 15:09:15 +01:00
parent 5ce41fa213
commit ebd19dec99
173 changed files with 1059 additions and 1265 deletions

View File

@ -0,0 +1,215 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <chrono>
#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<bool, MaxRendererSessions> buffers_reset{};
std::array<u64, MaxRendererSessions> 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

View File

@ -0,0 +1,115 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <memory>
#include <thread>
#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<bool> running{};
std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
/// The command lists to process
std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{};
/// The streams which will receive the processed samples
std::array<Sink::SinkStream*, MaxRendererSessions> streams{};
u64 signalled_tick{0};
};
} // namespace ADSP::AudioRenderer
} // namespace AudioCore

View File

@ -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

View File

@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#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<Renderer::CommandListHeader*>(buffer);
commands = reinterpret_cast<u8*>(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<u8*>(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<Renderer::ICommand*>(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

View File

@ -0,0 +1,120 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#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<s32> 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