Haydn: Part 1 (#2007)
* Haydn: Part 1 Based on my reverse of audio 11.0.0. As always, core implementation under LGPLv3 for the same reasons as for Amadeus. This place the bases of a more flexible audio system while making audout & audin accurate. This have the following improvements: - Complete reimplementation of audout and audin. - Audin currently only have a dummy backend. - Dramatically reduce CPU usage by up to 50% in common cases (SoundIO and OpenAL). - Audio Renderer now can output to 5.1 devices when supported. - Audio Renderer init its backend on demand instead of keeping two up all the time. - All backends implementation are now in their own project. - Ryujinx.Audio.Renderer was renamed Ryujinx.Audio and was refactored because of this. As a note, games having issues with OpenAL haven't improved and will not because of OpenAL design (stopping when buffers finish playing causing possible audio "pops" when buffers are very small). * Update for latest hexkyz's edits on Switchbrew * audren: Rollback channel configuration changes * Address gdkchan's comments * Fix typo in OpenAL backend driver * Address last comments * Fix a nit * Address gdkchan's comments
This commit is contained in:
parent
1c49089ff0
commit
f556c80d02
249 changed files with 5614 additions and 2712 deletions
838
Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
Normal file
838
Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
Normal file
|
@ -0,0 +1,838 @@
|
|||
//
|
||||
// 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.Integration;
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using Ryujinx.Audio.Renderer.Dsp.Command;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using Ryujinx.Audio.Renderer.Server.Mix;
|
||||
using Ryujinx.Audio.Renderer.Server.Performance;
|
||||
using Ryujinx.Audio.Renderer.Server.Sink;
|
||||
using Ryujinx.Audio.Renderer.Server.Splitter;
|
||||
using Ryujinx.Audio.Renderer.Server.Types;
|
||||
using Ryujinx.Audio.Renderer.Server.Upsampler;
|
||||
using Ryujinx.Audio.Renderer.Server.Voice;
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
using CpuAddress = System.UInt64;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
public class AudioRenderSystem : IDisposable
|
||||
{
|
||||
private object _lock = new object();
|
||||
|
||||
private AudioRendererExecutionMode _executionMode;
|
||||
private IWritableEvent _systemEvent;
|
||||
private ManualResetEvent _terminationEvent;
|
||||
private MemoryPoolState _dspMemoryPoolState;
|
||||
private VoiceContext _voiceContext;
|
||||
private MixContext _mixContext;
|
||||
private SinkContext _sinkContext;
|
||||
private SplitterContext _splitterContext;
|
||||
private EffectContext _effectContext;
|
||||
private PerformanceManager _performanceManager;
|
||||
private UpsamplerManager _upsamplerManager;
|
||||
private bool _isActive;
|
||||
private BehaviourContext _behaviourContext;
|
||||
private ulong _totalElapsedTicksUpdating;
|
||||
private ulong _totalElapsedTicks;
|
||||
private int _sessionId;
|
||||
private Memory<MemoryPoolState> _memoryPools;
|
||||
|
||||
private uint _sampleRate;
|
||||
private uint _sampleCount;
|
||||
private uint _mixBufferCount;
|
||||
private uint _voiceChannelCountMax;
|
||||
private uint _upsamplerCount;
|
||||
private uint _memoryPoolCount;
|
||||
private uint _processHandle;
|
||||
private ulong _appletResourceId;
|
||||
|
||||
private WritableRegion _workBufferRegion;
|
||||
private MemoryHandle _workBufferMemoryPin;
|
||||
|
||||
private Memory<float> _mixBuffer;
|
||||
private Memory<float> _depopBuffer;
|
||||
|
||||
private uint _renderingTimeLimitPercent;
|
||||
private bool _voiceDropEnabled;
|
||||
private uint _voiceDropCount;
|
||||
private bool _isDspRunningBehind;
|
||||
|
||||
private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator;
|
||||
|
||||
private Memory<byte> _performanceBuffer;
|
||||
|
||||
public IVirtualMemoryManager MemoryManager { get; private set; }
|
||||
|
||||
private ulong _elapsedFrameCount;
|
||||
private ulong _renderingStartTick;
|
||||
|
||||
private AudioRendererManager _manager;
|
||||
|
||||
public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent)
|
||||
{
|
||||
_manager = manager;
|
||||
_terminationEvent = new ManualResetEvent(false);
|
||||
_dspMemoryPoolState = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp);
|
||||
_voiceContext = new VoiceContext();
|
||||
_mixContext = new MixContext();
|
||||
_sinkContext = new SinkContext();
|
||||
_splitterContext = new SplitterContext();
|
||||
_effectContext = new EffectContext();
|
||||
|
||||
_commandProcessingTimeEstimator = null;
|
||||
_systemEvent = systemEvent;
|
||||
_behaviourContext = new BehaviourContext();
|
||||
|
||||
_totalElapsedTicksUpdating = 0;
|
||||
_sessionId = 0;
|
||||
}
|
||||
|
||||
public ResultCode Initialize(ref AudioRendererConfiguration parameter, uint processHandle, CpuAddress workBuffer, ulong workBufferSize, int sessionId, ulong appletResourceId, IVirtualMemoryManager memoryManager)
|
||||
{
|
||||
if (!BehaviourContext.CheckValidRevision(parameter.Revision))
|
||||
{
|
||||
return ResultCode.OperationFailed;
|
||||
}
|
||||
|
||||
if (GetWorkBufferSize(ref parameter) > workBufferSize)
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
Debug.Assert(parameter.RenderingDevice == AudioRendererRenderingDevice.Dsp && parameter.ExecutionMode == AudioRendererExecutionMode.Auto);
|
||||
|
||||
Logger.Info?.Print(LogClass.AudioRenderer, $"Initializing with REV{BehaviourContext.GetRevisionNumber(parameter.Revision)}");
|
||||
|
||||
_behaviourContext.SetUserRevision(parameter.Revision);
|
||||
|
||||
_sampleRate = parameter.SampleRate;
|
||||
_sampleCount = parameter.SampleCount;
|
||||
_mixBufferCount = parameter.MixBufferCount;
|
||||
_voiceChannelCountMax = Constants.VoiceChannelCountMax;
|
||||
_upsamplerCount = parameter.SinkCount + parameter.SubMixBufferCount;
|
||||
_appletResourceId = appletResourceId;
|
||||
_memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount;
|
||||
_executionMode = parameter.ExecutionMode;
|
||||
_sessionId = sessionId;
|
||||
MemoryManager = memoryManager;
|
||||
|
||||
WorkBufferAllocator workBufferAllocator;
|
||||
|
||||
_workBufferRegion = MemoryManager.GetWritableRegion(workBuffer, (int)workBufferSize);
|
||||
_workBufferRegion.Memory.Span.Fill(0);
|
||||
_workBufferMemoryPin = _workBufferRegion.Memory.Pin();
|
||||
|
||||
workBufferAllocator = new WorkBufferAllocator(_workBufferRegion.Memory);
|
||||
|
||||
PoolMapper poolMapper = new PoolMapper(processHandle, false);
|
||||
poolMapper.InitializeSystemPool(ref _dspMemoryPoolState, workBuffer, workBufferSize);
|
||||
|
||||
_mixBuffer = workBufferAllocator.Allocate<float>(_sampleCount * (_voiceChannelCountMax + _mixBufferCount), 0x10);
|
||||
|
||||
if (_mixBuffer.IsEmpty)
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
Memory<float> upSamplerWorkBuffer = workBufferAllocator.Allocate<float>(Constants.TargetSampleCount * (_voiceChannelCountMax + _mixBufferCount) * _upsamplerCount, 0x10);
|
||||
|
||||
if (upSamplerWorkBuffer.IsEmpty)
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
_depopBuffer = workBufferAllocator.Allocate<float>((ulong)BitUtils.AlignUp(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment);
|
||||
|
||||
if (_depopBuffer.IsEmpty)
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
// Invalidate DSP cache on what was currently allocated with workBuffer.
|
||||
AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset);
|
||||
|
||||
Debug.Assert((workBufferAllocator.Offset % Constants.BufferAlignment) == 0);
|
||||
|
||||
Memory<VoiceState> voices = workBufferAllocator.Allocate<VoiceState>(parameter.VoiceCount, VoiceState.Alignment);
|
||||
|
||||
if (voices.IsEmpty)
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
foreach (ref VoiceState voice in voices.Span)
|
||||
{
|
||||
voice.Initialize();
|
||||
}
|
||||
|
||||
// A pain to handle as we can't have VoiceState*, use indices to be a bit more safe
|
||||
Memory<int> sortedVoices = workBufferAllocator.Allocate<int>(parameter.VoiceCount, 0x10);
|
||||
|
||||
if (sortedVoices.IsEmpty)
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
// Clear memory (use -1 as it's an invalid index)
|
||||
sortedVoices.Span.Fill(-1);
|
||||
|
||||
Memory<VoiceChannelResource> voiceChannelResources = workBufferAllocator.Allocate<VoiceChannelResource>(parameter.VoiceCount, VoiceChannelResource.Alignment);
|
||||
|
||||
if (voiceChannelResources.IsEmpty)
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
for (uint id = 0; id < voiceChannelResources.Length; id++)
|
||||
{
|
||||
ref VoiceChannelResource voiceChannelResource = ref voiceChannelResources.Span[(int)id];
|
||||
|
||||
voiceChannelResource.Id = id;
|
||||
voiceChannelResource.IsUsed = false;
|
||||
}
|
||||
|
||||
Memory<VoiceUpdateState> voiceUpdateStates = workBufferAllocator.Allocate<VoiceUpdateState>(parameter.VoiceCount, VoiceUpdateState.Align);
|
||||
|
||||
if (voiceUpdateStates.IsEmpty)
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
uint mixesCount = parameter.SubMixBufferCount + 1;
|
||||
|
||||
Memory<MixState> mixes = workBufferAllocator.Allocate<MixState>(mixesCount, MixState.Alignment);
|
||||
|
||||
if (mixes.IsEmpty)
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
if (parameter.EffectCount == 0)
|
||||
{
|
||||
foreach (ref MixState mix in mixes.Span)
|
||||
{
|
||||
mix = new MixState(Memory<int>.Empty, ref _behaviourContext);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Memory<int> effectProcessingOrderArray = workBufferAllocator.Allocate<int>(parameter.EffectCount * mixesCount, 0x10);
|
||||
|
||||
foreach (ref MixState mix in mixes.Span)
|
||||
{
|
||||
mix = new MixState(effectProcessingOrderArray.Slice(0, (int)parameter.EffectCount), ref _behaviourContext);
|
||||
|
||||
effectProcessingOrderArray = effectProcessingOrderArray.Slice((int)parameter.EffectCount);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the final mix id
|
||||
mixes.Span[0].MixId = Constants.FinalMixId;
|
||||
|
||||
Memory<int> sortedMixesState = workBufferAllocator.Allocate<int>(mixesCount, 0x10);
|
||||
|
||||
if (sortedMixesState.IsEmpty)
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
// Clear memory (use -1 as it's an invalid index)
|
||||
sortedMixesState.Span.Fill(-1);
|
||||
|
||||
Memory<byte> nodeStatesWorkBuffer = Memory<byte>.Empty;
|
||||
Memory<byte> edgeMatrixWorkBuffer = Memory<byte>.Empty;
|
||||
|
||||
if (_behaviourContext.IsSplitterSupported())
|
||||
{
|
||||
nodeStatesWorkBuffer = workBufferAllocator.Allocate((uint)NodeStates.GetWorkBufferSize((int)mixesCount), 1);
|
||||
edgeMatrixWorkBuffer = workBufferAllocator.Allocate((uint)EdgeMatrix.GetWorkBufferSize((int)mixesCount), 1);
|
||||
|
||||
if (nodeStatesWorkBuffer.IsEmpty || edgeMatrixWorkBuffer.IsEmpty)
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
}
|
||||
|
||||
_mixContext.Initialize(sortedMixesState, mixes, nodeStatesWorkBuffer, edgeMatrixWorkBuffer);
|
||||
|
||||
_memoryPools = workBufferAllocator.Allocate<MemoryPoolState>(_memoryPoolCount, MemoryPoolState.Alignment);
|
||||
|
||||
if (_memoryPools.IsEmpty)
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
foreach (ref MemoryPoolState state in _memoryPools.Span)
|
||||
{
|
||||
state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu);
|
||||
}
|
||||
|
||||
if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator))
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
_processHandle = processHandle;
|
||||
|
||||
_upsamplerManager = new UpsamplerManager(upSamplerWorkBuffer, _upsamplerCount);
|
||||
|
||||
_effectContext.Initialize(parameter.EffectCount);
|
||||
_sinkContext.Initialize(parameter.SinkCount);
|
||||
|
||||
Memory<VoiceUpdateState> voiceUpdateStatesDsp = workBufferAllocator.Allocate<VoiceUpdateState>(parameter.VoiceCount, VoiceUpdateState.Align);
|
||||
|
||||
if (voiceUpdateStatesDsp.IsEmpty)
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
_voiceContext.Initialize(sortedVoices, voices, voiceChannelResources, voiceUpdateStates, voiceUpdateStatesDsp, parameter.VoiceCount);
|
||||
|
||||
if (parameter.PerformanceMetricFramesCount > 0)
|
||||
{
|
||||
ulong performanceBufferSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref _behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC;
|
||||
|
||||
_performanceBuffer = workBufferAllocator.Allocate(performanceBufferSize, Constants.BufferAlignment);
|
||||
|
||||
if (_performanceBuffer.IsEmpty)
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
_performanceManager = PerformanceManager.Create(_performanceBuffer, ref parameter, _behaviourContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
_performanceManager = null;
|
||||
}
|
||||
|
||||
_totalElapsedTicksUpdating = 0;
|
||||
_totalElapsedTicks = 0;
|
||||
_renderingTimeLimitPercent = 100;
|
||||
_voiceDropEnabled = parameter.VoiceDropEnabled && _executionMode == AudioRendererExecutionMode.Auto;
|
||||
|
||||
AudioProcessorMemoryManager.InvalidateDataCache(workBuffer, workBufferSize);
|
||||
|
||||
_processHandle = processHandle;
|
||||
_elapsedFrameCount = 0;
|
||||
|
||||
switch (_behaviourContext.GetCommandProcessingTimeEstimatorVersion())
|
||||
{
|
||||
case 1:
|
||||
_commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion1(_sampleCount, _mixBufferCount);
|
||||
break;
|
||||
case 2:
|
||||
_commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion2(_sampleCount, _mixBufferCount);
|
||||
break;
|
||||
case 3:
|
||||
_commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion3(_sampleCount, _mixBufferCount);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Unsupported processing time estimator version {_behaviourContext.GetCommandProcessingTimeEstimatorVersion()}.");
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Logger.Info?.Print(LogClass.AudioRenderer, $"Starting renderer id {_sessionId}");
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_elapsedFrameCount = 0;
|
||||
_isActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
Logger.Info?.Print(LogClass.AudioRenderer, $"Stopping renderer id {_sessionId}");
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_isActive = false;
|
||||
}
|
||||
|
||||
if (_executionMode == AudioRendererExecutionMode.Auto)
|
||||
{
|
||||
_terminationEvent.WaitOne();
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.AudioRenderer, $"Stopped renderer id {_sessionId}");
|
||||
}
|
||||
|
||||
public ResultCode Update(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
ulong updateStartTicks = GetSystemTicks();
|
||||
|
||||
output.Span.Fill(0);
|
||||
|
||||
StateUpdater stateUpdater = new StateUpdater(input, output, _processHandle, _behaviourContext);
|
||||
|
||||
ResultCode result;
|
||||
|
||||
result = stateUpdater.UpdateBehaviourContext();
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateMemoryPools(_memoryPools.Span);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateVoiceChannelResources(_voiceContext);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateVoices(_voiceContext, _memoryPools);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateEffects(_effectContext, _isActive, _memoryPools);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (_behaviourContext.IsSplitterSupported())
|
||||
{
|
||||
result = stateUpdater.UpdateSplitter(_splitterContext);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateMixes(_mixContext, GetMixBufferCount(), _effectContext, _splitterContext);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateSinks(_sinkContext, _memoryPools);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdatePerformanceBuffer(_performanceManager, performanceOutput.Span);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateErrorInfo();
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (_behaviourContext.IsElapsedFrameCountSupported())
|
||||
{
|
||||
result = stateUpdater.UpdateRendererInfo(_elapsedFrameCount);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result = stateUpdater.CheckConsumedSize();
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
_systemEvent.Clear();
|
||||
|
||||
ulong updateEndTicks = GetSystemTicks();
|
||||
|
||||
_totalElapsedTicksUpdating += (updateEndTicks - updateStartTicks);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private ulong GetSystemTicks()
|
||||
{
|
||||
double ticks = ARMeilleure.State.ExecutionContext.ElapsedTicks * ARMeilleure.State.ExecutionContext.TickFrequency;
|
||||
|
||||
return (ulong)(ticks * Constants.TargetTimerFrequency);
|
||||
}
|
||||
|
||||
private uint ComputeVoiceDrop(CommandBuffer commandBuffer, long voicesEstimatedTime, long deltaTimeDsp)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < commandBuffer.CommandList.Commands.Count; i++)
|
||||
{
|
||||
ICommand command = commandBuffer.CommandList.Commands[i];
|
||||
|
||||
CommandType commandType = command.CommandType;
|
||||
|
||||
if (commandType == CommandType.AdpcmDataSourceVersion1 ||
|
||||
commandType == CommandType.AdpcmDataSourceVersion2 ||
|
||||
commandType == CommandType.PcmInt16DataSourceVersion1 ||
|
||||
commandType == CommandType.PcmInt16DataSourceVersion2 ||
|
||||
commandType == CommandType.PcmFloatDataSourceVersion1 ||
|
||||
commandType == CommandType.PcmFloatDataSourceVersion2 ||
|
||||
commandType == CommandType.Performance)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint voiceDropped = 0;
|
||||
|
||||
for (; i < commandBuffer.CommandList.Commands.Count; i++)
|
||||
{
|
||||
ICommand targetCommand = commandBuffer.CommandList.Commands[i];
|
||||
|
||||
int targetNodeId = targetCommand.NodeId;
|
||||
|
||||
if (voicesEstimatedTime <= deltaTimeDsp || NodeIdHelper.GetType(targetNodeId) != NodeIdType.Voice)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ref VoiceState voice = ref _voiceContext.GetState(NodeIdHelper.GetBase(targetNodeId));
|
||||
|
||||
if (voice.Priority == Constants.VoiceHighestPriority)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// We can safely drop this voice, disable all associated commands while activating depop preparation commands.
|
||||
voiceDropped++;
|
||||
voice.VoiceDropFlag = true;
|
||||
|
||||
Logger.Warning?.Print(LogClass.AudioRenderer, $"Dropping voice {voice.NodeId}");
|
||||
|
||||
for (; i < commandBuffer.CommandList.Commands.Count; i++)
|
||||
{
|
||||
ICommand command = commandBuffer.CommandList.Commands[i];
|
||||
|
||||
if (command.NodeId != targetNodeId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (command.CommandType == CommandType.DepopPrepare)
|
||||
{
|
||||
command.Enabled = true;
|
||||
}
|
||||
else if (command.CommandType == CommandType.Performance || !command.Enabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
command.Enabled = false;
|
||||
|
||||
voicesEstimatedTime -= (long)command.EstimatedProcessingTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return voiceDropped;
|
||||
}
|
||||
|
||||
private void GenerateCommandList(out CommandList commandList)
|
||||
{
|
||||
Debug.Assert(_executionMode == AudioRendererExecutionMode.Auto);
|
||||
|
||||
PoolMapper.ClearUsageState(_memoryPools);
|
||||
|
||||
ulong startTicks = GetSystemTicks();
|
||||
|
||||
commandList = new CommandList(this);
|
||||
|
||||
if (_performanceManager != null)
|
||||
{
|
||||
_performanceManager.TapFrame(_isDspRunningBehind, _voiceDropCount, _renderingStartTick);
|
||||
|
||||
_isDspRunningBehind = false;
|
||||
_voiceDropCount = 0;
|
||||
_renderingStartTick = 0;
|
||||
}
|
||||
|
||||
CommandBuffer commandBuffer = new CommandBuffer(commandList, _commandProcessingTimeEstimator);
|
||||
|
||||
CommandGenerator commandGenerator = new CommandGenerator(commandBuffer, GetContext(), _voiceContext, _mixContext, _effectContext, _sinkContext, _splitterContext, _performanceManager);
|
||||
|
||||
_voiceContext.Sort();
|
||||
commandGenerator.GenerateVoices();
|
||||
|
||||
long voicesEstimatedTime = (long)commandBuffer.EstimatedProcessingTime;
|
||||
|
||||
commandGenerator.GenerateSubMixes();
|
||||
commandGenerator.GenerateFinalMixes();
|
||||
commandGenerator.GenerateSinks();
|
||||
|
||||
long totalEstimatedTime = (long)commandBuffer.EstimatedProcessingTime;
|
||||
|
||||
if (_voiceDropEnabled)
|
||||
{
|
||||
long maxDspTime = GetMaxAllocatedTimeForDsp();
|
||||
|
||||
long restEstimateTime = totalEstimatedTime - voicesEstimatedTime;
|
||||
|
||||
long deltaTimeDsp = Math.Max(maxDspTime - restEstimateTime, 0);
|
||||
|
||||
_voiceDropCount = ComputeVoiceDrop(commandBuffer, voicesEstimatedTime, deltaTimeDsp);
|
||||
}
|
||||
|
||||
_voiceContext.UpdateForCommandGeneration();
|
||||
|
||||
ulong endTicks = GetSystemTicks();
|
||||
|
||||
_totalElapsedTicks = endTicks - startTicks;
|
||||
|
||||
_renderingStartTick = GetSystemTicks();
|
||||
_elapsedFrameCount++;
|
||||
}
|
||||
|
||||
private int GetMaxAllocatedTimeForDsp()
|
||||
{
|
||||
return (int)(Constants.AudioProcessorMaxUpdateTimePerSessions * _behaviourContext.GetAudioRendererProcessingTimeLimit() * (GetRenderingTimeLimit() / 100.0f));
|
||||
}
|
||||
|
||||
public void SendCommands()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_isActive)
|
||||
{
|
||||
_terminationEvent.Reset();
|
||||
|
||||
GenerateCommandList(out CommandList commands);
|
||||
|
||||
_manager.Processor.Send(_sessionId,
|
||||
commands,
|
||||
GetMaxAllocatedTimeForDsp(),
|
||||
_appletResourceId);
|
||||
|
||||
_systemEvent.Signal();
|
||||
}
|
||||
else
|
||||
{
|
||||
_terminationEvent.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetMixBufferCount()
|
||||
{
|
||||
return _mixBufferCount;
|
||||
}
|
||||
|
||||
public void SetRenderingTimeLimitPercent(uint percent)
|
||||
{
|
||||
Debug.Assert(percent <= 100);
|
||||
|
||||
_renderingTimeLimitPercent = percent;
|
||||
}
|
||||
|
||||
public uint GetRenderingTimeLimit()
|
||||
{
|
||||
return _renderingTimeLimitPercent;
|
||||
}
|
||||
|
||||
public Memory<float> GetMixBuffer()
|
||||
{
|
||||
return _mixBuffer;
|
||||
}
|
||||
|
||||
public uint GetSampleCount()
|
||||
{
|
||||
return _sampleCount;
|
||||
}
|
||||
|
||||
public uint GetSampleRate()
|
||||
{
|
||||
return _sampleRate;
|
||||
}
|
||||
|
||||
public uint GetVoiceChannelCountMax()
|
||||
{
|
||||
return _voiceChannelCountMax;
|
||||
}
|
||||
|
||||
public bool IsActive()
|
||||
{
|
||||
return _isActive;
|
||||
}
|
||||
|
||||
private RendererSystemContext GetContext()
|
||||
{
|
||||
return new RendererSystemContext
|
||||
{
|
||||
ChannelCount = _manager.Processor.OutputDevices[_sessionId].GetChannelCount(),
|
||||
BehaviourContext = _behaviourContext,
|
||||
DepopBuffer = _depopBuffer,
|
||||
MixBufferCount = GetMixBufferCount(),
|
||||
SessionId = _sessionId,
|
||||
UpsamplerManager = _upsamplerManager
|
||||
};
|
||||
}
|
||||
|
||||
public int GetSessionId()
|
||||
{
|
||||
return _sessionId;
|
||||
}
|
||||
|
||||
public static ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter)
|
||||
{
|
||||
BehaviourContext behaviourContext = new BehaviourContext();
|
||||
|
||||
behaviourContext.SetUserRevision(parameter.Revision);
|
||||
|
||||
uint mixesCount = parameter.SubMixBufferCount + 1;
|
||||
|
||||
uint memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount;
|
||||
|
||||
ulong size = 0;
|
||||
|
||||
// Mix Buffers
|
||||
size = WorkBufferAllocator.GetTargetSize<float>(size, parameter.SampleCount * (Constants.VoiceChannelCountMax + parameter.MixBufferCount), 0x10);
|
||||
|
||||
// Upsampler workbuffer
|
||||
size = WorkBufferAllocator.GetTargetSize<float>(size, Constants.TargetSampleCount * (Constants.VoiceChannelCountMax + parameter.MixBufferCount) * (parameter.SinkCount + parameter.SubMixBufferCount), 0x10);
|
||||
|
||||
// Depop buffer
|
||||
size = WorkBufferAllocator.GetTargetSize<float>(size, (ulong)BitUtils.AlignUp(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment);
|
||||
|
||||
// Voice
|
||||
size = WorkBufferAllocator.GetTargetSize<VoiceState>(size, parameter.VoiceCount, VoiceState.Alignment);
|
||||
size = WorkBufferAllocator.GetTargetSize<int>(size, parameter.VoiceCount, 0x10);
|
||||
size = WorkBufferAllocator.GetTargetSize<VoiceChannelResource>(size, parameter.VoiceCount, VoiceChannelResource.Alignment);
|
||||
size = WorkBufferAllocator.GetTargetSize<VoiceUpdateState>(size, parameter.VoiceCount, VoiceUpdateState.Align);
|
||||
|
||||
// Mix
|
||||
size = WorkBufferAllocator.GetTargetSize<MixState>(size, mixesCount, MixState.Alignment);
|
||||
size = WorkBufferAllocator.GetTargetSize<int>(size, parameter.EffectCount * mixesCount, 0x10);
|
||||
size = WorkBufferAllocator.GetTargetSize<int>(size, mixesCount, 0x10);
|
||||
|
||||
if (behaviourContext.IsSplitterSupported())
|
||||
{
|
||||
size += (ulong)BitUtils.AlignUp(NodeStates.GetWorkBufferSize((int)mixesCount) + EdgeMatrix.GetWorkBufferSize((int)mixesCount), 0x10);
|
||||
}
|
||||
|
||||
// Memory Pool
|
||||
size = WorkBufferAllocator.GetTargetSize<MemoryPoolState>(size, memoryPoolCount, MemoryPoolState.Alignment);
|
||||
|
||||
// Splitter
|
||||
size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter);
|
||||
|
||||
// DSP Voice
|
||||
size = WorkBufferAllocator.GetTargetSize<VoiceUpdateState>(size, parameter.VoiceCount, VoiceUpdateState.Align);
|
||||
|
||||
// Performance
|
||||
if (parameter.PerformanceMetricFramesCount > 0)
|
||||
{
|
||||
ulong performanceMetricsPerFramesSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC;
|
||||
|
||||
size += BitUtils.AlignUp(performanceMetricsPerFramesSize, Constants.PerformanceMetricsPerFramesSizeAlignment);
|
||||
}
|
||||
|
||||
return BitUtils.AlignUp(size, Constants.WorkBufferAlignment);
|
||||
}
|
||||
|
||||
public ResultCode QuerySystemEvent(out IWritableEvent systemEvent)
|
||||
{
|
||||
systemEvent = default;
|
||||
|
||||
if (_executionMode == AudioRendererExecutionMode.Manual)
|
||||
{
|
||||
return ResultCode.UnsupportedOperation;
|
||||
}
|
||||
|
||||
systemEvent = _systemEvent;
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (_isActive)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
PoolMapper mapper = new PoolMapper(_processHandle, false);
|
||||
mapper.Unmap(ref _dspMemoryPoolState);
|
||||
|
||||
PoolMapper.ClearUsageState(_memoryPools);
|
||||
|
||||
for (int i = 0; i < _memoryPoolCount; i++)
|
||||
{
|
||||
ref MemoryPoolState memoryPool = ref _memoryPools.Span[i];
|
||||
|
||||
if (memoryPool.IsMapped())
|
||||
{
|
||||
mapper.Unmap(ref memoryPool);
|
||||
}
|
||||
}
|
||||
|
||||
_manager.Unregister(this);
|
||||
_terminationEvent.Dispose();
|
||||
_workBufferMemoryPin.Dispose();
|
||||
_workBufferRegion.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
334
Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
Normal file
334
Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
Normal file
|
@ -0,0 +1,334 @@
|
|||
//
|
||||
// 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.Integration;
|
||||
using Ryujinx.Audio.Renderer.Dsp;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// The audio renderer manager.
|
||||
/// </summary>
|
||||
public class AudioRendererManager : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Lock used for session allocation.
|
||||
/// </summary>
|
||||
private object _sessionLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Lock used to control the <see cref="AudioProcessor"/> running state.
|
||||
/// </summary>
|
||||
private object _audioProcessorLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// The session ids allocation table.
|
||||
/// </summary>
|
||||
private int[] _sessionIds;
|
||||
|
||||
/// <summary>
|
||||
/// The events linked to each session.
|
||||
/// </summary>
|
||||
private IWritableEvent[] _sessionsSystemEvent;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AudioRenderSystem"/> sessions instances.
|
||||
/// </summary>
|
||||
private AudioRenderSystem[] _sessions;
|
||||
|
||||
/// <summary>
|
||||
/// The count of active sessions.
|
||||
/// </summary>
|
||||
private int _activeSessionCount;
|
||||
|
||||
/// <summary>
|
||||
/// The worker thread used to run <see cref="SendCommands"/>.
|
||||
/// </summary>
|
||||
private Thread _workerThread;
|
||||
|
||||
/// <summary>
|
||||
/// Indicate if the worker thread and <see cref="AudioProcessor"/> are running.
|
||||
/// </summary>
|
||||
private bool _isRunning;
|
||||
|
||||
/// <summary>
|
||||
/// The audio device driver to create audio outputs.
|
||||
/// </summary>
|
||||
private IHardwareDeviceDriver _deviceDriver;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AudioProcessor"/> instance associated to this manager.
|
||||
/// </summary>
|
||||
public AudioProcessor Processor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="AudioRendererManager"/>.
|
||||
/// </summary>
|
||||
public AudioRendererManager()
|
||||
{
|
||||
Processor = new AudioProcessor();
|
||||
_sessionIds = new int[Constants.AudioRendererSessionCountMax];
|
||||
_sessions = new AudioRenderSystem[Constants.AudioRendererSessionCountMax];
|
||||
_activeSessionCount = 0;
|
||||
|
||||
for (int i = 0; i < _sessionIds.Length; i++)
|
||||
{
|
||||
_sessionIds[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the <see cref="AudioRendererManager"/>.
|
||||
/// </summary>
|
||||
/// <param name="sessionSystemEvents">The events associated to each session.</param>
|
||||
/// <param name="deviceDriver">The device driver to use to create audio outputs.</param>
|
||||
public void Initialize(IWritableEvent[] sessionSystemEvents, IHardwareDeviceDriver deviceDriver)
|
||||
{
|
||||
_sessionsSystemEvent = sessionSystemEvents;
|
||||
_deviceDriver = deviceDriver;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the work buffer size required by a session.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The user configuration</param>
|
||||
/// <returns>The work buffer size required by a session.</returns>
|
||||
public static ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter)
|
||||
{
|
||||
return AudioRenderSystem.GetWorkBufferSize(ref parameter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Acquire a new session id.
|
||||
/// </summary>
|
||||
/// <returns>A new session id.</returns>
|
||||
private int AcquireSessionId()
|
||||
{
|
||||
lock (_sessionLock)
|
||||
{
|
||||
int index = _activeSessionCount;
|
||||
|
||||
Debug.Assert(index < _sessionIds.Length);
|
||||
|
||||
int sessionId = _sessionIds[index];
|
||||
|
||||
_sessionIds[index] = -1;
|
||||
|
||||
_activeSessionCount++;
|
||||
|
||||
Logger.Info?.Print(LogClass.AudioRenderer, $"Registered new renderer ({sessionId})");
|
||||
|
||||
return sessionId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release a given <paramref name="sessionId"/>.
|
||||
/// </summary>
|
||||
/// <param name="sessionId">The session id to release.</param>
|
||||
private void ReleaseSessionId(int sessionId)
|
||||
{
|
||||
lock (_sessionLock)
|
||||
{
|
||||
Debug.Assert(_activeSessionCount > 0);
|
||||
|
||||
int newIndex = --_activeSessionCount;
|
||||
|
||||
_sessionIds[newIndex] = sessionId;
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.AudioRenderer, $"Unregistered renderer ({sessionId})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if there is any audio renderer active.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if there is any audio renderer active.</returns>
|
||||
private bool HasAnyActiveRendererLocked()
|
||||
{
|
||||
foreach (AudioRenderSystem renderer in _sessions)
|
||||
{
|
||||
if (renderer != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the <see cref="AudioProcessor"/> and worker thread.
|
||||
/// </summary>
|
||||
private void StartLocked()
|
||||
{
|
||||
_isRunning = true;
|
||||
|
||||
// TODO: virtual device mapping (IAudioDevice)
|
||||
Processor.Start(_deviceDriver);
|
||||
|
||||
_workerThread = new Thread(SendCommands)
|
||||
{
|
||||
Name = "AudioRendererManager.Worker"
|
||||
};
|
||||
|
||||
_workerThread.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the <see cref="AudioProcessor"/> and worker thread.
|
||||
/// </summary>
|
||||
private void StopLocked()
|
||||
{
|
||||
_isRunning = false;
|
||||
|
||||
_workerThread.Join();
|
||||
Processor.Stop();
|
||||
|
||||
Logger.Info?.Print(LogClass.AudioRenderer, "Stopped audio renderer");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker main function. This is used to dispatch audio renderer commands to the <see cref="AudioProcessor"/>.
|
||||
/// </summary>
|
||||
private void SendCommands()
|
||||
{
|
||||
Logger.Info?.Print(LogClass.AudioRenderer, "Starting audio renderer");
|
||||
Processor.Wait();
|
||||
|
||||
while (_isRunning)
|
||||
{
|
||||
lock (_sessionLock)
|
||||
{
|
||||
foreach(AudioRenderSystem renderer in _sessions)
|
||||
{
|
||||
renderer?.SendCommands();
|
||||
}
|
||||
}
|
||||
|
||||
Processor.Signal();
|
||||
Processor.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a new <see cref="AudioRenderSystem"/>.
|
||||
/// </summary>
|
||||
/// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
|
||||
private void Register(AudioRenderSystem renderer)
|
||||
{
|
||||
lock (_sessionLock)
|
||||
{
|
||||
_sessions[renderer.GetSessionId()] = renderer;
|
||||
}
|
||||
|
||||
lock (_audioProcessorLock)
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
StartLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister a new <see cref="AudioRenderSystem"/>.
|
||||
/// </summary>
|
||||
/// <param name="renderer">The <see cref="AudioRenderSystem"/> to unregister.</param>
|
||||
internal void Unregister(AudioRenderSystem renderer)
|
||||
{
|
||||
lock (_sessionLock)
|
||||
{
|
||||
int sessionId = renderer.GetSessionId();
|
||||
|
||||
_sessions[renderer.GetSessionId()] = null;
|
||||
|
||||
ReleaseSessionId(sessionId);
|
||||
}
|
||||
|
||||
lock (_audioProcessorLock)
|
||||
{
|
||||
if (_isRunning && !HasAnyActiveRendererLocked())
|
||||
{
|
||||
StopLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open a new <see cref="AudioRenderSystem"/>
|
||||
/// </summary>
|
||||
/// <param name="renderer">The new <see cref="AudioRenderSystem"/></param>
|
||||
/// <param name="memoryManager">The memory manager that will be used for all guest memory operations.</param>
|
||||
/// <param name="parameter">The user configuration</param>
|
||||
/// <param name="appletResourceUserId">The applet resource user id of the application.</param>
|
||||
/// <param name="workBufferAddress">The guest work buffer address.</param>
|
||||
/// <param name="workBufferSize">The guest work buffer size.</param>
|
||||
/// <param name="processHandle">The process handle of the application.</param>
|
||||
/// <returns>A <see cref="ResultCode"/> reporting an error or a success.</returns>
|
||||
public ResultCode OpenAudioRenderer(out AudioRenderSystem renderer, IVirtualMemoryManager memoryManager, ref AudioRendererConfiguration parameter, ulong appletResourceUserId, ulong workBufferAddress, ulong workBufferSize, uint processHandle)
|
||||
{
|
||||
int sessionId = AcquireSessionId();
|
||||
|
||||
AudioRenderSystem audioRenderer = new AudioRenderSystem(this, _sessionsSystemEvent[sessionId]);
|
||||
|
||||
ResultCode result = audioRenderer.Initialize(ref parameter, processHandle, workBufferAddress, workBufferSize, sessionId, appletResourceUserId, memoryManager);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
renderer = audioRenderer;
|
||||
|
||||
Register(renderer);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReleaseSessionId(sessionId);
|
||||
|
||||
renderer = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
lock (_audioProcessorLock)
|
||||
{
|
||||
if (_isRunning)
|
||||
{
|
||||
StopLocked();
|
||||
}
|
||||
}
|
||||
|
||||
Processor.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
405
Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
Normal file
405
Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
Normal file
|
@ -0,0 +1,405 @@
|
|||
//
|
||||
// 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 System;
|
||||
using System.Diagnostics;
|
||||
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Behaviour context.
|
||||
/// </summary>
|
||||
/// <remarks>This handles features based on the audio renderer revision provided by the user.</remarks>
|
||||
public class BehaviourContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The base magic of the Audio Renderer revision.
|
||||
/// </summary>
|
||||
public const int BaseRevisionMagic = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('0' << 24);
|
||||
|
||||
/// <summary>
|
||||
/// REV1: first revision.
|
||||
/// </summary>
|
||||
public const int Revision1 = 1 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// REV2: Added support for splitter and fix GC-ADPCM context not being provided to the DSP.
|
||||
/// </summary>
|
||||
/// <remarks>This was added in system update 2.0.0</remarks>
|
||||
public const int Revision2 = 2 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// REV3: Incremented the max pre-delay from 150 to 350 for the reverb command and removed the (unused) codec system.
|
||||
/// </summary>
|
||||
/// <remarks>This was added in system update 3.0.0</remarks>
|
||||
public const int Revision3 = 3 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// REV4: Added USB audio device support and incremented the rendering limit percent to 75%.
|
||||
/// </summary>
|
||||
/// <remarks>This was added in system update 4.0.0</remarks>
|
||||
public const int Revision4 = 4 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// REV5: <see cref="Parameter.VoiceInParameter.DecodingBehaviour"/>, <see cref="Parameter.VoiceInParameter.FlushWaveBufferCount"/> were added to voice.
|
||||
/// A new performance frame format (version 2) was added with support for more information about DSP timing.
|
||||
/// <see cref="Parameter.RendererInfoOutStatus"/> was added to supply the count of update done sent to the DSP.
|
||||
/// A new version of the command estimator was added to address timing changes caused by the voice changes.
|
||||
/// Additionally, the rendering limit percent was incremented to 80%.
|
||||
///
|
||||
/// </summary>
|
||||
/// <remarks>This was added in system update 6.0.0</remarks>
|
||||
public const int Revision5 = 5 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// REV6: This fixed a bug in the biquad filter command not clearing up <see cref="Dsp.State.BiquadFilterState"/> with <see cref="Effect.UsageState.New"/> usage state.
|
||||
/// </summary>
|
||||
/// <remarks>This was added in system update 6.1.0</remarks>
|
||||
public const int Revision6 = 6 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// REV7: Client side (finally) doesn't send all the mix client state to the server and can do partial updates.
|
||||
/// </summary>
|
||||
/// <remarks>This was added in system update 8.0.0</remarks>
|
||||
public const int Revision7 = 7 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// REV8:
|
||||
/// Wavebuffer was changed to support more control over loop (you can now specify where to start and end a loop, and how many times to loop).
|
||||
/// <see cref="Parameter.VoiceInParameter.SrcQuality"/> was added (see <see cref="Parameter.VoiceInParameter.SampleRateConversionQuality"/> for more info).
|
||||
/// Final leftovers of the codec system were removed.
|
||||
/// <see cref="Common.SampleFormat.PcmFloat"/> support was added.
|
||||
/// A new version of the command estimator was added to address timing changes caused by the voice and command changes.
|
||||
/// </summary>
|
||||
/// <remarks>This was added in system update 9.0.0</remarks>
|
||||
public const int Revision8 = 8 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// Last revision supported by the implementation.
|
||||
/// </summary>
|
||||
public const int LastRevision = Revision8;
|
||||
|
||||
/// <summary>
|
||||
/// Target revision magic supported by the implementation.
|
||||
/// </summary>
|
||||
public const int ProcessRevision = BaseRevisionMagic + LastRevision;
|
||||
|
||||
/// <summary>
|
||||
/// Get the revision number from the revision magic.
|
||||
/// </summary>
|
||||
/// <param name="revision">The revision magic.</param>
|
||||
/// <returns>The revision number.</returns>
|
||||
public static int GetRevisionNumber(int revision) => (revision - BaseRevisionMagic) >> 24;
|
||||
|
||||
/// <summary>
|
||||
/// Current active revision.
|
||||
/// </summary>
|
||||
public int UserRevision { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Error storage.
|
||||
/// </summary>
|
||||
private ErrorInfo[] _errorInfos;
|
||||
|
||||
/// <summary>
|
||||
/// Current position in the <see cref="_errorInfos"/> array.
|
||||
/// </summary>
|
||||
private uint _errorIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Current flags of the <see cref="BehaviourContext"/>.
|
||||
/// </summary>
|
||||
private ulong _flags;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance of <see cref="BehaviourContext"/>.
|
||||
/// </summary>
|
||||
public BehaviourContext()
|
||||
{
|
||||
UserRevision = 0;
|
||||
_errorInfos = new ErrorInfo[Constants.MaxErrorInfos];
|
||||
_errorIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the active revision.
|
||||
/// </summary>
|
||||
/// <param name="userRevision">The active revision.</param>
|
||||
public void SetUserRevision(int userRevision)
|
||||
{
|
||||
UserRevision = userRevision;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update flags of the <see cref="BehaviourContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="flags">The new flags.</param>
|
||||
public void UpdateFlags(ulong flags)
|
||||
{
|
||||
_flags = flags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a given revision is valid/supported.
|
||||
/// </summary>
|
||||
/// <param name="revision">The revision magic to check.</param>
|
||||
/// <returns>Returns true if the given revision is valid/supported</returns>
|
||||
public static bool CheckValidRevision(int revision)
|
||||
{
|
||||
return GetRevisionNumber(revision) <= GetRevisionNumber(ProcessRevision);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given revision is greater than or equal the supported revision.
|
||||
/// </summary>
|
||||
/// <param name="revision">The revision magic to check.</param>
|
||||
/// <param name="supportedRevision">The revision magic of the supported revision.</param>
|
||||
/// <returns>Returns true if the given revision is greater than or equal the supported revision.</returns>
|
||||
public static bool CheckFeatureSupported(int revision, int supportedRevision)
|
||||
{
|
||||
int revA = GetRevisionNumber(revision);
|
||||
int revB = GetRevisionNumber(supportedRevision);
|
||||
|
||||
if (revA > LastRevision)
|
||||
{
|
||||
revA = 1;
|
||||
}
|
||||
|
||||
if (revB > LastRevision)
|
||||
{
|
||||
revB = 1;
|
||||
}
|
||||
|
||||
return revA >= revB;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the memory pool mapping bypass flag is active.
|
||||
/// </summary>
|
||||
/// <returns>True if the memory pool mapping bypass flag is active.</returns>
|
||||
public bool IsMemoryPoolForceMappingEnabled()
|
||||
{
|
||||
return (_flags & 1) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer should fix the GC-ADPCM context not being provided to the DSP.
|
||||
/// </summary>
|
||||
/// <returns>True if if the audio renderer should fix it.</returns>
|
||||
public bool IsAdpcmLoopContextBugFixed()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer should accept splitters.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer should accept splitters.</returns>
|
||||
public bool IsSplitterSupported()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer should use a max pre-delay of 350 instead of 150.
|
||||
/// </summary>
|
||||
/// <returns>True if the max pre-delay must be 350.</returns>
|
||||
public bool IsLongSizePreDelaySupported()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer should expose USB audio device.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer should expose USB audio device.</returns>
|
||||
public bool IsAudioUsbDeviceOutputSupported()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the percentage allocated to the audio renderer on the DSP for processing.
|
||||
/// </summary>
|
||||
/// <returns>The percentage allocated to the audio renderer on the DSP for processing.</returns>
|
||||
public float GetAudioRendererProcessingTimeLimit()
|
||||
{
|
||||
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5))
|
||||
{
|
||||
return 0.80f;
|
||||
}
|
||||
else if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision4))
|
||||
{
|
||||
return 0.75f;
|
||||
}
|
||||
|
||||
return 0.70f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio render should support voice flushing.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio render should support voice flushing.</returns>
|
||||
public bool IsFlushVoiceWaveBuffersSupported()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5);
|
||||
}
|
||||
|
||||
/// <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})"/>.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer should trust the user destination count.</returns>
|
||||
public bool IsSplitterBugFixed()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer should supply the elapsed frame count to the user when updating.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer should supply the elapsed frame count to the user when updating.</returns>
|
||||
public bool IsElapsedFrameCountSupported()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the performance metric data format version.
|
||||
/// </summary>
|
||||
/// <returns>The performance metric data format version.</returns>
|
||||
public uint GetPerformanceMetricsDataFormat()
|
||||
{
|
||||
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer should support <see cref="Parameter.VoiceInParameter.DecodingBehaviour"/>.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer should support <see cref="Parameter.VoiceInParameter.DecodingBehaviour"/>.</returns>
|
||||
public bool IsDecodingBehaviourFlagSupported()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer should fix the biquad filter command not clearing up <see cref="Dsp.State.BiquadFilterState"/> with <see cref="Effect.UsageState.New"/> usage state.
|
||||
/// </summary>
|
||||
/// <returns>True if the biquad filter state should be cleared.</returns>
|
||||
public bool IsBiquadFilterEffectStateClearBugFixed()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision6);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer should accept partial mix updates.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer should accept partial mix updates.</returns>
|
||||
public bool IsMixInParameterDirtyOnlyUpdateSupported()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision7);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer should use the new wavebuffer format.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer should use the new wavebuffer format.</returns>
|
||||
public bool IsWaveBufferVersion2Supported()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8);
|
||||
}
|
||||
|
||||
/// <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 + Revision8))
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Append a new <see cref="ErrorInfo"/> to the error array.
|
||||
/// </summary>
|
||||
/// <param name="errorInfo">The new <see cref="ErrorInfo"/> to add.</param>
|
||||
public void AppendError(ref ErrorInfo errorInfo)
|
||||
{
|
||||
Debug.Assert(errorInfo.ErrorCode == ResultCode.Success);
|
||||
|
||||
if (_errorIndex <= Constants.MaxErrorInfos - 1)
|
||||
{
|
||||
_errorInfos[_errorIndex++] = errorInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy the internal <see cref="ErrorInfo"/> array to the given <see cref="Span{ErrorInfo}"/> and output the count copied.
|
||||
/// </summary>
|
||||
/// <param name="errorInfos">The output <see cref="Span{ErrorInfo}"/>.</param>
|
||||
/// <param name="errorCount">The output error count containing the count of <see cref="ErrorInfo"/> copied.</param>
|
||||
public void CopyErrorInfo(Span<ErrorInfo> errorInfos, out uint errorCount)
|
||||
{
|
||||
if (errorInfos.Length != Constants.MaxErrorInfos)
|
||||
{
|
||||
throw new ArgumentException("Invalid size of errorInfos span!");
|
||||
}
|
||||
|
||||
errorCount = Math.Min(_errorIndex, Constants.MaxErrorInfos);
|
||||
|
||||
for (int i = 0; i < Constants.MaxErrorInfos; i++)
|
||||
{
|
||||
if (i < errorCount)
|
||||
{
|
||||
errorInfos[i] = _errorInfos[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
errorInfos[i] = new ErrorInfo
|
||||
{
|
||||
ErrorCode = 0,
|
||||
ExtraErrorInfo = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the <see cref="ErrorInfo"/> array.
|
||||
/// </summary>
|
||||
public void ClearError()
|
||||
{
|
||||
_errorIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
484
Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
Normal file
484
Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
Normal file
|
@ -0,0 +1,484 @@
|
|||
//
|
||||
// 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.Command;
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
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 System;
|
||||
using CpuAddress = System.UInt64;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// An API to generate commands and aggregate them into a <see cref="CommandList"/>.
|
||||
/// </summary>
|
||||
public class CommandBuffer
|
||||
{
|
||||
/// <summary>
|
||||
/// The command processing time estimator in use.
|
||||
/// </summary>
|
||||
private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator;
|
||||
|
||||
/// <summary>
|
||||
/// The estimated total processing time.
|
||||
/// </summary>
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The command list that is populated by the <see cref="CommandBuffer"/>.
|
||||
/// </summary>
|
||||
public CommandList CommandList { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="CommandBuffer"/>.
|
||||
/// </summary>
|
||||
/// <param name="commandList">The command list that will store the generated commands.</param>
|
||||
/// <param name="commandProcessingTimeEstimator">The command processing time estimator to use.</param>
|
||||
public CommandBuffer(CommandList commandList, ICommandProcessingTimeEstimator commandProcessingTimeEstimator)
|
||||
{
|
||||
CommandList = commandList;
|
||||
EstimatedProcessingTime = 0;
|
||||
_commandProcessingTimeEstimator = commandProcessingTimeEstimator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new generated command to the <see cref="CommandList"/>.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to add.</param>
|
||||
private void AddCommand(ICommand command)
|
||||
{
|
||||
EstimatedProcessingTime += command.EstimatedProcessingTime;
|
||||
|
||||
CommandList.AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="ClearMixBufferCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateClearMixBuffer(int nodeId)
|
||||
{
|
||||
ClearMixBufferCommand command = new ClearMixBufferCommand(nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="DepopPrepareCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="state">The voice state associated.</param>
|
||||
/// <param name="depopBuffer">The depop buffer.</param>
|
||||
/// <param name="bufferCount">The buffer count.</param>
|
||||
/// <param name="bufferOffset">The target buffer offset.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
/// <param name="wasPlaying">Set to true if the voice was playing previously.</param>
|
||||
public void GenerateDepopPrepare(Memory<VoiceUpdateState> state, Memory<float> depopBuffer, uint bufferCount, uint bufferOffset, int nodeId, bool wasPlaying)
|
||||
{
|
||||
DepopPrepareCommand command = new DepopPrepareCommand(state, depopBuffer, bufferCount, bufferOffset, nodeId, wasPlaying);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="PerformanceCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="performanceEntryAddresses">The <see cref="PerformanceEntryAddresses"/>.</param>
|
||||
/// <param name="type">The performance operation to perform.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GeneratePerformance(ref PerformanceEntryAddresses performanceEntryAddresses, PerformanceCommand.Type type, int nodeId)
|
||||
{
|
||||
PerformanceCommand command = new PerformanceCommand(ref performanceEntryAddresses, type, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="VolumeRampCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="previousVolume">The previous volume.</param>
|
||||
/// <param name="volume">The new volume.</param>
|
||||
/// <param name="bufferIndex">The index of the mix buffer to use.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateVolumeRamp(float previousVolume, float volume, uint bufferIndex, int nodeId)
|
||||
{
|
||||
VolumeRampCommand command = new VolumeRampCommand(previousVolume, volume, bufferIndex, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="DataSourceVersion2Command"/>.
|
||||
/// </summary>
|
||||
/// <param name="voiceState">The <see cref="VoiceState"/> to generate the command from.</param>
|
||||
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
||||
/// <param name="outputBufferIndex">The output buffer index to use.</param>
|
||||
/// <param name="channelIndex">The target channel index.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateDataSourceVersion2(ref VoiceState voiceState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
|
||||
{
|
||||
DataSourceVersion2Command command = new DataSourceVersion2Command(ref voiceState, state, outputBufferIndex, channelIndex, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="PcmInt16DataSourceCommandVersion1"/>.
|
||||
/// </summary>
|
||||
/// <param name="voiceState">The <see cref="VoiceState"/> to generate the command from.</param>
|
||||
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
||||
/// <param name="outputBufferIndex">The output buffer index to use.</param>
|
||||
/// <param name="channelIndex">The target channel index.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GeneratePcmInt16DataSourceVersion1(ref VoiceState voiceState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
|
||||
{
|
||||
PcmInt16DataSourceCommandVersion1 command = new PcmInt16DataSourceCommandVersion1(ref voiceState, state, outputBufferIndex, channelIndex, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="PcmFloatDataSourceCommandVersion1"/>.
|
||||
/// </summary>
|
||||
/// <param name="voiceState">The <see cref="VoiceState"/> to generate the command from.</param>
|
||||
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
||||
/// <param name="outputBufferIndex">The output buffer index to use.</param>
|
||||
/// <param name="channelIndex">The target channel index.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GeneratePcmFloatDataSourceVersion1(ref VoiceState voiceState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
|
||||
{
|
||||
PcmFloatDataSourceCommandVersion1 command = new PcmFloatDataSourceCommandVersion1(ref voiceState, state, outputBufferIndex, channelIndex, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="AdpcmDataSourceCommandVersion1"/>.
|
||||
/// </summary>
|
||||
/// <param name="voiceState">The <see cref="VoiceState"/> to generate the command from.</param>
|
||||
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
||||
/// <param name="outputBufferIndex">The output buffer index to use.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateAdpcmDataSourceVersion1(ref VoiceState voiceState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, int nodeId)
|
||||
{
|
||||
AdpcmDataSourceCommandVersion1 command = new AdpcmDataSourceCommandVersion1(ref voiceState, state, outputBufferIndex, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="BiquadFilterCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="baseIndex">The base index of the input and output buffer.</param>
|
||||
/// <param name="filter">The biquad filter parameter.</param>
|
||||
/// <param name="biquadFilterStateMemory">The biquad state.</param>
|
||||
/// <param name="inputBufferOffset">The input buffer offset.</param>
|
||||
/// <param name="outputBufferOffset">The output buffer offset.</param>
|
||||
/// <param name="needInitialization">Set to true if the biquad filter state needs to be initialized.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateBiquadFilter(int baseIndex, ref BiquadFilterParameter filter, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId)
|
||||
{
|
||||
BiquadFilterCommand command = new BiquadFilterCommand(baseIndex, ref filter, biquadFilterStateMemory, inputBufferOffset, outputBufferOffset, needInitialization, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="MixRampGroupedCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="mixBufferCount">The mix buffer count.</param>
|
||||
/// <param name="inputBufferIndex">The base input index.</param>
|
||||
/// <param name="outputBufferIndex">The base output index.</param>
|
||||
/// <param name="previousVolume">The previous volume.</param>
|
||||
/// <param name="volume">The new volume.</param>
|
||||
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span<float> previousVolume, Span<float> volume, Memory<VoiceUpdateState> state, int nodeId)
|
||||
{
|
||||
MixRampGroupedCommand command = new MixRampGroupedCommand(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="MixRampCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="previousVolume">The previous volume.</param>
|
||||
/// <param name="volume">The new volume.</param>
|
||||
/// <param name="inputBufferIndex">The input buffer index.</param>
|
||||
/// <param name="outputBufferIndex">The output buffer index.</param>
|
||||
/// <param name="lastSampleIndex">The index in the <see cref="VoiceUpdateState.LastSamples"/> array to store the ramped sample.</param>
|
||||
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateMixRamp(float previousVolume, float volume, uint inputBufferIndex, uint outputBufferIndex, int lastSampleIndex, Memory<VoiceUpdateState> state, int nodeId)
|
||||
{
|
||||
MixRampCommand command = new MixRampCommand(previousVolume, volume, inputBufferIndex, outputBufferIndex, lastSampleIndex, state, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="DepopForMixBuffersCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="depopBuffer">The depop buffer.</param>
|
||||
/// <param name="bufferOffset">The target buffer offset.</param>
|
||||
/// <param name="bufferCount">The buffer count.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
/// <param name="sampleRate">The target sample rate in use.</param>
|
||||
public void GenerateDepopForMixBuffersCommand(Memory<float> depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate)
|
||||
{
|
||||
DepopForMixBuffersCommand command = new DepopForMixBuffersCommand(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="CopyMixBufferCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="inputBufferIndex">The input buffer index.</param>
|
||||
/// <param name="outputBufferIndex">The output buffer index.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateCopyMixBuffer(uint inputBufferIndex, uint outputBufferIndex, int nodeId)
|
||||
{
|
||||
CopyMixBufferCommand command = new CopyMixBufferCommand(inputBufferIndex, outputBufferIndex, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="MixCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="inputBufferIndex">The input buffer index.</param>
|
||||
/// <param name="outputBufferIndex">The output buffer index.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
/// <param name="volume">The mix volume.</param>
|
||||
public void GenerateMix(uint inputBufferIndex, uint outputBufferIndex, int nodeId, float volume)
|
||||
{
|
||||
MixCommand command = new MixCommand(inputBufferIndex, outputBufferIndex, nodeId, volume);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="ReverbCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="bufferOffset">The target buffer offset.</param>
|
||||
/// <param name="parameter">The reverb parameter.</param>
|
||||
/// <param name="state">The reverb state.</param>
|
||||
/// <param name="isEnabled">Set to true if the effect should be active.</param>
|
||||
/// <param name="workBuffer">The work buffer to use for processing.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
/// <param name="isLongSizePreDelaySupported">If set to true, the long size pre-delay is supported.</param>
|
||||
public void GenerateReverbEffect(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool isLongSizePreDelaySupported)
|
||||
{
|
||||
if (parameter.IsChannelCountValid())
|
||||
{
|
||||
ReverbCommand command = new ReverbCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, isLongSizePreDelaySupported);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="Reverb3dCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="bufferOffset">The target buffer offset.</param>
|
||||
/// <param name="parameter">The reverb 3d parameter.</param>
|
||||
/// <param name="state">The reverb 3d state.</param>
|
||||
/// <param name="isEnabled">Set to true if the effect should be active.</param>
|
||||
/// <param name="workBuffer">The work buffer to use for processing.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateReverb3dEffect(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, CpuAddress workBuffer, int nodeId)
|
||||
{
|
||||
if (parameter.IsChannelCountValid())
|
||||
{
|
||||
Reverb3dCommand command = new Reverb3dCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="DelayCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="bufferOffset">The target buffer offset.</param>
|
||||
/// <param name="parameter">The delay parameter.</param>
|
||||
/// <param name="state">The delay state.</param>
|
||||
/// <param name="isEnabled">Set to true if the effect should be active.</param>
|
||||
/// <param name="workBuffer">The work buffer to use for processing.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateDelayEffect(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, CpuAddress workBuffer, int nodeId)
|
||||
{
|
||||
if (parameter.IsChannelCountValid())
|
||||
{
|
||||
DelayCommand command = new DelayCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="AuxiliaryBufferCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="bufferOffset">The target buffer offset.</param>
|
||||
/// <param name="inputBufferOffset">The input buffer offset.</param>
|
||||
/// <param name="outputBufferOffset">The output buffer offset.</param>
|
||||
/// <param name="state">The aux 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="inputBuffer">The guest address of the input buffer.</param>
|
||||
/// <param name="updateCount">The count to add on the offset after write/read operations.</param>
|
||||
/// <param name="writeOffset">The write offset.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateAuxEffect(uint bufferOffset, byte inputBufferOffset, byte outputBufferOffset, ref AuxiliaryBufferAddresses state, bool isEnabled, uint countMax, CpuAddress outputBuffer, CpuAddress inputBuffer, uint updateCount, uint writeOffset, int nodeId)
|
||||
{
|
||||
if (state.SendBufferInfoBase != 0 && state.ReturnBufferInfoBase != 0)
|
||||
{
|
||||
AuxiliaryBufferCommand command = new AuxiliaryBufferCommand(bufferOffset, inputBufferOffset, outputBufferOffset, ref state, isEnabled, countMax, outputBuffer, inputBuffer, updateCount, writeOffset, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="VolumeCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="volume">The target volume to apply.</param>
|
||||
/// <param name="bufferOffset">The offset of the mix buffer.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateVolume(float volume, uint bufferOffset, int nodeId)
|
||||
{
|
||||
VolumeCommand command = new VolumeCommand(volume, bufferOffset, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="CircularBufferSinkCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="bufferOffset">The offset of the mix buffer.</param>
|
||||
/// <param name="sink">The <see cref="BaseSink"/> of the circular buffer.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateCircularBuffer(uint bufferOffset, CircularBufferSink sink, int nodeId)
|
||||
{
|
||||
CircularBufferSinkCommand command = new CircularBufferSinkCommand(bufferOffset, ref sink.Parameter, ref sink.CircularBufferAddressInfo, sink.CurrentWriteOffset, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="DownMixSurroundToStereoCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="bufferOffset">The offset of the mix buffer.</param>
|
||||
/// <param name="inputBufferOffset">The input buffer offset.</param>
|
||||
/// <param name="outputBufferOffset">The output buffer offset.</param>
|
||||
/// <param name="downMixParameter">The downmixer parameters to use.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateDownMixSurroundToStereo(uint bufferOffset, Span<byte> inputBufferOffset, Span<byte> outputBufferOffset, ReadOnlySpan<float> downMixParameter, int nodeId)
|
||||
{
|
||||
DownMixSurroundToStereoCommand command = new DownMixSurroundToStereoCommand(bufferOffset, inputBufferOffset, outputBufferOffset, downMixParameter, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="UpsampleCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="bufferOffset">The offset of the mix buffer.</param>
|
||||
/// <param name="upsampler">The <see cref="UpsamplerState"/> associated.</param>
|
||||
/// <param name="inputCount">The total input count.</param>
|
||||
/// <param name="inputBufferOffset">The input buffer mix offset.</param>
|
||||
/// <param name="bufferCountPerSample">The buffer count per sample.</param>
|
||||
/// <param name="sampleCount">The source sample count.</param>
|
||||
/// <param name="sampleRate">The source sample rate.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateUpsample(uint bufferOffset, UpsamplerState upsampler, uint inputCount, Span<byte> inputBufferOffset, uint bufferCountPerSample, uint sampleCount, uint sampleRate, int nodeId)
|
||||
{
|
||||
UpsampleCommand command = new UpsampleCommand(bufferOffset, upsampler, inputCount, inputBufferOffset, bufferCountPerSample, sampleCount, sampleRate, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="DeviceSinkCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="bufferOffset">The offset of the mix buffer.</param>
|
||||
/// <param name="sink">The <see cref="BaseSink"/> of the device sink.</param>
|
||||
/// <param name="sessionId">The current audio renderer session id.</param>
|
||||
/// <param name="buffer">The mix buffer in use.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateDeviceSink(uint bufferOffset, DeviceSink sink, int sessionId, Memory<float> buffer, int nodeId)
|
||||
{
|
||||
DeviceSinkCommand command = new DeviceSinkCommand(bufferOffset, sink, sessionId, buffer, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
}
|
||||
}
|
940
Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
Normal file
940
Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
Normal file
|
@ -0,0 +1,940 @@
|
|||
//
|
||||
// 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.Common;
|
||||
using Ryujinx.Audio.Renderer.Dsp.Command;
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||
using Ryujinx.Audio.Renderer.Server.Mix;
|
||||
using Ryujinx.Audio.Renderer.Server.Performance;
|
||||
using Ryujinx.Audio.Renderer.Server.Sink;
|
||||
using Ryujinx.Audio.Renderer.Server.Splitter;
|
||||
using Ryujinx.Audio.Renderer.Server.Voice;
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
public class CommandGenerator
|
||||
{
|
||||
private CommandBuffer _commandBuffer;
|
||||
private RendererSystemContext _rendererContext;
|
||||
private VoiceContext _voiceContext;
|
||||
private MixContext _mixContext;
|
||||
private EffectContext _effectContext;
|
||||
private SinkContext _sinkContext;
|
||||
private SplitterContext _splitterContext;
|
||||
private PerformanceManager _performanceManager;
|
||||
|
||||
public CommandGenerator(CommandBuffer commandBuffer, RendererSystemContext rendererContext, VoiceContext voiceContext, MixContext mixContext, EffectContext effectContext, SinkContext sinkContext, SplitterContext splitterContext, PerformanceManager performanceManager)
|
||||
{
|
||||
_commandBuffer = commandBuffer;
|
||||
_rendererContext = rendererContext;
|
||||
_voiceContext = voiceContext;
|
||||
_mixContext = mixContext;
|
||||
_effectContext = effectContext;
|
||||
_sinkContext = sinkContext;
|
||||
_splitterContext = splitterContext;
|
||||
_performanceManager = performanceManager;
|
||||
|
||||
_commandBuffer.GenerateClearMixBuffer(Constants.InvalidNodeId);
|
||||
}
|
||||
|
||||
private void GenerateDataSource(ref VoiceState voiceState, Memory<VoiceUpdateState> dspState, int channelIndex)
|
||||
{
|
||||
if (voiceState.MixId != Constants.UnusedMixId)
|
||||
{
|
||||
ref MixState mix = ref _mixContext.GetState(voiceState.MixId);
|
||||
|
||||
_commandBuffer.GenerateDepopPrepare(dspState,
|
||||
_rendererContext.DepopBuffer,
|
||||
mix.BufferCount,
|
||||
mix.BufferOffset,
|
||||
voiceState.NodeId,
|
||||
voiceState.WasPlaying);
|
||||
}
|
||||
else if (voiceState.SplitterId != Constants.UnusedSplitterId)
|
||||
{
|
||||
int destinationId = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++);
|
||||
|
||||
if (destinationSpan.IsEmpty)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ref SplitterDestination destination = ref destinationSpan[0];
|
||||
|
||||
if (destination.IsConfigured())
|
||||
{
|
||||
int mixId = destination.DestinationId;
|
||||
|
||||
if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt)
|
||||
{
|
||||
ref MixState mix = ref _mixContext.GetState(mixId);
|
||||
|
||||
_commandBuffer.GenerateDepopPrepare(dspState,
|
||||
_rendererContext.DepopBuffer,
|
||||
mix.BufferCount,
|
||||
mix.BufferOffset,
|
||||
voiceState.NodeId,
|
||||
voiceState.WasPlaying);
|
||||
|
||||
destination.MarkAsNeedToUpdateInternalState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!voiceState.WasPlaying)
|
||||
{
|
||||
Debug.Assert(voiceState.SampleFormat != SampleFormat.Adpcm || channelIndex == 0);
|
||||
|
||||
if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported())
|
||||
{
|
||||
_commandBuffer.GenerateDataSourceVersion2(ref voiceState,
|
||||
dspState,
|
||||
(ushort)_rendererContext.MixBufferCount,
|
||||
(ushort)channelIndex,
|
||||
voiceState.NodeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (voiceState.SampleFormat)
|
||||
{
|
||||
case SampleFormat.PcmInt16:
|
||||
_commandBuffer.GeneratePcmInt16DataSourceVersion1(ref voiceState,
|
||||
dspState,
|
||||
(ushort)_rendererContext.MixBufferCount,
|
||||
(ushort)channelIndex,
|
||||
voiceState.NodeId);
|
||||
break;
|
||||
case SampleFormat.PcmFloat:
|
||||
_commandBuffer.GeneratePcmFloatDataSourceVersion1(ref voiceState,
|
||||
dspState,
|
||||
(ushort)_rendererContext.MixBufferCount,
|
||||
(ushort)channelIndex,
|
||||
voiceState.NodeId);
|
||||
break;
|
||||
case SampleFormat.Adpcm:
|
||||
_commandBuffer.GenerateAdpcmDataSourceVersion1(ref voiceState,
|
||||
dspState,
|
||||
(ushort)_rendererContext.MixBufferCount,
|
||||
voiceState.NodeId);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory<VoiceUpdateState> state, int baseIndex, int bufferOffset, int nodeId)
|
||||
{
|
||||
for (int i = 0; i < voiceState.BiquadFilters.Length; i++)
|
||||
{
|
||||
ref BiquadFilterParameter filter = ref voiceState.BiquadFilters[i];
|
||||
|
||||
if (filter.Enable)
|
||||
{
|
||||
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state).Slice(VoiceUpdateState.BiquadStateOffset, VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount);
|
||||
|
||||
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
|
||||
|
||||
_commandBuffer.GenerateBiquadFilter(baseIndex,
|
||||
ref filter,
|
||||
stateMemory.Slice(i, 1),
|
||||
bufferOffset,
|
||||
bufferOffset,
|
||||
!voiceState.BiquadFilterNeedInitialization[i],
|
||||
nodeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateVoiceMix(Span<float> mixVolumes, Span<float> previousMixVolumes, Memory<VoiceUpdateState> state, uint bufferOffset, uint bufferCount, uint bufferIndex, int nodeId)
|
||||
{
|
||||
if (bufferCount > Constants.VoiceChannelCountMax)
|
||||
{
|
||||
_commandBuffer.GenerateMixRampGrouped(bufferCount,
|
||||
bufferIndex,
|
||||
bufferOffset,
|
||||
previousMixVolumes,
|
||||
mixVolumes,
|
||||
state,
|
||||
nodeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < bufferCount; i++)
|
||||
{
|
||||
float previousMixVolume = previousMixVolumes[i];
|
||||
float mixVolume = mixVolumes[i];
|
||||
|
||||
if (mixVolume != 0.0f || previousMixVolume != 0.0f)
|
||||
{
|
||||
_commandBuffer.GenerateMixRamp(previousMixVolume,
|
||||
mixVolume,
|
||||
bufferIndex,
|
||||
bufferOffset + (uint)i,
|
||||
i,
|
||||
state,
|
||||
nodeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateVoice(ref VoiceState voiceState)
|
||||
{
|
||||
int nodeId = voiceState.NodeId;
|
||||
uint channelsCount = voiceState.ChannelsCount;
|
||||
|
||||
for (int channelIndex = 0; channelIndex < channelsCount; channelIndex++)
|
||||
{
|
||||
Memory<VoiceUpdateState> dspStateMemory = _voiceContext.GetUpdateStateForDsp(voiceState.ChannelResourceIds[channelIndex]);
|
||||
|
||||
ref VoiceChannelResource channelResource = ref _voiceContext.GetChannelResource(voiceState.ChannelResourceIds[channelIndex]);
|
||||
|
||||
PerformanceDetailType dataSourceDetailType = PerformanceDetailType.Adpcm;
|
||||
|
||||
if (voiceState.SampleFormat == SampleFormat.PcmInt16)
|
||||
{
|
||||
dataSourceDetailType = PerformanceDetailType.PcmInt16;
|
||||
}
|
||||
else if (voiceState.SampleFormat == SampleFormat.PcmFloat)
|
||||
{
|
||||
dataSourceDetailType = PerformanceDetailType.PcmFloat;
|
||||
}
|
||||
|
||||
bool performanceInitialized = false;
|
||||
|
||||
PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
|
||||
|
||||
if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, dataSourceDetailType, PerformanceEntryType.Voice, nodeId))
|
||||
{
|
||||
performanceInitialized = true;
|
||||
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||
}
|
||||
|
||||
GenerateDataSource(ref voiceState, dspStateMemory, channelIndex);
|
||||
|
||||
if (performanceInitialized)
|
||||
{
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
|
||||
}
|
||||
|
||||
if (voiceState.WasPlaying)
|
||||
{
|
||||
voiceState.PreviousVolume = 0.0f;
|
||||
}
|
||||
else if (voiceState.HasAnyDestination())
|
||||
{
|
||||
performanceInitialized = false;
|
||||
|
||||
if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.BiquadFilter, PerformanceEntryType.Voice, nodeId))
|
||||
{
|
||||
performanceInitialized = true;
|
||||
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||
}
|
||||
|
||||
GenerateBiquadFilterForVoice(ref voiceState, dspStateMemory, (int)_rendererContext.MixBufferCount, channelIndex, nodeId);
|
||||
|
||||
if (performanceInitialized)
|
||||
{
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
|
||||
}
|
||||
|
||||
performanceInitialized = false;
|
||||
|
||||
if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.VolumeRamp, PerformanceEntryType.Voice, nodeId))
|
||||
{
|
||||
performanceInitialized = true;
|
||||
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||
}
|
||||
|
||||
_commandBuffer.GenerateVolumeRamp(voiceState.PreviousVolume,
|
||||
voiceState.Volume,
|
||||
_rendererContext.MixBufferCount + (uint)channelIndex,
|
||||
nodeId);
|
||||
|
||||
if (performanceInitialized)
|
||||
{
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
|
||||
}
|
||||
|
||||
voiceState.PreviousVolume = voiceState.Volume;
|
||||
|
||||
if (voiceState.MixId == Constants.UnusedMixId)
|
||||
{
|
||||
if (voiceState.SplitterId != Constants.UnusedSplitterId)
|
||||
{
|
||||
int destinationId = channelIndex;
|
||||
|
||||
while (true)
|
||||
{
|
||||
Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId);
|
||||
|
||||
if (destinationSpan.IsEmpty)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ref SplitterDestination destination = ref destinationSpan[0];
|
||||
|
||||
destinationId += (int)channelsCount;
|
||||
|
||||
if (destination.IsConfigured())
|
||||
{
|
||||
int mixId = destination.DestinationId;
|
||||
|
||||
if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt)
|
||||
{
|
||||
ref MixState mix = ref _mixContext.GetState(mixId);
|
||||
|
||||
GenerateVoiceMix(destination.MixBufferVolume,
|
||||
destination.PreviousMixBufferVolume,
|
||||
dspStateMemory,
|
||||
mix.BufferOffset,
|
||||
mix.BufferCount,
|
||||
_rendererContext.MixBufferCount + (uint)channelIndex,
|
||||
nodeId);
|
||||
|
||||
destination.MarkAsNeedToUpdateInternalState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ref MixState mix = ref _mixContext.GetState(voiceState.MixId);
|
||||
|
||||
performanceInitialized = false;
|
||||
|
||||
if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.Voice, nodeId))
|
||||
{
|
||||
performanceInitialized = true;
|
||||
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||
}
|
||||
|
||||
GenerateVoiceMix(channelResource.Mix.ToSpan(),
|
||||
channelResource.PreviousMix.ToSpan(),
|
||||
dspStateMemory,
|
||||
mix.BufferOffset,
|
||||
mix.BufferCount,
|
||||
_rendererContext.MixBufferCount + (uint)channelIndex,
|
||||
nodeId);
|
||||
|
||||
if (performanceInitialized)
|
||||
{
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
|
||||
}
|
||||
|
||||
channelResource.UpdateState();
|
||||
}
|
||||
|
||||
for (int i = 0; i < voiceState.BiquadFilterNeedInitialization.Length; i++)
|
||||
{
|
||||
voiceState.BiquadFilterNeedInitialization[i] = voiceState.BiquadFilters[i].Enable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void GenerateVoices()
|
||||
{
|
||||
for (int i = 0; i < _voiceContext.GetCount(); i++)
|
||||
{
|
||||
ref VoiceState sortedState = ref _voiceContext.GetSortedState(i);
|
||||
|
||||
if (!sortedState.ShouldSkip() && sortedState.UpdateForCommandGeneration(_voiceContext))
|
||||
{
|
||||
int nodeId = sortedState.NodeId;
|
||||
|
||||
PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
|
||||
|
||||
bool performanceInitialized = false;
|
||||
|
||||
if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.Voice, nodeId))
|
||||
{
|
||||
performanceInitialized = true;
|
||||
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||
}
|
||||
|
||||
GenerateVoice(ref sortedState);
|
||||
|
||||
if (performanceInitialized)
|
||||
{
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_splitterContext.UpdateInternalState();
|
||||
}
|
||||
|
||||
public void GeneratePerformance(ref PerformanceEntryAddresses performanceEntryAddresses, PerformanceCommand.Type type, int nodeId)
|
||||
{
|
||||
_commandBuffer.GeneratePerformance(ref performanceEntryAddresses, type, nodeId);
|
||||
}
|
||||
|
||||
private void GenerateBufferMixerEffect(int bufferOffset, BufferMixEffect effect, int nodeId)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.BufferMix);
|
||||
|
||||
if (effect.IsEnabled)
|
||||
{
|
||||
for (int i = 0; i < effect.Parameter.MixesCount; i++)
|
||||
{
|
||||
if (effect.Parameter.Volumes[i] != 0.0f)
|
||||
{
|
||||
_commandBuffer.GenerateMix((uint)bufferOffset + effect.Parameter.Input[i],
|
||||
(uint)bufferOffset + effect.Parameter.Output[i],
|
||||
nodeId,
|
||||
effect.Parameter.Volumes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateAuxEffect(uint bufferOffset, AuxiliaryBufferEffect effect, int nodeId)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.AuxiliaryBuffer);
|
||||
|
||||
if (effect.IsEnabled)
|
||||
{
|
||||
effect.GetWorkBuffer(0);
|
||||
effect.GetWorkBuffer(1);
|
||||
}
|
||||
|
||||
if (effect.State.SendBufferInfoBase != 0 && effect.State.ReturnBufferInfoBase != 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) != 0)
|
||||
{
|
||||
updateCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
updateCount = newUpdateCount;
|
||||
}
|
||||
|
||||
_commandBuffer.GenerateAuxEffect(bufferOffset,
|
||||
effect.Parameter.Input[i],
|
||||
effect.Parameter.Output[i],
|
||||
ref effect.State,
|
||||
effect.IsEnabled,
|
||||
effect.Parameter.BufferStorageSize,
|
||||
effect.State.SendBufferInfoBase,
|
||||
effect.State.ReturnBufferInfoBase,
|
||||
updateCount,
|
||||
writeOffset,
|
||||
nodeId);
|
||||
|
||||
writeOffset = newUpdateCount;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateDelayEffect(uint bufferOffset, DelayEffect effect, int nodeId)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.Delay);
|
||||
|
||||
ulong workBuffer = effect.GetWorkBuffer(-1);
|
||||
|
||||
_commandBuffer.GenerateDelayEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId);
|
||||
}
|
||||
|
||||
private void GenerateReverbEffect(uint bufferOffset, ReverbEffect effect, int nodeId, bool isLongSizePreDelaySupported)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.Reverb);
|
||||
|
||||
ulong workBuffer = effect.GetWorkBuffer(-1);
|
||||
|
||||
_commandBuffer.GenerateReverbEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, isLongSizePreDelaySupported);
|
||||
}
|
||||
|
||||
private void GenerateReverb3dEffect(uint bufferOffset, Reverb3dEffect effect, int nodeId)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.Reverb3d);
|
||||
|
||||
ulong workBuffer = effect.GetWorkBuffer(-1);
|
||||
|
||||
_commandBuffer.GenerateReverb3dEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId);
|
||||
}
|
||||
|
||||
private void GenerateBiquadFilterEffect(uint bufferOffset, BiquadFilterEffect effect, int nodeId)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.BiquadFilter);
|
||||
|
||||
if (effect.IsEnabled)
|
||||
{
|
||||
bool needInitialization = effect.Parameter.Status == UsageState.Invalid ||
|
||||
(effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed());
|
||||
|
||||
BiquadFilterParameter parameter = new BiquadFilterParameter();
|
||||
|
||||
parameter.Enable = true;
|
||||
effect.Parameter.Denominator.ToSpan().CopyTo(parameter.Denominator.ToSpan());
|
||||
effect.Parameter.Numerator.ToSpan().CopyTo(parameter.Numerator.ToSpan());
|
||||
|
||||
for (int i = 0; i < effect.Parameter.ChannelCount; i++)
|
||||
{
|
||||
_commandBuffer.GenerateBiquadFilter((int)bufferOffset, ref parameter, effect.State.Slice(i, 1),
|
||||
effect.Parameter.Input[i],
|
||||
effect.Parameter.Output[i],
|
||||
needInitialization,
|
||||
nodeId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < effect.Parameter.ChannelCount; i++)
|
||||
{
|
||||
uint inputBufferIndex = bufferOffset + effect.Parameter.Input[i];
|
||||
uint outputBufferIndex = bufferOffset + effect.Parameter.Output[i];
|
||||
|
||||
// If the input and output isn't the same, generate a command.
|
||||
if (inputBufferIndex != outputBufferIndex)
|
||||
{
|
||||
_commandBuffer.GenerateCopyMixBuffer(inputBufferIndex, outputBufferIndex, nodeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateEffect(ref MixState mix, BaseEffect effect)
|
||||
{
|
||||
int nodeId = mix.NodeId;
|
||||
|
||||
bool isFinalMix = mix.MixId == Constants.FinalMixId;
|
||||
|
||||
PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
|
||||
|
||||
bool performanceInitialized = false;
|
||||
|
||||
if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, effect.GetPerformanceDetailType(),
|
||||
isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix, nodeId))
|
||||
{
|
||||
performanceInitialized = true;
|
||||
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||
}
|
||||
|
||||
switch (effect.Type)
|
||||
{
|
||||
case EffectType.BufferMix:
|
||||
GenerateBufferMixerEffect((int)mix.BufferOffset, (BufferMixEffect)effect, nodeId);
|
||||
break;
|
||||
case EffectType.AuxiliaryBuffer:
|
||||
GenerateAuxEffect(mix.BufferOffset, (AuxiliaryBufferEffect)effect, nodeId);
|
||||
break;
|
||||
case EffectType.Delay:
|
||||
GenerateDelayEffect(mix.BufferOffset, (DelayEffect)effect, nodeId);
|
||||
break;
|
||||
case EffectType.Reverb:
|
||||
GenerateReverbEffect(mix.BufferOffset, (ReverbEffect)effect, nodeId, mix.IsLongSizePreDelaySupported);
|
||||
break;
|
||||
case EffectType.Reverb3d:
|
||||
GenerateReverb3dEffect(mix.BufferOffset, (Reverb3dEffect)effect, nodeId);
|
||||
break;
|
||||
case EffectType.BiquadFilter:
|
||||
GenerateBiquadFilterEffect(mix.BufferOffset, (BiquadFilterEffect)effect, nodeId);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Unsupported effect type {effect.Type}");
|
||||
}
|
||||
|
||||
if (performanceInitialized)
|
||||
{
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
|
||||
}
|
||||
|
||||
effect.UpdateForCommandGeneration();
|
||||
}
|
||||
|
||||
private void GenerateEffects(ref MixState mix)
|
||||
{
|
||||
ReadOnlySpan<int> effectProcessingOrderArray = mix.EffectProcessingOrderArray;
|
||||
|
||||
Debug.Assert(_effectContext.GetCount() == 0 || !effectProcessingOrderArray.IsEmpty);
|
||||
|
||||
for (int i = 0; i < _effectContext.GetCount(); i++)
|
||||
{
|
||||
int effectOrder = effectProcessingOrderArray[i];
|
||||
|
||||
if (effectOrder == Constants.InvalidProcessingOrder)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// BaseEffect is a class, we don't need to pass it by ref
|
||||
BaseEffect effect = _effectContext.GetEffect(effectOrder);
|
||||
|
||||
Debug.Assert(effect.Type != EffectType.Invalid);
|
||||
Debug.Assert(effect.MixId == mix.MixId);
|
||||
|
||||
if (!effect.ShouldSkip())
|
||||
{
|
||||
GenerateEffect(ref mix, effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateMix(ref MixState mix)
|
||||
{
|
||||
if (mix.HasAnyDestination())
|
||||
{
|
||||
Debug.Assert(mix.DestinationMixId != Constants.UnusedMixId || mix.DestinationSplitterId != Constants.UnusedSplitterId);
|
||||
|
||||
if (mix.DestinationMixId == Constants.UnusedMixId)
|
||||
{
|
||||
if (mix.DestinationSplitterId != Constants.UnusedSplitterId)
|
||||
{
|
||||
int destinationId = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int destinationIndex = destinationId++;
|
||||
|
||||
Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex);
|
||||
|
||||
if (destinationSpan.IsEmpty)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ref SplitterDestination destination = ref destinationSpan[0];
|
||||
|
||||
if (destination.IsConfigured())
|
||||
{
|
||||
int mixId = destination.DestinationId;
|
||||
|
||||
if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt)
|
||||
{
|
||||
ref MixState destinationMix = ref _mixContext.GetState(mixId);
|
||||
|
||||
uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount);
|
||||
|
||||
for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++)
|
||||
{
|
||||
float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex);
|
||||
|
||||
if (volume != 0.0f)
|
||||
{
|
||||
_commandBuffer.GenerateMix(inputBufferIndex,
|
||||
destinationMix.BufferOffset + bufferDestinationIndex,
|
||||
mix.NodeId,
|
||||
volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ref MixState destinationMix = ref _mixContext.GetState(mix.DestinationMixId);
|
||||
|
||||
for (uint bufferIndex = 0; bufferIndex < mix.BufferCount; bufferIndex++)
|
||||
{
|
||||
for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++)
|
||||
{
|
||||
float volume = mix.Volume * mix.GetMixBufferVolume((int)bufferIndex, (int)bufferDestinationIndex);
|
||||
|
||||
if (volume != 0.0f)
|
||||
{
|
||||
_commandBuffer.GenerateMix(mix.BufferOffset + bufferIndex,
|
||||
destinationMix.BufferOffset + bufferDestinationIndex,
|
||||
mix.NodeId,
|
||||
volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateSubMix(ref MixState subMix)
|
||||
{
|
||||
_commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
|
||||
subMix.BufferOffset,
|
||||
subMix.BufferCount,
|
||||
subMix.NodeId,
|
||||
subMix.SampleRate);
|
||||
|
||||
GenerateEffects(ref subMix);
|
||||
|
||||
PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
|
||||
|
||||
int nodeId = subMix.NodeId;
|
||||
|
||||
bool performanceInitialized = false;
|
||||
|
||||
if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.SubMix, nodeId))
|
||||
{
|
||||
performanceInitialized = true;
|
||||
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||
}
|
||||
|
||||
GenerateMix(ref subMix);
|
||||
|
||||
if (performanceInitialized)
|
||||
{
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
public void GenerateSubMixes()
|
||||
{
|
||||
for (int id = 0; id < _mixContext.GetCount(); id++)
|
||||
{
|
||||
ref MixState sortedState = ref _mixContext.GetSortedState(id);
|
||||
|
||||
if (sortedState.IsUsed && sortedState.MixId != Constants.FinalMixId)
|
||||
{
|
||||
int nodeId = sortedState.NodeId;
|
||||
|
||||
PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
|
||||
|
||||
bool performanceInitialized = false;
|
||||
|
||||
if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.SubMix, nodeId))
|
||||
{
|
||||
performanceInitialized = true;
|
||||
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||
}
|
||||
|
||||
GenerateSubMix(ref sortedState);
|
||||
|
||||
if (performanceInitialized)
|
||||
{
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateFinalMix()
|
||||
{
|
||||
ref MixState finalMix = ref _mixContext.GetFinalState();
|
||||
|
||||
_commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
|
||||
finalMix.BufferOffset,
|
||||
finalMix.BufferCount,
|
||||
finalMix.NodeId,
|
||||
finalMix.SampleRate);
|
||||
|
||||
GenerateEffects(ref finalMix);
|
||||
|
||||
PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
|
||||
|
||||
int nodeId = finalMix.NodeId;
|
||||
|
||||
bool performanceInitialized = false;
|
||||
|
||||
if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.FinalMix, nodeId))
|
||||
{
|
||||
performanceInitialized = true;
|
||||
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||
}
|
||||
|
||||
// Only generate volume command if the volume isn't 100%.
|
||||
if (finalMix.Volume != 1.0f)
|
||||
{
|
||||
for (uint bufferIndex = 0; bufferIndex < finalMix.BufferCount; bufferIndex++)
|
||||
{
|
||||
bool performanceSubInitialized = false;
|
||||
|
||||
if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.VolumeRamp, PerformanceEntryType.FinalMix, nodeId))
|
||||
{
|
||||
performanceSubInitialized = true;
|
||||
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||
}
|
||||
|
||||
_commandBuffer.GenerateVolume(finalMix.Volume,
|
||||
finalMix.BufferOffset + bufferIndex,
|
||||
nodeId);
|
||||
|
||||
if (performanceSubInitialized)
|
||||
{
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (performanceInitialized)
|
||||
{
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
public void GenerateFinalMixes()
|
||||
{
|
||||
int nodeId = _mixContext.GetFinalState().NodeId;
|
||||
|
||||
PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
|
||||
|
||||
bool performanceInitialized = false;
|
||||
|
||||
if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.FinalMix, nodeId))
|
||||
{
|
||||
performanceInitialized = true;
|
||||
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||
}
|
||||
|
||||
GenerateFinalMix();
|
||||
|
||||
if (performanceInitialized)
|
||||
{
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateCircularBuffer(CircularBufferSink sink, ref MixState finalMix)
|
||||
{
|
||||
_commandBuffer.GenerateCircularBuffer(finalMix.BufferOffset, sink, Constants.InvalidNodeId);
|
||||
}
|
||||
|
||||
private void GenerateDevice(DeviceSink sink, ref MixState finalMix)
|
||||
{
|
||||
if (_commandBuffer.CommandList.SampleRate != 48000 && sink.UpsamplerState == null)
|
||||
{
|
||||
sink.UpsamplerState = _rendererContext.UpsamplerManager.Allocate();
|
||||
}
|
||||
|
||||
bool useCustomDownMixingCommand = _rendererContext.ChannelCount == 2 && sink.Parameter.DownMixParameterEnabled;
|
||||
|
||||
if (useCustomDownMixingCommand)
|
||||
{
|
||||
_commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
|
||||
sink.Parameter.Input.ToSpan(),
|
||||
sink.Parameter.Input.ToSpan(),
|
||||
sink.DownMixCoefficients,
|
||||
Constants.InvalidNodeId);
|
||||
}
|
||||
// NOTE: We do the downmixing at the DSP level as it's easier that way.
|
||||
else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6)
|
||||
{
|
||||
_commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
|
||||
sink.Parameter.Input.ToSpan(),
|
||||
sink.Parameter.Input.ToSpan(),
|
||||
Constants.DefaultSurroundToStereoCoefficients,
|
||||
Constants.InvalidNodeId);
|
||||
}
|
||||
|
||||
CommandList commandList = _commandBuffer.CommandList;
|
||||
|
||||
if (sink.UpsamplerState != null)
|
||||
{
|
||||
_commandBuffer.GenerateUpsample(finalMix.BufferOffset,
|
||||
sink.UpsamplerState,
|
||||
sink.Parameter.InputCount,
|
||||
sink.Parameter.Input.ToSpan(),
|
||||
commandList.BufferCount,
|
||||
commandList.SampleCount,
|
||||
commandList.SampleRate,
|
||||
Constants.InvalidNodeId);
|
||||
}
|
||||
|
||||
_commandBuffer.GenerateDeviceSink(finalMix.BufferOffset,
|
||||
sink,
|
||||
_rendererContext.SessionId,
|
||||
commandList.Buffers,
|
||||
Constants.InvalidNodeId);
|
||||
}
|
||||
|
||||
private void GenerateSink(BaseSink sink, ref MixState finalMix)
|
||||
{
|
||||
bool performanceInitialized = false;
|
||||
|
||||
PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
|
||||
|
||||
if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.Sink, sink.NodeId))
|
||||
{
|
||||
performanceInitialized = true;
|
||||
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, sink.NodeId);
|
||||
}
|
||||
|
||||
if (!sink.ShouldSkip)
|
||||
{
|
||||
switch (sink.Type)
|
||||
{
|
||||
case SinkType.CircularBuffer:
|
||||
GenerateCircularBuffer((CircularBufferSink)sink, ref finalMix);
|
||||
break;
|
||||
case SinkType.Device:
|
||||
GenerateDevice((DeviceSink)sink, ref finalMix);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Unsupported sink type {sink.Type}");
|
||||
}
|
||||
|
||||
sink.UpdateForCommandGeneration();
|
||||
}
|
||||
|
||||
if (performanceInitialized)
|
||||
{
|
||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, sink.NodeId);
|
||||
}
|
||||
}
|
||||
|
||||
public void GenerateSinks()
|
||||
{
|
||||
ref MixState finalMix = ref _mixContext.GetFinalState();
|
||||
|
||||
for (int i = 0; i < _sinkContext.GetCount(); i++)
|
||||
{
|
||||
// BaseSink is a class, we don't need to pass it by ref
|
||||
BaseSink sink = _sinkContext.GetSink(i);
|
||||
|
||||
if (sink.IsUsed)
|
||||
{
|
||||
GenerateSink(sink, ref finalMix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
//
|
||||
// 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.Dsp.Command;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ICommandProcessingTimeEstimator"/> version 1.
|
||||
/// </summary>
|
||||
public class CommandProcessingTimeEstimatorVersion1 : ICommandProcessingTimeEstimator
|
||||
{
|
||||
private uint _sampleCount;
|
||||
private uint _bufferCount;
|
||||
|
||||
public CommandProcessingTimeEstimatorVersion1(uint sampleCount, uint bufferCount)
|
||||
{
|
||||
_sampleCount = sampleCount;
|
||||
_bufferCount = bufferCount;
|
||||
}
|
||||
|
||||
public uint Estimate(PerformanceCommand command)
|
||||
{
|
||||
return 1454;
|
||||
}
|
||||
|
||||
public uint Estimate(ClearMixBufferCommand command)
|
||||
{
|
||||
return (uint)(_sampleCount * 0.83f * _bufferCount * 1.2f);
|
||||
}
|
||||
|
||||
public uint Estimate(BiquadFilterCommand command)
|
||||
{
|
||||
return (uint)(_sampleCount * 58.0f * 1.2f);
|
||||
}
|
||||
|
||||
public uint Estimate(MixRampGroupedCommand command)
|
||||
{
|
||||
int volumeCount = 0;
|
||||
|
||||
for (int i = 0; i < command.MixBufferCount; i++)
|
||||
{
|
||||
if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f)
|
||||
{
|
||||
volumeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return (uint)(_sampleCount * 14.4f * 1.2f * volumeCount);
|
||||
}
|
||||
|
||||
public uint Estimate(MixRampCommand command)
|
||||
{
|
||||
return (uint)(_sampleCount * 14.4f * 1.2f);
|
||||
}
|
||||
|
||||
public uint Estimate(DepopPrepareCommand command)
|
||||
{
|
||||
return 1080;
|
||||
}
|
||||
|
||||
public uint Estimate(VolumeRampCommand command)
|
||||
{
|
||||
return (uint)(_sampleCount * 9.8f * 1.2f);
|
||||
}
|
||||
|
||||
public uint Estimate(PcmInt16DataSourceCommandVersion1 command)
|
||||
{
|
||||
return (uint)(command.Pitch * 0.25f * 1.2f);
|
||||
}
|
||||
|
||||
public uint Estimate(AdpcmDataSourceCommandVersion1 command)
|
||||
{
|
||||
return (uint)(command.Pitch * 0.46f * 1.2f);
|
||||
}
|
||||
|
||||
public uint Estimate(DepopForMixBuffersCommand command)
|
||||
{
|
||||
return (uint)(_sampleCount * 8.9f * command.MixBufferCount);
|
||||
}
|
||||
|
||||
public uint Estimate(CopyMixBufferCommand command)
|
||||
{
|
||||
// NOTE: Nintendo returns 0 here for some reasons even if it will generate a command like that on version 1.. maybe a mistake?
|
||||
return 0;
|
||||
}
|
||||
|
||||
public uint Estimate(MixCommand command)
|
||||
{
|
||||
return (uint)(_sampleCount * 10.0f * 1.2f);
|
||||
}
|
||||
|
||||
public uint Estimate(DelayCommand command)
|
||||
{
|
||||
return (uint)(_sampleCount * command.Parameter.ChannelCount * 202.5f);
|
||||
}
|
||||
|
||||
public uint Estimate(ReverbCommand command)
|
||||
{
|
||||
Debug.Assert(command.Parameter.IsChannelCountValid());
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
return (uint)(750 * _sampleCount * command.Parameter.ChannelCount * 1.2f);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public uint Estimate(Reverb3dCommand command)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
return (uint)(530 * _sampleCount * command.Parameter.ChannelCount * 1.2f);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public uint Estimate(AuxiliaryBufferCommand command)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
return 15956;
|
||||
}
|
||||
|
||||
return 3765;
|
||||
}
|
||||
|
||||
public uint Estimate(VolumeCommand command)
|
||||
{
|
||||
return (uint)(_sampleCount * 8.8f * 1.2f);
|
||||
}
|
||||
|
||||
public uint Estimate(CircularBufferSinkCommand command)
|
||||
{
|
||||
return 55;
|
||||
}
|
||||
|
||||
public uint Estimate(DownMixSurroundToStereoCommand command)
|
||||
{
|
||||
return 16108;
|
||||
}
|
||||
|
||||
public uint Estimate(UpsampleCommand command)
|
||||
{
|
||||
return 357915;
|
||||
}
|
||||
|
||||
public uint Estimate(DeviceSinkCommand command)
|
||||
{
|
||||
return 10042;
|
||||
}
|
||||
|
||||
public uint Estimate(PcmFloatDataSourceCommandVersion1 command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public uint Estimate(DataSourceVersion2Command command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,544 @@
|
|||
//
|
||||
// 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.Dsp.Command;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ICommandProcessingTimeEstimator"/> version 2. (added with REV5)
|
||||
/// </summary>
|
||||
public class CommandProcessingTimeEstimatorVersion2 : ICommandProcessingTimeEstimator
|
||||
{
|
||||
private uint _sampleCount;
|
||||
private uint _bufferCount;
|
||||
|
||||
public CommandProcessingTimeEstimatorVersion2(uint sampleCount, uint bufferCount)
|
||||
{
|
||||
_sampleCount = sampleCount;
|
||||
_bufferCount = bufferCount;
|
||||
}
|
||||
|
||||
public uint Estimate(PerformanceCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)489.35f;
|
||||
}
|
||||
|
||||
return (uint)491.18f;
|
||||
}
|
||||
|
||||
public uint Estimate(ClearMixBufferCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
float costPerBuffer = 668.8f;
|
||||
float baseCost = 193.2f;
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
costPerBuffer = 260.4f;
|
||||
baseCost = 139.65f;
|
||||
}
|
||||
|
||||
return (uint)(baseCost + costPerBuffer * _bufferCount);
|
||||
}
|
||||
|
||||
public uint Estimate(BiquadFilterCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)4813.2f;
|
||||
}
|
||||
|
||||
return (uint)6915.4f;
|
||||
}
|
||||
|
||||
public uint Estimate(MixRampGroupedCommand command)
|
||||
{
|
||||
const float costPerSample = 7.245f;
|
||||
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
int volumeCount = 0;
|
||||
|
||||
for (int i = 0; i < command.MixBufferCount; i++)
|
||||
{
|
||||
if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f)
|
||||
{
|
||||
volumeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return (uint)(_sampleCount * costPerSample * volumeCount);
|
||||
}
|
||||
|
||||
public uint Estimate(MixRampCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)1859.0f;
|
||||
}
|
||||
|
||||
return (uint)2286.1f;
|
||||
}
|
||||
|
||||
public uint Estimate(DepopPrepareCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)306.62f;
|
||||
}
|
||||
|
||||
return (uint)293.22f;
|
||||
}
|
||||
|
||||
public uint Estimate(VolumeRampCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)1403.9f;
|
||||
}
|
||||
|
||||
return (uint)1884.3f;
|
||||
}
|
||||
|
||||
public uint Estimate(PcmInt16DataSourceCommandVersion1 command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
float costPerSample = 1195.5f;
|
||||
float baseCost = 7797.0f;
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
costPerSample = 749.27f;
|
||||
baseCost = 6138.9f;
|
||||
}
|
||||
|
||||
return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f))));
|
||||
}
|
||||
|
||||
public uint Estimate(AdpcmDataSourceCommandVersion1 command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
float costPerSample = 3564.1f;
|
||||
float baseCost = 6225.5f;
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
costPerSample = 2125.6f;
|
||||
baseCost = 9039.5f;
|
||||
}
|
||||
|
||||
return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f))));
|
||||
}
|
||||
|
||||
public uint Estimate(DepopForMixBuffersCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)762.96f;
|
||||
}
|
||||
|
||||
return (uint)726.96f;
|
||||
}
|
||||
|
||||
public uint Estimate(CopyMixBufferCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)836.32f;
|
||||
}
|
||||
|
||||
return (uint)1000.9f;
|
||||
}
|
||||
|
||||
public uint Estimate(MixCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)1342.2f;
|
||||
}
|
||||
|
||||
return (uint)1833.2f;
|
||||
}
|
||||
|
||||
public uint Estimate(DelayCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)41636.0f;
|
||||
case 2:
|
||||
return (uint)97861.0f;
|
||||
case 4:
|
||||
return (uint)192520.0f;
|
||||
case 6:
|
||||
return (uint)301760.0f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)578.53f;
|
||||
case 2:
|
||||
return (uint)663.06f;
|
||||
case 4:
|
||||
return (uint)703.98f;
|
||||
case 6:
|
||||
return (uint)760.03f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)8770.3f;
|
||||
case 2:
|
||||
return (uint)25741.0f;
|
||||
case 4:
|
||||
return (uint)47551.0f;
|
||||
case 6:
|
||||
return (uint)81629.0f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)521.28f;
|
||||
case 2:
|
||||
return (uint)585.4f;
|
||||
case 4:
|
||||
return (uint)629.88f;
|
||||
case 6:
|
||||
return (uint)713.57f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint Estimate(ReverbCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)97192.0f;
|
||||
case 2:
|
||||
return (uint)103280.0f;
|
||||
case 4:
|
||||
return (uint)109580.0f;
|
||||
case 6:
|
||||
return (uint)115070.0f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)492.01f;
|
||||
case 2:
|
||||
return (uint)554.46f;
|
||||
case 4:
|
||||
return (uint)595.86f;
|
||||
case 6:
|
||||
return (uint)656.62f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)136460.0f;
|
||||
case 2:
|
||||
return (uint)145750.0f;
|
||||
case 4:
|
||||
return (uint)154800.0f;
|
||||
case 6:
|
||||
return (uint)161970.0f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)495.79f;
|
||||
case 2:
|
||||
return (uint)527.16f;
|
||||
case 4:
|
||||
return (uint)598.75f;
|
||||
case 6:
|
||||
return (uint)666.03f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint Estimate(Reverb3dCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)138840.0f;
|
||||
case 2:
|
||||
return (uint)135430.0f;
|
||||
case 4:
|
||||
return (uint)199180.0f;
|
||||
case 6:
|
||||
return (uint)247350.0f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)718.7f;
|
||||
case 2:
|
||||
return (uint)751.3f;
|
||||
case 4:
|
||||
return (uint)797.46f;
|
||||
case 6:
|
||||
return (uint)867.43f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)199950.0f;
|
||||
case 2:
|
||||
return (uint)195200.0f;
|
||||
case 4:
|
||||
return (uint)290580.0f;
|
||||
case 6:
|
||||
return (uint)363490.0f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)534.24f;
|
||||
case 2:
|
||||
return (uint)570.87f;
|
||||
case 4:
|
||||
return (uint)660.93f;
|
||||
case 6:
|
||||
return (uint)694.6f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint Estimate(AuxiliaryBufferCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
return (uint)7177.9f;
|
||||
}
|
||||
|
||||
return (uint)489.16f;
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
return (uint)9499.8f;
|
||||
}
|
||||
|
||||
return (uint)485.56f;
|
||||
}
|
||||
|
||||
public uint Estimate(VolumeCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)1280.3f;
|
||||
}
|
||||
|
||||
return (uint)1737.8f;
|
||||
}
|
||||
|
||||
public uint Estimate(CircularBufferSinkCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
float costPerBuffer = 1726.0f;
|
||||
float baseCost = 1369.7f;
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
costPerBuffer = 853.63f;
|
||||
baseCost = 1284.5f;
|
||||
}
|
||||
|
||||
return (uint)(baseCost + costPerBuffer * command.InputCount);
|
||||
}
|
||||
|
||||
public uint Estimate(DownMixSurroundToStereoCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)10009.0f;
|
||||
}
|
||||
|
||||
return (uint)14577.0f;
|
||||
}
|
||||
|
||||
public uint Estimate(UpsampleCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)292000.0f;
|
||||
}
|
||||
|
||||
return (uint)0.0f;
|
||||
}
|
||||
|
||||
public uint Estimate(DeviceSinkCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
Debug.Assert(command.InputCount == 2 || command.InputCount == 6);
|
||||
|
||||
if (command.InputCount == 2)
|
||||
{
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)9261.5f;
|
||||
}
|
||||
|
||||
return (uint)9336.1f;
|
||||
}
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)9111.8f;
|
||||
}
|
||||
|
||||
return (uint)9566.7f;
|
||||
}
|
||||
|
||||
public uint Estimate(PcmFloatDataSourceCommandVersion1 command)
|
||||
{
|
||||
// NOTE: This was added between REV7 and REV8 and for some reasons the estimator v2 was changed...
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
float costPerSample = 3490.9f;
|
||||
float baseCost = 10091.0f;
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
costPerSample = 2310.4f;
|
||||
baseCost = 7845.3f;
|
||||
}
|
||||
|
||||
return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f))));
|
||||
}
|
||||
|
||||
public uint Estimate(DataSourceVersion2Command command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,636 @@
|
|||
//
|
||||
// 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.Common;
|
||||
using Ryujinx.Audio.Renderer.Dsp.Command;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ICommandProcessingTimeEstimator"/> version 3. (added with REV8)
|
||||
/// </summary>
|
||||
public class CommandProcessingTimeEstimatorVersion3 : ICommandProcessingTimeEstimator
|
||||
{
|
||||
private uint _sampleCount;
|
||||
private uint _bufferCount;
|
||||
|
||||
public CommandProcessingTimeEstimatorVersion3(uint sampleCount, uint bufferCount)
|
||||
{
|
||||
_sampleCount = sampleCount;
|
||||
_bufferCount = bufferCount;
|
||||
}
|
||||
|
||||
public uint Estimate(PerformanceCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)498.17f;
|
||||
}
|
||||
|
||||
return (uint)489.42f;
|
||||
}
|
||||
|
||||
public uint Estimate(ClearMixBufferCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
float costPerBuffer = 440.68f;
|
||||
float baseCost = 0;
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
costPerBuffer = 266.65f;
|
||||
}
|
||||
|
||||
return (uint)(baseCost + costPerBuffer * _bufferCount);
|
||||
}
|
||||
|
||||
public uint Estimate(BiquadFilterCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)4173.2f;
|
||||
}
|
||||
|
||||
return (uint)5585.1f;
|
||||
}
|
||||
|
||||
public uint Estimate(MixRampGroupedCommand command)
|
||||
{
|
||||
float costPerSample = 6.4434f;
|
||||
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
costPerSample = 6.708f;
|
||||
}
|
||||
|
||||
int volumeCount = 0;
|
||||
|
||||
for (int i = 0; i < command.MixBufferCount; i++)
|
||||
{
|
||||
if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f)
|
||||
{
|
||||
volumeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return (uint)(_sampleCount * costPerSample * volumeCount);
|
||||
}
|
||||
|
||||
public uint Estimate(MixRampCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)1968.7f;
|
||||
}
|
||||
|
||||
return (uint)2459.4f;
|
||||
}
|
||||
|
||||
public uint Estimate(DepopPrepareCommand command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public uint Estimate(VolumeRampCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)1425.3f;
|
||||
}
|
||||
|
||||
return (uint)1700.0f;
|
||||
}
|
||||
|
||||
public uint Estimate(PcmInt16DataSourceCommandVersion1 command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
float costPerSample = 710.143f;
|
||||
float baseCost = 7853.286f;
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
costPerSample = 427.52f;
|
||||
baseCost = 6329.442f;
|
||||
}
|
||||
|
||||
return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f))));
|
||||
}
|
||||
|
||||
public uint Estimate(AdpcmDataSourceCommandVersion1 command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
float costPerSample = 3564.1f;
|
||||
float baseCost = 9736.702f;
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
costPerSample = 2125.6f;
|
||||
baseCost = 7913.808f;
|
||||
}
|
||||
|
||||
return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f))));
|
||||
}
|
||||
|
||||
public uint Estimate(DepopForMixBuffersCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)739.64f;
|
||||
}
|
||||
|
||||
return (uint)910.97f;
|
||||
}
|
||||
|
||||
public uint Estimate(CopyMixBufferCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)842.59f;
|
||||
}
|
||||
|
||||
return (uint)986.72f;
|
||||
}
|
||||
|
||||
public uint Estimate(MixCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)1402.8f;
|
||||
}
|
||||
|
||||
return (uint)1853.2f;
|
||||
}
|
||||
|
||||
public uint Estimate(DelayCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)8929.04f;
|
||||
case 2:
|
||||
return (uint)25500.75f;
|
||||
case 4:
|
||||
return (uint)47759.62f;
|
||||
case 6:
|
||||
return (uint)82203.07f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)1295.20f;
|
||||
case 2:
|
||||
return (uint)1213.60f;
|
||||
case 4:
|
||||
return (uint)942.03f;
|
||||
case 6:
|
||||
return (uint)1001.55f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)11941.05f;
|
||||
case 2:
|
||||
return (uint)37197.37f;
|
||||
case 4:
|
||||
return (uint)69749.84f;
|
||||
case 6:
|
||||
return (uint)120042.40f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)997.67f;
|
||||
case 2:
|
||||
return (uint)977.63f;
|
||||
case 4:
|
||||
return (uint)792.30f;
|
||||
case 6:
|
||||
return (uint)875.43f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint Estimate(ReverbCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)81475.05f;
|
||||
case 2:
|
||||
return (uint)84975.0f;
|
||||
case 4:
|
||||
return (uint)91625.15f;
|
||||
case 6:
|
||||
return (uint)95332.27f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)536.30f;
|
||||
case 2:
|
||||
return (uint)588.70f;
|
||||
case 4:
|
||||
return (uint)643.70f;
|
||||
case 6:
|
||||
return (uint)706.0f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)120174.47f;
|
||||
case 2:
|
||||
return (uint)25262.22f;
|
||||
case 4:
|
||||
return (uint)135751.23f;
|
||||
case 6:
|
||||
return (uint)141129.23f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)617.64f;
|
||||
case 2:
|
||||
return (uint)659.54f;
|
||||
case 4:
|
||||
return (uint)711.43f;
|
||||
case 6:
|
||||
return (uint)778.07f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint Estimate(Reverb3dCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)116754.0f;
|
||||
case 2:
|
||||
return (uint)125912.05f;
|
||||
case 4:
|
||||
return (uint)146336.03f;
|
||||
case 6:
|
||||
return (uint)165812.66f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)734.0f;
|
||||
case 2:
|
||||
return (uint)766.62f;
|
||||
case 4:
|
||||
return (uint)797.46f;
|
||||
case 6:
|
||||
return (uint)867.43f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)170292.34f;
|
||||
case 2:
|
||||
return (uint)183875.63f;
|
||||
case 4:
|
||||
return (uint)214696.19f;
|
||||
case 6:
|
||||
return (uint)243846.77f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)508.47f;
|
||||
case 2:
|
||||
return (uint)582.45f;
|
||||
case 4:
|
||||
return (uint)626.42f;
|
||||
case 6:
|
||||
return (uint)682.47f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint Estimate(AuxiliaryBufferCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
return (uint)7182.14f;
|
||||
}
|
||||
|
||||
return (uint)472.11f;
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
return (uint)9435.96f;
|
||||
}
|
||||
|
||||
return (uint)462.62f;
|
||||
}
|
||||
|
||||
public uint Estimate(VolumeCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)1311.1f;
|
||||
}
|
||||
|
||||
return (uint)1713.6f;
|
||||
}
|
||||
|
||||
public uint Estimate(CircularBufferSinkCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
float costPerBuffer = 770.26f;
|
||||
float baseCost = 0f;
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
costPerBuffer = 531.07f;
|
||||
}
|
||||
|
||||
return (uint)(baseCost + costPerBuffer * command.InputCount);
|
||||
}
|
||||
|
||||
public uint Estimate(DownMixSurroundToStereoCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)9949.7f;
|
||||
}
|
||||
|
||||
return (uint)14679.0f;
|
||||
}
|
||||
|
||||
public uint Estimate(UpsampleCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)312990.0f;
|
||||
}
|
||||
|
||||
return (uint)0.0f;
|
||||
}
|
||||
|
||||
public uint Estimate(DeviceSinkCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
Debug.Assert(command.InputCount == 2 || command.InputCount == 6);
|
||||
|
||||
if (command.InputCount == 2)
|
||||
{
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)8980.0f;
|
||||
}
|
||||
|
||||
return (uint)9221.9f;
|
||||
}
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
return (uint)9177.9f;
|
||||
}
|
||||
|
||||
return (uint)9725.9f;
|
||||
}
|
||||
|
||||
public uint Estimate(PcmFloatDataSourceCommandVersion1 command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
float costPerSample = 3490.9f;
|
||||
float baseCost = 10090.9f;
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
costPerSample = 2310.4f;
|
||||
baseCost = 7845.25f;
|
||||
}
|
||||
|
||||
return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f))));
|
||||
}
|
||||
|
||||
public uint Estimate(DataSourceVersion2Command command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
(float baseCost, float costPerSample) = GetCostByFormat(_sampleCount, command.SampleFormat, command.SrcQuality);
|
||||
|
||||
return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f) - 1.0f)));
|
||||
}
|
||||
|
||||
private static (float, float) GetCostByFormat(uint sampleCount, SampleFormat format, SampleRateConversionQuality quality)
|
||||
{
|
||||
Debug.Assert(sampleCount == 160 || sampleCount == 240);
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case SampleFormat.PcmInt16:
|
||||
switch (quality)
|
||||
{
|
||||
case SampleRateConversionQuality.Default:
|
||||
if (sampleCount == 160)
|
||||
{
|
||||
return (6329.44f, 427.52f);
|
||||
}
|
||||
|
||||
return (7853.28f, 710.14f);
|
||||
case SampleRateConversionQuality.High:
|
||||
if (sampleCount == 160)
|
||||
{
|
||||
return (8049.42f, 371.88f);
|
||||
}
|
||||
|
||||
return (10138.84f, 610.49f);
|
||||
case SampleRateConversionQuality.Low:
|
||||
if (sampleCount == 160)
|
||||
{
|
||||
return (5062.66f, 423.43f);
|
||||
}
|
||||
|
||||
return (5810.96f, 676.72f);
|
||||
default:
|
||||
throw new NotImplementedException($"{format} {quality}");
|
||||
}
|
||||
case SampleFormat.PcmFloat:
|
||||
switch (quality)
|
||||
{
|
||||
case SampleRateConversionQuality.Default:
|
||||
if (sampleCount == 160)
|
||||
{
|
||||
return (7845.25f, 2310.4f);
|
||||
}
|
||||
|
||||
return (10090.9f, 3490.9f);
|
||||
case SampleRateConversionQuality.High:
|
||||
if (sampleCount == 160)
|
||||
{
|
||||
return (9446.36f, 2308.91f);
|
||||
}
|
||||
|
||||
return (12520.85f, 3480.61f);
|
||||
case SampleRateConversionQuality.Low:
|
||||
if (sampleCount == 160)
|
||||
{
|
||||
return (9446.36f, 2308.91f);
|
||||
}
|
||||
|
||||
return (12520.85f, 3480.61f);
|
||||
default:
|
||||
throw new NotImplementedException($"{format} {quality}");
|
||||
}
|
||||
case SampleFormat.Adpcm:
|
||||
switch (quality)
|
||||
{
|
||||
case SampleRateConversionQuality.Default:
|
||||
if (sampleCount == 160)
|
||||
{
|
||||
return (7913.81f, 1827.66f);
|
||||
}
|
||||
|
||||
return (9736.70f, 2756.37f);
|
||||
case SampleRateConversionQuality.High:
|
||||
if (sampleCount == 160)
|
||||
{
|
||||
return (9607.81f, 1829.29f);
|
||||
}
|
||||
|
||||
return (12154.38f, 2731.31f);
|
||||
case SampleRateConversionQuality.Low:
|
||||
if (sampleCount == 160)
|
||||
{
|
||||
return (6517.48f, 1824.61f);
|
||||
}
|
||||
|
||||
return (7929.44f, 2732.15f);
|
||||
default:
|
||||
throw new NotImplementedException($"{format} {quality}");
|
||||
}
|
||||
default:
|
||||
throw new NotImplementedException($"{format}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// 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 auxiliary buffer effect.
|
||||
/// </summary>
|
||||
public class AuxiliaryBufferEffect : BaseEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// The auxiliary buffer parameter.
|
||||
/// </summary>
|
||||
public AuxiliaryBufferParameter Parameter;
|
||||
|
||||
/// <summary>
|
||||
/// Auxiliary buffer state.
|
||||
/// </summary>
|
||||
public AuxiliaryBufferAddresses State;
|
||||
|
||||
public override EffectType TargetEffectType => EffectType.AuxiliaryBuffer;
|
||||
|
||||
public override DspAddress GetWorkBuffer(int index)
|
||||
{
|
||||
return WorkBuffers[index].GetReference(true);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
|
||||
{
|
||||
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>() * 2;
|
||||
|
||||
bool sendBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], Parameter.SendBufferInfoAddress, bufferSize);
|
||||
bool returnBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[1], Parameter.ReturnBufferInfoAddress, bufferSize);
|
||||
|
||||
BufferUnmapped = sendBufferUnmapped && returnBufferUnmapped;
|
||||
|
||||
if (!BufferUnmapped)
|
||||
{
|
||||
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.ReturnBufferInfo = returnDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>();
|
||||
State.ReturnBufferInfoBase = returnDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>() * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateForCommandGeneration()
|
||||
{
|
||||
UpdateUsageStateForCommandGeneration();
|
||||
}
|
||||
}
|
||||
}
|
257
Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs
Normal file
257
Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs
Normal file
|
@ -0,0 +1,257 @@
|
|||
//
|
||||
// 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.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
||||
|
||||
using DspAddress = System.UInt64;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Effect
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class used as a server state for an effect.
|
||||
/// </summary>
|
||||
public class BaseEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="EffectType"/> of the effect.
|
||||
/// </summary>
|
||||
public EffectType Type;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the effect must be active.
|
||||
/// </summary>
|
||||
public bool IsEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the internal effect work buffers used wasn't mapped.
|
||||
/// </summary>
|
||||
public bool BufferUnmapped;
|
||||
|
||||
/// <summary>
|
||||
/// The current state of the effect.
|
||||
/// </summary>
|
||||
public UsageState UsageState;
|
||||
|
||||
/// <summary>
|
||||
/// The target mix id of the effect.
|
||||
/// </summary>
|
||||
public int MixId;
|
||||
|
||||
/// <summary>
|
||||
/// Position of the effect while processing effects.
|
||||
/// </summary>
|
||||
public uint ProcessingOrder;
|
||||
|
||||
/// <summary>
|
||||
/// Array of all the work buffer used by the effect.
|
||||
/// </summary>
|
||||
protected AddressInfo[] WorkBuffers;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="BaseEffect"/>.
|
||||
/// </summary>
|
||||
public BaseEffect()
|
||||
{
|
||||
Type = TargetEffectType;
|
||||
UsageState = UsageState.Invalid;
|
||||
|
||||
IsEnabled = false;
|
||||
BufferUnmapped = false;
|
||||
MixId = Constants.UnusedMixId;
|
||||
ProcessingOrder = uint.MaxValue;
|
||||
|
||||
WorkBuffers = new AddressInfo[2];
|
||||
|
||||
foreach (ref AddressInfo info in WorkBuffers.AsSpan())
|
||||
{
|
||||
info = AddressInfo.Create();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The target <see cref="EffectType"/> handled by this <see cref="BaseEffect"/>.
|
||||
/// </summary>
|
||||
public virtual EffectType TargetEffectType => EffectType.Invalid;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the <see cref="EffectType"/> sent by the user match the internal <see cref="EffectType"/>.
|
||||
/// </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(ref EffectInParameter parameter)
|
||||
{
|
||||
return parameter.Type == TargetEffectType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the usage state during command generation.
|
||||
/// </summary>
|
||||
protected void UpdateUsageStateForCommandGeneration()
|
||||
{
|
||||
UsageState = IsEnabled ? UsageState.Enabled : UsageState.Disabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal common parameters from a user parameter.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
protected void UpdateParameterBase(ref EffectInParameter parameter)
|
||||
{
|
||||
MixId = parameter.MixId;
|
||||
ProcessingOrder = parameter.ProcessingOrder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force unmap all the work buffers.
|
||||
/// </summary>
|
||||
/// <param name="mapper">The mapper to use.</param>
|
||||
public void ForceUnmapBuffers(PoolMapper mapper)
|
||||
{
|
||||
foreach (ref AddressInfo info in WorkBuffers.AsSpan())
|
||||
{
|
||||
if (info.GetReference(false) != 0)
|
||||
{
|
||||
mapper.ForceUnmap(ref info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the effect needs to be skipped.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the effect needs to be skipped.</returns>
|
||||
public bool ShouldSkip()
|
||||
{
|
||||
return BufferUnmapped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the <see cref="BaseEffect"/> state during command generation.
|
||||
/// </summary>
|
||||
public virtual void UpdateForCommandGeneration()
|
||||
{
|
||||
Debug.Assert(Type == TargetEffectType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal state from a user parameter.
|
||||
/// </summary>
|
||||
/// <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 EffectInParameter parameter, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
|
||||
updateErrorInfo = new ErrorInfo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the work buffer DSP address at the given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the work buffer</param>
|
||||
/// <returns>The work buffer DSP address at the given index.</returns>
|
||||
public virtual DspAddress GetWorkBuffer(int index)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the first work buffer DSP address.
|
||||
/// </summary>
|
||||
/// <returns>The first work buffer DSP address.</returns>
|
||||
protected DspAddress GetSingleBuffer()
|
||||
{
|
||||
if (IsEnabled)
|
||||
{
|
||||
return WorkBuffers[0].GetReference(true);
|
||||
}
|
||||
|
||||
if (UsageState != UsageState.Disabled)
|
||||
{
|
||||
DspAddress address = WorkBuffers[0].GetReference(false);
|
||||
ulong size = WorkBuffers[0].Size;
|
||||
|
||||
if (address != 0 && size != 0)
|
||||
{
|
||||
AudioProcessorMemoryManager.InvalidateDataCache(address, size);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Store the output status to the given user output.
|
||||
/// </summary>
|
||||
/// <param name="outStatus">The given user output.</param>
|
||||
/// <param name="isAudioRendererActive">If set to true, the <see cref="AudioRenderSystem"/> is active.</param>
|
||||
public void StoreStatus(ref EffectOutStatus outStatus, bool isAudioRendererActive)
|
||||
{
|
||||
if (isAudioRendererActive)
|
||||
{
|
||||
if (UsageState == UsageState.Disabled)
|
||||
{
|
||||
outStatus.State = EffectOutStatus.EffectState.Disabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
outStatus.State = EffectOutStatus.EffectState.Enabled;
|
||||
}
|
||||
}
|
||||
else if (UsageState == UsageState.New)
|
||||
{
|
||||
outStatus.State = EffectOutStatus.EffectState.Enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
outStatus.State = EffectOutStatus.EffectState.Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="PerformanceDetailType"/> associated to the <see cref="Type"/> of this effect.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="PerformanceDetailType"/> associated to the <see cref="Type"/> of this effect.</returns>
|
||||
public PerformanceDetailType GetPerformanceDetailType()
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case EffectType.BiquadFilter:
|
||||
return PerformanceDetailType.BiquadFilter;
|
||||
case EffectType.AuxiliaryBuffer:
|
||||
return PerformanceDetailType.Aux;
|
||||
case EffectType.Delay:
|
||||
return PerformanceDetailType.Delay;
|
||||
case EffectType.Reverb:
|
||||
return PerformanceDetailType.Reverb;
|
||||
case EffectType.Reverb3d:
|
||||
return PerformanceDetailType.Reverb3d;
|
||||
case EffectType.BufferMix:
|
||||
return PerformanceDetailType.Mix;
|
||||
default:
|
||||
throw new NotImplementedException($"{Type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
74
Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs
Normal file
74
Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs
Normal file
|
@ -0,0 +1,74 @@
|
|||
//
|
||||
// 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;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Effect
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a biquad filter effect.
|
||||
/// </summary>
|
||||
public class BiquadFilterEffect : BaseEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// The biquad filter parameter.
|
||||
/// </summary>
|
||||
public BiquadFilterEffectParameter Parameter;
|
||||
|
||||
/// <summary>
|
||||
/// The biquad filter state.
|
||||
/// </summary>
|
||||
public Memory<BiquadFilterState> State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="BiquadFilterEffect"/>.
|
||||
/// </summary>
|
||||
public BiquadFilterEffect()
|
||||
{
|
||||
Parameter = new BiquadFilterEffectParameter();
|
||||
State = new BiquadFilterState[Constants.ChannelCountMax];
|
||||
}
|
||||
|
||||
public override EffectType TargetEffectType => EffectType.BiquadFilter;
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
|
||||
Parameter = MemoryMarshal.Cast<byte, BiquadFilterEffectParameter>(parameter.SpecificData)[0];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
||||
updateErrorInfo = new BehaviourParameter.ErrorInfo();
|
||||
}
|
||||
|
||||
public override void UpdateForCommandGeneration()
|
||||
{
|
||||
UpdateUsageStateForCommandGeneration();
|
||||
|
||||
Parameter.Status = UsageState.Enabled;
|
||||
}
|
||||
}
|
||||
}
|
56
Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs
Normal file
56
Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
// 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.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Effect
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a buffer mix effect.
|
||||
/// </summary>
|
||||
public class BufferMixEffect : BaseEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// The buffer mix parameter.
|
||||
/// </summary>
|
||||
public BufferMixParameter Parameter;
|
||||
|
||||
public override EffectType TargetEffectType => EffectType.BufferMix;
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
|
||||
Parameter = MemoryMarshal.Cast<byte, BufferMixParameter>(parameter.SpecificData)[0];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
||||
updateErrorInfo = new BehaviourParameter.ErrorInfo();
|
||||
}
|
||||
|
||||
public override void UpdateForCommandGeneration()
|
||||
{
|
||||
UpdateUsageStateForCommandGeneration();
|
||||
}
|
||||
}
|
||||
}
|
100
Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs
Normal file
100
Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs
Normal file
|
@ -0,0 +1,100 @@
|
|||
//
|
||||
// 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;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using DspAddress = System.UInt64;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Effect
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a delay effect.
|
||||
/// </summary>
|
||||
public class DelayEffect : BaseEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// The delay parameter.
|
||||
/// </summary>
|
||||
public DelayParameter Parameter;
|
||||
|
||||
/// <summary>
|
||||
/// The delay state.
|
||||
/// </summary>
|
||||
public Memory<DelayState> State { get; }
|
||||
|
||||
public DelayEffect()
|
||||
{
|
||||
State = new DelayState[1];
|
||||
}
|
||||
|
||||
public override EffectType TargetEffectType => EffectType.Delay;
|
||||
|
||||
public override DspAddress GetWorkBuffer(int index)
|
||||
{
|
||||
return GetSingleBuffer();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
|
||||
ref DelayParameter delayParameter = ref MemoryMarshal.Cast<byte, DelayParameter>(parameter.SpecificData)[0];
|
||||
|
||||
updateErrorInfo = new BehaviourParameter.ErrorInfo();
|
||||
|
||||
if (delayParameter.IsChannelCountMaxValid())
|
||||
{
|
||||
UpdateParameterBase(ref parameter);
|
||||
|
||||
UsageState oldParameterStatus = Parameter.Status;
|
||||
|
||||
Parameter = delayParameter;
|
||||
|
||||
if (delayParameter.IsChannelCountValid())
|
||||
{
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
||||
if (oldParameterStatus != UsageState.Enabled)
|
||||
{
|
||||
Parameter.Status = oldParameterStatus;
|
||||
}
|
||||
|
||||
if (BufferUnmapped || parameter.IsNew)
|
||||
{
|
||||
UsageState = UsageState.New;
|
||||
Parameter.Status = UsageState.Invalid;
|
||||
|
||||
BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateForCommandGeneration()
|
||||
{
|
||||
UpdateUsageStateForCommandGeneration();
|
||||
|
||||
Parameter.Status = UsageState.Enabled;
|
||||
}
|
||||
}
|
||||
}
|
82
Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs
Normal file
82
Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs
Normal file
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// 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 System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Effect
|
||||
{
|
||||
/// <summary>
|
||||
/// Effect context.
|
||||
/// </summary>
|
||||
public class EffectContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Storage for <see cref="BaseEffect"/>.
|
||||
/// </summary>
|
||||
private BaseEffect[] _effects;
|
||||
|
||||
/// <summary>
|
||||
/// The total effect count.
|
||||
/// </summary>
|
||||
private uint _effectCount;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="EffectContext"/>.
|
||||
/// </summary>
|
||||
public EffectContext()
|
||||
{
|
||||
_effects = null;
|
||||
_effectCount = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the <see cref="EffectContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="effectCount">The total effect count.</param>
|
||||
public void Initialize(uint effectCount)
|
||||
{
|
||||
_effectCount = effectCount;
|
||||
_effects = new BaseEffect[effectCount];
|
||||
|
||||
for (int i = 0; i < _effectCount; i++)
|
||||
{
|
||||
_effects[i] = new BaseEffect();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the total effect count.
|
||||
/// </summary>
|
||||
/// <returns>The total effect count.</returns>
|
||||
public uint GetCount()
|
||||
{
|
||||
return _effectCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a reference to a <see cref="BaseEffect"/> at the given <paramref name="index"/>.
|
||||
/// </summary>
|
||||
/// <param name="index">The index to use.</param>
|
||||
/// <returns>A reference to a <see cref="BaseEffect"/> at the given <paramref name="index"/>.</returns>
|
||||
public ref BaseEffect GetEffect(int index)
|
||||
{
|
||||
Debug.Assert(index >= 0 && index < _effectCount);
|
||||
|
||||
return ref _effects[index];
|
||||
}
|
||||
}
|
||||
}
|
99
Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs
Normal file
99
Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs
Normal 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;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Effect
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a 3D reverberation effect.
|
||||
/// </summary>
|
||||
public class Reverb3dEffect : BaseEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// The 3D reverberation parameter.
|
||||
/// </summary>
|
||||
public Reverb3dParameter Parameter;
|
||||
|
||||
/// <summary>
|
||||
/// The 3D reverberation state.
|
||||
/// </summary>
|
||||
public Memory<Reverb3dState> State { get; }
|
||||
|
||||
public Reverb3dEffect()
|
||||
{
|
||||
State = new Reverb3dState[1];
|
||||
}
|
||||
|
||||
public override EffectType TargetEffectType => EffectType.Reverb3d;
|
||||
|
||||
public override ulong GetWorkBuffer(int index)
|
||||
{
|
||||
return GetSingleBuffer();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
|
||||
ref Reverb3dParameter reverbParameter = ref MemoryMarshal.Cast<byte, Reverb3dParameter>(parameter.SpecificData)[0];
|
||||
|
||||
updateErrorInfo = new BehaviourParameter.ErrorInfo();
|
||||
|
||||
if (reverbParameter.IsChannelCountMaxValid())
|
||||
{
|
||||
UpdateParameterBase(ref parameter);
|
||||
|
||||
UsageState oldParameterStatus = Parameter.ParameterStatus;
|
||||
|
||||
Parameter = reverbParameter;
|
||||
|
||||
if (reverbParameter.IsChannelCountValid())
|
||||
{
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
||||
if (oldParameterStatus != UsageState.Enabled)
|
||||
{
|
||||
Parameter.ParameterStatus = oldParameterStatus;
|
||||
}
|
||||
|
||||
if (BufferUnmapped || parameter.IsNew)
|
||||
{
|
||||
UsageState = UsageState.New;
|
||||
Parameter.ParameterStatus = UsageState.Invalid;
|
||||
|
||||
BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateForCommandGeneration()
|
||||
{
|
||||
UpdateUsageStateForCommandGeneration();
|
||||
|
||||
Parameter.ParameterStatus = UsageState.Enabled;
|
||||
}
|
||||
}
|
||||
}
|
102
Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs
Normal file
102
Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs
Normal file
|
@ -0,0 +1,102 @@
|
|||
//
|
||||
// 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;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Effect
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a reverberation effect.
|
||||
/// </summary>
|
||||
public class ReverbEffect : BaseEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// The reverberation parameter.
|
||||
/// </summary>
|
||||
public ReverbParameter Parameter;
|
||||
|
||||
/// <summary>
|
||||
/// The reverberation state.
|
||||
/// </summary>
|
||||
public Memory<ReverbState> State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ReverbEffect"/>.
|
||||
/// </summary>
|
||||
public ReverbEffect()
|
||||
{
|
||||
State = new ReverbState[1];
|
||||
}
|
||||
|
||||
public override EffectType TargetEffectType => EffectType.Reverb;
|
||||
|
||||
public override ulong GetWorkBuffer(int index)
|
||||
{
|
||||
return GetSingleBuffer();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
|
||||
ref ReverbParameter reverbParameter = ref MemoryMarshal.Cast<byte, ReverbParameter>(parameter.SpecificData)[0];
|
||||
|
||||
updateErrorInfo = new BehaviourParameter.ErrorInfo();
|
||||
|
||||
if (reverbParameter.IsChannelCountMaxValid())
|
||||
{
|
||||
UpdateParameterBase(ref parameter);
|
||||
|
||||
UsageState oldParameterStatus = Parameter.Status;
|
||||
|
||||
Parameter = reverbParameter;
|
||||
|
||||
if (reverbParameter.IsChannelCountValid())
|
||||
{
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
||||
if (oldParameterStatus != UsageState.Enabled)
|
||||
{
|
||||
Parameter.Status = oldParameterStatus;
|
||||
}
|
||||
|
||||
if (BufferUnmapped || parameter.IsNew)
|
||||
{
|
||||
UsageState = UsageState.New;
|
||||
Parameter.Status = UsageState.Invalid;
|
||||
|
||||
BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateForCommandGeneration()
|
||||
{
|
||||
UpdateUsageStateForCommandGeneration();
|
||||
|
||||
Parameter.Status = UsageState.Enabled;
|
||||
}
|
||||
}
|
||||
}
|
45
Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs
Normal file
45
Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// 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/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Effect
|
||||
{
|
||||
/// <summary>
|
||||
/// The usage state of an effect.
|
||||
/// </summary>
|
||||
public enum UsageState : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The effect is in an invalid state.
|
||||
/// </summary>
|
||||
Invalid,
|
||||
|
||||
/// <summary>
|
||||
/// The effect is new.
|
||||
/// </summary>
|
||||
New,
|
||||
|
||||
/// <summary>
|
||||
/// The effect is enabled.
|
||||
/// </summary>
|
||||
Enabled,
|
||||
|
||||
/// <summary>
|
||||
/// The effect is disabled.
|
||||
/// </summary>
|
||||
Disabled
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// 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.Dsp.Command;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Estimate the time that a <see cref="ICommand"/> should take.
|
||||
/// </summary>
|
||||
/// <remarks>This is used for voice dropping.</remarks>
|
||||
public interface ICommandProcessingTimeEstimator
|
||||
{
|
||||
uint Estimate(AuxiliaryBufferCommand command);
|
||||
uint Estimate(BiquadFilterCommand command);
|
||||
uint Estimate(ClearMixBufferCommand command);
|
||||
uint Estimate(DelayCommand command);
|
||||
uint Estimate(Reverb3dCommand command);
|
||||
uint Estimate(ReverbCommand command);
|
||||
uint Estimate(DepopPrepareCommand command);
|
||||
uint Estimate(DepopForMixBuffersCommand command);
|
||||
uint Estimate(MixCommand command);
|
||||
uint Estimate(MixRampCommand command);
|
||||
uint Estimate(MixRampGroupedCommand command);
|
||||
uint Estimate(CopyMixBufferCommand command);
|
||||
uint Estimate(PerformanceCommand command);
|
||||
uint Estimate(VolumeCommand command);
|
||||
uint Estimate(VolumeRampCommand command);
|
||||
uint Estimate(PcmInt16DataSourceCommandVersion1 command);
|
||||
uint Estimate(PcmFloatDataSourceCommandVersion1 command);
|
||||
uint Estimate(AdpcmDataSourceCommandVersion1 command);
|
||||
uint Estimate(DataSourceVersion2Command command);
|
||||
uint Estimate(CircularBufferSinkCommand command);
|
||||
uint Estimate(DeviceSinkCommand command);
|
||||
uint Estimate(DownMixSurroundToStereoCommand command);
|
||||
uint Estimate(UpsampleCommand command);
|
||||
}
|
||||
}
|
151
Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs
Normal file
151
Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs
Normal file
|
@ -0,0 +1,151 @@
|
|||
//
|
||||
// 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 System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using DspAddress = System.UInt64;
|
||||
using CpuAddress = System.UInt64;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.MemoryPool
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the information of a region shared between the CPU and DSP.
|
||||
/// </summary>
|
||||
public struct AddressInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The target CPU address of the region.
|
||||
/// </summary>
|
||||
public CpuAddress CpuAddress;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the region.
|
||||
/// </summary>
|
||||
public ulong Size;
|
||||
|
||||
private unsafe MemoryPoolState* _memoryPools;
|
||||
|
||||
/// <summary>
|
||||
/// The forced DSP address of the region.
|
||||
/// </summary>
|
||||
public DspAddress ForceMappedDspAddress;
|
||||
|
||||
private unsafe ref MemoryPoolState MemoryPoolState => ref *_memoryPools;
|
||||
|
||||
public unsafe bool HasMemoryPoolState => (IntPtr)_memoryPools != IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Create an new empty <see cref="AddressInfo"/>.
|
||||
/// </summary>
|
||||
/// <returns>A new empty <see cref="AddressInfo"/>.</returns>
|
||||
public static AddressInfo Create()
|
||||
{
|
||||
return Create(0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="AddressInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="cpuAddress">The target <see cref="CpuAddress"/> of the region.</param>
|
||||
/// <param name="size">The target size of the region.</param>
|
||||
/// <returns>A new <see cref="AddressInfo"/>.</returns>
|
||||
public static AddressInfo Create(CpuAddress cpuAddress, ulong size)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return new AddressInfo
|
||||
{
|
||||
CpuAddress = cpuAddress,
|
||||
_memoryPools = MemoryPoolState.Null,
|
||||
Size = size,
|
||||
ForceMappedDspAddress = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the CPU address and size of the <see cref="AddressInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="cpuAddress">The target <see cref="CpuAddress"/> of the region.</param>
|
||||
/// <param name="size">The size of the region.</param>
|
||||
public void Setup(CpuAddress cpuAddress, ulong size)
|
||||
{
|
||||
CpuAddress = cpuAddress;
|
||||
Size = size;
|
||||
ForceMappedDspAddress = 0;
|
||||
|
||||
unsafe
|
||||
{
|
||||
_memoryPools = MemoryPoolState.Null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the <see cref="MemoryPoolState"/> associated.
|
||||
/// </summary>
|
||||
/// <param name="memoryPoolState">The <see cref="MemoryPoolState"/> associated.</param>
|
||||
public void SetupMemoryPool(Span<MemoryPoolState> memoryPoolState)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (MemoryPoolState* ptr = &MemoryMarshal.GetReference(memoryPoolState))
|
||||
{
|
||||
SetupMemoryPool(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the <see cref="MemoryPoolState"/> associated.
|
||||
/// </summary>
|
||||
/// <param name="memoryPoolState">The <see cref="MemoryPoolState"/> associated.</param>
|
||||
public unsafe void SetupMemoryPool(MemoryPoolState* memoryPoolState)
|
||||
{
|
||||
_memoryPools = memoryPoolState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the <see cref="MemoryPoolState"/> is mapped.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the <see cref="MemoryPoolState"/> is mapped.</returns>
|
||||
public bool HasMappedMemoryPool()
|
||||
{
|
||||
return HasMemoryPoolState && MemoryPoolState.IsMapped();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the DSP address associated to the <see cref="AddressInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="markUsed">If true, mark the <see cref="MemoryPoolState"/> as used.</param>
|
||||
/// <returns>Returns the DSP address associated to the <see cref="AddressInfo"/>.</returns>
|
||||
public DspAddress GetReference(bool markUsed)
|
||||
{
|
||||
if (!HasMappedMemoryPool())
|
||||
{
|
||||
return ForceMappedDspAddress;
|
||||
}
|
||||
|
||||
if (markUsed)
|
||||
{
|
||||
MemoryPoolState.IsUsed = true;
|
||||
}
|
||||
|
||||
return MemoryPoolState.Translate(CpuAddress, Size);
|
||||
}
|
||||
}
|
||||
}
|
148
Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs
Normal file
148
Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs
Normal file
|
@ -0,0 +1,148 @@
|
|||
//
|
||||
// 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 System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using DspAddress = System.UInt64;
|
||||
using CpuAddress = System.UInt64;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.MemoryPool
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a memory pool.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = Alignment)]
|
||||
public struct MemoryPoolState
|
||||
{
|
||||
public const int Alignment = 0x10;
|
||||
|
||||
/// <summary>
|
||||
/// The location of the <see cref="MemoryPoolState"/>.
|
||||
/// </summary>
|
||||
public enum LocationType : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="MemoryPoolState"/> located on the CPU side for user use.
|
||||
/// </summary>
|
||||
Cpu,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="MemoryPoolState"/> located on the DSP side for system use.
|
||||
/// </summary>
|
||||
Dsp
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The CPU address associated to the <see cref="MemoryPoolState"/>.
|
||||
/// </summary>
|
||||
public CpuAddress CpuAddress;
|
||||
|
||||
/// <summary>
|
||||
/// The DSP address associated to the <see cref="MemoryPoolState"/>.
|
||||
/// </summary>
|
||||
public DspAddress DspAddress;
|
||||
|
||||
/// <summary>
|
||||
/// The size associated to the <see cref="MemoryPoolState"/>.
|
||||
/// </summary>
|
||||
public ulong Size;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LocationType"/> associated to the <see cref="MemoryPoolState"/>.
|
||||
/// </summary>
|
||||
public LocationType Location;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the <see cref="MemoryPoolState"/> is used.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsUsed;
|
||||
|
||||
public static unsafe MemoryPoolState* Null => (MemoryPoolState*)IntPtr.Zero.ToPointer();
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="MemoryPoolState"/> with the given <see cref="LocationType"/>.
|
||||
/// </summary>
|
||||
/// <param name="location">The location type to use.</param>
|
||||
/// <returns>A new <see cref="MemoryPoolState"/> with the given <see cref="LocationType"/>.</returns>
|
||||
public static MemoryPoolState Create(LocationType location)
|
||||
{
|
||||
return new MemoryPoolState
|
||||
{
|
||||
CpuAddress = 0,
|
||||
DspAddress = 0,
|
||||
Size = 0,
|
||||
Location = location
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the <see cref="CpuAddress"/> and size of the <see cref="MemoryPoolState"/>.
|
||||
/// </summary>
|
||||
/// <param name="cpuAddress">The <see cref="CpuAddress"/>.</param>
|
||||
/// <param name="size">The size.</param>
|
||||
public void SetCpuAddress(CpuAddress cpuAddress, ulong size)
|
||||
{
|
||||
CpuAddress = cpuAddress;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given <see cref="CpuAddress"/> and size is contains in the <see cref="MemoryPoolState"/>.
|
||||
/// </summary>
|
||||
/// <param name="targetCpuAddress">The <see cref="CpuAddress"/>.</param>
|
||||
/// <param name="size">The size.</param>
|
||||
/// <returns>True if the <see cref="CpuAddress"/> is contained inside the <see cref="MemoryPoolState"/>.</returns>
|
||||
public bool Contains(CpuAddress targetCpuAddress, ulong size)
|
||||
{
|
||||
if (CpuAddress <= targetCpuAddress && size + targetCpuAddress <= Size + CpuAddress)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translate the given CPU address to a DSP address.
|
||||
/// </summary>
|
||||
/// <param name="targetCpuAddress">The <see cref="CpuAddress"/>.</param>
|
||||
/// <param name="size">The size.</param>
|
||||
/// <returns>the target DSP address.</returns>
|
||||
public DspAddress Translate(CpuAddress targetCpuAddress, ulong size)
|
||||
{
|
||||
if (Contains(targetCpuAddress, size) && IsMapped())
|
||||
{
|
||||
ulong offset = targetCpuAddress - CpuAddress;
|
||||
|
||||
return DspAddress + offset;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the <see cref="MemoryPoolState"/> mapped on the DSP?
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the <see cref="MemoryPoolState"/> is mapped on the DSP.</returns>
|
||||
public bool IsMapped()
|
||||
{
|
||||
return DspAddress != 0;
|
||||
}
|
||||
}
|
||||
}
|
383
Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs
Normal file
383
Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs
Normal file
|
@ -0,0 +1,383 @@
|
|||
//
|
||||
// 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.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
||||
|
||||
using CpuAddress = System.UInt64;
|
||||
using DspAddress = System.UInt64;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.MemoryPool
|
||||
{
|
||||
/// <summary>
|
||||
/// Memory pool mapping helper.
|
||||
/// </summary>
|
||||
public class PoolMapper
|
||||
{
|
||||
const uint CurrentProcessPseudoHandle = 0xFFFF8001;
|
||||
|
||||
/// <summary>
|
||||
/// The result of <see cref="Update(ref MemoryPoolState, ref MemoryPoolInParameter, ref MemoryPoolOutStatus)"/>.
|
||||
/// </summary>
|
||||
public enum UpdateResult : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// No error reported.
|
||||
/// </summary>
|
||||
Success = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The user parameters were invalid.
|
||||
/// </summary>
|
||||
InvalidParameter = 1,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Dsp.AudioProcessor"/> mapping failed.
|
||||
/// </summary>
|
||||
MapError = 2,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Dsp.AudioProcessor"/> unmapping failed.
|
||||
/// </summary>
|
||||
UnmapError = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The handle of the process owning the CPU memory manipulated.
|
||||
/// </summary>
|
||||
private uint _processHandle;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Memory{MemoryPoolState}"/> that will be manipulated.
|
||||
/// </summary>
|
||||
private Memory<MemoryPoolState> _memoryPools;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, this will try to force map memory pool even if their state are considered invalid.
|
||||
/// </summary>
|
||||
private bool _isForceMapEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="PoolMapper"/> used for system mapping.
|
||||
/// </summary>
|
||||
/// <param name="processHandle">The handle of the process owning the CPU memory manipulated.</param>
|
||||
/// <param name="isForceMapEnabled">If set to true, this will try to force map memory pool even if their state are considered invalid.</param>
|
||||
public PoolMapper(uint processHandle, bool isForceMapEnabled)
|
||||
{
|
||||
_processHandle = processHandle;
|
||||
_isForceMapEnabled = isForceMapEnabled;
|
||||
_memoryPools = Memory<MemoryPoolState>.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="PoolMapper"/> used for user mapping.
|
||||
/// </summary>
|
||||
/// <param name="processHandle">The handle of the process owning the CPU memory manipulated.</param>
|
||||
/// <param name="memoryPool">The user memory pools.</param>
|
||||
/// <param name="isForceMapEnabled">If set to true, this will try to force map memory pool even if their state are considered invalid.</param>
|
||||
public PoolMapper(uint processHandle, Memory<MemoryPoolState> memoryPool, bool isForceMapEnabled)
|
||||
{
|
||||
_processHandle = processHandle;
|
||||
_memoryPools = memoryPool;
|
||||
_isForceMapEnabled = isForceMapEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the <see cref="MemoryPoolState"/> for system use.
|
||||
/// </summary>
|
||||
/// <param name="memoryPool">The <see cref="MemoryPoolState"/> for system use.</param>
|
||||
/// <param name="cpuAddress">The <see cref="CpuAddress"/> to assign.</param>
|
||||
/// <param name="size">The size to assign.</param>
|
||||
/// <returns>Returns true if mapping on the <see cref="Dsp.AudioProcessor"/> succeeded.</returns>
|
||||
public bool InitializeSystemPool(ref MemoryPoolState memoryPool, CpuAddress cpuAddress, ulong size)
|
||||
{
|
||||
if (memoryPool.Location != MemoryPoolState.LocationType.Dsp)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return InitializePool(ref memoryPool, cpuAddress, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the <see cref="MemoryPoolState"/>.
|
||||
/// </summary>
|
||||
/// <param name="memoryPool">The <see cref="MemoryPoolState"/>.</param>
|
||||
/// <param name="cpuAddress">The <see cref="CpuAddress"/> to assign.</param>
|
||||
/// <param name="size">The size to assign.</param>
|
||||
/// <returns>Returns true if mapping on the <see cref="Dsp.AudioProcessor"/> succeeded.</returns>
|
||||
public bool InitializePool(ref MemoryPoolState memoryPool, CpuAddress cpuAddress, ulong size)
|
||||
{
|
||||
memoryPool.SetCpuAddress(cpuAddress, size);
|
||||
|
||||
return Map(ref memoryPool) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the process handle associated to the <see cref="MemoryPoolState"/>.
|
||||
/// </summary>
|
||||
/// <param name="memoryPool">The <see cref="MemoryPoolState"/>.</param>
|
||||
/// <returns>Returns the process handle associated to the <see cref="MemoryPoolState"/>.</returns>
|
||||
public uint GetProcessHandle(ref MemoryPoolState memoryPool)
|
||||
{
|
||||
if (memoryPool.Location == MemoryPoolState.LocationType.Cpu)
|
||||
{
|
||||
return CurrentProcessPseudoHandle;
|
||||
}
|
||||
else if (memoryPool.Location == MemoryPoolState.LocationType.Dsp)
|
||||
{
|
||||
return _processHandle;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map the <see cref="MemoryPoolState"/> on the <see cref="Dsp.AudioProcessor"/>.
|
||||
/// </summary>
|
||||
/// <param name="memoryPool">The <see cref="MemoryPoolState"/> to map.</param>
|
||||
/// <returns>Returns the DSP address mapped.</returns>
|
||||
public DspAddress Map(ref MemoryPoolState memoryPool)
|
||||
{
|
||||
DspAddress result = AudioProcessorMemoryManager.Map(GetProcessHandle(ref memoryPool), memoryPool.CpuAddress, memoryPool.Size);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
memoryPool.DspAddress = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unmap the <see cref="MemoryPoolState"/> from the <see cref="Dsp.AudioProcessor"/>.
|
||||
/// </summary>
|
||||
/// <param name="memoryPool">The <see cref="MemoryPoolState"/> to unmap.</param>
|
||||
/// <returns>Returns true if unmapped.</returns>
|
||||
public bool Unmap(ref MemoryPoolState memoryPool)
|
||||
{
|
||||
if (memoryPool.IsUsed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioProcessorMemoryManager.Unmap(GetProcessHandle(ref memoryPool), memoryPool.CpuAddress, memoryPool.Size);
|
||||
|
||||
memoryPool.SetCpuAddress(0, 0);
|
||||
memoryPool.DspAddress = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a <see cref="MemoryPoolState"/> associated to the region given.
|
||||
/// </summary>
|
||||
/// <param name="cpuAddress">The region <see cref="CpuAddress"/>.</param>
|
||||
/// <param name="size">The region size.</param>
|
||||
/// <returns>Returns the <see cref="MemoryPoolState"/> found or <see cref="Memory{MemoryPoolState}.Empty"/> if not found.</returns>
|
||||
private Span<MemoryPoolState> FindMemoryPool(CpuAddress cpuAddress, ulong size)
|
||||
{
|
||||
if (!_memoryPools.IsEmpty && _memoryPools.Length > 0)
|
||||
{
|
||||
for (int i = 0; i < _memoryPools.Length; i++)
|
||||
{
|
||||
if (_memoryPools.Span[i].Contains(cpuAddress, size))
|
||||
{
|
||||
return _memoryPools.Span.Slice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Span<MemoryPoolState>.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force unmap the given <see cref="AddressInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="addressInfo">The <see cref="AddressInfo"/> to force unmap</param>
|
||||
public void ForceUnmap(ref AddressInfo addressInfo)
|
||||
{
|
||||
if (_isForceMapEnabled)
|
||||
{
|
||||
Span<MemoryPoolState> memoryPool = FindMemoryPool(addressInfo.CpuAddress, addressInfo.Size);
|
||||
|
||||
if (!memoryPool.IsEmpty)
|
||||
{
|
||||
AudioProcessorMemoryManager.Unmap(_processHandle, memoryPool[0].CpuAddress, memoryPool[0].Size);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
AudioProcessorMemoryManager.Unmap(_processHandle, addressInfo.CpuAddress, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to attach the given region to the <see cref="AddressInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="errorInfo">The error information if an error was generated.</param>
|
||||
/// <param name="addressInfo">The <see cref="AddressInfo"/> to attach the region to.</param>
|
||||
/// <param name="cpuAddress">The region <see cref="CpuAddress"/>.</param>
|
||||
/// <param name="size">The region size.</param>
|
||||
/// <returns>Returns true if mapping was performed.</returns>
|
||||
public bool TryAttachBuffer(out ErrorInfo errorInfo, ref AddressInfo addressInfo, CpuAddress cpuAddress, ulong size)
|
||||
{
|
||||
errorInfo = new ErrorInfo();
|
||||
|
||||
addressInfo.Setup(cpuAddress, size);
|
||||
|
||||
if (AssignDspAddress(ref addressInfo))
|
||||
{
|
||||
errorInfo.ErrorCode = 0x0;
|
||||
errorInfo.ExtraErrorInfo = 0x0;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorInfo.ErrorCode = ResultCode.InvalidAddressInfo;
|
||||
errorInfo.ExtraErrorInfo = addressInfo.CpuAddress;
|
||||
|
||||
return _isForceMapEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update a <see cref="MemoryPoolState"/> using user parameters.
|
||||
/// </summary>
|
||||
/// <param name="memoryPool">The <see cref="MemoryPoolState"/> to update.</param>
|
||||
/// <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)
|
||||
{
|
||||
MemoryPoolUserState inputState = inParameter.State;
|
||||
|
||||
MemoryPoolUserState outputState;
|
||||
|
||||
const uint pageSize = 0x1000;
|
||||
|
||||
if (inputState != MemoryPoolUserState.RequestAttach && inputState != MemoryPoolUserState.RequestDetach)
|
||||
{
|
||||
return UpdateResult.Success;
|
||||
}
|
||||
|
||||
if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress & (pageSize - 1)) != 0)
|
||||
{
|
||||
return UpdateResult.InvalidParameter;
|
||||
}
|
||||
|
||||
if (inParameter.Size == 0 || (inParameter.Size & (pageSize - 1)) != 0)
|
||||
{
|
||||
return UpdateResult.InvalidParameter;
|
||||
}
|
||||
|
||||
if (inputState == MemoryPoolUserState.RequestAttach)
|
||||
{
|
||||
bool initializeSuccess = InitializePool(ref memoryPool, inParameter.CpuAddress, inParameter.Size);
|
||||
|
||||
if (!initializeSuccess)
|
||||
{
|
||||
memoryPool.SetCpuAddress(0, 0);
|
||||
|
||||
Logger.Error?.Print(LogClass.AudioRenderer, $"Map of memory pool (address: 0x{inParameter.CpuAddress:x}, size 0x{inParameter.Size:x}) failed!");
|
||||
return UpdateResult.MapError;
|
||||
}
|
||||
|
||||
outputState = MemoryPoolUserState.Attached;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (memoryPool.CpuAddress != inParameter.CpuAddress || memoryPool.Size != inParameter.Size)
|
||||
{
|
||||
return UpdateResult.InvalidParameter;
|
||||
}
|
||||
|
||||
if (!Unmap(ref memoryPool))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.AudioRenderer, $"Unmap of memory pool (address: 0x{memoryPool.CpuAddress:x}, size 0x{memoryPool.Size:x}) failed!");
|
||||
return UpdateResult.UnmapError;
|
||||
}
|
||||
|
||||
outputState = MemoryPoolUserState.Detached;
|
||||
}
|
||||
|
||||
outStatus.State = outputState;
|
||||
|
||||
return UpdateResult.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map the <see cref="AddressInfo"/> to the <see cref="Dsp.AudioProcessor"/>.
|
||||
/// </summary>
|
||||
/// <param name="addressInfo">The <see cref="AddressInfo"/> to map.</param>
|
||||
/// <returns>Returns true if mapping was performed.</returns>
|
||||
private bool AssignDspAddress(ref AddressInfo addressInfo)
|
||||
{
|
||||
if (addressInfo.CpuAddress == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_memoryPools.Length > 0)
|
||||
{
|
||||
Span<MemoryPoolState> memoryPool = FindMemoryPool(addressInfo.CpuAddress, addressInfo.Size);
|
||||
|
||||
if (!memoryPool.IsEmpty)
|
||||
{
|
||||
addressInfo.SetupMemoryPool(memoryPool);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_isForceMapEnabled)
|
||||
{
|
||||
DspAddress dspAddress = AudioProcessorMemoryManager.Map(_processHandle, addressInfo.CpuAddress, addressInfo.Size);
|
||||
|
||||
addressInfo.ForceMappedDspAddress = dspAddress;
|
||||
|
||||
AudioProcessorMemoryManager.Map(_processHandle, addressInfo.CpuAddress, addressInfo.Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
addressInfo.SetupMemoryPool(MemoryPoolState.Null);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the usage flag from all the <see cref="MemoryPoolState"/>.
|
||||
/// </summary>
|
||||
/// <param name="memoryPool">The <see cref="Memory{MemoryPoolState}"/> to reset.</param>
|
||||
public static void ClearUsageState(Memory<MemoryPoolState> memoryPool)
|
||||
{
|
||||
foreach (ref MemoryPoolState info in memoryPool.Span)
|
||||
{
|
||||
info.IsUsed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
276
Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs
Normal file
276
Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs
Normal file
|
@ -0,0 +1,276 @@
|
|||
//
|
||||
// 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.Server.Splitter;
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Mix
|
||||
{
|
||||
/// <summary>
|
||||
/// Mix context.
|
||||
/// </summary>
|
||||
public class MixContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The total mix count.
|
||||
/// </summary>
|
||||
private uint _mixesCount;
|
||||
|
||||
/// <summary>
|
||||
/// Storage for <see cref="MixState"/>.
|
||||
/// </summary>
|
||||
private Memory<MixState> _mixes;
|
||||
|
||||
/// <summary>
|
||||
/// Storage of the sorted indices to <see cref="MixState"/>.
|
||||
/// </summary>
|
||||
private Memory<int> _sortedMixes;
|
||||
|
||||
/// <summary>
|
||||
/// Graph state.
|
||||
/// </summary>
|
||||
public NodeStates NodeStates { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The instance of the adjacent matrix.
|
||||
/// </summary>
|
||||
public EdgeMatrix EdgeMatrix { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance of <see cref="MixContext"/>.
|
||||
/// </summary>
|
||||
public MixContext()
|
||||
{
|
||||
NodeStates = new NodeStates();
|
||||
EdgeMatrix = new EdgeMatrix();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the <see cref="MixContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="sortedMixes">The storage for sorted indices.</param>
|
||||
/// <param name="mixes">The storage of <see cref="MixState"/>.</param>
|
||||
/// <param name="nodeStatesWorkBuffer">The storage used for the <see cref="NodeStates"/>.</param>
|
||||
/// <param name="edgeMatrixWorkBuffer">The storage used for the <see cref="EdgeMatrix"/>.</param>
|
||||
public void Initialize(Memory<int> sortedMixes, Memory<MixState> mixes, Memory<byte> nodeStatesWorkBuffer, Memory<byte> edgeMatrixWorkBuffer)
|
||||
{
|
||||
_mixesCount = (uint)mixes.Length;
|
||||
_mixes = mixes;
|
||||
_sortedMixes = sortedMixes;
|
||||
|
||||
if (!nodeStatesWorkBuffer.IsEmpty && !edgeMatrixWorkBuffer.IsEmpty)
|
||||
{
|
||||
NodeStates.Initialize(nodeStatesWorkBuffer, mixes.Length);
|
||||
EdgeMatrix.Initialize(edgeMatrixWorkBuffer, mixes.Length);
|
||||
}
|
||||
|
||||
int sortedId = 0;
|
||||
for (int i = 0; i < _mixes.Length; i++)
|
||||
{
|
||||
SetSortedState(sortedId++, i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Associate the given <paramref name="targetIndex"/> to a given <paramref cref="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The sorted id.</param>
|
||||
/// <param name="targetIndex">The index to associate.</param>
|
||||
private void SetSortedState(int id, int targetIndex)
|
||||
{
|
||||
_sortedMixes.Span[id] = targetIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a reference to the final <see cref="MixState"/>.
|
||||
/// </summary>
|
||||
/// <returns>A reference to the final <see cref="MixState"/>.</returns>
|
||||
public ref MixState GetFinalState()
|
||||
{
|
||||
return ref GetState(Constants.FinalMixId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a reference to a <see cref="MixState"/> at the given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The index to use.</param>
|
||||
/// <returns>A reference to a <see cref="MixState"/> at the given <paramref name="id"/>.</returns>
|
||||
public ref MixState GetState(int id)
|
||||
{
|
||||
return ref SpanIOHelper.GetFromMemory(_mixes, id, _mixesCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a reference to a <see cref="MixState"/> at the given <paramref name="id"/> of the sorted mix info.
|
||||
/// </summary>
|
||||
/// <param name="id">The index to use.</param>
|
||||
/// <returns>A reference to a <see cref="MixState"/> at the given <paramref name="id"/>.</returns>
|
||||
public ref MixState GetSortedState(int id)
|
||||
{
|
||||
Debug.Assert(id >= 0 && id < _mixesCount);
|
||||
|
||||
return ref GetState(_sortedMixes.Span[id]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the total mix count.
|
||||
/// </summary>
|
||||
/// <returns>The total mix count.</returns>
|
||||
public uint GetCount()
|
||||
{
|
||||
return _mixesCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal distance from the final mix value of every <see cref="MixState"/>.
|
||||
/// </summary>
|
||||
private void UpdateDistancesFromFinalMix()
|
||||
{
|
||||
foreach (ref MixState mix in _mixes.Span)
|
||||
{
|
||||
mix.ClearDistanceFromFinalMix();
|
||||
}
|
||||
|
||||
for (int i = 0; i < GetCount(); i++)
|
||||
{
|
||||
ref MixState mix = ref GetState(i);
|
||||
|
||||
SetSortedState(i, i);
|
||||
|
||||
if (mix.IsUsed)
|
||||
{
|
||||
uint distance;
|
||||
|
||||
if (mix.MixId != Constants.FinalMixId)
|
||||
{
|
||||
int mixId = mix.MixId;
|
||||
|
||||
for (distance = 0; distance < GetCount(); distance++)
|
||||
{
|
||||
if (mixId == Constants.UnusedMixId)
|
||||
{
|
||||
distance = MixState.InvalidDistanceFromFinalMix;
|
||||
break;
|
||||
}
|
||||
|
||||
ref MixState distanceMix = ref GetState(mixId);
|
||||
|
||||
if (distanceMix.DistanceFromFinalMix != MixState.InvalidDistanceFromFinalMix)
|
||||
{
|
||||
distance = distanceMix.DistanceFromFinalMix + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
mixId = distanceMix.DestinationMixId;
|
||||
|
||||
if (mixId == Constants.FinalMixId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (distance > GetCount())
|
||||
{
|
||||
distance = MixState.InvalidDistanceFromFinalMix;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
distance = MixState.InvalidDistanceFromFinalMix;
|
||||
}
|
||||
|
||||
mix.DistanceFromFinalMix = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal mix buffer offset of all <see cref="MixState"/>.
|
||||
/// </summary>
|
||||
private void UpdateMixBufferOffset()
|
||||
{
|
||||
uint offset = 0;
|
||||
|
||||
foreach (ref MixState mix in _mixes.Span)
|
||||
{
|
||||
mix.BufferOffset = offset;
|
||||
|
||||
offset += mix.BufferCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sort the mixes using distance from the final mix.
|
||||
/// </summary>
|
||||
public void Sort()
|
||||
{
|
||||
UpdateDistancesFromFinalMix();
|
||||
|
||||
int[] sortedMixesTemp = _sortedMixes.Slice(0, (int)GetCount()).ToArray();
|
||||
|
||||
Array.Sort(sortedMixesTemp, (a, b) =>
|
||||
{
|
||||
ref MixState stateA = ref GetState(a);
|
||||
ref MixState stateB = ref GetState(b);
|
||||
|
||||
return stateB.DistanceFromFinalMix.CompareTo(stateA.DistanceFromFinalMix);
|
||||
});
|
||||
|
||||
sortedMixesTemp.AsSpan().CopyTo(_sortedMixes.Span);
|
||||
|
||||
UpdateMixBufferOffset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sort the mixes and splitters using an adjacency matrix.
|
||||
/// </summary>
|
||||
/// <param name="splitterContext">The <see cref="SplitterContext"/> used.</param>
|
||||
/// <returns>Return true, if no errors in the graph were detected.</returns>
|
||||
public bool Sort(SplitterContext splitterContext)
|
||||
{
|
||||
if (splitterContext.UsingSplitter())
|
||||
{
|
||||
bool isValid = NodeStates.Sort(EdgeMatrix);
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
ReadOnlySpan<int> sortedMixesIndex = NodeStates.GetTsortResult();
|
||||
|
||||
int id = 0;
|
||||
|
||||
for (int i = sortedMixesIndex.Length - 1; i >= 0; i--)
|
||||
{
|
||||
SetSortedState(id++, sortedMixesIndex[i]);
|
||||
}
|
||||
|
||||
UpdateMixBufferOffset();
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateMixBufferOffset();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
330
Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
Normal file
330
Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
Normal file
|
@ -0,0 +1,330 @@
|
|||
//
|
||||
// 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.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||
using Ryujinx.Audio.Renderer.Server.Splitter;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using static Ryujinx.Audio.Constants;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Mix
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a mix.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x940, Pack = Alignment)]
|
||||
public struct MixState
|
||||
{
|
||||
public const uint InvalidDistanceFromFinalMix = 0x80000000;
|
||||
|
||||
public const int Alignment = 0x10;
|
||||
|
||||
/// <summary>
|
||||
/// Base volume of the mix.
|
||||
/// </summary>
|
||||
public float Volume;
|
||||
|
||||
/// <summary>
|
||||
/// Target sample rate of the mix.
|
||||
/// </summary>
|
||||
public uint SampleRate;
|
||||
|
||||
/// <summary>
|
||||
/// Target buffer count.
|
||||
/// </summary>
|
||||
public uint BufferCount;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if in use.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsUsed;
|
||||
|
||||
/// <summary>
|
||||
/// The id of the mix.
|
||||
/// </summary>
|
||||
public int MixId;
|
||||
|
||||
/// <summary>
|
||||
/// The mix node id.
|
||||
/// </summary>
|
||||
public int NodeId;
|
||||
|
||||
/// <summary>
|
||||
/// the buffer offset to use for command generation.
|
||||
/// </summary>
|
||||
public uint BufferOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The distance of the mix from the final mix.
|
||||
/// </summary>
|
||||
public uint DistanceFromFinalMix;
|
||||
|
||||
/// <summary>
|
||||
/// The effect processing order storage.
|
||||
/// </summary>
|
||||
private IntPtr _effectProcessingOrderArrayPointer;
|
||||
|
||||
/// <summary>
|
||||
/// The max element count that can be found in the effect processing order storage.
|
||||
/// </summary>
|
||||
public uint EffectProcessingOrderArrayMaxCount;
|
||||
|
||||
/// <summary>
|
||||
/// The mix to output the result of this mix.
|
||||
/// </summary>
|
||||
public int DestinationMixId;
|
||||
|
||||
/// <summary>
|
||||
/// Mix buffer volumes storage.
|
||||
/// </summary>
|
||||
private MixVolumeArray _mixVolumeArray;
|
||||
|
||||
/// <summary>
|
||||
/// The splitter to output the result of this mix.
|
||||
/// </summary>
|
||||
public uint DestinationSplitterId;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, the long size pre-delay is supported on the reverb command.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsLongSizePreDelaySupported;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
|
||||
private struct MixVolumeArray
|
||||
{
|
||||
private const int Size = 4 * MixBufferCountMax * MixBufferCountMax;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mix buffer volumes.
|
||||
/// </summary>
|
||||
/// <remarks>Used when no splitter id is specified.</remarks>
|
||||
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixVolumeArray, float>(ref _mixVolumeArray);
|
||||
|
||||
/// <summary>
|
||||
/// Get the volume for a given connection destination.
|
||||
/// </summary>
|
||||
/// <param name="sourceIndex">The source node index.</param>
|
||||
/// <param name="destinationIndex">The destination node index</param>
|
||||
/// <returns>The volume for the given connection destination.</returns>
|
||||
public float GetMixBufferVolume(int sourceIndex, int destinationIndex)
|
||||
{
|
||||
return MixBufferVolume[sourceIndex * MixBufferCountMax + destinationIndex];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The array used to order effects associated to this mix.
|
||||
/// </summary>
|
||||
public Span<int> EffectProcessingOrderArray
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_effectProcessingOrderArrayPointer == IntPtr.Zero)
|
||||
{
|
||||
return Span<int>.Empty;
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
return new Span<int>((void*)_effectProcessingOrderArrayPointer, (int)EffectProcessingOrderArrayMaxCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="MixState"/>
|
||||
/// </summary>
|
||||
/// <param name="effectProcessingOrderArray"></param>
|
||||
/// <param name="behaviourContext"></param>
|
||||
public MixState(Memory<int> effectProcessingOrderArray, ref BehaviourContext behaviourContext) : this()
|
||||
{
|
||||
MixId = UnusedMixId;
|
||||
|
||||
DistanceFromFinalMix = InvalidDistanceFromFinalMix;
|
||||
|
||||
DestinationMixId = UnusedMixId;
|
||||
|
||||
DestinationSplitterId = UnusedSplitterId;
|
||||
|
||||
unsafe
|
||||
{
|
||||
// SAFETY: safe as effectProcessingOrderArray comes from the work buffer memory that is pinned.
|
||||
_effectProcessingOrderArrayPointer = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetReference(effectProcessingOrderArray.Span));
|
||||
}
|
||||
|
||||
EffectProcessingOrderArrayMaxCount = (uint)effectProcessingOrderArray.Length;
|
||||
|
||||
IsLongSizePreDelaySupported = behaviourContext.IsLongSizePreDelaySupported();
|
||||
|
||||
ClearEffectProcessingOrder();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the <see cref="DistanceFromFinalMix"/> value to its default state.
|
||||
/// </summary>
|
||||
public void ClearDistanceFromFinalMix()
|
||||
{
|
||||
DistanceFromFinalMix = InvalidDistanceFromFinalMix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the <see cref="EffectProcessingOrderArray"/> to its default state.
|
||||
/// </summary>
|
||||
public void ClearEffectProcessingOrder()
|
||||
{
|
||||
EffectProcessingOrderArray.Fill(-1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the mix has any destinations.
|
||||
/// </summary>
|
||||
/// <returns>True if the mix has any destinations.</returns>
|
||||
public bool HasAnyDestination()
|
||||
{
|
||||
return DestinationMixId != UnusedMixId || DestinationSplitterId != UnusedSplitterId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the mix connection on the adjacency matrix.
|
||||
/// </summary>
|
||||
/// <param name="edgeMatrix">The adjacency matrix.</param>
|
||||
/// <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)
|
||||
{
|
||||
bool hasNewConnections;
|
||||
|
||||
if (DestinationSplitterId == UnusedSplitterId)
|
||||
{
|
||||
hasNewConnections = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ref SplitterState splitter = ref splitterContext.GetState((int)DestinationSplitterId);
|
||||
|
||||
hasNewConnections = splitter.HasNewConnection;
|
||||
}
|
||||
|
||||
if (DestinationMixId == parameter.DestinationMixId && DestinationSplitterId == parameter.DestinationSplitterId && !hasNewConnections)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
edgeMatrix.RemoveEdges(MixId);
|
||||
|
||||
if (parameter.DestinationMixId == UnusedMixId)
|
||||
{
|
||||
if (parameter.DestinationSplitterId != UnusedSplitterId)
|
||||
{
|
||||
ref SplitterState splitter = ref splitterContext.GetState((int)parameter.DestinationSplitterId);
|
||||
|
||||
for (int i = 0; i < splitter.DestinationCount; i++)
|
||||
{
|
||||
Span<SplitterDestination> destination = splitter.GetData(i);
|
||||
|
||||
if (!destination.IsEmpty)
|
||||
{
|
||||
int destinationMixId = destination[0].DestinationId;
|
||||
|
||||
if (destinationMixId != UnusedMixId)
|
||||
{
|
||||
edgeMatrix.Connect(MixId, destinationMixId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
edgeMatrix.Connect(MixId, parameter.DestinationMixId);
|
||||
}
|
||||
|
||||
DestinationMixId = parameter.DestinationMixId;
|
||||
DestinationSplitterId = parameter.DestinationSplitterId;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the mix from user information.
|
||||
/// </summary>
|
||||
/// <param name="edgeMatrix">The adjacency matrix.</param>
|
||||
/// <param name="parameter">The input parameter of the mix.</param>
|
||||
/// <param name="effectContext">The effect context.</param>
|
||||
/// <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)
|
||||
{
|
||||
bool isDirty;
|
||||
|
||||
Volume = parameter.Volume;
|
||||
SampleRate = parameter.SampleRate;
|
||||
BufferCount = parameter.BufferCount;
|
||||
IsUsed = parameter.IsUsed;
|
||||
MixId = parameter.MixId;
|
||||
NodeId = parameter.NodeId;
|
||||
parameter.MixBufferVolume.CopyTo(MixBufferVolume);
|
||||
|
||||
if (behaviourContext.IsSplitterSupported())
|
||||
{
|
||||
isDirty = UpdateConnection(edgeMatrix, ref parameter, ref splitterContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
isDirty = DestinationMixId != parameter.DestinationMixId;
|
||||
|
||||
if (DestinationMixId != parameter.DestinationMixId)
|
||||
{
|
||||
DestinationMixId = parameter.DestinationMixId;
|
||||
}
|
||||
|
||||
DestinationSplitterId = UnusedSplitterId;
|
||||
}
|
||||
|
||||
ClearEffectProcessingOrder();
|
||||
|
||||
for (int i = 0; i < effectContext.GetCount(); i++)
|
||||
{
|
||||
ref BaseEffect effect = ref effectContext.GetEffect(i);
|
||||
|
||||
if (effect.MixId == MixId)
|
||||
{
|
||||
Debug.Assert(effect.ProcessingOrder <= EffectProcessingOrderArrayMaxCount);
|
||||
|
||||
if (effect.ProcessingOrder > EffectProcessingOrderArrayMaxCount)
|
||||
{
|
||||
return isDirty;
|
||||
}
|
||||
|
||||
EffectProcessingOrderArray[(int)effect.ProcessingOrder] = i;
|
||||
}
|
||||
}
|
||||
|
||||
return isDirty;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// 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;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Performance
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a detailed entry in a performance frame.
|
||||
/// </summary>
|
||||
public interface IPerformanceDetailEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the start time of this entry event (in microseconds).
|
||||
/// </summary>
|
||||
/// <returns>The start time of this entry event (in microseconds).</returns>
|
||||
int GetStartTime();
|
||||
|
||||
/// <summary>
|
||||
/// Get the start time offset in this structure.
|
||||
/// </summary>
|
||||
/// <returns>The start time offset in this structure.</returns>
|
||||
int GetStartTimeOffset();
|
||||
|
||||
/// <summary>
|
||||
/// Get the processing time of this entry event (in microseconds).
|
||||
/// </summary>
|
||||
/// <returns>The processing time of this entry event (in microseconds).</returns>
|
||||
int GetProcessingTime();
|
||||
|
||||
/// <summary>
|
||||
/// Get the processing time offset in this structure.
|
||||
/// </summary>
|
||||
/// <returns>The processing time offset in this structure.</returns>
|
||||
int GetProcessingTimeOffset();
|
||||
|
||||
/// <summary>
|
||||
/// Set the <paramref name="nodeId"/> of this entry.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The node id of this entry.</param>
|
||||
void SetNodeId(int nodeId);
|
||||
|
||||
/// <summary>
|
||||
/// Set the <see cref="PerformanceEntryType"/> of this entry.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to use.</param>
|
||||
void SetEntryType(PerformanceEntryType type);
|
||||
|
||||
/// <summary>
|
||||
/// Set the <see cref="PerformanceDetailType"/> of this entry.
|
||||
/// </summary>
|
||||
/// <param name="detailType">The type to use.</param>
|
||||
void SetDetailType(PerformanceDetailType detailType);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// 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;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Performance
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an entry in a performance frame.
|
||||
/// </summary>
|
||||
public interface IPerformanceEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the start time of this entry event (in microseconds).
|
||||
/// </summary>
|
||||
/// <returns>The start time of this entry event (in microseconds).</returns>
|
||||
int GetStartTime();
|
||||
|
||||
/// <summary>
|
||||
/// Get the start time offset in this structure.
|
||||
/// </summary>
|
||||
/// <returns>The start time offset in this structure.</returns>
|
||||
int GetStartTimeOffset();
|
||||
|
||||
/// <summary>
|
||||
/// Get the processing time of this entry event (in microseconds).
|
||||
/// </summary>
|
||||
/// <returns>The processing time of this entry event (in microseconds).</returns>
|
||||
int GetProcessingTime();
|
||||
|
||||
/// <summary>
|
||||
/// Get the processing time offset in this structure.
|
||||
/// </summary>
|
||||
/// <returns>The processing time offset in this structure.</returns>
|
||||
int GetProcessingTimeOffset();
|
||||
|
||||
/// <summary>
|
||||
/// Set the <paramref name="nodeId"/> of this entry.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The node id of this entry.</param>
|
||||
void SetNodeId(int nodeId);
|
||||
|
||||
/// <summary>
|
||||
/// Set the <see cref="PerformanceEntryType"/> of this entry.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to use.</param>
|
||||
void SetEntryType(PerformanceEntryType type);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
//
|
||||
// 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/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Performance
|
||||
{
|
||||
/// <summary>
|
||||
/// The header of a performance frame.
|
||||
/// </summary>
|
||||
public interface IPerformanceHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the entry count offset in this structure.
|
||||
/// </summary>
|
||||
/// <returns>The entry count offset in this structure.</returns>
|
||||
int GetEntryCountOffset();
|
||||
|
||||
/// <summary>
|
||||
/// Set the DSP running behind flag.
|
||||
/// </summary>
|
||||
/// <param name="isRunningBehind">The flag.</param>
|
||||
void SetDspRunningBehind(bool isRunningBehind);
|
||||
|
||||
/// <summary>
|
||||
/// Set the count of voices that were dropped.
|
||||
/// </summary>
|
||||
/// <param name="voiceCount">The count of voices that were dropped.</param>
|
||||
void SetVoiceDropCount(uint voiceCount);
|
||||
|
||||
/// <summary>
|
||||
/// Set the start ticks of the <see cref="Dsp.AudioProcessor"/>. (before sending commands)
|
||||
/// </summary>
|
||||
/// <param name="startTicks">The start ticks of the <see cref="Dsp.AudioProcessor"/>. (before sending commands)</param>
|
||||
void SetStartRenderingTicks(ulong startTicks);
|
||||
|
||||
/// <summary>
|
||||
/// Set the header magic.
|
||||
/// </summary>
|
||||
/// <param name="magic">The header magic.</param>
|
||||
void SetMagic(uint magic);
|
||||
|
||||
/// <summary>
|
||||
/// Set the offset of the next performance header.
|
||||
/// </summary>
|
||||
/// <param name="nextOffset">The offset of the next performance header.</param>
|
||||
void SetNextOffset(int nextOffset);
|
||||
|
||||
/// <summary>
|
||||
/// Set the total time taken by all the commands profiled.
|
||||
/// </summary>
|
||||
/// <param name="totalProcessingTime">The total time taken by all the commands profiled.</param>
|
||||
void SetTotalProcessingTime(int totalProcessingTime);
|
||||
|
||||
/// <summary>
|
||||
/// Set the index of this performance frame.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of this performance frame.</param>
|
||||
void SetIndex(uint index);
|
||||
|
||||
/// <summary>
|
||||
/// Get the total count of entries in this frame.
|
||||
/// </summary>
|
||||
/// <returns>The total count of entries in this frame.</returns>
|
||||
int GetEntryCount();
|
||||
|
||||
/// <summary>
|
||||
/// Get the total count of detailed entries in this frame.
|
||||
/// </summary>
|
||||
/// <returns>The total count of detailed entries in this frame.</returns>
|
||||
int GetEntryDetailCount();
|
||||
|
||||
/// <summary>
|
||||
/// Set the total count of entries in this frame.
|
||||
/// </summary>
|
||||
/// <param name="entryCount">The total count of entries in this frame.</param>
|
||||
void SetEntryCount(int entryCount);
|
||||
|
||||
/// <summary>
|
||||
/// Set the total count of detailed entries in this frame.
|
||||
/// </summary>
|
||||
/// <param name="entryDetailCount">The total count of detailed entries in this frame.</param>
|
||||
void SetEntryDetailCount(int entryDetailCount);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
//
|
||||
// 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 System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Performance
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IPerformanceDetailEntry"/> for performance metrics version 1.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
|
||||
public struct PerformanceDetailVersion1 : IPerformanceDetailEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The node id associated to this detailed entry.
|
||||
/// </summary>
|
||||
public int NodeId;
|
||||
|
||||
/// <summary>
|
||||
/// The start time (in microseconds) associated to this detailed entry.
|
||||
/// </summary>
|
||||
public int StartTime;
|
||||
|
||||
/// <summary>
|
||||
/// The processing time (in microseconds) associated to this detailed entry.
|
||||
/// </summary>
|
||||
public int ProcessingTime;
|
||||
|
||||
/// <summary>
|
||||
/// The detailed entry type associated to this detailed entry.
|
||||
/// </summary>
|
||||
public PerformanceDetailType DetailType;
|
||||
|
||||
/// <summary>
|
||||
/// The entry type associated to this detailed entry.
|
||||
/// </summary>
|
||||
public PerformanceEntryType EntryType;
|
||||
|
||||
public int GetProcessingTime()
|
||||
{
|
||||
return ProcessingTime;
|
||||
}
|
||||
|
||||
public int GetProcessingTimeOffset()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
public int GetStartTime()
|
||||
{
|
||||
return StartTime;
|
||||
}
|
||||
|
||||
public int GetStartTimeOffset()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
public void SetDetailType(PerformanceDetailType detailType)
|
||||
{
|
||||
DetailType = detailType;
|
||||
}
|
||||
|
||||
public void SetEntryType(PerformanceEntryType type)
|
||||
{
|
||||
EntryType = type;
|
||||
}
|
||||
|
||||
public void SetNodeId(int nodeId)
|
||||
{
|
||||
NodeId = nodeId;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
//
|
||||
// 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 System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Performance
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IPerformanceDetailEntry"/> for performance metrics version 2.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)]
|
||||
public struct PerformanceDetailVersion2 : IPerformanceDetailEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The node id associated to this detailed entry.
|
||||
/// </summary>
|
||||
public int NodeId;
|
||||
|
||||
/// <summary>
|
||||
/// The start time (in microseconds) associated to this detailed entry.
|
||||
/// </summary>
|
||||
public int StartTime;
|
||||
|
||||
/// <summary>
|
||||
/// The processing time (in microseconds) associated to this detailed entry.
|
||||
/// </summary>
|
||||
public int ProcessingTime;
|
||||
|
||||
/// <summary>
|
||||
/// The detailed entry type associated to this detailed entry.
|
||||
/// </summary>
|
||||
public PerformanceDetailType DetailType;
|
||||
|
||||
/// <summary>
|
||||
/// The entry type associated to this detailed entry.
|
||||
/// </summary>
|
||||
public PerformanceEntryType EntryType;
|
||||
|
||||
public int GetProcessingTime()
|
||||
{
|
||||
return ProcessingTime;
|
||||
}
|
||||
|
||||
public int GetProcessingTimeOffset()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
public int GetStartTime()
|
||||
{
|
||||
return StartTime;
|
||||
}
|
||||
|
||||
public int GetStartTimeOffset()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
public void SetDetailType(PerformanceDetailType detailType)
|
||||
{
|
||||
DetailType = detailType;
|
||||
}
|
||||
|
||||
public void SetEntryType(PerformanceEntryType type)
|
||||
{
|
||||
EntryType = type;
|
||||
}
|
||||
|
||||
public void SetNodeId(int nodeId)
|
||||
{
|
||||
NodeId = nodeId;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// 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 System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Performance
|
||||
{
|
||||
/// <summary>
|
||||
/// Information used by the performance command to store informations in the performance entry.
|
||||
/// </summary>
|
||||
public class PerformanceEntryAddresses
|
||||
{
|
||||
/// <summary>
|
||||
/// The memory storing the performance entry.
|
||||
/// </summary>
|
||||
public Memory<int> BaseMemory;
|
||||
|
||||
/// <summary>
|
||||
/// The offset to the start time field.
|
||||
/// </summary>
|
||||
public uint StartTimeOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The offset to the entry count field.
|
||||
/// </summary>
|
||||
public uint EntryCountOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The offset to the processing time field.
|
||||
/// </summary>
|
||||
public uint ProcessingTimeOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Increment the entry count.
|
||||
/// </summary>
|
||||
public void IncrementEntryCount()
|
||||
{
|
||||
BaseMemory.Span[(int)EntryCountOffset / 4]++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the start time in the entry.
|
||||
/// </summary>
|
||||
/// <param name="startTimeNano">The start time in nanoseconds.</param>
|
||||
public void SetStartTime(ulong startTimeNano)
|
||||
{
|
||||
BaseMemory.Span[(int)StartTimeOffset / 4] = (int)(startTimeNano / 1000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the processing time in the entry.
|
||||
/// </summary>
|
||||
/// <param name="endTimeNano">The end time in nanoseconds.</param>
|
||||
public void SetProcessingTime(ulong endTimeNano)
|
||||
{
|
||||
BaseMemory.Span[(int)ProcessingTimeOffset / 4] = (int)(endTimeNano / 1000) - BaseMemory.Span[(int)StartTimeOffset / 4];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// 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 System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Performance
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IPerformanceEntry"/> for performance metrics version 1.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
|
||||
public struct PerformanceEntryVersion1 : IPerformanceEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The node id associated to this entry.
|
||||
/// </summary>
|
||||
public int NodeId;
|
||||
|
||||
/// <summary>
|
||||
/// The start time (in microseconds) associated to this entry.
|
||||
/// </summary>
|
||||
public int StartTime;
|
||||
|
||||
/// <summary>
|
||||
/// The processing time (in microseconds) associated to this entry.
|
||||
/// </summary>
|
||||
public int ProcessingTime;
|
||||
|
||||
/// <summary>
|
||||
/// The entry type associated to this entry.
|
||||
/// </summary>
|
||||
public PerformanceEntryType EntryType;
|
||||
|
||||
public int GetProcessingTime()
|
||||
{
|
||||
return ProcessingTime;
|
||||
}
|
||||
|
||||
public int GetProcessingTimeOffset()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
public int GetStartTime()
|
||||
{
|
||||
return StartTime;
|
||||
}
|
||||
|
||||
public int GetStartTimeOffset()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
public void SetEntryType(PerformanceEntryType type)
|
||||
{
|
||||
EntryType = type;
|
||||
}
|
||||
|
||||
public void SetNodeId(int nodeId)
|
||||
{
|
||||
NodeId = nodeId;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// 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 System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Performance
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IPerformanceEntry"/> for performance metrics version 2.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)]
|
||||
public struct PerformanceEntryVersion2 : IPerformanceEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The node id associated to this entry.
|
||||
/// </summary>
|
||||
public int NodeId;
|
||||
|
||||
/// <summary>
|
||||
/// The start time (in microseconds) associated to this entry.
|
||||
/// </summary>
|
||||
public int StartTime;
|
||||
|
||||
/// <summary>
|
||||
/// The processing time (in microseconds) associated to this entry.
|
||||
/// </summary>
|
||||
public int ProcessingTime;
|
||||
|
||||
/// <summary>
|
||||
/// The entry type associated to this entry.
|
||||
/// </summary>
|
||||
public PerformanceEntryType EntryType;
|
||||
|
||||
public int GetProcessingTime()
|
||||
{
|
||||
return ProcessingTime;
|
||||
}
|
||||
|
||||
public int GetProcessingTimeOffset()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
public int GetStartTime()
|
||||
{
|
||||
return StartTime;
|
||||
}
|
||||
|
||||
public int GetStartTimeOffset()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
public void SetEntryType(PerformanceEntryType type)
|
||||
{
|
||||
EntryType = type;
|
||||
}
|
||||
|
||||
public void SetNodeId(int nodeId)
|
||||
{
|
||||
NodeId = nodeId;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
//
|
||||
// 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 System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Performance
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IPerformanceHeader"/> for performance metrics version 1.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)]
|
||||
public struct PerformanceFrameHeaderVersion1 : IPerformanceHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// The magic of the performance header.
|
||||
/// </summary>
|
||||
public uint Magic;
|
||||
|
||||
/// <summary>
|
||||
/// The total count of entries in this frame.
|
||||
/// </summary>
|
||||
public int EntryCount;
|
||||
|
||||
/// <summary>
|
||||
/// The total count of detailed entries in this frame.
|
||||
/// </summary>
|
||||
public int EntryDetailCount;
|
||||
|
||||
/// <summary>
|
||||
/// The offset of the next performance header.
|
||||
/// </summary>
|
||||
public int NextOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The total time taken by all the commands profiled.
|
||||
/// </summary>
|
||||
public int TotalProcessingTime;
|
||||
|
||||
/// <summary>
|
||||
/// The count of voices that were dropped.
|
||||
/// </summary>
|
||||
public uint VoiceDropCount;
|
||||
|
||||
public int GetEntryCount()
|
||||
{
|
||||
return EntryCount;
|
||||
}
|
||||
|
||||
public int GetEntryCountOffset()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
public int GetEntryDetailCount()
|
||||
{
|
||||
return EntryDetailCount;
|
||||
}
|
||||
|
||||
public void SetDspRunningBehind(bool isRunningBehind)
|
||||
{
|
||||
// NOTE: Not present in version 1
|
||||
}
|
||||
|
||||
public void SetEntryCount(int entryCount)
|
||||
{
|
||||
EntryCount = entryCount;
|
||||
}
|
||||
|
||||
public void SetEntryDetailCount(int entryDetailCount)
|
||||
{
|
||||
EntryDetailCount = entryDetailCount;
|
||||
}
|
||||
|
||||
public void SetIndex(uint index)
|
||||
{
|
||||
// NOTE: Not present in version 1
|
||||
}
|
||||
|
||||
public void SetMagic(uint magic)
|
||||
{
|
||||
Magic = magic;
|
||||
}
|
||||
|
||||
public void SetNextOffset(int nextOffset)
|
||||
{
|
||||
NextOffset = nextOffset;
|
||||
}
|
||||
|
||||
public void SetStartRenderingTicks(ulong startTicks)
|
||||
{
|
||||
// NOTE: not present in version 1
|
||||
}
|
||||
|
||||
public void SetTotalProcessingTime(int totalProcessingTime)
|
||||
{
|
||||
TotalProcessingTime = totalProcessingTime;
|
||||
}
|
||||
|
||||
public void SetVoiceDropCount(uint voiceCount)
|
||||
{
|
||||
VoiceDropCount = voiceCount;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
//
|
||||
// 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 System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Performance
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IPerformanceHeader"/> for performance metrics version 2.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x30)]
|
||||
public struct PerformanceFrameHeaderVersion2 : IPerformanceHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// The magic of the performance header.
|
||||
/// </summary>
|
||||
public uint Magic;
|
||||
|
||||
/// <summary>
|
||||
/// The total count of entries in this frame.
|
||||
/// </summary>
|
||||
public int EntryCount;
|
||||
|
||||
/// <summary>
|
||||
/// The total count of detailed entries in this frame.
|
||||
/// </summary>
|
||||
public int EntryDetailCount;
|
||||
|
||||
/// <summary>
|
||||
/// The offset of the next performance header.
|
||||
/// </summary>
|
||||
public int NextOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The total time taken by all the commands profiled.
|
||||
/// </summary>
|
||||
public int TotalProcessingTime;
|
||||
|
||||
/// <summary>
|
||||
/// The count of voices that were dropped.
|
||||
/// </summary>
|
||||
public uint VoiceDropCount;
|
||||
|
||||
/// <summary>
|
||||
/// The start ticks of the <see cref="Dsp.AudioProcessor"/>. (before sending commands)
|
||||
/// </summary>
|
||||
public ulong StartRenderingTicks;
|
||||
|
||||
/// <summary>
|
||||
/// The index of this performance frame.
|
||||
/// </summary>
|
||||
public uint Index;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, the DSP is running behind.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsDspRunningBehind;
|
||||
|
||||
public int GetEntryCount()
|
||||
{
|
||||
return EntryCount;
|
||||
}
|
||||
|
||||
public int GetEntryCountOffset()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
public int GetEntryDetailCount()
|
||||
{
|
||||
return EntryDetailCount;
|
||||
}
|
||||
|
||||
public void SetDspRunningBehind(bool isRunningBehind)
|
||||
{
|
||||
IsDspRunningBehind = isRunningBehind;
|
||||
}
|
||||
|
||||
public void SetEntryCount(int entryCount)
|
||||
{
|
||||
EntryCount = entryCount;
|
||||
}
|
||||
|
||||
public void SetEntryDetailCount(int entryDetailCount)
|
||||
{
|
||||
EntryDetailCount = entryDetailCount;
|
||||
}
|
||||
|
||||
public void SetIndex(uint index)
|
||||
{
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public void SetMagic(uint magic)
|
||||
{
|
||||
Magic = magic;
|
||||
}
|
||||
|
||||
public void SetNextOffset(int nextOffset)
|
||||
{
|
||||
NextOffset = nextOffset;
|
||||
}
|
||||
|
||||
public void SetStartRenderingTicks(ulong startTicks)
|
||||
{
|
||||
StartRenderingTicks = startTicks;
|
||||
}
|
||||
|
||||
public void SetTotalProcessingTime(int totalProcessingTime)
|
||||
{
|
||||
TotalProcessingTime = totalProcessingTime;
|
||||
}
|
||||
|
||||
public void SetVoiceDropCount(uint voiceCount)
|
||||
{
|
||||
VoiceDropCount = voiceCount;
|
||||
}
|
||||
}
|
||||
}
|
124
Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs
Normal file
124
Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs
Normal file
|
@ -0,0 +1,124 @@
|
|||
//
|
||||
// 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.Parameter;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Performance
|
||||
{
|
||||
public abstract class PerformanceManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the required size for a single performance frame.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The audio renderer configuration.</param>
|
||||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
/// <returns>The required size for a single performance frame.</returns>
|
||||
public static ulong GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref AudioRendererConfiguration parameter, ref BehaviourContext behaviourContext)
|
||||
{
|
||||
uint version = behaviourContext.GetPerformanceMetricsDataFormat();
|
||||
|
||||
if (version == 2)
|
||||
{
|
||||
return (ulong)PerformanceManagerGeneric<PerformanceFrameHeaderVersion2,
|
||||
PerformanceEntryVersion2,
|
||||
PerformanceDetailVersion2>.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
|
||||
}
|
||||
else if (version == 1)
|
||||
{
|
||||
return (ulong)PerformanceManagerGeneric<PerformanceFrameHeaderVersion1,
|
||||
PerformanceEntryVersion1,
|
||||
PerformanceDetailVersion1>.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
|
||||
}
|
||||
|
||||
throw new NotImplementedException($"Unknown Performance metrics data format version {version}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy the performance frame history to the supplied user buffer and returns the size copied.
|
||||
/// </summary>
|
||||
/// <param name="performanceOutput">The supplied user buffer to store the performance frame into.</param>
|
||||
/// <returns>The size copied to the supplied buffer.</returns>
|
||||
public abstract uint CopyHistories(Span<byte> performanceOutput);
|
||||
|
||||
/// <summary>
|
||||
/// Set the target node id to profile.
|
||||
/// </summary>
|
||||
/// <param name="target">The target node id to profile.</param>
|
||||
public abstract void SetTargetNodeId(int target);
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given target node id is profiled.
|
||||
/// </summary>
|
||||
/// <param name="target">The target node id to check.</param>
|
||||
/// <returns>Return true, if the given target node id is profiled.</returns>
|
||||
public abstract bool IsTargetNodeId(int target);
|
||||
|
||||
/// <summary>
|
||||
/// Get the next buffer to store a performance entry.
|
||||
/// </summary>
|
||||
/// <param name="performanceEntry">The output <see cref="PerformanceEntryAddresses"/>.</param>
|
||||
/// <param name="entryType">The <see cref="PerformanceEntryType"/> info.</param>
|
||||
/// <param name="nodeId">The node id of the entry.</param>
|
||||
/// <returns>Return true, if a valid <see cref="PerformanceEntryAddresses"/> was returned.</returns>
|
||||
public abstract bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceEntryType entryType, int nodeId);
|
||||
|
||||
/// <summary>
|
||||
/// Get the next buffer to store a performance detailed entry.
|
||||
/// </summary>
|
||||
/// <param name="performanceEntry">The output <see cref="PerformanceEntryAddresses"/>.</param>
|
||||
/// <param name="detailType">The <see cref="PerformanceDetailType"/> info.</param>
|
||||
/// <param name="entryType">The <see cref="PerformanceEntryType"/> info.</param>
|
||||
/// <param name="nodeId">The node id of the entry.</param>
|
||||
/// <returns>Return true, if a valid <see cref="PerformanceEntryAddresses"/> was returned.</returns>
|
||||
public abstract bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceDetailType detailType, PerformanceEntryType entryType, int nodeId);
|
||||
|
||||
/// <summary>
|
||||
/// Finalize the current performance frame.
|
||||
/// </summary>
|
||||
/// <param name="dspRunningBehind">Indicate if the DSP is running behind.</param>
|
||||
/// <param name="voiceDropCount">The count of voices that were dropped.</param>
|
||||
/// <param name="startRenderingTicks">The start ticks of the audio rendering.</param>
|
||||
public abstract void TapFrame(bool dspRunningBehind, uint voiceDropCount, ulong startRenderingTicks);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="PerformanceManager"/>.
|
||||
/// </summary>
|
||||
/// <param name="performanceBuffer">The backing memory available for use by the manager.</param>
|
||||
/// <param name="parameter">The audio renderer configuration.</param>
|
||||
/// <param name="behaviourContext">The behaviour context;</param>
|
||||
/// <returns>A new <see cref="PerformanceManager"/>.</returns>
|
||||
public static PerformanceManager Create(Memory<byte> performanceBuffer, ref AudioRendererConfiguration parameter, BehaviourContext behaviourContext)
|
||||
{
|
||||
uint version = behaviourContext.GetPerformanceMetricsDataFormat();
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
return new PerformanceManagerGeneric<PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, PerformanceDetailVersion1>(performanceBuffer,
|
||||
ref parameter);
|
||||
case 2:
|
||||
return new PerformanceManagerGeneric<PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, PerformanceDetailVersion2>(performanceBuffer,
|
||||
ref parameter);
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown Performance metrics data format version {version}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,311 @@
|
|||
//
|
||||
// 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.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Performance
|
||||
{
|
||||
/// <summary>
|
||||
/// A Generic implementation of <see cref="PerformanceManager"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="THeader">The header implementation of the performance frame.</typeparam>
|
||||
/// <typeparam name="TEntry">The entry implementation of the performance frame.</typeparam>
|
||||
/// <typeparam name="TEntryDetail">A detailed implementation of the performance frame.</typeparam>
|
||||
public class PerformanceManagerGeneric<THeader, TEntry, TEntryDetail> : PerformanceManager where THeader: unmanaged, IPerformanceHeader where TEntry : unmanaged, IPerformanceEntry where TEntryDetail: unmanaged, IPerformanceDetailEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The magic used for the <see cref="THeader"/>.
|
||||
/// </summary>
|
||||
private const uint MagicPerformanceBuffer = 0x46524550;
|
||||
|
||||
/// <summary>
|
||||
/// The fixed amount of <see cref="TEntryDetail"/> that can be stored in a frame.
|
||||
/// </summary>
|
||||
private const int MaxFrameDetailCount = 100;
|
||||
|
||||
private Memory<byte> _buffer;
|
||||
private Memory<byte> _historyBuffer;
|
||||
|
||||
private Memory<byte> CurrentBuffer => _buffer.Slice(0, _frameSize);
|
||||
private Memory<byte> CurrentBufferData => CurrentBuffer.Slice(Unsafe.SizeOf<THeader>());
|
||||
|
||||
private ref THeader CurrentHeader => ref MemoryMarshal.Cast<byte, THeader>(CurrentBuffer.Span)[0];
|
||||
|
||||
private Span<TEntry> Entries => MemoryMarshal.Cast<byte, TEntry>(CurrentBufferData.Span.Slice(0, GetEntriesSize()));
|
||||
private Span<TEntryDetail> EntriesDetail => MemoryMarshal.Cast<byte, TEntryDetail>(CurrentBufferData.Span.Slice(GetEntriesSize(), GetEntriesDetailSize()));
|
||||
|
||||
private int _frameSize;
|
||||
private int _availableFrameCount;
|
||||
private int _entryCountPerFrame;
|
||||
private int _detailTarget;
|
||||
private int _entryIndex;
|
||||
private int _entryDetailIndex;
|
||||
private int _indexHistoryWrite;
|
||||
private int _indexHistoryRead;
|
||||
private uint _historyFrameIndex;
|
||||
|
||||
public PerformanceManagerGeneric(Memory<byte> buffer, ref AudioRendererConfiguration parameter)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_frameSize = GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
|
||||
|
||||
_entryCountPerFrame = (int)GetEntryCount(ref parameter);
|
||||
_availableFrameCount = buffer.Length / _frameSize - 1;
|
||||
|
||||
_historyFrameIndex = 0;
|
||||
|
||||
_historyBuffer = _buffer.Slice(_frameSize);
|
||||
|
||||
SetupNewHeader();
|
||||
}
|
||||
|
||||
private Span<byte> GetBufferFromIndex(Span<byte> data, int index)
|
||||
{
|
||||
return data.Slice(index * _frameSize, _frameSize);
|
||||
}
|
||||
|
||||
private ref THeader GetHeaderFromBuffer(Span<byte> data, int index)
|
||||
{
|
||||
return ref MemoryMarshal.Cast<byte, THeader>(GetBufferFromIndex(data, index))[0];
|
||||
}
|
||||
|
||||
private Span<TEntry> GetEntriesFromBuffer(Span<byte> data, int index)
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, TEntry>(GetBufferFromIndex(data, index).Slice(Unsafe.SizeOf<THeader>(), GetEntriesSize()));
|
||||
}
|
||||
|
||||
private Span<TEntryDetail> GetEntriesDetailFromBuffer(Span<byte> data, int index)
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, TEntryDetail>(GetBufferFromIndex(data, index).Slice(Unsafe.SizeOf<THeader>() + GetEntriesSize(), GetEntriesDetailSize()));
|
||||
}
|
||||
|
||||
private void SetupNewHeader()
|
||||
{
|
||||
_entryIndex = 0;
|
||||
_entryDetailIndex = 0;
|
||||
|
||||
CurrentHeader.SetEntryCount(0);
|
||||
CurrentHeader.SetEntryDetailCount(0);
|
||||
}
|
||||
|
||||
public static uint GetEntryCount(ref AudioRendererConfiguration parameter)
|
||||
{
|
||||
return parameter.VoiceCount + parameter.EffectCount + parameter.SubMixBufferCount + parameter.SinkCount + 1;
|
||||
}
|
||||
|
||||
public int GetEntriesSize()
|
||||
{
|
||||
return Unsafe.SizeOf<TEntry>() * _entryCountPerFrame;
|
||||
}
|
||||
|
||||
public static int GetEntriesDetailSize()
|
||||
{
|
||||
return Unsafe.SizeOf<TEntryDetail>() * MaxFrameDetailCount;
|
||||
}
|
||||
|
||||
public static int GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref AudioRendererConfiguration parameter)
|
||||
{
|
||||
return Unsafe.SizeOf<TEntry>() * (int)GetEntryCount(ref parameter) + GetEntriesDetailSize() + Unsafe.SizeOf<THeader>();
|
||||
}
|
||||
|
||||
public override uint CopyHistories(Span<byte> performanceOutput)
|
||||
{
|
||||
if (performanceOutput.IsEmpty)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nextOffset = 0;
|
||||
|
||||
while (_indexHistoryRead != _indexHistoryWrite)
|
||||
{
|
||||
if (nextOffset >= performanceOutput.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ref THeader inputHeader = ref GetHeaderFromBuffer(_historyBuffer.Span, _indexHistoryRead);
|
||||
Span<TEntry> inputEntries = GetEntriesFromBuffer(_historyBuffer.Span, _indexHistoryRead);
|
||||
Span<TEntryDetail> inputEntriesDetail = GetEntriesDetailFromBuffer(_historyBuffer.Span, _indexHistoryRead);
|
||||
|
||||
Span<byte> targetSpan = performanceOutput.Slice(nextOffset);
|
||||
|
||||
ref THeader outputHeader = ref MemoryMarshal.Cast<byte, THeader>(targetSpan)[0];
|
||||
|
||||
nextOffset += Unsafe.SizeOf<THeader>();
|
||||
|
||||
Span<TEntry> outputEntries = MemoryMarshal.Cast<byte, TEntry>(targetSpan.Slice(nextOffset));
|
||||
|
||||
int totalProcessingTime = 0;
|
||||
|
||||
int effectiveEntryCount = 0;
|
||||
|
||||
for (int entryIndex = 0; entryIndex < inputHeader.GetEntryCount(); entryIndex++)
|
||||
{
|
||||
ref TEntry input = ref inputEntries[entryIndex];
|
||||
|
||||
if (input.GetProcessingTime() != 0 || input.GetStartTime() != 0)
|
||||
{
|
||||
ref TEntry output = ref outputEntries[effectiveEntryCount++];
|
||||
|
||||
output = input;
|
||||
|
||||
nextOffset += Unsafe.SizeOf<TEntry>();
|
||||
|
||||
totalProcessingTime += input.GetProcessingTime();
|
||||
}
|
||||
}
|
||||
|
||||
Span<TEntryDetail> outputEntriesDetail = MemoryMarshal.Cast<byte, TEntryDetail>(targetSpan.Slice(nextOffset));
|
||||
|
||||
int effectiveEntryDetailCount = 0;
|
||||
|
||||
for (int entryDetailIndex = 0; entryDetailIndex < inputHeader.GetEntryDetailCount(); entryDetailIndex++)
|
||||
{
|
||||
ref TEntryDetail input = ref inputEntriesDetail[entryDetailIndex];
|
||||
|
||||
if (input.GetProcessingTime() != 0 || input.GetStartTime() != 0)
|
||||
{
|
||||
ref TEntryDetail output = ref outputEntriesDetail[effectiveEntryDetailCount++];
|
||||
|
||||
output = input;
|
||||
|
||||
nextOffset += Unsafe.SizeOf<TEntryDetail>();
|
||||
}
|
||||
}
|
||||
|
||||
outputHeader = inputHeader;
|
||||
outputHeader.SetMagic(MagicPerformanceBuffer);
|
||||
outputHeader.SetTotalProcessingTime(totalProcessingTime);
|
||||
outputHeader.SetNextOffset(nextOffset);
|
||||
outputHeader.SetEntryCount(effectiveEntryCount);
|
||||
outputHeader.SetEntryDetailCount(effectiveEntryDetailCount);
|
||||
|
||||
_indexHistoryRead = (_indexHistoryRead + 1) % _availableFrameCount;
|
||||
}
|
||||
|
||||
if (nextOffset < performanceOutput.Length && (performanceOutput.Length - nextOffset) >= Unsafe.SizeOf<THeader>())
|
||||
{
|
||||
ref THeader outputHeader = ref MemoryMarshal.Cast<byte, THeader>(performanceOutput.Slice(nextOffset))[0];
|
||||
|
||||
outputHeader = default;
|
||||
}
|
||||
|
||||
return (uint)nextOffset;
|
||||
}
|
||||
|
||||
public override bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceEntryType entryType, int nodeId)
|
||||
{
|
||||
performanceEntry = new PerformanceEntryAddresses();
|
||||
performanceEntry.BaseMemory = SpanMemoryManager<int>.Cast(CurrentBuffer);
|
||||
performanceEntry.EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset();
|
||||
|
||||
uint baseEntryOffset = (uint)(Unsafe.SizeOf<THeader>() + Unsafe.SizeOf<TEntry>() * _entryIndex);
|
||||
|
||||
ref TEntry entry = ref Entries[_entryIndex];
|
||||
|
||||
performanceEntry.StartTimeOffset = baseEntryOffset + (uint)entry.GetStartTimeOffset();
|
||||
performanceEntry.ProcessingTimeOffset = baseEntryOffset + (uint)entry.GetProcessingTimeOffset();
|
||||
|
||||
entry = default;
|
||||
entry.SetEntryType(entryType);
|
||||
entry.SetNodeId(nodeId);
|
||||
|
||||
_entryIndex++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceDetailType detailType, PerformanceEntryType entryType, int nodeId)
|
||||
{
|
||||
performanceEntry = null;
|
||||
|
||||
if (_entryDetailIndex > MaxFrameDetailCount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
performanceEntry = new PerformanceEntryAddresses();
|
||||
performanceEntry.BaseMemory = SpanMemoryManager<int>.Cast(CurrentBuffer);
|
||||
performanceEntry.EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset();
|
||||
|
||||
uint baseEntryOffset = (uint)(Unsafe.SizeOf<THeader>() + GetEntriesSize() + Unsafe.SizeOf<IPerformanceDetailEntry>() * _entryDetailIndex);
|
||||
|
||||
ref TEntryDetail entryDetail = ref EntriesDetail[_entryDetailIndex];
|
||||
|
||||
performanceEntry.StartTimeOffset = baseEntryOffset + (uint)entryDetail.GetStartTimeOffset();
|
||||
performanceEntry.ProcessingTimeOffset = baseEntryOffset + (uint)entryDetail.GetProcessingTimeOffset();
|
||||
|
||||
entryDetail = default;
|
||||
entryDetail.SetDetailType(detailType);
|
||||
entryDetail.SetEntryType(entryType);
|
||||
entryDetail.SetNodeId(nodeId);
|
||||
|
||||
_entryDetailIndex++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool IsTargetNodeId(int target)
|
||||
{
|
||||
return _detailTarget == target;
|
||||
}
|
||||
|
||||
public override void SetTargetNodeId(int target)
|
||||
{
|
||||
_detailTarget = target;
|
||||
}
|
||||
|
||||
public override void TapFrame(bool dspRunningBehind, uint voiceDropCount, ulong startRenderingTicks)
|
||||
{
|
||||
if (_availableFrameCount > 0)
|
||||
{
|
||||
int targetIndexForHistory = _indexHistoryWrite;
|
||||
|
||||
_indexHistoryWrite = (_indexHistoryWrite + 1) % _availableFrameCount;
|
||||
|
||||
ref THeader targetHeader = ref GetHeaderFromBuffer(_historyBuffer.Span, targetIndexForHistory);
|
||||
|
||||
CurrentBuffer.Span.CopyTo(GetBufferFromIndex(_historyBuffer.Span, targetIndexForHistory));
|
||||
|
||||
uint targetHistoryFrameIndex = _historyFrameIndex;
|
||||
|
||||
if (_historyFrameIndex == uint.MaxValue)
|
||||
{
|
||||
_historyFrameIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_historyFrameIndex++;
|
||||
}
|
||||
|
||||
targetHeader.SetDspRunningBehind(dspRunningBehind);
|
||||
targetHeader.SetVoiceDropCount(voiceDropCount);
|
||||
targetHeader.SetStartRenderingTicks(startRenderingTicks);
|
||||
targetHeader.SetIndex(targetHistoryFrameIndex);
|
||||
|
||||
// Finally setup the new header
|
||||
SetupNewHeader();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
65
Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs
Normal file
65
Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// 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.Server.Upsampler;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a lite version of <see cref="AudioRenderSystem"/> used by the <see cref="Dsp.AudioProcessor"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This also allows to reduce dependencies on the <see cref="AudioRenderSystem"/> for unit testing.
|
||||
/// </remarks>
|
||||
public sealed class RendererSystemContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The session id of the current renderer.
|
||||
/// </summary>
|
||||
public int SessionId;
|
||||
|
||||
/// <summary>
|
||||
/// The target channel count for sink.
|
||||
/// </summary>
|
||||
/// <remarks>See <see cref="CommandGenerator.GenerateDevice(Sink.DeviceSink, ref Mix.MixState)"/> for usage.</remarks>
|
||||
public uint ChannelCount;
|
||||
|
||||
/// <summary>
|
||||
/// The total count of mix buffer.
|
||||
/// </summary>
|
||||
public uint MixBufferCount;
|
||||
|
||||
/// <summary>
|
||||
/// Instance of the <see cref="BehaviourContext"/> used to derive bug fixes and features of the current audio renderer revision.
|
||||
/// </summary>
|
||||
public BehaviourContext BehaviourContext;
|
||||
|
||||
/// <summary>
|
||||
/// Instance of the <see cref="UpsamplerManager"/> used for upsampling (see <see cref="UpsamplerState"/>)
|
||||
/// </summary>
|
||||
public UpsamplerManager UpsamplerManager;
|
||||
|
||||
/// <summary>
|
||||
/// The memory to use for depop processing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see cref="Dsp.Command.DepopForMixBuffersCommand"/> and <see cref="Dsp.Command.DepopPrepareCommand"/>
|
||||
/// </remarks>
|
||||
public Memory<float> DepopBuffer;
|
||||
}
|
||||
}
|
119
Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs
Normal file
119
Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs
Normal file
|
@ -0,0 +1,119 @@
|
|||
//
|
||||
// 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.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using System.Diagnostics;
|
||||
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Sink
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class used for server information of a sink.
|
||||
/// </summary>
|
||||
public class BaseSink
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of this <see cref="BaseSink"/>.
|
||||
/// </summary>
|
||||
public SinkType Type;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the sink is used.
|
||||
/// </summary>
|
||||
public bool IsUsed;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the sink need to be skipped because of invalid state.
|
||||
/// </summary>
|
||||
public bool ShouldSkip;
|
||||
|
||||
/// <summary>
|
||||
/// The node id of the sink.
|
||||
/// </summary>
|
||||
public int NodeId;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="BaseSink"/>.
|
||||
/// </summary>
|
||||
public BaseSink()
|
||||
{
|
||||
CleanUp();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean up the internal state of the <see cref="BaseSink"/>.
|
||||
/// </summary>
|
||||
public virtual void CleanUp()
|
||||
{
|
||||
Type = TargetSinkType;
|
||||
IsUsed = false;
|
||||
ShouldSkip = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The target <see cref="SinkType"/> handled by this <see cref="BaseSink"/>.
|
||||
/// </summary>
|
||||
public virtual SinkType TargetSinkType => SinkType.Invalid;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the <see cref="SinkType"/> sent by the user match the internal <see cref="SinkType"/>.
|
||||
/// </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)
|
||||
{
|
||||
return parameter.Type == TargetSinkType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the <see cref="BaseSink"/> state during command generation.
|
||||
/// </summary>
|
||||
public virtual void UpdateForCommandGeneration()
|
||||
{
|
||||
Debug.Assert(Type == TargetSinkType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal common parameters from user parameter.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
protected void UpdateStandardParameter(ref SinkInParameter parameter)
|
||||
{
|
||||
if (IsUsed != parameter.IsUsed)
|
||||
{
|
||||
IsUsed = parameter.IsUsed;
|
||||
NodeId = parameter.NodeId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal state from user parameter.
|
||||
/// </summary>
|
||||
/// <param name="errorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
|
||||
/// <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)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
|
||||
errorInfo = new ErrorInfo();
|
||||
}
|
||||
}
|
||||
}
|
126
Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs
Normal file
126
Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs
Normal file
|
@ -0,0 +1,126 @@
|
|||
//
|
||||
// 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.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Sink;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Sink
|
||||
{
|
||||
/// <summary>
|
||||
/// Server information for a circular buffer sink.
|
||||
/// </summary>
|
||||
public class CircularBufferSink : BaseSink
|
||||
{
|
||||
/// <summary>
|
||||
/// The circular buffer parameter.
|
||||
/// </summary>
|
||||
public CircularBufferParameter Parameter;
|
||||
|
||||
/// <summary>
|
||||
/// The last written data offset on the circular buffer.
|
||||
/// </summary>
|
||||
private uint _lastWrittenOffset;
|
||||
|
||||
/// <summary>
|
||||
/// THe previous written offset of the circular buffer.
|
||||
/// </summary>
|
||||
private uint _oldWrittenOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The current offset to write data on the circular buffer.
|
||||
/// </summary>
|
||||
public uint CurrentWriteOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AddressInfo"/> of the circular buffer.
|
||||
/// </summary>
|
||||
public AddressInfo CircularBufferAddressInfo;
|
||||
|
||||
public CircularBufferSink()
|
||||
{
|
||||
CircularBufferAddressInfo = AddressInfo.Create();
|
||||
}
|
||||
|
||||
public override SinkType TargetSinkType => SinkType.CircularBuffer;
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
|
||||
{
|
||||
errorInfo = new BehaviourParameter.ErrorInfo();
|
||||
outStatus = new SinkOutStatus();
|
||||
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
|
||||
ref CircularBufferParameter inputDeviceParameter = ref MemoryMarshal.Cast<byte, CircularBufferParameter>(parameter.SpecificData)[0];
|
||||
|
||||
if (parameter.IsUsed != IsUsed || ShouldSkip)
|
||||
{
|
||||
UpdateStandardParameter(ref parameter);
|
||||
|
||||
if (parameter.IsUsed)
|
||||
{
|
||||
Debug.Assert(CircularBufferAddressInfo.CpuAddress == 0);
|
||||
Debug.Assert(CircularBufferAddressInfo.GetReference(false) == 0);
|
||||
|
||||
ShouldSkip = !mapper.TryAttachBuffer(out errorInfo, ref CircularBufferAddressInfo, inputDeviceParameter.BufferAddress, inputDeviceParameter.BufferSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(CircularBufferAddressInfo.CpuAddress != 0);
|
||||
Debug.Assert(CircularBufferAddressInfo.GetReference(false) != 0);
|
||||
}
|
||||
|
||||
Parameter = inputDeviceParameter;
|
||||
}
|
||||
|
||||
outStatus.LastWrittenOffset = _lastWrittenOffset;
|
||||
}
|
||||
|
||||
public override void UpdateForCommandGeneration()
|
||||
{
|
||||
Debug.Assert(Type == TargetSinkType);
|
||||
|
||||
if (IsUsed)
|
||||
{
|
||||
uint frameSize = Constants.TargetSampleSize * Parameter.SampleCount * Parameter.InputCount;
|
||||
|
||||
_lastWrittenOffset = _oldWrittenOffset;
|
||||
|
||||
_oldWrittenOffset = CurrentWriteOffset;
|
||||
|
||||
CurrentWriteOffset += frameSize;
|
||||
|
||||
if (Parameter.BufferSize > 0)
|
||||
{
|
||||
CurrentWriteOffset %= Parameter.BufferSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void CleanUp()
|
||||
{
|
||||
CircularBufferAddressInfo = AddressInfo.Create();
|
||||
_lastWrittenOffset = 0;
|
||||
_oldWrittenOffset = 0;
|
||||
CurrentWriteOffset = 0;
|
||||
base.CleanUp();
|
||||
}
|
||||
}
|
||||
}
|
92
Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs
Normal file
92
Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs
Normal file
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// 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.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Sink;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using Ryujinx.Audio.Renderer.Server.Upsampler;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Sink
|
||||
{
|
||||
/// <summary>
|
||||
/// Server information for a device sink.
|
||||
/// </summary>
|
||||
public class DeviceSink : BaseSink
|
||||
{
|
||||
/// <summary>
|
||||
/// The downmix coefficients.
|
||||
/// </summary>
|
||||
public float[] DownMixCoefficients;
|
||||
|
||||
/// <summary>
|
||||
/// The device parameters.
|
||||
/// </summary>
|
||||
public DeviceParameter Parameter;
|
||||
|
||||
/// <summary>
|
||||
/// The upsampler instance used by this sink.
|
||||
/// </summary>
|
||||
/// <remarks>Null if no upsampling is needed.</remarks>
|
||||
public UpsamplerState UpsamplerState;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="DeviceSink"/>.
|
||||
/// </summary>
|
||||
public DeviceSink()
|
||||
{
|
||||
DownMixCoefficients = new float[4];
|
||||
}
|
||||
|
||||
public override void CleanUp()
|
||||
{
|
||||
UpsamplerState?.Release();
|
||||
|
||||
UpsamplerState = null;
|
||||
|
||||
base.CleanUp();
|
||||
}
|
||||
|
||||
public override SinkType TargetSinkType => SinkType.Device;
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
|
||||
ref DeviceParameter inputDeviceParameter = ref MemoryMarshal.Cast<byte, DeviceParameter>(parameter.SpecificData)[0];
|
||||
|
||||
if (parameter.IsUsed != IsUsed)
|
||||
{
|
||||
UpdateStandardParameter(ref parameter);
|
||||
Parameter = inputDeviceParameter;
|
||||
}
|
||||
else
|
||||
{
|
||||
Parameter.DownMixParameterEnabled = inputDeviceParameter.DownMixParameterEnabled;
|
||||
inputDeviceParameter.DownMixParameter.ToSpan().CopyTo(Parameter.DownMixParameter.ToSpan());
|
||||
}
|
||||
|
||||
Parameter.DownMixParameter.ToSpan().CopyTo(DownMixCoefficients.AsSpan());
|
||||
|
||||
errorInfo = new BehaviourParameter.ErrorInfo();
|
||||
outStatus = new SinkOutStatus();
|
||||
}
|
||||
}
|
||||
}
|
73
Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs
Normal file
73
Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs
Normal file
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// 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 System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Sink
|
||||
{
|
||||
/// <summary>
|
||||
/// Sink context.
|
||||
/// </summary>
|
||||
public class SinkContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Storage for <see cref="BaseSink"/>.
|
||||
/// </summary>
|
||||
private BaseSink[] _sinks;
|
||||
|
||||
/// <summary>
|
||||
/// The total sink count.
|
||||
/// </summary>
|
||||
private uint _sinkCount;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the <see cref="SinkContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="sinksCount">The total sink count.</param>
|
||||
public void Initialize(uint sinksCount)
|
||||
{
|
||||
_sinkCount = sinksCount;
|
||||
_sinks = new BaseSink[_sinkCount];
|
||||
|
||||
for (int i = 0; i < _sinkCount; i++)
|
||||
{
|
||||
_sinks[i] = new BaseSink();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the total sink count.
|
||||
/// </summary>
|
||||
/// <returns>The total sink count.</returns>
|
||||
public uint GetCount()
|
||||
{
|
||||
return _sinkCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a reference to a <see cref="BaseSink"/> at the given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The index to use.</param>
|
||||
/// <returns>A reference to a <see cref="BaseSink"/> at the given <paramref name="id"/>.</returns>
|
||||
public ref BaseSink GetSink(int id)
|
||||
{
|
||||
Debug.Assert(id >= 0 && id < _sinkCount);
|
||||
|
||||
return ref _sinks[id];
|
||||
}
|
||||
}
|
||||
}
|
320
Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
Normal file
320
Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
Normal file
|
@ -0,0 +1,320 @@
|
|||
//
|
||||
// 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.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Splitter context.
|
||||
/// </summary>
|
||||
public class SplitterContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Storage for <see cref="SplitterState"/>.
|
||||
/// </summary>
|
||||
private Memory<SplitterState> _splitters;
|
||||
|
||||
/// <summary>
|
||||
/// Storage for <see cref="SplitterDestination"/>.
|
||||
/// </summary>
|
||||
private Memory<SplitterDestination> _splitterDestinations;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, ref SplitterInParameter, ReadOnlySpan{byte})"/>.
|
||||
/// </summary>
|
||||
public bool IsBugFixed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize <see cref="SplitterContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
/// <param name="parameter">The audio renderer configuration.</param>
|
||||
/// <param name="workBufferAllocator">The <see cref="WorkBufferAllocator"/>.</param>
|
||||
/// <returns>Return true if the initialization was successful.</returns>
|
||||
public bool Initialize(ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter, WorkBufferAllocator workBufferAllocator)
|
||||
{
|
||||
if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0)
|
||||
{
|
||||
Setup(Memory<SplitterState>.Empty, Memory<SplitterDestination>.Empty, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Memory<SplitterState> splitters = workBufferAllocator.Allocate<SplitterState>(parameter.SplitterCount, SplitterState.Alignment);
|
||||
|
||||
if (splitters.IsEmpty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int splitterId = 0;
|
||||
|
||||
foreach (ref SplitterState splitter in splitters.Span)
|
||||
{
|
||||
splitter = new SplitterState(splitterId++);
|
||||
}
|
||||
|
||||
Memory<SplitterDestination> splitterDestinations = workBufferAllocator.Allocate<SplitterDestination>(parameter.SplitterDestinationCount,
|
||||
SplitterDestination.Alignment);
|
||||
|
||||
if (splitterDestinations.IsEmpty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int splitterDestinationId = 0;
|
||||
foreach (ref SplitterDestination data in splitterDestinations.Span)
|
||||
{
|
||||
data = new SplitterDestination(splitterDestinationId++);
|
||||
}
|
||||
|
||||
SplitterState.InitializeSplitters(splitters.Span);
|
||||
|
||||
Setup(splitters, splitterDestinations, behaviourContext.IsSplitterBugFixed());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the work buffer size while adding the size needed for splitter to operate.
|
||||
/// </summary>
|
||||
/// <param name="size">The current size.</param>
|
||||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
/// <param name="parameter">The renderer configuration.</param>
|
||||
/// <returns>Return the new size taking splitter into account.</returns>
|
||||
public static ulong GetWorkBufferSize(ulong size, ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter)
|
||||
{
|
||||
if (behaviourContext.IsSplitterSupported())
|
||||
{
|
||||
size = WorkBufferAllocator.GetTargetSize<SplitterState>(size, parameter.SplitterCount, SplitterState.Alignment);
|
||||
size = WorkBufferAllocator.GetTargetSize<SplitterDestination>(size, parameter.SplitterDestinationCount, SplitterDestination.Alignment);
|
||||
|
||||
if (behaviourContext.IsSplitterBugFixed())
|
||||
{
|
||||
size = WorkBufferAllocator.GetTargetSize<int>(size, parameter.SplitterDestinationCount, 0x10);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
else
|
||||
{
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the <see cref="SplitterContext"/> instance.
|
||||
/// </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>
|
||||
private void Setup(Memory<SplitterState> splitters, Memory<SplitterDestination> splitterDestinations, bool isBugFixed)
|
||||
{
|
||||
_splitters = splitters;
|
||||
_splitterDestinations = splitterDestinations;
|
||||
IsBugFixed = isBugFixed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the new connection flag.
|
||||
/// </summary>
|
||||
private void ClearAllNewConnectionFlag()
|
||||
{
|
||||
foreach (ref SplitterState splitter in _splitters.Span)
|
||||
{
|
||||
splitter.ClearNewConnectionFlag();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the destination count using the count of splitter.
|
||||
/// </summary>
|
||||
/// <returns>The destination count using the count of splitter.</returns>
|
||||
public int GetDestinationCountPerStateForCompatibility()
|
||||
{
|
||||
if (_splitters.IsEmpty)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return _splitterDestinations.Length / _splitters.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update one or multiple <see cref="SplitterState"/> from user parameters.
|
||||
/// </summary>
|
||||
/// <param name="inputHeader">The splitter header.</param>
|
||||
/// <param name="input">The raw data after the splitter header.</param>
|
||||
private void UpdateState(ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan<byte> input)
|
||||
{
|
||||
for (int i = 0; i < inputHeader.SplitterCount; i++)
|
||||
{
|
||||
SplitterInParameter parameter = MemoryMarshal.Read<SplitterInParameter>(input);
|
||||
|
||||
Debug.Assert(parameter.IsMagicValid());
|
||||
|
||||
if (parameter.IsMagicValid())
|
||||
{
|
||||
if (parameter.Id >= 0 && parameter.Id < _splitters.Length)
|
||||
{
|
||||
ref SplitterState splitter = ref GetState(parameter.Id);
|
||||
|
||||
splitter.Update(this, ref parameter, input.Slice(Unsafe.SizeOf<SplitterInParameter>()));
|
||||
}
|
||||
|
||||
input = input.Slice(0x1C + (int)parameter.DestinationCount * 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update one or multiple <see cref="SplitterDestination"/> from user parameters.
|
||||
/// </summary>
|
||||
/// <param name="inputHeader">The splitter header.</param>
|
||||
/// <param name="input">The raw data after the splitter header.</param>
|
||||
private void UpdateData(ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan<byte> input)
|
||||
{
|
||||
for (int i = 0; i < inputHeader.SplitterDestinationCount; i++)
|
||||
{
|
||||
SplitterDestinationInParameter parameter = MemoryMarshal.Read<SplitterDestinationInParameter>(input);
|
||||
|
||||
Debug.Assert(parameter.IsMagicValid());
|
||||
|
||||
if (parameter.IsMagicValid())
|
||||
{
|
||||
if (parameter.Id >= 0 && parameter.Id < _splitterDestinations.Length)
|
||||
{
|
||||
ref SplitterDestination destination = ref GetDestination(parameter.Id);
|
||||
|
||||
destination.Update(parameter);
|
||||
}
|
||||
|
||||
input = input.Slice(Unsafe.SizeOf<SplitterDestinationInParameter>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
{
|
||||
if (_splitterDestinations.IsEmpty || _splitters.IsEmpty)
|
||||
{
|
||||
consumedSize = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int originalSize = input.Length;
|
||||
|
||||
SplitterInParameterHeader header = SpanIOHelper.Read<SplitterInParameterHeader>(ref input);
|
||||
|
||||
if (header.IsMagicValid())
|
||||
{
|
||||
ClearAllNewConnectionFlag();
|
||||
|
||||
UpdateState(ref header, ref input);
|
||||
UpdateData(ref header, ref input);
|
||||
|
||||
consumedSize = BitUtils.AlignUp(originalSize - input.Length, 0x10);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
consumedSize = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a reference to a <see cref="SplitterState"/> at the given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The index to use.</param>
|
||||
/// <returns>A reference to a <see cref="SplitterState"/> at the given <paramref name="id"/>.</returns>
|
||||
public ref SplitterState GetState(int id)
|
||||
{
|
||||
return ref SpanIOHelper.GetFromMemory(_splitters, id, (uint)_splitters.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a reference to a <see cref="SplitterDestination"/> at the given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The index to use.</param>
|
||||
/// <returns>A reference to a <see cref="SplitterDestination"/> at the given <paramref name="id"/>.</returns>
|
||||
public ref SplitterDestination GetDestination(int id)
|
||||
{
|
||||
return ref SpanIOHelper.GetFromMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="Memory{SplitterDestination}"/> at the given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The index to use.</param>
|
||||
/// <returns>A <see cref="Memory{SplitterDestination}"/> at the given <paramref name="id"/>.</returns>
|
||||
public Memory<SplitterDestination> GetDestinationMemory(int id)
|
||||
{
|
||||
return SpanIOHelper.GetMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="Span{SplitterDestination}"/> in the <see cref="SplitterState"/> at <paramref name="id"/> and pass <paramref name="destinationId"/> to <see cref="SplitterState.GetData(int)"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The index to use to get the <see cref="SplitterState"/>.</param>
|
||||
/// <param name="destinationId">The index of the <see cref="SplitterDestination"/>.</param>
|
||||
/// <returns>A <see cref="Span{SplitterDestination}"/>.</returns>
|
||||
public Span<SplitterDestination> GetDestination(int id, int destinationId)
|
||||
{
|
||||
ref SplitterState splitter = ref GetState(id);
|
||||
|
||||
return splitter.GetData(destinationId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the audio renderer has any splitters.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer has any splitters.</returns>
|
||||
public bool UsingSplitter()
|
||||
{
|
||||
return !_splitters.IsEmpty && !_splitterDestinations.IsEmpty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal state of all splitters.
|
||||
/// </summary>
|
||||
public void UpdateInternalState()
|
||||
{
|
||||
foreach (ref SplitterState splitter in _splitters.Span)
|
||||
{
|
||||
splitter.UpdateInternalState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
210
Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
Normal file
210
Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
Normal file
|
@ -0,0 +1,210 @@
|
|||
//
|
||||
// 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.Parameter;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a splitter destination.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)]
|
||||
public struct SplitterDestination
|
||||
{
|
||||
public const int Alignment = 0x10;
|
||||
|
||||
/// <summary>
|
||||
/// The unique id of this <see cref="SplitterDestination"/>.
|
||||
/// </summary>
|
||||
public int Id;
|
||||
|
||||
/// <summary>
|
||||
/// The mix to output the result of the splitter.
|
||||
/// </summary>
|
||||
public int DestinationId;
|
||||
|
||||
/// <summary>
|
||||
/// Mix buffer volumes storage.
|
||||
/// </summary>
|
||||
private MixArray _mix;
|
||||
private MixArray _previousMix;
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the next linked element.
|
||||
/// </summary>
|
||||
private unsafe SplitterDestination* _next;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if in use.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsUsed;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the internal state need to be updated.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool NeedToUpdateInternalState;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)]
|
||||
private struct MixArray { }
|
||||
|
||||
/// <summary>
|
||||
/// Mix buffer volumes.
|
||||
/// </summary>
|
||||
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mix);
|
||||
|
||||
/// <summary>
|
||||
/// Previous mix buffer volumes.
|
||||
/// </summary>
|
||||
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||
public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix);
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Span{SplitterDestination}"/> of the next element or <see cref="Span{SplitterDestination}.Empty"/> if not present.
|
||||
/// </summary>
|
||||
public Span<SplitterDestination> Next
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return _next != null ? new Span<SplitterDestination>(_next, 1) : Span<SplitterDestination>.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="SplitterDestination"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The unique id of this <see cref="SplitterDestination"/>.</param>
|
||||
public SplitterDestination(int id) : this()
|
||||
{
|
||||
Id = id;
|
||||
DestinationId = Constants.UnusedMixId;
|
||||
|
||||
ClearVolumes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the <see cref="SplitterDestination"/> from user parameter.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
public void Update(SplitterDestinationInParameter parameter)
|
||||
{
|
||||
Debug.Assert(Id == parameter.Id);
|
||||
|
||||
if (parameter.IsMagicValid() && Id == parameter.Id)
|
||||
{
|
||||
DestinationId = parameter.DestinationId;
|
||||
|
||||
parameter.MixBufferVolume.CopyTo(MixBufferVolume);
|
||||
|
||||
if (!IsUsed && parameter.IsUsed)
|
||||
{
|
||||
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
||||
|
||||
NeedToUpdateInternalState = false;
|
||||
}
|
||||
|
||||
IsUsed = parameter.IsUsed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal state of the instance.
|
||||
/// </summary>
|
||||
public void UpdateInternalState()
|
||||
{
|
||||
if (IsUsed && NeedToUpdateInternalState)
|
||||
{
|
||||
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
||||
}
|
||||
|
||||
NeedToUpdateInternalState = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the update internal state marker.
|
||||
/// </summary>
|
||||
public void MarkAsNeedToUpdateInternalState()
|
||||
{
|
||||
NeedToUpdateInternalState = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the <see cref="SplitterDestination"/> is used and has a destination.
|
||||
/// </summary>
|
||||
/// <returns>True if the <see cref="SplitterDestination"/> is used and has a destination.</returns>
|
||||
public bool IsConfigured()
|
||||
{
|
||||
return IsUsed && DestinationId != Constants.UnusedMixId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the volume for a given destination.
|
||||
/// </summary>
|
||||
/// <param name="destinationIndex">The destination index to use.</param>
|
||||
/// <returns>The volume for the given destination.</returns>
|
||||
public float GetMixVolume(int destinationIndex)
|
||||
{
|
||||
Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
|
||||
|
||||
return MixBufferVolume[destinationIndex];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the volumes.
|
||||
/// </summary>
|
||||
public void ClearVolumes()
|
||||
{
|
||||
MixBufferVolume.Fill(0);
|
||||
PreviousMixBufferVolume.Fill(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Link the next element to the given <see cref="SplitterDestination"/>.
|
||||
/// </summary>
|
||||
/// <param name="next">The given <see cref="SplitterDestination"/> to link.</param>
|
||||
public void Link(ref SplitterDestination next)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (SplitterDestination *nextPtr = &next)
|
||||
{
|
||||
_next = nextPtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the link to the next element.
|
||||
/// </summary>
|
||||
public void Unlink()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
_next = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
237
Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs
Normal file
237
Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs
Normal file
|
@ -0,0 +1,237 @@
|
|||
//
|
||||
// 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.Parameter;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a splitter.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = Alignment)]
|
||||
public struct SplitterState
|
||||
{
|
||||
public const int Alignment = 0x10;
|
||||
|
||||
/// <summary>
|
||||
/// The unique id of this <see cref="SplitterState"/>.
|
||||
/// </summary>
|
||||
public int Id;
|
||||
|
||||
/// <summary>
|
||||
/// Target sample rate to use on the splitter.
|
||||
/// </summary>
|
||||
public uint SampleRate;
|
||||
|
||||
/// <summary>
|
||||
/// Count of splitter destinations (<see cref="SplitterDestination"/>).
|
||||
/// </summary>
|
||||
public int DestinationCount;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the splitter has a new connection.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool HasNewConnection;
|
||||
|
||||
/// <summary>
|
||||
/// Linked list of <see cref="SplitterDestination"/>.
|
||||
/// </summary>
|
||||
private unsafe SplitterDestination* _destinationsData;
|
||||
|
||||
/// <summary>
|
||||
/// Span to the first element of the linked list of <see cref="SplitterDestination"/>.
|
||||
/// </summary>
|
||||
public Span<SplitterDestination> Destinations
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return (IntPtr)_destinationsData != IntPtr.Zero ? new Span<SplitterDestination>(_destinationsData, 1) : Span<SplitterDestination>.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="SplitterState"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The unique id of this <see cref="SplitterState"/>.</param>
|
||||
public SplitterState(int id) : this()
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public Span<SplitterDestination> GetData(int index)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
Span<SplitterDestination> result = Destinations;
|
||||
|
||||
while (i < index)
|
||||
{
|
||||
if (result.IsEmpty)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
result = result[0].Next;
|
||||
i++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the new connection flag.
|
||||
/// </summary>
|
||||
public void ClearNewConnectionFlag()
|
||||
{
|
||||
HasNewConnection = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility function to apply a given <see cref="SpanAction{T, TArg}"/> to all <see cref="Destinations"/>.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to execute on each elements.</param>
|
||||
private void ForEachDestination(SpanAction<SplitterDestination, int> action)
|
||||
{
|
||||
Span<SplitterDestination> temp = Destinations;
|
||||
|
||||
int i = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (temp.IsEmpty)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Span<SplitterDestination> next = temp[0].Next;
|
||||
|
||||
action.Invoke(temp, i++);
|
||||
|
||||
temp = next;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the <see cref="SplitterState"/> from user parameter.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
ClearLinks();
|
||||
|
||||
int destinationCount;
|
||||
|
||||
if (context.IsBugFixed)
|
||||
{
|
||||
destinationCount = parameter.DestinationCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
destinationCount = Math.Min(context.GetDestinationCountPerStateForCompatibility(), parameter.DestinationCount);
|
||||
}
|
||||
|
||||
if (destinationCount > 0)
|
||||
{
|
||||
ReadOnlySpan<int> destinationIds = MemoryMarshal.Cast<byte, int>(input);
|
||||
|
||||
Memory<SplitterDestination> destination = context.GetDestinationMemory(destinationIds[0]);
|
||||
|
||||
SetDestination(ref destination.Span[0]);
|
||||
|
||||
DestinationCount = destinationCount;
|
||||
|
||||
for (int i = 1; i < destinationCount; i++)
|
||||
{
|
||||
Memory<SplitterDestination> nextDestination = context.GetDestinationMemory(destinationIds[i]);
|
||||
|
||||
destination.Span[0].Link(ref nextDestination.Span[0]);
|
||||
destination = nextDestination;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Assert(parameter.Id == Id);
|
||||
|
||||
if (parameter.Id == Id)
|
||||
{
|
||||
SampleRate = parameter.SampleRate;
|
||||
HasNewConnection = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the head of the linked list of <see cref="Destinations"/>.
|
||||
/// </summary>
|
||||
/// <param name="newValue">A reference to a <see cref="SplitterDestination"/>.</param>
|
||||
public void SetDestination(ref SplitterDestination newValue)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (SplitterDestination* newValuePtr = &newValue)
|
||||
{
|
||||
_destinationsData = newValuePtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal state of this instance.
|
||||
/// </summary>
|
||||
public void UpdateInternalState()
|
||||
{
|
||||
ForEachDestination((destination, _) => destination[0].UpdateInternalState());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all links from the <see cref="Destinations"/>.
|
||||
/// </summary>
|
||||
public void ClearLinks()
|
||||
{
|
||||
ForEachDestination((destination, _) => destination[0].Unlink());
|
||||
|
||||
unsafe
|
||||
{
|
||||
_destinationsData = (SplitterDestination*)IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a given <see cref="Span{SplitterState}"/>.
|
||||
/// </summary>
|
||||
/// <param name="splitters">All the <see cref="SplitterState"/> to initialize.</param>
|
||||
public static void InitializeSplitters(Span<SplitterState> splitters)
|
||||
{
|
||||
foreach (ref SplitterState splitter in splitters)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
splitter._destinationsData = (SplitterDestination*)IntPtr.Zero;
|
||||
}
|
||||
|
||||
splitter.DestinationCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
575
Ryujinx.Audio/Renderer/Server/StateUpdater.cs
Normal file
575
Ryujinx.Audio/Renderer/Server/StateUpdater.cs
Normal file
|
@ -0,0 +1,575 @@
|
|||
//
|
||||
// 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.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Performance;
|
||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using Ryujinx.Audio.Renderer.Server.Mix;
|
||||
using Ryujinx.Audio.Renderer.Server.Performance;
|
||||
using Ryujinx.Audio.Renderer.Server.Sink;
|
||||
using Ryujinx.Audio.Renderer.Server.Splitter;
|
||||
using Ryujinx.Audio.Renderer.Server.Voice;
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
public class StateUpdater
|
||||
{
|
||||
private readonly ReadOnlyMemory<byte> _inputOrigin;
|
||||
private ReadOnlyMemory <byte> _outputOrigin;
|
||||
private ReadOnlyMemory<byte> _input;
|
||||
|
||||
private Memory<byte> _output;
|
||||
private uint _processHandle;
|
||||
private BehaviourContext _behaviourContext;
|
||||
|
||||
private UpdateDataHeader _inputHeader;
|
||||
private Memory<UpdateDataHeader> _outputHeader;
|
||||
|
||||
private ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0];
|
||||
|
||||
public StateUpdater(ReadOnlyMemory<byte> input, Memory<byte> output, uint processHandle, BehaviourContext behaviourContext)
|
||||
{
|
||||
_input = input;
|
||||
_inputOrigin = _input;
|
||||
_output = output;
|
||||
_outputOrigin = _output;
|
||||
_processHandle = processHandle;
|
||||
_behaviourContext = behaviourContext;
|
||||
|
||||
_inputHeader = SpanIOHelper.Read<UpdateDataHeader>(ref _input);
|
||||
|
||||
_outputHeader = SpanMemoryManager<UpdateDataHeader>.Cast(_output.Slice(0, Unsafe.SizeOf<UpdateDataHeader>()));
|
||||
OutputHeader.Initialize(_behaviourContext.UserRevision);
|
||||
_output = _output.Slice(Unsafe.SizeOf<UpdateDataHeader>());
|
||||
}
|
||||
|
||||
public ResultCode UpdateBehaviourContext()
|
||||
{
|
||||
BehaviourParameter parameter = SpanIOHelper.Read<BehaviourParameter>(ref _input);
|
||||
|
||||
if (!BehaviourContext.CheckValidRevision(parameter.UserRevision) || parameter.UserRevision != _behaviourContext.UserRevision)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
_behaviourContext.ClearError();
|
||||
_behaviourContext.UpdateFlags(parameter.Flags);
|
||||
|
||||
if (_inputHeader.BehaviourSize != Unsafe.SizeOf<BehaviourParameter>())
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateMemoryPools(Span<MemoryPoolState> memoryPools)
|
||||
{
|
||||
PoolMapper mapper = new PoolMapper(_processHandle, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
|
||||
if (memoryPools.Length * Unsafe.SizeOf<MemoryPoolInParameter>() != _inputHeader.MemoryPoolsSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
foreach (ref MemoryPoolState memoryPool in memoryPools)
|
||||
{
|
||||
MemoryPoolInParameter parameter = SpanIOHelper.Read<MemoryPoolInParameter>(ref _input);
|
||||
|
||||
ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef<MemoryPoolOutStatus>(ref _output)[0];
|
||||
|
||||
PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, ref parameter, ref outStatus);
|
||||
|
||||
if (updateResult != PoolMapper.UpdateResult.Success &&
|
||||
updateResult != PoolMapper.UpdateResult.MapError &&
|
||||
updateResult != PoolMapper.UpdateResult.UnmapError)
|
||||
{
|
||||
if (updateResult != PoolMapper.UpdateResult.InvalidParameter)
|
||||
{
|
||||
throw new InvalidOperationException($"{updateResult}");
|
||||
}
|
||||
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
}
|
||||
|
||||
OutputHeader.MemoryPoolsSize = (uint)(Unsafe.SizeOf<MemoryPoolOutStatus>() * memoryPools.Length);
|
||||
OutputHeader.TotalSize += OutputHeader.MemoryPoolsSize;
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateVoiceChannelResources(VoiceContext context)
|
||||
{
|
||||
if (context.GetCount() * Unsafe.SizeOf<VoiceChannelResourceInParameter>() != _inputHeader.VoiceResourcesSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
VoiceChannelResourceInParameter parameter = SpanIOHelper.Read<VoiceChannelResourceInParameter>(ref _input);
|
||||
|
||||
ref VoiceChannelResource resource = ref context.GetChannelResource(i);
|
||||
|
||||
resource.Id = parameter.Id;
|
||||
parameter.Mix.ToSpan().CopyTo(resource.Mix.ToSpan());
|
||||
resource.IsUsed = parameter.IsUsed;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateVoices(VoiceContext context, Memory<MemoryPoolState> memoryPools)
|
||||
{
|
||||
if (context.GetCount() * Unsafe.SizeOf<VoiceInParameter>() != _inputHeader.VoicesSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<VoiceInParameter> parameters = MemoryMarshal.Cast<byte, VoiceInParameter>(_input.Slice(0, (int)_inputHeader.VoicesSize).Span);
|
||||
|
||||
_input = _input.Slice((int)_inputHeader.VoicesSize);
|
||||
|
||||
PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
|
||||
// First make everything not in use.
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
ref VoiceState state = ref context.GetState(i);
|
||||
|
||||
state.InUse = false;
|
||||
}
|
||||
|
||||
// Start processing
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
VoiceInParameter parameter = parameters[i];
|
||||
|
||||
Memory<VoiceUpdateState>[] voiceUpdateStates = new Memory<VoiceUpdateState>[Constants.VoiceChannelCountMax];
|
||||
|
||||
ref VoiceOutStatus outStatus = ref SpanIOHelper.GetWriteRef<VoiceOutStatus>(ref _output)[0];
|
||||
|
||||
if (parameter.InUse)
|
||||
{
|
||||
ref VoiceState currentVoiceState = ref context.GetState(i);
|
||||
|
||||
for (int channelResourceIndex = 0; channelResourceIndex < parameter.ChannelCount; channelResourceIndex++)
|
||||
{
|
||||
int channelId = parameter.ChannelResourceIds[channelResourceIndex];
|
||||
|
||||
Debug.Assert(channelId >= 0 && channelId < context.GetCount());
|
||||
|
||||
voiceUpdateStates[channelResourceIndex] = context.GetUpdateStateForCpu(channelId);
|
||||
}
|
||||
|
||||
if (parameter.IsNew)
|
||||
{
|
||||
currentVoiceState.Initialize();
|
||||
}
|
||||
|
||||
currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, ref parameter, ref mapper, ref _behaviourContext);
|
||||
|
||||
if (updateParameterError.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
_behaviourContext.AppendError(ref updateParameterError);
|
||||
}
|
||||
|
||||
currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, ref parameter, voiceUpdateStates, ref mapper, ref _behaviourContext);
|
||||
|
||||
foreach (ref ErrorInfo errorInfo in waveBufferUpdateErrorInfos.AsSpan())
|
||||
{
|
||||
if (errorInfo.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
_behaviourContext.AppendError(ref errorInfo);
|
||||
}
|
||||
}
|
||||
|
||||
currentVoiceState.WriteOutStatus(ref outStatus, ref parameter, voiceUpdateStates);
|
||||
}
|
||||
}
|
||||
|
||||
int currentOutputSize = _output.Length;
|
||||
|
||||
OutputHeader.VoicesSize = (uint)(Unsafe.SizeOf<VoiceOutStatus>() * context.GetCount());
|
||||
OutputHeader.TotalSize += OutputHeader.VoicesSize;
|
||||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private static void ResetEffect(ref BaseEffect effect, ref EffectInParameter parameter, PoolMapper mapper)
|
||||
{
|
||||
effect.ForceUnmapBuffers(mapper);
|
||||
|
||||
switch (parameter.Type)
|
||||
{
|
||||
case EffectType.Invalid:
|
||||
effect = new BaseEffect();
|
||||
break;
|
||||
case EffectType.BufferMix:
|
||||
effect = new BufferMixEffect();
|
||||
break;
|
||||
case EffectType.AuxiliaryBuffer:
|
||||
effect = new AuxiliaryBufferEffect();
|
||||
break;
|
||||
case EffectType.Delay:
|
||||
effect = new DelayEffect();
|
||||
break;
|
||||
case EffectType.Reverb:
|
||||
effect = new ReverbEffect();
|
||||
break;
|
||||
case EffectType.Reverb3d:
|
||||
effect = new Reverb3dEffect();
|
||||
break;
|
||||
case EffectType.BiquadFilter:
|
||||
effect = new BiquadFilterEffect();
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"EffectType {parameter.Type} not implemented!");
|
||||
}
|
||||
}
|
||||
|
||||
public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
|
||||
{
|
||||
if (context.GetCount() * Unsafe.SizeOf<EffectInParameter>() != _inputHeader.EffectsSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<EffectInParameter> parameters = MemoryMarshal.Cast<byte, EffectInParameter>(_input.Slice(0, (int)_inputHeader.EffectsSize).Span);
|
||||
|
||||
_input = _input.Slice((int)_inputHeader.EffectsSize);
|
||||
|
||||
PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
EffectInParameter parameter = parameters[i];
|
||||
|
||||
ref EffectOutStatus outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatus>(ref _output)[0];
|
||||
|
||||
ref BaseEffect effect = ref context.GetEffect(i);
|
||||
|
||||
if (!effect.IsTypeValid(ref parameter))
|
||||
{
|
||||
ResetEffect(ref effect, ref parameter, mapper);
|
||||
}
|
||||
|
||||
effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper);
|
||||
|
||||
if (updateErrorInfo.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
_behaviourContext.AppendError(ref updateErrorInfo);
|
||||
}
|
||||
|
||||
effect.StoreStatus(ref outStatus, isAudioRendererActive);
|
||||
}
|
||||
|
||||
int currentOutputSize = _output.Length;
|
||||
|
||||
OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf<EffectOutStatus>() * context.GetCount());
|
||||
OutputHeader.TotalSize += OutputHeader.EffectsSize;
|
||||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateSplitter(SplitterContext context)
|
||||
{
|
||||
if (context.Update(_input.Span, out int consumedSize))
|
||||
{
|
||||
_input = _input.Slice(consumedSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, ReadOnlySpan<MixParameter> parameters)
|
||||
{
|
||||
uint maxMixStateCount = mixContext.GetCount();
|
||||
uint totalRequiredMixBufferCount = 0;
|
||||
|
||||
for (int i = 0; i < inputMixCount; i++)
|
||||
{
|
||||
if (parameters[i].IsUsed)
|
||||
{
|
||||
if (parameters[i].DestinationMixId != Constants.UnusedMixId &&
|
||||
parameters[i].DestinationMixId > maxMixStateCount &&
|
||||
parameters[i].MixId != Constants.FinalMixId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
totalRequiredMixBufferCount += parameters[i].BufferCount;
|
||||
}
|
||||
}
|
||||
|
||||
return totalRequiredMixBufferCount > mixBufferCount;
|
||||
}
|
||||
|
||||
public ResultCode UpdateMixes(MixContext mixContext, uint mixBufferCount, EffectContext effectContext, SplitterContext splitterContext)
|
||||
{
|
||||
uint mixCount;
|
||||
uint inputMixSize;
|
||||
uint inputSize = 0;
|
||||
|
||||
if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
|
||||
{
|
||||
MixInParameterDirtyOnlyUpdate parameter = MemoryMarshal.Cast<byte, MixInParameterDirtyOnlyUpdate>(_input.Span)[0];
|
||||
|
||||
mixCount = parameter.MixCount;
|
||||
|
||||
inputSize += (uint)Unsafe.SizeOf<MixInParameterDirtyOnlyUpdate>();
|
||||
}
|
||||
else
|
||||
{
|
||||
mixCount = mixContext.GetCount();
|
||||
}
|
||||
|
||||
inputMixSize = mixCount * (uint)Unsafe.SizeOf<MixParameter>();
|
||||
|
||||
inputSize += inputMixSize;
|
||||
|
||||
if (inputSize != _inputHeader.MixesSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
|
||||
{
|
||||
_input = _input.Slice(Unsafe.SizeOf<MixInParameterDirtyOnlyUpdate>());
|
||||
}
|
||||
|
||||
ReadOnlySpan<MixParameter> parameters = MemoryMarshal.Cast<byte, MixParameter>(_input.Span.Slice(0, (int)inputMixSize));
|
||||
|
||||
_input = _input.Slice((int)inputMixSize);
|
||||
|
||||
if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, parameters))
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
bool isMixContextDirty = false;
|
||||
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
MixParameter parameter = parameters[i];
|
||||
|
||||
int mixId = i;
|
||||
|
||||
if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
|
||||
{
|
||||
mixId = parameter.MixId;
|
||||
}
|
||||
|
||||
ref MixState mix = ref mixContext.GetState(mixId);
|
||||
|
||||
if (parameter.IsUsed != mix.IsUsed)
|
||||
{
|
||||
mix.IsUsed = parameter.IsUsed;
|
||||
|
||||
if (parameter.IsUsed)
|
||||
{
|
||||
mix.ClearEffectProcessingOrder();
|
||||
}
|
||||
|
||||
isMixContextDirty = true;
|
||||
}
|
||||
|
||||
if (mix.IsUsed)
|
||||
{
|
||||
isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, ref parameter, effectContext, splitterContext, _behaviourContext);
|
||||
}
|
||||
}
|
||||
|
||||
if (isMixContextDirty)
|
||||
{
|
||||
if (_behaviourContext.IsSplitterSupported() && splitterContext.UsingSplitter())
|
||||
{
|
||||
if (!mixContext.Sort(splitterContext))
|
||||
{
|
||||
return ResultCode.InvalidMixSorting;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mixContext.Sort();
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private static void ResetSink(ref BaseSink sink, ref SinkInParameter parameter)
|
||||
{
|
||||
sink.CleanUp();
|
||||
|
||||
switch (parameter.Type)
|
||||
{
|
||||
case SinkType.Invalid:
|
||||
sink = new BaseSink();
|
||||
break;
|
||||
case SinkType.CircularBuffer:
|
||||
sink = new CircularBufferSink();
|
||||
break;
|
||||
case SinkType.Device:
|
||||
sink = new DeviceSink();
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"SinkType {parameter.Type} not implemented!");
|
||||
}
|
||||
}
|
||||
|
||||
public ResultCode UpdateSinks(SinkContext context, Memory<MemoryPoolState> memoryPools)
|
||||
{
|
||||
PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
|
||||
if (context.GetCount() * Unsafe.SizeOf<SinkInParameter>() != _inputHeader.SinksSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<SinkInParameter> parameters = MemoryMarshal.Cast<byte, SinkInParameter>(_input.Slice(0, (int)_inputHeader.SinksSize).Span);
|
||||
|
||||
_input = _input.Slice((int)_inputHeader.SinksSize);
|
||||
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
SinkInParameter parameter = parameters[i];
|
||||
ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef<SinkOutStatus>(ref _output)[0];
|
||||
ref BaseSink sink = ref context.GetSink(i);
|
||||
|
||||
if (!sink.IsTypeValid(ref parameter))
|
||||
{
|
||||
ResetSink(ref sink, ref parameter);
|
||||
}
|
||||
|
||||
sink.Update(out ErrorInfo updateErrorInfo, ref parameter, ref outStatus, mapper);
|
||||
|
||||
if (updateErrorInfo.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
_behaviourContext.AppendError(ref updateErrorInfo);
|
||||
}
|
||||
}
|
||||
|
||||
int currentOutputSize = _output.Length;
|
||||
|
||||
OutputHeader.SinksSize = (uint)(Unsafe.SizeOf<SinkOutStatus>() * context.GetCount());
|
||||
OutputHeader.TotalSize += OutputHeader.SinksSize;
|
||||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdatePerformanceBuffer(PerformanceManager manager, Span<byte> performanceOutput)
|
||||
{
|
||||
if (Unsafe.SizeOf<PerformanceInParameter>() != _inputHeader.PerformanceBufferSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
PerformanceInParameter parameter = SpanIOHelper.Read<PerformanceInParameter>(ref _input);
|
||||
|
||||
ref PerformanceOutStatus outStatus = ref SpanIOHelper.GetWriteRef<PerformanceOutStatus>(ref _output)[0];
|
||||
|
||||
if (manager != null)
|
||||
{
|
||||
outStatus.HistorySize = manager.CopyHistories(performanceOutput);
|
||||
|
||||
manager.SetTargetNodeId(parameter.TargetNodeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
outStatus.HistorySize = 0;
|
||||
}
|
||||
|
||||
OutputHeader.PerformanceBufferSize = (uint)Unsafe.SizeOf<PerformanceOutStatus>();
|
||||
OutputHeader.TotalSize += OutputHeader.PerformanceBufferSize;
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateErrorInfo()
|
||||
{
|
||||
ref BehaviourErrorInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef<BehaviourErrorInfoOutStatus>(ref _output)[0];
|
||||
|
||||
_behaviourContext.CopyErrorInfo(outStatus.ErrorInfos.ToSpan(), out outStatus.ErrorInfosCount);
|
||||
|
||||
OutputHeader.BehaviourSize = (uint)Unsafe.SizeOf<BehaviourErrorInfoOutStatus>();
|
||||
OutputHeader.TotalSize += OutputHeader.BehaviourSize;
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateRendererInfo(ulong elapsedFrameCount)
|
||||
{
|
||||
ref RendererInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef<RendererInfoOutStatus>(ref _output)[0];
|
||||
|
||||
outStatus.ElapsedFrameCount = elapsedFrameCount;
|
||||
|
||||
OutputHeader.RenderInfoSize = (uint)Unsafe.SizeOf<RendererInfoOutStatus>();
|
||||
OutputHeader.TotalSize += OutputHeader.RenderInfoSize;
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode CheckConsumedSize()
|
||||
{
|
||||
int consumedInputSize = _inputOrigin.Length - _input.Length;
|
||||
int consumedOutputSize = _outputOrigin.Length - _output.Length;
|
||||
|
||||
if (consumedInputSize != _inputHeader.TotalSize)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.AudioRenderer, $"Consumed input size mismatch (got {consumedInputSize} expected {_inputHeader.TotalSize})");
|
||||
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
if (consumedOutputSize != OutputHeader.TotalSize)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.AudioRenderer, $"Consumed output size mismatch (got {consumedOutputSize} expected {OutputHeader.TotalSize})");
|
||||
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// 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/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// The execution mode of an <see cref="AudioRenderSystem"/>.
|
||||
/// </summary>
|
||||
public enum AudioRendererExecutionMode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Automatically send commands to the DSP at a fixed rate (see <see cref="AudioRenderSystem.SendCommands"/>
|
||||
/// </summary>
|
||||
Auto,
|
||||
|
||||
/// <summary>
|
||||
/// Audio renderer operation needs to be done manually via ExecuteAudioRenderer.
|
||||
/// </summary>
|
||||
/// <remarks>This is not supported on the DSP and is as such stubbed.</remarks>
|
||||
Manual
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// 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/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// The rendering device of an <see cref="AudioRenderSystem"/>.
|
||||
/// </summary>
|
||||
public enum AudioRendererRenderingDevice : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Rendering is performed on the DSP.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only supports <see cref="AudioRendererExecutionMode.Auto"/>.
|
||||
/// </remarks>
|
||||
Dsp,
|
||||
|
||||
/// <summary>
|
||||
/// Rendering is performed on the CPU.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only supports <see cref="AudioRendererExecutionMode.Manual"/>.
|
||||
/// </remarks>
|
||||
Cpu
|
||||
}
|
||||
}
|
56
Ryujinx.Audio/Renderer/Server/Types/PlayState.cs
Normal file
56
Ryujinx.Audio/Renderer/Server/Types/PlayState.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
// 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/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// The internal play state of a <see cref="Voice.VoiceState"/>
|
||||
/// </summary>
|
||||
public enum PlayState
|
||||
{
|
||||
/// <summary>
|
||||
/// The voice has been started and is playing.
|
||||
/// </summary>
|
||||
Started,
|
||||
|
||||
/// <summary>
|
||||
/// The voice has been stopped.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This cannot be directly set by user.
|
||||
/// See <see cref="Stopping"/> for correct usage.
|
||||
/// </remarks>
|
||||
Stopped,
|
||||
|
||||
/// <summary>
|
||||
/// The user asked the voice to be stopped.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is changed to the <see cref="Stopped"/> state after command generation.
|
||||
/// <seealso cref="Voice.VoiceState.UpdateForCommandGeneration(Voice.VoiceContext)"/>
|
||||
/// </remarks>
|
||||
Stopping,
|
||||
|
||||
/// <summary>
|
||||
/// The voice has been paused by user request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The user can resume to the <see cref="Started"/> state.
|
||||
/// </remarks>
|
||||
Paused
|
||||
}
|
||||
}
|
101
Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs
Normal file
101
Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs
Normal file
|
@ -0,0 +1,101 @@
|
|||
//
|
||||
// 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 System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Upsampler
|
||||
{
|
||||
/// <summary>
|
||||
/// Upsampler manager.
|
||||
/// </summary>
|
||||
public class UpsamplerManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Work buffer for upsampler.
|
||||
/// </summary>
|
||||
private Memory<float> _upSamplerWorkBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Global lock of the object.
|
||||
/// </summary>
|
||||
private object Lock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// The upsamplers instances.
|
||||
/// </summary>
|
||||
private UpsamplerState[] _upsamplers;
|
||||
|
||||
/// <summary>
|
||||
/// The count of upsamplers.
|
||||
/// </summary>
|
||||
private uint _count;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="UpsamplerManager"/>.
|
||||
/// </summary>
|
||||
/// <param name="upSamplerWorkBuffer">Work buffer for upsampler.</param>
|
||||
/// <param name="count">The count of upsamplers.</param>
|
||||
public UpsamplerManager(Memory<float> upSamplerWorkBuffer, uint count)
|
||||
{
|
||||
_upSamplerWorkBuffer = upSamplerWorkBuffer;
|
||||
_count = count;
|
||||
|
||||
_upsamplers = new UpsamplerState[_count];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocate a new <see cref="UpsamplerState"/>.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="UpsamplerState"/> or null if out of memory.</returns>
|
||||
public UpsamplerState Allocate()
|
||||
{
|
||||
int workBufferOffset = 0;
|
||||
|
||||
lock (Lock)
|
||||
{
|
||||
for (int i = 0; i < _count; i++)
|
||||
{
|
||||
if (_upsamplers[i] == null)
|
||||
{
|
||||
_upsamplers[i] = new UpsamplerState(this, i, _upSamplerWorkBuffer.Slice(workBufferOffset, Constants.UpSampleEntrySize), Constants.TargetSampleCount);
|
||||
|
||||
return _upsamplers[i];
|
||||
}
|
||||
|
||||
workBufferOffset += Constants.UpSampleEntrySize;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Free a <see cref="UpsamplerState"/> at the given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the <see cref="UpsamplerState"/> to free.</param>
|
||||
public void Free(int index)
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
Debug.Assert(_upsamplers[index] != null);
|
||||
|
||||
_upsamplers[index] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
80
Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs
Normal file
80
Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs
Normal file
|
@ -0,0 +1,80 @@
|
|||
//
|
||||
// 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 System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Upsampler
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a upsampling.
|
||||
/// </summary>
|
||||
public class UpsamplerState
|
||||
{
|
||||
/// <summary>
|
||||
/// The output buffer containing the target samples.
|
||||
/// </summary>
|
||||
public Memory<float> OutputBuffer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The target sample count.
|
||||
/// </summary>
|
||||
public uint SampleCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The index of the <see cref="UpsamplerState"/>. (used to free it)
|
||||
/// </summary>
|
||||
private int _index;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="UpsamplerManager"/>.
|
||||
/// </summary>
|
||||
private UpsamplerManager _manager;
|
||||
|
||||
/// <summary>
|
||||
/// The source sample count.
|
||||
/// </summary>
|
||||
public uint SourceSampleCount;
|
||||
|
||||
/// <summary>
|
||||
/// The input buffer indices of the buffers holding the samples that need upsampling.
|
||||
/// </summary>
|
||||
public ushort[] InputBufferIndices;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="UpsamplerState"/>.
|
||||
/// </summary>
|
||||
/// <param name="manager">The upsampler manager.</param>
|
||||
/// <param name="index">The index of the <see cref="UpsamplerState"/>. (used to free it)</param>
|
||||
/// <param name="outputBuffer">The output buffer used to contain the target samples.</param>
|
||||
/// <param name="sampleCount">The target sample count.</param>
|
||||
public UpsamplerState(UpsamplerManager manager, int index, Memory<float> outputBuffer, uint sampleCount)
|
||||
{
|
||||
_manager = manager;
|
||||
_index = index;
|
||||
OutputBuffer = outputBuffer;
|
||||
SampleCount = sampleCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release the <see cref="UpsamplerState"/>.
|
||||
/// </summary>
|
||||
public void Release()
|
||||
{
|
||||
_manager.Free(_index);
|
||||
}
|
||||
}
|
||||
}
|
57
Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs
Normal file
57
Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs
Normal file
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// 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.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Voice
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a voice channel resource.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0xD0, Pack = Alignment)]
|
||||
public struct VoiceChannelResource
|
||||
{
|
||||
public const int Alignment = 0x10;
|
||||
|
||||
/// <summary>
|
||||
/// Mix volumes for the resource.
|
||||
/// </summary>
|
||||
public Array24<float> Mix;
|
||||
|
||||
/// <summary>
|
||||
/// Previous mix volumes for resource.
|
||||
/// </summary>
|
||||
public Array24<float> PreviousMix;
|
||||
|
||||
/// <summary>
|
||||
/// The id of the resource.
|
||||
/// </summary>
|
||||
public uint Id;
|
||||
|
||||
/// <summary>
|
||||
/// Indicate if the resource is used.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsUsed;
|
||||
|
||||
public void UpdateState()
|
||||
{
|
||||
Mix.ToSpan().CopyTo(PreviousMix.ToSpan());
|
||||
}
|
||||
}
|
||||
}
|
166
Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs
Normal file
166
Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs
Normal file
|
@ -0,0 +1,166 @@
|
|||
//
|
||||
// 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.Utils;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Voice
|
||||
{
|
||||
/// <summary>
|
||||
/// Voice context.
|
||||
/// </summary>
|
||||
public class VoiceContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Storage of the sorted indices to <see cref="VoiceState"/>.
|
||||
/// </summary>
|
||||
private Memory<int> _sortedVoices;
|
||||
|
||||
/// <summary>
|
||||
/// Storage for <see cref="VoiceState"/>.
|
||||
/// </summary>
|
||||
private Memory<VoiceState> _voices;
|
||||
|
||||
/// <summary>
|
||||
/// Storage for <see cref="VoiceChannelResource"/>.
|
||||
/// </summary>
|
||||
private Memory<VoiceChannelResource> _voiceChannelResources;
|
||||
|
||||
/// <summary>
|
||||
/// Storage for <see cref="VoiceUpdateState"/> that are used during audio renderer server updates.
|
||||
/// </summary>
|
||||
private Memory<VoiceUpdateState> _voiceUpdateStatesCpu;
|
||||
|
||||
/// <summary>
|
||||
/// Storage for <see cref="VoiceUpdateState"/> for the <see cref="Dsp.AudioProcessor"/>.
|
||||
/// </summary>
|
||||
private Memory<VoiceUpdateState> _voiceUpdateStatesDsp;
|
||||
|
||||
/// <summary>
|
||||
/// The total voice count.
|
||||
/// </summary>
|
||||
private uint _voiceCount;
|
||||
|
||||
public void Initialize(Memory<int> sortedVoices, Memory<VoiceState> voices, Memory<VoiceChannelResource> voiceChannelResources, Memory<VoiceUpdateState> voiceUpdateStatesCpu, Memory<VoiceUpdateState> voiceUpdateStatesDsp, uint voiceCount)
|
||||
{
|
||||
_sortedVoices = sortedVoices;
|
||||
_voices = voices;
|
||||
_voiceChannelResources = voiceChannelResources;
|
||||
_voiceUpdateStatesCpu = voiceUpdateStatesCpu;
|
||||
_voiceUpdateStatesDsp = voiceUpdateStatesDsp;
|
||||
_voiceCount = voiceCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the total voice count.
|
||||
/// </summary>
|
||||
/// <returns>The total voice count.</returns>
|
||||
public uint GetCount()
|
||||
{
|
||||
return _voiceCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a reference to a <see cref="VoiceChannelResource"/> at the given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The index to use.</param>
|
||||
/// <returns>A reference to a <see cref="VoiceChannelResource"/> at the given <paramref name="id"/>.</returns>
|
||||
public ref VoiceChannelResource GetChannelResource(int id)
|
||||
{
|
||||
return ref SpanIOHelper.GetFromMemory(_voiceChannelResources, id, _voiceCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="Memory{VoiceUpdateState}"/> at the given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The index to use.</param>
|
||||
/// <returns>A <see cref="Memory{VoiceUpdateState}"/> at the given <paramref name="id"/>.</returns>
|
||||
/// <remarks>The returned <see cref="Memory{VoiceUpdateState}"/> should only be used when updating the server state.</remarks>
|
||||
public Memory<VoiceUpdateState> GetUpdateStateForCpu(int id)
|
||||
{
|
||||
return SpanIOHelper.GetMemory(_voiceUpdateStatesCpu, id, _voiceCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="Memory{VoiceUpdateState}"/> at the given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The index to use.</param>
|
||||
/// <returns>A <see cref="Memory{VoiceUpdateState}"/> at the given <paramref name="id"/>.</returns>
|
||||
/// <remarks>The returned <see cref="Memory{VoiceUpdateState}"/> should only be used in the context of processing on the <see cref="Dsp.AudioProcessor"/>.</remarks>
|
||||
public Memory<VoiceUpdateState> GetUpdateStateForDsp(int id)
|
||||
{
|
||||
return SpanIOHelper.GetMemory(_voiceUpdateStatesDsp, id, _voiceCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a reference to a <see cref="VoiceState"/> at the given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The index to use.</param>
|
||||
/// <returns>A reference to a <see cref="VoiceState"/> at the given <paramref name="id"/>.</returns>
|
||||
public ref VoiceState GetState(int id)
|
||||
{
|
||||
return ref SpanIOHelper.GetFromMemory(_voices, id, _voiceCount);
|
||||
}
|
||||
|
||||
public ref VoiceState GetSortedState(int id)
|
||||
{
|
||||
Debug.Assert(id >= 0 && id < _voiceCount);
|
||||
|
||||
return ref GetState(_sortedVoices.Span[id]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update internal state during command generation.
|
||||
/// </summary>
|
||||
public void UpdateForCommandGeneration()
|
||||
{
|
||||
_voiceUpdateStatesDsp.CopyTo(_voiceUpdateStatesCpu);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sort the internal voices by priority and sorting order (if the priorities match).
|
||||
/// </summary>
|
||||
public void Sort()
|
||||
{
|
||||
for (int i = 0; i < _voiceCount; i++)
|
||||
{
|
||||
_sortedVoices.Span[i] = i;
|
||||
}
|
||||
|
||||
int[] sortedVoicesTemp = _sortedVoices.Slice(0, (int)GetCount()).ToArray();
|
||||
|
||||
Array.Sort(sortedVoicesTemp, (a, b) =>
|
||||
{
|
||||
ref VoiceState aState = ref GetState(a);
|
||||
ref VoiceState bState = ref GetState(b);
|
||||
|
||||
int result = aState.Priority.CompareTo(bState.Priority);
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
return aState.SortingOrder.CompareTo(bState.SortingOrder);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
sortedVoicesTemp.AsSpan().CopyTo(_sortedVoices.Span);
|
||||
}
|
||||
}
|
||||
}
|
716
Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs
Normal file
716
Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs
Normal file
|
@ -0,0 +1,716 @@
|
|||
//
|
||||
// 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.Common;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
||||
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Voice
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = Alignment)]
|
||||
public struct VoiceState
|
||||
{
|
||||
public const int Alignment = 0x10;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the voice is used.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool InUse;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the voice is new.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsNew;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool WasPlaying;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SampleFormat"/> of the voice.
|
||||
/// </summary>
|
||||
public SampleFormat SampleFormat;
|
||||
|
||||
/// <summary>
|
||||
/// The sample rate of the voice.
|
||||
/// </summary>
|
||||
public uint SampleRate;
|
||||
|
||||
/// <summary>
|
||||
/// The total channel count used.
|
||||
/// </summary>
|
||||
public uint ChannelsCount;
|
||||
|
||||
/// <summary>
|
||||
/// Id of the voice.
|
||||
/// </summary>
|
||||
public int Id;
|
||||
|
||||
/// <summary>
|
||||
/// Node id of the voice.
|
||||
/// </summary>
|
||||
public int NodeId;
|
||||
|
||||
/// <summary>
|
||||
/// The target mix id of the voice.
|
||||
/// </summary>
|
||||
public int MixId;
|
||||
|
||||
/// <summary>
|
||||
/// The current voice <see cref="Types.PlayState"/>.
|
||||
/// </summary>
|
||||
public Types.PlayState PlayState;
|
||||
|
||||
/// <summary>
|
||||
/// The previous voice <see cref="Types.PlayState"/>.
|
||||
/// </summary>
|
||||
public Types.PlayState PreviousPlayState;
|
||||
|
||||
/// <summary>
|
||||
/// The priority of the voice.
|
||||
/// </summary>
|
||||
public uint Priority;
|
||||
|
||||
/// <summary>
|
||||
/// Target sorting position of the voice. (used to sort voice with the same <see cref="Priority"/>)
|
||||
/// </summary>
|
||||
public uint SortingOrder;
|
||||
|
||||
/// <summary>
|
||||
/// The pitch used on the voice.
|
||||
/// </summary>
|
||||
public float Pitch;
|
||||
|
||||
/// <summary>
|
||||
/// The output volume of the voice.
|
||||
/// </summary>
|
||||
public float Volume;
|
||||
|
||||
/// <summary>
|
||||
/// The previous output volume of the voice.
|
||||
/// </summary>
|
||||
public float PreviousVolume;
|
||||
|
||||
/// <summary>
|
||||
/// Biquad filters to apply to the output of the voice.
|
||||
/// </summary>
|
||||
public Array2<BiquadFilterParameter> BiquadFilters;
|
||||
|
||||
/// <summary>
|
||||
/// Total count of <see cref="WaveBufferInternal"/> of the voice.
|
||||
/// </summary>
|
||||
public uint WaveBuffersCount;
|
||||
|
||||
/// <summary>
|
||||
/// Current playing <see cref="WaveBufferInternal"/> of the voice.
|
||||
/// </summary>
|
||||
public uint WaveBuffersIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Change the behaviour of the voice.
|
||||
/// </summary>
|
||||
/// <remarks>This was added on REV5.</remarks>
|
||||
public DecodingBehaviour DecodingBehaviour;
|
||||
|
||||
/// <summary>
|
||||
/// User state <see cref="AddressInfo"/> required by the data source.
|
||||
/// </summary>
|
||||
/// <remarks>Only used for <see cref="SampleFormat.Adpcm"/> as the GC-ADPCM coefficients.</remarks>
|
||||
public AddressInfo DataSourceStateAddressInfo;
|
||||
|
||||
/// <summary>
|
||||
/// The wavebuffers of this voice.
|
||||
/// </summary>
|
||||
public Array4<WaveBuffer> WaveBuffers;
|
||||
|
||||
/// <summary>
|
||||
/// The channel resource ids associated to the voice.
|
||||
/// </summary>
|
||||
public Array6<int> ChannelResourceIds;
|
||||
|
||||
/// <summary>
|
||||
/// The target splitter id of the voice.
|
||||
/// </summary>
|
||||
public uint SplitterId;
|
||||
|
||||
/// <summary>
|
||||
/// Change the Sample Rate Conversion (SRC) quality of the voice.
|
||||
/// </summary>
|
||||
/// <remarks>This was added on REV8.</remarks>
|
||||
public SampleRateConversionQuality SrcQuality;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, the voice was dropped.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool VoiceDropFlag;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the data source state work buffer wasn't mapped.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool DataSourceStateUnmapped;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if any of the <see cref="WaveBuffer.BufferAddressInfo"/> work buffer wasn't mapped.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool BufferInfoUnmapped;
|
||||
|
||||
/// <summary>
|
||||
/// The biquad filter initialization state storage.
|
||||
/// </summary>
|
||||
private BiquadFilterNeedInitializationArrayStruct _biquadFilterNeedInitialization;
|
||||
|
||||
/// <summary>
|
||||
/// Flush the amount of wavebuffer specified. This will result in the wavebuffer being skipped and marked played.
|
||||
/// </summary>
|
||||
/// <remarks>This was added on REV5.</remarks>
|
||||
public byte FlushWaveBufferCount;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = Constants.VoiceBiquadFilterCount)]
|
||||
private struct BiquadFilterNeedInitializationArrayStruct { }
|
||||
|
||||
/// <summary>
|
||||
/// The biquad filter initialization state array.
|
||||
/// </summary>
|
||||
public Span<bool> BiquadFilterNeedInitialization => SpanHelpers.AsSpan<BiquadFilterNeedInitializationArrayStruct, bool>(ref _biquadFilterNeedInitialization);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the <see cref="VoiceState"/>.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
IsNew = false;
|
||||
VoiceDropFlag = false;
|
||||
DataSourceStateUnmapped = false;
|
||||
BufferInfoUnmapped = false;
|
||||
FlushWaveBufferCount = 0;
|
||||
PlayState = Types.PlayState.Stopped;
|
||||
Priority = Constants.VoiceLowestPriority;
|
||||
Id = 0;
|
||||
NodeId = 0;
|
||||
SampleRate = 0;
|
||||
SampleFormat = SampleFormat.Invalid;
|
||||
ChannelsCount = 0;
|
||||
Pitch = 0.0f;
|
||||
Volume= 0.0f;
|
||||
PreviousVolume = 0.0f;
|
||||
BiquadFilters.ToSpan().Fill(new BiquadFilterParameter());
|
||||
WaveBuffersCount = 0;
|
||||
WaveBuffersIndex = 0;
|
||||
MixId = Constants.UnusedMixId;
|
||||
SplitterId = Constants.UnusedSplitterId;
|
||||
DataSourceStateAddressInfo.Setup(0, 0);
|
||||
|
||||
InitializeWaveBuffers();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the <see cref="WaveBuffer"/> in this <see cref="VoiceState"/>.
|
||||
/// </summary>
|
||||
private void InitializeWaveBuffers()
|
||||
{
|
||||
for (int i = 0; i < WaveBuffers.Length; i++)
|
||||
{
|
||||
WaveBuffers[i].StartSampleOffset = 0;
|
||||
WaveBuffers[i].EndSampleOffset = 0;
|
||||
WaveBuffers[i].ShouldLoop = false;
|
||||
WaveBuffers[i].IsEndOfStream = false;
|
||||
WaveBuffers[i].BufferAddressInfo.Setup(0, 0);
|
||||
WaveBuffers[i].ContextAddressInfo.Setup(0, 0);
|
||||
WaveBuffers[i].IsSendToAudioProcessor = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the voice needs to be skipped.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the voice needs to be skipped.</returns>
|
||||
public bool ShouldSkip()
|
||||
{
|
||||
return !InUse || WaveBuffersCount == 0 || DataSourceStateUnmapped || BufferInfoUnmapped || VoiceDropFlag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the mix has any destinations.
|
||||
/// </summary>
|
||||
/// <returns>True if the mix has any destinations.</returns>
|
||||
public bool HasAnyDestination()
|
||||
{
|
||||
return MixId != Constants.UnusedMixId || SplitterId != Constants.UnusedSplitterId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicate if the server voice information needs to be updated.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <returns>Return true, if the server voice information needs to be updated.</returns>
|
||||
private bool ShouldUpdateParameters(ref VoiceInParameter parameter)
|
||||
{
|
||||
if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress)
|
||||
{
|
||||
return DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize;
|
||||
}
|
||||
|
||||
return DataSourceStateAddressInfo.CpuAddress != parameter.DataSourceStateAddress ||
|
||||
DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize ||
|
||||
DataSourceStateUnmapped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal state from a user parameter.
|
||||
/// </summary>
|
||||
/// <param name="outErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
|
||||
/// <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)
|
||||
{
|
||||
InUse = parameter.InUse;
|
||||
Id = parameter.Id;
|
||||
NodeId = parameter.NodeId;
|
||||
|
||||
UpdatePlayState(parameter.PlayState);
|
||||
|
||||
SrcQuality = parameter.SrcQuality;
|
||||
|
||||
Priority = parameter.Priority;
|
||||
SortingOrder = parameter.SortingOrder;
|
||||
SampleRate = parameter.SampleRate;
|
||||
SampleFormat = parameter.SampleFormat;
|
||||
ChannelsCount = parameter.ChannelCount;
|
||||
Pitch = parameter.Pitch;
|
||||
Volume = parameter.Volume;
|
||||
parameter.BiquadFilters.ToSpan().CopyTo(BiquadFilters.ToSpan());
|
||||
WaveBuffersCount = parameter.WaveBuffersCount;
|
||||
WaveBuffersIndex = parameter.WaveBuffersIndex;
|
||||
|
||||
if (behaviourContext.IsFlushVoiceWaveBuffersSupported())
|
||||
{
|
||||
FlushWaveBufferCount += parameter.FlushWaveBufferCount;
|
||||
}
|
||||
|
||||
MixId = parameter.MixId;
|
||||
|
||||
if (behaviourContext.IsSplitterSupported())
|
||||
{
|
||||
SplitterId = parameter.SplitterId;
|
||||
}
|
||||
else
|
||||
{
|
||||
SplitterId = Constants.UnusedSplitterId;
|
||||
}
|
||||
|
||||
parameter.ChannelResourceIds.ToSpan().CopyTo(ChannelResourceIds.ToSpan());
|
||||
|
||||
DecodingBehaviour behaviour = DecodingBehaviour.Default;
|
||||
|
||||
if (behaviourContext.IsDecodingBehaviourFlagSupported())
|
||||
{
|
||||
behaviour = parameter.DecodingBehaviourFlags;
|
||||
}
|
||||
|
||||
DecodingBehaviour = behaviour;
|
||||
|
||||
if (parameter.ResetVoiceDropFlag)
|
||||
{
|
||||
VoiceDropFlag = false;
|
||||
}
|
||||
|
||||
if (ShouldUpdateParameters(ref parameter))
|
||||
{
|
||||
DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
outErrorInfo = new ErrorInfo();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal play state from user play state.
|
||||
/// </summary>
|
||||
/// <param name="userPlayState">The target user play state.</param>
|
||||
public void UpdatePlayState(PlayState userPlayState)
|
||||
{
|
||||
Types.PlayState oldServerPlayState = PlayState;
|
||||
|
||||
PreviousPlayState = oldServerPlayState;
|
||||
|
||||
Types.PlayState newServerPlayState;
|
||||
|
||||
switch (userPlayState)
|
||||
{
|
||||
case Common.PlayState.Start:
|
||||
newServerPlayState = Types.PlayState.Started;
|
||||
break;
|
||||
|
||||
case Common.PlayState.Stop:
|
||||
if (oldServerPlayState == Types.PlayState.Stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
newServerPlayState = Types.PlayState.Stopping;
|
||||
break;
|
||||
|
||||
case Common.PlayState.Pause:
|
||||
newServerPlayState = Types.PlayState.Paused;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unhandled PlayState.{userPlayState}");
|
||||
}
|
||||
|
||||
PlayState = newServerPlayState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the status of the voice to the given user output.
|
||||
/// </summary>
|
||||
/// <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, Memory<VoiceUpdateState>[] voiceUpdateStates)
|
||||
{
|
||||
#if DEBUG
|
||||
// Sanity check in debug mode of the internal state
|
||||
if (!parameter.IsNew && !IsNew)
|
||||
{
|
||||
for (int i = 1; i < ChannelsCount; i++)
|
||||
{
|
||||
ref VoiceUpdateState stateA = ref voiceUpdateStates[i - 1].Span[0];
|
||||
ref VoiceUpdateState stateB = ref voiceUpdateStates[i].Span[0];
|
||||
|
||||
Debug.Assert(stateA.WaveBufferConsumed == stateB.WaveBufferConsumed);
|
||||
Debug.Assert(stateA.PlayedSampleCount == stateB.PlayedSampleCount);
|
||||
Debug.Assert(stateA.Offset == stateB.Offset);
|
||||
Debug.Assert(stateA.WaveBufferIndex == stateB.WaveBufferIndex);
|
||||
Debug.Assert(stateA.Fraction == stateB.Fraction);
|
||||
Debug.Assert(stateA.IsWaveBufferValid.SequenceEqual(stateB.IsWaveBufferValid));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (parameter.IsNew || IsNew)
|
||||
{
|
||||
IsNew = true;
|
||||
|
||||
outStatus.VoiceDropFlag = false;
|
||||
outStatus.PlayedWaveBuffersCount = 0;
|
||||
outStatus.PlayedSampleCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ref VoiceUpdateState state = ref voiceUpdateStates[0].Span[0];
|
||||
|
||||
outStatus.VoiceDropFlag = VoiceDropFlag;
|
||||
outStatus.PlayedWaveBuffersCount = state.WaveBufferConsumed;
|
||||
outStatus.PlayedSampleCount = state.PlayedSampleCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal state of all the <see cref="WaveBuffer"/> of the <see cref="VoiceState"/>.
|
||||
/// </summary>
|
||||
/// <param name="errorInfos">An array of <see cref="ErrorInfo"/> used to report errors when mapping any of the <see cref="WaveBuffer"/>.</param>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <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, Memory<VoiceUpdateState>[] voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext)
|
||||
{
|
||||
errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2];
|
||||
|
||||
if (parameter.IsNew)
|
||||
{
|
||||
InitializeWaveBuffers();
|
||||
|
||||
for (int i = 0; i < parameter.ChannelCount; i++)
|
||||
{
|
||||
voiceUpdateStates[i].Span[0].IsWaveBufferValid.Fill(false);
|
||||
}
|
||||
}
|
||||
|
||||
ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[0].Span[0];
|
||||
|
||||
for (int i = 0; i < Constants.VoiceWaveBufferCount; i++)
|
||||
{
|
||||
UpdateWaveBuffer(errorInfos.AsSpan().Slice(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal state of one of the <see cref="WaveBuffer"/> of the <see cref="VoiceState"/>.
|
||||
/// </summary>
|
||||
/// <param name="errorInfos">A <see cref="Span{ErrorInfo}"/> used to report errors when mapping the <see cref="WaveBuffer"/>.</param>
|
||||
/// <param name="waveBuffer">The <see cref="WaveBuffer"/> to update.</param>
|
||||
/// <param name="inputWaveBuffer">The <see cref="WaveBufferInternal"/> from the user input.</param>
|
||||
/// <param name="sampleFormat">The <see cref="SampleFormat"/> from the user input.</param>
|
||||
/// <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)
|
||||
{
|
||||
if (!isValid && waveBuffer.IsSendToAudioProcessor && waveBuffer.BufferAddressInfo.CpuAddress != 0)
|
||||
{
|
||||
mapper.ForceUnmap(ref waveBuffer.BufferAddressInfo);
|
||||
waveBuffer.BufferAddressInfo.Setup(0, 0);
|
||||
}
|
||||
|
||||
if (!inputWaveBuffer.SentToServer || BufferInfoUnmapped)
|
||||
{
|
||||
if (inputWaveBuffer.IsSampleOffsetValid(sampleFormat))
|
||||
{
|
||||
Debug.Assert(waveBuffer.IsSendToAudioProcessor);
|
||||
|
||||
waveBuffer.IsSendToAudioProcessor = false;
|
||||
waveBuffer.StartSampleOffset = inputWaveBuffer.StartSampleOffset;
|
||||
waveBuffer.EndSampleOffset = inputWaveBuffer.EndSampleOffset;
|
||||
waveBuffer.ShouldLoop = inputWaveBuffer.ShouldLoop;
|
||||
waveBuffer.IsEndOfStream = inputWaveBuffer.IsEndOfStream;
|
||||
waveBuffer.LoopStartSampleOffset = inputWaveBuffer.LoopFirstSampleOffset;
|
||||
waveBuffer.LoopEndSampleOffset = inputWaveBuffer.LoopLastSampleOffset;
|
||||
waveBuffer.LoopCount = inputWaveBuffer.LoopCount;
|
||||
|
||||
BufferInfoUnmapped = !mapper.TryAttachBuffer(out ErrorInfo bufferInfoError, ref waveBuffer.BufferAddressInfo, inputWaveBuffer.Address, inputWaveBuffer.Size);
|
||||
|
||||
errorInfos[0] = bufferInfoError;
|
||||
|
||||
if (sampleFormat == SampleFormat.Adpcm && behaviourContext.IsAdpcmLoopContextBugFixed() && inputWaveBuffer.ContextAddress != 0)
|
||||
{
|
||||
bool adpcmLoopContextMapped = mapper.TryAttachBuffer(out ErrorInfo adpcmLoopContextInfoError,
|
||||
ref waveBuffer.ContextAddressInfo,
|
||||
inputWaveBuffer.ContextAddress,
|
||||
inputWaveBuffer.ContextSize);
|
||||
|
||||
errorInfos[1] = adpcmLoopContextInfoError;
|
||||
|
||||
if (adpcmLoopContextMapped)
|
||||
{
|
||||
BufferInfoUnmapped = DataSourceStateUnmapped;
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferInfoUnmapped = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
waveBuffer.ContextAddressInfo.Setup(0, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errorInfos[0].ErrorCode = ResultCode.InvalidAddressInfo;
|
||||
errorInfos[0].ExtraErrorInfo = inputWaveBuffer.Address;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the resources associated to this <see cref="VoiceState"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The voice context.</param>
|
||||
private void ResetResources(VoiceContext context)
|
||||
{
|
||||
for (int i = 0; i < ChannelsCount; i++)
|
||||
{
|
||||
int channelResourceId = ChannelResourceIds[i];
|
||||
|
||||
ref VoiceChannelResource voiceChannelResource = ref context.GetChannelResource(channelResourceId);
|
||||
|
||||
Debug.Assert(voiceChannelResource.IsUsed);
|
||||
|
||||
Memory<VoiceUpdateState> dspSharedState = context.GetUpdateStateForDsp(channelResourceId);
|
||||
|
||||
MemoryMarshal.Cast<VoiceUpdateState, byte>(dspSharedState.Span).Fill(0);
|
||||
|
||||
voiceChannelResource.UpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush a certain amount of <see cref="WaveBuffer"/>.
|
||||
/// </summary>
|
||||
/// <param name="waveBufferCount">The amount of wavebuffer to flush.</param>
|
||||
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
|
||||
/// <param name="channelCount">The channel count from user input.</param>
|
||||
private void FlushWaveBuffers(uint waveBufferCount, Memory<VoiceUpdateState>[] voiceUpdateStates, uint channelCount)
|
||||
{
|
||||
uint waveBufferIndex = WaveBuffersIndex;
|
||||
|
||||
for (int i = 0; i < waveBufferCount; i++)
|
||||
{
|
||||
WaveBuffers[(int)waveBufferIndex].IsSendToAudioProcessor = true;
|
||||
|
||||
for (int j = 0; j < channelCount; j++)
|
||||
{
|
||||
ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[j].Span[0];
|
||||
|
||||
voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % Constants.VoiceWaveBufferCount;
|
||||
voiceUpdateState.WaveBufferConsumed++;
|
||||
voiceUpdateState.IsWaveBufferValid[(int)waveBufferIndex] = false;
|
||||
}
|
||||
|
||||
waveBufferIndex = (waveBufferIndex + 1) % Constants.VoiceWaveBufferCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal parameters for command generation.
|
||||
/// </summary>
|
||||
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
|
||||
/// <returns>Return true if this voice should be played.</returns>
|
||||
public bool UpdateParametersForCommandGeneration(Memory<VoiceUpdateState>[] voiceUpdateStates)
|
||||
{
|
||||
if (FlushWaveBufferCount != 0)
|
||||
{
|
||||
FlushWaveBuffers(FlushWaveBufferCount, voiceUpdateStates, ChannelsCount);
|
||||
|
||||
FlushWaveBufferCount = 0;
|
||||
}
|
||||
|
||||
switch (PlayState)
|
||||
{
|
||||
case Types.PlayState.Started:
|
||||
for (int i = 0; i < WaveBuffers.Length; i++)
|
||||
{
|
||||
ref WaveBuffer wavebuffer = ref WaveBuffers[i];
|
||||
|
||||
if (!wavebuffer.IsSendToAudioProcessor)
|
||||
{
|
||||
for (int y = 0; y < ChannelsCount; y++)
|
||||
{
|
||||
Debug.Assert(!voiceUpdateStates[y].Span[0].IsWaveBufferValid[i]);
|
||||
|
||||
voiceUpdateStates[y].Span[0].IsWaveBufferValid[i] = true;
|
||||
}
|
||||
|
||||
wavebuffer.IsSendToAudioProcessor = true;
|
||||
}
|
||||
}
|
||||
|
||||
WasPlaying = false;
|
||||
|
||||
ref VoiceUpdateState primaryVoiceUpdateState = ref voiceUpdateStates[0].Span[0];
|
||||
|
||||
for (int i = 0; i < primaryVoiceUpdateState.IsWaveBufferValid.Length; i++)
|
||||
{
|
||||
if (primaryVoiceUpdateState.IsWaveBufferValid[i])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
case Types.PlayState.Stopping:
|
||||
for (int i = 0; i < WaveBuffers.Length; i++)
|
||||
{
|
||||
ref WaveBuffer wavebuffer = ref WaveBuffers[i];
|
||||
|
||||
wavebuffer.IsSendToAudioProcessor = true;
|
||||
|
||||
for (int j = 0; j < ChannelsCount; j++)
|
||||
{
|
||||
ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[j].Span[0];
|
||||
|
||||
if (voiceUpdateState.IsWaveBufferValid[i])
|
||||
{
|
||||
voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % Constants.VoiceWaveBufferCount;
|
||||
voiceUpdateState.WaveBufferConsumed++;
|
||||
}
|
||||
|
||||
voiceUpdateState.IsWaveBufferValid[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < ChannelsCount; i++)
|
||||
{
|
||||
ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[i].Span[0];
|
||||
|
||||
voiceUpdateState.Offset = 0;
|
||||
voiceUpdateState.PlayedSampleCount = 0;
|
||||
voiceUpdateState.Pitch.ToSpan().Fill(0);
|
||||
voiceUpdateState.Fraction = 0;
|
||||
voiceUpdateState.LoopContext = new Dsp.State.AdpcmLoopContext();
|
||||
}
|
||||
|
||||
PlayState = Types.PlayState.Stopped;
|
||||
WasPlaying = PreviousPlayState == Types.PlayState.Started;
|
||||
|
||||
return WasPlaying;
|
||||
|
||||
case Types.PlayState.Stopped:
|
||||
case Types.PlayState.Paused:
|
||||
foreach (ref WaveBuffer wavebuffer in WaveBuffers.ToSpan())
|
||||
{
|
||||
wavebuffer.BufferAddressInfo.GetReference(true);
|
||||
wavebuffer.ContextAddressInfo.GetReference(true);
|
||||
}
|
||||
|
||||
if (SampleFormat == SampleFormat.Adpcm)
|
||||
{
|
||||
if (DataSourceStateAddressInfo.CpuAddress != 0)
|
||||
{
|
||||
DataSourceStateAddressInfo.GetReference(true);
|
||||
}
|
||||
}
|
||||
|
||||
WasPlaying = PreviousPlayState == Types.PlayState.Started;
|
||||
|
||||
return WasPlaying;
|
||||
default:
|
||||
throw new NotImplementedException($"{PlayState}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal state for command generation.
|
||||
/// </summary>
|
||||
/// <param name="context">The voice context.</param>
|
||||
/// <returns>Return true if this voice should be played.</returns>
|
||||
public bool UpdateForCommandGeneration(VoiceContext context)
|
||||
{
|
||||
if (IsNew)
|
||||
{
|
||||
ResetResources(context);
|
||||
PreviousVolume = Volume;
|
||||
IsNew = false;
|
||||
}
|
||||
|
||||
Memory<VoiceUpdateState>[] voiceUpdateStates = new Memory<VoiceUpdateState>[Constants.VoiceChannelCountMax];
|
||||
|
||||
for (int i = 0; i < ChannelsCount; i++)
|
||||
{
|
||||
voiceUpdateStates[i] = context.GetUpdateStateForDsp(ChannelResourceIds[i]);
|
||||
}
|
||||
|
||||
return UpdateParametersForCommandGeneration(voiceUpdateStates);
|
||||
}
|
||||
}
|
||||
}
|
121
Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs
Normal file
121
Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs
Normal file
|
@ -0,0 +1,121 @@
|
|||
//
|
||||
// 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.Server.MemoryPool;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Voice
|
||||
{
|
||||
/// <summary>
|
||||
/// A wavebuffer used for server update.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x58, Pack = 1)]
|
||||
public struct WaveBuffer
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="AddressInfo"/> of the sample data of the wavebuffer.
|
||||
/// </summary>
|
||||
public AddressInfo BufferAddressInfo;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AddressInfo"/> of the context of the wavebuffer.
|
||||
/// </summary>
|
||||
/// <remarks>Only used by <see cref="Common.SampleFormat.Adpcm"/>.</remarks>
|
||||
public AddressInfo ContextAddressInfo;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// First sample to play of the wavebuffer.
|
||||
/// </summary>
|
||||
public uint StartSampleOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Last sample to play of the wavebuffer.
|
||||
/// </summary>
|
||||
public uint EndSampleOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the wavebuffer is looping.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool ShouldLoop;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the wavebuffer is the end of stream.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsEndOfStream;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the wavebuffer wasn't sent to the <see cref="Dsp.AudioProcessor"/>.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsSendToAudioProcessor;
|
||||
|
||||
/// <summary>
|
||||
/// First sample to play when looping the wavebuffer.
|
||||
/// </summary>
|
||||
public uint LoopStartSampleOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Last sample to play when looping the wavebuffer.
|
||||
/// </summary>
|
||||
public uint LoopEndSampleOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The max loop count.
|
||||
/// </summary>
|
||||
public int LoopCount;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="Common.WaveBuffer"/> for use by the <see cref="Dsp.AudioProcessor"/>.
|
||||
/// </summary>
|
||||
/// <param name="version">The target version of the wavebuffer.</param>
|
||||
/// <returns>A new <see cref="Common.WaveBuffer"/> for use by the <see cref="Dsp.AudioProcessor"/>.</returns>
|
||||
public Common.WaveBuffer ToCommon(int version)
|
||||
{
|
||||
Common.WaveBuffer waveBuffer = new Common.WaveBuffer();
|
||||
|
||||
waveBuffer.Buffer = BufferAddressInfo.GetReference(true);
|
||||
waveBuffer.BufferSize = (uint)BufferAddressInfo.Size;
|
||||
|
||||
if (ContextAddressInfo.CpuAddress != 0)
|
||||
{
|
||||
waveBuffer.Context = ContextAddressInfo.GetReference(true);
|
||||
waveBuffer.ContextSize = (uint)ContextAddressInfo.Size;
|
||||
}
|
||||
|
||||
waveBuffer.StartSampleOffset = StartSampleOffset;
|
||||
waveBuffer.EndSampleOffset = EndSampleOffset;
|
||||
waveBuffer.Looping = ShouldLoop;
|
||||
waveBuffer.IsEndOfStream = IsEndOfStream;
|
||||
|
||||
if (version == 2)
|
||||
{
|
||||
waveBuffer.LoopCount = LoopCount;
|
||||
waveBuffer.LoopStartSampleOffset = LoopStartSampleOffset;
|
||||
waveBuffer.LoopEndSampleOffset = LoopEndSampleOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
waveBuffer.LoopCount = -1;
|
||||
}
|
||||
|
||||
return waveBuffer;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue