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