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(); } } } }