change IAudioRenderer.RequestUpdate() to take a ReadOnlySequence<byte>, enabling zero-copy audio rendering
This commit is contained in:
parent
a8633ec737
commit
b67963dd89
27 changed files with 758 additions and 207 deletions
|
@ -41,7 +41,7 @@ namespace Ryujinx.Audio.Backends.Common
|
|||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (size > _size)
|
||||
|
@ -79,7 +79,7 @@ namespace Ryujinx.Audio.Backends.Common
|
|||
}
|
||||
|
||||
_bufferOwner.Dispose();
|
||||
|
||||
|
||||
_bufferOwner = newBufferOwner;
|
||||
_buffer = newBuffer;
|
||||
_headOffset = 0;
|
||||
|
@ -130,7 +130,7 @@ namespace Ryujinx.Audio.Backends.Common
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (count > _size)
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Common
|
|||
public ulong Flags;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an error during <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.ReadOnlyMemory{byte})"/>.
|
||||
/// Represents an error during <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.Buffers.ReadOnlySequence{byte})"/>.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct ErrorInfo
|
||||
|
|
|
@ -4,7 +4,7 @@ using System.Runtime.CompilerServices;
|
|||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Update data header used for input and output of <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.ReadOnlyMemory{byte})"/>.
|
||||
/// Update data header used for input and output of <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.Buffers.ReadOnlySequence{byte})"/>.
|
||||
/// </summary>
|
||||
public struct UpdateDataHeader
|
||||
{
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
|||
/// <summary>
|
||||
/// Output information for behaviour.
|
||||
/// </summary>
|
||||
/// <remarks>This is used to report errors to the user during <see cref="Server.AudioRenderSystem.Update(Memory{byte}, Memory{byte}, ReadOnlyMemory{byte})"/> processing.</remarks>
|
||||
/// <remarks>This is used to report errors to the user during <see cref="Server.AudioRenderSystem.Update(Memory{byte}, Memory{byte}, System.Buffers.ReadOnlySequence{byte})"/> processing.</remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct BehaviourErrorInfoOutStatus
|
||||
{
|
||||
|
|
|
@ -386,7 +386,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
}
|
||||
|
||||
public ResultCode Update(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input)
|
||||
public ResultCode Update(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlySequence<byte> input)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
|
@ -419,14 +419,16 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateVoices(_voiceContext, _memoryPools);
|
||||
PoolMapper poolMapper = new PoolMapper(_processHandle, _memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
|
||||
result = stateUpdater.UpdateVoices(_voiceContext, poolMapper);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateEffects(_effectContext, _isActive, _memoryPools);
|
||||
result = stateUpdater.UpdateEffects(_effectContext, _isActive, poolMapper);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
|
@ -450,7 +452,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateSinks(_sinkContext, _memoryPools);
|
||||
result = stateUpdater.UpdateSinks(_sinkContext, poolMapper);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
||||
|
||||
|
@ -273,7 +274,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer should trust the user destination count in <see cref="Splitter.SplitterState.Update(Splitter.SplitterContext, ref Parameter.SplitterInParameter, ReadOnlySpan{byte})"/>.
|
||||
/// Check if the audio renderer should trust the user destination count in <see cref="Renderer.Server.Splitter.SplitterState.Update(Renderer.Server.Splitter.SplitterContext,Renderer.Parameter.SplitterInParameter,SequenceReader{byte})"/>.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer should trust the user destination count.</returns>
|
||||
public bool IsSplitterBugFixed()
|
||||
|
|
|
@ -33,21 +33,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
return WorkBuffers[index].GetReference(true);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
Parameter = MemoryMarshal.Cast<byte, AuxiliaryBufferParameter>(parameter.SpecificData)[0];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
|
|
@ -81,7 +81,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <returns>Returns true if the <see cref="EffectType"/> sent by the user matches the internal <see cref="EffectType"/>.</returns>
|
||||
public bool IsTypeValid<T>(ref T parameter) where T : unmanaged, IEffectInParameter
|
||||
public bool IsTypeValid<T>(in T parameter) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
return parameter.Type == TargetEffectType;
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
/// Update the internal common parameters from a user parameter.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
protected void UpdateParameterBase<T>(ref T parameter) where T : unmanaged, IEffectInParameter
|
||||
protected void UpdateParameterBase<T>(in T parameter) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
MixId = parameter.MixId;
|
||||
ProcessingOrder = parameter.ProcessingOrder;
|
||||
|
@ -139,7 +139,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
/// <summary>
|
||||
/// Initialize the given <paramref name="state"/> result state.
|
||||
/// </summary>
|
||||
/// <param name="state">The state to initalize</param>
|
||||
/// <param name="state">The state to initialize</param>
|
||||
public virtual void InitializeResultState(ref EffectResultState state) { }
|
||||
|
||||
/// <summary>
|
||||
|
@ -155,9 +155,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
/// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="mapper">The mapper to use.</param>
|
||||
public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public virtual void Update(out ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
updateErrorInfo = new ErrorInfo();
|
||||
}
|
||||
|
@ -168,9 +168,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
/// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="mapper">The mapper to use.</param>
|
||||
public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public virtual void Update(out ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
updateErrorInfo = new ErrorInfo();
|
||||
}
|
||||
|
|
|
@ -35,21 +35,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
|
||||
public override EffectType TargetEffectType => EffectType.BiquadFilter;
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
Parameter = MemoryMarshal.Cast<byte, BiquadFilterEffectParameter>(parameter.SpecificData)[0];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
|
|
@ -19,21 +19,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
|
||||
public override EffectType TargetEffectType => EffectType.BufferMix;
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
Parameter = MemoryMarshal.Cast<byte, BufferMixParameter>(parameter.SpecificData)[0];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
|
|
@ -32,21 +32,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
return WorkBuffers[index].GetReference(true);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
Parameter = MemoryMarshal.Cast<byte, AuxiliaryBufferParameter>(parameter.SpecificData)[0];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
|
|
@ -39,17 +39,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
return GetSingleBuffer();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
// Nintendo doesn't do anything here but we still require updateErrorInfo to be initialised.
|
||||
updateErrorInfo = new BehaviourParameter.ErrorInfo();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
Parameter = MemoryMarshal.Cast<byte, CompressorParameter>(parameter.SpecificData)[0];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
|
|
@ -37,19 +37,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
return GetSingleBuffer();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
ref DelayParameter delayParameter = ref MemoryMarshal.Cast<byte, DelayParameter>(parameter.SpecificData)[0];
|
||||
|
||||
|
@ -57,7 +57,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
|
||||
if (delayParameter.IsChannelCountMaxValid())
|
||||
{
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
UsageState oldParameterStatus = Parameter.Status;
|
||||
|
||||
|
|
|
@ -39,25 +39,25 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
return GetSingleBuffer();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
ref LimiterParameter limiterParameter = ref MemoryMarshal.Cast<byte, LimiterParameter>(parameter.SpecificData)[0];
|
||||
|
||||
updateErrorInfo = new BehaviourParameter.ErrorInfo();
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
Parameter = limiterParameter;
|
||||
|
||||
|
|
|
@ -36,19 +36,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
return GetSingleBuffer();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
ref Reverb3dParameter reverbParameter = ref MemoryMarshal.Cast<byte, Reverb3dParameter>(parameter.SpecificData)[0];
|
||||
|
||||
|
@ -56,7 +56,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
|
||||
if (reverbParameter.IsChannelCountMaxValid())
|
||||
{
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
UsageState oldParameterStatus = Parameter.ParameterStatus;
|
||||
|
||||
|
|
|
@ -39,19 +39,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
return GetSingleBuffer();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
ref ReverbParameter reverbParameter = ref MemoryMarshal.Cast<byte, ReverbParameter>(parameter.SpecificData)[0];
|
||||
|
||||
|
@ -59,7 +59,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
|
||||
if (reverbParameter.IsChannelCountMaxValid())
|
||||
{
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
UsageState oldParameterStatus = Parameter.Status;
|
||||
|
||||
|
|
|
@ -249,7 +249,7 @@ namespace Ryujinx.Audio.Renderer.Server.MemoryPool
|
|||
/// <param name="inParameter">Input user parameter.</param>
|
||||
/// <param name="outStatus">Output user parameter.</param>
|
||||
/// <returns>Returns the <see cref="UpdateResult"/> of the operations performed.</returns>
|
||||
public UpdateResult Update(ref MemoryPoolState memoryPool, ref MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus)
|
||||
public UpdateResult Update(ref MemoryPoolState memoryPool, in MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus)
|
||||
{
|
||||
MemoryPoolUserState inputState = inParameter.State;
|
||||
|
||||
|
|
|
@ -195,7 +195,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
|
|||
/// <param name="parameter">The input parameter of the mix.</param>
|
||||
/// <param name="splitterContext">The splitter context.</param>
|
||||
/// <returns>Return true, new connections were done on the adjacency matrix.</returns>
|
||||
private bool UpdateConnection(EdgeMatrix edgeMatrix, ref MixParameter parameter, ref SplitterContext splitterContext)
|
||||
private bool UpdateConnection(EdgeMatrix edgeMatrix, in MixParameter parameter, ref SplitterContext splitterContext)
|
||||
{
|
||||
bool hasNewConnections;
|
||||
|
||||
|
@ -259,7 +259,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
|
|||
/// <param name="splitterContext">The splitter context.</param>
|
||||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
/// <returns>Return true if the mix was changed.</returns>
|
||||
public bool Update(EdgeMatrix edgeMatrix, ref MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext)
|
||||
public bool Update(EdgeMatrix edgeMatrix, in MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext)
|
||||
{
|
||||
bool isDirty;
|
||||
|
||||
|
@ -273,7 +273,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
|
|||
|
||||
if (behaviourContext.IsSplitterSupported())
|
||||
{
|
||||
isDirty = UpdateConnection(edgeMatrix, ref parameter, ref splitterContext);
|
||||
isDirty = UpdateConnection(edgeMatrix, in parameter, ref splitterContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -59,7 +59,7 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
|
|||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <returns>Return true, if the <see cref="SinkType"/> sent by the user match the internal <see cref="SinkType"/>.</returns>
|
||||
public bool IsTypeValid(ref SinkInParameter parameter)
|
||||
public bool IsTypeValid(in SinkInParameter parameter)
|
||||
{
|
||||
return parameter.Type == TargetSinkType;
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
|
|||
/// Update the internal common parameters from user parameter.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
protected void UpdateStandardParameter(ref SinkInParameter parameter)
|
||||
protected void UpdateStandardParameter(in SinkInParameter parameter)
|
||||
{
|
||||
if (IsUsed != parameter.IsUsed)
|
||||
{
|
||||
|
@ -92,9 +92,9 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
|
|||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="outStatus">The user output status.</param>
|
||||
/// <param name="mapper">The mapper to use.</param>
|
||||
public virtual void Update(out ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
|
||||
public virtual void Update(out ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
errorInfo = new ErrorInfo();
|
||||
}
|
||||
|
|
|
@ -44,18 +44,18 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
|
|||
|
||||
public override SinkType TargetSinkType => SinkType.CircularBuffer;
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
|
||||
{
|
||||
errorInfo = new BehaviourParameter.ErrorInfo();
|
||||
outStatus = new SinkOutStatus();
|
||||
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
ref CircularBufferParameter inputDeviceParameter = ref MemoryMarshal.Cast<byte, CircularBufferParameter>(parameter.SpecificData)[0];
|
||||
|
||||
if (parameter.IsUsed != IsUsed || ShouldSkip)
|
||||
{
|
||||
UpdateStandardParameter(ref parameter);
|
||||
UpdateStandardParameter(in parameter);
|
||||
|
||||
if (parameter.IsUsed)
|
||||
{
|
||||
|
|
|
@ -49,15 +49,15 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
|
|||
|
||||
public override SinkType TargetSinkType => SinkType.Device;
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
ref DeviceParameter inputDeviceParameter = ref MemoryMarshal.Cast<byte, DeviceParameter>(parameter.SpecificData)[0];
|
||||
|
||||
if (parameter.IsUsed != IsUsed)
|
||||
{
|
||||
UpdateStandardParameter(ref parameter);
|
||||
UpdateStandardParameter(in parameter);
|
||||
Parameter = inputDeviceParameter;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -2,10 +2,11 @@ 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;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||
{
|
||||
|
@ -25,7 +26,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
private Memory<SplitterDestination> _splitterDestinations;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, ref SplitterInParameter, ReadOnlySpan{byte})"/>.
|
||||
/// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext,in SplitterInParameter,ref SequenceReader{byte})"/>.
|
||||
/// </summary>
|
||||
public bool IsBugFixed { get; private set; }
|
||||
|
||||
|
@ -110,7 +111,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// </summary>
|
||||
/// <param name="splitters">The <see cref="SplitterState"/> storage.</param>
|
||||
/// <param name="splitterDestinations">The <see cref="SplitterDestination"/> storage.</param>
|
||||
/// <param name="isBugFixed">If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, ref SplitterInParameter, ReadOnlySpan{byte})"/>.</param>
|
||||
/// <param name="isBugFixed">If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext,in SplitterInParameter,ref SequenceReader{byte})"/>.</param>
|
||||
private void Setup(Memory<SplitterState> splitters, Memory<SplitterDestination> splitterDestinations, bool isBugFixed)
|
||||
{
|
||||
_splitters = splitters;
|
||||
|
@ -148,11 +149,13 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// </summary>
|
||||
/// <param name="inputHeader">The splitter header.</param>
|
||||
/// <param name="input">The raw data after the splitter header.</param>
|
||||
private void UpdateState(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan<byte> input)
|
||||
private void UpdateState(in SplitterInParameterHeader inputHeader, ref SequenceReader<byte> input)
|
||||
{
|
||||
for (int i = 0; i < inputHeader.SplitterCount; i++)
|
||||
{
|
||||
SplitterInParameter parameter = MemoryMarshal.Read<SplitterInParameter>(input);
|
||||
// NOTE: this Rewind/Advance logic is done here to mimic the behavior of the previous implementation.
|
||||
ref readonly SplitterInParameter parameter = ref input.GetRefOrRefToCopy<SplitterInParameter>(out _);
|
||||
input.Rewind(Unsafe.SizeOf<SplitterInParameter>());
|
||||
|
||||
Debug.Assert(parameter.IsMagicValid());
|
||||
|
||||
|
@ -162,10 +165,22 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
{
|
||||
ref SplitterState splitter = ref GetState(parameter.Id);
|
||||
|
||||
splitter.Update(this, ref parameter, input[Unsafe.SizeOf<SplitterInParameter>()..]);
|
||||
input.Advance(Unsafe.SizeOf<SplitterInParameter>());
|
||||
splitter.Update(this, in parameter, ref input);
|
||||
}
|
||||
|
||||
input = input[(0x1C + parameter.DestinationCount * 4)..];
|
||||
// NOTE: this historically has been advancing 0xC (12) bytes without explanation. It was hard
|
||||
// to discern as it was combined with the sizeof(SplitterInParameter) (16), == 0x1C (28).
|
||||
|
||||
// NOTE: this historically was also advancing by parameter.DestinationCount * 4, but that was
|
||||
// to account for the reading done by splitter.Update(). Now we pass in `ref input`, so the
|
||||
// reads performed by splitter.Update() are automatically accounted for.
|
||||
|
||||
input.Advance(0xC);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,11 +190,13 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// </summary>
|
||||
/// <param name="inputHeader">The splitter header.</param>
|
||||
/// <param name="input">The raw data after the splitter header.</param>
|
||||
private void UpdateData(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan<byte> input)
|
||||
private void UpdateData(in SplitterInParameterHeader inputHeader, ref SequenceReader<byte> input)
|
||||
{
|
||||
for (int i = 0; i < inputHeader.SplitterDestinationCount; i++)
|
||||
{
|
||||
SplitterDestinationInParameter parameter = MemoryMarshal.Read<SplitterDestinationInParameter>(input);
|
||||
// NOTE: this Rewind/Advance logic is done here to mimic the behavior of the previous implementation.
|
||||
ref readonly SplitterDestinationInParameter parameter = ref input.GetRefOrRefToCopy<SplitterDestinationInParameter>(out _);
|
||||
input.Rewind(Unsafe.SizeOf<SplitterDestinationInParameter>());
|
||||
|
||||
Debug.Assert(parameter.IsMagicValid());
|
||||
|
||||
|
@ -192,7 +209,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
destination.Update(parameter);
|
||||
}
|
||||
|
||||
input = input[Unsafe.SizeOf<SplitterDestinationInParameter>()..];
|
||||
input.Advance(Unsafe.SizeOf<SplitterDestinationInParameter>());
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -201,36 +222,33 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// Update splitter from user parameters.
|
||||
/// </summary>
|
||||
/// <param name="input">The input raw user data.</param>
|
||||
/// <param name="consumedSize">The total consumed size.</param>
|
||||
/// <returns>Return true if the update was successful.</returns>
|
||||
public bool Update(ReadOnlySpan<byte> input, out int consumedSize)
|
||||
public bool Update(ref SequenceReader<byte> input)
|
||||
{
|
||||
if (_splitterDestinations.IsEmpty || _splitters.IsEmpty)
|
||||
{
|
||||
consumedSize = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int originalSize = input.Length;
|
||||
|
||||
SplitterInParameterHeader header = SpanIOHelper.Read<SplitterInParameterHeader>(ref input);
|
||||
ref readonly SplitterInParameterHeader header = ref input.GetRefOrRefToCopy<SplitterInParameterHeader>(out _);
|
||||
|
||||
if (header.IsMagicValid())
|
||||
{
|
||||
ClearAllNewConnectionFlag();
|
||||
|
||||
UpdateState(ref header, ref input);
|
||||
UpdateData(ref header, ref input);
|
||||
UpdateState(in header, ref input);
|
||||
UpdateData(in header, ref input);
|
||||
|
||||
consumedSize = BitUtils.AlignUp(originalSize - input.Length, 0x10);
|
||||
input.SetConsumed(BitUtils.AlignUp(input.Consumed, 0x10));
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
input.Rewind(Unsafe.SizeOf<SplitterInParameterHeader>());
|
||||
|
||||
consumedSize = 0;
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Common.Extensions;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
|
@ -122,7 +123,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// <param name="context">The splitter context.</param>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="input">The raw input data after the <paramref name="parameter"/>.</param>
|
||||
public void Update(SplitterContext context, ref SplitterInParameter parameter, ReadOnlySpan<byte> input)
|
||||
public void Update(SplitterContext context, in SplitterInParameter parameter, ref SequenceReader<byte> input)
|
||||
{
|
||||
ClearLinks();
|
||||
|
||||
|
@ -139,9 +140,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
|
||||
if (destinationCount > 0)
|
||||
{
|
||||
ReadOnlySpan<int> destinationIds = MemoryMarshal.Cast<byte, int>(input);
|
||||
input.ReadLittleEndian(out int destinationId);
|
||||
|
||||
Memory<SplitterDestination> destination = context.GetDestinationMemory(destinationIds[0]);
|
||||
Memory<SplitterDestination> destination = context.GetDestinationMemory(destinationId);
|
||||
|
||||
SetDestination(ref destination.Span[0]);
|
||||
|
||||
|
@ -149,7 +150,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
|
||||
for (int i = 1; i < destinationCount; i++)
|
||||
{
|
||||
Memory<SplitterDestination> nextDestination = context.GetDestinationMemory(destinationIds[i]);
|
||||
input.ReadLittleEndian(out destinationId);
|
||||
|
||||
Memory<SplitterDestination> nextDestination = context.GetDestinationMemory(destinationId);
|
||||
|
||||
destination.Span[0].Link(ref nextDestination.Span[0]);
|
||||
destination = nextDestination;
|
||||
|
|
|
@ -9,41 +9,40 @@ 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 System.Runtime.InteropServices;
|
||||
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
public class StateUpdater
|
||||
public ref struct StateUpdater
|
||||
{
|
||||
private readonly ReadOnlyMemory<byte> _inputOrigin;
|
||||
private SequenceReader<byte> _inputReader;
|
||||
|
||||
private readonly ReadOnlyMemory<byte> _outputOrigin;
|
||||
private ReadOnlyMemory<byte> _input;
|
||||
|
||||
private Memory<byte> _output;
|
||||
private readonly uint _processHandle;
|
||||
private BehaviourContext _behaviourContext;
|
||||
|
||||
private UpdateDataHeader _inputHeader;
|
||||
private readonly ref readonly UpdateDataHeader _inputHeader;
|
||||
private readonly Memory<UpdateDataHeader> _outputHeader;
|
||||
|
||||
private ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0];
|
||||
|
||||
public StateUpdater(ReadOnlyMemory<byte> input, Memory<byte> output, uint processHandle, BehaviourContext behaviourContext)
|
||||
public StateUpdater(ReadOnlySequence<byte> input, Memory<byte> output, uint processHandle, BehaviourContext behaviourContext)
|
||||
{
|
||||
_input = input;
|
||||
_inputOrigin = _input;
|
||||
_inputReader = new SequenceReader<byte>(input);
|
||||
_output = output;
|
||||
_outputOrigin = _output;
|
||||
_processHandle = processHandle;
|
||||
_behaviourContext = behaviourContext;
|
||||
|
||||
_inputHeader = SpanIOHelper.Read<UpdateDataHeader>(ref _input);
|
||||
_inputHeader = ref _inputReader.GetRefOrRefToCopy<UpdateDataHeader>(out _);
|
||||
|
||||
_outputHeader = SpanMemoryManager<UpdateDataHeader>.Cast(_output[..Unsafe.SizeOf<UpdateDataHeader>()]);
|
||||
OutputHeader.Initialize(_behaviourContext.UserRevision);
|
||||
|
@ -52,7 +51,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
public ResultCode UpdateBehaviourContext()
|
||||
{
|
||||
BehaviourParameter parameter = SpanIOHelper.Read<BehaviourParameter>(ref _input);
|
||||
ref readonly BehaviourParameter parameter = ref _inputReader.GetRefOrRefToCopy<BehaviourParameter>(out _);
|
||||
|
||||
if (!BehaviourContext.CheckValidRevision(parameter.UserRevision) || parameter.UserRevision != _behaviourContext.UserRevision)
|
||||
{
|
||||
|
@ -81,11 +80,11 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
foreach (ref MemoryPoolState memoryPool in memoryPools)
|
||||
{
|
||||
MemoryPoolInParameter parameter = SpanIOHelper.Read<MemoryPoolInParameter>(ref _input);
|
||||
ref readonly MemoryPoolInParameter parameter = ref _inputReader.GetRefOrRefToCopy<MemoryPoolInParameter>(out _);
|
||||
|
||||
ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef<MemoryPoolOutStatus>(ref _output)[0];
|
||||
|
||||
PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, ref parameter, ref outStatus);
|
||||
PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, in parameter, ref outStatus);
|
||||
|
||||
if (updateResult != PoolMapper.UpdateResult.Success &&
|
||||
updateResult != PoolMapper.UpdateResult.MapError &&
|
||||
|
@ -115,7 +114,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
VoiceChannelResourceInParameter parameter = SpanIOHelper.Read<VoiceChannelResourceInParameter>(ref _input);
|
||||
ref readonly VoiceChannelResourceInParameter parameter = ref _inputReader.GetRefOrRefToCopy<VoiceChannelResourceInParameter>(out _);
|
||||
|
||||
ref VoiceChannelResource resource = ref context.GetChannelResource(i);
|
||||
|
||||
|
@ -127,7 +126,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateVoices(VoiceContext context, Memory<MemoryPoolState> memoryPools)
|
||||
public ResultCode UpdateVoices(VoiceContext context, PoolMapper mapper)
|
||||
{
|
||||
if (context.GetCount() * Unsafe.SizeOf<VoiceInParameter>() != _inputHeader.VoicesSize)
|
||||
{
|
||||
|
@ -136,11 +135,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<VoiceInParameter> parameters = MemoryMarshal.Cast<byte, VoiceInParameter>(_input[..(int)_inputHeader.VoicesSize].Span);
|
||||
|
||||
_input = _input[(int)_inputHeader.VoicesSize..];
|
||||
|
||||
PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
// First make everything not in use.
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
|
@ -157,7 +152,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
// Start processing
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
VoiceInParameter parameter = parameters[i];
|
||||
ref readonly VoiceInParameter parameter = ref _inputReader.GetRefOrRefToCopy<VoiceInParameter>(out _);
|
||||
|
||||
voiceUpdateStates.Fill(Memory<VoiceUpdateState>.Empty);
|
||||
|
||||
|
@ -181,14 +176,14 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
currentVoiceState.Initialize();
|
||||
}
|
||||
|
||||
currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, ref parameter, ref mapper, ref _behaviourContext);
|
||||
currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, in parameter, mapper, ref _behaviourContext);
|
||||
|
||||
if (updateParameterError.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
_behaviourContext.AppendError(ref updateParameterError);
|
||||
}
|
||||
|
||||
currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, ref parameter, voiceUpdateStates, ref mapper, ref _behaviourContext);
|
||||
currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, in parameter, voiceUpdateStates, mapper, ref _behaviourContext);
|
||||
|
||||
foreach (ref ErrorInfo errorInfo in waveBufferUpdateErrorInfos.AsSpan())
|
||||
{
|
||||
|
@ -198,7 +193,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
}
|
||||
|
||||
currentVoiceState.WriteOutStatus(ref outStatus, ref parameter, voiceUpdateStates);
|
||||
currentVoiceState.WriteOutStatus(ref outStatus, in parameter, voiceUpdateStates);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,10 +206,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize);
|
||||
|
||||
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.VoicesSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private static void ResetEffect<T>(ref BaseEffect effect, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
private static void ResetEffect<T>(ref BaseEffect effect, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
effect.ForceUnmapBuffers(mapper);
|
||||
|
||||
|
@ -234,17 +231,17 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
};
|
||||
}
|
||||
|
||||
public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
|
||||
public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, PoolMapper mapper)
|
||||
{
|
||||
if (_behaviourContext.IsEffectInfoVersion2Supported())
|
||||
{
|
||||
return UpdateEffectsVersion2(context, isAudioRendererActive, memoryPools);
|
||||
return UpdateEffectsVersion2(context, isAudioRendererActive, mapper);
|
||||
}
|
||||
|
||||
return UpdateEffectsVersion1(context, isAudioRendererActive, memoryPools);
|
||||
return UpdateEffectsVersion1(context, isAudioRendererActive, mapper);
|
||||
}
|
||||
|
||||
public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
|
||||
public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, PoolMapper mapper)
|
||||
{
|
||||
if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion2>() != _inputHeader.EffectsSize)
|
||||
{
|
||||
|
@ -253,26 +250,22 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<EffectInParameterVersion2> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion2>(_input[..(int)_inputHeader.EffectsSize].Span);
|
||||
|
||||
_input = _input[(int)_inputHeader.EffectsSize..];
|
||||
|
||||
PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
EffectInParameterVersion2 parameter = parameters[i];
|
||||
ref readonly EffectInParameterVersion2 parameter = ref _inputReader.GetRefOrRefToCopy<EffectInParameterVersion2>(out _);
|
||||
|
||||
ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion2>(ref _output)[0];
|
||||
|
||||
ref BaseEffect effect = ref context.GetEffect(i);
|
||||
|
||||
if (!effect.IsTypeValid(ref parameter))
|
||||
if (!effect.IsTypeValid(in parameter))
|
||||
{
|
||||
ResetEffect(ref effect, ref parameter, mapper);
|
||||
ResetEffect(ref effect, in parameter, mapper);
|
||||
}
|
||||
|
||||
effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper);
|
||||
effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper);
|
||||
|
||||
if (updateErrorInfo.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
|
@ -297,10 +290,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
|
||||
|
||||
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
|
||||
public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, PoolMapper mapper)
|
||||
{
|
||||
if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion1>() != _inputHeader.EffectsSize)
|
||||
{
|
||||
|
@ -309,26 +304,22 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<EffectInParameterVersion1> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion1>(_input[..(int)_inputHeader.EffectsSize].Span);
|
||||
|
||||
_input = _input[(int)_inputHeader.EffectsSize..];
|
||||
|
||||
PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
EffectInParameterVersion1 parameter = parameters[i];
|
||||
ref readonly EffectInParameterVersion1 parameter = ref _inputReader.GetRefOrRefToCopy<EffectInParameterVersion1>(out _);
|
||||
|
||||
ref EffectOutStatusVersion1 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion1>(ref _output)[0];
|
||||
|
||||
ref BaseEffect effect = ref context.GetEffect(i);
|
||||
|
||||
if (!effect.IsTypeValid(ref parameter))
|
||||
if (!effect.IsTypeValid(in parameter))
|
||||
{
|
||||
ResetEffect(ref effect, ref parameter, mapper);
|
||||
ResetEffect(ref effect, in parameter, mapper);
|
||||
}
|
||||
|
||||
effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper);
|
||||
effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper);
|
||||
|
||||
if (updateErrorInfo.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
|
@ -345,38 +336,40 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
|
||||
|
||||
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateSplitter(SplitterContext context)
|
||||
{
|
||||
if (context.Update(_input.Span, out int consumedSize))
|
||||
if (context.Update(ref _inputReader))
|
||||
{
|
||||
_input = _input[consumedSize..];
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
private static bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, ReadOnlySpan<MixParameter> parameters)
|
||||
private static bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, SequenceReader<byte> parameters)
|
||||
{
|
||||
uint maxMixStateCount = mixContext.GetCount();
|
||||
uint totalRequiredMixBufferCount = 0;
|
||||
|
||||
for (int i = 0; i < inputMixCount; i++)
|
||||
{
|
||||
if (parameters[i].IsUsed)
|
||||
ref readonly MixParameter parameter = ref parameters.GetRefOrRefToCopy<MixParameter>(out _);
|
||||
|
||||
if (parameter.IsUsed)
|
||||
{
|
||||
if (parameters[i].DestinationMixId != Constants.UnusedMixId &&
|
||||
parameters[i].DestinationMixId > maxMixStateCount &&
|
||||
parameters[i].MixId != Constants.FinalMixId)
|
||||
if (parameter.DestinationMixId != Constants.UnusedMixId &&
|
||||
parameter.DestinationMixId > maxMixStateCount &&
|
||||
parameter.MixId != Constants.FinalMixId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
totalRequiredMixBufferCount += parameters[i].BufferCount;
|
||||
totalRequiredMixBufferCount += parameter.BufferCount;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -391,7 +384,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
|
||||
{
|
||||
MixInParameterDirtyOnlyUpdate parameter = MemoryMarshal.Cast<byte, MixInParameterDirtyOnlyUpdate>(_input.Span)[0];
|
||||
ref readonly MixInParameterDirtyOnlyUpdate parameter = ref _inputReader.GetRefOrRefToCopy<MixInParameterDirtyOnlyUpdate>(out _);
|
||||
|
||||
mixCount = parameter.MixCount;
|
||||
|
||||
|
@ -411,25 +404,20 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
|
||||
{
|
||||
_input = _input[Unsafe.SizeOf<MixInParameterDirtyOnlyUpdate>()..];
|
||||
}
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
ReadOnlySpan<MixParameter> parameters = MemoryMarshal.Cast<byte, MixParameter>(_input.Span[..(int)inputMixSize]);
|
||||
int parameterCount = (int)inputMixSize / Unsafe.SizeOf<MixParameter>();
|
||||
|
||||
_input = _input[(int)inputMixSize..];
|
||||
|
||||
if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, parameters))
|
||||
if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, _inputReader))
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
bool isMixContextDirty = false;
|
||||
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
for (int i = 0; i < parameterCount; i++)
|
||||
{
|
||||
MixParameter parameter = parameters[i];
|
||||
ref readonly MixParameter parameter = ref _inputReader.GetRefOrRefToCopy<MixParameter>(out _);
|
||||
|
||||
int mixId = i;
|
||||
|
||||
|
@ -454,7 +442,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
if (mix.IsUsed)
|
||||
{
|
||||
isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, ref parameter, effectContext, splitterContext, _behaviourContext);
|
||||
isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, in parameter, effectContext, splitterContext, _behaviourContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -473,10 +461,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
}
|
||||
|
||||
_inputReader.SetConsumed(initialInputConsumed + inputMixSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private static void ResetSink(ref BaseSink sink, ref SinkInParameter parameter)
|
||||
private static void ResetSink(ref BaseSink sink, in SinkInParameter parameter)
|
||||
{
|
||||
sink.CleanUp();
|
||||
|
||||
|
@ -489,10 +479,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
};
|
||||
}
|
||||
|
||||
public ResultCode UpdateSinks(SinkContext context, Memory<MemoryPoolState> memoryPools)
|
||||
public ResultCode UpdateSinks(SinkContext context, PoolMapper mapper)
|
||||
{
|
||||
PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
|
||||
if (context.GetCount() * Unsafe.SizeOf<SinkInParameter>() != _inputHeader.SinksSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
|
@ -500,22 +488,20 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<SinkInParameter> parameters = MemoryMarshal.Cast<byte, SinkInParameter>(_input[..(int)_inputHeader.SinksSize].Span);
|
||||
|
||||
_input = _input[(int)_inputHeader.SinksSize..];
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
SinkInParameter parameter = parameters[i];
|
||||
ref readonly SinkInParameter parameter = ref _inputReader.GetRefOrRefToCopy<SinkInParameter>(out _);
|
||||
ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef<SinkOutStatus>(ref _output)[0];
|
||||
ref BaseSink sink = ref context.GetSink(i);
|
||||
|
||||
if (!sink.IsTypeValid(ref parameter))
|
||||
if (!sink.IsTypeValid(in parameter))
|
||||
{
|
||||
ResetSink(ref sink, ref parameter);
|
||||
ResetSink(ref sink, in parameter);
|
||||
}
|
||||
|
||||
sink.Update(out ErrorInfo updateErrorInfo, ref parameter, ref outStatus, mapper);
|
||||
sink.Update(out ErrorInfo updateErrorInfo, in parameter, ref outStatus, mapper);
|
||||
|
||||
if (updateErrorInfo.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
|
@ -530,6 +516,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize);
|
||||
|
||||
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.SinksSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
|
@ -540,7 +528,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
PerformanceInParameter parameter = SpanIOHelper.Read<PerformanceInParameter>(ref _input);
|
||||
ref readonly PerformanceInParameter parameter = ref _inputReader.GetRefOrRefToCopy<PerformanceInParameter>(out _);
|
||||
|
||||
ref PerformanceOutStatus outStatus = ref SpanIOHelper.GetWriteRef<PerformanceOutStatus>(ref _output)[0];
|
||||
|
||||
|
@ -587,7 +575,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
public ResultCode CheckConsumedSize()
|
||||
{
|
||||
int consumedInputSize = _inputOrigin.Length - _input.Length;
|
||||
long consumedInputSize = _inputReader.Consumed;
|
||||
int consumedOutputSize = _outputOrigin.Length - _output.Length;
|
||||
|
||||
if (consumedInputSize != _inputHeader.TotalSize)
|
||||
|
|
|
@ -254,7 +254,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <returns>Return true, if the server voice information needs to be updated.</returns>
|
||||
private readonly bool ShouldUpdateParameters(ref VoiceInParameter parameter)
|
||||
private readonly bool ShouldUpdateParameters(in VoiceInParameter parameter)
|
||||
{
|
||||
if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress)
|
||||
{
|
||||
|
@ -273,7 +273,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="poolMapper">The mapper to use.</param>
|
||||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
public void UpdateParameters(out ErrorInfo outErrorInfo, ref VoiceInParameter parameter, ref PoolMapper poolMapper, ref BehaviourContext behaviourContext)
|
||||
public void UpdateParameters(out ErrorInfo outErrorInfo, in VoiceInParameter parameter, PoolMapper poolMapper, ref BehaviourContext behaviourContext)
|
||||
{
|
||||
InUse = parameter.InUse;
|
||||
Id = parameter.Id;
|
||||
|
@ -326,7 +326,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
VoiceDropFlag = false;
|
||||
}
|
||||
|
||||
if (ShouldUpdateParameters(ref parameter))
|
||||
if (ShouldUpdateParameters(in parameter))
|
||||
{
|
||||
DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize);
|
||||
}
|
||||
|
@ -380,7 +380,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
/// <param name="outStatus">The given user output.</param>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
|
||||
public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates)
|
||||
public void WriteOutStatus(ref VoiceOutStatus outStatus, in VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates)
|
||||
{
|
||||
#if DEBUG
|
||||
// Sanity check in debug mode of the internal state
|
||||
|
@ -426,7 +426,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
|
||||
/// <param name="mapper">The mapper to use.</param>
|
||||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext)
|
||||
public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, in VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates, PoolMapper mapper, ref BehaviourContext behaviourContext)
|
||||
{
|
||||
errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2];
|
||||
|
||||
|
@ -444,7 +444,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
|
||||
for (int i = 0; i < Constants.VoiceWaveBufferCount; i++)
|
||||
{
|
||||
UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext);
|
||||
UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], mapper, ref behaviourContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -458,7 +458,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
/// <param name="isValid">If set to true, the server side wavebuffer is considered valid.</param>
|
||||
/// <param name="mapper">The mapper to use.</param>
|
||||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
private void UpdateWaveBuffer(Span<ErrorInfo> errorInfos, ref WaveBuffer waveBuffer, ref WaveBufferInternal inputWaveBuffer, SampleFormat sampleFormat, bool isValid, ref PoolMapper mapper, ref BehaviourContext behaviourContext)
|
||||
private void UpdateWaveBuffer(Span<ErrorInfo> errorInfos, ref WaveBuffer waveBuffer, ref WaveBufferInternal inputWaveBuffer, SampleFormat sampleFormat, bool isValid, PoolMapper mapper, ref BehaviourContext behaviourContext)
|
||||
{
|
||||
if (!isValid && waveBuffer.IsSendToAudioProcessor && waveBuffer.BufferAddressInfo.CpuAddress != 0)
|
||||
{
|
||||
|
|
180
src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs
Normal file
180
src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs
Normal file
|
@ -0,0 +1,180 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.Extensions
|
||||
{
|
||||
public static class SequenceReaderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Dumps the entire <see cref="SequenceReader{byte}"/> to a file, restoring its previous location afterward.
|
||||
/// Useful for debugging purposes.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
/// <param name="fileFullName"></param>
|
||||
public static void DumpToFile(this ref SequenceReader<byte> reader, string fileFullName)
|
||||
{
|
||||
var initialConsumed = reader.Consumed;
|
||||
|
||||
reader.Rewind(initialConsumed);
|
||||
|
||||
using (var fileStream = System.IO.File.Create(fileFullName, 4096, System.IO.FileOptions.None))
|
||||
{
|
||||
while (reader.End == false)
|
||||
{
|
||||
var span = reader.CurrentSpan;
|
||||
fileStream.Write(span);
|
||||
reader.Advance(span.Length);
|
||||
}
|
||||
}
|
||||
|
||||
reader.SetConsumed(initialConsumed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the desired value. This ref should always be used. The argument passed in <paramref name="copyDestinationIfRequiredDoNotUse"/> should never be used, as this is only used for storage if the value
|
||||
/// must be copied from multiple <see cref="ReadOnlyMemory{Byte}"/> segments held by the <see cref="SequenceReader{Byte}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
|
||||
/// <param name="copyDestinationIfRequiredDoNotUse">A location used as storage if (and only if) the value to be read spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments</param>
|
||||
/// <returns>A reference to the desired value, either directly to memory in the <see cref="SequenceReader{Byte}"/>, or to <paramref name="copyDestinationIfRequiredDoNotUse"/> if it has been used for copying the value in to</returns>
|
||||
/// <remarks>
|
||||
/// DO NOT use <paramref name="copyDestinationIfRequiredDoNotUse"/> after calling this method, as it will only
|
||||
/// contain a value if the value couldn't be referenced directly because it spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments.
|
||||
/// To discourage use, it is recommended to to call this method like the following:
|
||||
/// <c>
|
||||
/// ref readonly MyStruct value = ref sequenceReader.GetRefOrRefToCopy{MyStruct}(out _);
|
||||
/// </c>
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception>
|
||||
public static ref readonly T GetRefOrRefToCopy<T>(this scoped ref SequenceReader<byte> reader, out T copyDestinationIfRequiredDoNotUse) where T : unmanaged
|
||||
{
|
||||
int lengthRequired = Unsafe.SizeOf<T>();
|
||||
|
||||
ReadOnlySpan<byte> span = reader.UnreadSpan;
|
||||
if (lengthRequired <= span.Length)
|
||||
{
|
||||
reader.Advance(lengthRequired);
|
||||
|
||||
copyDestinationIfRequiredDoNotUse = default;
|
||||
|
||||
ReadOnlySpan<T> spanOfT = MemoryMarshal.Cast<byte, T>(span);
|
||||
|
||||
return ref spanOfT[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
copyDestinationIfRequiredDoNotUse = default;
|
||||
|
||||
Span<T> valueSpan = MemoryMarshal.CreateSpan(ref copyDestinationIfRequiredDoNotUse, 1);
|
||||
|
||||
Span<byte> valueBytesSpan = MemoryMarshal.AsBytes(valueSpan);
|
||||
|
||||
if (!reader.TryCopyTo(valueBytesSpan))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(reader), "The sequence is not long enough to read the desired value.");
|
||||
}
|
||||
|
||||
reader.Advance(lengthRequired);
|
||||
|
||||
return ref valueSpan[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an <see cref="int"/> as little endian.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
|
||||
/// <param name="value">A location to receive the read value</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if there wasn't enough data for an <see cref="int"/></exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReadLittleEndian(this ref SequenceReader<byte> reader, out int value)
|
||||
{
|
||||
if (!reader.TryReadLittleEndian(out value))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the desired unmanaged value by copying it to the specified <paramref name="value"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
|
||||
/// <param name="value">The target that will receive the read value</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReadUnmanaged<T>(this ref SequenceReader<byte> reader, out T value) where T : unmanaged
|
||||
{
|
||||
if (!reader.TryReadUnmanaged(out value))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the reader's position as bytes consumed.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to set the position</param>
|
||||
/// <param name="consumed">The number of bytes consumed</param>
|
||||
public static void SetConsumed(ref this SequenceReader<byte> reader, long consumed)
|
||||
{
|
||||
reader.Rewind(reader.Consumed);
|
||||
reader.Advance(consumed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary
|
||||
/// structs - see remarks for full details.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to
|
||||
/// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit
|
||||
/// overloads such as <see cref="SequenceReader{T}.TryReadLittleEndian"/>
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// True if successful. <paramref name="value"/> will be default if failed (due to lack of space).
|
||||
/// </returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe bool TryReadUnmanaged<T>(ref this SequenceReader<byte> reader, out T value) where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> span = reader.UnreadSpan;
|
||||
|
||||
if (span.Length < sizeof(T))
|
||||
{
|
||||
return TryReadUnmanagedMultiSegment(ref reader, out value);
|
||||
}
|
||||
|
||||
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(span));
|
||||
|
||||
reader.Advance(sizeof(T));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static unsafe bool TryReadUnmanagedMultiSegment<T>(ref SequenceReader<byte> reader, out T value) where T : unmanaged
|
||||
{
|
||||
Debug.Assert(reader.UnreadSpan.Length < sizeof(T));
|
||||
|
||||
// Not enough data in the current segment, try to peek for the data we need.
|
||||
T buffer = default;
|
||||
|
||||
Span<byte> tempSpan = new Span<byte>(&buffer, sizeof(T));
|
||||
|
||||
if (!reader.TryCopyTo(tempSpan))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(tempSpan));
|
||||
|
||||
reader.Advance(sizeof(T));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,359 @@
|
|||
using NUnit.Framework;
|
||||
using Ryujinx.Common.Extensions;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Tests.Common.Extensions
|
||||
{
|
||||
public class SequenceReaderExtensionsTests
|
||||
{
|
||||
[TestCase(null)]
|
||||
[TestCase(sizeof(int) + 1)]
|
||||
public void GetRefOrRefToCopy_ReadsMultiSegmentedSequenceSuccessfully(int? maxSegmentSize)
|
||||
{
|
||||
// Arrange
|
||||
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(3).ToArray();
|
||||
|
||||
ReadOnlySequence<byte> sequence =
|
||||
CreateSegmentedByteSequence(originalStructs, maxSegmentSize ?? Unsafe.SizeOf<MyUnmanagedStruct>());
|
||||
|
||||
var sequenceReader = new SequenceReader<byte>(sequence);
|
||||
|
||||
foreach (var original in originalStructs)
|
||||
{
|
||||
// Act
|
||||
ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
|
||||
|
||||
// Assert
|
||||
MyUnmanagedStruct.Assert(Assert.AreEqual, original, read);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetRefOrRefToCopy_FragmentedSequenceReturnsRefToCopy()
|
||||
{
|
||||
// Arrange
|
||||
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray();
|
||||
|
||||
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, 3);
|
||||
|
||||
var sequenceReader = new SequenceReader<byte>(sequence);
|
||||
|
||||
foreach (var original in originalStructs)
|
||||
{
|
||||
// Act
|
||||
ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out var copy);
|
||||
|
||||
// Assert
|
||||
MyUnmanagedStruct.Assert(Assert.AreEqual, original, read);
|
||||
MyUnmanagedStruct.Assert(Assert.AreEqual, read, copy);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetRefOrRefToCopy_ContiguousSequenceReturnsRefToBuffer()
|
||||
{
|
||||
// Arrange
|
||||
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray();
|
||||
|
||||
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue);
|
||||
|
||||
var sequenceReader = new SequenceReader<byte>(sequence);
|
||||
|
||||
foreach (var original in originalStructs)
|
||||
{
|
||||
// Act
|
||||
ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out var copy);
|
||||
|
||||
// Assert
|
||||
MyUnmanagedStruct.Assert(Assert.AreEqual, original, read);
|
||||
MyUnmanagedStruct.Assert(Assert.AreNotEqual, read, copy);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetRefOrRefToCopy_ThrowsWhenNotEnoughData()
|
||||
{
|
||||
// Arrange
|
||||
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray();
|
||||
|
||||
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue);
|
||||
|
||||
// Act/Assert
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
var sequenceReader = new SequenceReader<byte>(sequence);
|
||||
|
||||
sequenceReader.Advance(1);
|
||||
|
||||
ref readonly MyUnmanagedStruct result = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadLittleEndian_Int32_RoundTripsSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
const int TestValue = 0x1234abcd;
|
||||
|
||||
byte[] buffer = new byte[sizeof(int)];
|
||||
|
||||
BinaryPrimitives.WriteInt32LittleEndian(buffer.AsSpan(), TestValue);
|
||||
|
||||
var sequenceReader = new SequenceReader<byte>(new ReadOnlySequence<byte>(buffer));
|
||||
|
||||
// Act
|
||||
sequenceReader.ReadLittleEndian(out int roundTrippedValue);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(TestValue, roundTrippedValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadLittleEndian_Int32_ResultIsNotBigEndian()
|
||||
{
|
||||
// Arrange
|
||||
const int TestValue = 0x1234abcd;
|
||||
|
||||
byte[] buffer = new byte[sizeof(int)];
|
||||
|
||||
BinaryPrimitives.WriteInt32BigEndian(buffer.AsSpan(), TestValue);
|
||||
|
||||
var sequenceReader = new SequenceReader<byte>(new ReadOnlySequence<byte>(buffer));
|
||||
|
||||
// Act
|
||||
sequenceReader.ReadLittleEndian(out int roundTrippedValue);
|
||||
|
||||
// Assert
|
||||
Assert.AreNotEqual(TestValue, roundTrippedValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadLittleEndian_Int32_ThrowsWhenNotEnoughData()
|
||||
{
|
||||
// Arrange
|
||||
const int TestValue = 0x1234abcd;
|
||||
|
||||
byte[] buffer = new byte[sizeof(int)];
|
||||
|
||||
BinaryPrimitives.WriteInt32BigEndian(buffer.AsSpan(), TestValue);
|
||||
|
||||
// Act/Assert
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
var sequenceReader = new SequenceReader<byte>(new ReadOnlySequence<byte>(buffer));
|
||||
sequenceReader.Advance(1);
|
||||
|
||||
sequenceReader.ReadLittleEndian(out int roundTrippedValue);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadUnmanaged_ContiguousSequence_Succeeds()
|
||||
=> ReadUnmanaged_Succeeds(int.MaxValue);
|
||||
|
||||
[Test]
|
||||
public void ReadUnmanaged_FragmentedSequence_Succeeds()
|
||||
=> ReadUnmanaged_Succeeds(sizeof(int) + 1);
|
||||
|
||||
[Test]
|
||||
public void ReadUnmanaged_ThrowsWhenNotEnoughData()
|
||||
{
|
||||
// Arrange
|
||||
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray();
|
||||
|
||||
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue);
|
||||
|
||||
// Act/Assert
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
var sequenceReader = new SequenceReader<byte>(sequence);
|
||||
|
||||
sequenceReader.Advance(1);
|
||||
|
||||
sequenceReader.ReadUnmanaged(out MyUnmanagedStruct read);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetConsumed_ContiguousSequence_SucceedsWhenValid()
|
||||
=> SetConsumed_SucceedsWhenValid(int.MaxValue);
|
||||
|
||||
[Test]
|
||||
public void SetConsumed_FragmentedSequence_SucceedsWhenValid()
|
||||
=> SetConsumed_SucceedsWhenValid(sizeof(int) + 1);
|
||||
|
||||
[Test]
|
||||
public void SetConsumed_ThrowsWhenBeyondActualLength()
|
||||
{
|
||||
const int StructCount = 2;
|
||||
// Arrange
|
||||
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(StructCount).ToArray();
|
||||
|
||||
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, MyUnmanagedStruct.SizeOf);
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
var sequenceReader = new SequenceReader<byte>(sequence);
|
||||
|
||||
sequenceReader.SetConsumed(MyUnmanagedStruct.SizeOf * StructCount + 1);
|
||||
});
|
||||
}
|
||||
|
||||
private static void ReadUnmanaged_Succeeds(int maxSegmentLength)
|
||||
{
|
||||
// Arrange
|
||||
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(3).ToArray();
|
||||
|
||||
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, maxSegmentLength);
|
||||
|
||||
var sequenceReader = new SequenceReader<byte>(sequence);
|
||||
|
||||
foreach (var original in originalStructs)
|
||||
{
|
||||
// Act
|
||||
sequenceReader.ReadUnmanaged(out MyUnmanagedStruct read);
|
||||
|
||||
// Assert
|
||||
MyUnmanagedStruct.Assert(Assert.AreEqual, original, read);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetConsumed_SucceedsWhenValid(int maxSegmentLength)
|
||||
{
|
||||
// Arrange
|
||||
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(2).ToArray();
|
||||
|
||||
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, maxSegmentLength);
|
||||
|
||||
var sequenceReader = new SequenceReader<byte>(sequence);
|
||||
|
||||
static void SetConsumedAndAssert(scoped ref SequenceReader<byte> sequenceReader, long consumed)
|
||||
{
|
||||
sequenceReader.SetConsumed(consumed);
|
||||
Assert.AreEqual(consumed, sequenceReader.Consumed);
|
||||
}
|
||||
|
||||
// Act/Assert
|
||||
ref readonly MyUnmanagedStruct struct0A = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
|
||||
|
||||
Assert.AreEqual(sequenceReader.Consumed, MyUnmanagedStruct.SizeOf);
|
||||
|
||||
SetConsumedAndAssert(ref sequenceReader, 0);
|
||||
|
||||
ref readonly MyUnmanagedStruct struct0B = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
|
||||
|
||||
MyUnmanagedStruct.Assert(Assert.AreEqual, struct0A, struct0B);
|
||||
|
||||
SetConsumedAndAssert(ref sequenceReader, 1);
|
||||
|
||||
SetConsumedAndAssert(ref sequenceReader, MyUnmanagedStruct.SizeOf);
|
||||
|
||||
ref readonly MyUnmanagedStruct struct1A = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
|
||||
|
||||
SetConsumedAndAssert(ref sequenceReader, MyUnmanagedStruct.SizeOf);
|
||||
|
||||
ref readonly MyUnmanagedStruct struct1B = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
|
||||
|
||||
MyUnmanagedStruct.Assert(Assert.AreEqual, struct1A, struct1B);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
private struct MyUnmanagedStruct
|
||||
{
|
||||
public int BehaviourSize;
|
||||
public int MemoryPoolsSize;
|
||||
public short VoicesSize;
|
||||
public int VoiceResourcesSize;
|
||||
public short EffectsSize;
|
||||
public int RenderInfoSize;
|
||||
|
||||
public unsafe fixed byte Reserved[16];
|
||||
|
||||
public static readonly int SizeOf = Unsafe.SizeOf<MyUnmanagedStruct>();
|
||||
|
||||
public static unsafe MyUnmanagedStruct Generate(Random rng)
|
||||
{
|
||||
const int BaseInt32Value = 0x1234abcd;
|
||||
const short BaseInt16Value = 0x5678;
|
||||
|
||||
var result = new MyUnmanagedStruct
|
||||
{
|
||||
BehaviourSize = BaseInt32Value ^ rng.Next(),
|
||||
MemoryPoolsSize = BaseInt32Value ^ rng.Next(),
|
||||
VoicesSize = (short)(BaseInt16Value ^ rng.Next()),
|
||||
VoiceResourcesSize = BaseInt32Value ^ rng.Next(),
|
||||
EffectsSize = (short)(BaseInt16Value ^ rng.Next()),
|
||||
RenderInfoSize = BaseInt32Value ^ rng.Next(),
|
||||
};
|
||||
|
||||
Unsafe.Write(result.Reserved, rng.NextInt64());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static unsafe void Assert(Action<object, object> assert, in MyUnmanagedStruct expected, in MyUnmanagedStruct actual)
|
||||
{
|
||||
assert(expected.BehaviourSize, actual.BehaviourSize);
|
||||
assert(expected.MemoryPoolsSize, actual.MemoryPoolsSize);
|
||||
assert(expected.VoicesSize, actual.VoicesSize);
|
||||
assert(expected.VoiceResourcesSize, actual.VoiceResourcesSize);
|
||||
assert(expected.EffectsSize, actual.EffectsSize);
|
||||
assert(expected.RenderInfoSize, actual.RenderInfoSize);
|
||||
|
||||
fixed (void* expectedReservedPtr = expected.Reserved)
|
||||
fixed (void* actualReservedPtr = actual.Reserved)
|
||||
{
|
||||
long expectedReservedLong = Unsafe.Read<long>(expectedReservedPtr);
|
||||
long actualReservedLong = Unsafe.Read<long>(actualReservedPtr);
|
||||
|
||||
assert(expectedReservedLong, actualReservedLong);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<MyUnmanagedStruct> EnumerateNewUnmanagedStructs()
|
||||
{
|
||||
var rng = new Random(0);
|
||||
|
||||
while (true)
|
||||
{
|
||||
yield return MyUnmanagedStruct.Generate(rng);
|
||||
}
|
||||
}
|
||||
|
||||
private static ReadOnlySequence<byte> CreateSegmentedByteSequence<T>(T[] array, int maxSegmentLength) where T : unmanaged
|
||||
{
|
||||
byte[] arrayBytes = MemoryMarshal.AsBytes(array.AsSpan()).ToArray();
|
||||
var memory = new Memory<byte>(arrayBytes);
|
||||
int index = 0;
|
||||
|
||||
BytesReadOnlySequenceSegment first = null, last = null;
|
||||
|
||||
while (index < memory.Length)
|
||||
{
|
||||
int nextSegmentLength = Math.Min(maxSegmentLength, memory.Length - index);
|
||||
var nextSegment = memory.Slice(index, nextSegmentLength);
|
||||
|
||||
if (first == null)
|
||||
{
|
||||
first = last = new BytesReadOnlySequenceSegment(nextSegment);
|
||||
}
|
||||
else
|
||||
{
|
||||
last = last.Append(nextSegment);
|
||||
}
|
||||
|
||||
index += nextSegmentLength;
|
||||
}
|
||||
|
||||
return new ReadOnlySequence<byte>(first, 0, last, (int)(memory.Length - last.RunningIndex));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue