amadeus: Update to REV10 (#2654)

* amadeus: Update to REV10

This implements all the changes made with REV10 on 13.0.0.

* Address Ack's comment

* Address gdkchan's comment
This commit is contained in:
Mary 2021-09-19 12:29:19 +02:00 committed by GitHub
parent fe9d5a1981
commit e17eb7bfaf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 923 additions and 96 deletions

View file

@ -360,6 +360,9 @@ namespace Ryujinx.Audio.Renderer.Server
case 3:
_commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion3(_sampleCount, _mixBufferCount);
break;
case 4:
_commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion4(_sampleCount, _mixBufferCount);
break;
default:
throw new NotImplementedException($"Unsupported processing time estimator version {_behaviourContext.GetCommandProcessingTimeEstimatorVersion()}.");
}

View file

@ -97,10 +97,20 @@ namespace Ryujinx.Audio.Renderer.Server
/// <remarks>This was added in system update 12.0.0</remarks>
public const int Revision9 = 9 << 24;
/// <summary>
/// REV10:
/// Added Bluetooth audio device support and removed the unused "GetAudioSystemMasterVolumeSetting" audio device API.
/// A new effect was added: Capture. This effect allows the client side to capture audio buffers of a mix.
/// A new command was added for double biquad filters on voices. This is implemented using a direct form 1 (instead of the usual direct form 2).
/// A new version of the command estimator was added to support the new commands.
/// </summary>
/// <remarks>This was added in system update 13.0.0</remarks>
public const int Revision10 = 10 << 24;
/// <summary>
/// Last revision supported by the implementation.
/// </summary>
public const int LastRevision = Revision9;
public const int LastRevision = Revision10;
/// <summary>
/// Target revision magic supported by the implementation.
@ -347,12 +357,26 @@ namespace Ryujinx.Audio.Renderer.Server
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision9);
}
/// <summary>
/// Check if the audio renderer should use an optimized Biquad Filter (Direct Form 1) in case of two biquad filters are defined on a voice.
/// </summary>
/// <returns>True if the audio renderer should use the optimization.</returns>
public bool IsBiquadFilterGroupedOptimizationSupported()
{
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10);
}
/// <summary>
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
/// </summary>
/// <returns>The version of the <see cref="ICommandProcessingTimeEstimator"/>.</returns>
public int GetCommandProcessingTimeEstimatorVersion()
{
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10))
{
return 4;
}
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8))
{
return 3;

View file

@ -24,6 +24,7 @@ using Ryujinx.Audio.Renderer.Server.Performance;
using Ryujinx.Audio.Renderer.Server.Sink;
using Ryujinx.Audio.Renderer.Server.Upsampler;
using Ryujinx.Audio.Renderer.Server.Voice;
using Ryujinx.Common.Memory;
using System;
using CpuAddress = System.UInt64;
@ -220,6 +221,25 @@ namespace Ryujinx.Audio.Renderer.Server
AddCommand(command);
}
/// <summary>
/// Create a new <see cref="GroupedBiquadFilterCommand"/>.
/// </summary>
/// <param name="baseIndex">The base index of the input and output buffer.</param>
/// <param name="filters">The biquad filter parameters.</param>
/// <param name="biquadFilterStatesMemory">The biquad states.</param>
/// <param name="inputBufferOffset">The input buffer offset.</param>
/// <param name="outputBufferOffset">The output buffer offset.</param>
/// <param name="isInitialized">Set to true if the biquad filter state is initialized.</param>
/// <param name="nodeId">The node id associated to this command.</param>
public void GenerateGroupedBiquadFilter(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
{
GroupedBiquadFilterCommand command = new GroupedBiquadFilterCommand(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
AddCommand(command);
}
/// <summary>
/// Generate a new <see cref="MixRampGroupedCommand"/>.
/// </summary>
@ -440,6 +460,30 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
/// <summary>
/// Generate a new <see cref="CaptureBufferCommand"/>.
/// </summary>
/// <param name="bufferOffset">The target buffer offset.</param>
/// <param name="inputBufferOffset">The input buffer offset.</param>
/// <param name="sendBufferInfo">The capture state.</param>
/// <param name="isEnabled">Set to true if the effect should be active.</param>
/// <param name="countMax">The limit of the circular buffer.</param>
/// <param name="outputBuffer">The guest address of the output buffer.</param>
/// <param name="updateCount">The count to add on the offset after write operations.</param>
/// <param name="writeOffset">The write offset.</param>
/// <param name="nodeId">The node id associated to this command.</param>
public void GenerateCaptureEffect(uint bufferOffset, byte inputBufferOffset, ulong sendBufferInfo, bool isEnabled, uint countMax, CpuAddress outputBuffer, uint updateCount, uint writeOffset, int nodeId)
{
if (sendBufferInfo != 0)
{
CaptureBufferCommand command = new CaptureBufferCommand(bufferOffset, inputBufferOffset, sendBufferInfo, isEnabled, countMax, outputBuffer, updateCount, writeOffset, nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
AddCommand(command);
}
}
/// <summary>
/// Generate a new <see cref="VolumeCommand"/>.
/// </summary>

View file

@ -151,23 +151,35 @@ namespace Ryujinx.Audio.Renderer.Server
private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory<VoiceUpdateState> state, int baseIndex, int bufferOffset, int nodeId)
{
for (int i = 0; i < voiceState.BiquadFilters.Length; i++)
bool supportsOptimizedPath = _rendererContext.BehaviourContext.IsBiquadFilterGroupedOptimizationSupported();
if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable)
{
ref BiquadFilterParameter filter = ref voiceState.BiquadFilters[i];
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state).Slice(VoiceUpdateState.BiquadStateOffset, VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount);
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
if (filter.Enable)
_commandBuffer.GenerateGroupedBiquadFilter(baseIndex, voiceState.BiquadFilters.ToSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId);
}
else
{
for (int i = 0; i < voiceState.BiquadFilters.Length; i++)
{
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state).Slice(VoiceUpdateState.BiquadStateOffset, VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount);
ref BiquadFilterParameter filter = ref voiceState.BiquadFilters[i];
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
if (filter.Enable)
{
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state).Slice(VoiceUpdateState.BiquadStateOffset, VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount);
_commandBuffer.GenerateBiquadFilter(baseIndex,
ref filter,
stateMemory.Slice(i, 1),
bufferOffset,
bufferOffset,
!voiceState.BiquadFilterNeedInitialization[i],
nodeId);
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
_commandBuffer.GenerateBiquadFilter(baseIndex,
ref filter,
stateMemory.Slice(i, 1),
bufferOffset,
bufferOffset,
!voiceState.BiquadFilterNeedInitialization[i],
nodeId);
}
}
}
}
@ -443,7 +455,7 @@ namespace Ryujinx.Audio.Renderer.Server
uint updateCount;
if ((channelIndex - 1) != 0)
if (channelIndex != 1)
{
updateCount = 0;
}
@ -556,6 +568,52 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
private void GenerateCaptureEffect(uint bufferOffset, CaptureBufferEffect effect, int nodeId)
{
Debug.Assert(effect.Type == EffectType.CaptureBuffer);
if (effect.IsEnabled)
{
effect.GetWorkBuffer(0);
}
if (effect.State.SendBufferInfoBase != 0)
{
int i = 0;
uint writeOffset = 0;
for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--)
{
uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount;
uint updateCount;
if (channelIndex != 1)
{
updateCount = 0;
}
else
{
updateCount = newUpdateCount;
}
_commandBuffer.GenerateCaptureEffect(bufferOffset,
effect.Parameter.Input[i],
effect.State.SendBufferInfo,
effect.IsEnabled,
effect.Parameter.BufferStorageSize,
effect.State.SendBufferInfoBase,
updateCount,
writeOffset,
nodeId);
writeOffset = newUpdateCount;
i++;
}
}
}
private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
{
int nodeId = mix.NodeId;
@ -597,6 +655,9 @@ namespace Ryujinx.Audio.Renderer.Server
case EffectType.Limiter:
GenerateLimiterEffect(mix.BufferOffset, (LimiterEffect)effect, nodeId, effectId);
break;
case EffectType.CaptureBuffer:
GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
break;
default:
throw new NotImplementedException($"Unsupported effect type {effect.Type}");
}

View file

@ -186,5 +186,15 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
public uint Estimate(GroupedBiquadFilterCommand command)
{
return 0;
}
public uint Estimate(CaptureBufferCommand command)
{
return 0;
}
}
}

View file

@ -550,5 +550,15 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
public uint Estimate(GroupedBiquadFilterCommand command)
{
return 0;
}
public uint Estimate(CaptureBufferCommand command)
{
return 0;
}
}
}

View file

@ -16,7 +16,6 @@
//
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.Command;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System;
@ -30,8 +29,8 @@ namespace Ryujinx.Audio.Renderer.Server
/// </summary>
public class CommandProcessingTimeEstimatorVersion3 : ICommandProcessingTimeEstimator
{
private uint _sampleCount;
private uint _bufferCount;
protected uint _sampleCount;
protected uint _bufferCount;
public CommandProcessingTimeEstimatorVersion3(uint sampleCount, uint bufferCount)
{
@ -755,5 +754,15 @@ namespace Ryujinx.Audio.Renderer.Server
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
public virtual uint Estimate(GroupedBiquadFilterCommand command)
{
return 0;
}
public virtual uint Estimate(CaptureBufferCommand command)
{
return 0;
}
}
}

View file

@ -0,0 +1,68 @@
//
// Copyright (c) 2019-2021 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 <https://www.gnu.org/licenses/>.
//
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Renderer.Dsp.Command;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System;
using System.Diagnostics;
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
namespace Ryujinx.Audio.Renderer.Server
{
/// <summary>
/// <see cref="ICommandProcessingTimeEstimator"/> version 4. (added with REV10)
/// </summary>
public class CommandProcessingTimeEstimatorVersion4 : CommandProcessingTimeEstimatorVersion3
{
public CommandProcessingTimeEstimatorVersion4(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { }
public override uint Estimate(GroupedBiquadFilterCommand command)
{
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
if (_sampleCount == 160)
{
return (uint)7424.5f;
}
return (uint)9730.4f;
}
public override uint Estimate(CaptureBufferCommand command)
{
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
if (_sampleCount == 160)
{
if (command.Enabled)
{
return (uint)435.2f;
}
return (uint)4261.0f;
}
if (command.Enabled)
{
return (uint)5858.26f;
}
return (uint)435.2f;
}
}
}

View file

@ -23,7 +23,7 @@ using Ryujinx.Audio.Renderer.Server.MemoryPool;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static Ryujinx.Audio.Renderer.Dsp.State.AuxiliaryBufferHeader;
using DspAddress = System.UInt64;
namespace Ryujinx.Audio.Renderer.Server.Effect
@ -73,7 +73,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
if (BufferUnmapped || parameter.IsNew)
{
ulong bufferSize = (ulong)Unsafe.SizeOf<int>() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf<AuxiliaryBufferHeader>() * 2;
ulong bufferSize = (ulong)Unsafe.SizeOf<int>() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf<AuxiliaryBufferHeader>();
bool sendBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], Parameter.SendBufferInfoAddress, bufferSize);
bool returnBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[1], Parameter.ReturnBufferInfoAddress, bufferSize);
@ -85,11 +85,11 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
DspAddress sendDspAddress = WorkBuffers[0].GetReference(false);
DspAddress returnDspAddress = WorkBuffers[1].GetReference(false);
State.SendBufferInfo = sendDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>();
State.SendBufferInfoBase = sendDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>() * 2;
State.SendBufferInfo = sendDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferInfo>();
State.SendBufferInfoBase = sendDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>();
State.ReturnBufferInfo = returnDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>();
State.ReturnBufferInfoBase = returnDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>() * 2;
State.ReturnBufferInfo = returnDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferInfo>();
State.ReturnBufferInfoBase = returnDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>();
}
}
}

View file

@ -277,6 +277,8 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return PerformanceDetailType.Mix;
case EffectType.Limiter:
return PerformanceDetailType.Limiter;
case EffectType.CaptureBuffer:
return PerformanceDetailType.CaptureBuffer;
default:
throw new NotImplementedException($"{Type}");
}

View file

@ -0,0 +1,99 @@
//
// Copyright (c) 2019-2021 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 <https://www.gnu.org/licenses/>.
//
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using Ryujinx.Audio.Renderer.Server.MemoryPool;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using DspAddress = System.UInt64;
namespace Ryujinx.Audio.Renderer.Server.Effect
{
/// <summary>
/// Server state for an capture buffer effect.
/// </summary>
public class CaptureBufferEffect : BaseEffect
{
/// <summary>
/// The capture buffer parameter.
/// </summary>
public AuxiliaryBufferParameter Parameter;
/// <summary>
/// Capture buffer state.
/// </summary>
public AuxiliaryBufferAddresses State;
public override EffectType TargetEffectType => EffectType.CaptureBuffer;
public override DspAddress GetWorkBuffer(int index)
{
return WorkBuffers[index].GetReference(true);
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{
Debug.Assert(IsTypeValid(ref parameter));
UpdateParameterBase(ref parameter);
Parameter = MemoryMarshal.Cast<byte, AuxiliaryBufferParameter>(parameter.SpecificData)[0];
IsEnabled = parameter.IsEnabled;
updateErrorInfo = new BehaviourParameter.ErrorInfo();
if (BufferUnmapped || parameter.IsNew)
{
ulong bufferSize = (ulong)Unsafe.SizeOf<int>() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf<AuxiliaryBufferHeader>();
bool sendBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], Parameter.SendBufferInfoAddress, bufferSize);
BufferUnmapped = sendBufferUnmapped;
if (!BufferUnmapped)
{
DspAddress sendDspAddress = WorkBuffers[0].GetReference(false);
// NOTE: Nintendo directly interact with the CPU side structure in the processing of the DSP command.
State.SendBufferInfo = sendDspAddress;
State.SendBufferInfoBase = sendDspAddress + (ulong)Unsafe.SizeOf<AuxiliaryBufferHeader>();
State.ReturnBufferInfo = 0;
State.ReturnBufferInfoBase = 0;
}
}
}
public override void UpdateForCommandGeneration()
{
UpdateUsageStateForCommandGeneration();
}
}
}

View file

@ -50,5 +50,7 @@ namespace Ryujinx.Audio.Renderer.Server
uint Estimate(UpsampleCommand command);
uint Estimate(LimiterCommandVersion1 command);
uint Estimate(LimiterCommandVersion2 command);
uint Estimate(GroupedBiquadFilterCommand command);
uint Estimate(CaptureBufferCommand command);
}
}

View file

@ -254,6 +254,9 @@ namespace Ryujinx.Audio.Renderer.Server
case EffectType.Limiter:
effect = new LimiterEffect();
break;
case EffectType.CaptureBuffer:
effect = new CaptureBufferEffect();
break;
default:
throw new NotImplementedException($"EffectType {parameter.Type} not implemented!");
}