summaryrefslogtreecommitdiffstats
path: root/src/audio_core/renderer/performance
diff options
context:
space:
mode:
authorKelebek1 <eeeedddccc@hotmail.co.uk>2022-07-17 00:48:45 +0200
committerKelebek1 <eeeedddccc@hotmail.co.uk>2022-07-22 02:11:32 +0200
commit458da8a94877677f086f06cdeecf959ec4283a33 (patch)
tree583166d77602ad90a0d552f37de8729ad80fd6c1 /src/audio_core/renderer/performance
parentMerge pull request #8598 from Link4565/recv-dontwait (diff)
downloadyuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.gz
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.bz2
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.lz
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.xz
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.zst
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.zip
Diffstat (limited to 'src/audio_core/renderer/performance')
-rw-r--r--src/audio_core/renderer/performance/detail_aspect.cpp25
-rw-r--r--src/audio_core/renderer/performance/detail_aspect.h33
-rw-r--r--src/audio_core/renderer/performance/entry_aspect.cpp23
-rw-r--r--src/audio_core/renderer/performance/entry_aspect.h32
-rw-r--r--src/audio_core/renderer/performance/performance_detail.h50
-rw-r--r--src/audio_core/renderer/performance/performance_entry.h37
-rw-r--r--src/audio_core/renderer/performance/performance_entry_addresses.h17
-rw-r--r--src/audio_core/renderer/performance/performance_frame_header.h36
-rw-r--r--src/audio_core/renderer/performance/performance_manager.cpp645
-rw-r--r--src/audio_core/renderer/performance/performance_manager.h273
10 files changed, 1171 insertions, 0 deletions
diff --git a/src/audio_core/renderer/performance/detail_aspect.cpp b/src/audio_core/renderer/performance/detail_aspect.cpp
new file mode 100644
index 000000000..f6405937f
--- /dev/null
+++ b/src/audio_core/renderer/performance/detail_aspect.cpp
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/command/command_buffer.h"
+#include "audio_core/renderer/command/command_generator.h"
+#include "audio_core/renderer/performance/detail_aspect.h"
+
+namespace AudioCore::AudioRenderer {
+
+DetailAspect::DetailAspect(CommandGenerator& command_generator_,
+ const PerformanceEntryType entry_type, const s32 node_id_,
+ const PerformanceDetailType detail_type)
+ : command_generator{command_generator_}, node_id{node_id_} {
+ auto perf_manager{command_generator.GetPerformanceManager()};
+ if (perf_manager != nullptr && perf_manager->IsInitialized() &&
+ perf_manager->IsDetailTarget(node_id) &&
+ perf_manager->GetNextEntry(performance_entry_address, detail_type, entry_type, node_id)) {
+ command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start,
+ performance_entry_address);
+
+ initialized = true;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/detail_aspect.h b/src/audio_core/renderer/performance/detail_aspect.h
new file mode 100644
index 000000000..ee4ac2f76
--- /dev/null
+++ b/src/audio_core/renderer/performance/detail_aspect.h
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/performance/performance_entry_addresses.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class CommandGenerator;
+
+/**
+ * Holds detailed information about performance metrics, filled in by the AudioRenderer during
+ * Performance commands.
+ */
+class DetailAspect {
+public:
+ DetailAspect() = default;
+ DetailAspect(CommandGenerator& command_generator, PerformanceEntryType entry_type, s32 node_id,
+ PerformanceDetailType detail_type);
+
+ /// Command generator the command will be generated into
+ CommandGenerator& command_generator;
+ /// Addresses to be filled by the AudioRenderer
+ PerformanceEntryAddresses performance_entry_address{};
+ /// Is this detail aspect initialized?
+ bool initialized{};
+ /// Node id of this aspect
+ s32 node_id;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/entry_aspect.cpp b/src/audio_core/renderer/performance/entry_aspect.cpp
new file mode 100644
index 000000000..dd4165803
--- /dev/null
+++ b/src/audio_core/renderer/performance/entry_aspect.cpp
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/command/command_buffer.h"
+#include "audio_core/renderer/command/command_generator.h"
+#include "audio_core/renderer/performance/entry_aspect.h"
+
+namespace AudioCore::AudioRenderer {
+
+EntryAspect::EntryAspect(CommandGenerator& command_generator_, const PerformanceEntryType type,
+ const s32 node_id_)
+ : command_generator{command_generator_}, node_id{node_id_} {
+ auto perf_manager{command_generator.GetPerformanceManager()};
+ if (perf_manager != nullptr && perf_manager->IsInitialized() &&
+ perf_manager->GetNextEntry(performance_entry_address, type, node_id)) {
+ command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start,
+ performance_entry_address);
+
+ initialized = true;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/entry_aspect.h b/src/audio_core/renderer/performance/entry_aspect.h
new file mode 100644
index 000000000..01c1eb3f1
--- /dev/null
+++ b/src/audio_core/renderer/performance/entry_aspect.h
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/performance/performance_entry_addresses.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class CommandGenerator;
+
+/**
+ * Holds entry information about performance metrics, filled in by the AudioRenderer during
+ * Performance commands.
+ */
+class EntryAspect {
+public:
+ EntryAspect() = default;
+ EntryAspect(CommandGenerator& command_generator, PerformanceEntryType type, s32 node_id);
+
+ /// Command generator the command will be generated into
+ CommandGenerator& command_generator;
+ /// Addresses to be filled by the AudioRenderer
+ PerformanceEntryAddresses performance_entry_address{};
+ /// Is this detail aspect initialized?
+ bool initialized{};
+ /// Node id of this aspect
+ s32 node_id;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_detail.h b/src/audio_core/renderer/performance/performance_detail.h
new file mode 100644
index 000000000..3a4897e60
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_detail.h
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/performance/performance_entry.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+enum class PerformanceDetailType : u8 {
+ Invalid,
+ Unk1,
+ Unk2,
+ Unk3,
+ Unk4,
+ Unk5,
+ Unk6,
+ Unk7,
+ Unk8,
+ Unk9,
+ Unk10,
+ Unk11,
+ Unk12,
+ Unk13,
+};
+
+struct PerformanceDetailVersion1 {
+ /* 0x00 */ u32 node_id;
+ /* 0x04 */ u32 start_time;
+ /* 0x08 */ u32 processed_time;
+ /* 0x0C */ PerformanceDetailType detail_type;
+ /* 0x0D */ PerformanceEntryType entry_type;
+};
+static_assert(sizeof(PerformanceDetailVersion1) == 0x10,
+ "PerformanceDetailVersion1 has the worng size!");
+
+struct PerformanceDetailVersion2 {
+ /* 0x00 */ u32 node_id;
+ /* 0x04 */ u32 start_time;
+ /* 0x08 */ u32 processed_time;
+ /* 0x0C */ PerformanceDetailType detail_type;
+ /* 0x0D */ PerformanceEntryType entry_type;
+ /* 0x10 */ u32 unk_10;
+ /* 0x14 */ char unk14[0x4];
+};
+static_assert(sizeof(PerformanceDetailVersion2) == 0x18,
+ "PerformanceDetailVersion2 has the worng size!");
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_entry.h b/src/audio_core/renderer/performance/performance_entry.h
new file mode 100644
index 000000000..d1b21406b
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_entry.h
@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+enum class PerformanceEntryType : u8 {
+ Invalid,
+ Voice,
+ SubMix,
+ FinalMix,
+ Sink,
+};
+
+struct PerformanceEntryVersion1 {
+ /* 0x00 */ u32 node_id;
+ /* 0x04 */ u32 start_time;
+ /* 0x08 */ u32 processed_time;
+ /* 0x0C */ PerformanceEntryType entry_type;
+};
+static_assert(sizeof(PerformanceEntryVersion1) == 0x10,
+ "PerformanceEntryVersion1 has the worng size!");
+
+struct PerformanceEntryVersion2 {
+ /* 0x00 */ u32 node_id;
+ /* 0x04 */ u32 start_time;
+ /* 0x08 */ u32 processed_time;
+ /* 0x0C */ PerformanceEntryType entry_type;
+ /* 0x0D */ char unk0D[0xB];
+};
+static_assert(sizeof(PerformanceEntryVersion2) == 0x18,
+ "PerformanceEntryVersion2 has the worng size!");
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_entry_addresses.h b/src/audio_core/renderer/performance/performance_entry_addresses.h
new file mode 100644
index 000000000..e381d765c
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_entry_addresses.h
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/common/common.h"
+
+namespace AudioCore::AudioRenderer {
+
+struct PerformanceEntryAddresses {
+ CpuAddr translated_address;
+ CpuAddr entry_start_time_offset;
+ CpuAddr header_entry_count_offset;
+ CpuAddr entry_processed_time_offset;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_frame_header.h b/src/audio_core/renderer/performance/performance_frame_header.h
new file mode 100644
index 000000000..707cc0afb
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_frame_header.h
@@ -0,0 +1,36 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+struct PerformanceFrameHeaderVersion1 {
+ /* 0x00 */ u32 magic; // "PERF"
+ /* 0x04 */ u32 entry_count;
+ /* 0x08 */ u32 detail_count;
+ /* 0x0C */ u32 next_offset;
+ /* 0x10 */ u32 total_processing_time;
+ /* 0x14 */ u32 frame_index;
+};
+static_assert(sizeof(PerformanceFrameHeaderVersion1) == 0x18,
+ "PerformanceFrameHeaderVersion1 has the worng size!");
+
+struct PerformanceFrameHeaderVersion2 {
+ /* 0x00 */ u32 magic; // "PERF"
+ /* 0x04 */ u32 entry_count;
+ /* 0x08 */ u32 detail_count;
+ /* 0x0C */ u32 next_offset;
+ /* 0x10 */ u32 total_processing_time;
+ /* 0x14 */ u32 voices_dropped;
+ /* 0x18 */ u64 start_time;
+ /* 0x20 */ u32 frame_index;
+ /* 0x24 */ bool render_time_exceeded;
+ /* 0x25 */ char unk25[0xB];
+};
+static_assert(sizeof(PerformanceFrameHeaderVersion2) == 0x30,
+ "PerformanceFrameHeaderVersion2 has the worng size!");
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_manager.cpp b/src/audio_core/renderer/performance/performance_manager.cpp
new file mode 100644
index 000000000..fd5873e1e
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_manager.cpp
@@ -0,0 +1,645 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "common/common_funcs.h"
+
+namespace AudioCore::AudioRenderer {
+
+void PerformanceManager::CreateImpl(const size_t version) {
+ switch (version) {
+ case 1:
+ impl = std::make_unique<
+ PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1, PerformanceDetailVersion1>>();
+ break;
+ case 2:
+ impl = std::make_unique<
+ PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2, PerformanceDetailVersion2>>();
+ break;
+ default:
+ LOG_WARNING(Service_Audio, "Invalid PerformanceMetricsDataFormat {}, creating version 1",
+ static_cast<u32>(version));
+ impl = std::make_unique<
+ PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1, PerformanceDetailVersion1>>();
+ }
+}
+
+void PerformanceManager::Initialize(std::span<u8> workbuffer, const u64 workbuffer_size,
+ const AudioRendererParameterInternal& params,
+ const BehaviorInfo& behavior,
+ const MemoryPoolInfo& memory_pool) {
+ CreateImpl(behavior.GetPerformanceMetricsDataFormat());
+ impl->Initialize(workbuffer, workbuffer_size, params, behavior, memory_pool);
+}
+
+bool PerformanceManager::IsInitialized() const {
+ if (impl) {
+ return impl->IsInitialized();
+ }
+ return false;
+}
+
+u32 PerformanceManager::CopyHistories(u8* out_buffer, u64 out_size) {
+ if (impl) {
+ return impl->CopyHistories(out_buffer, out_size);
+ }
+ return 0;
+}
+
+bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
+ const PerformanceSysDetailType sys_detail_type,
+ const s32 node_id) {
+ if (impl) {
+ return impl->GetNextEntry(addresses, unk, sys_detail_type, node_id);
+ }
+ return false;
+}
+
+bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceEntryType entry_type, const s32 node_id) {
+ if (impl) {
+ return impl->GetNextEntry(addresses, entry_type, node_id);
+ }
+ return false;
+}
+
+bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceDetailType detail_type,
+ const PerformanceEntryType entry_type, const s32 node_id) {
+ if (impl) {
+ return impl->GetNextEntry(addresses, detail_type, entry_type, node_id);
+ }
+ return false;
+}
+
+void PerformanceManager::TapFrame(const bool dsp_behind, const u32 voices_dropped,
+ const u64 rendering_start_tick) {
+ if (impl) {
+ impl->TapFrame(dsp_behind, voices_dropped, rendering_start_tick);
+ }
+}
+
+bool PerformanceManager::IsDetailTarget(const u32 target_node_id) const {
+ if (impl) {
+ return impl->IsDetailTarget(target_node_id);
+ }
+ return false;
+}
+
+void PerformanceManager::SetDetailTarget(const u32 target_node_id) {
+ if (impl) {
+ impl->SetDetailTarget(target_node_id);
+ }
+}
+
+template <>
+void PerformanceManagerImpl<
+ PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size,
+ const AudioRendererParameterInternal& params,
+ const BehaviorInfo& behavior,
+ const MemoryPoolInfo& memory_pool) {
+ workbuffer = workbuffer_;
+ entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1;
+ max_detail_count = MaxDetailEntries;
+ frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params);
+ const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)};
+ max_frames = frame_count - 1;
+ translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size);
+
+ // The first frame is the "current" frame we're writing to.
+ auto buffer_offset{workbuffer.data()};
+ frame_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset);
+ buffer_offset += sizeof(PerformanceFrameHeaderVersion1);
+ entry_buffer = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset), entries_per_frame};
+ buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1);
+ detail_buffer = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset), max_detail_count};
+
+ // After the current, is a ringbuffer of history frames, the current frame will be copied here
+ // before a new frame is written.
+ frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size);
+
+ // If there's room for any history frames.
+ if (frame_count >= 2) {
+ buffer_offset = frame_history.data();
+ frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset);
+ buffer_offset += sizeof(PerformanceFrameHeaderVersion1);
+ frame_history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset),
+ entries_per_frame};
+ buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1);
+ frame_history_details = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset),
+ max_detail_count};
+ } else {
+ frame_history_header = {};
+ frame_history_entries = {};
+ frame_history_details = {};
+ }
+
+ target_node_id = 0;
+ version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat());
+ entry_count = 0;
+ detail_count = 0;
+ frame_header->entry_count = 0;
+ frame_header->detail_count = 0;
+ output_frame_index = 0;
+ last_output_frame_index = 0;
+ is_initialized = true;
+}
+
+template <>
+bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1, PerformanceDetailVersion1>::IsInitialized()
+ const {
+ return is_initialized;
+}
+
+template <>
+u32 PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::CopyHistories(u8* out_buffer, u64 out_size) {
+ if (out_buffer == nullptr || out_size == 0 || !is_initialized) {
+ return 0;
+ }
+
+ // Are there any new frames waiting to be output?
+ if (last_output_frame_index == output_frame_index) {
+ return 0;
+ }
+
+ PerformanceFrameHeaderVersion1* out_header{nullptr};
+ u32 out_history_size{0};
+
+ while (last_output_frame_index != output_frame_index) {
+ PerformanceFrameHeaderVersion1* history_header{nullptr};
+ std::span<PerformanceEntryVersion1> history_entries{};
+ std::span<PerformanceDetailVersion1> history_details{};
+
+ if (max_frames > 0) {
+ auto frame_offset{&frame_history[last_output_frame_index * frame_size]};
+ history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(frame_offset);
+ frame_offset += sizeof(PerformanceFrameHeaderVersion1);
+ history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(frame_offset),
+ history_header->entry_count};
+ frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion1);
+ history_details = {reinterpret_cast<PerformanceDetailVersion1*>(frame_offset),
+ history_header->detail_count};
+ } else {
+ // Original code does not break here, but will crash when trying to dereference the
+ // header in the next if, so let's just skip this frame and continue...
+ // Hopefully this will not happen.
+ LOG_WARNING(Service_Audio,
+ "max_frames should not be 0! Skipping frame to avoid a crash");
+ last_output_frame_index++;
+ continue;
+ }
+
+ if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion1) +
+ history_header->detail_count * sizeof(PerformanceDetailVersion1) +
+ 2 * sizeof(PerformanceFrameHeaderVersion1)) {
+ break;
+ }
+
+ u32 out_offset{sizeof(PerformanceFrameHeaderVersion1)};
+ auto out_entries{std::span<PerformanceEntryVersion1>(
+ reinterpret_cast<PerformanceEntryVersion1*>(out_buffer + out_offset),
+ history_header->entry_count)};
+ u32 out_entry_count{0};
+ u32 total_processing_time{0};
+ for (auto& history_entry : history_entries) {
+ if (history_entry.processed_time > 0 || history_entry.start_time > 0) {
+ out_entries[out_entry_count++] = history_entry;
+ total_processing_time += history_entry.processed_time;
+ }
+ }
+
+ out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion1));
+ auto out_details{std::span<PerformanceDetailVersion1>(
+ reinterpret_cast<PerformanceDetailVersion1*>(out_buffer + out_offset),
+ history_header->detail_count)};
+ u32 out_detail_count{0};
+ for (auto& history_detail : history_details) {
+ if (history_detail.processed_time > 0 || history_detail.start_time > 0) {
+ out_details[out_detail_count++] = history_detail;
+ }
+ }
+
+ out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion1));
+ out_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(out_buffer);
+ out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F');
+ out_header->entry_count = out_entry_count;
+ out_header->detail_count = out_detail_count;
+ out_header->next_offset = out_offset;
+ out_header->total_processing_time = total_processing_time;
+ out_header->frame_index = history_header->frame_index;
+
+ out_history_size += out_offset;
+
+ out_buffer += out_offset;
+ out_size -= out_offset;
+ last_output_frame_index = (last_output_frame_index + 1) % max_frames;
+ }
+
+ // We're out of frames to output, so if there's enough left in the output buffer for another
+ // header, and we output at least 1 frame, set the next header to null.
+ if (out_size > sizeof(PerformanceFrameHeaderVersion1) && out_header != nullptr) {
+ std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion1));
+ }
+
+ return out_history_size;
+}
+
+template <>
+bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1, PerformanceDetailVersion1>::
+ GetNextEntry([[maybe_unused]] PerformanceEntryAddresses& addresses, [[maybe_unused]] u32** unk,
+ [[maybe_unused]] PerformanceSysDetailType sys_detail_type,
+ [[maybe_unused]] s32 node_id) {
+ return false;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceEntryType entry_type,
+ const s32 node_id) {
+ if (!is_initialized) {
+ return false;
+ }
+
+ addresses.translated_address = translated_buffer;
+ addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceFrameHeaderVersion1, entry_count);
+
+ auto entry{&entry_buffer[entry_count++]};
+ addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceEntryVersion1, start_time);
+ addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceEntryVersion1, processed_time);
+
+ std::memset(entry, 0, sizeof(PerformanceEntryVersion1));
+ entry->node_id = node_id;
+ entry->entry_type = entry_type;
+ return true;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceDetailType detail_type,
+ const PerformanceEntryType entry_type,
+ const s32 node_id) {
+ if (!is_initialized || detail_count > MaxDetailEntries) {
+ return false;
+ }
+
+ auto detail{&detail_buffer[detail_count++]};
+
+ addresses.translated_address = translated_buffer;
+ addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceFrameHeaderVersion1, detail_count);
+ addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion1, start_time);
+ addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion1, processed_time);
+
+ std::memset(detail, 0, sizeof(PerformanceDetailVersion1));
+ detail->node_id = node_id;
+ detail->entry_type = entry_type;
+ detail->detail_type = detail_type;
+ return true;
+}
+
+template <>
+void PerformanceManagerImpl<
+ PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::TapFrame([[maybe_unused]] bool dsp_behind,
+ [[maybe_unused]] u32 voices_dropped,
+ [[maybe_unused]] u64 rendering_start_tick) {
+ if (!is_initialized) {
+ return;
+ }
+
+ if (max_frames > 0) {
+ if (!frame_history.empty() && !workbuffer.empty()) {
+ auto history_frame = reinterpret_cast<PerformanceFrameHeaderVersion1*>(
+ &frame_history[output_frame_index * frame_size]);
+ std::memcpy(history_frame, workbuffer.data(), frame_size);
+ history_frame->frame_index = history_frame_index++;
+ }
+ output_frame_index = (output_frame_index + 1) % max_frames;
+ }
+
+ entry_count = 0;
+ detail_count = 0;
+ frame_header->entry_count = 0;
+ frame_header->detail_count = 0;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::IsDetailTarget(const u32 target_node_id_) const {
+ return target_node_id == target_node_id_;
+}
+
+template <>
+void PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::SetDetailTarget(const u32 target_node_id_) {
+ target_node_id = target_node_id_;
+}
+
+template <>
+void PerformanceManagerImpl<
+ PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size,
+ const AudioRendererParameterInternal& params,
+ const BehaviorInfo& behavior,
+ const MemoryPoolInfo& memory_pool) {
+ workbuffer = workbuffer_;
+ entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1;
+ max_detail_count = MaxDetailEntries;
+ frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params);
+ const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)};
+ max_frames = frame_count - 1;
+ translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size);
+
+ // The first frame is the "current" frame we're writing to.
+ auto buffer_offset{workbuffer.data()};
+ frame_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset);
+ buffer_offset += sizeof(PerformanceFrameHeaderVersion2);
+ entry_buffer = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset), entries_per_frame};
+ buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2);
+ detail_buffer = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset), max_detail_count};
+
+ // After the current, is a ringbuffer of history frames, the current frame will be copied here
+ // before a new frame is written.
+ frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size);
+
+ // If there's room for any history frames.
+ if (frame_count >= 2) {
+ buffer_offset = frame_history.data();
+ frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset);
+ buffer_offset += sizeof(PerformanceFrameHeaderVersion2);
+ frame_history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset),
+ entries_per_frame};
+ buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2);
+ frame_history_details = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset),
+ max_detail_count};
+ } else {
+ frame_history_header = {};
+ frame_history_entries = {};
+ frame_history_details = {};
+ }
+
+ target_node_id = 0;
+ version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat());
+ entry_count = 0;
+ detail_count = 0;
+ frame_header->entry_count = 0;
+ frame_header->detail_count = 0;
+ output_frame_index = 0;
+ last_output_frame_index = 0;
+ is_initialized = true;
+}
+
+template <>
+bool PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2, PerformanceDetailVersion2>::IsInitialized()
+ const {
+ return is_initialized;
+}
+
+template <>
+u32 PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::CopyHistories(u8* out_buffer, u64 out_size) {
+ if (out_buffer == nullptr || out_size == 0 || !is_initialized) {
+ return 0;
+ }
+
+ // Are there any new frames waiting to be output?
+ if (last_output_frame_index == output_frame_index) {
+ return 0;
+ }
+
+ PerformanceFrameHeaderVersion2* out_header{nullptr};
+ u32 out_history_size{0};
+
+ while (last_output_frame_index != output_frame_index) {
+ PerformanceFrameHeaderVersion2* history_header{nullptr};
+ std::span<PerformanceEntryVersion2> history_entries{};
+ std::span<PerformanceDetailVersion2> history_details{};
+
+ if (max_frames > 0) {
+ auto frame_offset{&frame_history[last_output_frame_index * frame_size]};
+ history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(frame_offset);
+ frame_offset += sizeof(PerformanceFrameHeaderVersion2);
+ history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(frame_offset),
+ history_header->entry_count};
+ frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion2);
+ history_details = {reinterpret_cast<PerformanceDetailVersion2*>(frame_offset),
+ history_header->detail_count};
+ } else {
+ // Original code does not break here, but will crash when trying to dereference the
+ // header in the next if, so let's just skip this frame and continue...
+ // Hopefully this will not happen.
+ LOG_WARNING(Service_Audio,
+ "max_frames should not be 0! Skipping frame to avoid a crash");
+ last_output_frame_index++;
+ continue;
+ }
+
+ if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion2) +
+ history_header->detail_count * sizeof(PerformanceDetailVersion2) +
+ 2 * sizeof(PerformanceFrameHeaderVersion2)) {
+ break;
+ }
+
+ u32 out_offset{sizeof(PerformanceFrameHeaderVersion2)};
+ auto out_entries{std::span<PerformanceEntryVersion2>(
+ reinterpret_cast<PerformanceEntryVersion2*>(out_buffer + out_offset),
+ history_header->entry_count)};
+ u32 out_entry_count{0};
+ u32 total_processing_time{0};
+ for (auto& history_entry : history_entries) {
+ if (history_entry.processed_time > 0 || history_entry.start_time > 0) {
+ out_entries[out_entry_count++] = history_entry;
+ total_processing_time += history_entry.processed_time;
+ }
+ }
+
+ out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion2));
+ auto out_details{std::span<PerformanceDetailVersion2>(
+ reinterpret_cast<PerformanceDetailVersion2*>(out_buffer + out_offset),
+ history_header->detail_count)};
+ u32 out_detail_count{0};
+ for (auto& history_detail : history_details) {
+ if (history_detail.processed_time > 0 || history_detail.start_time > 0) {
+ out_details[out_detail_count++] = history_detail;
+ }
+ }
+
+ out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion2));
+ out_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(out_buffer);
+ out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F');
+ out_header->entry_count = out_entry_count;
+ out_header->detail_count = out_detail_count;
+ out_header->next_offset = out_offset;
+ out_header->total_processing_time = total_processing_time;
+ out_header->voices_dropped = history_header->voices_dropped;
+ out_header->start_time = history_header->start_time;
+ out_header->frame_index = history_header->frame_index;
+ out_header->render_time_exceeded = history_header->render_time_exceeded;
+
+ out_history_size += out_offset;
+
+ out_buffer += out_offset;
+ out_size -= out_offset;
+ last_output_frame_index = (last_output_frame_index + 1) % max_frames;
+ }
+
+ // We're out of frames to output, so if there's enough left in the output buffer for another
+ // header, and we output at least 1 frame, set the next header to null.
+ if (out_size > sizeof(PerformanceFrameHeaderVersion2) && out_header != nullptr) {
+ std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion2));
+ }
+
+ return out_history_size;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
+ const PerformanceSysDetailType sys_detail_type,
+ const s32 node_id) {
+ if (!is_initialized || detail_count > MaxDetailEntries) {
+ return false;
+ }
+
+ auto detail{&detail_buffer[detail_count++]};
+
+ addresses.translated_address = translated_buffer;
+ addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceFrameHeaderVersion2, detail_count);
+ addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion2, start_time);
+ addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion2, processed_time);
+
+ std::memset(detail, 0, sizeof(PerformanceDetailVersion2));
+ detail->node_id = node_id;
+ detail->detail_type = static_cast<PerformanceDetailType>(sys_detail_type);
+
+ if (unk) {
+ *unk = &detail->unk_10;
+ }
+ return true;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceEntryType entry_type,
+ const s32 node_id) {
+ if (!is_initialized) {
+ return false;
+ }
+
+ auto entry{&entry_buffer[entry_count++]};
+
+ addresses.translated_address = translated_buffer;
+ addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceFrameHeaderVersion2, entry_count);
+ addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceEntryVersion2, start_time);
+ addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceEntryVersion2, processed_time);
+
+ std::memset(entry, 0, sizeof(PerformanceEntryVersion2));
+ entry->node_id = node_id;
+ entry->entry_type = entry_type;
+ return true;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceDetailType detail_type,
+ const PerformanceEntryType entry_type,
+ const s32 node_id) {
+ if (!is_initialized || detail_count > MaxDetailEntries) {
+ return false;
+ }
+
+ auto detail{&detail_buffer[detail_count++]};
+
+ addresses.translated_address = translated_buffer;
+ addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceFrameHeaderVersion2, detail_count);
+ addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion2, start_time);
+ addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion2, processed_time);
+
+ std::memset(detail, 0, sizeof(PerformanceDetailVersion2));
+ detail->node_id = node_id;
+ detail->entry_type = entry_type;
+ detail->detail_type = detail_type;
+ return true;
+}
+
+template <>
+void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::TapFrame(const bool dsp_behind,
+ const u32 voices_dropped,
+ const u64 rendering_start_tick) {
+ if (!is_initialized) {
+ return;
+ }
+
+ if (max_frames > 0) {
+ if (!frame_history.empty() && !workbuffer.empty()) {
+ auto history_frame{reinterpret_cast<PerformanceFrameHeaderVersion2*>(
+ &frame_history[output_frame_index * frame_size])};
+ std::memcpy(history_frame, workbuffer.data(), frame_size);
+ history_frame->render_time_exceeded = dsp_behind;
+ history_frame->voices_dropped = voices_dropped;
+ history_frame->start_time = rendering_start_tick;
+ history_frame->frame_index = history_frame_index++;
+ }
+ output_frame_index = (output_frame_index + 1) % max_frames;
+ }
+
+ entry_count = 0;
+ detail_count = 0;
+ frame_header->entry_count = 0;
+ frame_header->detail_count = 0;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::IsDetailTarget(const u32 target_node_id_) const {
+ return target_node_id == target_node_id_;
+}
+
+template <>
+void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::SetDetailTarget(const u32 target_node_id_) {
+ target_node_id = target_node_id_;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_manager.h b/src/audio_core/renderer/performance/performance_manager.h
new file mode 100644
index 000000000..b82176bef
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_manager.h
@@ -0,0 +1,273 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+#include <span>
+
+#include "audio_core/common/audio_renderer_parameter.h"
+#include "audio_core/renderer/performance/performance_detail.h"
+#include "audio_core/renderer/performance/performance_entry.h"
+#include "audio_core/renderer/performance/performance_entry_addresses.h"
+#include "audio_core/renderer/performance/performance_frame_header.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class BehaviorInfo;
+class MemoryPoolInfo;
+
+enum class PerformanceVersion {
+ Version1,
+ Version2,
+};
+
+enum class PerformanceSysDetailType {
+ PcmInt16 = 15,
+ PcmFloat = 16,
+ Adpcm = 17,
+ LightLimiter = 37,
+};
+
+enum class PerformanceState {
+ Invalid,
+ Start,
+ Stop,
+};
+
+/**
+ * Manages performance information.
+ *
+ * The performance buffer is split into frames, each comprised of:
+ * Frame header - Information about the number of entries/details and some others
+ * Entries - Created when starting to generate types of commands, such as voice
+ * commands, mix commands, sink commands etc. Details - Created for specific commands
+ * within each group. Up to MaxDetailEntries per frame.
+ *
+ * A current frame is written to by the AudioRenderer, and before it processes the next command
+ * list, the current frame is copied to a ringbuffer of history frames. These frames are then
+ * output back to the game if it supplies a performance buffer to RequestUpdate.
+ *
+ * Two versions currently exist, version 2 adds a few extra fields to the header, and a new
+ * SysDetail type which is seemingly unused.
+ */
+class PerformanceManager {
+public:
+ static constexpr size_t MaxDetailEntries = 100;
+
+ struct InParameter {
+ /* 0x00 */ s32 target_node_id;
+ /* 0x04 */ char unk04[0xC];
+ };
+ static_assert(sizeof(InParameter) == 0x10,
+ "PerformanceManager::InParameter has the wrong size!");
+
+ struct OutStatus {
+ /* 0x00 */ s32 history_size;
+ /* 0x04 */ char unk04[0xC];
+ };
+ static_assert(sizeof(OutStatus) == 0x10, "PerformanceManager::OutStatus has the wrong size!");
+
+ /**
+ * Calculate the required size for the performance workbuffer.
+ *
+ * @param behavior - Check which version is supported.
+ * @param params - Input parameters.
+ * @return Required workbuffer size.
+ */
+ static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame(
+ const BehaviorInfo& behavior, const AudioRendererParameterInternal& params) {
+ u64 entry_count{params.voices + params.effects + params.sub_mixes + params.sinks + 1};
+ switch (behavior.GetPerformanceMetricsDataFormat()) {
+ case 1:
+ return sizeof(PerformanceFrameHeaderVersion1) +
+ PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) +
+ entry_count * sizeof(PerformanceEntryVersion1);
+ case 2:
+ return sizeof(PerformanceFrameHeaderVersion2) +
+ PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion2) +
+ entry_count * sizeof(PerformanceEntryVersion2);
+ }
+
+ LOG_WARNING(Service_Audio, "Invalid PerformanceMetrics version, assuming version 1");
+ return sizeof(PerformanceFrameHeaderVersion1) +
+ PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) +
+ entry_count * sizeof(PerformanceEntryVersion1);
+ }
+
+ virtual ~PerformanceManager() = default;
+
+ /**
+ * Initialize the performance manager.
+ *
+ * @param workbuffer - Workbuffer to use for performance frames.
+ * @param workbuffer_size - Size of the workbuffer.
+ * @param params - Input parameters.
+ * @param behavior - Behaviour to check version and data format.
+ * @param memory_pool - Used to translate the workbuffer address for the DSP.
+ */
+ virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size,
+ const AudioRendererParameterInternal& params,
+ const BehaviorInfo& behavior, const MemoryPoolInfo& memory_pool);
+
+ /**
+ * Check if the manager is initialized.
+ *
+ * @return True if initialized, otherwise false.
+ */
+ virtual bool IsInitialized() const;
+
+ /**
+ * Copy the waiting performance frames to the output buffer.
+ *
+ * @param out_buffer - Output buffer to store performance frames.
+ * @param out_size - Size of the output buffer.
+ * @return Size in bytes that were written to the buffer.
+ */
+ virtual u32 CopyHistories(u8* out_buffer, u64 out_size);
+
+ /**
+ * Setup a new sys detail in the current frame, filling in addresses with offsets to the
+ * current workbuffer, to be written by the AudioRenderer. Note: This version is
+ * unused/incomplete.
+ *
+ * @param addresses - Filled with pointers to the new entry, which should be passed to
+ * the AudioRenderer with Performance commands to be written.
+ * @param unk - Unknown.
+ * @param sys_detail_type - Sys detail type.
+ * @param node_id - Node id for this entry.
+ * @return True if a new entry was created and the offsets are valid, otherwise false.
+ */
+ virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
+ PerformanceSysDetailType sys_detail_type, s32 node_id);
+
+ /**
+ * Setup a new entry in the current frame, filling in addresses with offsets to the current
+ * workbuffer, to be written by the AudioRenderer.
+ *
+ * @param addresses - Filled with pointers to the new entry, which should be passed to
+ * the AudioRenderer with Performance commands to be written.
+ * @param entry_type - The type of this entry. See PerformanceEntryType
+ * @param node_id - Node id for this entry.
+ * @return True if a new entry was created and the offsets are valid, otherwise false.
+ */
+ virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type,
+ s32 node_id);
+
+ /**
+ * Setup a new detail in the current frame, filling in addresses with offsets to the current
+ * workbuffer, to be written by the AudioRenderer.
+ *
+ * @param addresses - Filled with pointers to the new detail, which should be passed
+ * to the AudioRenderer with Performance commands to be written.
+ * @param entry_type - The type of this detail. See PerformanceEntryType
+ * @param node_id - Node id for this detail.
+ * @return True if a new detail was created and the offsets are valid, otherwise false.
+ */
+ virtual bool GetNextEntry(PerformanceEntryAddresses& addresses,
+ PerformanceDetailType detail_type, PerformanceEntryType entry_type,
+ s32 node_id);
+
+ /**
+ * Save the current frame to the ring buffer.
+ *
+ * @param dsp_behind - Did the AudioRenderer fall behind and not
+ * finish processing the command list?
+ * @param voices_dropped - The number of voices that were dropped.
+ * @param rendering_start_tick - The tick rendering started.
+ */
+ virtual void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick);
+
+ /**
+ * Check if the node id is a detail type.
+ *
+ * @return True if the node is a detail type, otherwise false.
+ */
+ virtual bool IsDetailTarget(u32 target_node_id) const;
+
+ /**
+ * Set the given node to be a detail type.
+ *
+ * @param target_node_id - Node to set.
+ */
+ virtual void SetDetailTarget(u32 target_node_id);
+
+private:
+ /**
+ * Create the performance manager.
+ *
+ * @param version - Performance version to create.
+ */
+ void CreateImpl(size_t version);
+
+ std::unique_ptr<PerformanceManager>
+ /// Impl for the performance manager, may be version 1 or 2.
+ impl;
+};
+
+template <PerformanceVersion Version, typename FrameHeaderVersion, typename EntryVersion,
+ typename DetailVersion>
+class PerformanceManagerImpl : public PerformanceManager {
+public:
+ void Initialize(std::span<u8> workbuffer, u64 workbuffer_size,
+ const AudioRendererParameterInternal& params, const BehaviorInfo& behavior,
+ const MemoryPoolInfo& memory_pool) override;
+ bool IsInitialized() const override;
+ u32 CopyHistories(u8* out_buffer, u64 out_size) override;
+ bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
+ PerformanceSysDetailType sys_detail_type, s32 node_id) override;
+ bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type,
+ s32 node_id) override;
+ bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceDetailType detail_type,
+ PerformanceEntryType entry_type, s32 node_id) override;
+ void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick) override;
+ bool IsDetailTarget(u32 target_node_id) const override;
+ void SetDetailTarget(u32 target_node_id) override;
+
+private:
+ /// Workbuffer used to store the current performance frame
+ std::span<u8> workbuffer{};
+ /// DSP address of the workbuffer, used by the AudioRenderer
+ CpuAddr translated_buffer{};
+ /// Current frame index
+ u32 history_frame_index{};
+ /// Current frame header
+ FrameHeaderVersion* frame_header{};
+ /// Current frame entry buffer
+ std::span<EntryVersion> entry_buffer{};
+ /// Current frame detail buffer
+ std::span<DetailVersion> detail_buffer{};
+ /// Current frame entry count
+ u32 entry_count{};
+ /// Current frame detail count
+ u32 detail_count{};
+ /// Ringbuffer of previous frames
+ std::span<u8> frame_history{};
+ /// Current history frame header
+ FrameHeaderVersion* frame_history_header{};
+ /// Current history entry buffer
+ std::span<EntryVersion> frame_history_entries{};
+ /// Current history detail buffer
+ std::span<DetailVersion> frame_history_details{};
+ /// Current history ringbuffer write index
+ u32 output_frame_index{};
+ /// Last history frame index that was written back to the game
+ u32 last_output_frame_index{};
+ /// Maximum number of history frames in the ringbuffer
+ u32 max_frames{};
+ /// Number of entries per frame
+ u32 entries_per_frame{};
+ /// Maximum number of details per frame
+ u32 max_detail_count{};
+ /// Frame size in bytes
+ u64 frame_size{};
+ /// Is the performance manager initialized?
+ bool is_initialized{};
+ /// Target node id
+ u32 target_node_id{};
+ /// Performance version in use
+ PerformanceVersion version{};
+};
+
+} // namespace AudioCore::AudioRenderer