Merge branch 'master' into turbo

This commit is contained in:
Nicolas Abram 2024-04-16 21:22:32 -03:00 committed by GitHub
commit ef781cb32a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
214 changed files with 6283 additions and 3515 deletions

View file

@ -13,14 +13,14 @@
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" /> <PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" /> <PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="DynamicData" Version="8.3.27" /> <PackageVersion Include="DynamicData" Version="8.4.1" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" /> <PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" /> <PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" /> <PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
<PackageVersion Include="LibHac" Version="0.19.0" /> <PackageVersion Include="LibHac" Version="0.19.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" /> <PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.4.0" /> <PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.5.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" /> <PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" /> <PackageVersion Include="MsgPack.Cli" Version="1.0.1" />

View file

@ -33,8 +33,3 @@ Project Docs
================= =================
To be added. Many project files will contain basic XML docs for key functions and classes in the meantime. To be added. Many project files will contain basic XML docs for key functions and classes in the meantime.
Other Information
=================
- N/A

View file

@ -19,6 +19,12 @@ namespace ARMeilleure.Instructions
Operand n = GetAluN(context); Operand n = GetAluN(context);
Operand m = GetAluM(context, setCarry: false); Operand m = GetAluM(context, setCarry: false);
if (op.Rn == RegisterAlias.Aarch32Pc && op is OpCodeT32AluImm12)
{
// For ADR, PC is always 4 bytes aligned, even in Thumb mode.
n = context.BitwiseAnd(n, Const(~3u));
}
Operand res = context.Add(n, m); Operand res = context.Add(n, m);
if (ShouldSetFlags(context)) if (ShouldSetFlags(context))
@ -467,6 +473,12 @@ namespace ARMeilleure.Instructions
Operand n = GetAluN(context); Operand n = GetAluN(context);
Operand m = GetAluM(context, setCarry: false); Operand m = GetAluM(context, setCarry: false);
if (op.Rn == RegisterAlias.Aarch32Pc && op is OpCodeT32AluImm12)
{
// For ADR, PC is always 4 bytes aligned, even in Thumb mode.
n = context.BitwiseAnd(n, Const(~3u));
}
Operand res = context.Subtract(n, m); Operand res = context.Subtract(n, m);
if (ShouldSetFlags(context)) if (ShouldSetFlags(context))

View file

@ -2426,7 +2426,11 @@ namespace ARMeilleure.Instructions
} }
else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0)
{ {
Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtss, GetVec(op.Rn)), scalar: true); // RSQRTSS handles subnormals as zero, which differs from Arm, so we can't use it here.
Operand res = context.AddIntrinsic(Intrinsic.X86Sqrtss, GetVec(op.Rn));
res = context.AddIntrinsic(Intrinsic.X86Rcpss, res);
res = EmitSse41Round32Exp8OpF(context, res, scalar: true);
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
} }
@ -2451,7 +2455,11 @@ namespace ARMeilleure.Instructions
} }
else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0)
{ {
Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtps, GetVec(op.Rn)), scalar: false); // RSQRTPS handles subnormals as zero, which differs from Arm, so we can't use it here.
Operand res = context.AddIntrinsic(Intrinsic.X86Sqrtps, GetVec(op.Rn));
res = context.AddIntrinsic(Intrinsic.X86Rcpps, res);
res = EmitSse41Round32Exp8OpF(context, res, scalar: false);
if (op.RegisterSize == RegisterSize.Simd64) if (op.RegisterSize == RegisterSize.Simd64)
{ {

View file

@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 5518; //! To be incremented manually for each change to the ARMeilleure project. private const uint InternalVersion = 6634; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0"; private const string ActualDir = "0";
private const string BackupDir = "1"; private const string BackupDir = "1";

View file

@ -1,5 +1,7 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers;
namespace Ryujinx.Audio.Backends.Common namespace Ryujinx.Audio.Backends.Common
{ {
@ -12,7 +14,8 @@ namespace Ryujinx.Audio.Backends.Common
private readonly object _lock = new(); private readonly object _lock = new();
private byte[] _buffer; private IMemoryOwner<byte> _bufferOwner;
private Memory<byte> _buffer;
private int _size; private int _size;
private int _headOffset; private int _headOffset;
private int _tailOffset; private int _tailOffset;
@ -21,7 +24,8 @@ namespace Ryujinx.Audio.Backends.Common
public DynamicRingBuffer(int initialCapacity = RingBufferAlignment) public DynamicRingBuffer(int initialCapacity = RingBufferAlignment)
{ {
_buffer = new byte[initialCapacity]; _bufferOwner = ByteMemoryPool.RentCleared(initialCapacity);
_buffer = _bufferOwner.Memory;
} }
public void Clear() public void Clear()
@ -33,6 +37,11 @@ namespace Ryujinx.Audio.Backends.Common
public void Clear(int size) public void Clear(int size)
{ {
if (size == 0)
{
return;
}
lock (_lock) lock (_lock)
{ {
if (size > _size) if (size > _size)
@ -40,11 +49,6 @@ namespace Ryujinx.Audio.Backends.Common
size = _size; size = _size;
} }
if (size == 0)
{
return;
}
_headOffset = (_headOffset + size) % _buffer.Length; _headOffset = (_headOffset + size) % _buffer.Length;
_size -= size; _size -= size;
@ -58,28 +62,31 @@ namespace Ryujinx.Audio.Backends.Common
private void SetCapacityLocked(int capacity) private void SetCapacityLocked(int capacity)
{ {
byte[] buffer = new byte[capacity]; IMemoryOwner<byte> newBufferOwner = ByteMemoryPool.RentCleared(capacity);
Memory<byte> newBuffer = newBufferOwner.Memory;
if (_size > 0) if (_size > 0)
{ {
if (_headOffset < _tailOffset) if (_headOffset < _tailOffset)
{ {
Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _size); _buffer.Slice(_headOffset, _size).CopyTo(newBuffer);
} }
else else
{ {
Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _buffer.Length - _headOffset); _buffer[_headOffset..].CopyTo(newBuffer);
Buffer.BlockCopy(_buffer, 0, buffer, _buffer.Length - _headOffset, _tailOffset); _buffer[.._tailOffset].CopyTo(newBuffer[(_buffer.Length - _headOffset)..]);
} }
} }
_buffer = buffer; _bufferOwner.Dispose();
_bufferOwner = newBufferOwner;
_buffer = newBuffer;
_headOffset = 0; _headOffset = 0;
_tailOffset = _size; _tailOffset = _size;
} }
public void Write(ReadOnlySpan<byte> buffer, int index, int count)
public void Write<T>(T[] buffer, int index, int count)
{ {
if (count == 0) if (count == 0)
{ {
@ -99,17 +106,17 @@ namespace Ryujinx.Audio.Backends.Common
if (tailLength >= count) if (tailLength >= count)
{ {
Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count); buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]);
} }
else else
{ {
Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, tailLength); buffer.Slice(index, tailLength).CopyTo(_buffer.Span[_tailOffset..]);
Buffer.BlockCopy(buffer, index + tailLength, _buffer, 0, count - tailLength); buffer.Slice(index + tailLength, count - tailLength).CopyTo(_buffer.Span);
} }
} }
else else
{ {
Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count); buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]);
} }
_size += count; _size += count;
@ -117,8 +124,13 @@ namespace Ryujinx.Audio.Backends.Common
} }
} }
public int Read<T>(T[] buffer, int index, int count) public int Read(Span<byte> buffer, int index, int count)
{ {
if (count == 0)
{
return 0;
}
lock (_lock) lock (_lock)
{ {
if (count > _size) if (count > _size)
@ -126,14 +138,9 @@ namespace Ryujinx.Audio.Backends.Common
count = _size; count = _size;
} }
if (count == 0)
{
return 0;
}
if (_headOffset < _tailOffset) if (_headOffset < _tailOffset)
{ {
Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count); _buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]);
} }
else else
{ {
@ -141,12 +148,12 @@ namespace Ryujinx.Audio.Backends.Common
if (tailLength >= count) if (tailLength >= count)
{ {
Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count); _buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]);
} }
else else
{ {
Buffer.BlockCopy(_buffer, _headOffset, buffer, index, tailLength); _buffer.Span.Slice(_headOffset, tailLength).CopyTo(buffer[index..]);
Buffer.BlockCopy(_buffer, 0, buffer, index + tailLength, count - tailLength); _buffer.Span[..(count - tailLength)].CopyTo(buffer[(index + tailLength)..]);
} }
} }

View file

@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Common
public ulong Flags; public ulong Flags;
/// <summary> /// <summary>
/// Represents an error during <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.ReadOnlyMemory{byte})"/>. /// Represents an error during <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.Buffers.ReadOnlySequence{byte})"/>.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ErrorInfo public struct ErrorInfo

View file

@ -4,7 +4,7 @@ using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Common namespace Ryujinx.Audio.Renderer.Common
{ {
/// <summary> /// <summary>
/// Update data header used for input and output of <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.ReadOnlyMemory{byte})"/>. /// Update data header used for input and output of <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.Buffers.ReadOnlySequence{byte})"/>.
/// </summary> /// </summary>
public struct UpdateDataHeader public struct UpdateDataHeader
{ {

View file

@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
/// <summary> /// <summary>
/// Output information for behaviour. /// Output information for behaviour.
/// </summary> /// </summary>
/// <remarks>This is used to report errors to the user during <see cref="Server.AudioRenderSystem.Update(Memory{byte}, Memory{byte}, ReadOnlyMemory{byte})"/> processing.</remarks> /// <remarks>This is used to report errors to the user during <see cref="Server.AudioRenderSystem.Update(Memory{byte}, Memory{byte}, System.Buffers.ReadOnlySequence{byte})"/> processing.</remarks>
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BehaviourErrorInfoOutStatus public struct BehaviourErrorInfoOutStatus
{ {

View file

@ -386,7 +386,7 @@ namespace Ryujinx.Audio.Renderer.Server
} }
} }
public ResultCode Update(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input) public ResultCode Update(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlySequence<byte> input)
{ {
lock (_lock) lock (_lock)
{ {
@ -419,14 +419,16 @@ namespace Ryujinx.Audio.Renderer.Server
return result; return result;
} }
result = stateUpdater.UpdateVoices(_voiceContext, _memoryPools); PoolMapper poolMapper = new PoolMapper(_processHandle, _memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
result = stateUpdater.UpdateVoices(_voiceContext, poolMapper);
if (result != ResultCode.Success) if (result != ResultCode.Success)
{ {
return result; return result;
} }
result = stateUpdater.UpdateEffects(_effectContext, _isActive, _memoryPools); result = stateUpdater.UpdateEffects(_effectContext, _isActive, poolMapper);
if (result != ResultCode.Success) if (result != ResultCode.Success)
{ {
@ -450,7 +452,7 @@ namespace Ryujinx.Audio.Renderer.Server
return result; return result;
} }
result = stateUpdater.UpdateSinks(_sinkContext, _memoryPools); result = stateUpdater.UpdateSinks(_sinkContext, poolMapper);
if (result != ResultCode.Success) if (result != ResultCode.Success)
{ {

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
@ -273,7 +274,7 @@ namespace Ryujinx.Audio.Renderer.Server
} }
/// <summary> /// <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})"/>. /// Check if the audio renderer should trust the user destination count in <see cref="Renderer.Server.Splitter.SplitterState.Update(Renderer.Server.Splitter.SplitterContext, Renderer.Parameter.SplitterInParameter, SequenceReader{byte})"/>.
/// </summary> /// </summary>
/// <returns>True if the audio renderer should trust the user destination count.</returns> /// <returns>True if the audio renderer should trust the user destination count.</returns>
public bool IsSplitterBugFixed() public bool IsSplitterBugFixed()

View file

@ -33,21 +33,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return WorkBuffers[index].GetReference(true); return WorkBuffers[index].GetReference(true);
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{ {
Debug.Assert(IsTypeValid(ref parameter)); Debug.Assert(IsTypeValid(in parameter));
UpdateParameterBase(ref parameter); UpdateParameterBase(in parameter);
Parameter = MemoryMarshal.Cast<byte, AuxiliaryBufferParameter>(parameter.SpecificData)[0]; Parameter = MemoryMarshal.Cast<byte, AuxiliaryBufferParameter>(parameter.SpecificData)[0];
IsEnabled = parameter.IsEnabled; IsEnabled = parameter.IsEnabled;

View file

@ -81,7 +81,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
/// </summary> /// </summary>
/// <param name="parameter">The user parameter.</param> /// <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> /// <returns>Returns true if the <see cref="EffectType"/> sent by the user matches the internal <see cref="EffectType"/>.</returns>
public bool IsTypeValid<T>(ref T parameter) where T : unmanaged, IEffectInParameter public bool IsTypeValid<T>(in T parameter) where T : unmanaged, IEffectInParameter
{ {
return parameter.Type == TargetEffectType; return parameter.Type == TargetEffectType;
} }
@ -98,7 +98,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
/// Update the internal common parameters from a user parameter. /// Update the internal common parameters from a user parameter.
/// </summary> /// </summary>
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
protected void UpdateParameterBase<T>(ref T parameter) where T : unmanaged, IEffectInParameter protected void UpdateParameterBase<T>(in T parameter) where T : unmanaged, IEffectInParameter
{ {
MixId = parameter.MixId; MixId = parameter.MixId;
ProcessingOrder = parameter.ProcessingOrder; ProcessingOrder = parameter.ProcessingOrder;
@ -139,7 +139,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
/// <summary> /// <summary>
/// Initialize the given <paramref name="state"/> result state. /// Initialize the given <paramref name="state"/> result state.
/// </summary> /// </summary>
/// <param name="state">The state to initalize</param> /// <param name="state">The state to initialize</param>
public virtual void InitializeResultState(ref EffectResultState state) { } public virtual void InitializeResultState(ref EffectResultState state) { }
/// <summary> /// <summary>
@ -155,9 +155,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
/// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param> /// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
/// <param name="mapper">The mapper to use.</param> /// <param name="mapper">The mapper to use.</param>
public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) public virtual void Update(out ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
{ {
Debug.Assert(IsTypeValid(ref parameter)); Debug.Assert(IsTypeValid(in parameter));
updateErrorInfo = new ErrorInfo(); updateErrorInfo = new ErrorInfo();
} }
@ -168,9 +168,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
/// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param> /// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
/// <param name="mapper">The mapper to use.</param> /// <param name="mapper">The mapper to use.</param>
public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) public virtual void Update(out ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
{ {
Debug.Assert(IsTypeValid(ref parameter)); Debug.Assert(IsTypeValid(in parameter));
updateErrorInfo = new ErrorInfo(); updateErrorInfo = new ErrorInfo();
} }

View file

@ -35,21 +35,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
public override EffectType TargetEffectType => EffectType.BiquadFilter; public override EffectType TargetEffectType => EffectType.BiquadFilter;
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{ {
Debug.Assert(IsTypeValid(ref parameter)); Debug.Assert(IsTypeValid(in parameter));
UpdateParameterBase(ref parameter); UpdateParameterBase(in parameter);
Parameter = MemoryMarshal.Cast<byte, BiquadFilterEffectParameter>(parameter.SpecificData)[0]; Parameter = MemoryMarshal.Cast<byte, BiquadFilterEffectParameter>(parameter.SpecificData)[0];
IsEnabled = parameter.IsEnabled; IsEnabled = parameter.IsEnabled;

View file

@ -19,21 +19,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
public override EffectType TargetEffectType => EffectType.BufferMix; public override EffectType TargetEffectType => EffectType.BufferMix;
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{ {
Debug.Assert(IsTypeValid(ref parameter)); Debug.Assert(IsTypeValid(in parameter));
UpdateParameterBase(ref parameter); UpdateParameterBase(in parameter);
Parameter = MemoryMarshal.Cast<byte, BufferMixParameter>(parameter.SpecificData)[0]; Parameter = MemoryMarshal.Cast<byte, BufferMixParameter>(parameter.SpecificData)[0];
IsEnabled = parameter.IsEnabled; IsEnabled = parameter.IsEnabled;

View file

@ -32,21 +32,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return WorkBuffers[index].GetReference(true); return WorkBuffers[index].GetReference(true);
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{ {
Debug.Assert(IsTypeValid(ref parameter)); Debug.Assert(IsTypeValid(in parameter));
UpdateParameterBase(ref parameter); UpdateParameterBase(in parameter);
Parameter = MemoryMarshal.Cast<byte, AuxiliaryBufferParameter>(parameter.SpecificData)[0]; Parameter = MemoryMarshal.Cast<byte, AuxiliaryBufferParameter>(parameter.SpecificData)[0];
IsEnabled = parameter.IsEnabled; IsEnabled = parameter.IsEnabled;

View file

@ -39,17 +39,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return GetSingleBuffer(); return GetSingleBuffer();
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
{ {
// Nintendo doesn't do anything here but we still require updateErrorInfo to be initialised. // Nintendo doesn't do anything here but we still require updateErrorInfo to be initialised.
updateErrorInfo = new BehaviourParameter.ErrorInfo(); updateErrorInfo = new BehaviourParameter.ErrorInfo();
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
{ {
Debug.Assert(IsTypeValid(ref parameter)); Debug.Assert(IsTypeValid(in parameter));
UpdateParameterBase(ref parameter); UpdateParameterBase(in parameter);
Parameter = MemoryMarshal.Cast<byte, CompressorParameter>(parameter.SpecificData)[0]; Parameter = MemoryMarshal.Cast<byte, CompressorParameter>(parameter.SpecificData)[0];
IsEnabled = parameter.IsEnabled; IsEnabled = parameter.IsEnabled;

View file

@ -37,19 +37,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return GetSingleBuffer(); return GetSingleBuffer();
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{ {
Debug.Assert(IsTypeValid(ref parameter)); Debug.Assert(IsTypeValid(in parameter));
ref DelayParameter delayParameter = ref MemoryMarshal.Cast<byte, DelayParameter>(parameter.SpecificData)[0]; ref DelayParameter delayParameter = ref MemoryMarshal.Cast<byte, DelayParameter>(parameter.SpecificData)[0];
@ -57,7 +57,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
if (delayParameter.IsChannelCountMaxValid()) if (delayParameter.IsChannelCountMaxValid())
{ {
UpdateParameterBase(ref parameter); UpdateParameterBase(in parameter);
UsageState oldParameterStatus = Parameter.Status; UsageState oldParameterStatus = Parameter.Status;

View file

@ -39,25 +39,25 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return GetSingleBuffer(); return GetSingleBuffer();
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{ {
Debug.Assert(IsTypeValid(ref parameter)); Debug.Assert(IsTypeValid(in parameter));
ref LimiterParameter limiterParameter = ref MemoryMarshal.Cast<byte, LimiterParameter>(parameter.SpecificData)[0]; ref LimiterParameter limiterParameter = ref MemoryMarshal.Cast<byte, LimiterParameter>(parameter.SpecificData)[0];
updateErrorInfo = new BehaviourParameter.ErrorInfo(); updateErrorInfo = new BehaviourParameter.ErrorInfo();
UpdateParameterBase(ref parameter); UpdateParameterBase(in parameter);
Parameter = limiterParameter; Parameter = limiterParameter;

View file

@ -36,19 +36,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return GetSingleBuffer(); return GetSingleBuffer();
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{ {
Debug.Assert(IsTypeValid(ref parameter)); Debug.Assert(IsTypeValid(in parameter));
ref Reverb3dParameter reverbParameter = ref MemoryMarshal.Cast<byte, Reverb3dParameter>(parameter.SpecificData)[0]; ref Reverb3dParameter reverbParameter = ref MemoryMarshal.Cast<byte, Reverb3dParameter>(parameter.SpecificData)[0];
@ -56,7 +56,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
if (reverbParameter.IsChannelCountMaxValid()) if (reverbParameter.IsChannelCountMaxValid())
{ {
UpdateParameterBase(ref parameter); UpdateParameterBase(in parameter);
UsageState oldParameterStatus = Parameter.ParameterStatus; UsageState oldParameterStatus = Parameter.ParameterStatus;

View file

@ -39,19 +39,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return GetSingleBuffer(); return GetSingleBuffer();
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
{ {
Update(out updateErrorInfo, ref parameter, mapper); Update(out updateErrorInfo, in parameter, mapper);
} }
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{ {
Debug.Assert(IsTypeValid(ref parameter)); Debug.Assert(IsTypeValid(in parameter));
ref ReverbParameter reverbParameter = ref MemoryMarshal.Cast<byte, ReverbParameter>(parameter.SpecificData)[0]; ref ReverbParameter reverbParameter = ref MemoryMarshal.Cast<byte, ReverbParameter>(parameter.SpecificData)[0];
@ -59,7 +59,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
if (reverbParameter.IsChannelCountMaxValid()) if (reverbParameter.IsChannelCountMaxValid())
{ {
UpdateParameterBase(ref parameter); UpdateParameterBase(in parameter);
UsageState oldParameterStatus = Parameter.Status; UsageState oldParameterStatus = Parameter.Status;

View file

@ -249,7 +249,7 @@ namespace Ryujinx.Audio.Renderer.Server.MemoryPool
/// <param name="inParameter">Input user parameter.</param> /// <param name="inParameter">Input user parameter.</param>
/// <param name="outStatus">Output user parameter.</param> /// <param name="outStatus">Output user parameter.</param>
/// <returns>Returns the <see cref="UpdateResult"/> of the operations performed.</returns> /// <returns>Returns the <see cref="UpdateResult"/> of the operations performed.</returns>
public UpdateResult Update(ref MemoryPoolState memoryPool, ref MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus) public UpdateResult Update(ref MemoryPoolState memoryPool, in MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus)
{ {
MemoryPoolUserState inputState = inParameter.State; MemoryPoolUserState inputState = inParameter.State;

View file

@ -195,7 +195,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
/// <param name="parameter">The input parameter of the mix.</param> /// <param name="parameter">The input parameter of the mix.</param>
/// <param name="splitterContext">The splitter context.</param> /// <param name="splitterContext">The splitter context.</param>
/// <returns>Return true, new connections were done on the adjacency matrix.</returns> /// <returns>Return true, new connections were done on the adjacency matrix.</returns>
private bool UpdateConnection(EdgeMatrix edgeMatrix, ref MixParameter parameter, ref SplitterContext splitterContext) private bool UpdateConnection(EdgeMatrix edgeMatrix, in MixParameter parameter, ref SplitterContext splitterContext)
{ {
bool hasNewConnections; bool hasNewConnections;
@ -259,7 +259,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
/// <param name="splitterContext">The splitter context.</param> /// <param name="splitterContext">The splitter context.</param>
/// <param name="behaviourContext">The behaviour context.</param> /// <param name="behaviourContext">The behaviour context.</param>
/// <returns>Return true if the mix was changed.</returns> /// <returns>Return true if the mix was changed.</returns>
public bool Update(EdgeMatrix edgeMatrix, ref MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext) public bool Update(EdgeMatrix edgeMatrix, in MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext)
{ {
bool isDirty; bool isDirty;
@ -273,7 +273,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
if (behaviourContext.IsSplitterSupported()) if (behaviourContext.IsSplitterSupported())
{ {
isDirty = UpdateConnection(edgeMatrix, ref parameter, ref splitterContext); isDirty = UpdateConnection(edgeMatrix, in parameter, ref splitterContext);
} }
else else
{ {

View file

@ -59,7 +59,7 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
/// </summary> /// </summary>
/// <param name="parameter">The user parameter.</param> /// <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> /// <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) public bool IsTypeValid(in SinkInParameter parameter)
{ {
return parameter.Type == TargetSinkType; return parameter.Type == TargetSinkType;
} }
@ -76,7 +76,7 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
/// Update the internal common parameters from user parameter. /// Update the internal common parameters from user parameter.
/// </summary> /// </summary>
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
protected void UpdateStandardParameter(ref SinkInParameter parameter) protected void UpdateStandardParameter(in SinkInParameter parameter)
{ {
if (IsUsed != parameter.IsUsed) if (IsUsed != parameter.IsUsed)
{ {
@ -92,9 +92,9 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
/// <param name="outStatus">The user output status.</param> /// <param name="outStatus">The user output status.</param>
/// <param name="mapper">The mapper to use.</param> /// <param name="mapper">The mapper to use.</param>
public virtual void Update(out ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) public virtual void Update(out ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
{ {
Debug.Assert(IsTypeValid(ref parameter)); Debug.Assert(IsTypeValid(in parameter));
errorInfo = new ErrorInfo(); errorInfo = new ErrorInfo();
} }

View file

@ -44,18 +44,18 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
public override SinkType TargetSinkType => SinkType.CircularBuffer; public override SinkType TargetSinkType => SinkType.CircularBuffer;
public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
{ {
errorInfo = new BehaviourParameter.ErrorInfo(); errorInfo = new BehaviourParameter.ErrorInfo();
outStatus = new SinkOutStatus(); outStatus = new SinkOutStatus();
Debug.Assert(IsTypeValid(ref parameter)); Debug.Assert(IsTypeValid(in parameter));
ref CircularBufferParameter inputDeviceParameter = ref MemoryMarshal.Cast<byte, CircularBufferParameter>(parameter.SpecificData)[0]; ref CircularBufferParameter inputDeviceParameter = ref MemoryMarshal.Cast<byte, CircularBufferParameter>(parameter.SpecificData)[0];
if (parameter.IsUsed != IsUsed || ShouldSkip) if (parameter.IsUsed != IsUsed || ShouldSkip)
{ {
UpdateStandardParameter(ref parameter); UpdateStandardParameter(in parameter);
if (parameter.IsUsed) if (parameter.IsUsed)
{ {

View file

@ -49,15 +49,15 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
public override SinkType TargetSinkType => SinkType.Device; public override SinkType TargetSinkType => SinkType.Device;
public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) public override void Update(out BehaviourParameter.ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
{ {
Debug.Assert(IsTypeValid(ref parameter)); Debug.Assert(IsTypeValid(in parameter));
ref DeviceParameter inputDeviceParameter = ref MemoryMarshal.Cast<byte, DeviceParameter>(parameter.SpecificData)[0]; ref DeviceParameter inputDeviceParameter = ref MemoryMarshal.Cast<byte, DeviceParameter>(parameter.SpecificData)[0];
if (parameter.IsUsed != IsUsed) if (parameter.IsUsed != IsUsed)
{ {
UpdateStandardParameter(ref parameter); UpdateStandardParameter(in parameter);
Parameter = inputDeviceParameter; Parameter = inputDeviceParameter;
} }
else else

View file

@ -2,10 +2,11 @@ using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Utils; using Ryujinx.Audio.Renderer.Utils;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Extensions;
using System; using System;
using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Server.Splitter namespace Ryujinx.Audio.Renderer.Server.Splitter
{ {
@ -25,7 +26,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
private Memory<SplitterDestination> _splitterDestinations; private Memory<SplitterDestination> _splitterDestinations;
/// <summary> /// <summary>
/// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, ref SplitterInParameter, ReadOnlySpan{byte})"/>. /// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.
/// </summary> /// </summary>
public bool IsBugFixed { get; private set; } public bool IsBugFixed { get; private set; }
@ -110,7 +111,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// </summary> /// </summary>
/// <param name="splitters">The <see cref="SplitterState"/> storage.</param> /// <param name="splitters">The <see cref="SplitterState"/> storage.</param>
/// <param name="splitterDestinations">The <see cref="SplitterDestination"/> 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> /// <param name="isBugFixed">If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.</param>
private void Setup(Memory<SplitterState> splitters, Memory<SplitterDestination> splitterDestinations, bool isBugFixed) private void Setup(Memory<SplitterState> splitters, Memory<SplitterDestination> splitterDestinations, bool isBugFixed)
{ {
_splitters = splitters; _splitters = splitters;
@ -148,11 +149,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// </summary> /// </summary>
/// <param name="inputHeader">The splitter header.</param> /// <param name="inputHeader">The splitter header.</param>
/// <param name="input">The raw data after the splitter header.</param> /// <param name="input">The raw data after the splitter header.</param>
private void UpdateState(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan<byte> input) private void UpdateState(in SplitterInParameterHeader inputHeader, ref SequenceReader<byte> input)
{ {
for (int i = 0; i < inputHeader.SplitterCount; i++) for (int i = 0; i < inputHeader.SplitterCount; i++)
{ {
SplitterInParameter parameter = MemoryMarshal.Read<SplitterInParameter>(input); ref readonly SplitterInParameter parameter = ref input.GetRefOrRefToCopy<SplitterInParameter>(out _);
Debug.Assert(parameter.IsMagicValid()); Debug.Assert(parameter.IsMagicValid());
@ -162,10 +163,16 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{ {
ref SplitterState splitter = ref GetState(parameter.Id); ref SplitterState splitter = ref GetState(parameter.Id);
splitter.Update(this, ref parameter, input[Unsafe.SizeOf<SplitterInParameter>()..]); splitter.Update(this, in parameter, ref input);
} }
input = input[(0x1C + parameter.DestinationCount * 4)..]; // NOTE: there are 12 bytes of unused/unknown data after the destination IDs array.
input.Advance(0xC);
}
else
{
input.Rewind(Unsafe.SizeOf<SplitterInParameter>());
break;
} }
} }
} }
@ -175,11 +182,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// </summary> /// </summary>
/// <param name="inputHeader">The splitter header.</param> /// <param name="inputHeader">The splitter header.</param>
/// <param name="input">The raw data after the splitter header.</param> /// <param name="input">The raw data after the splitter header.</param>
private void UpdateData(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan<byte> input) private void UpdateData(in SplitterInParameterHeader inputHeader, ref SequenceReader<byte> input)
{ {
for (int i = 0; i < inputHeader.SplitterDestinationCount; i++) for (int i = 0; i < inputHeader.SplitterDestinationCount; i++)
{ {
SplitterDestinationInParameter parameter = MemoryMarshal.Read<SplitterDestinationInParameter>(input); ref readonly SplitterDestinationInParameter parameter = ref input.GetRefOrRefToCopy<SplitterDestinationInParameter>(out _);
Debug.Assert(parameter.IsMagicValid()); Debug.Assert(parameter.IsMagicValid());
@ -191,8 +198,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
destination.Update(parameter); destination.Update(parameter);
} }
}
input = input[Unsafe.SizeOf<SplitterDestinationInParameter>()..]; else
{
input.Rewind(Unsafe.SizeOf<SplitterDestinationInParameter>());
break;
} }
} }
} }
@ -201,36 +211,33 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// Update splitter from user parameters. /// Update splitter from user parameters.
/// </summary> /// </summary>
/// <param name="input">The input raw user data.</param> /// <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> /// <returns>Return true if the update was successful.</returns>
public bool Update(ReadOnlySpan<byte> input, out int consumedSize) public bool Update(ref SequenceReader<byte> input)
{ {
if (_splitterDestinations.IsEmpty || _splitters.IsEmpty) if (_splitterDestinations.IsEmpty || _splitters.IsEmpty)
{ {
consumedSize = 0;
return true; return true;
} }
int originalSize = input.Length; ref readonly SplitterInParameterHeader header = ref input.GetRefOrRefToCopy<SplitterInParameterHeader>(out _);
SplitterInParameterHeader header = SpanIOHelper.Read<SplitterInParameterHeader>(ref input);
if (header.IsMagicValid()) if (header.IsMagicValid())
{ {
ClearAllNewConnectionFlag(); ClearAllNewConnectionFlag();
UpdateState(ref header, ref input); UpdateState(in header, ref input);
UpdateData(ref header, ref input); UpdateData(in header, ref input);
consumedSize = BitUtils.AlignUp(originalSize - input.Length, 0x10); input.SetConsumed(BitUtils.AlignUp(input.Consumed, 0x10));
return true; return true;
} }
else
{
input.Rewind(Unsafe.SizeOf<SplitterInParameterHeader>());
consumedSize = 0; return false;
}
return false;
} }
/// <summary> /// <summary>

View file

@ -1,4 +1,5 @@
using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Common.Extensions;
using System; using System;
using System.Buffers; using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
@ -122,7 +123,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// <param name="context">The splitter context.</param> /// <param name="context">The splitter context.</param>
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
/// <param name="input">The raw input data after the <paramref name="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) public void Update(SplitterContext context, in SplitterInParameter parameter, ref SequenceReader<byte> input)
{ {
ClearLinks(); ClearLinks();
@ -139,9 +140,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
if (destinationCount > 0) if (destinationCount > 0)
{ {
ReadOnlySpan<int> destinationIds = MemoryMarshal.Cast<byte, int>(input); input.ReadLittleEndian(out int destinationId);
Memory<SplitterDestination> destination = context.GetDestinationMemory(destinationIds[0]); Memory<SplitterDestination> destination = context.GetDestinationMemory(destinationId);
SetDestination(ref destination.Span[0]); SetDestination(ref destination.Span[0]);
@ -149,13 +150,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
for (int i = 1; i < destinationCount; i++) for (int i = 1; i < destinationCount; i++)
{ {
Memory<SplitterDestination> nextDestination = context.GetDestinationMemory(destinationIds[i]); input.ReadLittleEndian(out destinationId);
Memory<SplitterDestination> nextDestination = context.GetDestinationMemory(destinationId);
destination.Span[0].Link(ref nextDestination.Span[0]); destination.Span[0].Link(ref nextDestination.Span[0]);
destination = nextDestination; destination = nextDestination;
} }
} }
if (destinationCount < parameter.DestinationCount)
{
input.Advance((parameter.DestinationCount - destinationCount) * sizeof(int));
}
Debug.Assert(parameter.Id == Id); Debug.Assert(parameter.Id == Id);
if (parameter.Id == Id) if (parameter.Id == Id)

View file

@ -9,41 +9,40 @@ using Ryujinx.Audio.Renderer.Server.Sink;
using Ryujinx.Audio.Renderer.Server.Splitter; using Ryujinx.Audio.Renderer.Server.Splitter;
using Ryujinx.Audio.Renderer.Server.Voice; using Ryujinx.Audio.Renderer.Server.Voice;
using Ryujinx.Audio.Renderer.Utils; using Ryujinx.Audio.Renderer.Utils;
using Ryujinx.Common.Extensions;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using System; using System;
using System.Buffers; using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
namespace Ryujinx.Audio.Renderer.Server namespace Ryujinx.Audio.Renderer.Server
{ {
public class StateUpdater public ref struct StateUpdater
{ {
private readonly ReadOnlyMemory<byte> _inputOrigin; private SequenceReader<byte> _inputReader;
private readonly ReadOnlyMemory<byte> _outputOrigin; private readonly ReadOnlyMemory<byte> _outputOrigin;
private ReadOnlyMemory<byte> _input;
private Memory<byte> _output; private Memory<byte> _output;
private readonly uint _processHandle; private readonly uint _processHandle;
private BehaviourContext _behaviourContext; private BehaviourContext _behaviourContext;
private UpdateDataHeader _inputHeader; private readonly ref readonly UpdateDataHeader _inputHeader;
private readonly Memory<UpdateDataHeader> _outputHeader; private readonly Memory<UpdateDataHeader> _outputHeader;
private ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0]; private readonly ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0];
public StateUpdater(ReadOnlyMemory<byte> input, Memory<byte> output, uint processHandle, BehaviourContext behaviourContext) public StateUpdater(ReadOnlySequence<byte> input, Memory<byte> output, uint processHandle, BehaviourContext behaviourContext)
{ {
_input = input; _inputReader = new SequenceReader<byte>(input);
_inputOrigin = _input;
_output = output; _output = output;
_outputOrigin = _output; _outputOrigin = _output;
_processHandle = processHandle; _processHandle = processHandle;
_behaviourContext = behaviourContext; _behaviourContext = behaviourContext;
_inputHeader = SpanIOHelper.Read<UpdateDataHeader>(ref _input); _inputHeader = ref _inputReader.GetRefOrRefToCopy<UpdateDataHeader>(out _);
_outputHeader = SpanMemoryManager<UpdateDataHeader>.Cast(_output[..Unsafe.SizeOf<UpdateDataHeader>()]); _outputHeader = SpanMemoryManager<UpdateDataHeader>.Cast(_output[..Unsafe.SizeOf<UpdateDataHeader>()]);
OutputHeader.Initialize(_behaviourContext.UserRevision); OutputHeader.Initialize(_behaviourContext.UserRevision);
@ -52,7 +51,7 @@ namespace Ryujinx.Audio.Renderer.Server
public ResultCode UpdateBehaviourContext() public ResultCode UpdateBehaviourContext()
{ {
BehaviourParameter parameter = SpanIOHelper.Read<BehaviourParameter>(ref _input); ref readonly BehaviourParameter parameter = ref _inputReader.GetRefOrRefToCopy<BehaviourParameter>(out _);
if (!BehaviourContext.CheckValidRevision(parameter.UserRevision) || parameter.UserRevision != _behaviourContext.UserRevision) if (!BehaviourContext.CheckValidRevision(parameter.UserRevision) || parameter.UserRevision != _behaviourContext.UserRevision)
{ {
@ -81,11 +80,11 @@ namespace Ryujinx.Audio.Renderer.Server
foreach (ref MemoryPoolState memoryPool in memoryPools) foreach (ref MemoryPoolState memoryPool in memoryPools)
{ {
MemoryPoolInParameter parameter = SpanIOHelper.Read<MemoryPoolInParameter>(ref _input); ref readonly MemoryPoolInParameter parameter = ref _inputReader.GetRefOrRefToCopy<MemoryPoolInParameter>(out _);
ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef<MemoryPoolOutStatus>(ref _output)[0]; ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef<MemoryPoolOutStatus>(ref _output)[0];
PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, ref parameter, ref outStatus); PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, in parameter, ref outStatus);
if (updateResult != PoolMapper.UpdateResult.Success && if (updateResult != PoolMapper.UpdateResult.Success &&
updateResult != PoolMapper.UpdateResult.MapError && updateResult != PoolMapper.UpdateResult.MapError &&
@ -115,7 +114,7 @@ namespace Ryujinx.Audio.Renderer.Server
for (int i = 0; i < context.GetCount(); i++) for (int i = 0; i < context.GetCount(); i++)
{ {
VoiceChannelResourceInParameter parameter = SpanIOHelper.Read<VoiceChannelResourceInParameter>(ref _input); ref readonly VoiceChannelResourceInParameter parameter = ref _inputReader.GetRefOrRefToCopy<VoiceChannelResourceInParameter>(out _);
ref VoiceChannelResource resource = ref context.GetChannelResource(i); ref VoiceChannelResource resource = ref context.GetChannelResource(i);
@ -127,7 +126,7 @@ namespace Ryujinx.Audio.Renderer.Server
return ResultCode.Success; return ResultCode.Success;
} }
public ResultCode UpdateVoices(VoiceContext context, Memory<MemoryPoolState> memoryPools) public ResultCode UpdateVoices(VoiceContext context, PoolMapper mapper)
{ {
if (context.GetCount() * Unsafe.SizeOf<VoiceInParameter>() != _inputHeader.VoicesSize) if (context.GetCount() * Unsafe.SizeOf<VoiceInParameter>() != _inputHeader.VoicesSize)
{ {
@ -136,11 +135,7 @@ namespace Ryujinx.Audio.Renderer.Server
int initialOutputSize = _output.Length; int initialOutputSize = _output.Length;
ReadOnlySpan<VoiceInParameter> parameters = MemoryMarshal.Cast<byte, VoiceInParameter>(_input[..(int)_inputHeader.VoicesSize].Span); long initialInputConsumed = _inputReader.Consumed;
_input = _input[(int)_inputHeader.VoicesSize..];
PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
// First make everything not in use. // First make everything not in use.
for (int i = 0; i < context.GetCount(); i++) for (int i = 0; i < context.GetCount(); i++)
@ -157,7 +152,7 @@ namespace Ryujinx.Audio.Renderer.Server
// Start processing // Start processing
for (int i = 0; i < context.GetCount(); i++) for (int i = 0; i < context.GetCount(); i++)
{ {
VoiceInParameter parameter = parameters[i]; ref readonly VoiceInParameter parameter = ref _inputReader.GetRefOrRefToCopy<VoiceInParameter>(out _);
voiceUpdateStates.Fill(Memory<VoiceUpdateState>.Empty); voiceUpdateStates.Fill(Memory<VoiceUpdateState>.Empty);
@ -181,14 +176,14 @@ namespace Ryujinx.Audio.Renderer.Server
currentVoiceState.Initialize(); currentVoiceState.Initialize();
} }
currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, ref parameter, ref mapper, ref _behaviourContext); currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, in parameter, mapper, ref _behaviourContext);
if (updateParameterError.ErrorCode != ResultCode.Success) if (updateParameterError.ErrorCode != ResultCode.Success)
{ {
_behaviourContext.AppendError(ref updateParameterError); _behaviourContext.AppendError(ref updateParameterError);
} }
currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, ref parameter, voiceUpdateStates, ref mapper, ref _behaviourContext); currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, in parameter, voiceUpdateStates, mapper, ref _behaviourContext);
foreach (ref ErrorInfo errorInfo in waveBufferUpdateErrorInfos.AsSpan()) foreach (ref ErrorInfo errorInfo in waveBufferUpdateErrorInfos.AsSpan())
{ {
@ -198,7 +193,7 @@ namespace Ryujinx.Audio.Renderer.Server
} }
} }
currentVoiceState.WriteOutStatus(ref outStatus, ref parameter, voiceUpdateStates); currentVoiceState.WriteOutStatus(ref outStatus, in parameter, voiceUpdateStates);
} }
} }
@ -211,10 +206,12 @@ namespace Ryujinx.Audio.Renderer.Server
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize); Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize);
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.VoicesSize);
return ResultCode.Success; return ResultCode.Success;
} }
private static void ResetEffect<T>(ref BaseEffect effect, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter private static void ResetEffect<T>(ref BaseEffect effect, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{ {
effect.ForceUnmapBuffers(mapper); effect.ForceUnmapBuffers(mapper);
@ -234,17 +231,17 @@ namespace Ryujinx.Audio.Renderer.Server
}; };
} }
public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools) public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, PoolMapper mapper)
{ {
if (_behaviourContext.IsEffectInfoVersion2Supported()) if (_behaviourContext.IsEffectInfoVersion2Supported())
{ {
return UpdateEffectsVersion2(context, isAudioRendererActive, memoryPools); return UpdateEffectsVersion2(context, isAudioRendererActive, mapper);
} }
return UpdateEffectsVersion1(context, isAudioRendererActive, memoryPools); return UpdateEffectsVersion1(context, isAudioRendererActive, mapper);
} }
public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools) public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, PoolMapper mapper)
{ {
if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion2>() != _inputHeader.EffectsSize) if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion2>() != _inputHeader.EffectsSize)
{ {
@ -253,26 +250,22 @@ namespace Ryujinx.Audio.Renderer.Server
int initialOutputSize = _output.Length; int initialOutputSize = _output.Length;
ReadOnlySpan<EffectInParameterVersion2> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion2>(_input[..(int)_inputHeader.EffectsSize].Span); long initialInputConsumed = _inputReader.Consumed;
_input = _input[(int)_inputHeader.EffectsSize..];
PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
for (int i = 0; i < context.GetCount(); i++) for (int i = 0; i < context.GetCount(); i++)
{ {
EffectInParameterVersion2 parameter = parameters[i]; ref readonly EffectInParameterVersion2 parameter = ref _inputReader.GetRefOrRefToCopy<EffectInParameterVersion2>(out _);
ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion2>(ref _output)[0]; ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion2>(ref _output)[0];
ref BaseEffect effect = ref context.GetEffect(i); ref BaseEffect effect = ref context.GetEffect(i);
if (!effect.IsTypeValid(ref parameter)) if (!effect.IsTypeValid(in parameter))
{ {
ResetEffect(ref effect, ref parameter, mapper); ResetEffect(ref effect, in parameter, mapper);
} }
effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper); effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper);
if (updateErrorInfo.ErrorCode != ResultCode.Success) if (updateErrorInfo.ErrorCode != ResultCode.Success)
{ {
@ -297,10 +290,12 @@ namespace Ryujinx.Audio.Renderer.Server
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize);
return ResultCode.Success; return ResultCode.Success;
} }
public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools) public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, PoolMapper mapper)
{ {
if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion1>() != _inputHeader.EffectsSize) if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion1>() != _inputHeader.EffectsSize)
{ {
@ -309,26 +304,22 @@ namespace Ryujinx.Audio.Renderer.Server
int initialOutputSize = _output.Length; int initialOutputSize = _output.Length;
ReadOnlySpan<EffectInParameterVersion1> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion1>(_input[..(int)_inputHeader.EffectsSize].Span); long initialInputConsumed = _inputReader.Consumed;
_input = _input[(int)_inputHeader.EffectsSize..];
PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
for (int i = 0; i < context.GetCount(); i++) for (int i = 0; i < context.GetCount(); i++)
{ {
EffectInParameterVersion1 parameter = parameters[i]; ref readonly EffectInParameterVersion1 parameter = ref _inputReader.GetRefOrRefToCopy<EffectInParameterVersion1>(out _);
ref EffectOutStatusVersion1 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion1>(ref _output)[0]; ref EffectOutStatusVersion1 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion1>(ref _output)[0];
ref BaseEffect effect = ref context.GetEffect(i); ref BaseEffect effect = ref context.GetEffect(i);
if (!effect.IsTypeValid(ref parameter)) if (!effect.IsTypeValid(in parameter))
{ {
ResetEffect(ref effect, ref parameter, mapper); ResetEffect(ref effect, in parameter, mapper);
} }
effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper); effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper);
if (updateErrorInfo.ErrorCode != ResultCode.Success) if (updateErrorInfo.ErrorCode != ResultCode.Success)
{ {
@ -345,38 +336,40 @@ namespace Ryujinx.Audio.Renderer.Server
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize);
return ResultCode.Success; return ResultCode.Success;
} }
public ResultCode UpdateSplitter(SplitterContext context) public ResultCode UpdateSplitter(SplitterContext context)
{ {
if (context.Update(_input.Span, out int consumedSize)) if (context.Update(ref _inputReader))
{ {
_input = _input[consumedSize..];
return ResultCode.Success; return ResultCode.Success;
} }
return ResultCode.InvalidUpdateInfo; return ResultCode.InvalidUpdateInfo;
} }
private static bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, ReadOnlySpan<MixParameter> parameters) private static bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, SequenceReader<byte> parameters)
{ {
uint maxMixStateCount = mixContext.GetCount(); uint maxMixStateCount = mixContext.GetCount();
uint totalRequiredMixBufferCount = 0; uint totalRequiredMixBufferCount = 0;
for (int i = 0; i < inputMixCount; i++) for (int i = 0; i < inputMixCount; i++)
{ {
if (parameters[i].IsUsed) ref readonly MixParameter parameter = ref parameters.GetRefOrRefToCopy<MixParameter>(out _);
if (parameter.IsUsed)
{ {
if (parameters[i].DestinationMixId != Constants.UnusedMixId && if (parameter.DestinationMixId != Constants.UnusedMixId &&
parameters[i].DestinationMixId > maxMixStateCount && parameter.DestinationMixId > maxMixStateCount &&
parameters[i].MixId != Constants.FinalMixId) parameter.MixId != Constants.FinalMixId)
{ {
return true; return true;
} }
totalRequiredMixBufferCount += parameters[i].BufferCount; totalRequiredMixBufferCount += parameter.BufferCount;
} }
} }
@ -391,7 +384,7 @@ namespace Ryujinx.Audio.Renderer.Server
if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
{ {
MixInParameterDirtyOnlyUpdate parameter = MemoryMarshal.Cast<byte, MixInParameterDirtyOnlyUpdate>(_input.Span)[0]; ref readonly MixInParameterDirtyOnlyUpdate parameter = ref _inputReader.GetRefOrRefToCopy<MixInParameterDirtyOnlyUpdate>(out _);
mixCount = parameter.MixCount; mixCount = parameter.MixCount;
@ -411,25 +404,20 @@ namespace Ryujinx.Audio.Renderer.Server
return ResultCode.InvalidUpdateInfo; return ResultCode.InvalidUpdateInfo;
} }
if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) long initialInputConsumed = _inputReader.Consumed;
{
_input = _input[Unsafe.SizeOf<MixInParameterDirtyOnlyUpdate>()..];
}
ReadOnlySpan<MixParameter> parameters = MemoryMarshal.Cast<byte, MixParameter>(_input.Span[..(int)inputMixSize]); int parameterCount = (int)inputMixSize / Unsafe.SizeOf<MixParameter>();
_input = _input[(int)inputMixSize..]; if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, _inputReader))
if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, parameters))
{ {
return ResultCode.InvalidUpdateInfo; return ResultCode.InvalidUpdateInfo;
} }
bool isMixContextDirty = false; bool isMixContextDirty = false;
for (int i = 0; i < parameters.Length; i++) for (int i = 0; i < parameterCount; i++)
{ {
MixParameter parameter = parameters[i]; ref readonly MixParameter parameter = ref _inputReader.GetRefOrRefToCopy<MixParameter>(out _);
int mixId = i; int mixId = i;
@ -454,7 +442,7 @@ namespace Ryujinx.Audio.Renderer.Server
if (mix.IsUsed) if (mix.IsUsed)
{ {
isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, ref parameter, effectContext, splitterContext, _behaviourContext); isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, in parameter, effectContext, splitterContext, _behaviourContext);
} }
} }
@ -473,10 +461,12 @@ namespace Ryujinx.Audio.Renderer.Server
} }
} }
_inputReader.SetConsumed(initialInputConsumed + inputMixSize);
return ResultCode.Success; return ResultCode.Success;
} }
private static void ResetSink(ref BaseSink sink, ref SinkInParameter parameter) private static void ResetSink(ref BaseSink sink, in SinkInParameter parameter)
{ {
sink.CleanUp(); sink.CleanUp();
@ -489,10 +479,8 @@ namespace Ryujinx.Audio.Renderer.Server
}; };
} }
public ResultCode UpdateSinks(SinkContext context, Memory<MemoryPoolState> memoryPools) public ResultCode UpdateSinks(SinkContext context, PoolMapper mapper)
{ {
PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
if (context.GetCount() * Unsafe.SizeOf<SinkInParameter>() != _inputHeader.SinksSize) if (context.GetCount() * Unsafe.SizeOf<SinkInParameter>() != _inputHeader.SinksSize)
{ {
return ResultCode.InvalidUpdateInfo; return ResultCode.InvalidUpdateInfo;
@ -500,22 +488,20 @@ namespace Ryujinx.Audio.Renderer.Server
int initialOutputSize = _output.Length; int initialOutputSize = _output.Length;
ReadOnlySpan<SinkInParameter> parameters = MemoryMarshal.Cast<byte, SinkInParameter>(_input[..(int)_inputHeader.SinksSize].Span); long initialInputConsumed = _inputReader.Consumed;
_input = _input[(int)_inputHeader.SinksSize..];
for (int i = 0; i < context.GetCount(); i++) for (int i = 0; i < context.GetCount(); i++)
{ {
SinkInParameter parameter = parameters[i]; ref readonly SinkInParameter parameter = ref _inputReader.GetRefOrRefToCopy<SinkInParameter>(out _);
ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef<SinkOutStatus>(ref _output)[0]; ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef<SinkOutStatus>(ref _output)[0];
ref BaseSink sink = ref context.GetSink(i); ref BaseSink sink = ref context.GetSink(i);
if (!sink.IsTypeValid(ref parameter)) if (!sink.IsTypeValid(in parameter))
{ {
ResetSink(ref sink, ref parameter); ResetSink(ref sink, in parameter);
} }
sink.Update(out ErrorInfo updateErrorInfo, ref parameter, ref outStatus, mapper); sink.Update(out ErrorInfo updateErrorInfo, in parameter, ref outStatus, mapper);
if (updateErrorInfo.ErrorCode != ResultCode.Success) if (updateErrorInfo.ErrorCode != ResultCode.Success)
{ {
@ -530,6 +516,8 @@ namespace Ryujinx.Audio.Renderer.Server
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize); Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize);
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.SinksSize);
return ResultCode.Success; return ResultCode.Success;
} }
@ -540,7 +528,7 @@ namespace Ryujinx.Audio.Renderer.Server
return ResultCode.InvalidUpdateInfo; return ResultCode.InvalidUpdateInfo;
} }
PerformanceInParameter parameter = SpanIOHelper.Read<PerformanceInParameter>(ref _input); ref readonly PerformanceInParameter parameter = ref _inputReader.GetRefOrRefToCopy<PerformanceInParameter>(out _);
ref PerformanceOutStatus outStatus = ref SpanIOHelper.GetWriteRef<PerformanceOutStatus>(ref _output)[0]; ref PerformanceOutStatus outStatus = ref SpanIOHelper.GetWriteRef<PerformanceOutStatus>(ref _output)[0];
@ -585,9 +573,9 @@ namespace Ryujinx.Audio.Renderer.Server
return ResultCode.Success; return ResultCode.Success;
} }
public ResultCode CheckConsumedSize() public readonly ResultCode CheckConsumedSize()
{ {
int consumedInputSize = _inputOrigin.Length - _input.Length; long consumedInputSize = _inputReader.Consumed;
int consumedOutputSize = _outputOrigin.Length - _output.Length; int consumedOutputSize = _outputOrigin.Length - _output.Length;
if (consumedInputSize != _inputHeader.TotalSize) if (consumedInputSize != _inputHeader.TotalSize)

View file

@ -254,7 +254,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
/// </summary> /// </summary>
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
/// <returns>Return true, if the server voice information needs to be updated.</returns> /// <returns>Return true, if the server voice information needs to be updated.</returns>
private readonly bool ShouldUpdateParameters(ref VoiceInParameter parameter) private readonly bool ShouldUpdateParameters(in VoiceInParameter parameter)
{ {
if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress) if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress)
{ {
@ -273,7 +273,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
/// <param name="poolMapper">The mapper to use.</param> /// <param name="poolMapper">The mapper to use.</param>
/// <param name="behaviourContext">The behaviour context.</param> /// <param name="behaviourContext">The behaviour context.</param>
public void UpdateParameters(out ErrorInfo outErrorInfo, ref VoiceInParameter parameter, ref PoolMapper poolMapper, ref BehaviourContext behaviourContext) public void UpdateParameters(out ErrorInfo outErrorInfo, in VoiceInParameter parameter, PoolMapper poolMapper, ref BehaviourContext behaviourContext)
{ {
InUse = parameter.InUse; InUse = parameter.InUse;
Id = parameter.Id; Id = parameter.Id;
@ -326,7 +326,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
VoiceDropFlag = false; VoiceDropFlag = false;
} }
if (ShouldUpdateParameters(ref parameter)) if (ShouldUpdateParameters(in parameter))
{ {
DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize); DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize);
} }
@ -380,7 +380,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
/// <param name="outStatus">The given user output.</param> /// <param name="outStatus">The given user output.</param>
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param> /// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates) public void WriteOutStatus(ref VoiceOutStatus outStatus, in VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates)
{ {
#if DEBUG #if DEBUG
// Sanity check in debug mode of the internal state // Sanity check in debug mode of the internal state
@ -426,7 +426,12 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param> /// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
/// <param name="mapper">The mapper to use.</param> /// <param name="mapper">The mapper to use.</param>
/// <param name="behaviourContext">The behaviour context.</param> /// <param name="behaviourContext">The behaviour context.</param>
public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext) public void UpdateWaveBuffers(
out ErrorInfo[] errorInfos,
in VoiceInParameter parameter,
ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates,
PoolMapper mapper,
ref BehaviourContext behaviourContext)
{ {
errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2]; errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2];
@ -444,7 +449,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
for (int i = 0; i < Constants.VoiceWaveBufferCount; i++) for (int i = 0; i < Constants.VoiceWaveBufferCount; i++)
{ {
UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext); UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], mapper, ref behaviourContext);
} }
} }
@ -458,7 +463,14 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
/// <param name="isValid">If set to true, the server side wavebuffer is considered valid.</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="mapper">The mapper to use.</param>
/// <param name="behaviourContext">The behaviour context.</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) private void UpdateWaveBuffer(
Span<ErrorInfo> errorInfos,
ref WaveBuffer waveBuffer,
ref WaveBufferInternal inputWaveBuffer,
SampleFormat sampleFormat,
bool isValid,
PoolMapper mapper,
ref BehaviourContext behaviourContext)
{ {
if (!isValid && waveBuffer.IsSendToAudioProcessor && waveBuffer.BufferAddressInfo.CpuAddress != 0) if (!isValid && waveBuffer.IsSendToAudioProcessor && waveBuffer.BufferAddressInfo.CpuAddress != 0)
{ {

View file

@ -0,0 +1,181 @@
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Common.Extensions
{
public static class SequenceReaderExtensions
{
/// <summary>
/// Dumps the entire <see cref="SequenceReader{byte}"/> to a file, restoring its previous location afterward.
/// Useful for debugging purposes.
/// </summary>
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to write to a file</param>
/// <param name="fileFullName">The path and name of the file to create and dump to</param>
public static void DumpToFile(this ref SequenceReader<byte> reader, string fileFullName)
{
var initialConsumed = reader.Consumed;
reader.Rewind(initialConsumed);
using (var fileStream = System.IO.File.Create(fileFullName, 4096, System.IO.FileOptions.None))
{
while (reader.End == false)
{
var span = reader.CurrentSpan;
fileStream.Write(span);
reader.Advance(span.Length);
}
}
reader.SetConsumed(initialConsumed);
}
/// <summary>
/// Returns a reference to the desired value. This ref should always be used. The argument passed in <paramref name="copyDestinationIfRequiredDoNotUse"/> should never be used, as this is only used for storage if the value
/// must be copied from multiple <see cref="ReadOnlyMemory{Byte}"/> segments held by the <see cref="SequenceReader{Byte}"/>.
/// </summary>
/// <typeparam name="T">Type to get</typeparam>
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
/// <param name="copyDestinationIfRequiredDoNotUse">A location used as storage if (and only if) the value to be read spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments</param>
/// <returns>A reference to the desired value, either directly to memory in the <see cref="SequenceReader{Byte}"/>, or to <paramref name="copyDestinationIfRequiredDoNotUse"/> if it has been used for copying the value in to</returns>
/// <remarks>
/// DO NOT use <paramref name="copyDestinationIfRequiredDoNotUse"/> after calling this method, as it will only
/// contain a value if the value couldn't be referenced directly because it spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments.
/// To discourage use, it is recommended to to call this method like the following:
/// <c>
/// ref readonly MyStruct value = ref sequenceReader.GetRefOrRefToCopy{MyStruct}(out _);
/// </c>
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception>
public static ref readonly T GetRefOrRefToCopy<T>(this scoped ref SequenceReader<byte> reader, out T copyDestinationIfRequiredDoNotUse) where T : unmanaged
{
int lengthRequired = Unsafe.SizeOf<T>();
ReadOnlySpan<byte> span = reader.UnreadSpan;
if (lengthRequired <= span.Length)
{
reader.Advance(lengthRequired);
copyDestinationIfRequiredDoNotUse = default;
ReadOnlySpan<T> spanOfT = MemoryMarshal.Cast<byte, T>(span);
return ref spanOfT[0];
}
else
{
copyDestinationIfRequiredDoNotUse = default;
Span<T> valueSpan = MemoryMarshal.CreateSpan(ref copyDestinationIfRequiredDoNotUse, 1);
Span<byte> valueBytesSpan = MemoryMarshal.AsBytes(valueSpan);
if (!reader.TryCopyTo(valueBytesSpan))
{
throw new ArgumentOutOfRangeException(nameof(reader), "The sequence is not long enough to read the desired value.");
}
reader.Advance(lengthRequired);
return ref valueSpan[0];
}
}
/// <summary>
/// Reads an <see cref="int"/> as little endian.
/// </summary>
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
/// <param name="value">A location to receive the read value</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown if there wasn't enough data for an <see cref="int"/></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadLittleEndian(this ref SequenceReader<byte> reader, out int value)
{
if (!reader.TryReadLittleEndian(out value))
{
throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value.");
}
}
/// <summary>
/// Reads the desired unmanaged value by copying it to the specified <paramref name="value"/>.
/// </summary>
/// <typeparam name="T">Type to read</typeparam>
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
/// <param name="value">The target that will receive the read value</param>
/// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadUnmanaged<T>(this ref SequenceReader<byte> reader, out T value) where T : unmanaged
{
if (!reader.TryReadUnmanaged(out value))
{
throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value.");
}
}
/// <summary>
/// Sets the reader's position as bytes consumed.
/// </summary>
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to set the position</param>
/// <param name="consumed">The number of bytes consumed</param>
public static void SetConsumed(ref this SequenceReader<byte> reader, long consumed)
{
reader.Rewind(reader.Consumed);
reader.Advance(consumed);
}
/// <summary>
/// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary
/// structs - see remarks for full details.
/// </summary>
/// <typeparam name="T">Type to read</typeparam>
/// <remarks>
/// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to
/// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit
/// overloads such as <see cref="SequenceReader{T}.TryReadLittleEndian"/>
/// </remarks>
/// <returns>
/// True if successful. <paramref name="value"/> will be default if failed (due to lack of space).
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool TryReadUnmanaged<T>(ref this SequenceReader<byte> reader, out T value) where T : unmanaged
{
ReadOnlySpan<byte> span = reader.UnreadSpan;
if (span.Length < sizeof(T))
{
return TryReadUnmanagedMultiSegment(ref reader, out value);
}
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(span));
reader.Advance(sizeof(T));
return true;
}
private static unsafe bool TryReadUnmanagedMultiSegment<T>(ref SequenceReader<byte> reader, out T value) where T : unmanaged
{
Debug.Assert(reader.UnreadSpan.Length < sizeof(T));
// Not enough data in the current segment, try to peek for the data we need.
T buffer = default;
Span<byte> tempSpan = new Span<byte>(&buffer, sizeof(T));
if (!reader.TryCopyTo(tempSpan))
{
value = default;
return false;
}
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(tempSpan));
reader.Advance(sizeof(T));
return true;
}
}
}

View file

@ -4,7 +4,7 @@ using System.Threading;
namespace Ryujinx.Common.Memory namespace Ryujinx.Common.Memory
{ {
public sealed partial class ByteMemoryPool public partial class ByteMemoryPool
{ {
/// <summary> /// <summary>
/// Represents a <see cref="IMemoryOwner{Byte}"/> that wraps an array rented from /// Represents a <see cref="IMemoryOwner{Byte}"/> that wraps an array rented from

View file

@ -6,24 +6,8 @@ namespace Ryujinx.Common.Memory
/// <summary> /// <summary>
/// Provides a pool of re-usable byte array instances. /// Provides a pool of re-usable byte array instances.
/// </summary> /// </summary>
public sealed partial class ByteMemoryPool public static partial class ByteMemoryPool
{ {
private static readonly ByteMemoryPool _shared = new();
/// <summary>
/// Constructs a <see cref="ByteMemoryPool"/> instance. Private to force access through
/// the <see cref="ByteMemoryPool.Shared"/> instance.
/// </summary>
private ByteMemoryPool()
{
// No implementation
}
/// <summary>
/// Retrieves a shared <see cref="ByteMemoryPool"/> instance.
/// </summary>
public static ByteMemoryPool Shared => _shared;
/// <summary> /// <summary>
/// Returns the maximum buffer size supported by this pool. /// Returns the maximum buffer size supported by this pool.
/// </summary> /// </summary>
@ -95,6 +79,20 @@ namespace Ryujinx.Common.Memory
return buffer; return buffer;
} }
/// <summary>
/// Copies <paramref name="buffer"/> into a newly rented byte memory buffer.
/// </summary>
/// <param name="buffer">The byte buffer to copy</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory with <paramref name="buffer"/> copied to it</returns>
public static IMemoryOwner<byte> RentCopy(ReadOnlySpan<byte> buffer)
{
var copy = RentImpl(buffer.Length);
buffer.CopyTo(copy.Memory.Span);
return copy;
}
private static ByteMemoryPoolBuffer RentImpl(int length) private static ByteMemoryPoolBuffer RentImpl(int length)
{ {
if ((uint)length > Array.MaxLength) if ((uint)length > Array.MaxLength)

View file

@ -1,89 +0,0 @@
using System;
namespace Ryujinx.Common.Memory
{
/// <summary>
/// A struct that can represent both a Span and Array.
/// This is useful to keep the Array representation when possible to avoid copies.
/// </summary>
/// <typeparam name="T">Element Type</typeparam>
public readonly ref struct SpanOrArray<T> where T : unmanaged
{
public readonly T[] Array;
public readonly ReadOnlySpan<T> Span;
/// <summary>
/// Create a new SpanOrArray from an array.
/// </summary>
/// <param name="array">Array to store</param>
public SpanOrArray(T[] array)
{
Array = array;
Span = ReadOnlySpan<T>.Empty;
}
/// <summary>
/// Create a new SpanOrArray from a readonly span.
/// </summary>
/// <param name="array">Span to store</param>
public SpanOrArray(ReadOnlySpan<T> span)
{
Array = null;
Span = span;
}
/// <summary>
/// Return the contained array, or convert the span if necessary.
/// </summary>
/// <returns>An array containing the data</returns>
public T[] ToArray()
{
return Array ?? Span.ToArray();
}
/// <summary>
/// Return a ReadOnlySpan from either the array or ReadOnlySpan.
/// </summary>
/// <returns>A ReadOnlySpan containing the data</returns>
public ReadOnlySpan<T> AsSpan()
{
return Array ?? Span;
}
/// <summary>
/// Cast an array to a SpanOrArray.
/// </summary>
/// <param name="array">Source array</param>
public static implicit operator SpanOrArray<T>(T[] array)
{
return new SpanOrArray<T>(array);
}
/// <summary>
/// Cast a ReadOnlySpan to a SpanOrArray.
/// </summary>
/// <param name="span">Source ReadOnlySpan</param>
public static implicit operator SpanOrArray<T>(ReadOnlySpan<T> span)
{
return new SpanOrArray<T>(span);
}
/// <summary>
/// Cast a Span to a SpanOrArray.
/// </summary>
/// <param name="span">Source Span</param>
public static implicit operator SpanOrArray<T>(Span<T> span)
{
return new SpanOrArray<T>(span);
}
/// <summary>
/// Cast a SpanOrArray to a ReadOnlySpan
/// </summary>
/// <param name="spanOrArray">Source SpanOrArray</param>
public static implicit operator ReadOnlySpan<T>(SpanOrArray<T> spanOrArray)
{
return spanOrArray.AsSpan();
}
}
}

View file

@ -1,5 +1,6 @@
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using System; using System;
using System.Buffers;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -41,6 +42,22 @@ namespace Ryujinx.Common
return StreamUtils.StreamToBytes(stream); return StreamUtils.StreamToBytes(stream);
} }
public static IMemoryOwner<byte> ReadFileToRentedMemory(string filename)
{
var (assembly, path) = ResolveManifestPath(filename);
return ReadFileToRentedMemory(assembly, path);
}
public static IMemoryOwner<byte> ReadFileToRentedMemory(Assembly assembly, string filename)
{
using var stream = GetStream(assembly, filename);
return stream is null
? null
: StreamUtils.StreamToRentedMemory(stream);
}
public async static Task<byte[]> ReadAsync(Assembly assembly, string filename) public async static Task<byte[]> ReadAsync(Assembly assembly, string filename)
{ {
using var stream = GetStream(assembly, filename); using var stream = GetStream(assembly, filename);

View file

@ -1,4 +1,6 @@
using Microsoft.IO;
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using System.Buffers;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -9,12 +11,50 @@ namespace Ryujinx.Common.Utilities
{ {
public static byte[] StreamToBytes(Stream input) public static byte[] StreamToBytes(Stream input)
{ {
using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input);
return output.ToArray();
}
input.CopyTo(stream); public static IMemoryOwner<byte> StreamToRentedMemory(Stream input)
{
if (input is MemoryStream inputMemoryStream)
{
return MemoryStreamToRentedMemory(inputMemoryStream);
}
else if (input.CanSeek)
{
long bytesExpected = input.Length;
return stream.ToArray(); IMemoryOwner<byte> ownedMemory = ByteMemoryPool.Rent(bytesExpected);
var destSpan = ownedMemory.Memory.Span;
int totalBytesRead = 0;
while (totalBytesRead < bytesExpected)
{
int bytesRead = input.Read(destSpan[totalBytesRead..]);
if (bytesRead == 0)
{
ownedMemory.Dispose();
throw new IOException($"Tried reading {bytesExpected} but the stream closed after reading {totalBytesRead}.");
}
totalBytesRead += bytesRead;
}
return ownedMemory;
}
else
{
// If input is (non-seekable) then copy twice: first into a RecyclableMemoryStream, then to a rented IMemoryOwner<byte>.
using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input);
return MemoryStreamToRentedMemory(output);
}
} }
public static async Task<byte[]> StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default) public static async Task<byte[]> StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default)
@ -25,5 +65,26 @@ namespace Ryujinx.Common.Utilities
return stream.ToArray(); return stream.ToArray();
} }
private static IMemoryOwner<byte> MemoryStreamToRentedMemory(MemoryStream input)
{
input.Position = 0;
IMemoryOwner<byte> ownedMemory = ByteMemoryPool.Rent(input.Length);
// Discard the return value because we assume reading a MemoryStream always succeeds completely.
_ = input.Read(ownedMemory.Memory.Span);
return ownedMemory;
}
private static RecyclableMemoryStream StreamToRecyclableMemoryStream(Stream input)
{
RecyclableMemoryStream stream = MemoryStreamManager.Shared.GetStream();
input.CopyTo(stream);
return stream;
}
} }
} }

View file

@ -1,5 +1,3 @@
using Ryujinx.Common;
using Ryujinx.Common.Collections;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
@ -7,175 +5,23 @@ namespace Ryujinx.Cpu
{ {
public class AddressSpace : IDisposable public class AddressSpace : IDisposable
{ {
private const int DefaultBlockAlignment = 1 << 20;
private enum MappingType : byte
{
None,
Private,
Shared,
}
private class Mapping : IntrusiveRedBlackTreeNode<Mapping>, IComparable<Mapping>
{
public ulong Address { get; private set; }
public ulong Size { get; private set; }
public ulong EndAddress => Address + Size;
public MappingType Type { get; private set; }
public Mapping(ulong address, ulong size, MappingType type)
{
Address = address;
Size = size;
Type = type;
}
public Mapping Split(ulong splitAddress)
{
ulong leftSize = splitAddress - Address;
ulong rightSize = EndAddress - splitAddress;
Mapping left = new(Address, leftSize, Type);
Address = splitAddress;
Size = rightSize;
return left;
}
public void UpdateState(MappingType newType)
{
Type = newType;
}
public void Extend(ulong sizeDelta)
{
Size += sizeDelta;
}
public int CompareTo(Mapping other)
{
if (Address < other.Address)
{
return -1;
}
else if (Address <= other.EndAddress - 1UL)
{
return 0;
}
else
{
return 1;
}
}
}
private class PrivateMapping : IntrusiveRedBlackTreeNode<PrivateMapping>, IComparable<PrivateMapping>
{
public ulong Address { get; private set; }
public ulong Size { get; private set; }
public ulong EndAddress => Address + Size;
public PrivateMemoryAllocation PrivateAllocation { get; private set; }
public PrivateMapping(ulong address, ulong size, PrivateMemoryAllocation privateAllocation)
{
Address = address;
Size = size;
PrivateAllocation = privateAllocation;
}
public PrivateMapping Split(ulong splitAddress)
{
ulong leftSize = splitAddress - Address;
ulong rightSize = EndAddress - splitAddress;
(var leftAllocation, PrivateAllocation) = PrivateAllocation.Split(leftSize);
PrivateMapping left = new(Address, leftSize, leftAllocation);
Address = splitAddress;
Size = rightSize;
return left;
}
public void Map(MemoryBlock baseBlock, MemoryBlock mirrorBlock, PrivateMemoryAllocation newAllocation)
{
baseBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address, Size);
mirrorBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address, Size);
PrivateAllocation = newAllocation;
}
public void Unmap(MemoryBlock baseBlock, MemoryBlock mirrorBlock)
{
if (PrivateAllocation.IsValid)
{
baseBlock.UnmapView(PrivateAllocation.Memory, Address, Size);
mirrorBlock.UnmapView(PrivateAllocation.Memory, Address, Size);
PrivateAllocation.Dispose();
}
PrivateAllocation = default;
}
public void Extend(ulong sizeDelta)
{
Size += sizeDelta;
}
public int CompareTo(PrivateMapping other)
{
if (Address < other.Address)
{
return -1;
}
else if (Address <= other.EndAddress - 1UL)
{
return 0;
}
else
{
return 1;
}
}
}
private readonly MemoryBlock _backingMemory; private readonly MemoryBlock _backingMemory;
private readonly PrivateMemoryAllocator _privateMemoryAllocator;
private readonly IntrusiveRedBlackTree<Mapping> _mappingTree;
private readonly IntrusiveRedBlackTree<PrivateMapping> _privateTree;
private readonly object _treeLock;
private readonly bool _supports4KBPages;
public MemoryBlock Base { get; } public MemoryBlock Base { get; }
public MemoryBlock Mirror { get; } public MemoryBlock Mirror { get; }
public ulong AddressSpaceSize { get; } public ulong AddressSpaceSize { get; }
public AddressSpace(MemoryBlock backingMemory, MemoryBlock baseMemory, MemoryBlock mirrorMemory, ulong addressSpaceSize, bool supports4KBPages) public AddressSpace(MemoryBlock backingMemory, MemoryBlock baseMemory, MemoryBlock mirrorMemory, ulong addressSpaceSize)
{ {
if (!supports4KBPages)
{
_privateMemoryAllocator = new PrivateMemoryAllocator(DefaultBlockAlignment, MemoryAllocationFlags.Mirrorable | MemoryAllocationFlags.NoMap);
_mappingTree = new IntrusiveRedBlackTree<Mapping>();
_privateTree = new IntrusiveRedBlackTree<PrivateMapping>();
_treeLock = new object();
_mappingTree.Add(new Mapping(0UL, addressSpaceSize, MappingType.None));
_privateTree.Add(new PrivateMapping(0UL, addressSpaceSize, default));
}
_backingMemory = backingMemory; _backingMemory = backingMemory;
_supports4KBPages = supports4KBPages;
Base = baseMemory; Base = baseMemory;
Mirror = mirrorMemory; Mirror = mirrorMemory;
AddressSpaceSize = addressSpaceSize; AddressSpaceSize = addressSpaceSize;
} }
public static bool TryCreate(MemoryBlock backingMemory, ulong asSize, bool supports4KBPages, out AddressSpace addressSpace) public static bool TryCreate(MemoryBlock backingMemory, ulong asSize, out AddressSpace addressSpace)
{ {
addressSpace = null; addressSpace = null;
@ -193,7 +39,7 @@ namespace Ryujinx.Cpu
{ {
baseMemory = new MemoryBlock(addressSpaceSize, AsFlags); baseMemory = new MemoryBlock(addressSpaceSize, AsFlags);
mirrorMemory = new MemoryBlock(addressSpaceSize, AsFlags); mirrorMemory = new MemoryBlock(addressSpaceSize, AsFlags);
addressSpace = new AddressSpace(backingMemory, baseMemory, mirrorMemory, addressSpaceSize, supports4KBPages); addressSpace = new AddressSpace(backingMemory, baseMemory, mirrorMemory, addressSpaceSize);
break; break;
} }
@ -209,289 +55,20 @@ namespace Ryujinx.Cpu
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
{ {
if (_supports4KBPages) Base.MapView(_backingMemory, pa, va, size);
{ Mirror.MapView(_backingMemory, pa, va, size);
Base.MapView(_backingMemory, pa, va, size);
Mirror.MapView(_backingMemory, pa, va, size);
return;
}
lock (_treeLock)
{
ulong alignment = MemoryBlock.GetPageSize();
bool isAligned = ((va | pa | size) & (alignment - 1)) == 0;
if (flags.HasFlag(MemoryMapFlags.Private) && !isAligned)
{
Update(va, pa, size, MappingType.Private);
}
else
{
// The update method assumes that shared mappings are already aligned.
if (!flags.HasFlag(MemoryMapFlags.Private))
{
if ((va & (alignment - 1)) != (pa & (alignment - 1)))
{
throw new InvalidMemoryRegionException($"Virtual address 0x{va:X} and physical address 0x{pa:X} are misaligned and can't be aligned.");
}
ulong endAddress = va + size;
va = BitUtils.AlignDown(va, alignment);
pa = BitUtils.AlignDown(pa, alignment);
size = BitUtils.AlignUp(endAddress, alignment) - va;
}
Update(va, pa, size, MappingType.Shared);
}
}
} }
public void Unmap(ulong va, ulong size) public void Unmap(ulong va, ulong size)
{ {
if (_supports4KBPages) Base.UnmapView(_backingMemory, va, size);
{ Mirror.UnmapView(_backingMemory, va, size);
Base.UnmapView(_backingMemory, va, size);
Mirror.UnmapView(_backingMemory, va, size);
return;
}
lock (_treeLock)
{
Update(va, 0UL, size, MappingType.None);
}
}
private void Update(ulong va, ulong pa, ulong size, MappingType type)
{
Mapping map = _mappingTree.GetNode(new Mapping(va, 1UL, MappingType.None));
Update(map, va, pa, size, type);
}
private Mapping Update(Mapping map, ulong va, ulong pa, ulong size, MappingType type)
{
ulong endAddress = va + size;
for (; map != null; map = map.Successor)
{
if (map.Address < va)
{
_mappingTree.Add(map.Split(va));
}
if (map.EndAddress > endAddress)
{
Mapping newMap = map.Split(endAddress);
_mappingTree.Add(newMap);
map = newMap;
}
switch (type)
{
case MappingType.None:
if (map.Type == MappingType.Shared)
{
ulong startOffset = map.Address - va;
ulong mapVa = va + startOffset;
ulong mapSize = Math.Min(size - startOffset, map.Size);
ulong mapEndAddress = mapVa + mapSize;
ulong alignment = MemoryBlock.GetPageSize();
mapVa = BitUtils.AlignDown(mapVa, alignment);
mapEndAddress = BitUtils.AlignUp(mapEndAddress, alignment);
mapSize = mapEndAddress - mapVa;
Base.UnmapView(_backingMemory, mapVa, mapSize);
Mirror.UnmapView(_backingMemory, mapVa, mapSize);
}
else
{
UnmapPrivate(va, size);
}
break;
case MappingType.Private:
if (map.Type == MappingType.Shared)
{
throw new InvalidMemoryRegionException($"Private mapping request at 0x{va:X} with size 0x{size:X} overlaps shared mapping at 0x{map.Address:X} with size 0x{map.Size:X}.");
}
else
{
MapPrivate(va, size);
}
break;
case MappingType.Shared:
if (map.Type != MappingType.None)
{
throw new InvalidMemoryRegionException($"Shared mapping request at 0x{va:X} with size 0x{size:X} overlaps mapping at 0x{map.Address:X} with size 0x{map.Size:X}.");
}
else
{
ulong startOffset = map.Address - va;
ulong mapPa = pa + startOffset;
ulong mapVa = va + startOffset;
ulong mapSize = Math.Min(size - startOffset, map.Size);
Base.MapView(_backingMemory, mapPa, mapVa, mapSize);
Mirror.MapView(_backingMemory, mapPa, mapVa, mapSize);
}
break;
}
map.UpdateState(type);
map = TryCoalesce(map);
if (map.EndAddress >= endAddress)
{
break;
}
}
return map;
}
private Mapping TryCoalesce(Mapping map)
{
Mapping previousMap = map.Predecessor;
Mapping nextMap = map.Successor;
if (previousMap != null && CanCoalesce(previousMap, map))
{
previousMap.Extend(map.Size);
_mappingTree.Remove(map);
map = previousMap;
}
if (nextMap != null && CanCoalesce(map, nextMap))
{
map.Extend(nextMap.Size);
_mappingTree.Remove(nextMap);
}
return map;
}
private static bool CanCoalesce(Mapping left, Mapping right)
{
return left.Type == right.Type;
}
private void MapPrivate(ulong va, ulong size)
{
ulong endAddress = va + size;
ulong alignment = MemoryBlock.GetPageSize();
// Expand the range outwards based on page size to ensure that at least the requested region is mapped.
ulong vaAligned = BitUtils.AlignDown(va, alignment);
ulong endAddressAligned = BitUtils.AlignUp(endAddress, alignment);
PrivateMapping map = _privateTree.GetNode(new PrivateMapping(va, 1UL, default));
for (; map != null; map = map.Successor)
{
if (!map.PrivateAllocation.IsValid)
{
if (map.Address < vaAligned)
{
_privateTree.Add(map.Split(vaAligned));
}
if (map.EndAddress > endAddressAligned)
{
PrivateMapping newMap = map.Split(endAddressAligned);
_privateTree.Add(newMap);
map = newMap;
}
map.Map(Base, Mirror, _privateMemoryAllocator.Allocate(map.Size, MemoryBlock.GetPageSize()));
}
if (map.EndAddress >= endAddressAligned)
{
break;
}
}
}
private void UnmapPrivate(ulong va, ulong size)
{
ulong endAddress = va + size;
ulong alignment = MemoryBlock.GetPageSize();
// Shrink the range inwards based on page size to ensure we won't unmap memory that might be still in use.
ulong vaAligned = BitUtils.AlignUp(va, alignment);
ulong endAddressAligned = BitUtils.AlignDown(endAddress, alignment);
if (endAddressAligned <= vaAligned)
{
return;
}
PrivateMapping map = _privateTree.GetNode(new PrivateMapping(va, 1UL, default));
for (; map != null; map = map.Successor)
{
if (map.PrivateAllocation.IsValid)
{
if (map.Address < vaAligned)
{
_privateTree.Add(map.Split(vaAligned));
}
if (map.EndAddress > endAddressAligned)
{
PrivateMapping newMap = map.Split(endAddressAligned);
_privateTree.Add(newMap);
map = newMap;
}
map.Unmap(Base, Mirror);
map = TryCoalesce(map);
}
if (map.EndAddress >= endAddressAligned)
{
break;
}
}
}
private PrivateMapping TryCoalesce(PrivateMapping map)
{
PrivateMapping previousMap = map.Predecessor;
PrivateMapping nextMap = map.Successor;
if (previousMap != null && CanCoalesce(previousMap, map))
{
previousMap.Extend(map.Size);
_privateTree.Remove(map);
map = previousMap;
}
if (nextMap != null && CanCoalesce(map, nextMap))
{
map.Extend(nextMap.Size);
_privateTree.Remove(nextMap);
}
return map;
}
private static bool CanCoalesce(PrivateMapping left, PrivateMapping right)
{
return !left.PrivateAllocation.IsValid && !right.PrivateAllocation.IsValid;
} }
public void Dispose() public void Dispose()
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
_privateMemoryAllocator?.Dispose();
Base.Dispose(); Base.Dispose();
Mirror.Dispose(); Mirror.Dispose();
} }

View file

@ -3,10 +3,10 @@ using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking; using Ryujinx.Memory.Tracking;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
@ -15,7 +15,7 @@ namespace Ryujinx.Cpu.AppleHv
/// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table. /// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table.
/// </summary> /// </summary>
[SupportedOSPlatform("macos")] [SupportedOSPlatform("macos")]
public class HvMemoryManager : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock public sealed class HvMemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked
{ {
private readonly InvalidAccessHandler _invalidAccessHandler; private readonly InvalidAccessHandler _invalidAccessHandler;
@ -28,7 +28,7 @@ namespace Ryujinx.Cpu.AppleHv
private readonly ManagedPageFlags _pages; private readonly ManagedPageFlags _pages;
public bool Supports4KBPages => true; public bool UsesPrivateAllocations => false;
public int AddressSpaceBits { get; } public int AddressSpaceBits { get; }
@ -96,12 +96,6 @@ namespace Ryujinx.Cpu.AppleHv
} }
} }
/// <inheritdoc/>
public void MapForeign(ulong va, nuint hostPointer, ulong size)
{
throw new NotSupportedException();
}
/// <inheritdoc/> /// <inheritdoc/>
public void Unmap(ulong va, ulong size) public void Unmap(ulong va, ulong size)
{ {
@ -126,20 +120,11 @@ namespace Ryujinx.Cpu.AppleHv
} }
} }
/// <inheritdoc/> public override T ReadTracked<T>(ulong va)
public T Read<T>(ulong va) where T : unmanaged
{
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
}
/// <inheritdoc/>
public T ReadTracked<T>(ulong va) where T : unmanaged
{ {
try try
{ {
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false); return base.ReadTracked<T>(va);
return Read<T>(va);
} }
catch (InvalidMemoryRegionException) catch (InvalidMemoryRegionException)
{ {
@ -152,7 +137,6 @@ namespace Ryujinx.Cpu.AppleHv
} }
} }
/// <inheritdoc/>
public override void Read(ulong va, Span<byte> data) public override void Read(ulong va, Span<byte> data)
{ {
try try
@ -168,101 +152,11 @@ namespace Ryujinx.Cpu.AppleHv
} }
} }
/// <inheritdoc/> public override void Write(ulong va, ReadOnlySpan<byte> data)
public void Write<T>(ulong va, T value) where T : unmanaged
{
Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
}
/// <inheritdoc/>
public void Write(ulong va, ReadOnlySpan<byte> data)
{
if (data.Length == 0)
{
return;
}
SignalMemoryTracking(va, (ulong)data.Length, true);
WriteImpl(va, data);
}
/// <inheritdoc/>
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
{
if (data.Length == 0)
{
return;
}
WriteImpl(va, data);
}
/// <inheritdoc/>
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
{
if (data.Length == 0)
{
return false;
}
SignalMemoryTracking(va, (ulong)data.Length, false);
if (IsContiguousAndMapped(va, data.Length))
{
var target = _backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length);
bool changed = !data.SequenceEqual(target);
if (changed)
{
data.CopyTo(target);
}
return changed;
}
else
{
WriteImpl(va, data);
return true;
}
}
private void WriteImpl(ulong va, ReadOnlySpan<byte> data)
{ {
try try
{ {
AssertValidAddressAndSize(va, (ulong)data.Length); base.Write(va, data);
if (IsContiguousAndMapped(va, data.Length))
{
data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
}
else
{
int offset = 0, size;
if ((va & PageMask) != 0)
{
ulong pa = GetPhysicalAddressChecked(va);
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
data[..size].CopyTo(_backingMemory.GetSpan(pa, size));
offset += size;
}
for (; offset < data.Length; offset += size)
{
ulong pa = GetPhysicalAddressChecked(va + (ulong)offset);
size = Math.Min(data.Length - offset, PageSize);
data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size));
}
}
} }
catch (InvalidMemoryRegionException) catch (InvalidMemoryRegionException)
{ {
@ -273,61 +167,38 @@ namespace Ryujinx.Cpu.AppleHv
} }
} }
/// <inheritdoc/> public override void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{ {
if (size == 0) try
{ {
return ReadOnlySpan<byte>.Empty; base.WriteUntracked(va, data);
} }
catch (InvalidMemoryRegionException)
if (tracked)
{ {
SignalMemoryTracking(va, (ulong)size, false); if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
} {
throw;
if (IsContiguousAndMapped(va, size)) }
{
return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
}
else
{
Span<byte> data = new byte[size];
base.Read(va, data);
return data;
} }
} }
/// <inheritdoc/> public override ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false)
public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
{ {
if (size == 0) try
{ {
return new WritableRegion(null, va, Memory<byte>.Empty); return base.GetReadOnlySequence(va, size, tracked);
} }
catch (InvalidMemoryRegionException)
if (tracked)
{ {
SignalMemoryTracking(va, (ulong)size, true); if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
} {
throw;
}
if (IsContiguousAndMapped(va, size)) return ReadOnlySequence<byte>.Empty;
{
return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
}
else
{
Memory<byte> memory = new byte[size];
base.Read(va, memory.Span);
return new WritableRegion(this, va, memory);
} }
} }
/// <inheritdoc/>
public ref T GetRef<T>(ulong va) where T : unmanaged public ref T GetRef<T>(ulong va) where T : unmanaged
{ {
if (!IsContiguous(va, Unsafe.SizeOf<T>())) if (!IsContiguous(va, Unsafe.SizeOf<T>()))
@ -340,9 +211,8 @@ namespace Ryujinx.Cpu.AppleHv
return ref _backingMemory.GetRef<T>(GetPhysicalAddressChecked(va)); return ref _backingMemory.GetRef<T>(GetPhysicalAddressChecked(va));
} }
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsMapped(ulong va) public override bool IsMapped(ulong va)
{ {
return ValidateAddress(va) && _pages.IsMapped(va); return ValidateAddress(va) && _pages.IsMapped(va);
} }
@ -355,39 +225,6 @@ namespace Ryujinx.Cpu.AppleHv
return _pages.IsRangeMapped(va, size); return _pages.IsRangeMapped(va, size);
} }
private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsContiguous(ulong va, int size)
{
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size))
{
return false;
}
int pages = GetPagesCount(va, (uint)size, out va);
for (int page = 0; page < pages - 1; page++)
{
if (!ValidateAddress(va + PageSize))
{
return false;
}
if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize))
{
return false;
}
va += PageSize;
}
return true;
}
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size) public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
{ {
@ -464,11 +301,10 @@ namespace Ryujinx.Cpu.AppleHv
return regions; return regions;
} }
/// <inheritdoc/>
/// <remarks> /// <remarks>
/// This function also validates that the given range is both valid and mapped, and will throw if it is not. /// This function also validates that the given range is both valid and mapped, and will throw if it is not.
/// </remarks> /// </remarks>
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
{ {
AssertValidAddressAndSize(va, size); AssertValidAddressAndSize(va, size);
@ -481,24 +317,6 @@ namespace Ryujinx.Cpu.AppleHv
_pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
} }
/// <summary>
/// Computes the number of pages in a virtual address range.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range</param>
/// <param name="startVa">The virtual address of the beginning of the first page</param>
/// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetPagesCount(ulong va, ulong size, out ulong startVa)
{
// WARNING: Always check if ulong does not overflow during the operations.
startVa = va & ~(ulong)PageMask;
ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
return (int)(vaSpan / PageSize);
}
/// <inheritdoc/>
public void Reprotect(ulong va, ulong size, MemoryPermission protection) public void Reprotect(ulong va, ulong size, MemoryPermission protection)
{ {
// TODO // TODO
@ -535,7 +353,7 @@ namespace Ryujinx.Cpu.AppleHv
return Tracking.BeginSmartGranularTracking(address, size, granularity, id); return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
} }
private ulong GetPhysicalAddressChecked(ulong va) private nuint GetPhysicalAddressChecked(ulong va)
{ {
if (!IsMapped(va)) if (!IsMapped(va))
{ {
@ -545,9 +363,9 @@ namespace Ryujinx.Cpu.AppleHv
return GetPhysicalAddressInternal(va); return GetPhysicalAddressInternal(va);
} }
private ulong GetPhysicalAddressInternal(ulong va) private nuint GetPhysicalAddressInternal(ulong va)
{ {
return _pageTable.Read(va) + (va & PageMask); return (nuint)(_pageTable.Read(va) + (va & PageMask));
} }
/// <summary> /// <summary>
@ -558,10 +376,17 @@ namespace Ryujinx.Cpu.AppleHv
_addressSpace.Dispose(); _addressSpace.Dispose();
} }
protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size) protected override Memory<byte> GetPhysicalAddressMemory(nuint pa, int size)
=> _backingMemory.GetMemory(pa, size);
protected override Span<byte> GetPhysicalAddressSpan(nuint pa, int size)
=> _backingMemory.GetSpan(pa, size); => _backingMemory.GetSpan(pa, size);
protected override ulong TranslateVirtualAddressForRead(ulong va) protected override nuint TranslateVirtualAddressChecked(ulong va)
=> GetPhysicalAddressChecked(va); => GetPhysicalAddressChecked(va);
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
=> GetPhysicalAddressInternal(va);
} }
} }

View file

@ -3,6 +3,7 @@ using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking; using Ryujinx.Memory.Tracking;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -14,7 +15,7 @@ namespace Ryujinx.Cpu.Jit
/// <summary> /// <summary>
/// Represents a CPU memory manager. /// Represents a CPU memory manager.
/// </summary> /// </summary>
public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked
{ {
private const int PteSize = 8; private const int PteSize = 8;
@ -24,7 +25,7 @@ namespace Ryujinx.Cpu.Jit
private readonly InvalidAccessHandler _invalidAccessHandler; private readonly InvalidAccessHandler _invalidAccessHandler;
/// <inheritdoc/> /// <inheritdoc/>
public bool Supports4KBPages => true; public bool UsesPrivateAllocations => false;
/// <summary> /// <summary>
/// Address space width in bits. /// Address space width in bits.
@ -97,12 +98,6 @@ namespace Ryujinx.Cpu.Jit
Tracking.Map(oVa, size); Tracking.Map(oVa, size);
} }
/// <inheritdoc/>
public void MapForeign(ulong va, nuint hostPointer, ulong size)
{
throw new NotSupportedException();
}
/// <inheritdoc/> /// <inheritdoc/>
public void Unmap(ulong va, ulong size) public void Unmap(ulong va, ulong size)
{ {
@ -128,20 +123,11 @@ namespace Ryujinx.Cpu.Jit
} }
} }
/// <inheritdoc/> public override T ReadTracked<T>(ulong va)
public T Read<T>(ulong va) where T : unmanaged
{
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
}
/// <inheritdoc/>
public T ReadTracked<T>(ulong va) where T : unmanaged
{ {
try try
{ {
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false); return base.ReadTracked<T>(va);
return Read<T>(va);
} }
catch (InvalidMemoryRegionException) catch (InvalidMemoryRegionException)
{ {
@ -190,117 +176,11 @@ namespace Ryujinx.Cpu.Jit
} }
} }
/// <inheritdoc/> public override void Write(ulong va, ReadOnlySpan<byte> data)
public void Write<T>(ulong va, T value) where T : unmanaged
{
Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
}
/// <inheritdoc/>
public void Write(ulong va, ReadOnlySpan<byte> data)
{
if (data.Length == 0)
{
return;
}
SignalMemoryTracking(va, (ulong)data.Length, true);
WriteImpl(va, data);
}
/// <inheritdoc/>
public void WriteGuest<T>(ulong va, T value) where T : unmanaged
{
Span<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1));
SignalMemoryTrackingImpl(va, (ulong)data.Length, true, true);
WriteImpl(va, data);
}
/// <inheritdoc/>
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
{
if (data.Length == 0)
{
return;
}
WriteImpl(va, data);
}
/// <inheritdoc/>
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
{
if (data.Length == 0)
{
return false;
}
SignalMemoryTracking(va, (ulong)data.Length, false);
if (IsContiguousAndMapped(va, data.Length))
{
var target = _backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length);
bool changed = !data.SequenceEqual(target);
if (changed)
{
data.CopyTo(target);
}
return changed;
}
else
{
WriteImpl(va, data);
return true;
}
}
/// <summary>
/// Writes data to CPU mapped memory.
/// </summary>
/// <param name="va">Virtual address to write the data into</param>
/// <param name="data">Data to be written</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteImpl(ulong va, ReadOnlySpan<byte> data)
{ {
try try
{ {
AssertValidAddressAndSize(va, (ulong)data.Length); base.Write(va, data);
if (IsContiguousAndMapped(va, data.Length))
{
data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
}
else
{
int offset = 0, size;
if ((va & PageMask) != 0)
{
ulong pa = GetPhysicalAddressInternal(va);
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
data[..size].CopyTo(_backingMemory.GetSpan(pa, size));
offset += size;
}
for (; offset < data.Length; offset += size)
{
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
size = Math.Min(data.Length - offset, PageSize);
data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size));
}
}
} }
catch (InvalidMemoryRegionException) catch (InvalidMemoryRegionException)
{ {
@ -312,60 +192,47 @@ namespace Ryujinx.Cpu.Jit
} }
/// <inheritdoc/> /// <inheritdoc/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false) public void WriteGuest<T>(ulong va, T value) where T : unmanaged
{ {
if (size == 0) Span<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1));
SignalMemoryTrackingImpl(va, (ulong)data.Length, true, true);
Write(va, data);
}
public override void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
{
try
{ {
return ReadOnlySpan<byte>.Empty; base.WriteUntracked(va, data);
} }
catch (InvalidMemoryRegionException)
if (tracked)
{ {
SignalMemoryTracking(va, (ulong)size, false); if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
} {
throw;
if (IsContiguousAndMapped(va, size)) }
{
return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
}
else
{
Span<byte> data = new byte[size];
base.Read(va, data);
return data;
} }
} }
/// <inheritdoc/> public override ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false)
public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
{ {
if (size == 0) try
{ {
return new WritableRegion(null, va, Memory<byte>.Empty); return base.GetReadOnlySequence(va, size, tracked);
} }
catch (InvalidMemoryRegionException)
if (IsContiguousAndMapped(va, size))
{ {
if (tracked) if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{ {
SignalMemoryTracking(va, (ulong)size, true); throw;
} }
return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size)); return ReadOnlySequence<byte>.Empty;
}
else
{
Memory<byte> memory = new byte[size];
GetSpan(va, size).CopyTo(memory.Span);
return new WritableRegion(this, va, memory, tracked);
} }
} }
/// <inheritdoc/>
public ref T GetRef<T>(ulong va) where T : unmanaged public ref T GetRef<T>(ulong va) where T : unmanaged
{ {
if (!IsContiguous(va, Unsafe.SizeOf<T>())) if (!IsContiguous(va, Unsafe.SizeOf<T>()))
@ -378,56 +245,6 @@ namespace Ryujinx.Cpu.Jit
return ref _backingMemory.GetRef<T>(GetPhysicalAddressInternal(va)); return ref _backingMemory.GetRef<T>(GetPhysicalAddressInternal(va));
} }
/// <summary>
/// Computes the number of pages in a virtual address range.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range</param>
/// <param name="startVa">The virtual address of the beginning of the first page</param>
/// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetPagesCount(ulong va, uint size, out ulong startVa)
{
// WARNING: Always check if ulong does not overflow during the operations.
startVa = va & ~(ulong)PageMask;
ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
return (int)(vaSpan / PageSize);
}
private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsContiguous(ulong va, int size)
{
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size))
{
return false;
}
int pages = GetPagesCount(va, (uint)size, out va);
for (int page = 0; page < pages - 1; page++)
{
if (!ValidateAddress(va + PageSize))
{
return false;
}
if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize))
{
return false;
}
va += PageSize;
}
return true;
}
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size) public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
{ {
@ -532,9 +349,8 @@ namespace Ryujinx.Cpu.Jit
return true; return true;
} }
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsMapped(ulong va) public override bool IsMapped(ulong va)
{ {
if (!ValidateAddress(va)) if (!ValidateAddress(va))
{ {
@ -544,9 +360,9 @@ namespace Ryujinx.Cpu.Jit
return _pageTable.Read<ulong>((va / PageSize) * PteSize) != 0; return _pageTable.Read<ulong>((va / PageSize) * PteSize) != 0;
} }
private ulong GetPhysicalAddressInternal(ulong va) private nuint GetPhysicalAddressInternal(ulong va)
{ {
return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask); return (nuint)(PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask));
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -643,9 +459,7 @@ namespace Ryujinx.Cpu.Jit
{ {
ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize); ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
long pte; long pte = Volatile.Read(ref pageRef);
pte = Volatile.Read(ref pageRef);
if ((pte & tag) != 0) if ((pte & tag) != 0)
{ {
@ -663,7 +477,7 @@ namespace Ryujinx.Cpu.Jit
} }
/// <inheritdoc/> /// <inheritdoc/>
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
{ {
SignalMemoryTrackingImpl(va, size, write, false, precise, exemptId); SignalMemoryTrackingImpl(va, size, write, false, precise, exemptId);
} }
@ -683,10 +497,16 @@ namespace Ryujinx.Cpu.Jit
/// </summary> /// </summary>
protected override void Destroy() => _pageTable.Dispose(); protected override void Destroy() => _pageTable.Dispose();
protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size) protected override Memory<byte> GetPhysicalAddressMemory(nuint pa, int size)
=> _backingMemory.GetMemory(pa, size);
protected override Span<byte> GetPhysicalAddressSpan(nuint pa, int size)
=> _backingMemory.GetSpan(pa, size); => _backingMemory.GetSpan(pa, size);
protected override ulong TranslateVirtualAddressForRead(ulong va) protected override nuint TranslateVirtualAddressChecked(ulong va)
=> GetPhysicalAddressInternal(va);
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
=> GetPhysicalAddressInternal(va); => GetPhysicalAddressInternal(va);
} }
} }

View file

@ -3,6 +3,7 @@ using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking; using Ryujinx.Memory.Tracking;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -12,7 +13,7 @@ namespace Ryujinx.Cpu.Jit
/// <summary> /// <summary>
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
/// </summary> /// </summary>
public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked
{ {
private readonly InvalidAccessHandler _invalidAccessHandler; private readonly InvalidAccessHandler _invalidAccessHandler;
private readonly bool _unsafeMode; private readonly bool _unsafeMode;
@ -26,7 +27,7 @@ namespace Ryujinx.Cpu.Jit
private readonly ManagedPageFlags _pages; private readonly ManagedPageFlags _pages;
/// <inheritdoc/> /// <inheritdoc/>
public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize; public bool UsesPrivateAllocations => false;
public int AddressSpaceBits { get; } public int AddressSpaceBits { get; }
@ -96,12 +97,6 @@ namespace Ryujinx.Cpu.Jit
Tracking.Map(va, size); Tracking.Map(va, size);
} }
/// <inheritdoc/>
public void MapForeign(ulong va, nuint hostPointer, ulong size)
{
throw new NotSupportedException();
}
/// <inheritdoc/> /// <inheritdoc/>
public void Unmap(ulong va, ulong size) public void Unmap(ulong va, ulong size)
{ {
@ -138,8 +133,7 @@ namespace Ryujinx.Cpu.Jit
} }
} }
/// <inheritdoc/> public override T Read<T>(ulong va)
public T Read<T>(ulong va) where T : unmanaged
{ {
try try
{ {
@ -158,14 +152,11 @@ namespace Ryujinx.Cpu.Jit
} }
} }
/// <inheritdoc/> public override T ReadTracked<T>(ulong va)
public T ReadTracked<T>(ulong va) where T : unmanaged
{ {
try try
{ {
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false); return base.ReadTracked<T>(va);
return Read<T>(va);
} }
catch (InvalidMemoryRegionException) catch (InvalidMemoryRegionException)
{ {
@ -178,7 +169,6 @@ namespace Ryujinx.Cpu.Jit
} }
} }
/// <inheritdoc/>
public override void Read(ulong va, Span<byte> data) public override void Read(ulong va, Span<byte> data)
{ {
try try
@ -196,9 +186,7 @@ namespace Ryujinx.Cpu.Jit
} }
} }
public override void Write<T>(ulong va, T value)
/// <inheritdoc/>
public void Write<T>(ulong va, T value) where T : unmanaged
{ {
try try
{ {
@ -215,8 +203,7 @@ namespace Ryujinx.Cpu.Jit
} }
} }
/// <inheritdoc/> public override void Write(ulong va, ReadOnlySpan<byte> data)
public void Write(ulong va, ReadOnlySpan<byte> data)
{ {
try try
{ {
@ -233,8 +220,7 @@ namespace Ryujinx.Cpu.Jit
} }
} }
/// <inheritdoc/> public override void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
{ {
try try
{ {
@ -251,8 +237,7 @@ namespace Ryujinx.Cpu.Jit
} }
} }
/// <inheritdoc/> public override bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
{ {
try try
{ {
@ -279,8 +264,21 @@ namespace Ryujinx.Cpu.Jit
} }
} }
/// <inheritdoc/> public override ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false)
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false) {
if (tracked)
{
SignalMemoryTracking(va, (ulong)size, write: false);
}
else
{
AssertMapped(va, (ulong)size);
}
return new ReadOnlySequence<byte>(_addressSpace.Mirror.GetMemory(va, size));
}
public override ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{ {
if (tracked) if (tracked)
{ {
@ -294,8 +292,7 @@ namespace Ryujinx.Cpu.Jit
return _addressSpace.Mirror.GetSpan(va, size); return _addressSpace.Mirror.GetSpan(va, size);
} }
/// <inheritdoc/> public override WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
{ {
if (tracked) if (tracked)
{ {
@ -309,7 +306,6 @@ namespace Ryujinx.Cpu.Jit
return _addressSpace.Mirror.GetWritableRegion(va, size); return _addressSpace.Mirror.GetWritableRegion(va, size);
} }
/// <inheritdoc/>
public ref T GetRef<T>(ulong va) where T : unmanaged public ref T GetRef<T>(ulong va) where T : unmanaged
{ {
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true); SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
@ -317,9 +313,8 @@ namespace Ryujinx.Cpu.Jit
return ref _addressSpace.Mirror.GetRef<T>(va); return ref _addressSpace.Mirror.GetRef<T>(va);
} }
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsMapped(ulong va) public override bool IsMapped(ulong va)
{ {
return ValidateAddress(va) && _pages.IsMapped(va); return ValidateAddress(va) && _pages.IsMapped(va);
} }
@ -390,11 +385,10 @@ namespace Ryujinx.Cpu.Jit
return _pageTable.Read(va) + (va & PageMask); return _pageTable.Read(va) + (va & PageMask);
} }
/// <inheritdoc/>
/// <remarks> /// <remarks>
/// This function also validates that the given range is both valid and mapped, and will throw if it is not. /// This function also validates that the given range is both valid and mapped, and will throw if it is not.
/// </remarks> /// </remarks>
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
{ {
AssertValidAddressAndSize(va, size); AssertValidAddressAndSize(va, size);
@ -407,23 +401,6 @@ namespace Ryujinx.Cpu.Jit
_pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
} }
/// <summary>
/// Computes the number of pages in a virtual address range.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range</param>
/// <param name="startVa">The virtual address of the beginning of the first page</param>
/// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetPagesCount(ulong va, ulong size, out ulong startVa)
{
// WARNING: Always check if ulong does not overflow during the operations.
startVa = va & ~(ulong)PageMask;
ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
return (int)(vaSpan / PageSize);
}
/// <inheritdoc/> /// <inheritdoc/>
public void Reprotect(ulong va, ulong size, MemoryPermission protection) public void Reprotect(ulong va, ulong size, MemoryPermission protection)
{ {
@ -470,10 +447,16 @@ namespace Ryujinx.Cpu.Jit
_memoryEh.Dispose(); _memoryEh.Dispose();
} }
protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size) protected override Memory<byte> GetPhysicalAddressMemory(nuint pa, int size)
=> _addressSpace.Mirror.GetMemory(pa, size);
protected override Span<byte> GetPhysicalAddressSpan(nuint pa, int size)
=> _addressSpace.Mirror.GetSpan(pa, size); => _addressSpace.Mirror.GetSpan(pa, size);
protected override ulong TranslateVirtualAddressForRead(ulong va) protected override nuint TranslateVirtualAddressChecked(ulong va)
=> va; => (nuint)GetPhysicalAddressChecked(va);
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
=> (nuint)GetPhysicalAddressInternal(va);
} }
} }

View file

@ -1,21 +1,22 @@
using ARMeilleure.Memory; using ARMeilleure.Memory;
using Ryujinx.Common.Memory;
using Ryujinx.Cpu.Jit.HostTracked; using Ryujinx.Cpu.Jit.HostTracked;
using Ryujinx.Cpu.Signal; using Ryujinx.Cpu.Signal;
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking; using Ryujinx.Memory.Tracking;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Cpu.Jit namespace Ryujinx.Cpu.Jit
{ {
/// <summary> /// <summary>
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
/// </summary> /// </summary>
public sealed class MemoryManagerHostTracked : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IWritableBlock, IMemoryManager, IVirtualMemoryManagerTracked public sealed class MemoryManagerHostTracked : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked
{ {
private readonly InvalidAccessHandler _invalidAccessHandler; private readonly InvalidAccessHandler _invalidAccessHandler;
private readonly bool _unsafeMode; private readonly bool _unsafeMode;
@ -34,7 +35,7 @@ namespace Ryujinx.Cpu.Jit
protected override ulong AddressSpaceSize { get; } protected override ulong AddressSpaceSize { get; }
/// <inheritdoc/> /// <inheritdoc/>
public bool Supports4KBPages => false; public bool UsesPrivateAllocations => true;
public IntPtr PageTablePointer => _nativePageTable.PageTablePointer; public IntPtr PageTablePointer => _nativePageTable.PageTablePointer;
@ -100,12 +101,6 @@ namespace Ryujinx.Cpu.Jit
Tracking.Map(va, size); Tracking.Map(va, size);
} }
/// <inheritdoc/>
public void MapForeign(ulong va, nuint hostPointer, ulong size)
{
throw new NotSupportedException();
}
/// <inheritdoc/> /// <inheritdoc/>
public void Unmap(ulong va, ulong size) public void Unmap(ulong va, ulong size)
{ {
@ -120,18 +115,11 @@ namespace Ryujinx.Cpu.Jit
_nativePageTable.Unmap(va, size); _nativePageTable.Unmap(va, size);
} }
public T Read<T>(ulong va) where T : unmanaged public override T ReadTracked<T>(ulong va)
{
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
}
public T ReadTracked<T>(ulong va) where T : unmanaged
{ {
try try
{ {
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false); return base.ReadTracked<T>(va);
return Read<T>(va);
} }
catch (InvalidMemoryRegionException) catch (InvalidMemoryRegionException)
{ {
@ -145,38 +133,39 @@ namespace Ryujinx.Cpu.Jit
} }
public override void Read(ulong va, Span<byte> data) public override void Read(ulong va, Span<byte> data)
{
ReadImpl(va, data);
}
public void Write<T>(ulong va, T value) where T : unmanaged
{
Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
}
public void Write(ulong va, ReadOnlySpan<byte> data)
{ {
if (data.Length == 0) if (data.Length == 0)
{ {
return; return;
} }
SignalMemoryTracking(va, (ulong)data.Length, true); try
WriteImpl(va, data);
}
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
{
if (data.Length == 0)
{ {
return; AssertValidAddressAndSize(va, (ulong)data.Length);
}
WriteImpl(va, data); ulong endVa = va + (ulong)data.Length;
int offset = 0;
while (va < endVa)
{
(MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset));
memory.GetSpan(rangeOffset, (int)copySize).CopyTo(data.Slice(offset, (int)copySize));
va += copySize;
offset += (int)copySize;
}
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
} }
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data) public override bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
{ {
if (data.Length == 0) if (data.Length == 0)
{ {
@ -206,35 +195,7 @@ namespace Ryujinx.Cpu.Jit
} }
} }
private void WriteImpl(ulong va, ReadOnlySpan<byte> data) public override ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{
try
{
AssertValidAddressAndSize(va, (ulong)data.Length);
ulong endVa = va + (ulong)data.Length;
int offset = 0;
while (va < endVa)
{
(MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset));
data.Slice(offset, (int)copySize).CopyTo(memory.GetSpan(rangeOffset, (int)copySize));
va += copySize;
offset += (int)copySize;
}
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
}
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{ {
if (size == 0) if (size == 0)
{ {
@ -254,13 +215,13 @@ namespace Ryujinx.Cpu.Jit
{ {
Span<byte> data = new byte[size]; Span<byte> data = new byte[size];
ReadImpl(va, data); Read(va, data);
return data; return data;
} }
} }
public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) public override WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
{ {
if (size == 0) if (size == 0)
{ {
@ -278,11 +239,11 @@ namespace Ryujinx.Cpu.Jit
} }
else else
{ {
Memory<byte> memory = new byte[size]; IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size);
ReadImpl(va, memory.Span); Read(va, memoryOwner.Memory.Span);
return new WritableRegion(this, va, memory); return new WritableRegion(this, va, memoryOwner);
} }
} }
@ -299,7 +260,7 @@ namespace Ryujinx.Cpu.Jit
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsMapped(ulong va) public override bool IsMapped(ulong va)
{ {
return ValidateAddress(va) && _pages.IsMapped(va); return ValidateAddress(va) && _pages.IsMapped(va);
} }
@ -311,8 +272,6 @@ namespace Ryujinx.Cpu.Jit
return _pages.IsRangeMapped(va, size); return _pages.IsRangeMapped(va, size);
} }
private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
private bool TryGetVirtualContiguous(ulong va, int size, out MemoryBlock memory, out ulong offset) private bool TryGetVirtualContiguous(ulong va, int size, out MemoryBlock memory, out ulong offset)
{ {
if (_addressSpace.HasAnyPrivateAllocation(va, (ulong)size, out PrivateRange range)) if (_addressSpace.HasAnyPrivateAllocation(va, (ulong)size, out PrivateRange range))
@ -491,44 +450,11 @@ namespace Ryujinx.Cpu.Jit
return regions; return regions;
} }
private void ReadImpl(ulong va, Span<byte> data)
{
if (data.Length == 0)
{
return;
}
try
{
AssertValidAddressAndSize(va, (ulong)data.Length);
ulong endVa = va + (ulong)data.Length;
int offset = 0;
while (va < endVa)
{
(MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset));
memory.GetSpan(rangeOffset, (int)copySize).CopyTo(data.Slice(offset, (int)copySize));
va += copySize;
offset += (int)copySize;
}
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
}
/// <inheritdoc/> /// <inheritdoc/>
/// <remarks> /// <remarks>
/// This function also validates that the given range is both valid and mapped, and will throw if it is not. /// This function also validates that the given range is both valid and mapped, and will throw if it is not.
/// </remarks> /// </remarks>
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
{ {
AssertValidAddressAndSize(va, size); AssertValidAddressAndSize(va, size);
@ -543,23 +469,6 @@ namespace Ryujinx.Cpu.Jit
_pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
} }
/// <summary>
/// Computes the number of pages in a virtual address range.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range</param>
/// <param name="startVa">The virtual address of the beginning of the first page</param>
/// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetPagesCount(ulong va, ulong size, out ulong startVa)
{
// WARNING: Always check if ulong does not overflow during the operations.
startVa = va & ~(ulong)PageMask;
ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
return (int)(vaSpan / PageSize);
}
public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None)
{ {
return Tracking.BeginTracking(address, size, id, flags); return Tracking.BeginTracking(address, size, id, flags);
@ -618,10 +527,44 @@ namespace Ryujinx.Cpu.Jit
_nativePageTable.Dispose(); _nativePageTable.Dispose();
} }
protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size) protected override Memory<byte> GetPhysicalAddressMemory(nuint pa, int size)
=> _backingMemory.GetMemory(pa, size);
protected override Span<byte> GetPhysicalAddressSpan(nuint pa, int size)
=> _backingMemory.GetSpan(pa, size); => _backingMemory.GetSpan(pa, size);
protected override ulong TranslateVirtualAddressForRead(ulong va) protected override void WriteImpl(ulong va, ReadOnlySpan<byte> data)
=> GetPhysicalAddressInternal(va); {
try
{
AssertValidAddressAndSize(va, (ulong)data.Length);
ulong endVa = va + (ulong)data.Length;
int offset = 0;
while (va < endVa)
{
(MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset));
data.Slice(offset, (int)copySize).CopyTo(memory.GetSpan(rangeOffset, (int)copySize));
va += copySize;
offset += (int)copySize;
}
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
}
protected override nuint TranslateVirtualAddressChecked(ulong va)
=> (nuint)GetPhysicalAddressChecked(va);
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
=> (nuint)GetPhysicalAddressInternal(va);
} }
} }

View file

@ -1,13 +1,10 @@
using Ryujinx.Memory; using Ryujinx.Memory;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics;
using System.Threading; using System.Threading;
namespace Ryujinx.Cpu namespace Ryujinx.Cpu
{ {
public abstract class VirtualMemoryManagerRefCountedBase<TVirtual, TPhysical> : VirtualMemoryManagerBase<TVirtual, TPhysical>, IRefCounted public abstract class VirtualMemoryManagerRefCountedBase : VirtualMemoryManagerBase, IRefCounted
where TVirtual : IBinaryInteger<TVirtual>
where TPhysical : IBinaryInteger<TPhysical>
{ {
private int _referenceCount; private int _referenceCount;

View file

@ -1,5 +1,7 @@
using Ryujinx.Common.Memory;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -143,11 +145,11 @@ namespace Ryujinx.Graphics.Device
} }
else else
{ {
Memory<byte> memory = new byte[size]; IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size);
GetSpan(va, size).CopyTo(memory.Span); GetSpan(va, size).CopyTo(memoryOwner.Memory.Span);
return new WritableRegion(this, va, memory, tracked: true); return new WritableRegion(this, va, memoryOwner, tracked: true);
} }
} }

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.GAL
{
public interface IImageArray
{
void SetFormats(int index, Format[] imageFormats);
void SetImages(int index, ITexture[] images);
}
}

View file

@ -59,6 +59,7 @@ namespace Ryujinx.Graphics.GAL
void SetIndexBuffer(BufferRange buffer, IndexType type); void SetIndexBuffer(BufferRange buffer, IndexType type);
void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat); void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
void SetImageArray(ShaderStage stage, int binding, IImageArray array);
void SetLineParameters(float width, bool smooth); void SetLineParameters(float width, bool smooth);
@ -89,6 +90,7 @@ namespace Ryujinx.Graphics.GAL
void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers); void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers);
void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler); void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler);
void SetTextureArray(ShaderStage stage, int binding, ITextureArray array);
void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers); void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers); void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers);

View file

@ -21,10 +21,14 @@ namespace Ryujinx.Graphics.GAL
BufferHandle CreateBuffer(nint pointer, int size); BufferHandle CreateBuffer(nint pointer, int size);
BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers); BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers);
IImageArray CreateImageArray(int size, bool isBuffer);
IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info); IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
ISampler CreateSampler(SamplerCreateInfo info); ISampler CreateSampler(SamplerCreateInfo info);
ITexture CreateTexture(TextureCreateInfo info); ITexture CreateTexture(TextureCreateInfo info);
ITextureArray CreateTextureArray(int size, bool isBuffer);
bool PrepareHostMapping(nint address, ulong size); bool PrepareHostMapping(nint address, ulong size);
void CreateSync(ulong id, bool strict); void CreateSync(ulong id, bool strict);

View file

@ -1,4 +1,4 @@
using Ryujinx.Common.Memory; using System.Buffers;
namespace Ryujinx.Graphics.GAL namespace Ryujinx.Graphics.GAL
{ {
@ -17,10 +17,34 @@ namespace Ryujinx.Graphics.GAL
PinnedSpan<byte> GetData(); PinnedSpan<byte> GetData();
PinnedSpan<byte> GetData(int layer, int level); PinnedSpan<byte> GetData(int layer, int level);
void SetData(SpanOrArray<byte> data); /// <summary>
void SetData(SpanOrArray<byte> data, int layer, int level); /// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when
void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region); /// the operation completes.
/// </summary>
/// <param name="data">Texture data bytes</param>
void SetData(IMemoryOwner<byte> data);
/// <summary>
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when
/// the operation completes.
/// </summary>
/// <param name="data">Texture data bytes</param>
/// <param name="layer">Target layer</param>
/// <param name="level">Target level</param>
void SetData(IMemoryOwner<byte> data, int layer, int level);
/// <summary>
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when
/// the operation completes.
/// </summary>
/// <param name="data">Texture data bytes</param>
/// <param name="layer">Target layer</param>
/// <param name="level">Target level</param>
/// <param name="region">Target sub-region of the texture to update</param>
void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region);
void SetStorage(BufferRange buffer); void SetStorage(BufferRange buffer);
void Release(); void Release();
} }
} }

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.GAL
{
public interface ITextureArray
{
void SetSamplers(int index, ISampler[] samplers);
void SetTextures(int index, ITexture[] textures);
}
}

View file

@ -1,10 +1,12 @@
using Ryujinx.Graphics.GAL.Multithreading.Commands; using Ryujinx.Graphics.GAL.Multithreading.Commands;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer; using Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer;
using Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent; using Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent;
using Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Program; using Ryujinx.Graphics.GAL.Multithreading.Commands.Program;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer; using Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler; using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
using Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window; using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
using System; using System;
using System.Linq; using System.Linq;
@ -46,10 +48,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<CreateBufferAccessCommand>(CommandType.CreateBufferAccess); Register<CreateBufferAccessCommand>(CommandType.CreateBufferAccess);
Register<CreateBufferSparseCommand>(CommandType.CreateBufferSparse); Register<CreateBufferSparseCommand>(CommandType.CreateBufferSparse);
Register<CreateHostBufferCommand>(CommandType.CreateHostBuffer); Register<CreateHostBufferCommand>(CommandType.CreateHostBuffer);
Register<CreateImageArrayCommand>(CommandType.CreateImageArray);
Register<CreateProgramCommand>(CommandType.CreateProgram); Register<CreateProgramCommand>(CommandType.CreateProgram);
Register<CreateSamplerCommand>(CommandType.CreateSampler); Register<CreateSamplerCommand>(CommandType.CreateSampler);
Register<CreateSyncCommand>(CommandType.CreateSync); Register<CreateSyncCommand>(CommandType.CreateSync);
Register<CreateTextureCommand>(CommandType.CreateTexture); Register<CreateTextureCommand>(CommandType.CreateTexture);
Register<CreateTextureArrayCommand>(CommandType.CreateTextureArray);
Register<GetCapabilitiesCommand>(CommandType.GetCapabilities); Register<GetCapabilitiesCommand>(CommandType.GetCapabilities);
Register<PreFrameCommand>(CommandType.PreFrame); Register<PreFrameCommand>(CommandType.PreFrame);
Register<ReportCounterCommand>(CommandType.ReportCounter); Register<ReportCounterCommand>(CommandType.ReportCounter);
@ -63,6 +67,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose); Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose);
Register<CounterEventFlushCommand>(CommandType.CounterEventFlush); Register<CounterEventFlushCommand>(CommandType.CounterEventFlush);
Register<ImageArraySetFormatsCommand>(CommandType.ImageArraySetFormats);
Register<ImageArraySetImagesCommand>(CommandType.ImageArraySetImages);
Register<ProgramDisposeCommand>(CommandType.ProgramDispose); Register<ProgramDisposeCommand>(CommandType.ProgramDispose);
Register<ProgramGetBinaryCommand>(CommandType.ProgramGetBinary); Register<ProgramGetBinaryCommand>(CommandType.ProgramGetBinary);
Register<ProgramCheckLinkCommand>(CommandType.ProgramCheckLink); Register<ProgramCheckLinkCommand>(CommandType.ProgramCheckLink);
@ -82,6 +89,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion); Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion);
Register<TextureSetStorageCommand>(CommandType.TextureSetStorage); Register<TextureSetStorageCommand>(CommandType.TextureSetStorage);
Register<TextureArraySetSamplersCommand>(CommandType.TextureArraySetSamplers);
Register<TextureArraySetTexturesCommand>(CommandType.TextureArraySetTextures);
Register<WindowPresentCommand>(CommandType.WindowPresent); Register<WindowPresentCommand>(CommandType.WindowPresent);
Register<BarrierCommand>(CommandType.Barrier); Register<BarrierCommand>(CommandType.Barrier);
@ -114,6 +124,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<SetTransformFeedbackBuffersCommand>(CommandType.SetTransformFeedbackBuffers); Register<SetTransformFeedbackBuffersCommand>(CommandType.SetTransformFeedbackBuffers);
Register<SetUniformBuffersCommand>(CommandType.SetUniformBuffers); Register<SetUniformBuffersCommand>(CommandType.SetUniformBuffers);
Register<SetImageCommand>(CommandType.SetImage); Register<SetImageCommand>(CommandType.SetImage);
Register<SetImageArrayCommand>(CommandType.SetImageArray);
Register<SetIndexBufferCommand>(CommandType.SetIndexBuffer); Register<SetIndexBufferCommand>(CommandType.SetIndexBuffer);
Register<SetLineParametersCommand>(CommandType.SetLineParameters); Register<SetLineParametersCommand>(CommandType.SetLineParameters);
Register<SetLogicOpStateCommand>(CommandType.SetLogicOpState); Register<SetLogicOpStateCommand>(CommandType.SetLogicOpState);
@ -130,6 +141,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<SetScissorsCommand>(CommandType.SetScissor); Register<SetScissorsCommand>(CommandType.SetScissor);
Register<SetStencilTestCommand>(CommandType.SetStencilTest); Register<SetStencilTestCommand>(CommandType.SetStencilTest);
Register<SetTextureAndSamplerCommand>(CommandType.SetTextureAndSampler); Register<SetTextureAndSamplerCommand>(CommandType.SetTextureAndSampler);
Register<SetTextureArrayCommand>(CommandType.SetTextureArray);
Register<SetUserClipDistanceCommand>(CommandType.SetUserClipDistance); Register<SetUserClipDistanceCommand>(CommandType.SetUserClipDistance);
Register<SetVertexAttribsCommand>(CommandType.SetVertexAttribs); Register<SetVertexAttribsCommand>(CommandType.SetVertexAttribs);
Register<SetVertexBuffersCommand>(CommandType.SetVertexBuffers); Register<SetVertexBuffersCommand>(CommandType.SetVertexBuffers);

View file

@ -7,10 +7,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
CreateBufferAccess, CreateBufferAccess,
CreateBufferSparse, CreateBufferSparse,
CreateHostBuffer, CreateHostBuffer,
CreateImageArray,
CreateProgram, CreateProgram,
CreateSampler, CreateSampler,
CreateSync, CreateSync,
CreateTexture, CreateTexture,
CreateTextureArray,
GetCapabilities, GetCapabilities,
Unused, Unused,
PreFrame, PreFrame,
@ -25,6 +27,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
CounterEventDispose, CounterEventDispose,
CounterEventFlush, CounterEventFlush,
ImageArraySetFormats,
ImageArraySetImages,
ProgramDispose, ProgramDispose,
ProgramGetBinary, ProgramGetBinary,
ProgramCheckLink, ProgramCheckLink,
@ -44,6 +49,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
TextureSetDataSliceRegion, TextureSetDataSliceRegion,
TextureSetStorage, TextureSetStorage,
TextureArraySetSamplers,
TextureArraySetTextures,
WindowPresent, WindowPresent,
Barrier, Barrier,
@ -76,6 +84,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
SetTransformFeedbackBuffers, SetTransformFeedbackBuffers,
SetUniformBuffers, SetUniformBuffers,
SetImage, SetImage,
SetImageArray,
SetIndexBuffer, SetIndexBuffer,
SetLineParameters, SetLineParameters,
SetLogicOpState, SetLogicOpState,
@ -92,6 +101,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
SetScissor, SetScissor,
SetStencilTest, SetStencilTest,
SetTextureAndSampler, SetTextureAndSampler,
SetTextureArray,
SetUserClipDistance, SetUserClipDistance,
SetVertexAttribs, SetVertexAttribs,
SetVertexBuffers, SetVertexBuffers,

View file

@ -0,0 +1,26 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray
{
struct ImageArraySetFormatsCommand : IGALCommand, IGALCommand<ImageArraySetFormatsCommand>
{
public readonly CommandType CommandType => CommandType.ImageArraySetFormats;
private TableRef<ThreadedImageArray> _imageArray;
private int _index;
private TableRef<Format[]> _imageFormats;
public void Set(TableRef<ThreadedImageArray> imageArray, int index, TableRef<Format[]> imageFormats)
{
_imageArray = imageArray;
_index = index;
_imageFormats = imageFormats;
}
public static void Run(ref ImageArraySetFormatsCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ThreadedImageArray imageArray = command._imageArray.Get(threaded);
imageArray.Base.SetFormats(command._index, command._imageFormats.Get(threaded));
}
}
}

View file

@ -0,0 +1,27 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System.Linq;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray
{
struct ImageArraySetImagesCommand : IGALCommand, IGALCommand<ImageArraySetImagesCommand>
{
public readonly CommandType CommandType => CommandType.ImageArraySetImages;
private TableRef<ThreadedImageArray> _imageArray;
private int _index;
private TableRef<ITexture[]> _images;
public void Set(TableRef<ThreadedImageArray> imageArray, int index, TableRef<ITexture[]> images)
{
_imageArray = imageArray;
_index = index;
_images = images;
}
public static void Run(ref ImageArraySetImagesCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ThreadedImageArray imageArray = command._imageArray.Get(threaded);
imageArray.Base.SetImages(command._index, command._images.Get(threaded).Select(texture => ((ThreadedTexture)texture)?.Base).ToArray());
}
}
}

View file

@ -0,0 +1,25 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{
struct CreateImageArrayCommand : IGALCommand, IGALCommand<CreateImageArrayCommand>
{
public readonly CommandType CommandType => CommandType.CreateImageArray;
private TableRef<ThreadedImageArray> _imageArray;
private int _size;
private bool _isBuffer;
public void Set(TableRef<ThreadedImageArray> imageArray, int size, bool isBuffer)
{
_imageArray = imageArray;
_size = size;
_isBuffer = isBuffer;
}
public static void Run(ref CreateImageArrayCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
command._imageArray.Get(threaded).Base = renderer.CreateImageArray(command._size, command._isBuffer);
}
}
}

View file

@ -0,0 +1,25 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{
struct CreateTextureArrayCommand : IGALCommand, IGALCommand<CreateTextureArrayCommand>
{
public readonly CommandType CommandType => CommandType.CreateTextureArray;
private TableRef<ThreadedTextureArray> _textureArray;
private int _size;
private bool _isBuffer;
public void Set(TableRef<ThreadedTextureArray> textureArray, int size, bool isBuffer)
{
_textureArray = textureArray;
_size = size;
_isBuffer = isBuffer;
}
public static void Run(ref CreateTextureArrayCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
command._textureArray.Get(threaded).Base = renderer.CreateTextureArray(command._size, command._isBuffer);
}
}
}

View file

@ -0,0 +1,26 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{
struct SetImageArrayCommand : IGALCommand, IGALCommand<SetImageArrayCommand>
{
public readonly CommandType CommandType => CommandType.SetImageArray;
private ShaderStage _stage;
private int _binding;
private TableRef<IImageArray> _array;
public void Set(ShaderStage stage, int binding, TableRef<IImageArray> array)
{
_stage = stage;
_binding = binding;
_array = array;
}
public static void Run(ref SetImageArrayCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
renderer.Pipeline.SetImageArray(command._stage, command._binding, command._array.GetAs<ThreadedImageArray>(threaded)?.Base);
}
}
}

View file

@ -0,0 +1,26 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{
struct SetTextureArrayCommand : IGALCommand, IGALCommand<SetTextureArrayCommand>
{
public readonly CommandType CommandType => CommandType.SetTextureArray;
private ShaderStage _stage;
private int _binding;
private TableRef<ITextureArray> _array;
public void Set(ShaderStage stage, int binding, TableRef<ITextureArray> array)
{
_stage = stage;
_binding = binding;
_array = array;
}
public static void Run(ref SetTextureArrayCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
renderer.Pipeline.SetTextureArray(command._stage, command._binding, command._array.GetAs<ThreadedTextureArray>(threaded)?.Base);
}
}
}

View file

@ -1,6 +1,6 @@
using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources; using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System; using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
@ -8,9 +8,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
public readonly CommandType CommandType => CommandType.TextureSetData; public readonly CommandType CommandType => CommandType.TextureSetData;
private TableRef<ThreadedTexture> _texture; private TableRef<ThreadedTexture> _texture;
private TableRef<byte[]> _data; private TableRef<IMemoryOwner<byte>> _data;
public void Set(TableRef<ThreadedTexture> texture, TableRef<byte[]> data) public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data)
{ {
_texture = texture; _texture = texture;
_data = data; _data = data;
@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
public static void Run(ref TextureSetDataCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref TextureSetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
ThreadedTexture texture = command._texture.Get(threaded); ThreadedTexture texture = command._texture.Get(threaded);
texture.Base.SetData(new ReadOnlySpan<byte>(command._data.Get(threaded))); texture.Base.SetData(command._data.Get(threaded));
} }
} }
} }

View file

@ -1,6 +1,6 @@
using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources; using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System; using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
@ -8,11 +8,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
public readonly CommandType CommandType => CommandType.TextureSetDataSlice; public readonly CommandType CommandType => CommandType.TextureSetDataSlice;
private TableRef<ThreadedTexture> _texture; private TableRef<ThreadedTexture> _texture;
private TableRef<byte[]> _data; private TableRef<IMemoryOwner<byte>> _data;
private int _layer; private int _layer;
private int _level; private int _level;
public void Set(TableRef<ThreadedTexture> texture, TableRef<byte[]> data, int layer, int level) public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data, int layer, int level)
{ {
_texture = texture; _texture = texture;
_data = data; _data = data;
@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
public static void Run(ref TextureSetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref TextureSetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
ThreadedTexture texture = command._texture.Get(threaded); ThreadedTexture texture = command._texture.Get(threaded);
texture.Base.SetData(new ReadOnlySpan<byte>(command._data.Get(threaded)), command._layer, command._level); texture.Base.SetData(command._data.Get(threaded), command._layer, command._level);
} }
} }
} }

View file

@ -1,6 +1,6 @@
using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources; using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System; using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
@ -8,12 +8,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
public readonly CommandType CommandType => CommandType.TextureSetDataSliceRegion; public readonly CommandType CommandType => CommandType.TextureSetDataSliceRegion;
private TableRef<ThreadedTexture> _texture; private TableRef<ThreadedTexture> _texture;
private TableRef<byte[]> _data; private TableRef<IMemoryOwner<byte>> _data;
private int _layer; private int _layer;
private int _level; private int _level;
private Rectangle<int> _region; private Rectangle<int> _region;
public void Set(TableRef<ThreadedTexture> texture, TableRef<byte[]> data, int layer, int level, Rectangle<int> region) public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data, int layer, int level, Rectangle<int> region)
{ {
_texture = texture; _texture = texture;
_data = data; _data = data;
@ -25,7 +25,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
public static void Run(ref TextureSetDataSliceRegionCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref TextureSetDataSliceRegionCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
ThreadedTexture texture = command._texture.Get(threaded); ThreadedTexture texture = command._texture.Get(threaded);
texture.Base.SetData(new ReadOnlySpan<byte>(command._data.Get(threaded)), command._layer, command._level, command._region); texture.Base.SetData(command._data.Get(threaded), command._layer, command._level, command._region);
} }
} }
} }

View file

@ -0,0 +1,27 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System.Linq;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray
{
struct TextureArraySetSamplersCommand : IGALCommand, IGALCommand<TextureArraySetSamplersCommand>
{
public readonly CommandType CommandType => CommandType.TextureArraySetSamplers;
private TableRef<ThreadedTextureArray> _textureArray;
private int _index;
private TableRef<ISampler[]> _samplers;
public void Set(TableRef<ThreadedTextureArray> textureArray, int index, TableRef<ISampler[]> samplers)
{
_textureArray = textureArray;
_index = index;
_samplers = samplers;
}
public static void Run(ref TextureArraySetSamplersCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ThreadedTextureArray textureArray = command._textureArray.Get(threaded);
textureArray.Base.SetSamplers(command._index, command._samplers.Get(threaded).Select(sampler => ((ThreadedSampler)sampler)?.Base).ToArray());
}
}
}

View file

@ -0,0 +1,27 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System.Linq;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray
{
struct TextureArraySetTexturesCommand : IGALCommand, IGALCommand<TextureArraySetTexturesCommand>
{
public readonly CommandType CommandType => CommandType.TextureArraySetTextures;
private TableRef<ThreadedTextureArray> _textureArray;
private int _index;
private TableRef<ITexture[]> _textures;
public void Set(TableRef<ThreadedTextureArray> textureArray, int index, TableRef<ITexture[]> textures)
{
_textureArray = textureArray;
_index = index;
_textures = textures;
}
public static void Run(ref TextureArraySetTexturesCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ThreadedTextureArray textureArray = command._textureArray.Get(threaded);
textureArray.Base.SetTextures(command._index, command._textures.Get(threaded).Select(texture => ((ThreadedTexture)texture)?.Base).ToArray());
}
}
}

View file

@ -0,0 +1,36 @@
using Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray;
using Ryujinx.Graphics.GAL.Multithreading.Model;
namespace Ryujinx.Graphics.GAL.Multithreading.Resources
{
/// <summary>
/// Threaded representation of a image array.
/// </summary>
class ThreadedImageArray : IImageArray
{
private readonly ThreadedRenderer _renderer;
public IImageArray Base;
public ThreadedImageArray(ThreadedRenderer renderer)
{
_renderer = renderer;
}
private TableRef<T> Ref<T>(T reference)
{
return new TableRef<T>(_renderer, reference);
}
public void SetFormats(int index, Format[] imageFormats)
{
_renderer.New<ImageArraySetFormatsCommand>().Set(Ref(this), index, Ref(imageFormats));
_renderer.QueueCommand();
}
public void SetImages(int index, ITexture[] images)
{
_renderer.New<ImageArraySetImagesCommand>().Set(Ref(this), index, Ref(images));
_renderer.QueueCommand();
}
}
}

View file

@ -1,6 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Model;
using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Resources namespace Ryujinx.Graphics.GAL.Multithreading.Resources
{ {
@ -110,21 +110,24 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetData(SpanOrArray<byte> data) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data)
{ {
_renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data.ToArray())); _renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data));
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetData(SpanOrArray<byte> data, int layer, int level) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level)
{ {
_renderer.New<TextureSetDataSliceCommand>().Set(Ref(this), Ref(data.ToArray()), layer, level); _renderer.New<TextureSetDataSliceCommand>().Set(Ref(this), Ref(data), layer, level);
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
_renderer.New<TextureSetDataSliceRegionCommand>().Set(Ref(this), Ref(data.ToArray()), layer, level, region); _renderer.New<TextureSetDataSliceRegionCommand>().Set(Ref(this), Ref(data), layer, level, region);
_renderer.QueueCommand(); _renderer.QueueCommand();
} }

View file

@ -0,0 +1,37 @@
using Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray;
using Ryujinx.Graphics.GAL.Multithreading.Model;
using System.Linq;
namespace Ryujinx.Graphics.GAL.Multithreading.Resources
{
/// <summary>
/// Threaded representation of a texture and sampler array.
/// </summary>
class ThreadedTextureArray : ITextureArray
{
private readonly ThreadedRenderer _renderer;
public ITextureArray Base;
public ThreadedTextureArray(ThreadedRenderer renderer)
{
_renderer = renderer;
}
private TableRef<T> Ref<T>(T reference)
{
return new TableRef<T>(_renderer, reference);
}
public void SetSamplers(int index, ISampler[] samplers)
{
_renderer.New<TextureArraySetSamplersCommand>().Set(Ref(this), index, Ref(samplers.ToArray()));
_renderer.QueueCommand();
}
public void SetTextures(int index, ITexture[] textures)
{
_renderer.New<TextureArraySetTexturesCommand>().Set(Ref(this), index, Ref(textures.ToArray()));
_renderer.QueueCommand();
}
}
}

View file

@ -183,6 +183,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetImageArray(ShaderStage stage, int binding, IImageArray array)
{
_renderer.New<SetImageArrayCommand>().Set(stage, binding, Ref(array));
_renderer.QueueCommand();
}
public void SetIndexBuffer(BufferRange buffer, IndexType type) public void SetIndexBuffer(BufferRange buffer, IndexType type)
{ {
_renderer.New<SetIndexBufferCommand>().Set(buffer, type); _renderer.New<SetIndexBufferCommand>().Set(buffer, type);
@ -285,6 +291,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array)
{
_renderer.New<SetTextureArrayCommand>().Set(stage, binding, Ref(array));
_renderer.QueueCommand();
}
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers) public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
{ {
_renderer.New<SetTransformFeedbackBuffersCommand>().Set(_renderer.CopySpan(buffers)); _renderer.New<SetTransformFeedbackBuffersCommand>().Set(_renderer.CopySpan(buffers));

View file

@ -299,6 +299,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return handle; return handle;
} }
public IImageArray CreateImageArray(int size, bool isBuffer)
{
var imageArray = new ThreadedImageArray(this);
New<CreateImageArrayCommand>().Set(Ref(imageArray), size, isBuffer);
QueueCommand();
return imageArray;
}
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info) public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
{ {
var program = new ThreadedProgram(this); var program = new ThreadedProgram(this);
@ -349,6 +358,14 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return texture; return texture;
} }
} }
public ITextureArray CreateTextureArray(int size, bool isBuffer)
{
var textureArray = new ThreadedTextureArray(this);
New<CreateTextureArrayCommand>().Set(Ref(textureArray), size, isBuffer);
QueueCommand();
return textureArray;
}
public void DeleteBuffer(BufferHandle buffer) public void DeleteBuffer(BufferHandle buffer)
{ {

View file

@ -71,19 +71,21 @@ namespace Ryujinx.Graphics.GAL
public readonly struct ResourceUsage : IEquatable<ResourceUsage> public readonly struct ResourceUsage : IEquatable<ResourceUsage>
{ {
public int Binding { get; } public int Binding { get; }
public int ArrayLength { get; }
public ResourceType Type { get; } public ResourceType Type { get; }
public ResourceStages Stages { get; } public ResourceStages Stages { get; }
public ResourceUsage(int binding, ResourceType type, ResourceStages stages) public ResourceUsage(int binding, int arrayLength, ResourceType type, ResourceStages stages)
{ {
Binding = binding; Binding = binding;
ArrayLength = arrayLength;
Type = type; Type = type;
Stages = stages; Stages = stages;
} }
public override int GetHashCode() public override int GetHashCode()
{ {
return HashCode.Combine(Binding, Type, Stages); return HashCode.Combine(Binding, ArrayLength, Type, Stages);
} }
public override bool Equals(object obj) public override bool Equals(object obj)
@ -93,7 +95,7 @@ namespace Ryujinx.Graphics.GAL
public bool Equals(ResourceUsage other) public bool Equals(ResourceUsage other)
{ {
return Binding == other.Binding && Type == other.Type && Stages == other.Stages; return Binding == other.Binding && ArrayLength == other.ArrayLength && Type == other.Type && Stages == other.Stages;
} }
public static bool operator ==(ResourceUsage left, ResourceUsage right) public static bool operator ==(ResourceUsage left, ResourceUsage right)

View file

@ -89,5 +89,10 @@ namespace Ryujinx.Graphics.Gpu
/// Maximum size that an storage buffer is assumed to have when the correct size is unknown. /// Maximum size that an storage buffer is assumed to have when the correct size is unknown.
/// </summary> /// </summary>
public const ulong MaxUnknownStorageSize = 0x100000; public const ulong MaxUnknownStorageSize = 0x100000;
/// <summary>
/// Size of a bindless texture handle as exposed by guest graphics APIs.
/// </summary>
public const int TextureHandleSizeInBytes = sizeof(ulong);
} }
} }

View file

@ -4,6 +4,7 @@ using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -308,7 +309,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
if (target != null) if (target != null)
{ {
byte[] data; IMemoryOwner<byte> data;
if (srcLinear) if (srcLinear)
{ {
data = LayoutConverter.ConvertLinearStridedToLinear( data = LayoutConverter.ConvertLinearStridedToLinear(

View file

@ -1,4 +1,5 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Device; using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
using System; using System;
@ -198,7 +199,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
if (target != null) if (target != null)
{ {
target.SynchronizeMemory(); target.SynchronizeMemory();
target.SetData(data, 0, 0, new GAL.Rectangle<int>(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount)); var dataCopy = ByteMemoryPool.RentCopy(data);
target.SetData(dataCopy, 0, 0, new GAL.Rectangle<int>(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount));
target.SignalModified(); target.SignalModified();
return; return;

View file

@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
/// <returns>Texture target value</returns> /// <returns>Texture target value</returns>
public static Target GetTarget(SamplerType type) public static Target GetTarget(SamplerType type)
{ {
type &= ~(SamplerType.Indexed | SamplerType.Shadow); type &= ~SamplerType.Shadow;
switch (type) switch (type)
{ {

View file

@ -107,8 +107,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (texture.CacheNode != _textures.Last) if (texture.CacheNode != _textures.Last)
{ {
_textures.Remove(texture.CacheNode); _textures.Remove(texture.CacheNode);
_textures.AddLast(texture.CacheNode);
texture.CacheNode = _textures.AddLast(texture);
} }
if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion) if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion)

View file

@ -111,6 +111,21 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>The GPU resource with the given ID</returns> /// <returns>The GPU resource with the given ID</returns>
public abstract T1 Get(int id); public abstract T1 Get(int id);
/// <summary>
/// Gets the cached item with the given ID, or null if there is no cached item for the specified ID.
/// </summary>
/// <param name="id">ID of the item. This is effectively a zero-based index</param>
/// <returns>The cached item with the given ID</returns>
public T1 GetCachedItem(int id)
{
if (!IsValidId(id))
{
return default;
}
return Items[id];
}
/// <summary> /// <summary>
/// Checks if a given ID is valid and inside the range of the pool. /// Checks if a given ID is valid and inside the range of the pool.
/// </summary> /// </summary>
@ -197,6 +212,23 @@ namespace Ryujinx.Graphics.Gpu.Image
return false; return false;
} }
/// <summary>
/// Checks if the pool was modified by comparing the current <seealso cref="ModifiedSequenceNumber"/> with a cached one.
/// </summary>
/// <param name="sequenceNumber">Cached modified sequence number</param>
/// <returns>True if the pool was modified, false otherwise</returns>
public bool WasModified(ref int sequenceNumber)
{
if (sequenceNumber != ModifiedSequenceNumber)
{
sequenceNumber = ModifiedSequenceNumber;
return true;
}
return false;
}
protected abstract void InvalidateRangeImpl(ulong address, ulong size); protected abstract void InvalidateRangeImpl(ulong address, ulong size);
protected abstract void Delete(T1 item); protected abstract void Delete(T1 item);

View file

@ -1,5 +1,4 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
@ -7,6 +6,7 @@ using Ryujinx.Graphics.Texture.Astc;
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -573,7 +573,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary> /// <summary>
/// Discards all data for this texture. /// Discards all data for this texture.
/// This clears all dirty flags, modified flags, and pending copies from other textures. /// This clears all dirty flags and pending copies from other textures.
/// It should be used if the texture data will be fully overwritten by the next use. /// It should be used if the texture data will be fully overwritten by the next use.
/// </summary> /// </summary>
public void DiscardData() public void DiscardData()
@ -661,7 +661,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
SpanOrArray<byte> result = ConvertToHostCompatibleFormat(data); IMemoryOwner<byte> result = ConvertToHostCompatibleFormat(data);
if (ScaleFactor != 1f && AllowScaledSetData()) if (ScaleFactor != 1f && AllowScaledSetData())
{ {
@ -684,7 +684,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Uploads new texture data to the host GPU. /// Uploads new texture data to the host GPU.
/// </summary> /// </summary>
/// <param name="data">New data</param> /// <param name="data">New data</param>
public void SetData(SpanOrArray<byte> data) public void SetData(IMemoryOwner<byte> data)
{ {
BlacklistScale(); BlacklistScale();
@ -703,7 +703,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="data">New data</param> /// <param name="data">New data</param>
/// <param name="layer">Target layer</param> /// <param name="layer">Target layer</param>
/// <param name="level">Target level</param> /// <param name="level">Target level</param>
public void SetData(SpanOrArray<byte> data, int layer, int level) public void SetData(IMemoryOwner<byte> data, int layer, int level)
{ {
BlacklistScale(); BlacklistScale();
@ -721,7 +721,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="layer">Target layer</param> /// <param name="layer">Target layer</param>
/// <param name="level">Target level</param> /// <param name="level">Target level</param>
/// <param name="region">Target sub-region of the texture to update</param> /// <param name="region">Target sub-region of the texture to update</param>
public void SetData(ReadOnlySpan<byte> data, int layer, int level, Rectangle<int> region) public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
BlacklistScale(); BlacklistScale();
@ -739,7 +739,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="level">Mip level to convert</param> /// <param name="level">Mip level to convert</param>
/// <param name="single">True to convert a single slice</param> /// <param name="single">True to convert a single slice</param>
/// <returns>Converted data</returns> /// <returns>Converted data</returns>
public SpanOrArray<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false) public IMemoryOwner<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false)
{ {
int width = Info.Width; int width = Info.Width;
int height = Info.Height; int height = Info.Height;
@ -754,11 +754,11 @@ namespace Ryujinx.Graphics.Gpu.Image
int sliceDepth = single ? 1 : depth; int sliceDepth = single ? 1 : depth;
SpanOrArray<byte> result; IMemoryOwner<byte> linear;
if (Info.IsLinear) if (Info.IsLinear)
{ {
result = LayoutConverter.ConvertLinearStridedToLinear( linear = LayoutConverter.ConvertLinearStridedToLinear(
width, width,
height, height,
Info.FormatInfo.BlockWidth, Info.FormatInfo.BlockWidth,
@ -770,7 +770,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
else else
{ {
result = LayoutConverter.ConvertBlockLinearToLinear( linear = LayoutConverter.ConvertBlockLinearToLinear(
width, width,
height, height,
depth, depth,
@ -787,33 +787,41 @@ namespace Ryujinx.Graphics.Gpu.Image
data); data);
} }
IMemoryOwner<byte> result = linear;
// Handle compressed cases not supported by the host: // Handle compressed cases not supported by the host:
// - ASTC is usually not supported on desktop cards. // - ASTC is usually not supported on desktop cards.
// - BC4/BC5 is not supported on 3D textures. // - BC4/BC5 is not supported on 3D textures.
if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc()) if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc())
{ {
if (!AstcDecoder.TryDecodeToRgba8P( using (result)
result.ToArray(),
Info.FormatInfo.BlockWidth,
Info.FormatInfo.BlockHeight,
width,
height,
sliceDepth,
levels,
layers,
out byte[] decoded))
{ {
string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}"; if (!AstcDecoder.TryDecodeToRgba8P(
result.Memory,
Info.FormatInfo.BlockWidth,
Info.FormatInfo.BlockHeight,
width,
height,
sliceDepth,
levels,
layers,
out IMemoryOwner<byte> decoded))
{
string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}";
Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo})."); Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo}).");
}
if (GraphicsConfig.EnableTextureRecompression)
{
using (decoded)
{
return BCnEncoder.EncodeBC7(decoded.Memory, width, height, sliceDepth, levels, layers);
}
}
return decoded;
} }
if (GraphicsConfig.EnableTextureRecompression)
{
decoded = BCnEncoder.EncodeBC7(decoded, width, height, sliceDepth, levels, layers);
}
result = decoded;
} }
else if (!_context.Capabilities.SupportsEtc2Compression && Format.IsEtc2()) else if (!_context.Capabilities.SupportsEtc2Compression && Format.IsEtc2())
{ {
@ -821,16 +829,22 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
case Format.Etc2RgbaSrgb: case Format.Etc2RgbaSrgb:
case Format.Etc2RgbaUnorm: case Format.Etc2RgbaUnorm:
result = ETC2Decoder.DecodeRgba(result, width, height, sliceDepth, levels, layers); using (result)
break; {
return ETC2Decoder.DecodeRgba(result.Memory.Span, width, height, sliceDepth, levels, layers);
}
case Format.Etc2RgbPtaSrgb: case Format.Etc2RgbPtaSrgb:
case Format.Etc2RgbPtaUnorm: case Format.Etc2RgbPtaUnorm:
result = ETC2Decoder.DecodePta(result, width, height, sliceDepth, levels, layers); using (result)
break; {
return ETC2Decoder.DecodePta(result.Memory.Span, width, height, sliceDepth, levels, layers);
}
case Format.Etc2RgbSrgb: case Format.Etc2RgbSrgb:
case Format.Etc2RgbUnorm: case Format.Etc2RgbUnorm:
result = ETC2Decoder.DecodeRgb(result, width, height, sliceDepth, levels, layers); using (result)
break; {
return ETC2Decoder.DecodeRgb(result.Memory.Span, width, height, sliceDepth, levels, layers);
}
} }
} }
else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities)) else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities))
@ -839,48 +853,75 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
case Format.Bc1RgbaSrgb: case Format.Bc1RgbaSrgb:
case Format.Bc1RgbaUnorm: case Format.Bc1RgbaUnorm:
result = BCnDecoder.DecodeBC1(result, width, height, sliceDepth, levels, layers); using (result)
break; {
return BCnDecoder.DecodeBC1(result.Memory.Span, width, height, sliceDepth, levels, layers);
}
case Format.Bc2Srgb: case Format.Bc2Srgb:
case Format.Bc2Unorm: case Format.Bc2Unorm:
result = BCnDecoder.DecodeBC2(result, width, height, sliceDepth, levels, layers); using (result)
break; {
return BCnDecoder.DecodeBC2(result.Memory.Span, width, height, sliceDepth, levels, layers);
}
case Format.Bc3Srgb: case Format.Bc3Srgb:
case Format.Bc3Unorm: case Format.Bc3Unorm:
result = BCnDecoder.DecodeBC3(result, width, height, sliceDepth, levels, layers); using (result)
break; {
return BCnDecoder.DecodeBC3(result.Memory.Span, width, height, sliceDepth, levels, layers);
}
case Format.Bc4Snorm: case Format.Bc4Snorm:
case Format.Bc4Unorm: case Format.Bc4Unorm:
result = BCnDecoder.DecodeBC4(result, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm); using (result)
break; {
return BCnDecoder.DecodeBC4(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
}
case Format.Bc5Snorm: case Format.Bc5Snorm:
case Format.Bc5Unorm: case Format.Bc5Unorm:
result = BCnDecoder.DecodeBC5(result, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm); using (result)
break; {
return BCnDecoder.DecodeBC5(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
}
case Format.Bc6HSfloat: case Format.Bc6HSfloat:
case Format.Bc6HUfloat: case Format.Bc6HUfloat:
result = BCnDecoder.DecodeBC6(result, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat); using (result)
break; {
return BCnDecoder.DecodeBC6(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
}
case Format.Bc7Srgb: case Format.Bc7Srgb:
case Format.Bc7Unorm: case Format.Bc7Unorm:
result = BCnDecoder.DecodeBC7(result, width, height, sliceDepth, levels, layers); using (result)
break; {
return BCnDecoder.DecodeBC7(result.Memory.Span, width, height, sliceDepth, levels, layers);
}
} }
} }
else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm) else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm)
{ {
result = PixelConverter.ConvertR4G4ToR4G4B4A4(result, width); using (result)
if (!_context.Capabilities.SupportsR4G4B4A4Format)
{ {
result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width); var converted = PixelConverter.ConvertR4G4ToR4G4B4A4(result.Memory.Span, width);
if (_context.Capabilities.SupportsR4G4B4A4Format)
{
return converted;
}
else
{
using (converted)
{
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(converted.Memory.Span, width);
}
}
} }
} }
else if (Format == Format.R4G4B4A4Unorm) else if (Format == Format.R4G4B4A4Unorm)
{ {
if (!_context.Capabilities.SupportsR4G4B4A4Format) if (!_context.Capabilities.SupportsR4G4B4A4Format)
{ {
result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width); using (result)
{
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width);
}
} }
} }
else if (!_context.Capabilities.Supports5BitComponentFormat && Format.Is16BitPacked()) else if (!_context.Capabilities.Supports5BitComponentFormat && Format.Is16BitPacked())
@ -889,19 +930,27 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
case Format.B5G6R5Unorm: case Format.B5G6R5Unorm:
case Format.R5G6B5Unorm: case Format.R5G6B5Unorm:
result = PixelConverter.ConvertR5G6B5ToR8G8B8A8(result, width); using (result)
break; {
return PixelConverter.ConvertR5G6B5ToR8G8B8A8(result.Memory.Span, width);
}
case Format.B5G5R5A1Unorm: case Format.B5G5R5A1Unorm:
case Format.R5G5B5X1Unorm: case Format.R5G5B5X1Unorm:
case Format.R5G5B5A1Unorm: case Format.R5G5B5A1Unorm:
result = PixelConverter.ConvertR5G5B5ToR8G8B8A8(result, width, Format == Format.R5G5B5X1Unorm); using (result)
break; {
return PixelConverter.ConvertR5G5B5ToR8G8B8A8(result.Memory.Span, width, Format == Format.R5G5B5X1Unorm);
}
case Format.A1B5G5R5Unorm: case Format.A1B5G5R5Unorm:
result = PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result, width); using (result)
break; {
return PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result.Memory.Span, width);
}
case Format.R4G4B4A4Unorm: case Format.R4G4B4A4Unorm:
result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width); using (result)
break; {
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width);
}
} }
} }

View file

@ -24,6 +24,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public int Binding { get; } public int Binding { get; }
/// <summary>
/// For array of textures, this indicates the length of the array. A value of one indicates it is not an array.
/// </summary>
public int ArrayLength { get; }
/// <summary> /// <summary>
/// Constant buffer slot with the texture handle. /// Constant buffer slot with the texture handle.
/// </summary> /// </summary>
@ -45,14 +50,16 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="target">The shader sampler target type</param> /// <param name="target">The shader sampler target type</param>
/// <param name="format">Format of the image as declared on the shader</param> /// <param name="format">Format of the image as declared on the shader</param>
/// <param name="binding">The shader texture binding point</param> /// <param name="binding">The shader texture binding point</param>
/// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param>
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param> /// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param> /// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param> /// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
public TextureBindingInfo(Target target, Format format, int binding, int cbufSlot, int handle, TextureUsageFlags flags) public TextureBindingInfo(Target target, Format format, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags)
{ {
Target = target; Target = target;
Format = format; Format = format;
Binding = binding; Binding = binding;
ArrayLength = arrayLength;
CbufSlot = cbufSlot; CbufSlot = cbufSlot;
Handle = handle; Handle = handle;
Flags = flags; Flags = flags;
@ -63,10 +70,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
/// <param name="target">The shader sampler target type</param> /// <param name="target">The shader sampler target type</param>
/// <param name="binding">The shader texture binding point</param> /// <param name="binding">The shader texture binding point</param>
/// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param>
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param> /// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param> /// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param> /// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
public TextureBindingInfo(Target target, int binding, int cbufSlot, int handle, TextureUsageFlags flags) : this(target, (Format)0, binding, cbufSlot, handle, flags) public TextureBindingInfo(Target target, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags) : this(target, (Format)0, binding, arrayLength, cbufSlot, handle, flags)
{ {
} }
} }

View file

@ -0,0 +1,730 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Shader;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Texture bindings array cache.
/// </summary>
class TextureBindingsArrayCache
{
/// <summary>
/// Minimum timestamp delta until texture array can be removed from the cache.
/// </summary>
private const int MinDeltaForRemoval = 20000;
private readonly GpuContext _context;
private readonly GpuChannel _channel;
private readonly bool _isCompute;
/// <summary>
/// Array cache entry key.
/// </summary>
private readonly struct CacheEntryKey : IEquatable<CacheEntryKey>
{
/// <summary>
/// Whether the entry is for an image.
/// </summary>
public readonly bool IsImage;
/// <summary>
/// Texture or image target type.
/// </summary>
public readonly Target Target;
/// <summary>
/// Word offset of the first handle on the constant buffer.
/// </summary>
public readonly int HandleIndex;
/// <summary>
/// Number of entries of the array.
/// </summary>
public readonly int ArrayLength;
private readonly TexturePool _texturePool;
private readonly SamplerPool _samplerPool;
private readonly BufferBounds _textureBufferBounds;
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="isImage">Whether the entry is for an image</param>
/// <param name="bindingInfo">Binding information for the array</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
/// <param name="textureBufferBounds">Constant buffer bounds with the texture handles</param>
public CacheEntryKey(
bool isImage,
TextureBindingInfo bindingInfo,
TexturePool texturePool,
SamplerPool samplerPool,
ref BufferBounds textureBufferBounds)
{
IsImage = isImage;
Target = bindingInfo.Target;
HandleIndex = bindingInfo.Handle;
ArrayLength = bindingInfo.ArrayLength;
_texturePool = texturePool;
_samplerPool = samplerPool;
_textureBufferBounds = textureBufferBounds;
}
/// <summary>
/// Checks if the texture and sampler pools matches the cached pools.
/// </summary>
/// <param name="texturePool">Texture pool instance</param>
/// <param name="samplerPool">Sampler pool instance</param>
/// <returns>True if the pools match, false otherwise</returns>
private bool MatchesPools(TexturePool texturePool, SamplerPool samplerPool)
{
return _texturePool == texturePool && _samplerPool == samplerPool;
}
/// <summary>
/// Checks if the cached constant buffer address and size matches.
/// </summary>
/// <param name="textureBufferBounds">New buffer address and size</param>
/// <returns>True if the address and size matches, false otherwise</returns>
private bool MatchesBufferBounds(BufferBounds textureBufferBounds)
{
return _textureBufferBounds.Equals(textureBufferBounds);
}
public bool Equals(CacheEntryKey other)
{
return IsImage == other.IsImage &&
Target == other.Target &&
HandleIndex == other.HandleIndex &&
ArrayLength == other.ArrayLength &&
MatchesPools(other._texturePool, other._samplerPool) &&
MatchesBufferBounds(other._textureBufferBounds);
}
public override bool Equals(object obj)
{
return obj is CacheEntryKey other && Equals(other);
}
public override int GetHashCode()
{
return _textureBufferBounds.Range.GetHashCode();
}
}
/// <summary>
/// Array cache entry.
/// </summary>
private class CacheEntry
{
/// <summary>
/// Key for this entry on the cache.
/// </summary>
public readonly CacheEntryKey Key;
/// <summary>
/// Linked list node used on the texture bindings array cache.
/// </summary>
public LinkedListNode<CacheEntry> CacheNode;
/// <summary>
/// Timestamp set on the last use of the array by the cache.
/// </summary>
public int CacheTimestamp;
/// <summary>
/// All cached textures, along with their invalidated sequence number as value.
/// </summary>
public readonly Dictionary<Texture, int> Textures;
/// <summary>
/// All pool texture IDs along with their textures.
/// </summary>
public readonly Dictionary<int, Texture> TextureIds;
/// <summary>
/// All pool sampler IDs along with their samplers.
/// </summary>
public readonly Dictionary<int, Sampler> SamplerIds;
/// <summary>
/// Backend texture array if the entry is for a texture, otherwise null.
/// </summary>
public readonly ITextureArray TextureArray;
/// <summary>
/// Backend image array if the entry is for an image, otherwise null.
/// </summary>
public readonly IImageArray ImageArray;
private readonly TexturePool _texturePool;
private readonly SamplerPool _samplerPool;
private int _texturePoolSequence;
private int _samplerPoolSequence;
private int[] _cachedTextureBuffer;
private int[] _cachedSamplerBuffer;
private int _lastSequenceNumber;
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="key">Key for this entry on the cache</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
private CacheEntry(ref CacheEntryKey key, TexturePool texturePool, SamplerPool samplerPool)
{
Key = key;
Textures = new Dictionary<Texture, int>();
TextureIds = new Dictionary<int, Texture>();
SamplerIds = new Dictionary<int, Sampler>();
_texturePool = texturePool;
_samplerPool = samplerPool;
_lastSequenceNumber = -1;
}
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="key">Key for this entry on the cache</param>
/// <param name="array">Backend texture array</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
public CacheEntry(ref CacheEntryKey key, ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : this(ref key, texturePool, samplerPool)
{
TextureArray = array;
}
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="key">Key for this entry on the cache</param>
/// <param name="array">Backend image array</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
public CacheEntry(ref CacheEntryKey key, IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : this(ref key, texturePool, samplerPool)
{
ImageArray = array;
}
/// <summary>
/// Synchronizes memory for all textures in the array.
/// </summary>
/// <param name="isStore">Indicates if the texture may be modified by the access</param>
/// <param name="blacklistScale">Indicates if the texture should be blacklisted for scaling</param>
public void SynchronizeMemory(bool isStore, bool blacklistScale)
{
foreach (Texture texture in Textures.Keys)
{
texture.SynchronizeMemory();
if (isStore)
{
texture.SignalModified();
}
if (blacklistScale && texture.ScaleMode != TextureScaleMode.Blacklisted)
{
// Scaling textures used on arrays is currently not supported.
texture.BlacklistScale();
}
}
}
/// <summary>
/// Clears all cached texture instances.
/// </summary>
public void Reset()
{
Textures.Clear();
TextureIds.Clear();
SamplerIds.Clear();
}
/// <summary>
/// Updates the cached constant buffer data.
/// </summary>
/// <param name="cachedTextureBuffer">Constant buffer data with the texture handles (and sampler handles, if they are combined)</param>
/// <param name="cachedSamplerBuffer">Constant buffer data with the sampler handles</param>
/// <param name="separateSamplerBuffer">Whether <paramref name="cachedTextureBuffer"/> and <paramref name="cachedSamplerBuffer"/> comes from different buffers</param>
public void UpdateData(ReadOnlySpan<int> cachedTextureBuffer, ReadOnlySpan<int> cachedSamplerBuffer, bool separateSamplerBuffer)
{
_cachedTextureBuffer = cachedTextureBuffer.ToArray();
_cachedSamplerBuffer = separateSamplerBuffer ? cachedSamplerBuffer.ToArray() : _cachedTextureBuffer;
}
/// <summary>
/// Checks if any texture has been deleted since the last call to this method.
/// </summary>
/// <returns>True if one or more textures have been deleted, false otherwise</returns>
public bool ValidateTextures()
{
foreach ((Texture texture, int invalidatedSequence) in Textures)
{
if (texture.InvalidatedSequence != invalidatedSequence)
{
return false;
}
}
return true;
}
/// <summary>
/// Checks if the cached texture or sampler pool has been modified since the last call to this method.
/// </summary>
/// <returns>True if any used entries of the pools might have been modified, false otherwise</returns>
public bool PoolsModified()
{
bool texturePoolModified = _texturePool.WasModified(ref _texturePoolSequence);
bool samplerPoolModified = _samplerPool.WasModified(ref _samplerPoolSequence);
// If both pools were not modified since the last check, we have nothing else to check.
if (!texturePoolModified && !samplerPoolModified)
{
return false;
}
// If the pools were modified, let's check if any of the entries we care about changed.
// Check if any of our cached textures changed on the pool.
foreach ((int textureId, Texture texture) in TextureIds)
{
if (_texturePool.GetCachedItem(textureId) != texture)
{
return true;
}
}
// Check if any of our cached samplers changed on the pool.
foreach ((int samplerId, Sampler sampler) in SamplerIds)
{
if (_samplerPool.GetCachedItem(samplerId) != sampler)
{
return true;
}
}
return false;
}
/// <summary>
/// Checks if the sequence number matches the one used on the last call to this method.
/// </summary>
/// <param name="currentSequenceNumber">Current sequence number</param>
/// <returns>True if the sequence numbers match, false otherwise</returns>
public bool MatchesSequenceNumber(int currentSequenceNumber)
{
if (_lastSequenceNumber == currentSequenceNumber)
{
return true;
}
_lastSequenceNumber = currentSequenceNumber;
return false;
}
/// <summary>
/// Checks if the buffer data matches the cached data.
/// </summary>
/// <param name="cachedTextureBuffer">New texture buffer data</param>
/// <param name="cachedSamplerBuffer">New sampler buffer data</param>
/// <param name="separateSamplerBuffer">Whether <paramref name="cachedTextureBuffer"/> and <paramref name="cachedSamplerBuffer"/> comes from different buffers</param>
/// <param name="samplerWordOffset">Word offset of the sampler constant buffer handle that is used</param>
/// <returns>True if the data matches, false otherwise</returns>
public bool MatchesBufferData(
ReadOnlySpan<int> cachedTextureBuffer,
ReadOnlySpan<int> cachedSamplerBuffer,
bool separateSamplerBuffer,
int samplerWordOffset)
{
if (_cachedTextureBuffer != null && cachedTextureBuffer.Length > _cachedTextureBuffer.Length)
{
cachedTextureBuffer = cachedTextureBuffer[.._cachedTextureBuffer.Length];
}
if (!_cachedTextureBuffer.AsSpan().SequenceEqual(cachedTextureBuffer))
{
return false;
}
if (separateSamplerBuffer)
{
if (_cachedSamplerBuffer == null ||
_cachedSamplerBuffer.Length <= samplerWordOffset ||
cachedSamplerBuffer.Length <= samplerWordOffset)
{
return false;
}
int oldValue = _cachedSamplerBuffer[samplerWordOffset];
int newValue = cachedSamplerBuffer[samplerWordOffset];
return oldValue == newValue;
}
return true;
}
}
private readonly Dictionary<CacheEntryKey, CacheEntry> _cache;
private readonly LinkedList<CacheEntry> _lruCache;
private int _currentTimestamp;
/// <summary>
/// Creates a new instance of the texture bindings array cache.
/// </summary>
/// <param name="context">GPU context</param>
/// <param name="channel">GPU channel</param>
/// <param name="isCompute">Whether the bindings will be used for compute or graphics pipelines</param>
public TextureBindingsArrayCache(GpuContext context, GpuChannel channel, bool isCompute)
{
_context = context;
_channel = channel;
_isCompute = isCompute;
_cache = new Dictionary<CacheEntryKey, CacheEntry>();
_lruCache = new LinkedList<CacheEntry>();
}
/// <summary>
/// Updates a texture array bindings and textures.
/// </summary>
/// <param name="texturePool">Texture pool</param>
/// <param name="samplerPool">Sampler pool</param>
/// <param name="stage">Shader stage where the array is used</param>
/// <param name="stageIndex">Shader stage index where the array is used</param>
/// <param name="textureBufferIndex">Texture constant buffer index</param>
/// <param name="samplerIndex">Sampler handles source</param>
/// <param name="bindingInfo">Array binding information</param>
public void UpdateTextureArray(
TexturePool texturePool,
SamplerPool samplerPool,
ShaderStage stage,
int stageIndex,
int textureBufferIndex,
SamplerIndex samplerIndex,
TextureBindingInfo bindingInfo)
{
Update(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage: false, samplerIndex, bindingInfo);
}
/// <summary>
/// Updates a image array bindings and textures.
/// </summary>
/// <param name="texturePool">Texture pool</param>
/// <param name="stage">Shader stage where the array is used</param>
/// <param name="stageIndex">Shader stage index where the array is used</param>
/// <param name="textureBufferIndex">Texture constant buffer index</param>
/// <param name="bindingInfo">Array binding information</param>
public void UpdateImageArray(TexturePool texturePool, ShaderStage stage, int stageIndex, int textureBufferIndex, TextureBindingInfo bindingInfo)
{
Update(texturePool, null, stage, stageIndex, textureBufferIndex, isImage: true, SamplerIndex.ViaHeaderIndex, bindingInfo);
}
/// <summary>
/// Updates a texture or image array bindings and textures.
/// </summary>
/// <param name="texturePool">Texture pool</param>
/// <param name="samplerPool">Sampler pool</param>
/// <param name="stage">Shader stage where the array is used</param>
/// <param name="stageIndex">Shader stage index where the array is used</param>
/// <param name="textureBufferIndex">Texture constant buffer index</param>
/// <param name="isImage">Whether the array is a image or texture array</param>
/// <param name="samplerIndex">Sampler handles source</param>
/// <param name="bindingInfo">Array binding information</param>
private void Update(
TexturePool texturePool,
SamplerPool samplerPool,
ShaderStage stage,
int stageIndex,
int textureBufferIndex,
bool isImage,
SamplerIndex samplerIndex,
TextureBindingInfo bindingInfo)
{
(textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex);
bool separateSamplerBuffer = textureBufferIndex != samplerBufferIndex;
ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex);
ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex);
CacheEntry entry = GetOrAddEntry(
texturePool,
samplerPool,
bindingInfo,
isImage,
ref textureBufferBounds,
out bool isNewEntry);
bool poolsModified = entry.PoolsModified();
bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
bool resScaleUnsupported = bindingInfo.Flags.HasFlag(TextureUsageFlags.ResScaleUnsupported);
ReadOnlySpan<int> cachedTextureBuffer;
ReadOnlySpan<int> cachedSamplerBuffer;
if (!poolsModified && !isNewEntry && entry.ValidateTextures())
{
if (entry.MatchesSequenceNumber(_context.SequenceNumber))
{
entry.SynchronizeMemory(isStore, resScaleUnsupported);
if (isImage)
{
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
}
else
{
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
}
return;
}
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range));
if (separateSamplerBuffer)
{
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range));
}
else
{
cachedSamplerBuffer = cachedTextureBuffer;
}
(_, int samplerWordOffset, _) = TextureHandle.UnpackOffsets(bindingInfo.Handle);
if (entry.MatchesBufferData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer, samplerWordOffset))
{
entry.SynchronizeMemory(isStore, resScaleUnsupported);
if (isImage)
{
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
}
else
{
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
}
return;
}
}
else
{
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range));
if (separateSamplerBuffer)
{
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range));
}
else
{
cachedSamplerBuffer = cachedTextureBuffer;
}
}
if (!isNewEntry)
{
entry.Reset();
}
entry.UpdateData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer);
Format[] formats = isImage ? new Format[bindingInfo.ArrayLength] : null;
ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength];
ITexture[] textures = new ITexture[bindingInfo.ArrayLength];
for (int index = 0; index < bindingInfo.ArrayLength; index++)
{
int handleIndex = bindingInfo.Handle + index * (Constants.TextureHandleSizeInBytes / sizeof(int));
int packedId = TextureHandle.ReadPackedId(handleIndex, cachedTextureBuffer, cachedSamplerBuffer);
int textureId = TextureHandle.UnpackTextureId(packedId);
int samplerId;
if (samplerIndex == SamplerIndex.ViaHeaderIndex)
{
samplerId = textureId;
}
else
{
samplerId = TextureHandle.UnpackSamplerId(packedId);
}
ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, out Texture texture);
if (texture != null)
{
entry.Textures[texture] = texture.InvalidatedSequence;
if (isStore)
{
texture.SignalModified();
}
if (resScaleUnsupported && texture.ScaleMode != TextureScaleMode.Blacklisted)
{
// Scaling textures used on arrays is currently not supported.
texture.BlacklistScale();
}
}
Sampler sampler = samplerPool?.Get(samplerId);
entry.TextureIds[textureId] = texture;
entry.SamplerIds[samplerId] = sampler;
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
ISampler hostSampler = sampler?.GetHostSampler(texture);
Format format = bindingInfo.Format;
if (hostTexture != null && texture.Target == Target.TextureBuffer)
{
// Ensure that the buffer texture is using the correct buffer as storage.
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted.
if (isImage)
{
if (format == 0 && texture != null)
{
format = texture.Format;
}
_channel.BufferManager.SetBufferTextureStorage(entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
}
else
{
_channel.BufferManager.SetBufferTextureStorage(entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
}
}
else if (isImage)
{
if (format == 0 && texture != null)
{
format = texture.Format;
}
formats[index] = format;
textures[index] = hostTexture;
}
else
{
samplers[index] = hostSampler;
textures[index] = hostTexture;
}
}
if (isImage)
{
entry.ImageArray.SetFormats(0, formats);
entry.ImageArray.SetImages(0, textures);
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
}
else
{
entry.TextureArray.SetSamplers(0, samplers);
entry.TextureArray.SetTextures(0, textures);
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
}
}
/// <summary>
/// Gets a cached texture entry, or creates a new one if not found.
/// </summary>
/// <param name="texturePool">Texture pool</param>
/// <param name="samplerPool">Sampler pool</param>
/// <param name="bindingInfo">Array binding information</param>
/// <param name="isImage">Whether the array is a image or texture array</param>
/// <param name="textureBufferBounds">Constant buffer bounds with the texture handles</param>
/// <param name="isNew">Whether a new entry was created, or an existing one was returned</param>
/// <returns>Cache entry</returns>
private CacheEntry GetOrAddEntry(
TexturePool texturePool,
SamplerPool samplerPool,
TextureBindingInfo bindingInfo,
bool isImage,
ref BufferBounds textureBufferBounds,
out bool isNew)
{
CacheEntryKey key = new CacheEntryKey(
isImage,
bindingInfo,
texturePool,
samplerPool,
ref textureBufferBounds);
isNew = !_cache.TryGetValue(key, out CacheEntry entry);
if (isNew)
{
int arrayLength = bindingInfo.ArrayLength;
if (isImage)
{
IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
_cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool));
}
else
{
ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
_cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool));
}
}
if (entry.CacheNode != null)
{
_lruCache.Remove(entry.CacheNode);
_lruCache.AddLast(entry.CacheNode);
}
else
{
entry.CacheNode = _lruCache.AddLast(entry);
}
entry.CacheTimestamp = ++_currentTimestamp;
RemoveLeastUsedEntries();
return entry;
}
/// <summary>
/// Remove entries from the cache that have not been used for some time.
/// </summary>
private void RemoveLeastUsedEntries()
{
LinkedListNode<CacheEntry> nextNode = _lruCache.First;
while (nextNode != null && _currentTimestamp - nextNode.Value.CacheTimestamp >= MinDeltaForRemoval)
{
LinkedListNode<CacheEntry> toRemove = nextNode;
nextNode = nextNode.Next;
_cache.Remove(toRemove.Value.Key);
_lruCache.Remove(toRemove);
}
}
}
}

View file

@ -34,6 +34,8 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly TexturePoolCache _texturePoolCache; private readonly TexturePoolCache _texturePoolCache;
private readonly SamplerPoolCache _samplerPoolCache; private readonly SamplerPoolCache _samplerPoolCache;
private readonly TextureBindingsArrayCache _arrayBindingsCache;
private TexturePool _cachedTexturePool; private TexturePool _cachedTexturePool;
private SamplerPool _cachedSamplerPool; private SamplerPool _cachedSamplerPool;
@ -56,6 +58,8 @@ namespace Ryujinx.Graphics.Gpu.Image
private TextureState[] _textureState; private TextureState[] _textureState;
private TextureState[] _imageState; private TextureState[] _imageState;
private int[] _textureCounts;
private int _texturePoolSequence; private int _texturePoolSequence;
private int _samplerPoolSequence; private int _samplerPoolSequence;
@ -85,6 +89,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_isCompute = isCompute; _isCompute = isCompute;
_arrayBindingsCache = new TextureBindingsArrayCache(context, channel, isCompute);
int stages = isCompute ? 1 : Constants.ShaderStages; int stages = isCompute ? 1 : Constants.ShaderStages;
_textureBindings = new TextureBindingInfo[stages][]; _textureBindings = new TextureBindingInfo[stages][];
@ -95,9 +101,11 @@ namespace Ryujinx.Graphics.Gpu.Image
for (int stage = 0; stage < stages; stage++) for (int stage = 0; stage < stages; stage++)
{ {
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize]; _textureBindings[stage] = Array.Empty<TextureBindingInfo>();
_imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize]; _imageBindings[stage] = Array.Empty<TextureBindingInfo>();
} }
_textureCounts = Array.Empty<int>();
} }
/// <summary> /// <summary>
@ -109,6 +117,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_textureBindings = bindings.TextureBindings; _textureBindings = bindings.TextureBindings;
_imageBindings = bindings.ImageBindings; _imageBindings = bindings.ImageBindings;
_textureCounts = bindings.TextureCounts;
SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding); SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding);
} }
@ -401,27 +411,6 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
#pragma warning disable IDE0051 // Remove unused private member
/// <summary>
/// Counts the total number of texture bindings used by all shader stages.
/// </summary>
/// <returns>The total amount of textures used</returns>
private int GetTextureBindingsCount()
{
int count = 0;
foreach (TextureBindingInfo[] textureInfo in _textureBindings)
{
if (textureInfo != null)
{
count += textureInfo.Length;
}
}
return count;
}
#pragma warning restore IDE0051
/// <summary> /// <summary>
/// Ensures that the texture bindings are visible to the host GPU. /// Ensures that the texture bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API. /// Note: this actually performs the binding using the host graphics API.
@ -465,6 +454,13 @@ namespace Ryujinx.Graphics.Gpu.Image
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index]; TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
TextureUsageFlags usageFlags = bindingInfo.Flags; TextureUsageFlags usageFlags = bindingInfo.Flags;
if (bindingInfo.ArrayLength > 1)
{
_arrayBindingsCache.UpdateTextureArray(texturePool, samplerPool, stage, stageIndex, _textureBufferIndex, _samplerIndex, bindingInfo);
continue;
}
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex); UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
@ -582,7 +578,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
// Scales for images appear after the texture ones. // Scales for images appear after the texture ones.
int baseScaleIndex = _textureBindings[stageIndex].Length; int baseScaleIndex = _textureCounts[stageIndex];
int cachedTextureBufferIndex = -1; int cachedTextureBufferIndex = -1;
int cachedSamplerBufferIndex = -1; int cachedSamplerBufferIndex = -1;
@ -595,6 +591,14 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index]; TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
TextureUsageFlags usageFlags = bindingInfo.Flags; TextureUsageFlags usageFlags = bindingInfo.Flags;
if (bindingInfo.ArrayLength > 1)
{
_arrayBindingsCache.UpdateImageArray(pool, stage, stageIndex, _textureBufferIndex, bindingInfo);
continue;
}
int scaleIndex = baseScaleIndex + index; int scaleIndex = baseScaleIndex + index;
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
@ -620,7 +624,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isStore) if (isStore)
{ {
cachedTexture?.SignalModified(); cachedTexture.SignalModified();
} }
Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format; Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format;

View file

@ -247,6 +247,10 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
return TextureMatchQuality.FormatAlias; return TextureMatchQuality.FormatAlias;
} }
else if (lhs.FormatInfo.Format == Format.D32FloatS8Uint && rhs.FormatInfo.Format == Format.R32G32Float)
{
return TextureMatchQuality.FormatAlias;
}
} }
return lhs.FormatInfo.Format == rhs.FormatInfo.Format ? TextureMatchQuality.Perfect : TextureMatchQuality.NoMatch; return lhs.FormatInfo.Format == rhs.FormatInfo.Format ? TextureMatchQuality.Perfect : TextureMatchQuality.NoMatch;

View file

@ -1,4 +1,3 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
@ -6,6 +5,7 @@ using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking; using Ryujinx.Memory.Tracking;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -282,7 +282,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary> /// <summary>
/// Discards all data for a given texture. /// Discards all data for a given texture.
/// This clears all dirty flags, modified flags, and pending copies from other textures. /// This clears all dirty flags and pending copies from other textures.
/// </summary> /// </summary>
/// <param name="texture">The texture being discarded</param> /// <param name="texture">The texture being discarded</param>
public void DiscardData(Texture texture) public void DiscardData(Texture texture)
@ -445,7 +445,7 @@ namespace Ryujinx.Graphics.Gpu.Image
ReadOnlySpan<byte> data = dataSpan[(offset - spanBase)..]; ReadOnlySpan<byte> data = dataSpan[(offset - spanBase)..];
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true); IMemoryOwner<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level); Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level);
} }

View file

@ -182,11 +182,10 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary> /// <summary>
/// Discards all data for this handle. /// Discards all data for this handle.
/// This clears all dirty flags, modified flags, and pending copies from other handles. /// This clears all dirty flags and pending copies from other handles.
/// </summary> /// </summary>
public void DiscardData() public void DiscardData()
{ {
Modified = false;
DeferredCopy = null; DeferredCopy = null;
foreach (RegionHandle handle in Handles) foreach (RegionHandle handle in Handles)

View file

@ -1,12 +1,13 @@
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System;
namespace Ryujinx.Graphics.Gpu.Memory namespace Ryujinx.Graphics.Gpu.Memory
{ {
/// <summary> /// <summary>
/// Memory range used for buffers. /// Memory range used for buffers.
/// </summary> /// </summary>
readonly struct BufferBounds readonly struct BufferBounds : IEquatable<BufferBounds>
{ {
/// <summary> /// <summary>
/// Physical memory ranges where the buffer is mapped. /// Physical memory ranges where the buffer is mapped.
@ -33,5 +34,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
Range = range; Range = range;
Flags = flags; Flags = flags;
} }
public override bool Equals(object obj)
{
return obj is BufferBounds bounds && Equals(bounds);
}
public bool Equals(BufferBounds bounds)
{
return Range == bounds.Range && Flags == bounds.Flags;
}
public bool Equals(ref BufferBounds bounds)
{
return Range == bounds.Range && Flags == bounds.Flags;
}
public override int GetHashCode()
{
return HashCode.Combine(Range, Flags);
}
} }
} }

View file

@ -27,6 +27,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly VertexBuffer[] _vertexBuffers; private readonly VertexBuffer[] _vertexBuffers;
private readonly BufferBounds[] _transformFeedbackBuffers; private readonly BufferBounds[] _transformFeedbackBuffers;
private readonly List<BufferTextureBinding> _bufferTextures; private readonly List<BufferTextureBinding> _bufferTextures;
private readonly List<BufferTextureArrayBinding<ITextureArray>> _bufferTextureArrays;
private readonly List<BufferTextureArrayBinding<IImageArray>> _bufferImageArrays;
private readonly BufferAssignment[] _ranges; private readonly BufferAssignment[] _ranges;
/// <summary> /// <summary>
@ -140,11 +142,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
_bufferTextures = new List<BufferTextureBinding>(); _bufferTextures = new List<BufferTextureBinding>();
_bufferTextureArrays = new List<BufferTextureArrayBinding<ITextureArray>>();
_bufferImageArrays = new List<BufferTextureArrayBinding<IImageArray>>();
_ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages]; _ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages];
} }
/// <summary> /// <summary>
/// Sets the memory range with the index buffer data, to be used for subsequent draw calls. /// Sets the memory range with the index buffer data, to be used for subsequent draw calls.
/// </summary> /// </summary>
@ -418,6 +421,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
return _cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Address; return _cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Address;
} }
/// <summary>
/// Gets the size of the compute uniform buffer currently bound at the given index.
/// </summary>
/// <param name="index">Index of the uniform buffer binding</param>
/// <returns>The uniform buffer size, or an undefined value if the buffer is not currently bound</returns>
public int GetComputeUniformBufferSize(int index)
{
return (int)_cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Size;
}
/// <summary> /// <summary>
/// Gets the address of the graphics uniform buffer currently bound at the given index. /// Gets the address of the graphics uniform buffer currently bound at the given index.
/// </summary> /// </summary>
@ -429,6 +442,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
return _gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Address; return _gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Address;
} }
/// <summary>
/// Gets the size of the graphics uniform buffer currently bound at the given index.
/// </summary>
/// <param name="stage">Index of the shader stage</param>
/// <param name="index">Index of the uniform buffer binding</param>
/// <returns>The uniform buffer size, or an undefined value if the buffer is not currently bound</returns>
public int GetGraphicsUniformBufferSize(int stage, int index)
{
return (int)_gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Size;
}
/// <summary> /// <summary>
/// Gets the bounds of the uniform buffer currently bound at the given index. /// Gets the bounds of the uniform buffer currently bound at the given index.
/// </summary> /// </summary>
@ -459,7 +483,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
BindBuffers(bufferCache, _cpStorageBuffers, isStorage: true); BindBuffers(bufferCache, _cpStorageBuffers, isStorage: true);
BindBuffers(bufferCache, _cpUniformBuffers, isStorage: false); BindBuffers(bufferCache, _cpUniformBuffers, isStorage: false);
CommitBufferTextureBindings(); CommitBufferTextureBindings(bufferCache);
// Force rebind after doing compute work. // Force rebind after doing compute work.
Rebind(); Rebind();
@ -470,14 +494,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Commit any queued buffer texture bindings. /// Commit any queued buffer texture bindings.
/// </summary> /// </summary>
private void CommitBufferTextureBindings() /// <param name="bufferCache">Buffer cache</param>
private void CommitBufferTextureBindings(BufferCache bufferCache)
{ {
if (_bufferTextures.Count > 0) if (_bufferTextures.Count > 0)
{ {
foreach (var binding in _bufferTextures) foreach (var binding in _bufferTextures)
{ {
var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
var range = _channel.MemoryManager.Physical.BufferCache.GetBufferRange(binding.Range, isStore); var range = bufferCache.GetBufferRange(binding.Range, isStore);
binding.Texture.SetStorage(range); binding.Texture.SetStorage(range);
// The texture must be rebound to use the new storage if it was updated. // The texture must be rebound to use the new storage if it was updated.
@ -494,6 +519,33 @@ namespace Ryujinx.Graphics.Gpu.Memory
_bufferTextures.Clear(); _bufferTextures.Clear();
} }
if (_bufferTextureArrays.Count > 0 || _bufferImageArrays.Count > 0)
{
ITexture[] textureArray = new ITexture[1];
foreach (var binding in _bufferTextureArrays)
{
var range = bufferCache.GetBufferRange(binding.Range);
binding.Texture.SetStorage(range);
textureArray[0] = binding.Texture;
binding.Array.SetTextures(binding.Index, textureArray);
}
foreach (var binding in _bufferImageArrays)
{
var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
var range = bufferCache.GetBufferRange(binding.Range, isStore);
binding.Texture.SetStorage(range);
textureArray[0] = binding.Texture;
binding.Array.SetImages(binding.Index, textureArray);
}
_bufferTextureArrays.Clear();
_bufferImageArrays.Clear();
}
} }
/// <summary> /// <summary>
@ -676,7 +728,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
UpdateBuffers(_gpUniformBuffers); UpdateBuffers(_gpUniformBuffers);
} }
CommitBufferTextureBindings(); CommitBufferTextureBindings(bufferCache);
_rebind = false; _rebind = false;
@ -828,6 +880,50 @@ namespace Ryujinx.Graphics.Gpu.Memory
_bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage)); _bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage));
} }
/// <summary>
/// Sets the buffer storage of a buffer texture array element. This will be bound when the buffer manager commits bindings.
/// </summary>
/// <param name="array">Texture array where the element will be inserted</param>
/// <param name="texture">Buffer texture</param>
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
/// <param name="bindingInfo">Binding info for the buffer texture</param>
/// <param name="index">Index of the binding on the array</param>
/// <param name="format">Format of the buffer texture</param>
public void SetBufferTextureStorage(
ITextureArray array,
ITexture texture,
MultiRange range,
TextureBindingInfo bindingInfo,
int index,
Format format)
{
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
_bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, range, bindingInfo, index, format));
}
/// <summary>
/// Sets the buffer storage of a buffer image array element. This will be bound when the buffer manager commits bindings.
/// </summary>
/// <param name="array">Image array where the element will be inserted</param>
/// <param name="texture">Buffer texture</param>
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
/// <param name="bindingInfo">Binding info for the buffer texture</param>
/// <param name="index">Index of the binding on the array</param>
/// <param name="format">Format of the buffer texture</param>
public void SetBufferTextureStorage(
IImageArray array,
ITexture texture,
MultiRange range,
TextureBindingInfo bindingInfo,
int index,
Format format)
{
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
_bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, range, bindingInfo, index, format));
}
/// <summary> /// <summary>
/// Force all bound textures and images to be rebound the next time CommitBindings is called. /// Force all bound textures and images to be rebound the next time CommitBindings is called.
/// </summary> /// </summary>

View file

@ -0,0 +1,66 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Memory.Range;
namespace Ryujinx.Graphics.Gpu.Memory
{
/// <summary>
/// A buffer binding to apply to a buffer texture array element.
/// </summary>
readonly struct BufferTextureArrayBinding<T>
{
/// <summary>
/// Backend texture or image array.
/// </summary>
public T Array { get; }
/// <summary>
/// The buffer texture.
/// </summary>
public ITexture Texture { get; }
/// <summary>
/// Physical ranges of memory where the buffer texture data is located.
/// </summary>
public MultiRange Range { get; }
/// <summary>
/// The image or sampler binding info for the buffer texture.
/// </summary>
public TextureBindingInfo BindingInfo { get; }
/// <summary>
/// Index of the binding on the array.
/// </summary>
public int Index { get; }
/// <summary>
/// The image format for the binding.
/// </summary>
public Format Format { get; }
/// <summary>
/// Create a new buffer texture binding.
/// </summary>
/// <param name="texture">Buffer texture</param>
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
/// <param name="bindingInfo">Binding info</param>
/// <param name="index">Index of the binding on the array</param>
/// <param name="format">Binding format</param>
public BufferTextureArrayBinding(
T array,
ITexture texture,
MultiRange range,
TextureBindingInfo bindingInfo,
int index,
Format format)
{
Array = array;
Texture = texture;
Range = range;
BindingInfo = bindingInfo;
Index = index;
Format = format;
}
}
}

View file

@ -1,6 +1,8 @@
using Ryujinx.Common.Memory;
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -240,11 +242,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
else else
{ {
Memory<byte> memory = new byte[size]; IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size);
GetSpan(va, size).CopyTo(memory.Span); GetSpan(va, size).CopyTo(memoryOwner.Memory.Span);
return new WritableRegion(this, va, memory, tracked); return new WritableRegion(this, va, memoryOwner, tracked);
} }
} }

View file

@ -1,3 +1,4 @@
using Ryujinx.Common.Memory;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.Graphics.Device; using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.Image;
@ -6,6 +7,7 @@ using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking; using Ryujinx.Memory.Tracking;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -190,7 +192,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
else else
{ {
Memory<byte> memory = new byte[range.GetSize()]; IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(range.GetSize());
Memory<byte> memory = memoryOwner.Memory;
int offset = 0; int offset = 0;
for (int i = 0; i < range.Count; i++) for (int i = 0; i < range.Count; i++)
@ -204,7 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
offset += size; offset += size;
} }
return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memory, tracked); return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memoryOwner, tracked);
} }
} }

View file

@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
public BufferDescriptor[][] ConstantBufferBindings { get; } public BufferDescriptor[][] ConstantBufferBindings { get; }
public BufferDescriptor[][] StorageBufferBindings { get; } public BufferDescriptor[][] StorageBufferBindings { get; }
public int[] TextureCounts { get; }
public int MaxTextureBinding { get; } public int MaxTextureBinding { get; }
public int MaxImageBinding { get; } public int MaxImageBinding { get; }
@ -34,6 +36,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
ConstantBufferBindings = new BufferDescriptor[stageCount][]; ConstantBufferBindings = new BufferDescriptor[stageCount][];
StorageBufferBindings = new BufferDescriptor[stageCount][]; StorageBufferBindings = new BufferDescriptor[stageCount][];
TextureCounts = new int[stageCount];
int maxTextureBinding = -1; int maxTextureBinding = -1;
int maxImageBinding = -1; int maxImageBinding = -1;
int offset = isCompute ? 0 : 1; int offset = isCompute ? 0 : 1;
@ -59,13 +63,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
var result = new TextureBindingInfo( var result = new TextureBindingInfo(
target, target,
descriptor.Binding, descriptor.Binding,
descriptor.ArrayLength,
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); descriptor.Flags);
if (descriptor.Binding > maxTextureBinding) if (descriptor.ArrayLength <= 1)
{ {
maxTextureBinding = descriptor.Binding; if (descriptor.Binding > maxTextureBinding)
{
maxTextureBinding = descriptor.Binding;
}
TextureCounts[i]++;
} }
return result; return result;
@ -80,11 +90,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
target, target,
format, format,
descriptor.Binding, descriptor.Binding,
descriptor.ArrayLength,
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); descriptor.Flags);
if (descriptor.Binding > maxImageBinding) if (descriptor.ArrayLength <= 1 && descriptor.Binding > maxImageBinding)
{ {
maxImageBinding = descriptor.Binding; maxImageBinding = descriptor.Binding;
} }

View file

@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <param name="cb1Data">The constant buffer 1 data of the shader</param> /// <param name="cb1Data">The constant buffer 1 data of the shader</param>
/// <param name="oldSpecState">Shader specialization state of the cached shader</param> /// <param name="oldSpecState">Shader specialization state of the cached shader</param>
/// <param name="newSpecState">Shader specialization state of the recompiled shader</param> /// <param name="newSpecState">Shader specialization state of the recompiled shader</param>
/// <param name="counts">Resource counts shared across all shader stages</param>
/// <param name="stageIndex">Shader stage index</param> /// <param name="stageIndex">Shader stage index</param>
public DiskCacheGpuAccessor( public DiskCacheGpuAccessor(
GpuContext context, GpuContext context,
@ -108,6 +109,27 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters; return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters;
} }
/// <inheritdoc/>
public SamplerType QuerySamplerType(int handle, int cbufSlot)
{
_newSpecState.RecordTextureSamplerType(_stageIndex, handle, cbufSlot);
return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType();
}
/// <inheritdoc/>
public int QueryTextureArrayLengthFromBuffer(int slot)
{
if (!_oldSpecState.TextureArrayFromBufferRegistered(_stageIndex, 0, slot))
{
throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureArrayLength);
}
int arrayLength = _oldSpecState.GetTextureArrayFromBufferLength(_stageIndex, 0, slot);
_newSpecState.RegisterTextureArrayLengthFromBuffer(_stageIndex, 0, slot, arrayLength);
return arrayLength;
}
/// <inheritdoc/> /// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot) public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{ {
@ -116,13 +138,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return ConvertToTextureFormat(format, formatSrgb); return ConvertToTextureFormat(format, formatSrgb);
} }
/// <inheritdoc/>
public SamplerType QuerySamplerType(int handle, int cbufSlot)
{
_newSpecState.RecordTextureSamplerType(_stageIndex, handle, cbufSlot);
return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType();
}
/// <inheritdoc/> /// <inheritdoc/>
public bool QueryTextureCoordNormalized(int handle, int cbufSlot) public bool QueryTextureCoordNormalized(int handle, int cbufSlot)
{ {

View file

@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2; private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 6462; private const uint CodeGenVersion = 6489;
private const string SharedTocFileName = "shared.toc"; private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data"; private const string SharedDataFileName = "shared.data";

View file

@ -20,6 +20,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// </summary> /// </summary>
InvalidCb1DataLength, InvalidCb1DataLength,
/// <summary>
/// The cache is missing the length of a texture array used by the shader.
/// </summary>
MissingTextureArrayLength,
/// <summary> /// <summary>
/// The cache is missing the descriptor of a texture used by the shader. /// The cache is missing the descriptor of a texture used by the shader.
/// </summary> /// </summary>
@ -60,6 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
DiskCacheLoadResult.Success => "No error.", DiskCacheLoadResult.Success => "No error.",
DiskCacheLoadResult.NoAccess => "Could not access the cache file.", DiskCacheLoadResult.NoAccess => "Could not access the cache file.",
DiskCacheLoadResult.InvalidCb1DataLength => "Constant buffer 1 data length is too low.", DiskCacheLoadResult.InvalidCb1DataLength => "Constant buffer 1 data length is too low.",
DiskCacheLoadResult.MissingTextureArrayLength => "Texture array length missing from the cache file.",
DiskCacheLoadResult.MissingTextureDescriptor => "Texture descriptor missing from the cache file.", DiskCacheLoadResult.MissingTextureDescriptor => "Texture descriptor missing from the cache file.",
DiskCacheLoadResult.FileCorruptedGeneric => "The cache file is corrupted.", DiskCacheLoadResult.FileCorruptedGeneric => "The cache file is corrupted.",
DiskCacheLoadResult.FileCorruptedInvalidMagic => "Magic check failed, the cache file is corrupted.", DiskCacheLoadResult.FileCorruptedInvalidMagic => "Magic check failed, the cache file is corrupted.",

View file

@ -72,6 +72,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
public ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize) public ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize)
{ {
int size = Math.Max(minimumSize, 0x1000 - (int)(address & 0xfff)); int size = Math.Max(minimumSize, 0x1000 - (int)(address & 0xfff));
return MemoryMarshal.Cast<byte, ulong>(_channel.MemoryManager.GetSpan(address, size)); return MemoryMarshal.Cast<byte, ulong>(_channel.MemoryManager.GetSpan(address, size));
} }
@ -119,6 +120,27 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer; return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer;
} }
/// <inheritdoc/>
public SamplerType QuerySamplerType(int handle, int cbufSlot)
{
_state.SpecializationState?.RecordTextureSamplerType(_stageIndex, handle, cbufSlot);
return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType();
}
/// <inheritdoc/>
public int QueryTextureArrayLengthFromBuffer(int slot)
{
int size = _compute
? _channel.BufferManager.GetComputeUniformBufferSize(slot)
: _channel.BufferManager.GetGraphicsUniformBufferSize(_stageIndex, slot);
int arrayLength = size / Constants.TextureHandleSizeInBytes;
_state.SpecializationState?.RegisterTextureArrayLengthFromBuffer(_stageIndex, 0, slot, arrayLength);
return arrayLength;
}
//// <inheritdoc/> //// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot) public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{ {
@ -127,13 +149,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
return ConvertToTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb()); return ConvertToTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb());
} }
/// <inheritdoc/>
public SamplerType QuerySamplerType(int handle, int cbufSlot)
{
_state.SpecializationState?.RecordTextureSamplerType(_stageIndex, handle, cbufSlot);
return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType();
}
/// <inheritdoc/> /// <inheritdoc/>
public bool QueryTextureCoordNormalized(int handle, int cbufSlot) public bool QueryTextureCoordNormalized(int handle, int cbufSlot)
{ {

View file

@ -20,6 +20,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
private int _reservedTextures; private int _reservedTextures;
private int _reservedImages; private int _reservedImages;
private int _staticTexturesCount;
private int _staticImagesCount;
/// <summary> /// <summary>
/// Creates a new GPU accessor. /// Creates a new GPU accessor.
/// </summary> /// </summary>
@ -48,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
_reservedImages = rrc.ReservedImages; _reservedImages = rrc.ReservedImages;
} }
public int QueryBindingConstantBuffer(int index) public int CreateConstantBufferBinding(int index)
{ {
int binding; int binding;
@ -64,7 +67,39 @@ namespace Ryujinx.Graphics.Gpu.Shader
return binding + _reservedConstantBuffers; return binding + _reservedConstantBuffers;
} }
public int QueryBindingStorageBuffer(int index) public int CreateImageBinding(int count, bool isBuffer)
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
{
if (count == 1)
{
int index = _staticImagesCount++;
if (isBuffer)
{
index += (int)_context.Capabilities.MaximumImagesPerStage;
}
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image");
}
else
{
binding = (int)GetDynamicBaseIndexDual(_context.Capabilities.MaximumImagesPerStage) + _resourceCounts.ImagesCount++;
}
}
else
{
binding = _resourceCounts.ImagesCount;
_resourceCounts.ImagesCount += count;
}
return binding + _reservedImages;
}
public int CreateStorageBufferBinding(int index)
{ {
int binding; int binding;
@ -80,48 +115,38 @@ namespace Ryujinx.Graphics.Gpu.Shader
return binding + _reservedStorageBuffers; return binding + _reservedStorageBuffers;
} }
public int QueryBindingTexture(int index, bool isBuffer) public int CreateTextureBinding(int count, bool isBuffer)
{ {
int binding; int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan) if (_context.Capabilities.Api == TargetApi.Vulkan)
{ {
if (isBuffer) if (count == 1)
{ {
index += (int)_context.Capabilities.MaximumTexturesPerStage; int index = _staticTexturesCount++;
}
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture"); if (isBuffer)
{
index += (int)_context.Capabilities.MaximumTexturesPerStage;
}
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture");
}
else
{
binding = (int)GetDynamicBaseIndexDual(_context.Capabilities.MaximumTexturesPerStage) + _resourceCounts.TexturesCount++;
}
} }
else else
{ {
binding = _resourceCounts.TexturesCount++; binding = _resourceCounts.TexturesCount;
_resourceCounts.TexturesCount += count;
} }
return binding + _reservedTextures; return binding + _reservedTextures;
} }
public int QueryBindingImage(int index, bool isBuffer)
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
{
if (isBuffer)
{
index += (int)_context.Capabilities.MaximumImagesPerStage;
}
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image");
}
else
{
binding = _resourceCounts.ImagesCount++;
}
return binding + _reservedImages;
}
private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName) private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName)
{ {
if ((uint)index >= maxPerStage) if ((uint)index >= maxPerStage)
@ -148,6 +173,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
}; };
} }
private static uint GetDynamicBaseIndexDual(uint maxPerStage)
{
return GetDynamicBaseIndex(maxPerStage) * 2;
}
private static uint GetDynamicBaseIndex(uint maxPerStage)
{
return maxPerStage * Constants.ShaderStages;
}
public int QueryHostGatherBiasPrecision() => _context.Capabilities.GatherBiasPrecision; public int QueryHostGatherBiasPrecision() => _context.Capabilities.GatherBiasPrecision;
public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision; public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision;

View file

@ -132,6 +132,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage); AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage);
AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage); AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage);
AddArrayDescriptors(info.Textures, stages, TextureSetIndex, isImage: false);
AddArrayDescriptors(info.Images, stages, TextureSetIndex, isImage: true);
AddUsage(info.CBuffers, stages, UniformSetIndex, isStorage: false); AddUsage(info.CBuffers, stages, UniformSetIndex, isStorage: false);
AddUsage(info.SBuffers, stages, StorageSetIndex, isStorage: true); AddUsage(info.SBuffers, stages, StorageSetIndex, isStorage: true);
AddUsage(info.Textures, stages, TextureSetIndex, isImage: false); AddUsage(info.Textures, stages, TextureSetIndex, isImage: false);
@ -169,6 +172,30 @@ namespace Ryujinx.Graphics.Gpu.Shader
AddDescriptor(stages, type2, setIndex, binding + count, count); AddDescriptor(stages, type2, setIndex, binding + count, count);
} }
/// <summary>
/// Adds all array descriptors (those with an array length greater than one).
/// </summary>
/// <param name="textures">Textures to be added</param>
/// <param name="stages">Stages where the textures are used</param>
/// <param name="setIndex">Descriptor set index where the textures will be bound</param>
/// <param name="isImage">True for images, false for textures</param>
private void AddArrayDescriptors(IEnumerable<TextureDescriptor> textures, ResourceStages stages, int setIndex, bool isImage)
{
foreach (TextureDescriptor texture in textures)
{
if (texture.ArrayLength > 1)
{
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
ResourceType type = isBuffer
? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture)
: (isImage ? ResourceType.Image : ResourceType.TextureAndSampler);
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages));
}
}
}
/// <summary> /// <summary>
/// Adds buffer usage information to the list of usages. /// Adds buffer usage information to the list of usages.
/// </summary> /// </summary>
@ -181,7 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
for (int index = 0; index < count; index++) for (int index = 0; index < count; index++)
{ {
_resourceUsages[setIndex].Add(new ResourceUsage(binding + index, type, stages)); _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages));
} }
} }
@ -198,6 +225,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
_resourceUsages[setIndex].Add(new ResourceUsage( _resourceUsages[setIndex].Add(new ResourceUsage(
buffer.Binding, buffer.Binding,
1,
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer, isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
stages)); stages));
} }
@ -220,10 +248,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture) ? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture)
: (isImage ? ResourceType.Image : ResourceType.TextureAndSampler); : (isImage ? ResourceType.Image : ResourceType.TextureAndSampler);
_resourceUsages[setIndex].Add(new ResourceUsage( _resourceUsages[setIndex].Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages));
texture.Binding,
type,
stages));
} }
} }

View file

@ -30,6 +30,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
PrimitiveTopology = 1 << 1, PrimitiveTopology = 1 << 1,
TransformFeedback = 1 << 3, TransformFeedback = 1 << 3,
TextureArrayFromBuffer = 1 << 4,
} }
private QueriedStateFlags _queriedState; private QueriedStateFlags _queriedState;
@ -153,6 +154,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization; private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
private readonly Dictionary<TextureKey, int> _textureArraySpecialization;
private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures; private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
private Box<TextureSpecializationState>[][] _textureByBinding; private Box<TextureSpecializationState>[][] _textureByBinding;
private Box<TextureSpecializationState>[][] _imageByBinding; private Box<TextureSpecializationState>[][] _imageByBinding;
@ -163,6 +165,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
private ShaderSpecializationState() private ShaderSpecializationState()
{ {
_textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>(); _textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>();
_textureArraySpecialization = new Dictionary<TextureKey, int>();
} }
/// <summary> /// <summary>
@ -323,6 +326,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
state.Value.CoordNormalized = coordNormalized; state.Value.CoordNormalized = coordNormalized;
} }
/// <summary>
/// Indicates that the coordinate normalization state of a given texture was used during the shader translation process.
/// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <param name="length">Number of elements in the texture array</param>
public void RegisterTextureArrayLengthFromBuffer(int stageIndex, int handle, int cbufSlot, int length)
{
_textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)] = length;
_queriedState |= QueriedStateFlags.TextureArrayFromBuffer;
}
/// <summary> /// <summary>
/// Indicates that the format of a given texture was used during the shader translation process. /// Indicates that the format of a given texture was used during the shader translation process.
/// </summary> /// </summary>
@ -379,6 +395,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
return GetTextureSpecState(stageIndex, handle, cbufSlot) != null; return GetTextureSpecState(stageIndex, handle, cbufSlot) != null;
} }
/// <summary>
/// Checks if a given texture array (from constant buffer) was registerd on this specialization state.
/// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
public bool TextureArrayFromBufferRegistered(int stageIndex, int handle, int cbufSlot)
{
return _textureArraySpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot));
}
/// <summary> /// <summary>
/// Gets the recorded format of a given texture. /// Gets the recorded format of a given texture.
/// </summary> /// </summary>
@ -413,6 +440,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized; return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized;
} }
/// <summary>
/// Gets the recorded length of a given texture array (from constant buffer).
/// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
public int GetTextureArrayFromBufferLength(int stageIndex, int handle, int cbufSlot)
{
return _textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)];
}
/// <summary> /// <summary>
/// Gets texture specialization state for a given texture, or create a new one if not present. /// Gets texture specialization state for a given texture, or create a new one if not present.
/// </summary> /// </summary>
@ -548,6 +586,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
return Matches(channel, ref poolState, checkTextures, isCompute: false); return Matches(channel, ref poolState, checkTextures, isCompute: false);
} }
/// <summary>
/// Converts special vertex attribute groups to their generic equivalents, for comparison purposes.
/// </summary>
/// <param name="channel">GPU channel</param>
/// <param name="type">Vertex attribute type</param>
/// <returns>Filtered attribute</returns>
private static AttributeType FilterAttributeType(GpuChannel channel, AttributeType type) private static AttributeType FilterAttributeType(GpuChannel channel, AttributeType type)
{ {
type &= ~(AttributeType.Packed | AttributeType.PackedRgb10A2Signed); type &= ~(AttributeType.Packed | AttributeType.PackedRgb10A2Signed);
@ -838,6 +882,22 @@ namespace Ryujinx.Graphics.Gpu.Shader
specState._textureSpecialization[textureKey] = textureState; specState._textureSpecialization[textureKey] = textureState;
} }
if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer))
{
dataReader.Read(ref count);
for (int index = 0; index < count; index++)
{
TextureKey textureKey = default;
int length = 0;
dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic);
dataReader.Read(ref length);
specState._textureArraySpecialization[textureKey] = length;
}
}
return specState; return specState;
} }
@ -902,6 +962,21 @@ namespace Ryujinx.Graphics.Gpu.Shader
dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic); dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic);
dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic); dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic);
} }
if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer))
{
count = (ushort)_textureArraySpecialization.Count;
dataWriter.Write(ref count);
foreach (var kv in _textureArraySpecialization)
{
var textureKey = kv.Key;
var length = kv.Value;
dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic);
dataWriter.Write(ref length);
}
}
} }
} }
} }

View file

@ -33,7 +33,8 @@ namespace Ryujinx.Graphics.OpenGL.Effects.Smaa
public int Quality public int Quality
{ {
get => _quality; set get => _quality;
set
{ {
_quality = Math.Clamp(value, 0, _qualities.Length - 1); _quality = Math.Clamp(value, 0, _qualities.Length - 1);
} }
@ -150,8 +151,8 @@ namespace Ryujinx.Graphics.OpenGL.Effects.Smaa
_areaTexture = new TextureStorage(_renderer, areaInfo); _areaTexture = new TextureStorage(_renderer, areaInfo);
_searchTexture = new TextureStorage(_renderer, searchInfo); _searchTexture = new TextureStorage(_renderer, searchInfo);
var areaTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin"); var areaTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin");
var searchTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin"); var searchTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin");
var areaView = _areaTexture.CreateDefaultView(); var areaView = _areaTexture.CreateDefaultView();
var searchView = _searchTexture.CreateDefaultView(); var searchView = _searchTexture.CreateDefaultView();

View file

@ -1,4 +1,6 @@
using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
@ -8,9 +10,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
{ {
static class FormatConverter static class FormatConverter
{ {
public unsafe static byte[] ConvertS8D24ToD24S8(ReadOnlySpan<byte> data) public unsafe static IMemoryOwner<byte> ConvertS8D24ToD24S8(ReadOnlySpan<byte> data)
{ {
byte[] output = new byte[data.Length]; IMemoryOwner<byte> outputMemory = ByteMemoryPool.Rent(data.Length);
Span<byte> output = outputMemory.Memory.Span;
int start = 0; int start = 0;
@ -74,7 +78,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
outSpan[i] = BitOperations.RotateLeft(dataSpan[i], 8); outSpan[i] = BitOperations.RotateLeft(dataSpan[i], 8);
} }
return output; return outputMemory;
} }
public unsafe static byte[] ConvertD24S8ToS8D24(ReadOnlySpan<byte> data) public unsafe static byte[] ConvertD24S8ToS8D24(ReadOnlySpan<byte> data)

View file

@ -0,0 +1,67 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL;
using System;
namespace Ryujinx.Graphics.OpenGL.Image
{
class ImageArray : IImageArray
{
private record struct TextureRef
{
public int Handle;
public Format Format;
}
private readonly TextureRef[] _images;
public ImageArray(int size)
{
_images = new TextureRef[size];
}
public void SetFormats(int index, GAL.Format[] imageFormats)
{
for (int i = 0; i < imageFormats.Length; i++)
{
_images[index + i].Format = imageFormats[i];
}
}
public void SetImages(int index, ITexture[] images)
{
for (int i = 0; i < images.Length; i++)
{
ITexture image = images[i];
if (image is TextureBase imageBase)
{
_images[index + i].Handle = imageBase.Handle;
}
else
{
_images[index + i].Handle = 0;
}
}
}
public void Bind(int baseBinding)
{
for (int i = 0; i < _images.Length; i++)
{
if (_images[i].Handle == 0)
{
GL.BindImageTexture(baseBinding + i, 0, 0, true, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
}
else
{
SizedInternalFormat format = FormatTable.GetImageFormat(_images[i].Format);
if (format != 0)
{
GL.BindImageTexture(baseBinding + i, _images[i].Handle, 0, true, 0, TextureAccess.ReadWrite, format);
}
}
}
}
}
}

View file

@ -0,0 +1,52 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.OpenGL.Image
{
class TextureArray : ITextureArray
{
private record struct TextureRef
{
public TextureBase Texture;
public Sampler Sampler;
}
private readonly TextureRef[] _textureRefs;
public TextureArray(int size)
{
_textureRefs = new TextureRef[size];
}
public void SetSamplers(int index, ISampler[] samplers)
{
for (int i = 0; i < samplers.Length; i++)
{
_textureRefs[index + i].Sampler = samplers[i] as Sampler;
}
}
public void SetTextures(int index, ITexture[] textures)
{
for (int i = 0; i < textures.Length; i++)
{
_textureRefs[index + i].Texture = textures[i] as TextureBase;
}
}
public void Bind(int baseBinding)
{
for (int i = 0; i < _textureRefs.Length; i++)
{
if (_textureRefs[i].Texture != null)
{
_textureRefs[i].Texture.Bind(baseBinding + i);
_textureRefs[i].Sampler?.Bind(baseBinding + i);
}
else
{
TextureBase.ClearBinding(baseBinding + i);
}
}
}
}
}

View file

@ -1,7 +1,7 @@
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using System; using System;
using System.Buffers;
namespace Ryujinx.Graphics.OpenGL.Image namespace Ryujinx.Graphics.OpenGL.Image
{ {
@ -54,19 +54,24 @@ namespace Ryujinx.Graphics.OpenGL.Image
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void SetData(SpanOrArray<byte> data) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data)
{ {
var dataSpan = data.AsSpan(); var dataSpan = data.Memory.Span;
Buffer.SetData(_buffer, _bufferOffset, dataSpan[..Math.Min(dataSpan.Length, _bufferSize)]); Buffer.SetData(_buffer, _bufferOffset, dataSpan[..Math.Min(dataSpan.Length, _bufferSize)]);
data.Dispose();
} }
public void SetData(SpanOrArray<byte> data, int layer, int level) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }

View file

@ -1,8 +1,8 @@
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using System; using System;
using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
namespace Ryujinx.Graphics.OpenGL.Image namespace Ryujinx.Graphics.OpenGL.Image
@ -448,70 +448,59 @@ namespace Ryujinx.Graphics.OpenGL.Image
} }
} }
public void SetData(SpanOrArray<byte> data) public void SetData(IMemoryOwner<byte> data)
{ {
var dataSpan = data.AsSpan(); using (data = EnsureDataFormat(data))
if (Format == Format.S8UintD24Unorm)
{ {
dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan); unsafe
}
unsafe
{
fixed (byte* ptr = dataSpan)
{ {
ReadFrom((IntPtr)ptr, dataSpan.Length); var dataSpan = data.Memory.Span;
fixed (byte* ptr = dataSpan)
{
ReadFrom((IntPtr)ptr, dataSpan.Length);
}
} }
} }
} }
public void SetData(SpanOrArray<byte> data, int layer, int level) public void SetData(IMemoryOwner<byte> data, int layer, int level)
{ {
var dataSpan = data.AsSpan(); using (data = EnsureDataFormat(data))
if (Format == Format.S8UintD24Unorm)
{ {
dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan); unsafe
}
unsafe
{
fixed (byte* ptr = dataSpan)
{ {
int width = Math.Max(Info.Width >> level, 1); fixed (byte* ptr = data.Memory.Span)
int height = Math.Max(Info.Height >> level, 1); {
int width = Math.Max(Info.Width >> level, 1);
int height = Math.Max(Info.Height >> level, 1);
ReadFrom2D((IntPtr)ptr, layer, level, 0, 0, width, height); ReadFrom2D((IntPtr)ptr, layer, level, 0, 0, width, height);
}
} }
} }
} }
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region) public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
var dataSpan = data.AsSpan(); using (data = EnsureDataFormat(data))
if (Format == Format.S8UintD24Unorm)
{ {
dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan); int wInBlocks = BitUtils.DivRoundUp(region.Width, Info.BlockWidth);
} int hInBlocks = BitUtils.DivRoundUp(region.Height, Info.BlockHeight);
int wInBlocks = BitUtils.DivRoundUp(region.Width, Info.BlockWidth); unsafe
int hInBlocks = BitUtils.DivRoundUp(region.Height, Info.BlockHeight);
unsafe
{
fixed (byte* ptr = dataSpan)
{ {
ReadFrom2D( fixed (byte* ptr = data.Memory.Span)
(IntPtr)ptr, {
layer, ReadFrom2D(
level, (IntPtr)ptr,
region.X, layer,
region.Y, level,
region.Width, region.X,
region.Height, region.Y,
BitUtils.AlignUp(wInBlocks * Info.BytesPerPixel, 4) * hInBlocks); region.Width,
region.Height,
BitUtils.AlignUp(wInBlocks * Info.BytesPerPixel, 4) * hInBlocks);
}
} }
} }
} }
@ -533,6 +522,19 @@ namespace Ryujinx.Graphics.OpenGL.Image
ReadFrom2D(data, layer, level, x, y, width, height, mipSize); ReadFrom2D(data, layer, level, x, y, width, height, mipSize);
} }
private IMemoryOwner<byte> EnsureDataFormat(IMemoryOwner<byte> data)
{
if (Format == Format.S8UintD24Unorm)
{
using (data)
{
return FormatConverter.ConvertS8D24ToD24S8(data.Memory.Span);
}
}
return data;
}
private void ReadFrom2D(IntPtr data, int layer, int level, int x, int y, int width, int height, int mipSize) private void ReadFrom2D(IntPtr data, int layer, int level, int x, int y, int width, int height, int mipSize)
{ {
TextureTarget target = Target.Convert(); TextureTarget target = Target.Convert();

View file

@ -90,6 +90,11 @@ namespace Ryujinx.Graphics.OpenGL
throw new NotSupportedException(); throw new NotSupportedException();
} }
public IImageArray CreateImageArray(int size, bool isBuffer)
{
return new ImageArray(size);
}
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info) public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
{ {
return new Program(shaders, info.FragmentOutputMap); return new Program(shaders, info.FragmentOutputMap);
@ -112,6 +117,11 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
public ITextureArray CreateTextureArray(int size, bool isBuffer)
{
return new TextureArray(size);
}
public void DeleteBuffer(BufferHandle buffer) public void DeleteBuffer(BufferHandle buffer)
{ {
PersistentBuffers.Unmap(buffer); PersistentBuffers.Unmap(buffer);

Some files were not shown because too many files have changed in this diff Show more