Merge branch 'master' into turbo
This commit is contained in:
commit
ef781cb32a
214 changed files with 6283 additions and 3515 deletions
|
@ -13,14 +13,14 @@
|
|||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
||||
<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="GtkSharp.Dependencies" Version="1.1.1" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<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.IO.RecyclableMemoryStream" Version="3.0.0" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
|
|
|
@ -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.
|
||||
|
||||
Other Information
|
||||
=================
|
||||
|
||||
- N/A
|
||||
|
|
|
@ -19,6 +19,12 @@ namespace ARMeilleure.Instructions
|
|||
Operand n = GetAluN(context);
|
||||
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);
|
||||
|
||||
if (ShouldSetFlags(context))
|
||||
|
@ -467,6 +473,12 @@ namespace ARMeilleure.Instructions
|
|||
Operand n = GetAluN(context);
|
||||
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);
|
||||
|
||||
if (ShouldSetFlags(context))
|
||||
|
|
|
@ -2426,7 +2426,11 @@ namespace ARMeilleure.Instructions
|
|||
}
|
||||
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));
|
||||
}
|
||||
|
@ -2451,7 +2455,11 @@ namespace ARMeilleure.Instructions
|
|||
}
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC
|
|||
private const string OuterHeaderMagicString = "PTCohd\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 BackupDir = "1";
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.Common
|
||||
{
|
||||
|
@ -12,7 +14,8 @@ namespace Ryujinx.Audio.Backends.Common
|
|||
|
||||
private readonly object _lock = new();
|
||||
|
||||
private byte[] _buffer;
|
||||
private IMemoryOwner<byte> _bufferOwner;
|
||||
private Memory<byte> _buffer;
|
||||
private int _size;
|
||||
private int _headOffset;
|
||||
private int _tailOffset;
|
||||
|
@ -21,7 +24,8 @@ namespace Ryujinx.Audio.Backends.Common
|
|||
|
||||
public DynamicRingBuffer(int initialCapacity = RingBufferAlignment)
|
||||
{
|
||||
_buffer = new byte[initialCapacity];
|
||||
_bufferOwner = ByteMemoryPool.RentCleared(initialCapacity);
|
||||
_buffer = _bufferOwner.Memory;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
|
@ -33,6 +37,11 @@ namespace Ryujinx.Audio.Backends.Common
|
|||
|
||||
public void Clear(int size)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (size > _size)
|
||||
|
@ -40,11 +49,6 @@ namespace Ryujinx.Audio.Backends.Common
|
|||
size = _size;
|
||||
}
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_headOffset = (_headOffset + size) % _buffer.Length;
|
||||
_size -= size;
|
||||
|
||||
|
@ -58,28 +62,31 @@ namespace Ryujinx.Audio.Backends.Common
|
|||
|
||||
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 (_headOffset < _tailOffset)
|
||||
{
|
||||
Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _size);
|
||||
_buffer.Slice(_headOffset, _size).CopyTo(newBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _buffer.Length - _headOffset);
|
||||
Buffer.BlockCopy(_buffer, 0, buffer, _buffer.Length - _headOffset, _tailOffset);
|
||||
_buffer[_headOffset..].CopyTo(newBuffer);
|
||||
_buffer[.._tailOffset].CopyTo(newBuffer[(_buffer.Length - _headOffset)..]);
|
||||
}
|
||||
}
|
||||
|
||||
_buffer = buffer;
|
||||
_bufferOwner.Dispose();
|
||||
|
||||
_bufferOwner = newBufferOwner;
|
||||
_buffer = newBuffer;
|
||||
_headOffset = 0;
|
||||
_tailOffset = _size;
|
||||
}
|
||||
|
||||
|
||||
public void Write<T>(T[] buffer, int index, int count)
|
||||
public void Write(ReadOnlySpan<byte> buffer, int index, int count)
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
|
@ -99,17 +106,17 @@ namespace Ryujinx.Audio.Backends.Common
|
|||
|
||||
if (tailLength >= count)
|
||||
{
|
||||
Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count);
|
||||
buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, tailLength);
|
||||
Buffer.BlockCopy(buffer, index + tailLength, _buffer, 0, count - tailLength);
|
||||
buffer.Slice(index, tailLength).CopyTo(_buffer.Span[_tailOffset..]);
|
||||
buffer.Slice(index + tailLength, count - tailLength).CopyTo(_buffer.Span);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count);
|
||||
buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]);
|
||||
}
|
||||
|
||||
_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)
|
||||
{
|
||||
if (count > _size)
|
||||
|
@ -126,14 +138,9 @@ namespace Ryujinx.Audio.Backends.Common
|
|||
count = _size;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (_headOffset < _tailOffset)
|
||||
{
|
||||
Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count);
|
||||
_buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -141,12 +148,12 @@ namespace Ryujinx.Audio.Backends.Common
|
|||
|
||||
if (tailLength >= count)
|
||||
{
|
||||
Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count);
|
||||
_buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffer.BlockCopy(_buffer, _headOffset, buffer, index, tailLength);
|
||||
Buffer.BlockCopy(_buffer, 0, buffer, index + tailLength, count - tailLength);
|
||||
_buffer.Span.Slice(_headOffset, tailLength).CopyTo(buffer[index..]);
|
||||
_buffer.Span[..(count - tailLength)].CopyTo(buffer[(index + tailLength)..]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Common
|
|||
public ulong Flags;
|
||||
|
||||
/// <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>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct ErrorInfo
|
||||
|
|
|
@ -4,7 +4,7 @@ using System.Runtime.CompilerServices;
|
|||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <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>
|
||||
public struct UpdateDataHeader
|
||||
{
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
|||
/// <summary>
|
||||
/// Output information for behaviour.
|
||||
/// </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)]
|
||||
public struct BehaviourErrorInfoOutStatus
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
@ -419,14 +419,16 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateVoices(_voiceContext, _memoryPools);
|
||||
PoolMapper poolMapper = new PoolMapper(_processHandle, _memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
|
||||
result = stateUpdater.UpdateVoices(_voiceContext, poolMapper);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateEffects(_effectContext, _isActive, _memoryPools);
|
||||
result = stateUpdater.UpdateEffects(_effectContext, _isActive, poolMapper);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
|
@ -450,7 +452,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateSinks(_sinkContext, _memoryPools);
|
||||
result = stateUpdater.UpdateSinks(_sinkContext, poolMapper);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
||||
|
||||
|
@ -273,7 +274,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
|
||||
/// <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>
|
||||
/// <returns>True if the audio renderer should trust the user destination count.</returns>
|
||||
public bool IsSplitterBugFixed()
|
||||
|
|
|
@ -33,21 +33,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
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];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
|
|
@ -81,7 +81,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
/// </summary>
|
||||
/// <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>
|
||||
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;
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
/// Update the internal common parameters from a user parameter.
|
||||
/// </summary>
|
||||
/// <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;
|
||||
ProcessingOrder = parameter.ProcessingOrder;
|
||||
|
@ -139,7 +139,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
/// <summary>
|
||||
/// Initialize the given <paramref name="state"/> result state.
|
||||
/// </summary>
|
||||
/// <param name="state">The state to initalize</param>
|
||||
/// <param name="state">The state to initialize</param>
|
||||
public virtual void InitializeResultState(ref EffectResultState state) { }
|
||||
|
||||
/// <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="parameter">The user parameter.</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();
|
||||
}
|
||||
|
@ -168,9 +168,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
/// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
|
||||
/// <param name="parameter">The user parameter.</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();
|
||||
}
|
||||
|
|
|
@ -35,21 +35,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
|
||||
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];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
|
|
@ -19,21 +19,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
|
||||
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];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
|
|
@ -32,21 +32,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
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];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
|
|
@ -39,17 +39,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
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.
|
||||
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];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
|
|
@ -37,19 +37,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
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];
|
||||
|
||||
|
@ -57,7 +57,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
|
||||
if (delayParameter.IsChannelCountMaxValid())
|
||||
{
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
UsageState oldParameterStatus = Parameter.Status;
|
||||
|
||||
|
|
|
@ -39,25 +39,25 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
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];
|
||||
|
||||
updateErrorInfo = new BehaviourParameter.ErrorInfo();
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
Parameter = limiterParameter;
|
||||
|
||||
|
|
|
@ -36,19 +36,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
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];
|
||||
|
||||
|
@ -56,7 +56,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
|
||||
if (reverbParameter.IsChannelCountMaxValid())
|
||||
{
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
UsageState oldParameterStatus = Parameter.ParameterStatus;
|
||||
|
||||
|
|
|
@ -39,19 +39,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
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];
|
||||
|
||||
|
@ -59,7 +59,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
|
||||
if (reverbParameter.IsChannelCountMaxValid())
|
||||
{
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
UsageState oldParameterStatus = Parameter.Status;
|
||||
|
||||
|
|
|
@ -249,7 +249,7 @@ namespace Ryujinx.Audio.Renderer.Server.MemoryPool
|
|||
/// <param name="inParameter">Input user parameter.</param>
|
||||
/// <param name="outStatus">Output user parameter.</param>
|
||||
/// <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;
|
||||
|
||||
|
|
|
@ -195,7 +195,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
|
|||
/// <param name="parameter">The input parameter of the mix.</param>
|
||||
/// <param name="splitterContext">The splitter context.</param>
|
||||
/// <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;
|
||||
|
||||
|
@ -259,7 +259,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
|
|||
/// <param name="splitterContext">The splitter context.</param>
|
||||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
/// <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;
|
||||
|
||||
|
@ -273,7 +273,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
|
|||
|
||||
if (behaviourContext.IsSplitterSupported())
|
||||
{
|
||||
isDirty = UpdateConnection(edgeMatrix, ref parameter, ref splitterContext);
|
||||
isDirty = UpdateConnection(edgeMatrix, in parameter, ref splitterContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -59,7 +59,7 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
|
|||
/// </summary>
|
||||
/// <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>
|
||||
public bool IsTypeValid(ref SinkInParameter parameter)
|
||||
public bool IsTypeValid(in SinkInParameter parameter)
|
||||
{
|
||||
return parameter.Type == TargetSinkType;
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
|
|||
/// Update the internal common parameters from user parameter.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
protected void UpdateStandardParameter(ref SinkInParameter parameter)
|
||||
protected void UpdateStandardParameter(in SinkInParameter parameter)
|
||||
{
|
||||
if (IsUsed != parameter.IsUsed)
|
||||
{
|
||||
|
@ -92,9 +92,9 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
|
|||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="outStatus">The user output status.</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();
|
||||
}
|
||||
|
|
|
@ -44,18 +44,18 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
|
|||
|
||||
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();
|
||||
outStatus = new SinkOutStatus();
|
||||
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
ref CircularBufferParameter inputDeviceParameter = ref MemoryMarshal.Cast<byte, CircularBufferParameter>(parameter.SpecificData)[0];
|
||||
|
||||
if (parameter.IsUsed != IsUsed || ShouldSkip)
|
||||
{
|
||||
UpdateStandardParameter(ref parameter);
|
||||
UpdateStandardParameter(in parameter);
|
||||
|
||||
if (parameter.IsUsed)
|
||||
{
|
||||
|
|
|
@ -49,15 +49,15 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
|
|||
|
||||
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];
|
||||
|
||||
if (parameter.IsUsed != IsUsed)
|
||||
{
|
||||
UpdateStandardParameter(ref parameter);
|
||||
UpdateStandardParameter(in parameter);
|
||||
Parameter = inputDeviceParameter;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -2,10 +2,11 @@ using Ryujinx.Audio.Renderer.Common;
|
|||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Extensions;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||
{
|
||||
|
@ -25,7 +26,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
private Memory<SplitterDestination> _splitterDestinations;
|
||||
|
||||
/// <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>
|
||||
public bool IsBugFixed { get; private set; }
|
||||
|
||||
|
@ -110,7 +111,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// </summary>
|
||||
/// <param name="splitters">The <see cref="SplitterState"/> 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)
|
||||
{
|
||||
_splitters = splitters;
|
||||
|
@ -148,11 +149,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// </summary>
|
||||
/// <param name="inputHeader">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++)
|
||||
{
|
||||
SplitterInParameter parameter = MemoryMarshal.Read<SplitterInParameter>(input);
|
||||
ref readonly SplitterInParameter parameter = ref input.GetRefOrRefToCopy<SplitterInParameter>(out _);
|
||||
|
||||
Debug.Assert(parameter.IsMagicValid());
|
||||
|
||||
|
@ -162,10 +163,16 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
{
|
||||
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>
|
||||
/// <param name="inputHeader">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++)
|
||||
{
|
||||
SplitterDestinationInParameter parameter = MemoryMarshal.Read<SplitterDestinationInParameter>(input);
|
||||
ref readonly SplitterDestinationInParameter parameter = ref input.GetRefOrRefToCopy<SplitterDestinationInParameter>(out _);
|
||||
|
||||
Debug.Assert(parameter.IsMagicValid());
|
||||
|
||||
|
@ -191,8 +198,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
|
||||
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.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
public bool Update(ReadOnlySpan<byte> input, out int consumedSize)
|
||||
public bool Update(ref SequenceReader<byte> input)
|
||||
{
|
||||
if (_splitterDestinations.IsEmpty || _splitters.IsEmpty)
|
||||
{
|
||||
consumedSize = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int originalSize = input.Length;
|
||||
|
||||
SplitterInParameterHeader header = SpanIOHelper.Read<SplitterInParameterHeader>(ref input);
|
||||
ref readonly SplitterInParameterHeader header = ref input.GetRefOrRefToCopy<SplitterInParameterHeader>(out _);
|
||||
|
||||
if (header.IsMagicValid())
|
||||
{
|
||||
ClearAllNewConnectionFlag();
|
||||
|
||||
UpdateState(ref header, ref input);
|
||||
UpdateData(ref header, ref input);
|
||||
UpdateState(in header, ref input);
|
||||
UpdateData(in header, ref input);
|
||||
|
||||
consumedSize = BitUtils.AlignUp(originalSize - input.Length, 0x10);
|
||||
input.SetConsumed(BitUtils.AlignUp(input.Consumed, 0x10));
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
input.Rewind(Unsafe.SizeOf<SplitterInParameterHeader>());
|
||||
|
||||
consumedSize = 0;
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Common.Extensions;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
|
@ -122,7 +123,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// <param name="context">The splitter context.</param>
|
||||
/// <param name="parameter">The user 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();
|
||||
|
||||
|
@ -139,9 +140,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
|
||||
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]);
|
||||
|
||||
|
@ -149,13 +150,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
|
||||
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 = nextDestination;
|
||||
}
|
||||
}
|
||||
|
||||
if (destinationCount < parameter.DestinationCount)
|
||||
{
|
||||
input.Advance((parameter.DestinationCount - destinationCount) * sizeof(int));
|
||||
}
|
||||
|
||||
Debug.Assert(parameter.Id == Id);
|
||||
|
||||
if (parameter.Id == Id)
|
||||
|
|
|
@ -9,41 +9,40 @@ using Ryujinx.Audio.Renderer.Server.Sink;
|
|||
using Ryujinx.Audio.Renderer.Server.Splitter;
|
||||
using Ryujinx.Audio.Renderer.Server.Voice;
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using Ryujinx.Common.Extensions;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
||||
|
||||
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 ReadOnlyMemory<byte> _input;
|
||||
|
||||
private Memory<byte> _output;
|
||||
private readonly uint _processHandle;
|
||||
private BehaviourContext _behaviourContext;
|
||||
|
||||
private UpdateDataHeader _inputHeader;
|
||||
private readonly ref readonly UpdateDataHeader _inputHeader;
|
||||
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;
|
||||
_inputOrigin = _input;
|
||||
_inputReader = new SequenceReader<byte>(input);
|
||||
_output = output;
|
||||
_outputOrigin = _output;
|
||||
_processHandle = processHandle;
|
||||
_behaviourContext = behaviourContext;
|
||||
|
||||
_inputHeader = SpanIOHelper.Read<UpdateDataHeader>(ref _input);
|
||||
_inputHeader = ref _inputReader.GetRefOrRefToCopy<UpdateDataHeader>(out _);
|
||||
|
||||
_outputHeader = SpanMemoryManager<UpdateDataHeader>.Cast(_output[..Unsafe.SizeOf<UpdateDataHeader>()]);
|
||||
OutputHeader.Initialize(_behaviourContext.UserRevision);
|
||||
|
@ -52,7 +51,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
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)
|
||||
{
|
||||
|
@ -81,11 +80,11 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
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];
|
||||
|
||||
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 &&
|
||||
updateResult != PoolMapper.UpdateResult.MapError &&
|
||||
|
@ -115,7 +114,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
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);
|
||||
|
||||
|
@ -127,7 +126,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
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)
|
||||
{
|
||||
|
@ -136,11 +135,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<VoiceInParameter> parameters = MemoryMarshal.Cast<byte, VoiceInParameter>(_input[..(int)_inputHeader.VoicesSize].Span);
|
||||
|
||||
_input = _input[(int)_inputHeader.VoicesSize..];
|
||||
|
||||
PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
// First make everything not in use.
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
|
@ -157,7 +152,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
// Start processing
|
||||
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);
|
||||
|
||||
|
@ -181,14 +176,14 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
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)
|
||||
{
|
||||
_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())
|
||||
{
|
||||
|
@ -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);
|
||||
|
||||
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.VoicesSize);
|
||||
|
||||
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);
|
||||
|
||||
|
@ -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())
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -253,26 +250,22 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<EffectInParameterVersion2> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion2>(_input[..(int)_inputHeader.EffectsSize].Span);
|
||||
|
||||
_input = _input[(int)_inputHeader.EffectsSize..];
|
||||
|
||||
PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
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 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)
|
||||
{
|
||||
|
@ -297,10 +290,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
|
||||
|
||||
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize);
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -309,26 +304,22 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<EffectInParameterVersion1> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion1>(_input[..(int)_inputHeader.EffectsSize].Span);
|
||||
|
||||
_input = _input[(int)_inputHeader.EffectsSize..];
|
||||
|
||||
PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
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 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)
|
||||
{
|
||||
|
@ -345,38 +336,40 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
|
||||
|
||||
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
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.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 totalRequiredMixBufferCount = 0;
|
||||
|
||||
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 &&
|
||||
parameters[i].DestinationMixId > maxMixStateCount &&
|
||||
parameters[i].MixId != Constants.FinalMixId)
|
||||
if (parameter.DestinationMixId != Constants.UnusedMixId &&
|
||||
parameter.DestinationMixId > maxMixStateCount &&
|
||||
parameter.MixId != Constants.FinalMixId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
totalRequiredMixBufferCount += parameters[i].BufferCount;
|
||||
totalRequiredMixBufferCount += parameter.BufferCount;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -391,7 +384,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
|
||||
{
|
||||
MixInParameterDirtyOnlyUpdate parameter = MemoryMarshal.Cast<byte, MixInParameterDirtyOnlyUpdate>(_input.Span)[0];
|
||||
ref readonly MixInParameterDirtyOnlyUpdate parameter = ref _inputReader.GetRefOrRefToCopy<MixInParameterDirtyOnlyUpdate>(out _);
|
||||
|
||||
mixCount = parameter.MixCount;
|
||||
|
||||
|
@ -411,25 +404,20 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
|
||||
{
|
||||
_input = _input[Unsafe.SizeOf<MixInParameterDirtyOnlyUpdate>()..];
|
||||
}
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
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, parameters))
|
||||
if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, _inputReader))
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
@ -454,7 +442,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
private static void ResetSink(ref BaseSink sink, ref SinkInParameter parameter)
|
||||
private static void ResetSink(ref BaseSink sink, in SinkInParameter parameter)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
|
@ -500,22 +488,20 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<SinkInParameter> parameters = MemoryMarshal.Cast<byte, SinkInParameter>(_input[..(int)_inputHeader.SinksSize].Span);
|
||||
|
||||
_input = _input[(int)_inputHeader.SinksSize..];
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
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 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)
|
||||
{
|
||||
|
@ -530,6 +516,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize);
|
||||
|
||||
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.SinksSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
|
@ -540,7 +528,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
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];
|
||||
|
||||
|
@ -585,9 +573,9 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
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;
|
||||
|
||||
if (consumedInputSize != _inputHeader.TotalSize)
|
||||
|
|
|
@ -254,7 +254,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <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)
|
||||
{
|
||||
|
@ -273,7 +273,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="poolMapper">The mapper to use.</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;
|
||||
Id = parameter.Id;
|
||||
|
@ -326,7 +326,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
VoiceDropFlag = false;
|
||||
}
|
||||
|
||||
if (ShouldUpdateParameters(ref parameter))
|
||||
if (ShouldUpdateParameters(in parameter))
|
||||
{
|
||||
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="parameter">The user parameter.</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
|
||||
// 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="mapper">The mapper to use.</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];
|
||||
|
||||
|
@ -444,7 +449,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
|
||||
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="mapper">The mapper to use.</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)
|
||||
{
|
||||
|
|
181
src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs
Normal file
181
src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ using System.Threading;
|
|||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
public sealed partial class ByteMemoryPool
|
||||
public partial class ByteMemoryPool
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a <see cref="IMemoryOwner{Byte}"/> that wraps an array rented from
|
||||
|
|
|
@ -6,24 +6,8 @@ namespace Ryujinx.Common.Memory
|
|||
/// <summary>
|
||||
/// Provides a pool of re-usable byte array instances.
|
||||
/// </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>
|
||||
/// Returns the maximum buffer size supported by this pool.
|
||||
/// </summary>
|
||||
|
@ -95,6 +79,20 @@ namespace Ryujinx.Common.Memory
|
|||
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)
|
||||
{
|
||||
if ((uint)length > Array.MaxLength)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
@ -41,6 +42,22 @@ namespace Ryujinx.Common
|
|||
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)
|
||||
{
|
||||
using var stream = GetStream(assembly, filename);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using Microsoft.IO;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -9,12 +11,50 @@ namespace Ryujinx.Common.Utilities
|
|||
{
|
||||
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)
|
||||
|
@ -25,5 +65,26 @@ namespace Ryujinx.Common.Utilities
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Collections;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
|
||||
|
@ -7,175 +5,23 @@ namespace Ryujinx.Cpu
|
|||
{
|
||||
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 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 Mirror { 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;
|
||||
_supports4KBPages = supports4KBPages;
|
||||
|
||||
Base = baseMemory;
|
||||
Mirror = mirrorMemory;
|
||||
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;
|
||||
|
||||
|
@ -193,7 +39,7 @@ namespace Ryujinx.Cpu
|
|||
{
|
||||
baseMemory = 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;
|
||||
}
|
||||
|
@ -209,289 +55,20 @@ namespace Ryujinx.Cpu
|
|||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Base.MapView(_backingMemory, pa, va, size);
|
||||
Mirror.MapView(_backingMemory, pa, va, size);
|
||||
}
|
||||
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
if (_supports4KBPages)
|
||||
{
|
||||
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;
|
||||
Base.UnmapView(_backingMemory, va, size);
|
||||
Mirror.UnmapView(_backingMemory, va, size);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
_privateMemoryAllocator?.Dispose();
|
||||
Base.Dispose();
|
||||
Mirror.Dispose();
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@ using Ryujinx.Memory;
|
|||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
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.
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("macos")]
|
||||
public class HvMemoryManager : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
public sealed class HvMemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked
|
||||
{
|
||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||
|
||||
|
@ -28,7 +28,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
|
||||
private readonly ManagedPageFlags _pages;
|
||||
|
||||
public bool Supports4KBPages => true;
|
||||
public bool UsesPrivateAllocations => false;
|
||||
|
||||
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/>
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
|
@ -126,20 +120,11 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
public override T ReadTracked<T>(ulong va)
|
||||
{
|
||||
try
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
|
||||
|
||||
return Read<T>(va);
|
||||
return base.ReadTracked<T>(va);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
|
@ -152,7 +137,6 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Read(ulong va, Span<byte> data)
|
||||
{
|
||||
try
|
||||
|
@ -168,101 +152,11 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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)
|
||||
public override void Write(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
try
|
||||
{
|
||||
AssertValidAddressAndSize(va, (ulong)data.Length);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
base.Write(va, data);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
|
@ -273,61 +167,38 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
|
||||
public override void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (size == 0)
|
||||
try
|
||||
{
|
||||
return ReadOnlySpan<byte>.Empty;
|
||||
base.WriteUntracked(va, data);
|
||||
}
|
||||
|
||||
if (tracked)
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)size, false);
|
||||
}
|
||||
|
||||
if (IsContiguousAndMapped(va, size))
|
||||
{
|
||||
return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> data = new byte[size];
|
||||
|
||||
base.Read(va, data);
|
||||
|
||||
return data;
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
|
||||
public override ReadOnlySequence<byte> GetReadOnlySequence(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);
|
||||
}
|
||||
|
||||
if (tracked)
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)size, true);
|
||||
}
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
if (IsContiguousAndMapped(va, size))
|
||||
{
|
||||
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);
|
||||
return ReadOnlySequence<byte>.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ref T GetRef<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
if (!IsContiguous(va, Unsafe.SizeOf<T>()))
|
||||
|
@ -340,9 +211,8 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
return ref _backingMemory.GetRef<T>(GetPhysicalAddressChecked(va));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsMapped(ulong va)
|
||||
public override bool IsMapped(ulong va)
|
||||
{
|
||||
return ValidateAddress(va) && _pages.IsMapped(va);
|
||||
}
|
||||
|
@ -355,39 +225,6 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
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/>
|
||||
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
|
||||
{
|
||||
|
@ -464,11 +301,10 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
return regions;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
|
||||
/// </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);
|
||||
|
||||
|
@ -481,24 +317,6 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
_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)
|
||||
{
|
||||
// TODO
|
||||
|
@ -535,7 +353,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
|
||||
}
|
||||
|
||||
private ulong GetPhysicalAddressChecked(ulong va)
|
||||
private nuint GetPhysicalAddressChecked(ulong va)
|
||||
{
|
||||
if (!IsMapped(va))
|
||||
{
|
||||
|
@ -545,9 +363,9 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
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>
|
||||
|
@ -558,10 +376,17 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
_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);
|
||||
|
||||
protected override ulong TranslateVirtualAddressForRead(ulong va)
|
||||
protected override nuint TranslateVirtualAddressChecked(ulong va)
|
||||
=> GetPhysicalAddressChecked(va);
|
||||
|
||||
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
|
||||
=> GetPhysicalAddressInternal(va);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using Ryujinx.Memory;
|
|||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
@ -14,7 +15,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
/// <summary>
|
||||
/// Represents a CPU memory manager.
|
||||
/// </summary>
|
||||
public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked
|
||||
{
|
||||
private const int PteSize = 8;
|
||||
|
||||
|
@ -24,7 +25,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Supports4KBPages => true;
|
||||
public bool UsesPrivateAllocations => false;
|
||||
|
||||
/// <summary>
|
||||
/// Address space width in bits.
|
||||
|
@ -97,12 +98,6 @@ namespace Ryujinx.Cpu.Jit
|
|||
Tracking.Map(oVa, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void MapForeign(ulong va, nuint hostPointer, ulong size)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
|
@ -128,20 +123,11 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
public override T ReadTracked<T>(ulong va)
|
||||
{
|
||||
try
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
|
||||
|
||||
return Read<T>(va);
|
||||
return base.ReadTracked<T>(va);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
|
@ -190,117 +176,11 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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)
|
||||
public override void Write(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
try
|
||||
{
|
||||
AssertValidAddressAndSize(va, (ulong)data.Length);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
base.Write(va, data);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
|
@ -312,60 +192,47 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
if (tracked)
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)size, false);
|
||||
}
|
||||
|
||||
if (IsContiguousAndMapped(va, size))
|
||||
{
|
||||
return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> data = new byte[size];
|
||||
|
||||
base.Read(va, data);
|
||||
|
||||
return data;
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
|
||||
public override ReadOnlySequence<byte> GetReadOnlySequence(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);
|
||||
}
|
||||
|
||||
if (IsContiguousAndMapped(va, size))
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
if (tracked)
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)size, true);
|
||||
throw;
|
||||
}
|
||||
|
||||
return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
|
||||
}
|
||||
else
|
||||
{
|
||||
Memory<byte> memory = new byte[size];
|
||||
|
||||
GetSpan(va, size).CopyTo(memory.Span);
|
||||
|
||||
return new WritableRegion(this, va, memory, tracked);
|
||||
return ReadOnlySequence<byte>.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ref T GetRef<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
if (!IsContiguous(va, Unsafe.SizeOf<T>()))
|
||||
|
@ -378,56 +245,6 @@ namespace Ryujinx.Cpu.Jit
|
|||
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/>
|
||||
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
|
||||
{
|
||||
|
@ -532,9 +349,8 @@ namespace Ryujinx.Cpu.Jit
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsMapped(ulong va)
|
||||
public override bool IsMapped(ulong va)
|
||||
{
|
||||
if (!ValidateAddress(va))
|
||||
{
|
||||
|
@ -544,9 +360,9 @@ namespace Ryujinx.Cpu.Jit
|
|||
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/>
|
||||
|
@ -643,9 +459,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
{
|
||||
ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
|
||||
|
||||
long pte;
|
||||
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
long pte = Volatile.Read(ref pageRef);
|
||||
|
||||
if ((pte & tag) != 0)
|
||||
{
|
||||
|
@ -663,7 +477,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
@ -683,10 +497,16 @@ namespace Ryujinx.Cpu.Jit
|
|||
/// </summary>
|
||||
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);
|
||||
|
||||
protected override ulong TranslateVirtualAddressForRead(ulong va)
|
||||
protected override nuint TranslateVirtualAddressChecked(ulong va)
|
||||
=> GetPhysicalAddressInternal(va);
|
||||
|
||||
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
|
||||
=> GetPhysicalAddressInternal(va);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using Ryujinx.Memory;
|
|||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
@ -12,7 +13,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
/// <summary>
|
||||
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
|
||||
/// </summary>
|
||||
public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked
|
||||
{
|
||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||
private readonly bool _unsafeMode;
|
||||
|
@ -26,7 +27,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
private readonly ManagedPageFlags _pages;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize;
|
||||
public bool UsesPrivateAllocations => false;
|
||||
|
||||
public int AddressSpaceBits { get; }
|
||||
|
||||
|
@ -96,12 +97,6 @@ namespace Ryujinx.Cpu.Jit
|
|||
Tracking.Map(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void MapForeign(ulong va, nuint hostPointer, ulong size)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
|
@ -138,8 +133,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T Read<T>(ulong va) where T : unmanaged
|
||||
public override T Read<T>(ulong va)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -158,14 +152,11 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T ReadTracked<T>(ulong va) where T : unmanaged
|
||||
public override T ReadTracked<T>(ulong va)
|
||||
{
|
||||
try
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
|
||||
|
||||
return Read<T>(va);
|
||||
return base.ReadTracked<T>(va);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
|
@ -178,7 +169,6 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Read(ulong va, Span<byte> data)
|
||||
{
|
||||
try
|
||||
|
@ -196,9 +186,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Write<T>(ulong va, T value) where T : unmanaged
|
||||
public override void Write<T>(ulong va, T value)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -215,8 +203,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Write(ulong va, ReadOnlySpan<byte> data)
|
||||
public override void Write(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -233,8 +220,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
|
||||
public override void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -251,8 +237,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
|
||||
public override bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -279,8 +264,21 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
|
||||
public override ReadOnlySequence<byte> GetReadOnlySequence(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)
|
||||
{
|
||||
|
@ -294,8 +292,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
return _addressSpace.Mirror.GetSpan(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
|
||||
public override WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
|
||||
{
|
||||
if (tracked)
|
||||
{
|
||||
|
@ -309,7 +306,6 @@ namespace Ryujinx.Cpu.Jit
|
|||
return _addressSpace.Mirror.GetWritableRegion(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ref T GetRef<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
|
||||
|
@ -317,9 +313,8 @@ namespace Ryujinx.Cpu.Jit
|
|||
return ref _addressSpace.Mirror.GetRef<T>(va);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsMapped(ulong va)
|
||||
public override bool IsMapped(ulong va)
|
||||
{
|
||||
return ValidateAddress(va) && _pages.IsMapped(va);
|
||||
}
|
||||
|
@ -390,11 +385,10 @@ namespace Ryujinx.Cpu.Jit
|
|||
return _pageTable.Read(va) + (va & PageMask);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
|
||||
/// </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);
|
||||
|
||||
|
@ -407,23 +401,6 @@ namespace Ryujinx.Cpu.Jit
|
|||
_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)
|
||||
{
|
||||
|
@ -470,10 +447,16 @@ namespace Ryujinx.Cpu.Jit
|
|||
_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);
|
||||
|
||||
protected override ulong TranslateVirtualAddressForRead(ulong va)
|
||||
=> va;
|
||||
protected override nuint TranslateVirtualAddressChecked(ulong va)
|
||||
=> (nuint)GetPhysicalAddressChecked(va);
|
||||
|
||||
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
|
||||
=> (nuint)GetPhysicalAddressInternal(va);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Cpu.Jit.HostTracked;
|
||||
using Ryujinx.Cpu.Signal;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
|
||||
/// </summary>
|
||||
public sealed class MemoryManagerHostTracked : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IWritableBlock, IMemoryManager, IVirtualMemoryManagerTracked
|
||||
public sealed class MemoryManagerHostTracked : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked
|
||||
{
|
||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||
private readonly bool _unsafeMode;
|
||||
|
@ -34,7 +35,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
protected override ulong AddressSpaceSize { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Supports4KBPages => false;
|
||||
public bool UsesPrivateAllocations => true;
|
||||
|
||||
public IntPtr PageTablePointer => _nativePageTable.PageTablePointer;
|
||||
|
||||
|
@ -100,12 +101,6 @@ namespace Ryujinx.Cpu.Jit
|
|||
Tracking.Map(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void MapForeign(ulong va, nuint hostPointer, ulong size)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
|
@ -120,18 +115,11 @@ namespace Ryujinx.Cpu.Jit
|
|||
_nativePageTable.Unmap(va, size);
|
||||
}
|
||||
|
||||
public T Read<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
|
||||
}
|
||||
|
||||
public T ReadTracked<T>(ulong va) where T : unmanaged
|
||||
public override T ReadTracked<T>(ulong va)
|
||||
{
|
||||
try
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
|
||||
|
||||
return Read<T>(va);
|
||||
return base.ReadTracked<T>(va);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
|
@ -145,38 +133,39 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SignalMemoryTracking(va, (ulong)data.Length, true);
|
||||
|
||||
WriteImpl(va, data);
|
||||
}
|
||||
|
||||
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
try
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -206,35 +195,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
private void WriteImpl(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
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)
|
||||
public override ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
|
@ -254,13 +215,13 @@ namespace Ryujinx.Cpu.Jit
|
|||
{
|
||||
Span<byte> data = new byte[size];
|
||||
|
||||
ReadImpl(va, data);
|
||||
Read(va, 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)
|
||||
{
|
||||
|
@ -278,11 +239,11 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
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)]
|
||||
public bool IsMapped(ulong va)
|
||||
public override bool IsMapped(ulong va)
|
||||
{
|
||||
return ValidateAddress(va) && _pages.IsMapped(va);
|
||||
}
|
||||
|
@ -311,8 +272,6 @@ namespace Ryujinx.Cpu.Jit
|
|||
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)
|
||||
{
|
||||
if (_addressSpace.HasAnyPrivateAllocation(va, (ulong)size, out PrivateRange range))
|
||||
|
@ -491,44 +450,11 @@ namespace Ryujinx.Cpu.Jit
|
|||
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/>
|
||||
/// <remarks>
|
||||
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
|
||||
/// </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);
|
||||
|
||||
|
@ -543,23 +469,6 @@ namespace Ryujinx.Cpu.Jit
|
|||
_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)
|
||||
{
|
||||
return Tracking.BeginTracking(address, size, id, flags);
|
||||
|
@ -618,10 +527,44 @@ namespace Ryujinx.Cpu.Jit
|
|||
_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);
|
||||
|
||||
protected override ulong TranslateVirtualAddressForRead(ulong va)
|
||||
=> GetPhysicalAddressInternal(va);
|
||||
protected override void WriteImpl(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
using Ryujinx.Memory;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
public abstract class VirtualMemoryManagerRefCountedBase<TVirtual, TPhysical> : VirtualMemoryManagerBase<TVirtual, TPhysical>, IRefCounted
|
||||
where TVirtual : IBinaryInteger<TVirtual>
|
||||
where TPhysical : IBinaryInteger<TPhysical>
|
||||
public abstract class VirtualMemoryManagerRefCountedBase : VirtualMemoryManagerBase, IRefCounted
|
||||
{
|
||||
private int _referenceCount;
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -143,11 +145,11 @@ namespace Ryujinx.Graphics.Device
|
|||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
8
src/Ryujinx.Graphics.GAL/IImageArray.cs
Normal file
8
src/Ryujinx.Graphics.GAL/IImageArray.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public interface IImageArray
|
||||
{
|
||||
void SetFormats(int index, Format[] imageFormats);
|
||||
void SetImages(int index, ITexture[] images);
|
||||
}
|
||||
}
|
|
@ -59,6 +59,7 @@ namespace Ryujinx.Graphics.GAL
|
|||
void SetIndexBuffer(BufferRange buffer, IndexType type);
|
||||
|
||||
void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
|
||||
void SetImageArray(ShaderStage stage, int binding, IImageArray array);
|
||||
|
||||
void SetLineParameters(float width, bool smooth);
|
||||
|
||||
|
@ -89,6 +90,7 @@ namespace Ryujinx.Graphics.GAL
|
|||
void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers);
|
||||
|
||||
void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler);
|
||||
void SetTextureArray(ShaderStage stage, int binding, ITextureArray array);
|
||||
|
||||
void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
|
||||
void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers);
|
||||
|
|
|
@ -21,10 +21,14 @@ namespace Ryujinx.Graphics.GAL
|
|||
BufferHandle CreateBuffer(nint pointer, int size);
|
||||
BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers);
|
||||
|
||||
IImageArray CreateImageArray(int size, bool isBuffer);
|
||||
|
||||
IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
|
||||
|
||||
ISampler CreateSampler(SamplerCreateInfo info);
|
||||
ITexture CreateTexture(TextureCreateInfo info);
|
||||
ITextureArray CreateTextureArray(int size, bool isBuffer);
|
||||
|
||||
bool PrepareHostMapping(nint address, ulong size);
|
||||
|
||||
void CreateSync(ulong id, bool strict);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
|
@ -17,10 +17,34 @@ namespace Ryujinx.Graphics.GAL
|
|||
PinnedSpan<byte> GetData();
|
||||
PinnedSpan<byte> GetData(int layer, int level);
|
||||
|
||||
void SetData(SpanOrArray<byte> data);
|
||||
void SetData(SpanOrArray<byte> data, int layer, int level);
|
||||
void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region);
|
||||
/// <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>
|
||||
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 Release();
|
||||
}
|
||||
}
|
||||
|
|
8
src/Ryujinx.Graphics.GAL/ITextureArray.cs
Normal file
8
src/Ryujinx.Graphics.GAL/ITextureArray.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public interface ITextureArray
|
||||
{
|
||||
void SetSamplers(int index, ISampler[] samplers);
|
||||
void SetTextures(int index, ITexture[] textures);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Commands;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer;
|
||||
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.Renderer;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
@ -46,10 +48,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
Register<CreateBufferAccessCommand>(CommandType.CreateBufferAccess);
|
||||
Register<CreateBufferSparseCommand>(CommandType.CreateBufferSparse);
|
||||
Register<CreateHostBufferCommand>(CommandType.CreateHostBuffer);
|
||||
Register<CreateImageArrayCommand>(CommandType.CreateImageArray);
|
||||
Register<CreateProgramCommand>(CommandType.CreateProgram);
|
||||
Register<CreateSamplerCommand>(CommandType.CreateSampler);
|
||||
Register<CreateSyncCommand>(CommandType.CreateSync);
|
||||
Register<CreateTextureCommand>(CommandType.CreateTexture);
|
||||
Register<CreateTextureArrayCommand>(CommandType.CreateTextureArray);
|
||||
Register<GetCapabilitiesCommand>(CommandType.GetCapabilities);
|
||||
Register<PreFrameCommand>(CommandType.PreFrame);
|
||||
Register<ReportCounterCommand>(CommandType.ReportCounter);
|
||||
|
@ -63,6 +67,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose);
|
||||
Register<CounterEventFlushCommand>(CommandType.CounterEventFlush);
|
||||
|
||||
Register<ImageArraySetFormatsCommand>(CommandType.ImageArraySetFormats);
|
||||
Register<ImageArraySetImagesCommand>(CommandType.ImageArraySetImages);
|
||||
|
||||
Register<ProgramDisposeCommand>(CommandType.ProgramDispose);
|
||||
Register<ProgramGetBinaryCommand>(CommandType.ProgramGetBinary);
|
||||
Register<ProgramCheckLinkCommand>(CommandType.ProgramCheckLink);
|
||||
|
@ -82,6 +89,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion);
|
||||
Register<TextureSetStorageCommand>(CommandType.TextureSetStorage);
|
||||
|
||||
Register<TextureArraySetSamplersCommand>(CommandType.TextureArraySetSamplers);
|
||||
Register<TextureArraySetTexturesCommand>(CommandType.TextureArraySetTextures);
|
||||
|
||||
Register<WindowPresentCommand>(CommandType.WindowPresent);
|
||||
|
||||
Register<BarrierCommand>(CommandType.Barrier);
|
||||
|
@ -114,6 +124,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
Register<SetTransformFeedbackBuffersCommand>(CommandType.SetTransformFeedbackBuffers);
|
||||
Register<SetUniformBuffersCommand>(CommandType.SetUniformBuffers);
|
||||
Register<SetImageCommand>(CommandType.SetImage);
|
||||
Register<SetImageArrayCommand>(CommandType.SetImageArray);
|
||||
Register<SetIndexBufferCommand>(CommandType.SetIndexBuffer);
|
||||
Register<SetLineParametersCommand>(CommandType.SetLineParameters);
|
||||
Register<SetLogicOpStateCommand>(CommandType.SetLogicOpState);
|
||||
|
@ -130,6 +141,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
Register<SetScissorsCommand>(CommandType.SetScissor);
|
||||
Register<SetStencilTestCommand>(CommandType.SetStencilTest);
|
||||
Register<SetTextureAndSamplerCommand>(CommandType.SetTextureAndSampler);
|
||||
Register<SetTextureArrayCommand>(CommandType.SetTextureArray);
|
||||
Register<SetUserClipDistanceCommand>(CommandType.SetUserClipDistance);
|
||||
Register<SetVertexAttribsCommand>(CommandType.SetVertexAttribs);
|
||||
Register<SetVertexBuffersCommand>(CommandType.SetVertexBuffers);
|
||||
|
|
|
@ -7,10 +7,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
CreateBufferAccess,
|
||||
CreateBufferSparse,
|
||||
CreateHostBuffer,
|
||||
CreateImageArray,
|
||||
CreateProgram,
|
||||
CreateSampler,
|
||||
CreateSync,
|
||||
CreateTexture,
|
||||
CreateTextureArray,
|
||||
GetCapabilities,
|
||||
Unused,
|
||||
PreFrame,
|
||||
|
@ -25,6 +27,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
CounterEventDispose,
|
||||
CounterEventFlush,
|
||||
|
||||
ImageArraySetFormats,
|
||||
ImageArraySetImages,
|
||||
|
||||
ProgramDispose,
|
||||
ProgramGetBinary,
|
||||
ProgramCheckLink,
|
||||
|
@ -44,6 +49,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
TextureSetDataSliceRegion,
|
||||
TextureSetStorage,
|
||||
|
||||
TextureArraySetSamplers,
|
||||
TextureArraySetTextures,
|
||||
|
||||
WindowPresent,
|
||||
|
||||
Barrier,
|
||||
|
@ -76,6 +84,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
SetTransformFeedbackBuffers,
|
||||
SetUniformBuffers,
|
||||
SetImage,
|
||||
SetImageArray,
|
||||
SetIndexBuffer,
|
||||
SetLineParameters,
|
||||
SetLogicOpState,
|
||||
|
@ -92,6 +101,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
SetScissor,
|
||||
SetStencilTest,
|
||||
SetTextureAndSampler,
|
||||
SetTextureArray,
|
||||
SetUserClipDistance,
|
||||
SetVertexAttribs,
|
||||
SetVertexBuffers,
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||
{
|
||||
|
@ -8,9 +8,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
|||
{
|
||||
public readonly CommandType CommandType => CommandType.TextureSetData;
|
||||
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;
|
||||
_data = data;
|
||||
|
@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
|||
public static void Run(ref TextureSetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ThreadedTexture texture = command._texture.Get(threaded);
|
||||
texture.Base.SetData(new ReadOnlySpan<byte>(command._data.Get(threaded)));
|
||||
texture.Base.SetData(command._data.Get(threaded));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||
{
|
||||
|
@ -8,11 +8,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
|||
{
|
||||
public readonly CommandType CommandType => CommandType.TextureSetDataSlice;
|
||||
private TableRef<ThreadedTexture> _texture;
|
||||
private TableRef<byte[]> _data;
|
||||
private TableRef<IMemoryOwner<byte>> _data;
|
||||
private int _layer;
|
||||
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;
|
||||
_data = data;
|
||||
|
@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
|||
public static void Run(ref TextureSetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||
{
|
||||
|
@ -8,12 +8,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
|||
{
|
||||
public readonly CommandType CommandType => CommandType.TextureSetDataSliceRegion;
|
||||
private TableRef<ThreadedTexture> _texture;
|
||||
private TableRef<byte[]> _data;
|
||||
private TableRef<IMemoryOwner<byte>> _data;
|
||||
private int _layer;
|
||||
private int _level;
|
||||
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;
|
||||
_data = data;
|
||||
|
@ -25,7 +25,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
|||
public static void Run(ref TextureSetDataSliceRegionCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||
{
|
||||
|
@ -110,21 +110,24 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
|||
_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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -183,6 +183,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
_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)
|
||||
{
|
||||
_renderer.New<SetIndexBufferCommand>().Set(buffer, type);
|
||||
|
@ -285,6 +291,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
_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)
|
||||
{
|
||||
_renderer.New<SetTransformFeedbackBuffersCommand>().Set(_renderer.CopySpan(buffers));
|
||||
|
|
|
@ -299,6 +299,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
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)
|
||||
{
|
||||
var program = new ThreadedProgram(this);
|
||||
|
@ -349,6 +358,14 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
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)
|
||||
{
|
||||
|
|
|
@ -71,19 +71,21 @@ namespace Ryujinx.Graphics.GAL
|
|||
public readonly struct ResourceUsage : IEquatable<ResourceUsage>
|
||||
{
|
||||
public int Binding { get; }
|
||||
public int ArrayLength { get; }
|
||||
public ResourceType Type { 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;
|
||||
ArrayLength = arrayLength;
|
||||
Type = type;
|
||||
Stages = stages;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Binding, Type, Stages);
|
||||
return HashCode.Combine(Binding, ArrayLength, Type, Stages);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
|
@ -93,7 +95,7 @@ namespace Ryujinx.Graphics.GAL
|
|||
|
||||
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)
|
||||
|
|
|
@ -89,5 +89,10 @@ namespace Ryujinx.Graphics.Gpu
|
|||
/// Maximum size that an storage buffer is assumed to have when the correct size is unknown.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ using Ryujinx.Graphics.Gpu.Engine.Threed;
|
|||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
@ -308,7 +309,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
|||
|
||||
if (target != null)
|
||||
{
|
||||
byte[] data;
|
||||
IMemoryOwner<byte> data;
|
||||
if (srcLinear)
|
||||
{
|
||||
data = LayoutConverter.ConvertLinearStridedToLinear(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using System;
|
||||
|
@ -198,7 +199,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
|
|||
if (target != null)
|
||||
{
|
||||
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();
|
||||
|
||||
return;
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
/// <returns>Texture target value</returns>
|
||||
public static Target GetTarget(SamplerType type)
|
||||
{
|
||||
type &= ~(SamplerType.Indexed | SamplerType.Shadow);
|
||||
type &= ~SamplerType.Shadow;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
|
|
|
@ -107,8 +107,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
if (texture.CacheNode != _textures.Last)
|
||||
{
|
||||
_textures.Remove(texture.CacheNode);
|
||||
|
||||
texture.CacheNode = _textures.AddLast(texture);
|
||||
_textures.AddLast(texture.CacheNode);
|
||||
}
|
||||
|
||||
if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion)
|
||||
|
|
|
@ -111,6 +111,21 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <returns>The GPU resource with the given ID</returns>
|
||||
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>
|
||||
/// Checks if a given ID is valid and inside the range of the pool.
|
||||
/// </summary>
|
||||
|
@ -197,6 +212,23 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
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 Delete(T1 item);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
|
@ -7,6 +6,7 @@ using Ryujinx.Graphics.Texture.Astc;
|
|||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
@ -573,7 +573,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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())
|
||||
{
|
||||
|
@ -684,7 +684,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// Uploads new texture data to the host GPU.
|
||||
/// </summary>
|
||||
/// <param name="data">New data</param>
|
||||
public void SetData(SpanOrArray<byte> data)
|
||||
public void SetData(IMemoryOwner<byte> data)
|
||||
{
|
||||
BlacklistScale();
|
||||
|
||||
|
@ -703,7 +703,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <param name="data">New data</param>
|
||||
/// <param name="layer">Target layer</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();
|
||||
|
||||
|
@ -721,7 +721,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <param name="layer">Target layer</param>
|
||||
/// <param name="level">Target level</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();
|
||||
|
||||
|
@ -739,7 +739,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <param name="level">Mip level to convert</param>
|
||||
/// <param name="single">True to convert a single slice</param>
|
||||
/// <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 height = Info.Height;
|
||||
|
@ -754,11 +754,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
int sliceDepth = single ? 1 : depth;
|
||||
|
||||
SpanOrArray<byte> result;
|
||||
IMemoryOwner<byte> linear;
|
||||
|
||||
if (Info.IsLinear)
|
||||
{
|
||||
result = LayoutConverter.ConvertLinearStridedToLinear(
|
||||
linear = LayoutConverter.ConvertLinearStridedToLinear(
|
||||
width,
|
||||
height,
|
||||
Info.FormatInfo.BlockWidth,
|
||||
|
@ -770,7 +770,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
else
|
||||
{
|
||||
result = LayoutConverter.ConvertBlockLinearToLinear(
|
||||
linear = LayoutConverter.ConvertBlockLinearToLinear(
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
|
@ -787,33 +787,41 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
data);
|
||||
}
|
||||
|
||||
IMemoryOwner<byte> result = linear;
|
||||
|
||||
// Handle compressed cases not supported by the host:
|
||||
// - ASTC is usually not supported on desktop cards.
|
||||
// - BC4/BC5 is not supported on 3D textures.
|
||||
if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc())
|
||||
{
|
||||
if (!AstcDecoder.TryDecodeToRgba8P(
|
||||
result.ToArray(),
|
||||
Info.FormatInfo.BlockWidth,
|
||||
Info.FormatInfo.BlockHeight,
|
||||
width,
|
||||
height,
|
||||
sliceDepth,
|
||||
levels,
|
||||
layers,
|
||||
out byte[] decoded))
|
||||
using (result)
|
||||
{
|
||||
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())
|
||||
{
|
||||
|
@ -821,16 +829,22 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
case Format.Etc2RgbaSrgb:
|
||||
case Format.Etc2RgbaUnorm:
|
||||
result = ETC2Decoder.DecodeRgba(result, width, height, sliceDepth, levels, layers);
|
||||
break;
|
||||
using (result)
|
||||
{
|
||||
return ETC2Decoder.DecodeRgba(result.Memory.Span, width, height, sliceDepth, levels, layers);
|
||||
}
|
||||
case Format.Etc2RgbPtaSrgb:
|
||||
case Format.Etc2RgbPtaUnorm:
|
||||
result = ETC2Decoder.DecodePta(result, width, height, sliceDepth, levels, layers);
|
||||
break;
|
||||
using (result)
|
||||
{
|
||||
return ETC2Decoder.DecodePta(result.Memory.Span, width, height, sliceDepth, levels, layers);
|
||||
}
|
||||
case Format.Etc2RgbSrgb:
|
||||
case Format.Etc2RgbUnorm:
|
||||
result = ETC2Decoder.DecodeRgb(result, width, height, sliceDepth, levels, layers);
|
||||
break;
|
||||
using (result)
|
||||
{
|
||||
return ETC2Decoder.DecodeRgb(result.Memory.Span, width, height, sliceDepth, levels, layers);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities))
|
||||
|
@ -839,48 +853,75 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
case Format.Bc1RgbaSrgb:
|
||||
case Format.Bc1RgbaUnorm:
|
||||
result = BCnDecoder.DecodeBC1(result, width, height, sliceDepth, levels, layers);
|
||||
break;
|
||||
using (result)
|
||||
{
|
||||
return BCnDecoder.DecodeBC1(result.Memory.Span, width, height, sliceDepth, levels, layers);
|
||||
}
|
||||
case Format.Bc2Srgb:
|
||||
case Format.Bc2Unorm:
|
||||
result = BCnDecoder.DecodeBC2(result, width, height, sliceDepth, levels, layers);
|
||||
break;
|
||||
using (result)
|
||||
{
|
||||
return BCnDecoder.DecodeBC2(result.Memory.Span, width, height, sliceDepth, levels, layers);
|
||||
}
|
||||
case Format.Bc3Srgb:
|
||||
case Format.Bc3Unorm:
|
||||
result = BCnDecoder.DecodeBC3(result, width, height, sliceDepth, levels, layers);
|
||||
break;
|
||||
using (result)
|
||||
{
|
||||
return BCnDecoder.DecodeBC3(result.Memory.Span, width, height, sliceDepth, levels, layers);
|
||||
}
|
||||
case Format.Bc4Snorm:
|
||||
case Format.Bc4Unorm:
|
||||
result = BCnDecoder.DecodeBC4(result, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
|
||||
break;
|
||||
using (result)
|
||||
{
|
||||
return BCnDecoder.DecodeBC4(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
|
||||
}
|
||||
case Format.Bc5Snorm:
|
||||
case Format.Bc5Unorm:
|
||||
result = BCnDecoder.DecodeBC5(result, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
|
||||
break;
|
||||
using (result)
|
||||
{
|
||||
return BCnDecoder.DecodeBC5(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
|
||||
}
|
||||
case Format.Bc6HSfloat:
|
||||
case Format.Bc6HUfloat:
|
||||
result = BCnDecoder.DecodeBC6(result, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
|
||||
break;
|
||||
using (result)
|
||||
{
|
||||
return BCnDecoder.DecodeBC6(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
|
||||
}
|
||||
case Format.Bc7Srgb:
|
||||
case Format.Bc7Unorm:
|
||||
result = BCnDecoder.DecodeBC7(result, width, height, sliceDepth, levels, layers);
|
||||
break;
|
||||
using (result)
|
||||
{
|
||||
return BCnDecoder.DecodeBC7(result.Memory.Span, width, height, sliceDepth, levels, layers);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm)
|
||||
{
|
||||
result = PixelConverter.ConvertR4G4ToR4G4B4A4(result, width);
|
||||
|
||||
if (!_context.Capabilities.SupportsR4G4B4A4Format)
|
||||
using (result)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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())
|
||||
|
@ -889,19 +930,27 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
case Format.B5G6R5Unorm:
|
||||
case Format.R5G6B5Unorm:
|
||||
result = PixelConverter.ConvertR5G6B5ToR8G8B8A8(result, width);
|
||||
break;
|
||||
using (result)
|
||||
{
|
||||
return PixelConverter.ConvertR5G6B5ToR8G8B8A8(result.Memory.Span, width);
|
||||
}
|
||||
case Format.B5G5R5A1Unorm:
|
||||
case Format.R5G5B5X1Unorm:
|
||||
case Format.R5G5B5A1Unorm:
|
||||
result = PixelConverter.ConvertR5G5B5ToR8G8B8A8(result, width, Format == Format.R5G5B5X1Unorm);
|
||||
break;
|
||||
using (result)
|
||||
{
|
||||
return PixelConverter.ConvertR5G5B5ToR8G8B8A8(result.Memory.Span, width, Format == Format.R5G5B5X1Unorm);
|
||||
}
|
||||
case Format.A1B5G5R5Unorm:
|
||||
result = PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result, width);
|
||||
break;
|
||||
using (result)
|
||||
{
|
||||
return PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result.Memory.Span, width);
|
||||
}
|
||||
case Format.R4G4B4A4Unorm:
|
||||
result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width);
|
||||
break;
|
||||
using (result)
|
||||
{
|
||||
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
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>
|
||||
/// Constant buffer slot with the texture handle.
|
||||
/// </summary>
|
||||
|
@ -45,14 +50,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <param name="target">The shader sampler target type</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="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="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>
|
||||
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;
|
||||
Format = format;
|
||||
Binding = binding;
|
||||
ArrayLength = arrayLength;
|
||||
CbufSlot = cbufSlot;
|
||||
Handle = handle;
|
||||
Flags = flags;
|
||||
|
@ -63,10 +70,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
/// <param name="target">The shader sampler target type</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="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>
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
730
src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
Normal file
730
src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,6 +34,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
private readonly TexturePoolCache _texturePoolCache;
|
||||
private readonly SamplerPoolCache _samplerPoolCache;
|
||||
|
||||
private readonly TextureBindingsArrayCache _arrayBindingsCache;
|
||||
|
||||
private TexturePool _cachedTexturePool;
|
||||
private SamplerPool _cachedSamplerPool;
|
||||
|
||||
|
@ -56,6 +58,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
private TextureState[] _textureState;
|
||||
private TextureState[] _imageState;
|
||||
|
||||
private int[] _textureCounts;
|
||||
|
||||
private int _texturePoolSequence;
|
||||
private int _samplerPoolSequence;
|
||||
|
||||
|
@ -85,6 +89,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
_isCompute = isCompute;
|
||||
|
||||
_arrayBindingsCache = new TextureBindingsArrayCache(context, channel, isCompute);
|
||||
|
||||
int stages = isCompute ? 1 : Constants.ShaderStages;
|
||||
|
||||
_textureBindings = new TextureBindingInfo[stages][];
|
||||
|
@ -95,9 +101,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
for (int stage = 0; stage < stages; stage++)
|
||||
{
|
||||
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
|
||||
_imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize];
|
||||
_textureBindings[stage] = Array.Empty<TextureBindingInfo>();
|
||||
_imageBindings[stage] = Array.Empty<TextureBindingInfo>();
|
||||
}
|
||||
|
||||
_textureCounts = Array.Empty<int>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -109,6 +117,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
_textureBindings = bindings.TextureBindings;
|
||||
_imageBindings = bindings.ImageBindings;
|
||||
|
||||
_textureCounts = bindings.TextureCounts;
|
||||
|
||||
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>
|
||||
/// Ensures that the texture bindings are visible to the host GPU.
|
||||
/// 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];
|
||||
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);
|
||||
|
||||
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.
|
||||
int baseScaleIndex = _textureBindings[stageIndex].Length;
|
||||
int baseScaleIndex = _textureCounts[stageIndex];
|
||||
|
||||
int cachedTextureBufferIndex = -1;
|
||||
int cachedSamplerBufferIndex = -1;
|
||||
|
@ -595,6 +591,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
|
||||
TextureUsageFlags usageFlags = bindingInfo.Flags;
|
||||
|
||||
if (bindingInfo.ArrayLength > 1)
|
||||
{
|
||||
_arrayBindingsCache.UpdateImageArray(pool, stage, stageIndex, _textureBufferIndex, bindingInfo);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
int scaleIndex = baseScaleIndex + index;
|
||||
|
||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
|
||||
|
@ -620,7 +624,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
if (isStore)
|
||||
{
|
||||
cachedTexture?.SignalModified();
|
||||
cachedTexture.SignalModified();
|
||||
}
|
||||
|
||||
Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format;
|
||||
|
|
|
@ -247,6 +247,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
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;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
|
@ -6,6 +5,7 @@ using Ryujinx.Memory;
|
|||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
|
@ -282,7 +282,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <param name="texture">The texture being discarded</param>
|
||||
public void DiscardData(Texture texture)
|
||||
|
@ -445,7 +445,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -182,11 +182,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public void DiscardData()
|
||||
{
|
||||
Modified = false;
|
||||
DeferredCopy = null;
|
||||
|
||||
foreach (RegionHandle handle in Handles)
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Memory range used for buffers.
|
||||
/// </summary>
|
||||
readonly struct BufferBounds
|
||||
readonly struct BufferBounds : IEquatable<BufferBounds>
|
||||
{
|
||||
/// <summary>
|
||||
/// Physical memory ranges where the buffer is mapped.
|
||||
|
@ -33,5 +34,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
Range = range;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
private readonly VertexBuffer[] _vertexBuffers;
|
||||
private readonly BufferBounds[] _transformFeedbackBuffers;
|
||||
private readonly List<BufferTextureBinding> _bufferTextures;
|
||||
private readonly List<BufferTextureArrayBinding<ITextureArray>> _bufferTextureArrays;
|
||||
private readonly List<BufferTextureArrayBinding<IImageArray>> _bufferImageArrays;
|
||||
private readonly BufferAssignment[] _ranges;
|
||||
|
||||
/// <summary>
|
||||
|
@ -140,11 +142,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
}
|
||||
|
||||
_bufferTextures = new List<BufferTextureBinding>();
|
||||
_bufferTextureArrays = new List<BufferTextureArrayBinding<ITextureArray>>();
|
||||
_bufferImageArrays = new List<BufferTextureArrayBinding<IImageArray>>();
|
||||
|
||||
_ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the memory range with the index buffer data, to be used for subsequent draw calls.
|
||||
/// </summary>
|
||||
|
@ -418,6 +421,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
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>
|
||||
/// Gets the address of the graphics uniform buffer currently bound at the given index.
|
||||
/// </summary>
|
||||
|
@ -429,6 +442,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
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>
|
||||
/// Gets the bounds of the uniform buffer currently bound at the given index.
|
||||
/// </summary>
|
||||
|
@ -459,7 +483,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
BindBuffers(bufferCache, _cpStorageBuffers, isStorage: true);
|
||||
BindBuffers(bufferCache, _cpUniformBuffers, isStorage: false);
|
||||
|
||||
CommitBufferTextureBindings();
|
||||
CommitBufferTextureBindings(bufferCache);
|
||||
|
||||
// Force rebind after doing compute work.
|
||||
Rebind();
|
||||
|
@ -470,14 +494,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// <summary>
|
||||
/// Commit any queued buffer texture bindings.
|
||||
/// </summary>
|
||||
private void CommitBufferTextureBindings()
|
||||
/// <param name="bufferCache">Buffer cache</param>
|
||||
private void CommitBufferTextureBindings(BufferCache bufferCache)
|
||||
{
|
||||
if (_bufferTextures.Count > 0)
|
||||
{
|
||||
foreach (var binding in _bufferTextures)
|
||||
{
|
||||
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);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
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>
|
||||
|
@ -676,7 +728,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
UpdateBuffers(_gpUniformBuffers);
|
||||
}
|
||||
|
||||
CommitBufferTextureBindings();
|
||||
CommitBufferTextureBindings(bufferCache);
|
||||
|
||||
_rebind = false;
|
||||
|
||||
|
@ -828,6 +880,50 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
_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>
|
||||
/// Force all bound textures and images to be rebound the next time CommitBindings is called.
|
||||
/// </summary>
|
||||
|
|
66
src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs
Normal file
66
src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
@ -240,11 +242,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
|
@ -6,6 +7,7 @@ using Ryujinx.Memory;
|
|||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
@ -190,7 +192,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
}
|
||||
else
|
||||
{
|
||||
Memory<byte> memory = new byte[range.GetSize()];
|
||||
IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(range.GetSize());
|
||||
|
||||
Memory<byte> memory = memoryOwner.Memory;
|
||||
|
||||
int offset = 0;
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
|
@ -204,7 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
offset += size;
|
||||
}
|
||||
|
||||
return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memory, tracked);
|
||||
return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memoryOwner, tracked);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
public BufferDescriptor[][] ConstantBufferBindings { get; }
|
||||
public BufferDescriptor[][] StorageBufferBindings { get; }
|
||||
|
||||
public int[] TextureCounts { get; }
|
||||
|
||||
public int MaxTextureBinding { get; }
|
||||
public int MaxImageBinding { get; }
|
||||
|
||||
|
@ -34,6 +36,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
ConstantBufferBindings = new BufferDescriptor[stageCount][];
|
||||
StorageBufferBindings = new BufferDescriptor[stageCount][];
|
||||
|
||||
TextureCounts = new int[stageCount];
|
||||
|
||||
int maxTextureBinding = -1;
|
||||
int maxImageBinding = -1;
|
||||
int offset = isCompute ? 0 : 1;
|
||||
|
@ -59,13 +63,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
var result = new TextureBindingInfo(
|
||||
target,
|
||||
descriptor.Binding,
|
||||
descriptor.ArrayLength,
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
|
||||
if (descriptor.Binding > maxTextureBinding)
|
||||
if (descriptor.ArrayLength <= 1)
|
||||
{
|
||||
maxTextureBinding = descriptor.Binding;
|
||||
if (descriptor.Binding > maxTextureBinding)
|
||||
{
|
||||
maxTextureBinding = descriptor.Binding;
|
||||
}
|
||||
|
||||
TextureCounts[i]++;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -80,11 +90,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
target,
|
||||
format,
|
||||
descriptor.Binding,
|
||||
descriptor.ArrayLength,
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
|
||||
if (descriptor.Binding > maxImageBinding)
|
||||
if (descriptor.ArrayLength <= 1 && descriptor.Binding > maxImageBinding)
|
||||
{
|
||||
maxImageBinding = descriptor.Binding;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
/// <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="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>
|
||||
public DiskCacheGpuAccessor(
|
||||
GpuContext context,
|
||||
|
@ -108,6 +109,27 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
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/>
|
||||
public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
|
||||
{
|
||||
|
@ -116,13 +138,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
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/>
|
||||
public bool QueryTextureCoordNormalized(int handle, int cbufSlot)
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
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 SharedDataFileName = "shared.data";
|
||||
|
|
|
@ -20,6 +20,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
/// </summary>
|
||||
InvalidCb1DataLength,
|
||||
|
||||
/// <summary>
|
||||
/// The cache is missing the length of a texture array used by the shader.
|
||||
/// </summary>
|
||||
MissingTextureArrayLength,
|
||||
|
||||
/// <summary>
|
||||
/// The cache is missing the descriptor of a texture used by the shader.
|
||||
/// </summary>
|
||||
|
@ -60,6 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
DiskCacheLoadResult.Success => "No error.",
|
||||
DiskCacheLoadResult.NoAccess => "Could not access the cache file.",
|
||||
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.FileCorruptedGeneric => "The cache file is corrupted.",
|
||||
DiskCacheLoadResult.FileCorruptedInvalidMagic => "Magic check failed, the cache file is corrupted.",
|
||||
|
|
|
@ -72,6 +72,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
public ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize)
|
||||
{
|
||||
int size = Math.Max(minimumSize, 0x1000 - (int)(address & 0xfff));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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/>
|
||||
public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
|
||||
{
|
||||
|
@ -127,13 +149,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
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/>
|
||||
public bool QueryTextureCoordNormalized(int handle, int cbufSlot)
|
||||
{
|
||||
|
|
|
@ -20,6 +20,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
private int _reservedTextures;
|
||||
private int _reservedImages;
|
||||
|
||||
private int _staticTexturesCount;
|
||||
private int _staticImagesCount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU accessor.
|
||||
/// </summary>
|
||||
|
@ -48,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
_reservedImages = rrc.ReservedImages;
|
||||
}
|
||||
|
||||
public int QueryBindingConstantBuffer(int index)
|
||||
public int CreateConstantBufferBinding(int index)
|
||||
{
|
||||
int binding;
|
||||
|
||||
|
@ -64,7 +67,39 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
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;
|
||||
|
||||
|
@ -80,48 +115,38 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
return binding + _reservedStorageBuffers;
|
||||
}
|
||||
|
||||
public int QueryBindingTexture(int index, bool isBuffer)
|
||||
public int CreateTextureBinding(int count, bool isBuffer)
|
||||
{
|
||||
int binding;
|
||||
|
||||
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
|
||||
{
|
||||
binding = _resourceCounts.TexturesCount++;
|
||||
binding = _resourceCounts.TexturesCount;
|
||||
|
||||
_resourceCounts.TexturesCount += count;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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 bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision;
|
||||
|
|
|
@ -132,6 +132,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage);
|
||||
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.SBuffers, stages, StorageSetIndex, isStorage: true);
|
||||
AddUsage(info.Textures, stages, TextureSetIndex, isImage: false);
|
||||
|
@ -169,6 +172,30 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
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>
|
||||
/// Adds buffer usage information to the list of usages.
|
||||
/// </summary>
|
||||
|
@ -181,7 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
{
|
||||
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(
|
||||
buffer.Binding,
|
||||
1,
|
||||
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
|
||||
stages));
|
||||
}
|
||||
|
@ -220,10 +248,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture)
|
||||
: (isImage ? ResourceType.Image : ResourceType.TextureAndSampler);
|
||||
|
||||
_resourceUsages[setIndex].Add(new ResourceUsage(
|
||||
texture.Binding,
|
||||
type,
|
||||
stages));
|
||||
_resourceUsages[setIndex].Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
{
|
||||
PrimitiveTopology = 1 << 1,
|
||||
TransformFeedback = 1 << 3,
|
||||
TextureArrayFromBuffer = 1 << 4,
|
||||
}
|
||||
|
||||
private QueriedStateFlags _queriedState;
|
||||
|
@ -153,6 +154,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
}
|
||||
|
||||
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
|
||||
private readonly Dictionary<TextureKey, int> _textureArraySpecialization;
|
||||
private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
|
||||
private Box<TextureSpecializationState>[][] _textureByBinding;
|
||||
private Box<TextureSpecializationState>[][] _imageByBinding;
|
||||
|
@ -163,6 +165,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
private ShaderSpecializationState()
|
||||
{
|
||||
_textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>();
|
||||
_textureArraySpecialization = new Dictionary<TextureKey, int>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -323,6 +326,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
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>
|
||||
/// Indicates that the format of a given texture was used during the shader translation process.
|
||||
/// </summary>
|
||||
|
@ -379,6 +395,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
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>
|
||||
/// Gets the recorded format of a given texture.
|
||||
/// </summary>
|
||||
|
@ -413,6 +440,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
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>
|
||||
/// Gets texture specialization state for a given texture, or create a new one if not present.
|
||||
/// </summary>
|
||||
|
@ -548,6 +586,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
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)
|
||||
{
|
||||
type &= ~(AttributeType.Packed | AttributeType.PackedRgb10A2Signed);
|
||||
|
@ -838,6 +882,22 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -902,6 +962,21 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,8 @@ namespace Ryujinx.Graphics.OpenGL.Effects.Smaa
|
|||
|
||||
public int Quality
|
||||
{
|
||||
get => _quality; set
|
||||
get => _quality;
|
||||
set
|
||||
{
|
||||
_quality = Math.Clamp(value, 0, _qualities.Length - 1);
|
||||
}
|
||||
|
@ -150,8 +151,8 @@ namespace Ryujinx.Graphics.OpenGL.Effects.Smaa
|
|||
_areaTexture = new TextureStorage(_renderer, areaInfo);
|
||||
_searchTexture = new TextureStorage(_renderer, searchInfo);
|
||||
|
||||
var areaTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin");
|
||||
var searchTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin");
|
||||
var areaTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin");
|
||||
var searchTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin");
|
||||
|
||||
var areaView = _areaTexture.CreateDefaultView();
|
||||
var searchView = _searchTexture.CreateDefaultView();
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
@ -8,9 +10,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
{
|
||||
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;
|
||||
|
||||
|
@ -74,7 +78,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
outSpan[i] = BitOperations.RotateLeft(dataSpan[i], 8);
|
||||
}
|
||||
|
||||
return output;
|
||||
return outputMemory;
|
||||
}
|
||||
|
||||
public unsafe static byte[] ConvertD24S8ToS8D24(ReadOnlySpan<byte> data)
|
||||
|
|
67
src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs
Normal file
67
src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
52
src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs
Normal file
52
src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
|
@ -54,19 +54,24 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
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)]);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
|
||||
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();
|
||||
|
||||
if (Format == Format.S8UintD24Unorm)
|
||||
using (data = EnsureDataFormat(data))
|
||||
{
|
||||
dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan);
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = dataSpan)
|
||||
unsafe
|
||||
{
|
||||
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();
|
||||
|
||||
if (Format == Format.S8UintD24Unorm)
|
||||
using (data = EnsureDataFormat(data))
|
||||
{
|
||||
dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan);
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = dataSpan)
|
||||
unsafe
|
||||
{
|
||||
int width = Math.Max(Info.Width >> level, 1);
|
||||
int height = Math.Max(Info.Height >> level, 1);
|
||||
fixed (byte* ptr = data.Memory.Span)
|
||||
{
|
||||
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();
|
||||
|
||||
if (Format == Format.S8UintD24Unorm)
|
||||
using (data = EnsureDataFormat(data))
|
||||
{
|
||||
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);
|
||||
int hInBlocks = BitUtils.DivRoundUp(region.Height, Info.BlockHeight);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = dataSpan)
|
||||
unsafe
|
||||
{
|
||||
ReadFrom2D(
|
||||
(IntPtr)ptr,
|
||||
layer,
|
||||
level,
|
||||
region.X,
|
||||
region.Y,
|
||||
region.Width,
|
||||
region.Height,
|
||||
BitUtils.AlignUp(wInBlocks * Info.BytesPerPixel, 4) * hInBlocks);
|
||||
fixed (byte* ptr = data.Memory.Span)
|
||||
{
|
||||
ReadFrom2D(
|
||||
(IntPtr)ptr,
|
||||
layer,
|
||||
level,
|
||||
region.X,
|
||||
region.Y,
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
TextureTarget target = Target.Convert();
|
||||
|
|
|
@ -90,6 +90,11 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public IImageArray CreateImageArray(int size, bool isBuffer)
|
||||
{
|
||||
return new ImageArray(size);
|
||||
}
|
||||
|
||||
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
||||
{
|
||||
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)
|
||||
{
|
||||
PersistentBuffers.Unmap(buffer);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue