8b4ecf22d4
While were at it, we can also enable sign conversion warnings and other common warnings as errors to prevent these from creeping back into the codebase.
617 lines
19 KiB
C++
617 lines
19 KiB
C++
// Copyright 2020 yuzu Emulator Project
|
|
// Licensed under GPLv2 or any later version
|
|
// Refer to the license.txt file included.
|
|
|
|
#include "audio_core/behavior_info.h"
|
|
#include "audio_core/splitter_context.h"
|
|
#include "common/alignment.h"
|
|
#include "common/assert.h"
|
|
#include "common/logging/log.h"
|
|
|
|
namespace AudioCore {
|
|
|
|
ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id) : id(id) {}
|
|
ServerSplitterDestinationData::~ServerSplitterDestinationData() = default;
|
|
|
|
void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) {
|
|
// Log error as these are not actually failure states
|
|
if (header.magic != SplitterMagic::DataHeader) {
|
|
LOG_ERROR(Audio, "Splitter destination header is invalid!");
|
|
return;
|
|
}
|
|
|
|
// Incorrect splitter id
|
|
if (header.splitter_id != id) {
|
|
LOG_ERROR(Audio, "Splitter destination ids do not match!");
|
|
return;
|
|
}
|
|
|
|
mix_id = header.mix_id;
|
|
// Copy our mix volumes
|
|
std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin());
|
|
if (!in_use && header.in_use) {
|
|
// Update mix volumes
|
|
std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
|
|
needs_update = false;
|
|
}
|
|
in_use = header.in_use;
|
|
}
|
|
|
|
ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() {
|
|
return next;
|
|
}
|
|
|
|
const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const {
|
|
return next;
|
|
}
|
|
|
|
void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) {
|
|
next = dest;
|
|
}
|
|
|
|
bool ServerSplitterDestinationData::ValidMixId() const {
|
|
return GetMixId() != AudioCommon::NO_MIX;
|
|
}
|
|
|
|
s32 ServerSplitterDestinationData::GetMixId() const {
|
|
return mix_id;
|
|
}
|
|
|
|
bool ServerSplitterDestinationData::IsConfigured() const {
|
|
return in_use && ValidMixId();
|
|
}
|
|
|
|
float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const {
|
|
ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
|
|
return current_mix_volumes.at(i);
|
|
}
|
|
|
|
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
|
|
ServerSplitterDestinationData::CurrentMixVolumes() const {
|
|
return current_mix_volumes;
|
|
}
|
|
|
|
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
|
|
ServerSplitterDestinationData::LastMixVolumes() const {
|
|
return last_mix_volumes;
|
|
}
|
|
|
|
void ServerSplitterDestinationData::MarkDirty() {
|
|
needs_update = true;
|
|
}
|
|
|
|
void ServerSplitterDestinationData::UpdateInternalState() {
|
|
if (in_use && needs_update) {
|
|
std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
|
|
}
|
|
needs_update = false;
|
|
}
|
|
|
|
ServerSplitterInfo::ServerSplitterInfo(s32 id) : id(id) {}
|
|
ServerSplitterInfo::~ServerSplitterInfo() = default;
|
|
|
|
void ServerSplitterInfo::InitializeInfos() {
|
|
send_length = 0;
|
|
head = nullptr;
|
|
new_connection = true;
|
|
}
|
|
|
|
void ServerSplitterInfo::ClearNewConnectionFlag() {
|
|
new_connection = false;
|
|
}
|
|
|
|
std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) {
|
|
if (header.send_id != id) {
|
|
return 0;
|
|
}
|
|
|
|
sample_rate = header.sample_rate;
|
|
new_connection = true;
|
|
// We need to update the size here due to the splitter bug being present and providing an
|
|
// incorrect size. We're suppose to also update the header here but we just ignore and continue
|
|
return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3);
|
|
}
|
|
|
|
ServerSplitterDestinationData* ServerSplitterInfo::GetHead() {
|
|
return head;
|
|
}
|
|
|
|
const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const {
|
|
return head;
|
|
}
|
|
|
|
ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) {
|
|
auto current_head = head;
|
|
for (std::size_t i = 0; i < depth; i++) {
|
|
if (current_head == nullptr) {
|
|
return nullptr;
|
|
}
|
|
current_head = current_head->GetNextDestination();
|
|
}
|
|
return current_head;
|
|
}
|
|
|
|
const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const {
|
|
auto current_head = head;
|
|
for (std::size_t i = 0; i < depth; i++) {
|
|
if (current_head == nullptr) {
|
|
return nullptr;
|
|
}
|
|
current_head = current_head->GetNextDestination();
|
|
}
|
|
return current_head;
|
|
}
|
|
|
|
bool ServerSplitterInfo::HasNewConnection() const {
|
|
return new_connection;
|
|
}
|
|
|
|
s32 ServerSplitterInfo::GetLength() const {
|
|
return send_length;
|
|
}
|
|
|
|
void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) {
|
|
head = new_head;
|
|
}
|
|
|
|
void ServerSplitterInfo::SetHeadDepth(s32 length) {
|
|
send_length = length;
|
|
}
|
|
|
|
SplitterContext::SplitterContext() = default;
|
|
SplitterContext::~SplitterContext() = default;
|
|
|
|
void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count,
|
|
std::size_t _data_count) {
|
|
if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) {
|
|
Setup(0, 0, false);
|
|
return;
|
|
}
|
|
// Only initialize if we're using splitters
|
|
Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed());
|
|
}
|
|
|
|
bool SplitterContext::Update(const std::vector<u8>& input, std::size_t& input_offset,
|
|
std::size_t& bytes_read) {
|
|
const auto UpdateOffsets = [&](std::size_t read) {
|
|
input_offset += read;
|
|
bytes_read += read;
|
|
};
|
|
|
|
if (info_count == 0 || data_count == 0) {
|
|
bytes_read = 0;
|
|
return true;
|
|
}
|
|
|
|
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
|
|
sizeof(SplitterInfo::InHeader))) {
|
|
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
|
return false;
|
|
}
|
|
SplitterInfo::InHeader header{};
|
|
std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader));
|
|
UpdateOffsets(sizeof(SplitterInfo::InHeader));
|
|
|
|
if (header.magic != SplitterMagic::SplitterHeader) {
|
|
LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}",
|
|
SplitterMagic::SplitterHeader, header.magic);
|
|
return false;
|
|
}
|
|
|
|
// Clear all connections
|
|
for (auto& info : infos) {
|
|
info.ClearNewConnectionFlag();
|
|
}
|
|
|
|
UpdateInfo(input, input_offset, bytes_read, header.info_count);
|
|
UpdateData(input, input_offset, bytes_read, header.data_count);
|
|
const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16);
|
|
input_offset += aligned_bytes_read - bytes_read;
|
|
bytes_read = aligned_bytes_read;
|
|
return true;
|
|
}
|
|
|
|
bool SplitterContext::UsingSplitter() const {
|
|
return info_count > 0 && data_count > 0;
|
|
}
|
|
|
|
ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) {
|
|
ASSERT(i < info_count);
|
|
return infos.at(i);
|
|
}
|
|
|
|
const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const {
|
|
ASSERT(i < info_count);
|
|
return infos.at(i);
|
|
}
|
|
|
|
ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) {
|
|
ASSERT(i < data_count);
|
|
return datas.at(i);
|
|
}
|
|
|
|
const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const {
|
|
ASSERT(i < data_count);
|
|
return datas.at(i);
|
|
}
|
|
|
|
ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
|
|
std::size_t data) {
|
|
ASSERT(info < info_count);
|
|
auto& cur_info = GetInfo(info);
|
|
return cur_info.GetData(data);
|
|
}
|
|
|
|
const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
|
|
std::size_t data) const {
|
|
ASSERT(info < info_count);
|
|
auto& cur_info = GetInfo(info);
|
|
return cur_info.GetData(data);
|
|
}
|
|
|
|
void SplitterContext::UpdateInternalState() {
|
|
if (data_count == 0) {
|
|
return;
|
|
}
|
|
|
|
for (auto& data : datas) {
|
|
data.UpdateInternalState();
|
|
}
|
|
}
|
|
|
|
std::size_t SplitterContext::GetInfoCount() const {
|
|
return info_count;
|
|
}
|
|
|
|
std::size_t SplitterContext::GetDataCount() const {
|
|
return data_count;
|
|
}
|
|
|
|
void SplitterContext::Setup(std::size_t _info_count, std::size_t _data_count,
|
|
bool is_splitter_bug_fixed) {
|
|
|
|
info_count = _info_count;
|
|
data_count = _data_count;
|
|
|
|
for (std::size_t i = 0; i < info_count; i++) {
|
|
auto& splitter = infos.emplace_back(static_cast<s32>(i));
|
|
splitter.InitializeInfos();
|
|
}
|
|
for (std::size_t i = 0; i < data_count; i++) {
|
|
datas.emplace_back(static_cast<s32>(i));
|
|
}
|
|
|
|
bug_fixed = is_splitter_bug_fixed;
|
|
}
|
|
|
|
bool SplitterContext::UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
|
|
std::size_t& bytes_read, s32 in_splitter_count) {
|
|
const auto UpdateOffsets = [&](std::size_t read) {
|
|
input_offset += read;
|
|
bytes_read += read;
|
|
};
|
|
|
|
for (s32 i = 0; i < in_splitter_count; i++) {
|
|
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
|
|
sizeof(SplitterInfo::InInfoPrams))) {
|
|
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
|
return false;
|
|
}
|
|
SplitterInfo::InInfoPrams header{};
|
|
std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams));
|
|
|
|
// Logged as warning as these don't actually cause a bailout for some reason
|
|
if (header.magic != SplitterMagic::InfoHeader) {
|
|
LOG_ERROR(Audio, "Bad splitter data header");
|
|
break;
|
|
}
|
|
|
|
if (header.send_id < 0 || static_cast<std::size_t>(header.send_id) > info_count) {
|
|
LOG_ERROR(Audio, "Bad splitter data id");
|
|
break;
|
|
}
|
|
|
|
UpdateOffsets(sizeof(SplitterInfo::InInfoPrams));
|
|
auto& info = GetInfo(header.send_id);
|
|
if (!RecomposeDestination(info, header, input, input_offset)) {
|
|
LOG_ERROR(Audio, "Failed to recompose destination for splitter!");
|
|
return false;
|
|
}
|
|
const std::size_t read = info.Update(header);
|
|
bytes_read += read;
|
|
input_offset += read;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SplitterContext::UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
|
|
std::size_t& bytes_read, s32 in_data_count) {
|
|
const auto UpdateOffsets = [&](std::size_t read) {
|
|
input_offset += read;
|
|
bytes_read += read;
|
|
};
|
|
|
|
for (s32 i = 0; i < in_data_count; i++) {
|
|
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
|
|
sizeof(SplitterInfo::InDestinationParams))) {
|
|
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
|
return false;
|
|
}
|
|
SplitterInfo::InDestinationParams header{};
|
|
std::memcpy(&header, input.data() + input_offset,
|
|
sizeof(SplitterInfo::InDestinationParams));
|
|
UpdateOffsets(sizeof(SplitterInfo::InDestinationParams));
|
|
|
|
// Logged as warning as these don't actually cause a bailout for some reason
|
|
if (header.magic != SplitterMagic::DataHeader) {
|
|
LOG_ERROR(Audio, "Bad splitter data header");
|
|
break;
|
|
}
|
|
|
|
if (header.splitter_id < 0 || static_cast<std::size_t>(header.splitter_id) > data_count) {
|
|
LOG_ERROR(Audio, "Bad splitter data id");
|
|
break;
|
|
}
|
|
GetData(header.splitter_id).Update(header);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info,
|
|
SplitterInfo::InInfoPrams& header,
|
|
const std::vector<u8>& input,
|
|
const std::size_t& input_offset) {
|
|
// Clear our current destinations
|
|
auto* current_head = info.GetHead();
|
|
while (current_head != nullptr) {
|
|
auto next_head = current_head->GetNextDestination();
|
|
current_head->SetNextDestination(nullptr);
|
|
current_head = next_head;
|
|
}
|
|
info.SetHead(nullptr);
|
|
|
|
s32 size = header.length;
|
|
// If the splitter bug is present, calculate fixed size
|
|
if (!bug_fixed) {
|
|
if (info_count > 0) {
|
|
const auto factor = data_count / info_count;
|
|
size = std::min(header.length, static_cast<s32>(factor));
|
|
} else {
|
|
size = 0;
|
|
}
|
|
}
|
|
|
|
if (size < 1) {
|
|
LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size);
|
|
return true;
|
|
}
|
|
|
|
auto* start_head = &GetData(header.resource_id_base);
|
|
current_head = start_head;
|
|
std::vector<s32_le> resource_ids(size - 1);
|
|
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
|
|
resource_ids.size() * sizeof(s32_le))) {
|
|
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
|
return false;
|
|
}
|
|
std::memcpy(resource_ids.data(), input.data() + input_offset,
|
|
resource_ids.size() * sizeof(s32_le));
|
|
|
|
for (auto resource_id : resource_ids) {
|
|
auto* head = &GetData(resource_id);
|
|
current_head->SetNextDestination(head);
|
|
current_head = head;
|
|
}
|
|
|
|
info.SetHead(start_head);
|
|
info.SetHeadDepth(size);
|
|
|
|
return true;
|
|
}
|
|
|
|
NodeStates::NodeStates() = default;
|
|
NodeStates::~NodeStates() = default;
|
|
|
|
void NodeStates::Initialize(std::size_t node_count_) {
|
|
// Setup our work parameters
|
|
node_count = node_count_;
|
|
was_node_found.resize(node_count);
|
|
was_node_completed.resize(node_count);
|
|
index_list.resize(node_count);
|
|
index_stack.Reset(node_count * node_count);
|
|
}
|
|
|
|
bool NodeStates::Tsort(EdgeMatrix& edge_matrix) {
|
|
return DepthFirstSearch(edge_matrix);
|
|
}
|
|
|
|
std::size_t NodeStates::GetIndexPos() const {
|
|
return index_pos;
|
|
}
|
|
|
|
const std::vector<s32>& NodeStates::GetIndexList() const {
|
|
return index_list;
|
|
}
|
|
|
|
void NodeStates::PushTsortResult(s32 index) {
|
|
ASSERT(index < static_cast<s32>(node_count));
|
|
index_list[index_pos++] = index;
|
|
}
|
|
|
|
bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) {
|
|
ResetState();
|
|
for (std::size_t i = 0; i < node_count; i++) {
|
|
const auto node_id = static_cast<s32>(i);
|
|
|
|
// If we don't have a state, send to our index stack for work
|
|
if (GetState(i) == NodeStates::State::NoState) {
|
|
index_stack.push(node_id);
|
|
}
|
|
|
|
// While we have work to do in our stack
|
|
while (index_stack.Count() > 0) {
|
|
// Get the current node
|
|
const auto current_stack_index = index_stack.top();
|
|
// Check if we've seen the node yet
|
|
const auto index_state = GetState(current_stack_index);
|
|
if (index_state == NodeStates::State::NoState) {
|
|
// Mark the node as seen
|
|
UpdateState(NodeStates::State::InFound, current_stack_index);
|
|
} else if (index_state == NodeStates::State::InFound) {
|
|
// We've seen this node before, mark it as completed
|
|
UpdateState(NodeStates::State::InCompleted, current_stack_index);
|
|
// Update our index list
|
|
PushTsortResult(current_stack_index);
|
|
// Pop the stack
|
|
index_stack.pop();
|
|
continue;
|
|
} else if (index_state == NodeStates::State::InCompleted) {
|
|
// If our node is already sorted, clear it
|
|
index_stack.pop();
|
|
continue;
|
|
}
|
|
|
|
const auto node_count = edge_matrix.GetNodeCount();
|
|
for (s32 j = 0; j < static_cast<s32>(node_count); j++) {
|
|
// Check if our node is connected to our edge matrix
|
|
if (!edge_matrix.Connected(current_stack_index, j)) {
|
|
continue;
|
|
}
|
|
|
|
// Check if our node exists
|
|
const auto node_state = GetState(j);
|
|
if (node_state == NodeStates::State::NoState) {
|
|
// Add more work
|
|
index_stack.push(j);
|
|
} else if (node_state == NodeStates::State::InFound) {
|
|
UNREACHABLE_MSG("Node start marked as found");
|
|
ResetState();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void NodeStates::ResetState() {
|
|
// Reset to the start of our index stack
|
|
index_pos = 0;
|
|
for (std::size_t i = 0; i < node_count; i++) {
|
|
// Mark all nodes as not found
|
|
was_node_found[i] = false;
|
|
// Mark all nodes as uncompleted
|
|
was_node_completed[i] = false;
|
|
// Mark all indexes as invalid
|
|
index_list[i] = -1;
|
|
}
|
|
}
|
|
|
|
void NodeStates::UpdateState(NodeStates::State state, std::size_t i) {
|
|
switch (state) {
|
|
case NodeStates::State::NoState:
|
|
was_node_found[i] = false;
|
|
was_node_completed[i] = false;
|
|
break;
|
|
case NodeStates::State::InFound:
|
|
was_node_found[i] = true;
|
|
was_node_completed[i] = false;
|
|
break;
|
|
case NodeStates::State::InCompleted:
|
|
was_node_found[i] = false;
|
|
was_node_completed[i] = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
NodeStates::State NodeStates::GetState(std::size_t i) {
|
|
ASSERT(i < node_count);
|
|
if (was_node_found[i]) {
|
|
// If our node exists in our found list
|
|
return NodeStates::State::InFound;
|
|
} else if (was_node_completed[i]) {
|
|
// If node is in the completed list
|
|
return NodeStates::State::InCompleted;
|
|
} else {
|
|
// If in neither
|
|
return NodeStates::State::NoState;
|
|
}
|
|
}
|
|
|
|
NodeStates::Stack::Stack() = default;
|
|
NodeStates::Stack::~Stack() = default;
|
|
|
|
void NodeStates::Stack::Reset(std::size_t size) {
|
|
// Mark our stack as empty
|
|
stack.resize(size);
|
|
stack_size = size;
|
|
stack_pos = 0;
|
|
std::fill(stack.begin(), stack.end(), 0);
|
|
}
|
|
|
|
void NodeStates::Stack::push(s32 val) {
|
|
ASSERT(stack_pos < stack_size);
|
|
stack[stack_pos++] = val;
|
|
}
|
|
|
|
std::size_t NodeStates::Stack::Count() const {
|
|
return stack_pos;
|
|
}
|
|
|
|
s32 NodeStates::Stack::top() const {
|
|
ASSERT(stack_pos > 0);
|
|
return stack[stack_pos - 1];
|
|
}
|
|
|
|
s32 NodeStates::Stack::pop() {
|
|
ASSERT(stack_pos > 0);
|
|
stack_pos--;
|
|
return stack[stack_pos];
|
|
}
|
|
|
|
EdgeMatrix::EdgeMatrix() = default;
|
|
EdgeMatrix::~EdgeMatrix() = default;
|
|
|
|
void EdgeMatrix::Initialize(std::size_t _node_count) {
|
|
node_count = _node_count;
|
|
edge_matrix.resize(node_count * node_count);
|
|
}
|
|
|
|
bool EdgeMatrix::Connected(s32 a, s32 b) {
|
|
return GetState(a, b);
|
|
}
|
|
|
|
void EdgeMatrix::Connect(s32 a, s32 b) {
|
|
SetState(a, b, true);
|
|
}
|
|
|
|
void EdgeMatrix::Disconnect(s32 a, s32 b) {
|
|
SetState(a, b, false);
|
|
}
|
|
|
|
void EdgeMatrix::RemoveEdges(s32 edge) {
|
|
for (std::size_t i = 0; i < node_count; i++) {
|
|
SetState(edge, static_cast<s32>(i), false);
|
|
}
|
|
}
|
|
|
|
std::size_t EdgeMatrix::GetNodeCount() const {
|
|
return node_count;
|
|
}
|
|
|
|
void EdgeMatrix::SetState(s32 a, s32 b, bool state) {
|
|
ASSERT(InRange(a, b));
|
|
edge_matrix.at(a * node_count + b) = state;
|
|
}
|
|
|
|
bool EdgeMatrix::GetState(s32 a, s32 b) {
|
|
ASSERT(InRange(a, b));
|
|
return edge_matrix.at(a * node_count + b);
|
|
}
|
|
|
|
bool EdgeMatrix::InRange(s32 a, s32 b) const {
|
|
const std::size_t pos = a * node_count + b;
|
|
return pos < (node_count * node_count);
|
|
}
|
|
|
|
} // namespace AudioCore
|