AudioRenderSystem.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897
  1. using Ryujinx.Audio.Integration;
  2. using Ryujinx.Audio.Renderer.Common;
  3. using Ryujinx.Audio.Renderer.Dsp.Command;
  4. using Ryujinx.Audio.Renderer.Dsp.State;
  5. using Ryujinx.Audio.Renderer.Parameter;
  6. using Ryujinx.Audio.Renderer.Server.Effect;
  7. using Ryujinx.Audio.Renderer.Server.MemoryPool;
  8. using Ryujinx.Audio.Renderer.Server.Mix;
  9. using Ryujinx.Audio.Renderer.Server.Performance;
  10. using Ryujinx.Audio.Renderer.Server.Sink;
  11. using Ryujinx.Audio.Renderer.Server.Splitter;
  12. using Ryujinx.Audio.Renderer.Server.Types;
  13. using Ryujinx.Audio.Renderer.Server.Upsampler;
  14. using Ryujinx.Audio.Renderer.Server.Voice;
  15. using Ryujinx.Audio.Renderer.Utils;
  16. using Ryujinx.Common;
  17. using Ryujinx.Common.Logging;
  18. using Ryujinx.Memory;
  19. using System;
  20. using System.Buffers;
  21. using System.Diagnostics;
  22. using System.Threading;
  23. using CpuAddress = System.UInt64;
  24. namespace Ryujinx.Audio.Renderer.Server
  25. {
  26. public class AudioRenderSystem : IDisposable
  27. {
  28. private readonly object _lock = new();
  29. private AudioRendererRenderingDevice _renderingDevice;
  30. private AudioRendererExecutionMode _executionMode;
  31. private readonly IWritableEvent _systemEvent;
  32. private MemoryPoolState _dspMemoryPoolState;
  33. private readonly VoiceContext _voiceContext;
  34. private readonly MixContext _mixContext;
  35. private readonly SinkContext _sinkContext;
  36. private readonly SplitterContext _splitterContext;
  37. private readonly EffectContext _effectContext;
  38. private PerformanceManager _performanceManager;
  39. private UpsamplerManager _upsamplerManager;
  40. private bool _isActive;
  41. private BehaviourContext _behaviourContext;
  42. #pragma warning disable IDE0052 // Remove unread private member
  43. private ulong _totalElapsedTicksUpdating;
  44. private ulong _totalElapsedTicks;
  45. #pragma warning restore IDE0052
  46. private int _sessionId;
  47. private Memory<MemoryPoolState> _memoryPools;
  48. private uint _sampleRate;
  49. private uint _sampleCount;
  50. private uint _mixBufferCount;
  51. private uint _voiceChannelCountMax;
  52. private uint _upsamplerCount;
  53. private uint _memoryPoolCount;
  54. private uint _processHandle;
  55. private ulong _appletResourceId;
  56. private MemoryHandle _workBufferMemoryPin;
  57. private Memory<float> _mixBuffer;
  58. private Memory<float> _depopBuffer;
  59. private uint _renderingTimeLimitPercent;
  60. private bool _voiceDropEnabled;
  61. private uint _voiceDropCount;
  62. private float _voiceDropParameter;
  63. private bool _isDspRunningBehind;
  64. private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator;
  65. private Memory<byte> _performanceBuffer;
  66. public IVirtualMemoryManager MemoryManager { get; private set; }
  67. private ulong _elapsedFrameCount;
  68. private ulong _renderingStartTick;
  69. private readonly AudioRendererManager _manager;
  70. private int _disposeState;
  71. public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent)
  72. {
  73. _manager = manager;
  74. _dspMemoryPoolState = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp);
  75. _voiceContext = new VoiceContext();
  76. _mixContext = new MixContext();
  77. _sinkContext = new SinkContext();
  78. _splitterContext = new SplitterContext();
  79. _effectContext = new EffectContext();
  80. _commandProcessingTimeEstimator = null;
  81. _systemEvent = systemEvent;
  82. _behaviourContext = new BehaviourContext();
  83. _totalElapsedTicksUpdating = 0;
  84. _sessionId = 0;
  85. _voiceDropParameter = 1.0f;
  86. }
  87. public ResultCode Initialize(
  88. ref AudioRendererConfiguration parameter,
  89. uint processHandle,
  90. Memory<byte> workBufferMemory,
  91. CpuAddress workBuffer,
  92. ulong workBufferSize,
  93. int sessionId,
  94. ulong appletResourceId,
  95. IVirtualMemoryManager memoryManager)
  96. {
  97. if (!BehaviourContext.CheckValidRevision(parameter.Revision))
  98. {
  99. return ResultCode.OperationFailed;
  100. }
  101. if (GetWorkBufferSize(ref parameter) > workBufferSize)
  102. {
  103. return ResultCode.WorkBufferTooSmall;
  104. }
  105. Debug.Assert(parameter.RenderingDevice == AudioRendererRenderingDevice.Dsp && parameter.ExecutionMode == AudioRendererExecutionMode.Auto);
  106. Logger.Info?.Print(LogClass.AudioRenderer, $"Initializing with REV{BehaviourContext.GetRevisionNumber(parameter.Revision)}");
  107. _behaviourContext.SetUserRevision(parameter.Revision);
  108. _sampleRate = parameter.SampleRate;
  109. _sampleCount = parameter.SampleCount;
  110. _mixBufferCount = parameter.MixBufferCount;
  111. _voiceChannelCountMax = Constants.VoiceChannelCountMax;
  112. _upsamplerCount = parameter.SinkCount + parameter.SubMixBufferCount;
  113. _appletResourceId = appletResourceId;
  114. _memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount;
  115. _renderingDevice = parameter.RenderingDevice;
  116. _executionMode = parameter.ExecutionMode;
  117. _sessionId = sessionId;
  118. MemoryManager = memoryManager;
  119. if (memoryManager is IRefCounted rc)
  120. {
  121. rc.IncrementReferenceCount();
  122. }
  123. WorkBufferAllocator workBufferAllocator;
  124. workBufferMemory.Span.Clear();
  125. _workBufferMemoryPin = workBufferMemory.Pin();
  126. workBufferAllocator = new WorkBufferAllocator(workBufferMemory);
  127. PoolMapper poolMapper = new(processHandle, false);
  128. poolMapper.InitializeSystemPool(ref _dspMemoryPoolState, workBuffer, workBufferSize);
  129. _mixBuffer = workBufferAllocator.Allocate<float>(_sampleCount * (_voiceChannelCountMax + _mixBufferCount), 0x10);
  130. if (_mixBuffer.IsEmpty)
  131. {
  132. return ResultCode.WorkBufferTooSmall;
  133. }
  134. Memory<float> upSamplerWorkBuffer = workBufferAllocator.Allocate<float>(Constants.TargetSampleCount * (_voiceChannelCountMax + _mixBufferCount) * _upsamplerCount, 0x10);
  135. if (upSamplerWorkBuffer.IsEmpty)
  136. {
  137. return ResultCode.WorkBufferTooSmall;
  138. }
  139. _depopBuffer = workBufferAllocator.Allocate<float>(BitUtils.AlignUp<ulong>(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment);
  140. if (_depopBuffer.IsEmpty)
  141. {
  142. return ResultCode.WorkBufferTooSmall;
  143. }
  144. Memory<BiquadFilterState> splitterBqfStates = Memory<BiquadFilterState>.Empty;
  145. if (_behaviourContext.IsBiquadFilterParameterForSplitterEnabled() &&
  146. parameter.SplitterCount > 0 &&
  147. parameter.SplitterDestinationCount > 0)
  148. {
  149. splitterBqfStates = workBufferAllocator.Allocate<BiquadFilterState>(parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10);
  150. if (splitterBqfStates.IsEmpty)
  151. {
  152. return ResultCode.WorkBufferTooSmall;
  153. }
  154. splitterBqfStates.Span.Clear();
  155. }
  156. // Invalidate DSP cache on what was currently allocated with workBuffer.
  157. AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset);
  158. Debug.Assert((workBufferAllocator.Offset % Constants.BufferAlignment) == 0);
  159. Memory<VoiceState> voices = workBufferAllocator.Allocate<VoiceState>(parameter.VoiceCount, VoiceState.Alignment);
  160. if (voices.IsEmpty)
  161. {
  162. return ResultCode.WorkBufferTooSmall;
  163. }
  164. foreach (ref VoiceState voice in voices.Span)
  165. {
  166. voice.Initialize();
  167. }
  168. // A pain to handle as we can't have VoiceState*, use indices to be a bit more safe
  169. Memory<int> sortedVoices = workBufferAllocator.Allocate<int>(parameter.VoiceCount, 0x10);
  170. if (sortedVoices.IsEmpty)
  171. {
  172. return ResultCode.WorkBufferTooSmall;
  173. }
  174. // Clear memory (use -1 as it's an invalid index)
  175. sortedVoices.Span.Fill(-1);
  176. Memory<VoiceChannelResource> voiceChannelResources = workBufferAllocator.Allocate<VoiceChannelResource>(parameter.VoiceCount, VoiceChannelResource.Alignment);
  177. if (voiceChannelResources.IsEmpty)
  178. {
  179. return ResultCode.WorkBufferTooSmall;
  180. }
  181. for (uint id = 0; id < voiceChannelResources.Length; id++)
  182. {
  183. ref VoiceChannelResource voiceChannelResource = ref voiceChannelResources.Span[(int)id];
  184. voiceChannelResource.Id = id;
  185. voiceChannelResource.IsUsed = false;
  186. }
  187. Memory<VoiceUpdateState> voiceUpdateStates = workBufferAllocator.Allocate<VoiceUpdateState>(parameter.VoiceCount, VoiceUpdateState.Align);
  188. if (voiceUpdateStates.IsEmpty)
  189. {
  190. return ResultCode.WorkBufferTooSmall;
  191. }
  192. uint mixesCount = parameter.SubMixBufferCount + 1;
  193. Memory<MixState> mixes = workBufferAllocator.Allocate<MixState>(mixesCount, MixState.Alignment);
  194. if (mixes.IsEmpty)
  195. {
  196. return ResultCode.WorkBufferTooSmall;
  197. }
  198. if (parameter.EffectCount == 0)
  199. {
  200. foreach (ref MixState mix in mixes.Span)
  201. {
  202. mix = new MixState(Memory<int>.Empty, ref _behaviourContext);
  203. }
  204. }
  205. else
  206. {
  207. Memory<int> effectProcessingOrderArray = workBufferAllocator.Allocate<int>(parameter.EffectCount * mixesCount, 0x10);
  208. foreach (ref MixState mix in mixes.Span)
  209. {
  210. mix = new MixState(effectProcessingOrderArray[..(int)parameter.EffectCount], ref _behaviourContext);
  211. effectProcessingOrderArray = effectProcessingOrderArray[(int)parameter.EffectCount..];
  212. }
  213. }
  214. // Initialize the final mix id
  215. mixes.Span[0].MixId = Constants.FinalMixId;
  216. Memory<int> sortedMixesState = workBufferAllocator.Allocate<int>(mixesCount, 0x10);
  217. if (sortedMixesState.IsEmpty)
  218. {
  219. return ResultCode.WorkBufferTooSmall;
  220. }
  221. // Clear memory (use -1 as it's an invalid index)
  222. sortedMixesState.Span.Fill(-1);
  223. Memory<byte> nodeStatesWorkBuffer = Memory<byte>.Empty;
  224. Memory<byte> edgeMatrixWorkBuffer = Memory<byte>.Empty;
  225. if (_behaviourContext.IsSplitterSupported())
  226. {
  227. nodeStatesWorkBuffer = workBufferAllocator.Allocate((uint)NodeStates.GetWorkBufferSize((int)mixesCount), 1);
  228. edgeMatrixWorkBuffer = workBufferAllocator.Allocate((uint)EdgeMatrix.GetWorkBufferSize((int)mixesCount), 1);
  229. if (nodeStatesWorkBuffer.IsEmpty || edgeMatrixWorkBuffer.IsEmpty)
  230. {
  231. return ResultCode.WorkBufferTooSmall;
  232. }
  233. }
  234. _mixContext.Initialize(sortedMixesState, mixes, nodeStatesWorkBuffer, edgeMatrixWorkBuffer);
  235. _memoryPools = workBufferAllocator.Allocate<MemoryPoolState>(_memoryPoolCount, MemoryPoolState.Alignment);
  236. if (_memoryPools.IsEmpty)
  237. {
  238. return ResultCode.WorkBufferTooSmall;
  239. }
  240. foreach (ref MemoryPoolState state in _memoryPools.Span)
  241. {
  242. state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu);
  243. }
  244. if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator, splitterBqfStates))
  245. {
  246. return ResultCode.WorkBufferTooSmall;
  247. }
  248. _processHandle = processHandle;
  249. _upsamplerManager = new UpsamplerManager(upSamplerWorkBuffer, _upsamplerCount);
  250. _effectContext.Initialize(parameter.EffectCount, _behaviourContext.IsEffectInfoVersion2Supported() ? parameter.EffectCount : 0);
  251. _sinkContext.Initialize(parameter.SinkCount);
  252. Memory<VoiceUpdateState> voiceUpdateStatesDsp = workBufferAllocator.Allocate<VoiceUpdateState>(parameter.VoiceCount, VoiceUpdateState.Align);
  253. if (voiceUpdateStatesDsp.IsEmpty)
  254. {
  255. return ResultCode.WorkBufferTooSmall;
  256. }
  257. _voiceContext.Initialize(sortedVoices, voices, voiceChannelResources, voiceUpdateStates, voiceUpdateStatesDsp, parameter.VoiceCount);
  258. if (parameter.PerformanceMetricFramesCount > 0)
  259. {
  260. ulong performanceBufferSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref _behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC;
  261. _performanceBuffer = workBufferAllocator.Allocate(performanceBufferSize, Constants.BufferAlignment);
  262. if (_performanceBuffer.IsEmpty)
  263. {
  264. return ResultCode.WorkBufferTooSmall;
  265. }
  266. _performanceManager = PerformanceManager.Create(_performanceBuffer, ref parameter, _behaviourContext);
  267. }
  268. else
  269. {
  270. _performanceManager = null;
  271. }
  272. _totalElapsedTicksUpdating = 0;
  273. _totalElapsedTicks = 0;
  274. _renderingTimeLimitPercent = 100;
  275. _voiceDropEnabled = parameter.VoiceDropEnabled && _executionMode == AudioRendererExecutionMode.Auto;
  276. AudioProcessorMemoryManager.InvalidateDataCache(workBuffer, workBufferSize);
  277. _processHandle = processHandle;
  278. _elapsedFrameCount = 0;
  279. _voiceDropParameter = 1.0f;
  280. _commandProcessingTimeEstimator = _behaviourContext.GetCommandProcessingTimeEstimatorVersion() switch
  281. {
  282. 1 => new CommandProcessingTimeEstimatorVersion1(_sampleCount, _mixBufferCount),
  283. 2 => new CommandProcessingTimeEstimatorVersion2(_sampleCount, _mixBufferCount),
  284. 3 => new CommandProcessingTimeEstimatorVersion3(_sampleCount, _mixBufferCount),
  285. 4 => new CommandProcessingTimeEstimatorVersion4(_sampleCount, _mixBufferCount),
  286. 5 => new CommandProcessingTimeEstimatorVersion5(_sampleCount, _mixBufferCount),
  287. _ => throw new NotImplementedException($"Unsupported processing time estimator version {_behaviourContext.GetCommandProcessingTimeEstimatorVersion()}."),
  288. };
  289. return ResultCode.Success;
  290. }
  291. public void Start()
  292. {
  293. Logger.Info?.Print(LogClass.AudioRenderer, $"Starting renderer id {_sessionId}");
  294. lock (_lock)
  295. {
  296. _elapsedFrameCount = 0;
  297. _isActive = true;
  298. }
  299. }
  300. public void Stop()
  301. {
  302. Logger.Info?.Print(LogClass.AudioRenderer, $"Stopping renderer id {_sessionId}");
  303. lock (_lock)
  304. {
  305. _isActive = false;
  306. }
  307. Logger.Info?.Print(LogClass.AudioRenderer, $"Stopped renderer id {_sessionId}");
  308. }
  309. public void Disable()
  310. {
  311. lock (_lock)
  312. {
  313. _isActive = false;
  314. }
  315. }
  316. public ResultCode Update(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlySequence<byte> input)
  317. {
  318. lock (_lock)
  319. {
  320. ulong updateStartTicks = GetSystemTicks();
  321. output.Span.Clear();
  322. StateUpdater stateUpdater = new(input, output, _processHandle, _behaviourContext);
  323. ResultCode result;
  324. result = stateUpdater.UpdateBehaviourContext();
  325. if (result != ResultCode.Success)
  326. {
  327. return result;
  328. }
  329. result = stateUpdater.UpdateMemoryPools(_memoryPools.Span);
  330. if (result != ResultCode.Success)
  331. {
  332. return result;
  333. }
  334. result = stateUpdater.UpdateVoiceChannelResources(_voiceContext);
  335. if (result != ResultCode.Success)
  336. {
  337. return result;
  338. }
  339. PoolMapper poolMapper = new PoolMapper(_processHandle, _memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
  340. result = stateUpdater.UpdateVoices(_voiceContext, poolMapper);
  341. if (result != ResultCode.Success)
  342. {
  343. return result;
  344. }
  345. result = stateUpdater.UpdateEffects(_effectContext, _isActive, poolMapper);
  346. if (result != ResultCode.Success)
  347. {
  348. return result;
  349. }
  350. if (_behaviourContext.IsSplitterSupported())
  351. {
  352. result = stateUpdater.UpdateSplitter(_splitterContext);
  353. if (result != ResultCode.Success)
  354. {
  355. return result;
  356. }
  357. }
  358. result = stateUpdater.UpdateMixes(_mixContext, GetMixBufferCount(), _effectContext, _splitterContext);
  359. if (result != ResultCode.Success)
  360. {
  361. return result;
  362. }
  363. result = stateUpdater.UpdateSinks(_sinkContext, poolMapper);
  364. if (result != ResultCode.Success)
  365. {
  366. return result;
  367. }
  368. result = stateUpdater.UpdatePerformanceBuffer(_performanceManager, performanceOutput.Span);
  369. if (result != ResultCode.Success)
  370. {
  371. return result;
  372. }
  373. result = stateUpdater.UpdateErrorInfo();
  374. if (result != ResultCode.Success)
  375. {
  376. return result;
  377. }
  378. if (_behaviourContext.IsElapsedFrameCountSupported())
  379. {
  380. result = stateUpdater.UpdateRendererInfo(_elapsedFrameCount);
  381. if (result != ResultCode.Success)
  382. {
  383. return result;
  384. }
  385. }
  386. result = stateUpdater.CheckConsumedSize();
  387. if (result != ResultCode.Success)
  388. {
  389. return result;
  390. }
  391. _systemEvent.Clear();
  392. ulong updateEndTicks = GetSystemTicks();
  393. _totalElapsedTicksUpdating += (updateEndTicks - updateStartTicks);
  394. return result;
  395. }
  396. }
  397. private ulong GetSystemTicks()
  398. {
  399. return (ulong)(_manager.TickSource.ElapsedSeconds * Constants.TargetTimerFrequency);
  400. }
  401. private uint ComputeVoiceDrop(CommandBuffer commandBuffer, uint voicesEstimatedTime, long deltaTimeDsp)
  402. {
  403. int i;
  404. for (i = 0; i < commandBuffer.CommandList.Commands.Count; i++)
  405. {
  406. ICommand command = commandBuffer.CommandList.Commands[i];
  407. CommandType commandType = command.CommandType;
  408. if (commandType == CommandType.AdpcmDataSourceVersion1 ||
  409. commandType == CommandType.AdpcmDataSourceVersion2 ||
  410. commandType == CommandType.PcmInt16DataSourceVersion1 ||
  411. commandType == CommandType.PcmInt16DataSourceVersion2 ||
  412. commandType == CommandType.PcmFloatDataSourceVersion1 ||
  413. commandType == CommandType.PcmFloatDataSourceVersion2 ||
  414. commandType == CommandType.Performance)
  415. {
  416. break;
  417. }
  418. }
  419. uint voiceDropped = 0;
  420. for (; i < commandBuffer.CommandList.Commands.Count; i++)
  421. {
  422. ICommand targetCommand = commandBuffer.CommandList.Commands[i];
  423. int targetNodeId = targetCommand.NodeId;
  424. if (voicesEstimatedTime <= deltaTimeDsp || NodeIdHelper.GetType(targetNodeId) != NodeIdType.Voice)
  425. {
  426. break;
  427. }
  428. ref VoiceState voice = ref _voiceContext.GetState(NodeIdHelper.GetBase(targetNodeId));
  429. if (voice.Priority == Constants.VoiceHighestPriority)
  430. {
  431. break;
  432. }
  433. // We can safely drop this voice, disable all associated commands while activating depop preparation commands.
  434. voiceDropped++;
  435. voice.VoiceDropFlag = true;
  436. Logger.Warning?.Print(LogClass.AudioRenderer, $"Dropping voice {voice.NodeId}");
  437. for (; i < commandBuffer.CommandList.Commands.Count; i++)
  438. {
  439. ICommand command = commandBuffer.CommandList.Commands[i];
  440. if (command.NodeId != targetNodeId)
  441. {
  442. break;
  443. }
  444. if (command.CommandType == CommandType.DepopPrepare)
  445. {
  446. command.Enabled = true;
  447. }
  448. else if (command.CommandType == CommandType.Performance || !command.Enabled)
  449. {
  450. continue;
  451. }
  452. else
  453. {
  454. command.Enabled = false;
  455. voicesEstimatedTime -= (uint)(_voiceDropParameter * command.EstimatedProcessingTime);
  456. }
  457. }
  458. }
  459. return voiceDropped;
  460. }
  461. private void GenerateCommandList(out CommandList commandList)
  462. {
  463. Debug.Assert(_executionMode == AudioRendererExecutionMode.Auto);
  464. PoolMapper.ClearUsageState(_memoryPools);
  465. ulong startTicks = GetSystemTicks();
  466. commandList = new CommandList(this);
  467. if (_performanceManager != null)
  468. {
  469. _performanceManager.TapFrame(_isDspRunningBehind, _voiceDropCount, _renderingStartTick);
  470. _isDspRunningBehind = false;
  471. _voiceDropCount = 0;
  472. _renderingStartTick = 0;
  473. }
  474. CommandBuffer commandBuffer = new(commandList, _commandProcessingTimeEstimator);
  475. CommandGenerator commandGenerator = new(commandBuffer, GetContext(), _voiceContext, _mixContext, _effectContext, _sinkContext, _splitterContext, _performanceManager);
  476. _voiceContext.Sort();
  477. commandGenerator.GenerateVoices();
  478. uint voicesEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime);
  479. commandGenerator.GenerateSubMixes();
  480. commandGenerator.GenerateFinalMixes();
  481. commandGenerator.GenerateSinks();
  482. uint totalEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime);
  483. if (_voiceDropEnabled)
  484. {
  485. long maxDspTime = GetMaxAllocatedTimeForDsp();
  486. long restEstimateTime = totalEstimatedTime - voicesEstimatedTime;
  487. long deltaTimeDsp = Math.Max(maxDspTime - restEstimateTime, 0);
  488. _voiceDropCount = ComputeVoiceDrop(commandBuffer, voicesEstimatedTime, deltaTimeDsp);
  489. }
  490. _voiceContext.UpdateForCommandGeneration();
  491. if (_behaviourContext.IsEffectInfoVersion2Supported())
  492. {
  493. _effectContext.UpdateResultStateForCommandGeneration();
  494. }
  495. ulong endTicks = GetSystemTicks();
  496. _totalElapsedTicks = endTicks - startTicks;
  497. _renderingStartTick = GetSystemTicks();
  498. _elapsedFrameCount++;
  499. }
  500. private int GetMaxAllocatedTimeForDsp()
  501. {
  502. return (int)(Constants.AudioProcessorMaxUpdateTimePerSessions * _behaviourContext.GetAudioRendererProcessingTimeLimit() * (GetRenderingTimeLimit() / 100.0f));
  503. }
  504. public void SendCommands()
  505. {
  506. lock (_lock)
  507. {
  508. if (_isActive)
  509. {
  510. if (!_manager.Processor.HasRemainingCommands(_sessionId))
  511. {
  512. GenerateCommandList(out CommandList commands);
  513. _manager.Processor.Send(_sessionId,
  514. commands,
  515. GetMaxAllocatedTimeForDsp(),
  516. _appletResourceId);
  517. _systemEvent.Signal();
  518. }
  519. else
  520. {
  521. _isDspRunningBehind = true;
  522. }
  523. }
  524. }
  525. }
  526. public uint GetMixBufferCount()
  527. {
  528. return _mixBufferCount;
  529. }
  530. public void SetRenderingTimeLimitPercent(uint percent)
  531. {
  532. Debug.Assert(percent <= 100);
  533. _renderingTimeLimitPercent = percent;
  534. }
  535. public uint GetRenderingTimeLimit()
  536. {
  537. return _renderingTimeLimitPercent;
  538. }
  539. public Memory<float> GetMixBuffer()
  540. {
  541. return _mixBuffer;
  542. }
  543. public uint GetSampleCount()
  544. {
  545. return _sampleCount;
  546. }
  547. public uint GetSampleRate()
  548. {
  549. return _sampleRate;
  550. }
  551. public uint GetVoiceChannelCountMax()
  552. {
  553. return _voiceChannelCountMax;
  554. }
  555. public bool IsActive()
  556. {
  557. return _isActive;
  558. }
  559. private RendererSystemContext GetContext()
  560. {
  561. return new RendererSystemContext
  562. {
  563. ChannelCount = _manager.Processor.OutputDevices[_sessionId].GetChannelCount(),
  564. BehaviourContext = _behaviourContext,
  565. DepopBuffer = _depopBuffer,
  566. MixBufferCount = GetMixBufferCount(),
  567. SessionId = _sessionId,
  568. UpsamplerManager = _upsamplerManager,
  569. };
  570. }
  571. public int GetSessionId()
  572. {
  573. return _sessionId;
  574. }
  575. public static ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter)
  576. {
  577. BehaviourContext behaviourContext = new();
  578. behaviourContext.SetUserRevision(parameter.Revision);
  579. uint mixesCount = parameter.SubMixBufferCount + 1;
  580. uint memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount;
  581. ulong size = 0;
  582. // Mix Buffers
  583. size = WorkBufferAllocator.GetTargetSize<float>(size, parameter.SampleCount * (Constants.VoiceChannelCountMax + parameter.MixBufferCount), 0x10);
  584. // Upsampler workbuffer
  585. size = WorkBufferAllocator.GetTargetSize<float>(size, Constants.TargetSampleCount * (Constants.VoiceChannelCountMax + parameter.MixBufferCount) * (parameter.SinkCount + parameter.SubMixBufferCount), 0x10);
  586. // Depop buffer
  587. size = WorkBufferAllocator.GetTargetSize<float>(size, BitUtils.AlignUp<ulong>(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment);
  588. // Voice
  589. size = WorkBufferAllocator.GetTargetSize<VoiceState>(size, parameter.VoiceCount, VoiceState.Alignment);
  590. size = WorkBufferAllocator.GetTargetSize<int>(size, parameter.VoiceCount, 0x10);
  591. size = WorkBufferAllocator.GetTargetSize<VoiceChannelResource>(size, parameter.VoiceCount, VoiceChannelResource.Alignment);
  592. size = WorkBufferAllocator.GetTargetSize<VoiceUpdateState>(size, parameter.VoiceCount, VoiceUpdateState.Align);
  593. // Mix
  594. size = WorkBufferAllocator.GetTargetSize<MixState>(size, mixesCount, MixState.Alignment);
  595. size = WorkBufferAllocator.GetTargetSize<int>(size, parameter.EffectCount * mixesCount, 0x10);
  596. size = WorkBufferAllocator.GetTargetSize<int>(size, mixesCount, 0x10);
  597. if (behaviourContext.IsSplitterSupported())
  598. {
  599. size += (ulong)BitUtils.AlignUp(NodeStates.GetWorkBufferSize((int)mixesCount) + EdgeMatrix.GetWorkBufferSize((int)mixesCount), 0x10);
  600. }
  601. // Memory Pool
  602. size = WorkBufferAllocator.GetTargetSize<MemoryPoolState>(size, memoryPoolCount, MemoryPoolState.Alignment);
  603. // Splitter
  604. size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter);
  605. if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled() &&
  606. parameter.SplitterCount > 0 &&
  607. parameter.SplitterDestinationCount > 0)
  608. {
  609. size = WorkBufferAllocator.GetTargetSize<BiquadFilterState>(size, parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10);
  610. }
  611. // DSP Voice
  612. size = WorkBufferAllocator.GetTargetSize<VoiceUpdateState>(size, parameter.VoiceCount, VoiceUpdateState.Align);
  613. // Performance
  614. if (parameter.PerformanceMetricFramesCount > 0)
  615. {
  616. ulong performanceMetricsPerFramesSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC;
  617. size += BitUtils.AlignUp<ulong>(performanceMetricsPerFramesSize, Constants.PerformanceMetricsPerFramesSizeAlignment);
  618. }
  619. return BitUtils.AlignUp<ulong>(size, Constants.WorkBufferAlignment);
  620. }
  621. public ResultCode QuerySystemEvent(out IWritableEvent systemEvent)
  622. {
  623. systemEvent = default;
  624. if (_executionMode == AudioRendererExecutionMode.Manual)
  625. {
  626. return ResultCode.UnsupportedOperation;
  627. }
  628. systemEvent = _systemEvent;
  629. return ResultCode.Success;
  630. }
  631. public void Dispose()
  632. {
  633. GC.SuppressFinalize(this);
  634. if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
  635. {
  636. Dispose(true);
  637. }
  638. }
  639. protected virtual void Dispose(bool disposing)
  640. {
  641. if (disposing)
  642. {
  643. if (_isActive)
  644. {
  645. Stop();
  646. }
  647. PoolMapper mapper = new(_processHandle, false);
  648. mapper.Unmap(ref _dspMemoryPoolState);
  649. PoolMapper.ClearUsageState(_memoryPools);
  650. for (int i = 0; i < _memoryPoolCount; i++)
  651. {
  652. ref MemoryPoolState memoryPool = ref _memoryPools.Span[i];
  653. if (memoryPool.IsMapped())
  654. {
  655. mapper.Unmap(ref memoryPool);
  656. }
  657. }
  658. _manager.Unregister(this);
  659. _workBufferMemoryPin.Dispose();
  660. if (MemoryManager is IRefCounted rc)
  661. {
  662. rc.DecrementReferenceCount();
  663. MemoryManager = null;
  664. }
  665. }
  666. }
  667. public void SetVoiceDropParameter(float voiceDropParameter)
  668. {
  669. _voiceDropParameter = Math.Clamp(voiceDropParameter, 0.0f, 2.0f);
  670. }
  671. public float GetVoiceDropParameter()
  672. {
  673. return _voiceDropParameter;
  674. }
  675. public ResultCode ExecuteAudioRendererRendering()
  676. {
  677. if (_executionMode == AudioRendererExecutionMode.Manual && _renderingDevice == AudioRendererRenderingDevice.Cpu)
  678. {
  679. // NOTE: Here Nintendo aborts with this error code, we don't want that.
  680. return ResultCode.InvalidExecutionContextOperation;
  681. }
  682. return ResultCode.UnsupportedOperation;
  683. }
  684. }
  685. }