using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Utils;
using Ryujinx.Common;
using Ryujinx.Common.Extensions;
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
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;
}
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(in SplitterInParameterHeader inputHeader, ref SequenceReader input)
{
for (int i = 0; i < inputHeader.SplitterCount; i++)
{
ref readonly SplitterInParameter parameter = ref input.GetRefOrRefToCopy(out _);
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, in parameter, ref input);
}
// NOTE: there are 12 bytes of unused/unknown data after the destination IDs array.
input.Advance(0xC);
}
else
{
input.Rewind(Unsafe.SizeOf());
break;
}
}
}
///
/// Update one or multiple from user parameters.
///
/// The splitter header.
/// The raw data after the splitter header.
private void UpdateData(in SplitterInParameterHeader inputHeader, ref SequenceReader input)
{
for (int i = 0; i < inputHeader.SplitterDestinationCount; i++)
{
ref readonly SplitterDestinationInParameter parameter = ref input.GetRefOrRefToCopy(out _);
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);
}
}
else
{
input.Rewind(Unsafe.SizeOf());
break;
}
}
}
///
/// Update splitter from user parameters.
///
/// The input raw user data.
/// Return true if the update was successful.
public bool Update(ref SequenceReader input)
{
if (_splitterDestinations.IsEmpty || _splitters.IsEmpty)
{
return true;
}
ref readonly SplitterInParameterHeader header = ref input.GetRefOrRefToCopy(out _);
if (header.IsMagicValid())
{
ClearAllNewConnectionFlag();
UpdateState(in header, ref input);
UpdateData(in header, ref input);
input.SetConsumed(BitUtils.AlignUp(input.Consumed, 0x10));
return true;
}
else
{
input.Rewind(Unsafe.SizeOf());
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();
}
}
}
}