//
// 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.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.Logging;
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
namespace Ryujinx.Audio.Renderer.Server
{
public class StateUpdater
{
private readonly ReadOnlyMemory _inputOrigin;
private ReadOnlyMemory _outputOrigin;
private ReadOnlyMemory _input;
private Memory _output;
private uint _processHandle;
private BehaviourContext _behaviourContext;
private UpdateDataHeader _inputHeader;
private Memory _outputHeader;
private ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0];
public StateUpdater(ReadOnlyMemory input, Memory output, uint processHandle, BehaviourContext behaviourContext)
{
_input = input;
_inputOrigin = _input;
_output = output;
_outputOrigin = _output;
_processHandle = processHandle;
_behaviourContext = behaviourContext;
_inputHeader = SpanIOHelper.Read(ref _input);
_outputHeader = SpanMemoryManager.Cast(_output.Slice(0, Unsafe.SizeOf()));
OutputHeader.Initialize(_behaviourContext.UserRevision);
_output = _output.Slice(Unsafe.SizeOf());
}
public ResultCode UpdateBehaviourContext()
{
BehaviourParameter parameter = SpanIOHelper.Read(ref _input);
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 PoolMapper(_processHandle, _behaviourContext.IsMemoryPoolForceMappingEnabled());
if (memoryPools.Length * Unsafe.SizeOf() != _inputHeader.MemoryPoolsSize)
{
return ResultCode.InvalidUpdateInfo;
}
foreach (ref MemoryPoolState memoryPool in memoryPools)
{
MemoryPoolInParameter parameter = SpanIOHelper.Read(ref _input);
ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0];
PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, ref 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++)
{
VoiceChannelResourceInParameter parameter = SpanIOHelper.Read(ref _input);
ref VoiceChannelResource resource = ref context.GetChannelResource(i);
resource.Id = parameter.Id;
parameter.Mix.ToSpan().CopyTo(resource.Mix.ToSpan());
resource.IsUsed = parameter.IsUsed;
}
return ResultCode.Success;
}
public ResultCode UpdateVoices(VoiceContext context, Memory memoryPools)
{
if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.VoicesSize)
{
return ResultCode.InvalidUpdateInfo;
}
int initialOutputSize = _output.Length;
ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Slice(0, (int)_inputHeader.VoicesSize).Span);
_input = _input.Slice((int)_inputHeader.VoicesSize);
PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
// First make everything not in use.
for (int i = 0; i < context.GetCount(); i++)
{
ref VoiceState state = ref context.GetState(i);
state.InUse = false;
}
// Start processing
for (int i = 0; i < context.GetCount(); i++)
{
VoiceInParameter parameter = parameters[i];
Memory[] voiceUpdateStates = new Memory[RendererConstants.VoiceChannelCountMax];
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, ref parameter, ref mapper, ref _behaviourContext);
if (updateParameterError.ErrorCode != ResultCode.Success)
{
_behaviourContext.AppendError(ref updateParameterError);
}
currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, ref parameter, voiceUpdateStates, ref mapper, ref _behaviourContext);
foreach (ref ErrorInfo errorInfo in waveBufferUpdateErrorInfos.AsSpan())
{
if (errorInfo.ErrorCode != ResultCode.Success)
{
_behaviourContext.AppendError(ref errorInfo);
}
}
currentVoiceState.WriteOutStatus(ref outStatus, ref parameter, voiceUpdateStates);
}
}
int currentOutputSize = _output.Length;
OutputHeader.VoicesSize = (uint)(Unsafe.SizeOf() * context.GetCount());
OutputHeader.TotalSize += OutputHeader.VoicesSize;
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize);
return ResultCode.Success;
}
private static void ResetEffect(ref BaseEffect effect, ref EffectInParameter parameter, PoolMapper mapper)
{
effect.ForceUnmapBuffers(mapper);
switch (parameter.Type)
{
case EffectType.Invalid:
effect = new BaseEffect();
break;
case EffectType.BufferMix:
effect = new BufferMixEffect();
break;
case EffectType.AuxiliaryBuffer:
effect = new AuxiliaryBufferEffect();
break;
case EffectType.Delay:
effect = new DelayEffect();
break;
case EffectType.Reverb:
effect = new ReverbEffect();
break;
case EffectType.Reverb3d:
effect = new Reverb3dEffect();
break;
case EffectType.BiquadFilter:
effect = new BiquadFilterEffect();
break;
default:
throw new NotImplementedException($"EffectType {parameter.Type} not implemented!");
}
}
public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory memoryPools)
{
if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize)
{
return ResultCode.InvalidUpdateInfo;
}
int initialOutputSize = _output.Length;
ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Slice(0, (int)_inputHeader.EffectsSize).Span);
_input = _input.Slice((int)_inputHeader.EffectsSize);
PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
for (int i = 0; i < context.GetCount(); i++)
{
EffectInParameter parameter = parameters[i];
ref EffectOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0];
ref BaseEffect effect = ref context.GetEffect(i);
if (!effect.IsTypeValid(ref parameter))
{
ResetEffect(ref effect, ref parameter, mapper);
}
effect.Update(out ErrorInfo updateErrorInfo, ref 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);
return ResultCode.Success;
}
public ResultCode UpdateSplitter(SplitterContext context)
{
if (context.Update(_input.Span, out int consumedSize))
{
_input = _input.Slice(consumedSize);
return ResultCode.Success;
}
else
{
return ResultCode.InvalidUpdateInfo;
}
}
private bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, ReadOnlySpan parameters)
{
uint maxMixStateCount = mixContext.GetCount();
uint totalRequiredMixBufferCount = 0;
for (int i = 0; i < inputMixCount; i++)
{
if (parameters[i].IsUsed)
{
if (parameters[i].DestinationMixId != RendererConstants.UnusedMixId &&
parameters[i].DestinationMixId > maxMixStateCount &&
parameters[i].MixId != RendererConstants.FinalMixId)
{
return true;
}
totalRequiredMixBufferCount += parameters[i].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())
{
MixInParameterDirtyOnlyUpdate parameter = MemoryMarshal.Cast(_input.Span)[0];
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;
}
if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
{
_input = _input.Slice(Unsafe.SizeOf());
}
ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Span.Slice(0, (int)inputMixSize));
_input = _input.Slice((int)inputMixSize);
if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, parameters))
{
return ResultCode.InvalidUpdateInfo;
}
bool isMixContextDirty = false;
for (int i = 0; i < parameters.Length; i++)
{
MixParameter parameter = parameters[i];
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, ref parameter, effectContext, splitterContext, _behaviourContext);
}
}
if (isMixContextDirty)
{
if (_behaviourContext.IsSplitterSupported() && splitterContext.UsingSplitter())
{
if (!mixContext.Sort(splitterContext))
{
return ResultCode.InvalidMixSorting;
}
}
else
{
mixContext.Sort();
}
}
return ResultCode.Success;
}
private static void ResetSink(ref BaseSink sink, ref SinkInParameter parameter)
{
sink.CleanUp();
switch (parameter.Type)
{
case SinkType.Invalid:
sink = new BaseSink();
break;
case SinkType.CircularBuffer:
sink = new CircularBufferSink();
break;
case SinkType.Device:
sink = new DeviceSink();
break;
default:
throw new NotImplementedException($"SinkType {parameter.Type} not implemented!");
}
}
public ResultCode UpdateSinks(SinkContext context, Memory memoryPools)
{
PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.SinksSize)
{
return ResultCode.InvalidUpdateInfo;
}
int initialOutputSize = _output.Length;
ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Slice(0, (int)_inputHeader.SinksSize).Span);
_input = _input.Slice((int)_inputHeader.SinksSize);
for (int i = 0; i < context.GetCount(); i++)
{
SinkInParameter parameter = parameters[i];
ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0];
ref BaseSink sink = ref context.GetSink(i);
if (!sink.IsTypeValid(ref parameter))
{
ResetSink(ref sink, ref parameter);
}
sink.Update(out ErrorInfo updateErrorInfo, ref 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);
return ResultCode.Success;
}
public ResultCode UpdatePerformanceBuffer(PerformanceManager manager, Span performanceOutput)
{
if (Unsafe.SizeOf() != _inputHeader.PerformanceBufferSize)
{
return ResultCode.InvalidUpdateInfo;
}
PerformanceInParameter parameter = SpanIOHelper.Read(ref _input);
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.ToSpan(), 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 ResultCode CheckConsumedSize()
{
int consumedInputSize = _inputOrigin.Length - _input.Length;
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;
}
}
}