//
// Copyright (c) 2019-2020 Ryujinx
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see .
//
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Utils;
using Ryujinx.Common;
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Server.Splitter
{
///
/// Splitter context.
///
public class SplitterContext
{
///
/// Storage for .
///
private Memory _splitters;
///
/// Storage for .
///
private Memory _splitterDestinations;
///
/// If set to true, trust the user destination count in .
///
public bool IsBugFixed { get; private set; }
///
/// Initialize .
///
/// The behaviour context.
/// The audio renderer configuration.
/// The .
/// Return true if the initialization was successful.
public bool Initialize(ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter, WorkBufferAllocator workBufferAllocator)
{
if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0)
{
Setup(Memory.Empty, Memory.Empty, false);
return true;
}
Memory splitters = workBufferAllocator.Allocate(parameter.SplitterCount, SplitterState.Alignment);
if (splitters.IsEmpty)
{
return false;
}
int splitterId = 0;
foreach (ref SplitterState splitter in splitters.Span)
{
splitter = new SplitterState(splitterId++);
}
Memory splitterDestinations = workBufferAllocator.Allocate(parameter.SplitterDestinationCount,
SplitterDestination.Alignment);
if (splitterDestinations.IsEmpty)
{
return false;
}
int splitterDestinationId = 0;
foreach (ref SplitterDestination data in splitterDestinations.Span)
{
data = new SplitterDestination(splitterDestinationId++);
}
SplitterState.InitializeSplitters(splitters.Span);
Setup(splitters, splitterDestinations, behaviourContext.IsSplitterBugFixed());
return true;
}
///
/// Get the work buffer size while adding the size needed for splitter to operate.
///
/// The current size.
/// The behaviour context.
/// The renderer configuration.
/// Return the new size taking splitter into account.
public static ulong GetWorkBufferSize(ulong size, ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter)
{
if (behaviourContext.IsSplitterSupported())
{
size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterCount, SplitterState.Alignment);
size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestination.Alignment);
if (behaviourContext.IsSplitterBugFixed())
{
size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, 0x10);
}
return size;
}
else
{
return size;
}
}
///
/// Setup the instance.
///
/// The storage.
/// The storage.
/// If set to true, trust the user destination count in .
private void Setup(Memory splitters, Memory splitterDestinations, bool isBugFixed)
{
_splitters = splitters;
_splitterDestinations = splitterDestinations;
IsBugFixed = isBugFixed;
}
///
/// Clear the new connection flag.
///
private void ClearAllNewConnectionFlag()
{
foreach (ref SplitterState splitter in _splitters.Span)
{
splitter.ClearNewConnectionFlag();
}
}
///
/// Get the destination count using the count of splitter.
///
/// The destination count using the count of splitter.
public int GetDestinationCountPerStateForCompatibility()
{
if (_splitters.IsEmpty)
{
return 0;
}
return _splitterDestinations.Length / _splitters.Length;
}
///
/// Update one or multiple from user parameters.
///
/// The splitter header.
/// The raw data after the splitter header.
private void UpdateState(ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan input)
{
for (int i = 0; i < inputHeader.SplitterCount; i++)
{
SplitterInParameter parameter = MemoryMarshal.Read(input);
Debug.Assert(parameter.IsMagicValid());
if (parameter.IsMagicValid())
{
if (parameter.Id >= 0 && parameter.Id < _splitters.Length)
{
ref SplitterState splitter = ref GetState(parameter.Id);
splitter.Update(this, ref parameter, input.Slice(Unsafe.SizeOf()));
}
input = input.Slice(0x1C + (int)parameter.DestinationCount * 4);
}
}
}
///
/// Update one or multiple from user parameters.
///
/// The splitter header.
/// The raw data after the splitter header.
private void UpdateData(ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan input)
{
for (int i = 0; i < inputHeader.SplitterDestinationCount; i++)
{
SplitterDestinationInParameter parameter = MemoryMarshal.Read(input);
Debug.Assert(parameter.IsMagicValid());
if (parameter.IsMagicValid())
{
if (parameter.Id >= 0 && parameter.Id < _splitterDestinations.Length)
{
ref SplitterDestination destination = ref GetDestination(parameter.Id);
destination.Update(parameter);
}
input = input.Slice(Unsafe.SizeOf());
}
}
}
///
/// Update splitter from user parameters.
///
/// The input raw user data.
/// The total consumed size.
/// Return true if the update was successful.
public bool Update(ReadOnlySpan input, out int consumedSize)
{
if (_splitterDestinations.IsEmpty || _splitters.IsEmpty)
{
consumedSize = 0;
return true;
}
int originalSize = input.Length;
SplitterInParameterHeader header = SpanIOHelper.Read(ref input);
if (header.IsMagicValid())
{
ClearAllNewConnectionFlag();
UpdateState(ref header, ref input);
UpdateData(ref header, ref input);
consumedSize = BitUtils.AlignUp(originalSize - input.Length, 0x10);
return true;
}
else
{
consumedSize = 0;
return false;
}
}
///
/// Get a reference to a at the given .
///
/// The index to use.
/// A reference to a at the given .
public ref SplitterState GetState(int id)
{
return ref SpanIOHelper.GetFromMemory(_splitters, id, (uint)_splitters.Length);
}
///
/// Get a reference to a at the given .
///
/// The index to use.
/// A reference to a at the given .
public ref SplitterDestination GetDestination(int id)
{
return ref SpanIOHelper.GetFromMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
}
///
/// Get a at the given .
///
/// The index to use.
/// A at the given .
public Memory GetDestinationMemory(int id)
{
return SpanIOHelper.GetMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
}
///
/// Get a in the at and pass to .
///
/// The index to use to get the .
/// The index of the .
/// A .
public Span GetDestination(int id, int destinationId)
{
ref SplitterState splitter = ref GetState(id);
return splitter.GetData(destinationId);
}
///
/// Return true if the audio renderer has any splitters.
///
/// True if the audio renderer has any splitters.
public bool UsingSplitter()
{
return !_splitters.IsEmpty && !_splitterDestinations.IsEmpty;
}
///
/// Update the internal state of all splitters.
///
public void UpdateInternalState()
{
foreach (ref SplitterState splitter in _splitters.Span)
{
splitter.UpdateInternalState();
}
}
}
}