Project Andio

This commit is contained in:
Kelebek1
2022-07-16 23:48:45 +01:00
parent 6e36f4d230
commit 458da8a948
270 changed files with 33712 additions and 8445 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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