Compare commits

...

9 Commits

Author SHA1 Message Date
jhorv
ead9a25141 Audio rendering: reduce memory allocations (#6604)
* - WritableRegion: enable wrapping IMemoryOwner<byte>
- IVirtualMemoryManager impls of GetWritableRegion() use pooled memory when region is non-contiguous.
- IVirtualMemoryManager: add GetReadOnlySequence() and impls
- ByteMemoryPool: add new method RentCopy()
- ByteMemoryPool: make class static, remove ctor and singleton field from earlier impl

* - BytesReadOnlySequenceSegment: move from Ryujinx.Common.Memory to Ryujinx.Memory
- BytesReadOnlySequenceSegment: add IsContiguousWith() and Replace() methods
- VirtualMemoryManagerBase:
  - remove generic type parameters, instead use ulong for virtual addresses and nuint for host/physical addresses
  - implement IWritableBlock
  - add virtual GetReadOnlySequence() with coalescing of contiguous segments
  - add virtual GetSpan()
  - add virtual GetWritableRegion()
  - add abstract IsMapped()
  - add virtual MapForeign(ulong, nuint, ulong)
  - add virtual Read<T>()
  - add virtual Read(ulong, Span<byte>)
  - add virtual ReadTracked<T>()
  - add virtual SignalMemoryTracking()
  - add virtual Write()
  - add virtual Write<T>()
  - add virtual WriteUntracked()
  - add virtual WriteWithRedundancyCheck()
- VirtualMemoryManagerRefCountedBase: remove generic type parameters
- AddressSpaceManager: remove redundant methods, add required overrides
- HvMemoryManager: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling
- MemoryManager: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling
- MemoryManagerHostMapped: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling
- NativeMemoryManager: add get properties for Pointer and Length
- throughout: removed invalid <inheritdoc/> comments

* - WritableRegion: enable wrapping IMemoryOwner<byte>
- IVirtualMemoryManager impls of GetWritableRegion() use pooled memory when region is non-contiguous.
- IVirtualMemoryManager: add GetReadOnlySequence() and impls
- ByteMemoryPool: add new method RentCopy()
- ByteMemoryPool: make class static, remove ctor and singleton field from earlier impl

* add PagedMemoryRange enumerator types, use them in IVirtualMemoryManager implementations to consolidate page-handling logic and add a new capability - the coalescing of pages for consolidating memory copies and segmentation.

* new: more tests for PagedMemoryRangeCoalescingEnumerator showing coalescing of contiguous segments

* make some struct properties readonly

* put braces around `foreach` bodies

* encourage inlining of some PagedMemoryRange*Enumerator members

* DynamicRingBuffer:
 - use ByteMemoryPool
 - make some methods return without locking when size/count argument = 0
 - make generic Read<T>()/Write<T>() non-generic because its only usage is as T = byte
 - change Read(byte[]...) to Read(Span<byte>...)
 - change Write(byte[]...) to Write(Span<byte>...)

* change IAudioRenderer.RequestUpdate() to take a ReadOnlySequence<byte>, enabling zero-copy audio rendering

* HipcGenerator: support ReadOnlySequence<byte> as IPC method parameter

* change IAudioRenderer/AudioRenderer RequestUpdate* methods to take input as ReadOnlySequence<byte>

* MemoryManagerHostTracked: use rented memory when contiguous in `GetWritableRegion()`

* rebase cleanup

* dotnet format fixes

* format and comment fixes

* format long parameter list - take 2

* - add support to HipcGenerator for buffers of type `Memory<byte>`
- change `AudioRenderer` `RequestUpdate()` and `RequestUpdateAuto()` to use Memory<byte> for output buffers, removing another memory block allocation/copy

* SplitterContext `UpdateState()` and `UpdateData()` smooth out advance/rewind logic, only rewind if magic is invalid

* DynamicRingBuffer.Write(): change Span<byte> to ReadOnlySpan<byte>
2024-04-07 18:07:32 -03:00
Marco Carvalho
3e0d67533f Enhance Error Handling with Try-Pattern Refactoring (#6610)
* Enhance Error Handling with Try-Pattern Refactoring

* refactoring

* refactoring

* Update src/Ryujinx.HLE/FileSystem/ContentPath.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

---------

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2024-04-07 17:55:34 -03:00
Marco Carvalho
0b55914864 Replacing the try-catch block with null-conditional and null-coalescing operators (#6612)
* Replacing the try-catch block with null-conditional and null-coalescing operators

* repeating
2024-04-07 16:49:14 -03:00
MutantAura
451a28afb8 misc: Add ANGLE configuration option to JSON and CLI (#6520)
* Add hardware-acceleration toggle to ConfigurationState.

* Add command line override for easier RenderDoc use.

* Adjust CLI arguments.

* fix whitespace format check

* fix copypasta in comment

* Add X11 rendering mode toggle

* Remove ANGLE specific comments.
2024-04-06 14:58:02 -03:00
gdkchan
12b235700c Delete old 16KB page workarounds (#6584)
* Delete old 16KB page workarounds

* Rename Supports4KBPage to UsesPrivateAllocations

* Format whitespace

* This one should be false too

* Update XML doc
2024-04-06 13:51:44 -03:00
gdkchan
3be616207d Vulkan: Fix swapchain image view leak (#6509) 2024-04-06 13:38:52 -03:00
gdkchan
791bf22109 Vulkan: Skip draws when patches topology is used without a tessellation shader (#6508) 2024-04-06 13:25:51 -03:00
dependabot[bot]
66b1d59c66 nuget: bump DynamicData from 8.3.27 to 8.4.1 (#6536)
Bumps [DynamicData](https://github.com/reactiveui/DynamicData) from 8.3.27 to 8.4.1.
- [Release notes](https://github.com/reactiveui/DynamicData/releases)
- [Changelog](https://github.com/reactivemarbles/DynamicData/blob/main/ReleaseNotes.md)
- [Commits](https://github.com/reactiveui/DynamicData/compare/8.3.27...8.4.1)

---
updated-dependencies:
- dependency-name: DynamicData
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-06 14:07:06 +02:00
WilliamWsyHK
c8bb05633e Add mod enablement status in the log message (#6571) 2024-04-06 13:47:01 +02:00
56 changed files with 978 additions and 788 deletions

View File

@@ -13,7 +13,7 @@
<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" />

View File

@@ -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)..]);
}
}

View File

@@ -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

View File

@@ -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
{

View File

@@ -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
{

View File

@@ -386,7 +386,7 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
public ResultCode Update(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input)
public ResultCode Update(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlySequence<byte> input)
{
lock (_lock)
{
@@ -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)
{

View File

@@ -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()

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
{

View File

@@ -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();
}

View File

@@ -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)
{

View File

@@ -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

View File

@@ -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>

View File

@@ -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,7 +150,9 @@ 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;

View File

@@ -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)

View File

@@ -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)
{

View File

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

View File

@@ -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();
}

View File

@@ -28,7 +28,7 @@ namespace Ryujinx.Cpu.AppleHv
private readonly ManagedPageFlags _pages;
public bool Supports4KBPages => true;
public bool UsesPrivateAllocations => false;
public int AddressSpaceBits { get; }

View File

@@ -25,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.

View File

@@ -27,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; }

View File

@@ -1,10 +1,12 @@
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;
@@ -33,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;
@@ -237,11 +239,11 @@ namespace Ryujinx.Cpu.Jit
}
else
{
Memory<byte> memory = new byte[size];
IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size);
Read(va, memory.Span);
Read(va, memoryOwner.Memory.Span);
return new WritableRegion(this, va, memory);
return new WritableRegion(this, va, memoryOwner);
}
}

View File

@@ -981,6 +981,7 @@ namespace Ryujinx.Graphics.Vulkan
_bindingBarriersDirty = true;
_newState.PipelineLayout = internalProgram.PipelineLayout;
_newState.HasTessellationControlShader = internalProgram.HasTessellationControlShader;
_newState.StagesCount = (uint)stages.Length;
stages.CopyTo(_newState.Stages.AsSpan()[..stages.Length]);

View File

@@ -311,6 +311,7 @@ namespace Ryujinx.Graphics.Vulkan
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
}
public bool HasTessellationControlShader;
public NativeArray<PipelineShaderStageCreateInfo> Stages;
public PipelineLayout PipelineLayout;
public SpecData SpecializationData;
@@ -319,6 +320,7 @@ namespace Ryujinx.Graphics.Vulkan
public void Initialize()
{
HasTessellationControlShader = false;
Stages = new NativeArray<PipelineShaderStageCreateInfo>(Constants.MaxShaderStages);
AdvancedBlendSrcPreMultiplied = true;
@@ -419,6 +421,15 @@ namespace Ryujinx.Graphics.Vulkan
PVertexBindingDescriptions = pVertexBindingDescriptions,
};
// Using patches topology without a tessellation shader is invalid.
// If we find such a case, return null pipeline to skip the draw.
if (Topology == PrimitiveTopology.PatchList && !HasTessellationControlShader)
{
program.AddGraphicsPipeline(ref Internal, null);
return null;
}
bool primitiveRestartEnable = PrimitiveRestartEnable;
bool topologySupportsRestart;

View File

@@ -122,7 +122,6 @@ namespace Ryujinx.Graphics.Vulkan
gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
_renderPass?.Dispose();
_renderPass = new Auto<DisposableRenderPass>(new DisposableRenderPass(gd.Api, device, renderPass));
}
@@ -162,7 +161,7 @@ namespace Ryujinx.Graphics.Vulkan
public void Dispose()
{
// Dispose all framebuffers
// Dispose all framebuffers.
foreach (var fb in _framebuffers.Values)
{
@@ -175,6 +174,10 @@ namespace Ryujinx.Graphics.Vulkan
{
texture.RemoveRenderPass(_key);
}
// Dispose render pass.
_renderPass.Dispose();
}
}
}

View File

@@ -21,6 +21,7 @@ namespace Ryujinx.Graphics.Vulkan
public bool HasMinimalLayout { get; }
public bool UsePushDescriptors { get; }
public bool IsCompute { get; }
public bool HasTessellationControlShader => (Stages & (1u << 3)) != 0;
public uint Stages { get; }
@@ -461,6 +462,7 @@ namespace Ryujinx.Graphics.Vulkan
stages[i] = _shaders[i].GetInfo();
}
pipeline.HasTessellationControlShader = HasTessellationControlShader;
pipeline.StagesCount = (uint)_shaders.Length;
pipeline.PipelineLayout = PipelineLayout;

View File

@@ -4,6 +4,7 @@ using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Format = Ryujinx.Graphics.GAL.Format;
using VkBuffer = Silk.NET.Vulkan.Buffer;
using VkFormat = Silk.NET.Vulkan.Format;
@@ -36,7 +37,8 @@ namespace Ryujinx.Graphics.Vulkan
public int FirstLayer { get; }
public int FirstLevel { get; }
public VkFormat VkFormat { get; }
public bool Valid { get; private set; }
private int _isValid;
public bool Valid => Volatile.Read(ref _isValid) != 0;
public TextureView(
VulkanRenderer gd,
@@ -158,7 +160,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
Valid = true;
_isValid = 1;
}
/// <summary>
@@ -178,7 +180,7 @@ namespace Ryujinx.Graphics.Vulkan
VkFormat = format;
Valid = true;
_isValid = 1;
}
public Auto<DisposableImage> GetImage()
@@ -1017,10 +1019,11 @@ namespace Ryujinx.Graphics.Vulkan
{
if (disposing)
{
Valid = false;
if (_gd.Textures.Remove(this))
bool wasValid = Interlocked.Exchange(ref _isValid, 0) != 0;
if (wasValid)
{
_gd.Textures.Remove(this);
_imageView.Dispose();
_imageView2dArray?.Dispose();
@@ -1034,7 +1037,7 @@ namespace Ryujinx.Graphics.Vulkan
_imageViewDraw.Dispose();
}
Storage.DecrementViewsCount();
Storage?.DecrementViewsCount();
if (_renderPasses != null)
{
@@ -1045,22 +1048,22 @@ namespace Ryujinx.Graphics.Vulkan
pass.Dispose();
}
}
if (_selfManagedViews != null)
{
foreach (var view in _selfManagedViews.Values)
{
view.Dispose();
}
_selfManagedViews = null;
}
}
}
}
public void Dispose()
{
if (_selfManagedViews != null)
{
foreach (var view in _selfManagedViews.Values)
{
view.Dispose();
}
_selfManagedViews = null;
}
Dispose(true);
}

View File

@@ -104,20 +104,15 @@ namespace Ryujinx.HLE.FileSystem
foreach (StorageId storageId in Enum.GetValues<StorageId>())
{
string contentDirectory = null;
string contentPathString = null;
string registeredDirectory = null;
try
{
contentPathString = ContentPath.GetContentPath(storageId);
contentDirectory = ContentPath.GetRealPath(contentPathString);
registeredDirectory = Path.Combine(contentDirectory, "registered");
}
catch (NotSupportedException)
if (!ContentPath.TryGetContentPath(storageId, out var contentPathString))
{
continue;
}
if (!ContentPath.TryGetRealPath(contentPathString, out var contentDirectory))
{
continue;
}
var registeredDirectory = Path.Combine(contentDirectory, "registered");
Directory.CreateDirectory(registeredDirectory);
@@ -471,8 +466,8 @@ namespace Ryujinx.HLE.FileSystem
public void InstallFirmware(string firmwareSource)
{
string contentPathString = ContentPath.GetContentPath(StorageId.BuiltInSystem);
string contentDirectory = ContentPath.GetRealPath(contentPathString);
ContentPath.TryGetContentPath(StorageId.BuiltInSystem, out var contentPathString);
ContentPath.TryGetRealPath(contentPathString, out var contentDirectory);
string registeredDirectory = Path.Combine(contentDirectory, "registered");
string temporaryDirectory = Path.Combine(contentDirectory, "temp");

View File

@@ -26,17 +26,19 @@ namespace Ryujinx.HLE.FileSystem
public const string Nintendo = "Nintendo";
public const string Contents = "Contents";
public static string GetRealPath(string switchContentPath)
public static bool TryGetRealPath(string switchContentPath, out string realPath)
{
return switchContentPath switch
realPath = switchContentPath switch
{
SystemContent => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath, Contents),
UserContent => Path.Combine(AppDataManager.BaseDirPath, UserNandPath, Contents),
SdCardContent => Path.Combine(GetSdCardPath(), Nintendo, Contents),
System => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath),
User => Path.Combine(AppDataManager.BaseDirPath, UserNandPath),
_ => throw new NotSupportedException($"Content Path \"`{switchContentPath}`\" is not supported."),
_ => null,
};
return realPath != null;
}
public static string GetContentPath(ContentStorageId contentStorageId)
@@ -50,15 +52,17 @@ namespace Ryujinx.HLE.FileSystem
};
}
public static string GetContentPath(StorageId storageId)
public static bool TryGetContentPath(StorageId storageId, out string contentPath)
{
return storageId switch
contentPath = storageId switch
{
StorageId.BuiltInSystem => SystemContent,
StorageId.BuiltInUser => UserContent,
StorageId.SdCard => SdCardContent,
_ => throw new NotSupportedException($"Storage Id \"`{storageId}`\" is not supported."),
_ => null,
};
return contentPath != null;
}
public static StorageId GetStorageId(string contentPathString)

View File

@@ -75,7 +75,7 @@ namespace Ryujinx.HLE.HOS
// We want to use host tracked mode if the host page size is > 4KB.
if ((mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe) && MemoryBlock.GetPageSize() <= 0x1000)
{
if (!AddressSpace.TryCreate(context.Memory, addressSpaceSize, MemoryBlock.GetPageSize() == MemoryManagerHostMapped.PageSize, out addressSpace))
if (!AddressSpace.TryCreate(context.Memory, addressSpaceSize, out addressSpace))
{
Logger.Warning?.Print(LogClass.Cpu, "Address space creation failed, falling back to software page table");

View File

@@ -11,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{
private readonly IVirtualMemoryManager _cpuMemory;
protected override bool Supports4KBPages => _cpuMemory.Supports4KBPages;
protected override bool UsesPrivateAllocations => _cpuMemory.UsesPrivateAllocations;
public KPageTable(KernelContext context, IVirtualMemoryManager cpuMemory, ulong reservedAddressSpaceSize) : base(context, reservedAddressSpaceSize)
{

View File

@@ -32,7 +32,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
private const int MaxBlocksNeededForInsertion = 2;
protected readonly KernelContext Context;
protected virtual bool Supports4KBPages => true;
protected virtual bool UsesPrivateAllocations => false;
public ulong AddrSpaceStart { get; private set; }
public ulong AddrSpaceEnd { get; private set; }
@@ -1947,17 +1947,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
Result result;
if (srcPageTable.Supports4KBPages)
if (srcPageTable.UsesPrivateAllocations)
{
result = MapForeign(srcPageTable.GetHostRegions(addressRounded, alignedSize), currentVa, alignedSize);
}
else
{
KPageList pageList = new();
srcPageTable.GetPhysicalRegions(addressRounded, alignedSize, pageList);
result = MapPages(currentVa, pageList, permission, MemoryMapFlags.None);
}
else
{
result = MapForeign(srcPageTable.GetHostRegions(addressRounded, alignedSize), currentVa, alignedSize);
}
if (result != Result.Success)
{

View File

@@ -2,7 +2,6 @@ using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.Horizon.Common;
using Ryujinx.Memory;
namespace Ryujinx.HLE.HOS.Kernel.Memory
{
@@ -49,17 +48,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
return KernelResult.InvalidPermission;
}
// On platforms with page size > 4 KB, this can fail due to the address not being page aligned,
// we can return an error to force the application to retry with a different address.
try
{
return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission);
}
catch (InvalidMemoryRegionException)
{
return KernelResult.InvalidMemState;
}
return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission);
}
public Result UnmapFromProcess(KPageTableBase memoryManager, ulong address, ulong size, KProcess process)

View File

@@ -173,36 +173,16 @@ namespace Ryujinx.HLE.HOS
if (StrEquals(RomfsDir, modDir.Name))
{
bool enabled;
try
{
var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path));
enabled = modData.Enabled;
}
catch
{
// Mod is not in the list yet. New mods should be enabled by default.
enabled = true;
}
var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path));
var enabled = modData?.Enabled ?? true;
mods.RomfsDirs.Add(mod = new Mod<DirectoryInfo>(dir.Name, modDir, enabled));
types.Append('R');
}
else if (StrEquals(ExefsDir, modDir.Name))
{
bool enabled;
try
{
var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path));
enabled = modData.Enabled;
}
catch
{
// Mod is not in the list yet. New mods should be enabled by default.
enabled = true;
}
var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path));
var enabled = modData?.Enabled ?? true;
mods.ExefsDirs.Add(mod = new Mod<DirectoryInfo>(dir.Name, modDir, enabled));
types.Append('E');
@@ -218,7 +198,7 @@ namespace Ryujinx.HLE.HOS
if (types.Length > 0)
{
Logger.Info?.Print(LogClass.ModLoader, $"Found mod '{mod.Name}' [{types}]");
Logger.Info?.Print(LogClass.ModLoader, $"Found {(mod.Enabled ? "enabled" : "disabled")} mod '{mod.Name}' [{types}]");
}
}
}

View File

@@ -17,6 +17,8 @@ namespace Ryujinx.Horizon.Generators.Hipc
private const string ResponseVariableName = "response";
private const string OutRawDataVariableName = "outRawData";
private const string TypeSystemBuffersReadOnlySequence = "System.Buffers.ReadOnlySequence";
private const string TypeSystemMemory = "System.Memory";
private const string TypeSystemReadOnlySpan = "System.ReadOnlySpan";
private const string TypeSystemSpan = "System.Span";
private const string TypeStructLayoutAttribute = "System.Runtime.InteropServices.StructLayoutAttribute";
@@ -329,7 +331,15 @@ namespace Ryujinx.Horizon.Generators.Hipc
value = $"{InObjectsVariableName}[{inObjectIndex++}]";
break;
case CommandArgType.Buffer:
if (IsReadOnlySpan(compilation, parameter))
if (IsMemory(compilation, parameter))
{
value = $"CommandSerialization.GetWritableRegion(processor.GetBufferRange({index}))";
}
else if (IsReadOnlySequence(compilation, parameter))
{
value = $"CommandSerialization.GetReadOnlySequence(processor.GetBufferRange({index}))";
}
else if (IsReadOnlySpan(compilation, parameter))
{
string spanGenericTypeName = GetCanonicalTypeNameOfGenericArgument(compilation, parameter.Type, 0);
value = GenerateSpanCast(spanGenericTypeName, $"CommandSerialization.GetReadOnlySpan(processor.GetBufferRange({index}))");
@@ -346,7 +356,13 @@ namespace Ryujinx.Horizon.Generators.Hipc
break;
}
if (IsSpan(compilation, parameter))
if (IsMemory(compilation, parameter))
{
generator.AppendLine($"using var {argName} = {value};");
argName = $"{argName}.Memory";
}
else if (IsSpan(compilation, parameter))
{
generator.AppendLine($"using var {argName} = {value};");
@@ -637,7 +653,9 @@ namespace Ryujinx.Horizon.Generators.Hipc
private static bool IsValidTypeForBuffer(Compilation compilation, ParameterSyntax parameter)
{
return IsReadOnlySpan(compilation, parameter) ||
return IsMemory(compilation, parameter) ||
IsReadOnlySequence(compilation, parameter) ||
IsReadOnlySpan(compilation, parameter) ||
IsSpan(compilation, parameter) ||
IsUnmanagedType(compilation, parameter.Type);
}
@@ -649,6 +667,16 @@ namespace Ryujinx.Horizon.Generators.Hipc
return typeInfo.Type.IsUnmanagedType;
}
private static bool IsMemory(Compilation compilation, ParameterSyntax parameter)
{
return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemMemory;
}
private static bool IsReadOnlySequence(Compilation compilation, ParameterSyntax parameter)
{
return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemBuffersReadOnlySequence;
}
private static bool IsReadOnlySpan(Compilation compilation, ParameterSyntax parameter)
{
return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemReadOnlySpan;

View File

@@ -57,23 +57,11 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail
[CmifCommand(4)]
public Result RequestUpdate(
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> output,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> performanceOutput,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Memory<byte> output,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Memory<byte> performanceOutput,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySequence<byte> input)
{
using IMemoryOwner<byte> outputOwner = ByteMemoryPool.Rent(output.Length);
using IMemoryOwner<byte> performanceOutputOwner = ByteMemoryPool.Rent(performanceOutput.Length);
Memory<byte> outputMemory = outputOwner.Memory;
Memory<byte> performanceOutputMemory = performanceOutputOwner.Memory;
using MemoryHandle outputHandle = outputMemory.Pin();
using MemoryHandle performanceOutputHandle = performanceOutputMemory.Pin();
Result result = new Result((int)_renderSystem.Update(outputMemory, performanceOutputMemory, input.ToArray()));
outputMemory.Span.CopyTo(output);
performanceOutputMemory.Span.CopyTo(performanceOutput);
Result result = new Result((int)_renderSystem.Update(output, performanceOutput, input));
return result;
}
@@ -127,9 +115,9 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail
[CmifCommand(10)] // 3.0.0+
public Result RequestUpdateAuto(
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<byte> output,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<byte> performanceOutput,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<byte> input)
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Memory<byte> output,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Memory<byte> performanceOutput,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySequence<byte> input)
{
return RequestUpdate(output, performanceOutput, input);
}

View File

@@ -1,6 +1,7 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf;
using System;
using System.Buffers;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
@@ -10,13 +11,13 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail
Result GetSampleCount(out int sampleCount);
Result GetMixBufferCount(out int mixBufferCount);
Result GetState(out int state);
Result RequestUpdate(Span<byte> output, Span<byte> performanceOutput, ReadOnlySpan<byte> input);
Result RequestUpdate(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlySequence<byte> input);
Result Start();
Result Stop();
Result QuerySystemEvent(out int eventHandle);
Result SetRenderingTimeLimit(int percent);
Result GetRenderingTimeLimit(out int percent);
Result RequestUpdateAuto(Span<byte> output, Span<byte> performanceOutput, ReadOnlySpan<byte> input);
Result RequestUpdateAuto(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlySequence<byte> input);
Result ExecuteAudioRendererRendering();
Result SetVoiceDropParameter(float voiceDropParameter);
Result GetVoiceDropParameter(out float voiceDropParameter);

View File

@@ -2,6 +2,7 @@ using Ryujinx.Horizon.Sdk.Sf.Cmif;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using Ryujinx.Memory;
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -9,6 +10,11 @@ namespace Ryujinx.Horizon.Sdk.Sf
{
static class CommandSerialization
{
public static ReadOnlySequence<byte> GetReadOnlySequence(PointerAndSize bufferRange)
{
return HorizonStatic.AddressSpace.GetReadOnlySequence(bufferRange.Address, checked((int)bufferRange.Size));
}
public static ReadOnlySpan<byte> GetReadOnlySpan(PointerAndSize bufferRange)
{
return HorizonStatic.AddressSpace.GetSpan(bufferRange.Address, checked((int)bufferRange.Size));

View File

@@ -13,7 +13,7 @@ namespace Ryujinx.Memory
public sealed class AddressSpaceManager : VirtualMemoryManagerBase, IVirtualMemoryManager
{
/// <inheritdoc/>
public bool Supports4KBPages => true;
public bool UsesPrivateAllocations => false;
/// <summary>
/// Address space width in bits.

View File

@@ -8,10 +8,10 @@ namespace Ryujinx.Memory
public interface IVirtualMemoryManager
{
/// <summary>
/// Indicates whenever the memory manager supports aliasing pages at 4KB granularity.
/// Indicates whether the memory manager creates private allocations when the <see cref="MemoryMapFlags.Private"/> flag is set on map.
/// </summary>
/// <returns>True if 4KB pages are supported by the memory manager, false otherwise</returns>
bool Supports4KBPages { get; }
/// <returns>True if private mappings might be used, false otherwise</returns>
bool UsesPrivateAllocations { get; }
/// <summary>
/// Maps a virtual memory range into a physical memory range.

View File

@@ -8,7 +8,7 @@ namespace Ryujinx.Tests.Memory
{
public class MockVirtualMemoryManager : IVirtualMemoryManager
{
public bool Supports4KBPages => true;
public bool UsesPrivateAllocations => false;
public bool NoMappings = false;

View File

@@ -0,0 +1,359 @@
using NUnit.Framework;
using Ryujinx.Common.Extensions;
using Ryujinx.Memory;
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Tests.Common.Extensions
{
public class SequenceReaderExtensionsTests
{
[TestCase(null)]
[TestCase(sizeof(int) + 1)]
public void GetRefOrRefToCopy_ReadsMultiSegmentedSequenceSuccessfully(int? maxSegmentSize)
{
// Arrange
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(3).ToArray();
ReadOnlySequence<byte> sequence =
CreateSegmentedByteSequence(originalStructs, maxSegmentSize ?? Unsafe.SizeOf<MyUnmanagedStruct>());
var sequenceReader = new SequenceReader<byte>(sequence);
foreach (var original in originalStructs)
{
// Act
ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
// Assert
MyUnmanagedStruct.Assert(Assert.AreEqual, original, read);
}
}
[Test]
public void GetRefOrRefToCopy_FragmentedSequenceReturnsRefToCopy()
{
// Arrange
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray();
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, 3);
var sequenceReader = new SequenceReader<byte>(sequence);
foreach (var original in originalStructs)
{
// Act
ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out var copy);
// Assert
MyUnmanagedStruct.Assert(Assert.AreEqual, original, read);
MyUnmanagedStruct.Assert(Assert.AreEqual, read, copy);
}
}
[Test]
public void GetRefOrRefToCopy_ContiguousSequenceReturnsRefToBuffer()
{
// Arrange
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray();
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue);
var sequenceReader = new SequenceReader<byte>(sequence);
foreach (var original in originalStructs)
{
// Act
ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out var copy);
// Assert
MyUnmanagedStruct.Assert(Assert.AreEqual, original, read);
MyUnmanagedStruct.Assert(Assert.AreNotEqual, read, copy);
}
}
[Test]
public void GetRefOrRefToCopy_ThrowsWhenNotEnoughData()
{
// Arrange
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray();
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue);
// Act/Assert
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
var sequenceReader = new SequenceReader<byte>(sequence);
sequenceReader.Advance(1);
ref readonly MyUnmanagedStruct result = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
});
}
[Test]
public void ReadLittleEndian_Int32_RoundTripsSuccessfully()
{
// Arrange
const int TestValue = 0x1234abcd;
byte[] buffer = new byte[sizeof(int)];
BinaryPrimitives.WriteInt32LittleEndian(buffer.AsSpan(), TestValue);
var sequenceReader = new SequenceReader<byte>(new ReadOnlySequence<byte>(buffer));
// Act
sequenceReader.ReadLittleEndian(out int roundTrippedValue);
// Assert
Assert.AreEqual(TestValue, roundTrippedValue);
}
[Test]
public void ReadLittleEndian_Int32_ResultIsNotBigEndian()
{
// Arrange
const int TestValue = 0x1234abcd;
byte[] buffer = new byte[sizeof(int)];
BinaryPrimitives.WriteInt32BigEndian(buffer.AsSpan(), TestValue);
var sequenceReader = new SequenceReader<byte>(new ReadOnlySequence<byte>(buffer));
// Act
sequenceReader.ReadLittleEndian(out int roundTrippedValue);
// Assert
Assert.AreNotEqual(TestValue, roundTrippedValue);
}
[Test]
public void ReadLittleEndian_Int32_ThrowsWhenNotEnoughData()
{
// Arrange
const int TestValue = 0x1234abcd;
byte[] buffer = new byte[sizeof(int)];
BinaryPrimitives.WriteInt32BigEndian(buffer.AsSpan(), TestValue);
// Act/Assert
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
var sequenceReader = new SequenceReader<byte>(new ReadOnlySequence<byte>(buffer));
sequenceReader.Advance(1);
sequenceReader.ReadLittleEndian(out int roundTrippedValue);
});
}
[Test]
public void ReadUnmanaged_ContiguousSequence_Succeeds()
=> ReadUnmanaged_Succeeds(int.MaxValue);
[Test]
public void ReadUnmanaged_FragmentedSequence_Succeeds()
=> ReadUnmanaged_Succeeds(sizeof(int) + 1);
[Test]
public void ReadUnmanaged_ThrowsWhenNotEnoughData()
{
// Arrange
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray();
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue);
// Act/Assert
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
var sequenceReader = new SequenceReader<byte>(sequence);
sequenceReader.Advance(1);
sequenceReader.ReadUnmanaged(out MyUnmanagedStruct read);
});
}
[Test]
public void SetConsumed_ContiguousSequence_SucceedsWhenValid()
=> SetConsumed_SucceedsWhenValid(int.MaxValue);
[Test]
public void SetConsumed_FragmentedSequence_SucceedsWhenValid()
=> SetConsumed_SucceedsWhenValid(sizeof(int) + 1);
[Test]
public void SetConsumed_ThrowsWhenBeyondActualLength()
{
const int StructCount = 2;
// Arrange
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(StructCount).ToArray();
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, MyUnmanagedStruct.SizeOf);
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
var sequenceReader = new SequenceReader<byte>(sequence);
sequenceReader.SetConsumed(MyUnmanagedStruct.SizeOf * StructCount + 1);
});
}
private static void ReadUnmanaged_Succeeds(int maxSegmentLength)
{
// Arrange
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(3).ToArray();
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, maxSegmentLength);
var sequenceReader = new SequenceReader<byte>(sequence);
foreach (var original in originalStructs)
{
// Act
sequenceReader.ReadUnmanaged(out MyUnmanagedStruct read);
// Assert
MyUnmanagedStruct.Assert(Assert.AreEqual, original, read);
}
}
private static void SetConsumed_SucceedsWhenValid(int maxSegmentLength)
{
// Arrange
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(2).ToArray();
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, maxSegmentLength);
var sequenceReader = new SequenceReader<byte>(sequence);
static void SetConsumedAndAssert(scoped ref SequenceReader<byte> sequenceReader, long consumed)
{
sequenceReader.SetConsumed(consumed);
Assert.AreEqual(consumed, sequenceReader.Consumed);
}
// Act/Assert
ref readonly MyUnmanagedStruct struct0A = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
Assert.AreEqual(sequenceReader.Consumed, MyUnmanagedStruct.SizeOf);
SetConsumedAndAssert(ref sequenceReader, 0);
ref readonly MyUnmanagedStruct struct0B = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
MyUnmanagedStruct.Assert(Assert.AreEqual, struct0A, struct0B);
SetConsumedAndAssert(ref sequenceReader, 1);
SetConsumedAndAssert(ref sequenceReader, MyUnmanagedStruct.SizeOf);
ref readonly MyUnmanagedStruct struct1A = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
SetConsumedAndAssert(ref sequenceReader, MyUnmanagedStruct.SizeOf);
ref readonly MyUnmanagedStruct struct1B = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
MyUnmanagedStruct.Assert(Assert.AreEqual, struct1A, struct1B);
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct MyUnmanagedStruct
{
public int BehaviourSize;
public int MemoryPoolsSize;
public short VoicesSize;
public int VoiceResourcesSize;
public short EffectsSize;
public int RenderInfoSize;
public unsafe fixed byte Reserved[16];
public static readonly int SizeOf = Unsafe.SizeOf<MyUnmanagedStruct>();
public static unsafe MyUnmanagedStruct Generate(Random rng)
{
const int BaseInt32Value = 0x1234abcd;
const short BaseInt16Value = 0x5678;
var result = new MyUnmanagedStruct
{
BehaviourSize = BaseInt32Value ^ rng.Next(),
MemoryPoolsSize = BaseInt32Value ^ rng.Next(),
VoicesSize = (short)(BaseInt16Value ^ rng.Next()),
VoiceResourcesSize = BaseInt32Value ^ rng.Next(),
EffectsSize = (short)(BaseInt16Value ^ rng.Next()),
RenderInfoSize = BaseInt32Value ^ rng.Next(),
};
Unsafe.Write(result.Reserved, rng.NextInt64());
return result;
}
public static unsafe void Assert(Action<object, object> assert, in MyUnmanagedStruct expected, in MyUnmanagedStruct actual)
{
assert(expected.BehaviourSize, actual.BehaviourSize);
assert(expected.MemoryPoolsSize, actual.MemoryPoolsSize);
assert(expected.VoicesSize, actual.VoicesSize);
assert(expected.VoiceResourcesSize, actual.VoiceResourcesSize);
assert(expected.EffectsSize, actual.EffectsSize);
assert(expected.RenderInfoSize, actual.RenderInfoSize);
fixed (void* expectedReservedPtr = expected.Reserved)
fixed (void* actualReservedPtr = actual.Reserved)
{
long expectedReservedLong = Unsafe.Read<long>(expectedReservedPtr);
long actualReservedLong = Unsafe.Read<long>(actualReservedPtr);
assert(expectedReservedLong, actualReservedLong);
}
}
}
private static IEnumerable<MyUnmanagedStruct> EnumerateNewUnmanagedStructs()
{
var rng = new Random(0);
while (true)
{
yield return MyUnmanagedStruct.Generate(rng);
}
}
private static ReadOnlySequence<byte> CreateSegmentedByteSequence<T>(T[] array, int maxSegmentLength) where T : unmanaged
{
byte[] arrayBytes = MemoryMarshal.AsBytes(array.AsSpan()).ToArray();
var memory = new Memory<byte>(arrayBytes);
int index = 0;
BytesReadOnlySequenceSegment first = null, last = null;
while (index < memory.Length)
{
int nextSegmentLength = Math.Min(maxSegmentLength, memory.Length - index);
var nextSegment = memory.Slice(index, nextSegmentLength);
if (first == null)
{
first = last = new BytesReadOnlySequenceSegment(nextSegment);
}
else
{
last = last.Append(nextSegment);
}
index += nextSegmentLength;
}
return new ReadOnlySequence<byte>(first, 0, last, (int)(memory.Length - last.RunningIndex));
}
}
}

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.UI.Common.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 49;
public const int CurrentVersion = 50;
/// <summary>
/// Version of the configuration file format
@@ -162,6 +162,11 @@ namespace Ryujinx.UI.Common.Configuration
/// </summary>
public bool ShowConfirmExit { get; set; }
/// <summary>
/// Enables hardware-accelerated rendering for Avalonia
/// </summary>
public bool EnableHardwareAcceleration { get; set; }
/// <summary>
/// Whether to hide cursor on idle, always or never
/// </summary>

View File

@@ -626,6 +626,11 @@ namespace Ryujinx.UI.Common.Configuration
/// </summary>
public ReactiveObject<bool> ShowConfirmExit { get; private set; }
/// <summary>
/// Enables hardware-accelerated rendering for Avalonia
/// </summary>
public ReactiveObject<bool> EnableHardwareAcceleration { get; private set; }
/// <summary>
/// Hide Cursor on Idle
/// </summary>
@@ -642,6 +647,7 @@ namespace Ryujinx.UI.Common.Configuration
EnableDiscordIntegration = new ReactiveObject<bool>();
CheckUpdatesOnStart = new ReactiveObject<bool>();
ShowConfirmExit = new ReactiveObject<bool>();
EnableHardwareAcceleration = new ReactiveObject<bool>();
HideCursor = new ReactiveObject<HideCursorMode>();
}
@@ -678,6 +684,7 @@ namespace Ryujinx.UI.Common.Configuration
EnableDiscordIntegration = EnableDiscordIntegration,
CheckUpdatesOnStart = CheckUpdatesOnStart,
ShowConfirmExit = ShowConfirmExit,
EnableHardwareAcceleration = EnableHardwareAcceleration,
HideCursor = HideCursor,
EnableVsync = Graphics.EnableVsync,
EnableShaderCache = Graphics.EnableShaderCache,
@@ -785,6 +792,7 @@ namespace Ryujinx.UI.Common.Configuration
EnableDiscordIntegration.Value = true;
CheckUpdatesOnStart.Value = true;
ShowConfirmExit.Value = true;
EnableHardwareAcceleration.Value = true;
HideCursor.Value = HideCursorMode.OnIdle;
Graphics.EnableVsync.Value = true;
Graphics.EnableShaderCache.Value = true;
@@ -1442,6 +1450,15 @@ namespace Ryujinx.UI.Common.Configuration
configurationFileUpdated = true;
}
if (configurationFileFormat.Version < 50)
{
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 50.");
configurationFileFormat.EnableHardwareAcceleration = true;
configurationFileUpdated = true;
}
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
Graphics.ResScale.Value = configurationFileFormat.ResScale;
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
@@ -1472,6 +1489,7 @@ namespace Ryujinx.UI.Common.Configuration
EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration;
CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart;
ShowConfirmExit.Value = configurationFileFormat.ShowConfirmExit;
EnableHardwareAcceleration.Value = configurationFileFormat.EnableHardwareAcceleration;
HideCursor.Value = configurationFileFormat.HideCursor;
Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync;
Graphics.EnableShaderCache.Value = configurationFileFormat.EnableShaderCache;

View File

@@ -8,6 +8,7 @@ namespace Ryujinx.UI.Common.Helper
public static string[] Arguments { get; private set; }
public static bool? OverrideDockedMode { get; private set; }
public static bool? OverrideHardwareAcceleration { get; private set; }
public static string OverrideGraphicsBackend { get; private set; }
public static string OverrideHideCursor { get; private set; }
public static string BaseDirPathArg { get; private set; }
@@ -87,6 +88,12 @@ namespace Ryujinx.UI.Common.Helper
OverrideHideCursor = args[++i];
break;
case "--software-gui":
OverrideHardwareAcceleration = false;
break;
case "--hardware-gui":
OverrideHardwareAcceleration = true;
break;
default:
LaunchPathArg = arg;
break;

View File

@@ -60,12 +60,16 @@ namespace Ryujinx.Ava
EnableMultiTouch = true,
EnableIme = true,
EnableInputFocusProxy = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP") == "gamescope",
RenderingMode = new[] { X11RenderingMode.Glx, X11RenderingMode.Software },
RenderingMode = ConfigurationState.Instance.EnableHardwareAcceleration ?
new[] { X11RenderingMode.Glx, X11RenderingMode.Software } :
new[] { X11RenderingMode.Software },
})
.With(new Win32PlatformOptions
{
WinUICompositionBackdropCornerRadius = 8.0f,
RenderingMode = new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software },
RenderingMode = ConfigurationState.Instance.EnableHardwareAcceleration ?
new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software } :
new[] { Win32RenderingMode.Software },
})
.UseSkia();
}
@@ -191,6 +195,12 @@ namespace Ryujinx.Ava
_ => ConfigurationState.Instance.HideCursor.Value,
};
}
// Check if hardware-acceleration was overridden.
if (CommandLineState.OverrideHardwareAcceleration != null)
{
ConfigurationState.Instance.EnableHardwareAcceleration.Value = CommandLineState.OverrideHardwareAcceleration.Value;
}
}
private static void PrintSystemInfo()