Project Andio
This commit is contained in:
25
src/audio_core/renderer/performance/detail_aspect.cpp
Normal file
25
src/audio_core/renderer/performance/detail_aspect.cpp
Normal 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
|
33
src/audio_core/renderer/performance/detail_aspect.h
Normal file
33
src/audio_core/renderer/performance/detail_aspect.h
Normal 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
|
23
src/audio_core/renderer/performance/entry_aspect.cpp
Normal file
23
src/audio_core/renderer/performance/entry_aspect.cpp
Normal 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
|
32
src/audio_core/renderer/performance/entry_aspect.h
Normal file
32
src/audio_core/renderer/performance/entry_aspect.h
Normal 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
|
50
src/audio_core/renderer/performance/performance_detail.h
Normal file
50
src/audio_core/renderer/performance/performance_detail.h
Normal 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
|
37
src/audio_core/renderer/performance/performance_entry.h
Normal file
37
src/audio_core/renderer/performance/performance_entry.h
Normal 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
|
@ -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
|
@ -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
|
645
src/audio_core/renderer/performance/performance_manager.cpp
Normal file
645
src/audio_core/renderer/performance/performance_manager.cpp
Normal 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
|
273
src/audio_core/renderer/performance/performance_manager.h
Normal file
273
src/audio_core/renderer/performance/performance_manager.h
Normal 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
|
Reference in New Issue
Block a user