Ryujinx/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioRenderer.cs
Thog 23170da5a0
audren: implement Renderer Info output informations (#1179)
This implement the rendering information output informations of
RequestUpdate.

This is needed by some games to keep track of the count of update on the
DSP.
2020-04-30 13:03:05 +10:00

452 lines
15 KiB
C#

using ARMeilleure.Memory;
using Ryujinx.Audio;
using Ryujinx.Audio.Adpcm;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.Utilities;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
class IAudioRenderer : IpcService, IDisposable
{
// This is the amount of samples that are going to be appended
// each time that RequestUpdateAudioRenderer is called. Ideally,
// this value shouldn't be neither too small (to avoid the player
// starving due to running out of samples) or too large (to avoid
// high latency).
private const int MixBufferSamplesCount = 960;
private KEvent _updateEvent;
private MemoryManager _memory;
private IAalOutput _audioOut;
private AudioRendererParameter _params;
private MemoryPoolContext[] _memoryPools;
private VoiceContext[] _voices;
private EffectContext[] _effects;
private int _track;
private PlayState _playState;
private ulong _elapsedFrameCount;
public IAudioRenderer(
Horizon system,
MemoryManager memory,
IAalOutput audioOut,
AudioRendererParameter rendererParams)
{
_updateEvent = new KEvent(system);
_memory = memory;
_audioOut = audioOut;
_params = rendererParams;
_track = audioOut.OpenTrack(
AudioRendererConsts.HostSampleRate,
AudioRendererConsts.HostChannelsCount,
AudioCallback);
_memoryPools = CreateArray<MemoryPoolContext>(rendererParams.EffectCount + rendererParams.VoiceCount * 4);
_voices = CreateArray<VoiceContext>(rendererParams.VoiceCount);
_effects = CreateArray<EffectContext>(rendererParams.EffectCount);
_elapsedFrameCount = 0;
InitializeAudioOut();
_playState = PlayState.Stopped;
}
[Command(0)]
// GetSampleRate() -> u32
public ResultCode GetSampleRate(ServiceCtx context)
{
context.ResponseData.Write(_params.SampleRate);
return ResultCode.Success;
}
[Command(1)]
// GetSampleCount() -> u32
public ResultCode GetSampleCount(ServiceCtx context)
{
context.ResponseData.Write(_params.SampleCount);
return ResultCode.Success;
}
[Command(2)]
// GetMixBufferCount() -> u32
public ResultCode GetMixBufferCount(ServiceCtx context)
{
context.ResponseData.Write(_params.SubMixCount);
return ResultCode.Success;
}
[Command(3)]
// GetState() -> u32
public ResultCode GetState(ServiceCtx context)
{
context.ResponseData.Write((int)_playState);
Logger.PrintStub(LogClass.ServiceAudio, new { State = Enum.GetName(typeof(PlayState), _playState) });
return ResultCode.Success;
}
private void AudioCallback()
{
_updateEvent.ReadableEvent.Signal();
}
private static T[] CreateArray<T>(int size) where T : new()
{
T[] output = new T[size];
for (int index = 0; index < size; index++)
{
output[index] = new T();
}
return output;
}
private void InitializeAudioOut()
{
AppendMixedBuffer(0);
AppendMixedBuffer(1);
AppendMixedBuffer(2);
_audioOut.Start(_track);
}
[Command(4)]
// RequestUpdateAudioRenderer(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 5>)
// -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6>, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6>)
public ResultCode RequestUpdateAudioRenderer(ServiceCtx context)
{
long outputPosition = context.Request.ReceiveBuff[0].Position;
long outputSize = context.Request.ReceiveBuff[0].Size;
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
long inputPosition = context.Request.SendBuff[0].Position;
StructReader reader = new StructReader(context.Memory, inputPosition);
StructWriter writer = new StructWriter(context.Memory, outputPosition);
UpdateDataHeader inputHeader = reader.Read<UpdateDataHeader>();
BehaviorInfo behaviorInfo = new BehaviorInfo();
behaviorInfo.SetUserLibRevision(inputHeader.Revision);
reader.Read<BehaviorIn>(inputHeader.BehaviorSize);
MemoryPoolIn[] memoryPoolsIn = reader.Read<MemoryPoolIn>(inputHeader.MemoryPoolSize);
for (int index = 0; index < memoryPoolsIn.Length; index++)
{
MemoryPoolIn memoryPool = memoryPoolsIn[index];
if (memoryPool.State == MemoryPoolState.RequestAttach)
{
_memoryPools[index].OutStatus.State = MemoryPoolState.Attached;
}
else if (memoryPool.State == MemoryPoolState.RequestDetach)
{
_memoryPools[index].OutStatus.State = MemoryPoolState.Detached;
}
}
reader.Read<VoiceChannelResourceIn>(inputHeader.VoiceResourceSize);
VoiceIn[] voicesIn = reader.Read<VoiceIn>(inputHeader.VoiceSize);
for (int index = 0; index < voicesIn.Length; index++)
{
VoiceIn voice = voicesIn[index];
VoiceContext voiceCtx = _voices[index];
voiceCtx.SetAcquireState(voice.Acquired != 0);
if (voice.Acquired == 0)
{
continue;
}
if (voice.FirstUpdate != 0)
{
voiceCtx.AdpcmCtx = GetAdpcmDecoderContext(
voice.AdpcmCoeffsPosition,
voice.AdpcmCoeffsSize);
voiceCtx.SampleFormat = voice.SampleFormat;
voiceCtx.SampleRate = voice.SampleRate;
voiceCtx.ChannelsCount = voice.ChannelsCount;
voiceCtx.SetBufferIndex(voice.BaseWaveBufferIndex);
}
voiceCtx.WaveBuffers[0] = voice.WaveBuffer0;
voiceCtx.WaveBuffers[1] = voice.WaveBuffer1;
voiceCtx.WaveBuffers[2] = voice.WaveBuffer2;
voiceCtx.WaveBuffers[3] = voice.WaveBuffer3;
voiceCtx.Volume = voice.Volume;
voiceCtx.PlayState = voice.PlayState;
}
EffectIn[] effectsIn = reader.Read<EffectIn>(inputHeader.EffectSize);
for (int index = 0; index < effectsIn.Length; index++)
{
if (effectsIn[index].IsNew != 0)
{
_effects[index].OutStatus.State = EffectState.New;
}
}
UpdateAudio();
UpdateDataHeader outputHeader = new UpdateDataHeader();
int updateHeaderSize = Marshal.SizeOf<UpdateDataHeader>();
outputHeader.Revision = AudioRendererConsts.RevMagic;
outputHeader.BehaviorSize = 0xb0;
outputHeader.MemoryPoolSize = (_params.EffectCount + _params.VoiceCount * 4) * 0x10;
outputHeader.VoiceSize = _params.VoiceCount * 0x10;
outputHeader.EffectSize = _params.EffectCount * 0x10;
outputHeader.SinkSize = _params.SinkCount * 0x20;
outputHeader.PerformanceManagerSize = 0x10;
if (behaviorInfo.IsElapsedFrameCountSupported())
{
outputHeader.ElapsedFrameCountInfoSize = 0x10;
}
outputHeader.TotalSize = updateHeaderSize +
outputHeader.BehaviorSize +
outputHeader.MemoryPoolSize +
outputHeader.VoiceSize +
outputHeader.EffectSize +
outputHeader.SinkSize +
outputHeader.PerformanceManagerSize +
outputHeader.ElapsedFrameCountInfoSize;
writer.Write(outputHeader);
foreach (MemoryPoolContext memoryPool in _memoryPools)
{
writer.Write(memoryPool.OutStatus);
}
foreach (VoiceContext voice in _voices)
{
writer.Write(voice.OutStatus);
}
foreach (EffectContext effect in _effects)
{
writer.Write(effect.OutStatus);
}
writer.SkipBytes(_params.SinkCount * 0x20);
writer.SkipBytes(outputHeader.PerformanceManagerSize);
writer.SkipBytes(outputHeader.BehaviorSize);
if (behaviorInfo.IsElapsedFrameCountSupported())
{
writer.Write(new RendererInfoOut
{
ElapsedFrameCount = _elapsedFrameCount
});
}
return ResultCode.Success;
}
[Command(5)]
// Start()
public ResultCode StartAudioRenderer(ServiceCtx context)
{
Logger.PrintStub(LogClass.ServiceAudio);
_playState = PlayState.Playing;
return ResultCode.Success;
}
[Command(6)]
// Stop()
public ResultCode StopAudioRenderer(ServiceCtx context)
{
Logger.PrintStub(LogClass.ServiceAudio);
_playState = PlayState.Stopped;
return ResultCode.Success;
}
[Command(7)]
// QuerySystemEvent() -> handle<copy, event>
public ResultCode QuerySystemEvent(ServiceCtx context)
{
if (context.Process.HandleTable.GenerateHandle(_updateEvent.ReadableEvent, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
return ResultCode.Success;
}
private AdpcmDecoderContext GetAdpcmDecoderContext(long position, long size)
{
if (size == 0)
{
return null;
}
AdpcmDecoderContext context = new AdpcmDecoderContext
{
Coefficients = new short[size >> 1]
};
for (int offset = 0; offset < size; offset += 2)
{
context.Coefficients[offset >> 1] = _memory.ReadInt16(position + offset);
}
return context;
}
private void UpdateAudio()
{
long[] released = _audioOut.GetReleasedBuffers(_track, 2);
for (int index = 0; index < released.Length; index++)
{
AppendMixedBuffer(released[index]);
}
_elapsedFrameCount++;
}
private void AppendMixedBuffer(long tag)
{
int[] mixBuffer = new int[MixBufferSamplesCount * AudioRendererConsts.HostChannelsCount];
foreach (VoiceContext voice in _voices)
{
if (!voice.Playing || voice.CurrentWaveBuffer.Size == 0)
{
continue;
}
int outOffset = 0;
int pendingSamples = MixBufferSamplesCount;
while (pendingSamples > 0)
{
int[] samples = voice.GetBufferData(_memory, pendingSamples, out int returnedSamples);
if (returnedSamples == 0)
{
break;
}
pendingSamples -= returnedSamples;
for (int offset = 0; offset < samples.Length; offset++)
{
mixBuffer[outOffset++] += (int)(samples[offset] * voice.Volume);
}
}
}
_audioOut.AppendBuffer(_track, tag, GetFinalBuffer(mixBuffer));
}
private unsafe static short[] GetFinalBuffer(int[] buffer)
{
short[] output = new short[buffer.Length];
int offset = 0;
// Perform Saturation using SSE2 if supported
if (Sse2.IsSupported)
{
fixed (int* inptr = buffer)
fixed (short* outptr = output)
{
for (; offset + 32 <= buffer.Length; offset += 32)
{
// Unroll the loop a little to ensure the CPU pipeline
// is always full.
Vector128<int> block1A = Sse2.LoadVector128(inptr + offset + 0);
Vector128<int> block1B = Sse2.LoadVector128(inptr + offset + 4);
Vector128<int> block2A = Sse2.LoadVector128(inptr + offset + 8);
Vector128<int> block2B = Sse2.LoadVector128(inptr + offset + 12);
Vector128<int> block3A = Sse2.LoadVector128(inptr + offset + 16);
Vector128<int> block3B = Sse2.LoadVector128(inptr + offset + 20);
Vector128<int> block4A = Sse2.LoadVector128(inptr + offset + 24);
Vector128<int> block4B = Sse2.LoadVector128(inptr + offset + 28);
Vector128<short> output1 = Sse2.PackSignedSaturate(block1A, block1B);
Vector128<short> output2 = Sse2.PackSignedSaturate(block2A, block2B);
Vector128<short> output3 = Sse2.PackSignedSaturate(block3A, block3B);
Vector128<short> output4 = Sse2.PackSignedSaturate(block4A, block4B);
Sse2.Store(outptr + offset + 0, output1);
Sse2.Store(outptr + offset + 8, output2);
Sse2.Store(outptr + offset + 16, output3);
Sse2.Store(outptr + offset + 24, output4);
}
}
}
// Process left overs
for (; offset < buffer.Length; offset++)
{
output[offset] = DspUtils.Saturate(buffer[offset]);
}
return output;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_audioOut.CloseTrack(_track);
}
}
}
}