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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue