using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Parameter.Performance; using Ryujinx.Audio.Renderer.Server.Effect; using Ryujinx.Audio.Renderer.Server.MemoryPool; using Ryujinx.Audio.Renderer.Server.Mix; using Ryujinx.Audio.Renderer.Server.Performance; using Ryujinx.Audio.Renderer.Server.Sink; using Ryujinx.Audio.Renderer.Server.Splitter; using Ryujinx.Audio.Renderer.Server.Voice; using Ryujinx.Audio.Renderer.Utils; using Ryujinx.Common.Extensions; using Ryujinx.Common.Logging; using System; using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; namespace Ryujinx.Audio.Renderer.Server { public ref struct StateUpdater { private SequenceReader _inputReader; private readonly ReadOnlyMemory _outputOrigin; private Memory _output; private readonly uint _processHandle; private BehaviourContext _behaviourContext; private readonly ref readonly UpdateDataHeader _inputHeader; private readonly Memory _outputHeader; private readonly ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0]; public StateUpdater(ReadOnlySequence input, Memory output, uint processHandle, BehaviourContext behaviourContext) { _inputReader = new SequenceReader(input); _output = output; _outputOrigin = _output; _processHandle = processHandle; _behaviourContext = behaviourContext; _inputHeader = ref _inputReader.GetRefOrRefToCopy(out _); _outputHeader = SpanMemoryManager.Cast(_output[..Unsafe.SizeOf()]); OutputHeader.Initialize(_behaviourContext.UserRevision); _output = _output[Unsafe.SizeOf()..]; } public ResultCode UpdateBehaviourContext() { ref readonly BehaviourParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); if (!BehaviourContext.CheckValidRevision(parameter.UserRevision) || parameter.UserRevision != _behaviourContext.UserRevision) { return ResultCode.InvalidUpdateInfo; } _behaviourContext.ClearError(); _behaviourContext.UpdateFlags(parameter.Flags); if (_inputHeader.BehaviourSize != Unsafe.SizeOf()) { return ResultCode.InvalidUpdateInfo; } return ResultCode.Success; } public ResultCode UpdateMemoryPools(Span memoryPools) { PoolMapper mapper = new(_processHandle, _behaviourContext.IsMemoryPoolForceMappingEnabled()); if (memoryPools.Length * Unsafe.SizeOf() != _inputHeader.MemoryPoolsSize) { return ResultCode.InvalidUpdateInfo; } foreach (ref MemoryPoolState memoryPool in memoryPools) { ref readonly MemoryPoolInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, in parameter, ref outStatus); if (updateResult != PoolMapper.UpdateResult.Success && updateResult != PoolMapper.UpdateResult.MapError && updateResult != PoolMapper.UpdateResult.UnmapError) { if (updateResult != PoolMapper.UpdateResult.InvalidParameter) { throw new InvalidOperationException($"{updateResult}"); } return ResultCode.InvalidUpdateInfo; } } OutputHeader.MemoryPoolsSize = (uint)(Unsafe.SizeOf() * memoryPools.Length); OutputHeader.TotalSize += OutputHeader.MemoryPoolsSize; return ResultCode.Success; } public ResultCode UpdateVoiceChannelResources(VoiceContext context) { if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.VoiceResourcesSize) { return ResultCode.InvalidUpdateInfo; } for (int i = 0; i < context.GetCount(); i++) { ref readonly VoiceChannelResourceInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref VoiceChannelResource resource = ref context.GetChannelResource(i); resource.Id = parameter.Id; parameter.Mix.AsSpan().CopyTo(resource.Mix.AsSpan()); resource.IsUsed = parameter.IsUsed; } return ResultCode.Success; } public ResultCode UpdateVoices(VoiceContext context, PoolMapper mapper) { if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.VoicesSize) { return ResultCode.InvalidUpdateInfo; } int initialOutputSize = _output.Length; long initialInputConsumed = _inputReader.Consumed; // First make everything not in use. for (int i = 0; i < context.GetCount(); i++) { ref VoiceState state = ref context.GetState(i); state.InUse = false; } Memory[] voiceUpdateStatesArray = ArrayPool>.Shared.Rent(Constants.VoiceChannelCountMax); Span> voiceUpdateStates = voiceUpdateStatesArray.AsSpan(0, Constants.VoiceChannelCountMax); // Start processing for (int i = 0; i < context.GetCount(); i++) { ref readonly VoiceInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); voiceUpdateStates.Fill(Memory.Empty); ref VoiceOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; if (parameter.InUse) { ref VoiceState currentVoiceState = ref context.GetState(i); for (int channelResourceIndex = 0; channelResourceIndex < parameter.ChannelCount; channelResourceIndex++) { int channelId = parameter.ChannelResourceIds[channelResourceIndex]; Debug.Assert(channelId >= 0 && channelId < context.GetCount()); voiceUpdateStates[channelResourceIndex] = context.GetUpdateStateForCpu(channelId); } if (parameter.IsNew) { currentVoiceState.Initialize(); } currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, in parameter, mapper, ref _behaviourContext); if (updateParameterError.ErrorCode != ResultCode.Success) { _behaviourContext.AppendError(ref updateParameterError); } currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, in parameter, voiceUpdateStates, mapper, ref _behaviourContext); foreach (ref ErrorInfo errorInfo in waveBufferUpdateErrorInfos.AsSpan()) { if (errorInfo.ErrorCode != ResultCode.Success) { _behaviourContext.AppendError(ref errorInfo); } } currentVoiceState.WriteOutStatus(ref outStatus, in parameter, voiceUpdateStates); } } ArrayPool>.Shared.Return(voiceUpdateStatesArray); int currentOutputSize = _output.Length; OutputHeader.VoicesSize = (uint)(Unsafe.SizeOf() * context.GetCount()); OutputHeader.TotalSize += OutputHeader.VoicesSize; Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize); _inputReader.SetConsumed(initialInputConsumed + _inputHeader.VoicesSize); return ResultCode.Success; } private static void ResetEffect(ref BaseEffect effect, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { effect.ForceUnmapBuffers(mapper); effect = parameter.Type switch { EffectType.Invalid => new BaseEffect(), EffectType.BufferMix => new BufferMixEffect(), EffectType.AuxiliaryBuffer => new AuxiliaryBufferEffect(), EffectType.Delay => new DelayEffect(), EffectType.Reverb => new ReverbEffect(), EffectType.Reverb3d => new Reverb3dEffect(), EffectType.BiquadFilter => new BiquadFilterEffect(), EffectType.Limiter => new LimiterEffect(), EffectType.CaptureBuffer => new CaptureBufferEffect(), EffectType.Compressor => new CompressorEffect(), _ => throw new NotImplementedException($"EffectType {parameter.Type} not implemented!"), }; } public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, PoolMapper mapper) { if (_behaviourContext.IsEffectInfoVersion2Supported()) { return UpdateEffectsVersion2(context, isAudioRendererActive, mapper); } return UpdateEffectsVersion1(context, isAudioRendererActive, mapper); } public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, PoolMapper mapper) { if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) { return ResultCode.InvalidUpdateInfo; } int initialOutputSize = _output.Length; long initialInputConsumed = _inputReader.Consumed; for (int i = 0; i < context.GetCount(); i++) { ref readonly EffectInParameterVersion2 parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; ref BaseEffect effect = ref context.GetEffect(i); if (!effect.IsTypeValid(in parameter)) { ResetEffect(ref effect, in parameter, mapper); } effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper); if (updateErrorInfo.ErrorCode != ResultCode.Success) { _behaviourContext.AppendError(ref updateErrorInfo); } effect.StoreStatus(ref outStatus, isAudioRendererActive); if (parameter.IsNew) { effect.InitializeResultState(ref context.GetDspState(i)); effect.InitializeResultState(ref context.GetState(i)); } effect.UpdateResultState(ref outStatus.ResultState, ref context.GetState(i)); } int currentOutputSize = _output.Length; OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf() * context.GetCount()); OutputHeader.TotalSize += OutputHeader.EffectsSize; Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); _inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize); return ResultCode.Success; } public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, PoolMapper mapper) { if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) { return ResultCode.InvalidUpdateInfo; } int initialOutputSize = _output.Length; long initialInputConsumed = _inputReader.Consumed; for (int i = 0; i < context.GetCount(); i++) { ref readonly EffectInParameterVersion1 parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref EffectOutStatusVersion1 outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; ref BaseEffect effect = ref context.GetEffect(i); if (!effect.IsTypeValid(in parameter)) { ResetEffect(ref effect, in parameter, mapper); } effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper); if (updateErrorInfo.ErrorCode != ResultCode.Success) { _behaviourContext.AppendError(ref updateErrorInfo); } effect.StoreStatus(ref outStatus, isAudioRendererActive); } int currentOutputSize = _output.Length; OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf() * context.GetCount()); OutputHeader.TotalSize += OutputHeader.EffectsSize; Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); _inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize); return ResultCode.Success; } public ResultCode UpdateSplitter(SplitterContext context) { if (context.Update(ref _inputReader)) { return ResultCode.Success; } return ResultCode.InvalidUpdateInfo; } private static bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, SequenceReader parameters) { uint maxMixStateCount = mixContext.GetCount(); uint totalRequiredMixBufferCount = 0; for (int i = 0; i < inputMixCount; i++) { ref readonly MixParameter parameter = ref parameters.GetRefOrRefToCopy(out _); if (parameter.IsUsed) { if (parameter.DestinationMixId != Constants.UnusedMixId && parameter.DestinationMixId > maxMixStateCount && parameter.MixId != Constants.FinalMixId) { return true; } totalRequiredMixBufferCount += parameter.BufferCount; } } return totalRequiredMixBufferCount > mixBufferCount; } public ResultCode UpdateMixes(MixContext mixContext, uint mixBufferCount, EffectContext effectContext, SplitterContext splitterContext) { uint mixCount; uint inputMixSize; uint inputSize = 0; if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) { ref readonly MixInParameterDirtyOnlyUpdate parameter = ref _inputReader.GetRefOrRefToCopy(out _); mixCount = parameter.MixCount; inputSize += (uint)Unsafe.SizeOf(); } else { mixCount = mixContext.GetCount(); } inputMixSize = mixCount * (uint)Unsafe.SizeOf(); inputSize += inputMixSize; if (inputSize != _inputHeader.MixesSize) { return ResultCode.InvalidUpdateInfo; } long initialInputConsumed = _inputReader.Consumed; int parameterCount = (int)inputMixSize / Unsafe.SizeOf(); if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, _inputReader)) { return ResultCode.InvalidUpdateInfo; } bool isMixContextDirty = false; for (int i = 0; i < parameterCount; i++) { ref readonly MixParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); int mixId = i; if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) { mixId = parameter.MixId; } ref MixState mix = ref mixContext.GetState(mixId); if (parameter.IsUsed != mix.IsUsed) { mix.IsUsed = parameter.IsUsed; if (parameter.IsUsed) { mix.ClearEffectProcessingOrder(); } isMixContextDirty = true; } if (mix.IsUsed) { isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, in parameter, effectContext, splitterContext, _behaviourContext); } } if (isMixContextDirty) { if (_behaviourContext.IsSplitterSupported() && splitterContext.UsingSplitter()) { if (!mixContext.Sort(splitterContext)) { return ResultCode.InvalidMixSorting; } } else { mixContext.Sort(); } } _inputReader.SetConsumed(initialInputConsumed + inputMixSize); return ResultCode.Success; } private static void ResetSink(ref BaseSink sink, in SinkInParameter parameter) { sink.CleanUp(); sink = parameter.Type switch { SinkType.Invalid => new BaseSink(), SinkType.CircularBuffer => new CircularBufferSink(), SinkType.Device => new DeviceSink(), _ => throw new NotImplementedException($"SinkType {parameter.Type} not implemented!"), }; } public ResultCode UpdateSinks(SinkContext context, PoolMapper mapper) { if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.SinksSize) { return ResultCode.InvalidUpdateInfo; } int initialOutputSize = _output.Length; long initialInputConsumed = _inputReader.Consumed; for (int i = 0; i < context.GetCount(); i++) { ref readonly SinkInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; ref BaseSink sink = ref context.GetSink(i); if (!sink.IsTypeValid(in parameter)) { ResetSink(ref sink, in parameter); } sink.Update(out ErrorInfo updateErrorInfo, in parameter, ref outStatus, mapper); if (updateErrorInfo.ErrorCode != ResultCode.Success) { _behaviourContext.AppendError(ref updateErrorInfo); } } int currentOutputSize = _output.Length; OutputHeader.SinksSize = (uint)(Unsafe.SizeOf() * context.GetCount()); OutputHeader.TotalSize += OutputHeader.SinksSize; Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize); _inputReader.SetConsumed(initialInputConsumed + _inputHeader.SinksSize); return ResultCode.Success; } public ResultCode UpdatePerformanceBuffer(PerformanceManager manager, Span performanceOutput) { if (Unsafe.SizeOf() != _inputHeader.PerformanceBufferSize) { return ResultCode.InvalidUpdateInfo; } ref readonly PerformanceInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref PerformanceOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; if (manager != null) { outStatus.HistorySize = manager.CopyHistories(performanceOutput); manager.SetTargetNodeId(parameter.TargetNodeId); } else { outStatus.HistorySize = 0; } OutputHeader.PerformanceBufferSize = (uint)Unsafe.SizeOf(); OutputHeader.TotalSize += OutputHeader.PerformanceBufferSize; return ResultCode.Success; } public ResultCode UpdateErrorInfo() { ref BehaviourErrorInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; _behaviourContext.CopyErrorInfo(outStatus.ErrorInfos.AsSpan(), out outStatus.ErrorInfosCount); OutputHeader.BehaviourSize = (uint)Unsafe.SizeOf(); OutputHeader.TotalSize += OutputHeader.BehaviourSize; return ResultCode.Success; } public ResultCode UpdateRendererInfo(ulong elapsedFrameCount) { ref RendererInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; outStatus.ElapsedFrameCount = elapsedFrameCount; OutputHeader.RenderInfoSize = (uint)Unsafe.SizeOf(); OutputHeader.TotalSize += OutputHeader.RenderInfoSize; return ResultCode.Success; } public readonly ResultCode CheckConsumedSize() { long consumedInputSize = _inputReader.Consumed; int consumedOutputSize = _outputOrigin.Length - _output.Length; if (consumedInputSize != _inputHeader.TotalSize) { Logger.Error?.Print(LogClass.AudioRenderer, $"Consumed input size mismatch (got {consumedInputSize} expected {_inputHeader.TotalSize})"); return ResultCode.InvalidUpdateInfo; } if (consumedOutputSize != OutputHeader.TotalSize) { Logger.Error?.Print(LogClass.AudioRenderer, $"Consumed output size mismatch (got {consumedOutputSize} expected {OutputHeader.TotalSize})"); return ResultCode.InvalidUpdateInfo; } return ResultCode.Success; } } }