// // 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 . // 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 { /// /// A Generic implementation of . /// /// The header implementation of the performance frame. /// The entry implementation of the performance frame. /// A detailed implementation of the performance frame. public class PerformanceManagerGeneric : PerformanceManager where THeader: unmanaged, IPerformanceHeader where TEntry : unmanaged, IPerformanceEntry where TEntryDetail: unmanaged, IPerformanceDetailEntry { /// /// The magic used for the . /// private const uint MagicPerformanceBuffer = 0x46524550; /// /// The fixed amount of that can be stored in a frame. /// private const int MaxFrameDetailCount = 100; private Memory _buffer; private Memory _historyBuffer; private Memory CurrentBuffer => _buffer.Slice(0, _frameSize); private Memory CurrentBufferData => CurrentBuffer.Slice(Unsafe.SizeOf()); private ref THeader CurrentHeader => ref MemoryMarshal.Cast(CurrentBuffer.Span)[0]; private Span Entries => MemoryMarshal.Cast(CurrentBufferData.Span.Slice(0, GetEntriesSize())); private Span EntriesDetail => MemoryMarshal.Cast(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 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 GetBufferFromIndex(Span data, int index) { return data.Slice(index * _frameSize, _frameSize); } private ref THeader GetHeaderFromBuffer(Span data, int index) { return ref MemoryMarshal.Cast(GetBufferFromIndex(data, index))[0]; } private Span GetEntriesFromBuffer(Span data, int index) { return MemoryMarshal.Cast(GetBufferFromIndex(data, index).Slice(Unsafe.SizeOf(), GetEntriesSize())); } private Span GetEntriesDetailFromBuffer(Span data, int index) { return MemoryMarshal.Cast(GetBufferFromIndex(data, index).Slice(Unsafe.SizeOf() + 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() * _entryCountPerFrame; } public static int GetEntriesDetailSize() { return Unsafe.SizeOf() * MaxFrameDetailCount; } public static int GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref AudioRendererConfiguration parameter) { return Unsafe.SizeOf() * (int)GetEntryCount(ref parameter) + GetEntriesDetailSize() + Unsafe.SizeOf(); } public override uint CopyHistories(Span 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 inputEntries = GetEntriesFromBuffer(_historyBuffer.Span, _indexHistoryRead); Span inputEntriesDetail = GetEntriesDetailFromBuffer(_historyBuffer.Span, _indexHistoryRead); Span targetSpan = performanceOutput.Slice(nextOffset); ref THeader outputHeader = ref MemoryMarshal.Cast(targetSpan)[0]; nextOffset += Unsafe.SizeOf(); Span outputEntries = MemoryMarshal.Cast(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(); totalProcessingTime += input.GetProcessingTime(); } } Span outputEntriesDetail = MemoryMarshal.Cast(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(); } } 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()) { ref THeader outputHeader = ref MemoryMarshal.Cast(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.Cast(CurrentBuffer); performanceEntry.EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset(); uint baseEntryOffset = (uint)(Unsafe.SizeOf() + Unsafe.SizeOf() * _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.Cast(CurrentBuffer); performanceEntry.EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset(); uint baseEntryOffset = (uint)(Unsafe.SizeOf() + GetEntriesSize() + Unsafe.SizeOf() * _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(); } } } }