From 458da8a94877677f086f06cdeecf959ec4283a33 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Sat, 16 Jul 2022 23:48:45 +0100 Subject: Project Andio --- .../renderer/performance/detail_aspect.cpp | 25 + .../renderer/performance/detail_aspect.h | 33 ++ .../renderer/performance/entry_aspect.cpp | 23 + src/audio_core/renderer/performance/entry_aspect.h | 32 + .../renderer/performance/performance_detail.h | 50 ++ .../renderer/performance/performance_entry.h | 37 ++ .../performance/performance_entry_addresses.h | 17 + .../performance/performance_frame_header.h | 36 ++ .../renderer/performance/performance_manager.cpp | 645 +++++++++++++++++++++ .../renderer/performance/performance_manager.h | 273 +++++++++ 10 files changed, 1171 insertions(+) create mode 100644 src/audio_core/renderer/performance/detail_aspect.cpp create mode 100644 src/audio_core/renderer/performance/detail_aspect.h create mode 100644 src/audio_core/renderer/performance/entry_aspect.cpp create mode 100644 src/audio_core/renderer/performance/entry_aspect.h create mode 100644 src/audio_core/renderer/performance/performance_detail.h create mode 100644 src/audio_core/renderer/performance/performance_entry.h create mode 100644 src/audio_core/renderer/performance/performance_entry_addresses.h create mode 100644 src/audio_core/renderer/performance/performance_frame_header.h create mode 100644 src/audio_core/renderer/performance/performance_manager.cpp create mode 100644 src/audio_core/renderer/performance/performance_manager.h (limited to 'src/audio_core/renderer/performance') 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>(); + break; + case 2: + impl = std::make_unique< + PerformanceManagerImpl>(); + break; + default: + LOG_WARNING(Service_Audio, "Invalid PerformanceMetricsDataFormat {}, creating version 1", + static_cast(version)); + impl = std::make_unique< + PerformanceManagerImpl>(); + } +} + +void PerformanceManager::Initialize(std::span 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 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(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(buffer_offset); + buffer_offset += sizeof(PerformanceFrameHeaderVersion1); + entry_buffer = {reinterpret_cast(buffer_offset), entries_per_frame}; + buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1); + detail_buffer = {reinterpret_cast(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(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(buffer_offset); + buffer_offset += sizeof(PerformanceFrameHeaderVersion1); + frame_history_entries = {reinterpret_cast(buffer_offset), + entries_per_frame}; + buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1); + frame_history_details = {reinterpret_cast(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::IsInitialized() + const { + return is_initialized; +} + +template <> +u32 PerformanceManagerImpl::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 history_entries{}; + std::span history_details{}; + + if (max_frames > 0) { + auto frame_offset{&frame_history[last_output_frame_index * frame_size]}; + history_header = reinterpret_cast(frame_offset); + frame_offset += sizeof(PerformanceFrameHeaderVersion1); + history_entries = {reinterpret_cast(frame_offset), + history_header->entry_count}; + frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion1); + history_details = {reinterpret_cast(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( + reinterpret_cast(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(out_entry_count * sizeof(PerformanceEntryVersion1)); + auto out_details{std::span( + reinterpret_cast(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(out_detail_count * sizeof(PerformanceDetailVersion1)); + out_header = reinterpret_cast(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:: + 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( + &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::SetDetailTarget(const u32 target_node_id_) { + target_node_id = target_node_id_; +} + +template <> +void PerformanceManagerImpl< + PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, + PerformanceDetailVersion2>::Initialize(std::span 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(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(buffer_offset); + buffer_offset += sizeof(PerformanceFrameHeaderVersion2); + entry_buffer = {reinterpret_cast(buffer_offset), entries_per_frame}; + buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2); + detail_buffer = {reinterpret_cast(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(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(buffer_offset); + buffer_offset += sizeof(PerformanceFrameHeaderVersion2); + frame_history_entries = {reinterpret_cast(buffer_offset), + entries_per_frame}; + buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2); + frame_history_details = {reinterpret_cast(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::IsInitialized() + const { + return is_initialized; +} + +template <> +u32 PerformanceManagerImpl::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 history_entries{}; + std::span history_details{}; + + if (max_frames > 0) { + auto frame_offset{&frame_history[last_output_frame_index * frame_size]}; + history_header = reinterpret_cast(frame_offset); + frame_offset += sizeof(PerformanceFrameHeaderVersion2); + history_entries = {reinterpret_cast(frame_offset), + history_header->entry_count}; + frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion2); + history_details = {reinterpret_cast(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( + reinterpret_cast(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(out_entry_count * sizeof(PerformanceEntryVersion2)); + auto out_details{std::span( + reinterpret_cast(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(out_detail_count * sizeof(PerformanceDetailVersion2)); + out_header = reinterpret_cast(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(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::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( + &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::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 +#include +#include + +#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 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 + /// Impl for the performance manager, may be version 1 or 2. + impl; +}; + +template +class PerformanceManagerImpl : public PerformanceManager { +public: + void Initialize(std::span 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 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 entry_buffer{}; + /// Current frame detail buffer + std::span detail_buffer{}; + /// Current frame entry count + u32 entry_count{}; + /// Current frame detail count + u32 detail_count{}; + /// Ringbuffer of previous frames + std::span frame_history{}; + /// Current history frame header + FrameHeaderVersion* frame_history_header{}; + /// Current history entry buffer + std::span frame_history_entries{}; + /// Current history detail buffer + std::span 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 -- cgit v1.2.3