Compare commits

...

15 Commits

Author SHA1 Message Date
338ff79e1e Use ResScaleUnsupported flag for texture arrays (#6626) 2024-04-09 14:24:46 -03:00
80201466ae Fast D32S8 2D depth texture copy (#6636) 2024-04-09 14:03:55 -03:00
7a971edb57 Pin audio renderer update output buffers (#6633) 2024-04-08 20:26:47 -03:00
c1b0ab805a Disable CLI setting persistence for HW-accelerated GUI. (#6620) 2024-04-07 20:58:05 -03:00
3e6e0e4afa Add support for large sampler arrays on Vulkan (#6489)
* Add support for large sampler arrays on Vulkan

* Shader cache version bump

* Format whitespace

* Move DescriptorSetManager to PipelineLayoutCacheEntry to allow different pool sizes per layout

* Handle array textures with different types on the same buffer

* Somewhat better caching system

* Avoid useless buffer data modification checks

* Move redundant bindings update checking to the backend

* Fix an issue where texture arrays would get the same bindings across stages on Vulkan

* Backport some fixes from part 2

* Fix typo

* PR feedback

* Format whitespace

* Add some missing XML docs
2024-04-07 18:25:55 -03:00
808803d97a CPU: Fix PC alignment for ADR thumb instruction (#6613)
* Fix PC alignment for ADR thumb instruction

* PPTC version bump
2024-04-07 18:17:49 -03:00
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
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
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
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
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
3be616207d Vulkan: Fix swapchain image view leak (#6509) 2024-04-06 13:38:52 -03:00
791bf22109 Vulkan: Skip draws when patches topology is used without a tessellation shader (#6508) 2024-04-06 13:25:51 -03:00
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
c8bb05633e Add mod enablement status in the log message (#6571) 2024-04-06 13:47:01 +02:00
140 changed files with 4276 additions and 1743 deletions

View File

@ -13,7 +13,7 @@
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" /> <PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" /> <PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="DynamicData" Version="8.3.27" /> <PackageVersion Include="DynamicData" Version="8.4.1" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" /> <PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" /> <PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" /> <PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ namespace Ryujinx.Cpu.Jit
private readonly InvalidAccessHandler _invalidAccessHandler; private readonly InvalidAccessHandler _invalidAccessHandler;
/// <inheritdoc/> /// <inheritdoc/>
public bool Supports4KBPages => true; public bool UsesPrivateAllocations => false;
/// <summary> /// <summary>
/// Address space width in bits. /// Address space width in bits.

View File

@ -27,7 +27,7 @@ namespace Ryujinx.Cpu.Jit
private readonly ManagedPageFlags _pages; private readonly ManagedPageFlags _pages;
/// <inheritdoc/> /// <inheritdoc/>
public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize; public bool UsesPrivateAllocations => false;
public int AddressSpaceBits { get; } public int AddressSpaceBits { get; }

View File

@ -1,10 +1,12 @@
using ARMeilleure.Memory; using ARMeilleure.Memory;
using Ryujinx.Common.Memory;
using Ryujinx.Cpu.Jit.HostTracked; using Ryujinx.Cpu.Jit.HostTracked;
using Ryujinx.Cpu.Signal; using Ryujinx.Cpu.Signal;
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking; using Ryujinx.Memory.Tracking;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -33,7 +35,7 @@ namespace Ryujinx.Cpu.Jit
protected override ulong AddressSpaceSize { get; } protected override ulong AddressSpaceSize { get; }
/// <inheritdoc/> /// <inheritdoc/>
public bool Supports4KBPages => false; public bool UsesPrivateAllocations => true;
public IntPtr PageTablePointer => _nativePageTable.PageTablePointer; public IntPtr PageTablePointer => _nativePageTable.PageTablePointer;
@ -237,11 +239,11 @@ namespace Ryujinx.Cpu.Jit
} }
else 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -958,6 +958,11 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
public void SetImageArray(ShaderStage stage, int binding, IImageArray array)
{
(array as ImageArray).Bind(binding);
}
public void SetIndexBuffer(BufferRange buffer, IndexType type) public void SetIndexBuffer(BufferRange buffer, IndexType type)
{ {
_elementsType = type.Convert(); _elementsType = type.Convert();
@ -1302,6 +1307,10 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array)
{
(array as TextureArray).Bind(binding);
}
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers) public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
{ {

View File

@ -339,24 +339,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private static void DeclareSamplers(CodeGenContext context, IEnumerable<TextureDefinition> definitions) private static void DeclareSamplers(CodeGenContext context, IEnumerable<TextureDefinition> definitions)
{ {
int arraySize = 0;
foreach (var definition in definitions) foreach (var definition in definitions)
{ {
string indexExpr = string.Empty; string arrayDecl = string.Empty;
if (definition.Type.HasFlag(SamplerType.Indexed)) if (definition.ArrayLength > 1)
{ {
if (arraySize == 0) arrayDecl = $"[{NumberFormatter.FormatInt(definition.ArrayLength)}]";
{ }
arraySize = ResourceManager.SamplerArraySize; else if (definition.ArrayLength == 0)
} {
else if (--arraySize != 0) arrayDecl = "[]";
{
continue;
}
indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]";
} }
string samplerTypeName = definition.Type.ToGlslSamplerType(); string samplerTypeName = definition.Type.ToGlslSamplerType();
@ -368,30 +361,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
layout = $", set = {definition.Set}"; layout = $", set = {definition.Set}";
} }
context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {samplerTypeName} {definition.Name}{indexExpr};"); context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {samplerTypeName} {definition.Name}{arrayDecl};");
} }
} }
private static void DeclareImages(CodeGenContext context, IEnumerable<TextureDefinition> definitions) private static void DeclareImages(CodeGenContext context, IEnumerable<TextureDefinition> definitions)
{ {
int arraySize = 0;
foreach (var definition in definitions) foreach (var definition in definitions)
{ {
string indexExpr = string.Empty; string arrayDecl = string.Empty;
if (definition.Type.HasFlag(SamplerType.Indexed)) if (definition.ArrayLength > 1)
{ {
if (arraySize == 0) arrayDecl = $"[{NumberFormatter.FormatInt(definition.ArrayLength)}]";
{ }
arraySize = ResourceManager.SamplerArraySize; else if (definition.ArrayLength == 0)
} {
else if (--arraySize != 0) arrayDecl = "[]";
{
continue;
}
indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]";
} }
string imageTypeName = definition.Type.ToGlslImageType(definition.Format.GetComponentType()); string imageTypeName = definition.Type.ToGlslImageType(definition.Format.GetComponentType());
@ -413,7 +399,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
layout = $", set = {definition.Set}{layout}"; layout = $", set = {definition.Set}{layout}";
} }
context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {imageTypeName} {definition.Name}{indexExpr};"); context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {imageTypeName} {definition.Name}{arrayDecl};");
} }
} }

View File

@ -38,7 +38,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
AggregateType type = GetSrcVarType(operation.Inst, 0); AggregateType type = GetSrcVarType(operation.Inst, 0);
string srcExpr = GetSoureExpr(context, src, type); string srcExpr = GetSourceExpr(context, src, type);
string zero; string zero;
if (type == AggregateType.FP64) if (type == AggregateType.FP64)
@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
for (int argIndex = operation.SourcesCount - arity + 2; argIndex < operation.SourcesCount; argIndex++) for (int argIndex = operation.SourcesCount - arity + 2; argIndex < operation.SourcesCount; argIndex++)
{ {
builder.Append($", {GetSoureExpr(context, operation.GetSource(argIndex), dstType)}"); builder.Append($", {GetSourceExpr(context, operation.GetSource(argIndex), dstType)}");
} }
} }
else else
@ -94,7 +94,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
AggregateType dstType = GetSrcVarType(inst, argIndex); AggregateType dstType = GetSrcVarType(inst, argIndex);
builder.Append(GetSoureExpr(context, operation.GetSource(argIndex), dstType)); builder.Append(GetSourceExpr(context, operation.GetSource(argIndex), dstType));
} }
} }
@ -107,7 +107,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
// Return may optionally have a return value (and in this case it is unary). // Return may optionally have a return value (and in this case it is unary).
if (inst == Instruction.Return && operation.SourcesCount != 0) if (inst == Instruction.Return && operation.SourcesCount != 0)
{ {
return $"{op} {GetSoureExpr(context, operation.GetSource(0), context.CurrentFunction.ReturnType)}"; return $"{op} {GetSourceExpr(context, operation.GetSource(0), context.CurrentFunction.ReturnType)}";
} }
int arity = (int)(info.Type & InstType.ArityMask); int arity = (int)(info.Type & InstType.ArityMask);
@ -118,7 +118,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
IAstNode src = operation.GetSource(index); IAstNode src = operation.GetSource(index);
string srcExpr = GetSoureExpr(context, src, GetSrcVarType(inst, index)); string srcExpr = GetSourceExpr(context, src, GetSrcVarType(inst, index));
bool isLhs = arity == 2 && index == 0; bool isLhs = arity == 2 && index == 0;

View File

@ -12,7 +12,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
AggregateType dstType = GetSrcVarType(operation.Inst, 0); AggregateType dstType = GetSrcVarType(operation.Inst, 0);
string arg = GetSoureExpr(context, operation.GetSource(0), dstType); string arg = GetSourceExpr(context, operation.GetSource(0), dstType);
char component = "xyzw"[operation.Index]; char component = "xyzw"[operation.Index];
if (context.HostCapabilities.SupportsShaderBallot) if (context.HostCapabilities.SupportsShaderBallot)

View File

@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
for (int i = 0; i < args.Length; i++) for (int i = 0; i < args.Length; i++)
{ {
args[i] = GetSoureExpr(context, operation.GetSource(i + 1), function.GetArgumentType(i)); args[i] = GetSourceExpr(context, operation.GetSource(i + 1), function.GetArgumentType(i));
} }
return $"{function.Name}({string.Join(", ", args)})"; return $"{function.Name}({string.Join(", ", args)})";

View File

@ -140,7 +140,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return _infoTable[(int)(inst & Instruction.Mask)]; return _infoTable[(int)(inst & Instruction.Mask)];
} }
public static string GetSoureExpr(CodeGenContext context, IAstNode node, AggregateType dstType) public static string GetSourceExpr(CodeGenContext context, IAstNode node, AggregateType dstType)
{ {
return ReinterpretCast(context, node, OperandManager.GetNodeDestType(context, node), dstType); return ReinterpretCast(context, node, OperandManager.GetNodeDestType(context, node), dstType);
} }

View File

@ -14,35 +14,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
// TODO: Bindless texture support. For now we just return 0/do nothing.
if (isBindless)
{
switch (texOp.Inst)
{
case Instruction.ImageStore:
return "// imageStore(bindless)";
case Instruction.ImageLoad:
AggregateType componentType = texOp.Format.GetComponentType();
NumberFormatter.TryFormat(0, componentType, out string imageConst);
AggregateType outputType = texOp.GetVectorType(componentType);
if ((outputType & AggregateType.ElementCountMask) != 0)
{
return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({imageConst})";
}
return imageConst;
default:
return NumberFormatter.FormatInt(0);
}
}
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
var texCallBuilder = new StringBuilder(); var texCallBuilder = new StringBuilder();
@ -70,21 +42,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
texCallBuilder.Append(texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore"); texCallBuilder.Append(texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore");
} }
int srcIndex = isBindless ? 1 : 0; int srcIndex = 0;
string Src(AggregateType type) string Src(AggregateType type)
{ {
return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); return GetSourceExpr(context, texOp.GetSource(srcIndex++), type);
} }
string indexExpr = null; string imageName = GetImageName(context, texOp, ref srcIndex);
if (isIndexed)
{
indexExpr = Src(AggregateType.S32);
}
string imageName = GetImageName(context.Properties, texOp, indexExpr);
texCallBuilder.Append('('); texCallBuilder.Append('(');
texCallBuilder.Append(imageName); texCallBuilder.Append(imageName);
@ -198,27 +163,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = 0;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; string samplerName = GetSamplerName(context, texOp, ref coordsIndex);
// TODO: Bindless texture support. For now we just return 0.
if (isBindless)
{
return NumberFormatter.FormatFloat(0);
}
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
string indexExpr = null;
if (isIndexed)
{
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
}
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
int coordsIndex = isBindless || isIndexed ? 1 : 0;
string coordsExpr; string coordsExpr;
@ -228,14 +175,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
for (int index = 0; index < coordsCount; index++) for (int index = 0; index < coordsCount; index++)
{ {
elems[index] = GetSoureExpr(context, texOp.GetSource(coordsIndex + index), AggregateType.FP32); elems[index] = GetSourceExpr(context, texOp.GetSource(coordsIndex + index), AggregateType.FP32);
} }
coordsExpr = "vec" + coordsCount + "(" + string.Join(", ", elems) + ")"; coordsExpr = "vec" + coordsCount + "(" + string.Join(", ", elems) + ")";
} }
else else
{ {
coordsExpr = GetSoureExpr(context, texOp.GetSource(coordsIndex), AggregateType.FP32); coordsExpr = GetSourceExpr(context, texOp.GetSource(coordsIndex), AggregateType.FP32);
} }
return $"textureQueryLod({samplerName}, {coordsExpr}){GetMask(texOp.Index)}"; return $"textureQueryLod({samplerName}, {coordsExpr}){GetMask(texOp.Index)}";
@ -250,7 +197,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; bool isGather = (texOp.Flags & TextureFlags.Gather) != 0;
bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0;
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
@ -260,12 +206,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
bool colorIsVector = isGather || !isShadow;
SamplerType type = texOp.Type & SamplerType.Mask; SamplerType type = texOp.Type & SamplerType.Mask;
bool is2D = type == SamplerType.Texture2D; bool is2D = type == SamplerType.Texture2D;
@ -286,24 +229,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
hasLodLevel = false; hasLodLevel = false;
} }
// TODO: Bindless texture support. For now we just return 0.
if (isBindless)
{
string scalarValue = NumberFormatter.FormatFloat(0);
if (colorIsVector)
{
AggregateType outputType = texOp.GetVectorType(AggregateType.FP32);
if ((outputType & AggregateType.ElementCountMask) != 0)
{
return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({scalarValue})";
}
}
return scalarValue;
}
string texCall = intCoords ? "texelFetch" : "texture"; string texCall = intCoords ? "texelFetch" : "texture";
if (isGather) if (isGather)
@ -328,21 +253,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
texCall += "Offsets"; texCall += "Offsets";
} }
int srcIndex = isBindless ? 1 : 0; int srcIndex = 0;
string Src(AggregateType type) string Src(AggregateType type)
{ {
return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); return GetSourceExpr(context, texOp.GetSource(srcIndex++), type);
} }
string indexExpr = null; string samplerName = GetSamplerName(context, texOp, ref srcIndex);
if (isIndexed)
{
indexExpr = Src(AggregateType.S32);
}
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
texCall += "(" + samplerName; texCall += "(" + samplerName;
@ -512,6 +430,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
Append(Src(AggregateType.S32)); Append(Src(AggregateType.S32));
} }
bool colorIsVector = isGather || !isShadow;
texCall += ")" + (colorIsVector ? GetMaskMultiDest(texOp.Index) : ""); texCall += ")" + (colorIsVector ? GetMaskMultiDest(texOp.Index) : "");
return texCall; return texCall;
@ -521,24 +441,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; int srcIndex = 0;
// TODO: Bindless texture support. For now we just return 0. string samplerName = GetSamplerName(context, texOp, ref srcIndex);
if (isBindless)
{
return NumberFormatter.FormatInt(0);
}
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
string indexExpr = null;
if (isIndexed)
{
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
}
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
return $"textureSamples({samplerName})"; return $"textureSamples({samplerName})";
} }
@ -547,24 +452,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; int srcIndex = 0;
// TODO: Bindless texture support. For now we just return 0. string samplerName = GetSamplerName(context, texOp, ref srcIndex);
if (isBindless)
{
return NumberFormatter.FormatInt(0);
}
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
string indexExpr = null;
if (isIndexed)
{
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
}
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
if (texOp.Index == 3) if (texOp.Index == 3)
{ {
@ -578,9 +468,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (hasLod) if (hasLod)
{ {
int lodSrcIndex = isBindless || isIndexed ? 1 : 0; IAstNode lod = operation.GetSource(srcIndex);
IAstNode lod = operation.GetSource(lodSrcIndex); string lodExpr = GetSourceExpr(context, lod, GetSrcVarType(operation.Inst, srcIndex));
string lodExpr = GetSoureExpr(context, lod, GetSrcVarType(operation.Inst, lodSrcIndex));
texCall = $"textureSize({samplerName}, {lodExpr}){GetMask(texOp.Index)}"; texCall = $"textureSize({samplerName}, {lodExpr}){GetMask(texOp.Index)}";
} }
@ -697,12 +586,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (storageKind == StorageKind.Input) if (storageKind == StorageKind.Input)
{ {
string expr = GetSoureExpr(context, operation.GetSource(srcIndex++), AggregateType.S32); string expr = GetSourceExpr(context, operation.GetSource(srcIndex++), AggregateType.S32);
varName = $"gl_in[{expr}].{varName}"; varName = $"gl_in[{expr}].{varName}";
} }
else if (storageKind == StorageKind.Output) else if (storageKind == StorageKind.Output)
{ {
string expr = GetSoureExpr(context, operation.GetSource(srcIndex++), AggregateType.S32); string expr = GetSourceExpr(context, operation.GetSource(srcIndex++), AggregateType.S32);
varName = $"gl_out[{expr}].{varName}"; varName = $"gl_out[{expr}].{varName}";
} }
} }
@ -735,38 +624,40 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
} }
else else
{ {
varName += $"[{GetSoureExpr(context, src, AggregateType.S32)}]"; varName += $"[{GetSourceExpr(context, src, AggregateType.S32)}]";
} }
} }
if (isStore) if (isStore)
{ {
varType &= AggregateType.ElementTypeMask; varType &= AggregateType.ElementTypeMask;
varName = $"{varName} = {GetSoureExpr(context, operation.GetSource(srcIndex), varType)}"; varName = $"{varName} = {GetSourceExpr(context, operation.GetSource(srcIndex), varType)}";
} }
return varName; return varName;
} }
private static string GetSamplerName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr) private static string GetSamplerName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
{ {
string name = resourceDefinitions.Textures[texOp.Binding].Name; TextureDefinition definition = context.Properties.Textures[texOp.Binding];
string name = definition.Name;
if (texOp.Type.HasFlag(SamplerType.Indexed)) if (definition.ArrayLength != 1)
{ {
name = $"{name}[{indexExpr}]"; name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]";
} }
return name; return name;
} }
private static string GetImageName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr) private static string GetImageName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
{ {
string name = resourceDefinitions.Images[texOp.Binding].Name; TextureDefinition definition = context.Properties.Images[texOp.Binding];
string name = definition.Name;
if (texOp.Type.HasFlag(SamplerType.Indexed)) if (definition.ArrayLength != 1)
{ {
name = $"{name}[{indexExpr}]"; name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]";
} }
return name; return name;

View File

@ -13,8 +13,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IAstNode src0 = operation.GetSource(0); IAstNode src0 = operation.GetSource(0);
IAstNode src1 = operation.GetSource(1); IAstNode src1 = operation.GetSource(1);
string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0)); string src0Expr = GetSourceExpr(context, src0, GetSrcVarType(operation.Inst, 0));
string src1Expr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1)); string src1Expr = GetSourceExpr(context, src1, GetSrcVarType(operation.Inst, 1));
return $"packDouble2x32(uvec2({src0Expr}, {src1Expr}))"; return $"packDouble2x32(uvec2({src0Expr}, {src1Expr}))";
} }
@ -24,8 +24,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IAstNode src0 = operation.GetSource(0); IAstNode src0 = operation.GetSource(0);
IAstNode src1 = operation.GetSource(1); IAstNode src1 = operation.GetSource(1);
string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0)); string src0Expr = GetSourceExpr(context, src0, GetSrcVarType(operation.Inst, 0));
string src1Expr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1)); string src1Expr = GetSourceExpr(context, src1, GetSrcVarType(operation.Inst, 1));
return $"packHalf2x16(vec2({src0Expr}, {src1Expr}))"; return $"packHalf2x16(vec2({src0Expr}, {src1Expr}))";
} }
@ -34,7 +34,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
IAstNode src = operation.GetSource(0); IAstNode src = operation.GetSource(0);
string srcExpr = GetSoureExpr(context, src, GetSrcVarType(operation.Inst, 0)); string srcExpr = GetSourceExpr(context, src, GetSrcVarType(operation.Inst, 0));
return $"unpackDouble2x32({srcExpr}){GetMask(operation.Index)}"; return $"unpackDouble2x32({srcExpr}){GetMask(operation.Index)}";
} }
@ -43,7 +43,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
IAstNode src = operation.GetSource(0); IAstNode src = operation.GetSource(0);
string srcExpr = GetSoureExpr(context, src, GetSrcVarType(operation.Inst, 0)); string srcExpr = GetSourceExpr(context, src, GetSrcVarType(operation.Inst, 0));
return $"unpackHalf2x16({srcExpr}){GetMask(operation.Index)}"; return $"unpackHalf2x16({srcExpr}){GetMask(operation.Index)}";
} }

View File

@ -9,8 +9,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
public static string Shuffle(CodeGenContext context, AstOperation operation) public static string Shuffle(CodeGenContext context, AstOperation operation)
{ {
string value = GetSoureExpr(context, operation.GetSource(0), AggregateType.FP32); string value = GetSourceExpr(context, operation.GetSource(0), AggregateType.FP32);
string index = GetSoureExpr(context, operation.GetSource(1), AggregateType.U32); string index = GetSourceExpr(context, operation.GetSource(1), AggregateType.U32);
if (context.HostCapabilities.SupportsShaderBallot) if (context.HostCapabilities.SupportsShaderBallot)
{ {

View File

@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IAstNode vector = operation.GetSource(0); IAstNode vector = operation.GetSource(0);
IAstNode index = operation.GetSource(1); IAstNode index = operation.GetSource(1);
string vectorExpr = GetSoureExpr(context, vector, OperandManager.GetNodeDestType(context, vector)); string vectorExpr = GetSourceExpr(context, vector, OperandManager.GetNodeDestType(context, vector));
if (index is AstOperand indexOperand && indexOperand.Type == OperandType.Constant) if (index is AstOperand indexOperand && indexOperand.Type == OperandType.Constant)
{ {
@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
} }
else else
{ {
string indexExpr = GetSoureExpr(context, index, GetSrcVarType(operation.Inst, 1)); string indexExpr = GetSourceExpr(context, index, GetSrcVarType(operation.Inst, 1));
return $"{vectorExpr}[{indexExpr}]"; return $"{vectorExpr}[{indexExpr}]";
} }

View File

@ -146,9 +146,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
} }
else if (operation is AstTextureOperation texOp) else if (operation is AstTextureOperation texOp)
{ {
if (texOp.Inst == Instruction.ImageLoad || if (texOp.Inst.IsImage())
texOp.Inst == Instruction.ImageStore ||
texOp.Inst == Instruction.ImageAtomic)
{ {
return texOp.GetVectorType(texOp.Format.GetComponentType()); return texOp.GetVectorType(texOp.Format.GetComponentType());
} }

View File

@ -34,8 +34,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public Dictionary<int, Instruction> SharedMemories { get; } = new(); public Dictionary<int, Instruction> SharedMemories { get; } = new();
public Dictionary<int, SamplerType> SamplersTypes { get; } = new(); public Dictionary<int, SamplerType> SamplersTypes { get; } = new();
public Dictionary<int, (Instruction, Instruction, Instruction)> Samplers { get; } = new(); public Dictionary<int, SamplerDeclaration> Samplers { get; } = new();
public Dictionary<int, (Instruction, Instruction)> Images { get; } = new(); public Dictionary<int, ImageDeclaration> Images { get; } = new();
public Dictionary<IoDefinition, Instruction> Inputs { get; } = new(); public Dictionary<IoDefinition, Instruction> Inputs { get; } = new();
public Dictionary<IoDefinition, Instruction> Outputs { get; } = new(); public Dictionary<IoDefinition, Instruction> Outputs { get; } = new();

View File

@ -181,9 +181,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var sampledImageType = context.TypeSampledImage(imageType); var sampledImageType = context.TypeSampledImage(imageType);
var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageType); var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageType);
var sampledImageVariable = context.Variable(sampledImagePointerType, StorageClass.UniformConstant); var sampledImageArrayPointerType = sampledImagePointerType;
context.Samplers.Add(sampler.Binding, (imageType, sampledImageType, sampledImageVariable)); if (sampler.ArrayLength == 0)
{
var sampledImageArrayType = context.TypeRuntimeArray(sampledImageType);
sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType);
}
else if (sampler.ArrayLength != 1)
{
var sampledImageArrayType = context.TypeArray(sampledImageType, context.Constant(context.TypeU32(), sampler.ArrayLength));
sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType);
}
var sampledImageVariable = context.Variable(sampledImageArrayPointerType, StorageClass.UniformConstant);
context.Samplers.Add(sampler.Binding, new SamplerDeclaration(
imageType,
sampledImageType,
sampledImagePointerType,
sampledImageVariable,
sampler.ArrayLength != 1));
context.SamplersTypes.Add(sampler.Binding, sampler.Type); context.SamplersTypes.Add(sampler.Binding, sampler.Type);
context.Name(sampledImageVariable, sampler.Name); context.Name(sampledImageVariable, sampler.Name);
@ -211,9 +229,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
GetImageFormat(image.Format)); GetImageFormat(image.Format));
var imagePointerType = context.TypePointer(StorageClass.UniformConstant, imageType); var imagePointerType = context.TypePointer(StorageClass.UniformConstant, imageType);
var imageVariable = context.Variable(imagePointerType, StorageClass.UniformConstant); var imageArrayPointerType = imagePointerType;
context.Images.Add(image.Binding, (imageType, imageVariable)); if (image.ArrayLength == 0)
{
var imageArrayType = context.TypeRuntimeArray(imageType);
imageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, imageArrayType);
}
else if (image.ArrayLength != 1)
{
var imageArrayType = context.TypeArray(imageType, context.Constant(context.TypeU32(), image.ArrayLength));
imageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, imageArrayType);
}
var imageVariable = context.Variable(imageArrayPointerType, StorageClass.UniformConstant);
context.Images.Add(image.Binding, new ImageDeclaration(imageType, imagePointerType, imageVariable, image.ArrayLength != 1));
context.Name(imageVariable, image.Name); context.Name(imageVariable, image.Name);
context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex); context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);

View File

@ -0,0 +1,20 @@
using Spv.Generator;
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
readonly struct ImageDeclaration
{
public readonly Instruction ImageType;
public readonly Instruction ImagePointerType;
public readonly Instruction Image;
public readonly bool IsIndexed;
public ImageDeclaration(Instruction imageType, Instruction imagePointerType, Instruction image, bool isIndexed)
{
ImageType = imageType;
ImagePointerType = imagePointerType;
Image = image;
IsIndexed = isIndexed;
}
}
}

View File

@ -591,34 +591,28 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
var componentType = texOp.Format.GetComponentType(); var componentType = texOp.Format.GetComponentType();
// TODO: Bindless texture support. For now we just return 0/do nothing.
if (isBindless)
{
return new OperationResult(componentType, componentType switch
{
AggregateType.S32 => context.Constant(context.TypeS32(), 0),
AggregateType.U32 => context.Constant(context.TypeU32(), 0u),
_ => context.Constant(context.TypeFP32(), 0f),
});
}
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
int srcIndex = isBindless ? 1 : 0; int srcIndex = 0;
SpvInstruction Src(AggregateType type) SpvInstruction Src(AggregateType type)
{ {
return context.Get(type, texOp.GetSource(srcIndex++)); return context.Get(type, texOp.GetSource(srcIndex++));
} }
if (isIndexed) ImageDeclaration declaration = context.Images[texOp.Binding];
SpvInstruction image = declaration.Image;
SpvInstruction resultType = context.GetType(componentType);
SpvInstruction imagePointerType = context.TypePointer(StorageClass.Image, resultType);
if (declaration.IsIndexed)
{ {
Src(AggregateType.S32); SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(imagePointerType, image, textureIndex);
} }
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
@ -646,14 +640,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
SpvInstruction value = Src(componentType); SpvInstruction value = Src(componentType);
(var imageType, var imageVariable) = context.Images[texOp.Binding]; var pointer = context.ImageTexelPointer(imagePointerType, image, pCoords, context.Constant(context.TypeU32(), 0));
context.Load(imageType, imageVariable);
SpvInstruction resultType = context.GetType(componentType);
SpvInstruction imagePointerType = context.TypePointer(StorageClass.Image, resultType);
var pointer = context.ImageTexelPointer(imagePointerType, imageVariable, pCoords, context.Constant(context.TypeU32(), 0));
var one = context.Constant(context.TypeU32(), 1); var one = context.Constant(context.TypeU32(), 1);
var zero = context.Constant(context.TypeU32(), 0); var zero = context.Constant(context.TypeU32(), 0);
@ -683,31 +670,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
var componentType = texOp.Format.GetComponentType(); var componentType = texOp.Format.GetComponentType();
// TODO: Bindless texture support. For now we just return 0/do nothing.
if (isBindless)
{
return GetZeroOperationResult(context, texOp, componentType, isVector: true);
}
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
int srcIndex = isBindless ? 1 : 0; int srcIndex = 0;
SpvInstruction Src(AggregateType type) SpvInstruction Src(AggregateType type)
{ {
return context.Get(type, texOp.GetSource(srcIndex++)); return context.Get(type, texOp.GetSource(srcIndex++));
} }
if (isIndexed) ImageDeclaration declaration = context.Images[texOp.Binding];
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
{ {
Src(AggregateType.S32); SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(declaration.ImagePointerType, image, textureIndex);
} }
image = context.Load(declaration.ImageType, image);
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
int pCount = coordsCount + (isArray ? 1 : 0); int pCount = coordsCount + (isArray ? 1 : 0);
@ -731,9 +716,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
pCoords = Src(AggregateType.S32); pCoords = Src(AggregateType.S32);
} }
(var imageType, var imageVariable) = context.Images[texOp.Binding];
var image = context.Load(imageType, imageVariable);
var imageComponentType = context.GetType(componentType); var imageComponentType = context.GetType(componentType);
var swizzledResultType = texOp.GetVectorType(componentType); var swizzledResultType = texOp.GetVectorType(componentType);
@ -747,29 +729,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
// TODO: Bindless texture support. For now we just return 0/do nothing.
if (isBindless)
{
return OperationResult.Invalid;
}
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
int srcIndex = isBindless ? 1 : 0; int srcIndex = 0;
SpvInstruction Src(AggregateType type) SpvInstruction Src(AggregateType type)
{ {
return context.Get(type, texOp.GetSource(srcIndex++)); return context.Get(type, texOp.GetSource(srcIndex++));
} }
if (isIndexed) ImageDeclaration declaration = context.Images[texOp.Binding];
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
{ {
Src(AggregateType.S32); SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(declaration.ImagePointerType, image, textureIndex);
} }
image = context.Load(declaration.ImageType, image);
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
int pCount = coordsCount + (isArray ? 1 : 0); int pCount = coordsCount + (isArray ? 1 : 0);
@ -818,10 +798,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var texel = context.CompositeConstruct(context.TypeVector(context.GetType(componentType), ComponentsCount), cElems); var texel = context.CompositeConstruct(context.TypeVector(context.GetType(componentType), ComponentsCount), cElems);
(var imageType, var imageVariable) = context.Images[texOp.Binding];
var image = context.Load(imageType, imageVariable);
context.ImageWrite(image, pCoords, texel, ImageOperandsMask.MaskNone); context.ImageWrite(image, pCoords, texel, ImageOperandsMask.MaskNone);
return OperationResult.Invalid; return OperationResult.Invalid;
@ -854,16 +830,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
// TODO: Bindless texture support. For now we just return 0.
if (isBindless)
{
return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0));
}
int srcIndex = 0; int srcIndex = 0;
SpvInstruction Src(AggregateType type) SpvInstruction Src(AggregateType type)
@ -871,11 +837,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return context.Get(type, texOp.GetSource(srcIndex++)); return context.Get(type, texOp.GetSource(srcIndex++));
} }
if (isIndexed) SamplerDeclaration declaration = context.Samplers[texOp.Binding];
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
{ {
Src(AggregateType.S32); SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
} }
image = context.Load(declaration.SampledImageType, image);
int pCount = texOp.Type.GetDimensions(); int pCount = texOp.Type.GetDimensions();
SpvInstruction pCoords; SpvInstruction pCoords;
@ -897,10 +870,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
pCoords = Src(AggregateType.FP32); pCoords = Src(AggregateType.FP32);
} }
(_, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
var image = context.Load(sampledImageType, sampledImageVariable);
var resultType = context.TypeVector(context.TypeFP32(), 2); var resultType = context.TypeVector(context.TypeFP32(), 2);
var packed = context.ImageQueryLod(resultType, image, pCoords); var packed = context.ImageQueryLod(resultType, image, pCoords);
var result = context.CompositeExtract(context.TypeFP32(), packed, (SpvLiteralInteger)texOp.Index); var result = context.CompositeExtract(context.TypeFP32(), packed, (SpvLiteralInteger)texOp.Index);
@ -1182,7 +1151,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; bool isGather = (texOp.Flags & TextureFlags.Gather) != 0;
bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0;
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
@ -1192,30 +1160,28 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
bool colorIsVector = isGather || !isShadow; int srcIndex = 0;
// TODO: Bindless texture support. For now we just return 0.
if (isBindless)
{
return GetZeroOperationResult(context, texOp, AggregateType.FP32, colorIsVector);
}
int srcIndex = isBindless ? 1 : 0;
SpvInstruction Src(AggregateType type) SpvInstruction Src(AggregateType type)
{ {
return context.Get(type, texOp.GetSource(srcIndex++)); return context.Get(type, texOp.GetSource(srcIndex++));
} }
if (isIndexed) SamplerDeclaration declaration = context.Samplers[texOp.Binding];
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
{ {
Src(AggregateType.S32); SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
} }
image = context.Load(declaration.SampledImageType, image);
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
int pCount = coordsCount; int pCount = coordsCount;
@ -1419,15 +1385,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
operandsList.Add(sample); operandsList.Add(sample);
} }
bool colorIsVector = isGather || !isShadow;
var resultType = colorIsVector ? context.TypeVector(context.TypeFP32(), 4) : context.TypeFP32(); var resultType = colorIsVector ? context.TypeVector(context.TypeFP32(), 4) : context.TypeFP32();
(var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
var image = context.Load(sampledImageType, sampledImageVariable);
if (intCoords) if (intCoords)
{ {
image = context.Image(imageType, image); image = context.Image(declaration.ImageType, image);
} }
var operands = operandsList.ToArray(); var operands = operandsList.ToArray();
@ -1485,25 +1449,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; SamplerDeclaration declaration = context.Samplers[texOp.Binding];
SpvInstruction image = declaration.Image;
// TODO: Bindless texture support. For now we just return 0. if (declaration.IsIndexed)
if (isBindless)
{ {
return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0)); SpvInstruction textureIndex = context.GetS32(texOp.GetSource(0));
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
} }
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; image = context.Load(declaration.SampledImageType, image);
image = context.Image(declaration.ImageType, image);
if (isIndexed)
{
context.GetS32(texOp.GetSource(0));
}
(var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
var image = context.Load(sampledImageType, sampledImageVariable);
image = context.Image(imageType, image);
SpvInstruction result = context.ImageQuerySamples(context.TypeS32(), image); SpvInstruction result = context.ImageQuerySamples(context.TypeS32(), image);
@ -1514,25 +1471,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; SamplerDeclaration declaration = context.Samplers[texOp.Binding];
SpvInstruction image = declaration.Image;
// TODO: Bindless texture support. For now we just return 0. if (declaration.IsIndexed)
if (isBindless)
{ {
return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0)); SpvInstruction textureIndex = context.GetS32(texOp.GetSource(0));
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
} }
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; image = context.Load(declaration.SampledImageType, image);
image = context.Image(declaration.ImageType, image);
if (isIndexed)
{
context.GetS32(texOp.GetSource(0));
}
(var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
var image = context.Load(sampledImageType, sampledImageVariable);
image = context.Image(imageType, image);
if (texOp.Index == 3) if (texOp.Index == 3)
{ {
@ -1556,7 +1506,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (hasLod) if (hasLod)
{ {
int lodSrcIndex = isBindless || isIndexed ? 1 : 0; int lodSrcIndex = declaration.IsIndexed ? 1 : 0;
var lod = context.GetS32(operation.GetSource(lodSrcIndex)); var lod = context.GetS32(operation.GetSource(lodSrcIndex));
result = context.ImageQuerySizeLod(resultType, image, lod); result = context.ImageQuerySizeLod(resultType, image, lod);
} }
@ -1929,38 +1879,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return context.Load(context.GetType(varType), context.Inputs[ioDefinition]); return context.Load(context.GetType(varType), context.Inputs[ioDefinition]);
} }
private static OperationResult GetZeroOperationResult(
CodeGenContext context,
AstTextureOperation texOp,
AggregateType scalarType,
bool isVector)
{
var zero = scalarType switch
{
AggregateType.S32 => context.Constant(context.TypeS32(), 0),
AggregateType.U32 => context.Constant(context.TypeU32(), 0u),
_ => context.Constant(context.TypeFP32(), 0f),
};
if (isVector)
{
AggregateType outputType = texOp.GetVectorType(scalarType);
if ((outputType & AggregateType.ElementCountMask) != 0)
{
int componentsCount = BitOperations.PopCount((uint)texOp.Index);
SpvInstruction[] values = new SpvInstruction[componentsCount];
values.AsSpan().Fill(zero);
return new OperationResult(outputType, context.ConstantComposite(context.GetType(outputType), values));
}
}
return new OperationResult(scalarType, zero);
}
private static SpvInstruction GetSwizzledResult(CodeGenContext context, SpvInstruction vector, AggregateType swizzledResultType, int mask) private static SpvInstruction GetSwizzledResult(CodeGenContext context, SpvInstruction vector, AggregateType swizzledResultType, int mask)
{ {
if ((swizzledResultType & AggregateType.ElementCountMask) != 0) if ((swizzledResultType & AggregateType.ElementCountMask) != 0)

View File

@ -0,0 +1,27 @@
using Spv.Generator;
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
readonly struct SamplerDeclaration
{
public readonly Instruction ImageType;
public readonly Instruction SampledImageType;
public readonly Instruction SampledImagePointerType;
public readonly Instruction Image;
public readonly bool IsIndexed;
public SamplerDeclaration(
Instruction imageType,
Instruction sampledImageType,
Instruction sampledImagePointerType,
Instruction image,
bool isIndexed)
{
ImageType = imageType;
SampledImageType = sampledImageType;
SampledImagePointerType = sampledImagePointerType;
Image = image;
IsIndexed = isIndexed;
}
}
}

View File

@ -26,47 +26,42 @@ namespace Ryujinx.Graphics.Shader
/// <returns>Span of the memory location</returns> /// <returns>Span of the memory location</returns>
ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize); ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize);
/// <summary>
/// Gets the size in bytes of a bound constant buffer for the current shader stage.
/// </summary>
/// <param name="slot">The number of the constant buffer to get the size from</param>
/// <returns>Size in bytes</returns>
int QueryTextureArrayLengthFromBuffer(int slot);
/// <summary> /// <summary>
/// Queries the binding number of a constant buffer. /// Queries the binding number of a constant buffer.
/// </summary> /// </summary>
/// <param name="index">Constant buffer index</param> /// <param name="index">Constant buffer index</param>
/// <returns>Binding number</returns> /// <returns>Binding number</returns>
int QueryBindingConstantBuffer(int index) int CreateConstantBufferBinding(int index);
{
return index + 1; /// <summary>
} /// Queries the binding number of an image.
/// </summary>
/// <param name="count">For array of images, the number of elements of the array, otherwise it should be 1</param>
/// <param name="isBuffer">Indicates if the image is a buffer image</param>
/// <returns>Binding number</returns>
int CreateImageBinding(int count, bool isBuffer);
/// <summary> /// <summary>
/// Queries the binding number of a storage buffer. /// Queries the binding number of a storage buffer.
/// </summary> /// </summary>
/// <param name="index">Storage buffer index</param> /// <param name="index">Storage buffer index</param>
/// <returns>Binding number</returns> /// <returns>Binding number</returns>
int QueryBindingStorageBuffer(int index) int CreateStorageBufferBinding(int index);
{
return index;
}
/// <summary> /// <summary>
/// Queries the binding number of a texture. /// Queries the binding number of a texture.
/// </summary> /// </summary>
/// <param name="index">Texture index</param> /// <param name="count">For array of textures, the number of elements of the array, otherwise it should be 1</param>
/// <param name="isBuffer">Indicates if the texture is a buffer texture</param> /// <param name="isBuffer">Indicates if the texture is a buffer texture</param>
/// <returns>Binding number</returns> /// <returns>Binding number</returns>
int QueryBindingTexture(int index, bool isBuffer) int CreateTextureBinding(int count, bool isBuffer);
{
return index;
}
/// <summary>
/// Queries the binding number of an image.
/// </summary>
/// <param name="index">Image index</param>
/// <param name="isBuffer">Indicates if the image is a buffer image</param>
/// <returns>Binding number</returns>
int QueryBindingImage(int index, bool isBuffer)
{
return index;
}
/// <summary> /// <summary>
/// Queries Local Size X for compute shaders. /// Queries Local Size X for compute shaders.

View File

@ -161,5 +161,17 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
inst &= Instruction.Mask; inst &= Instruction.Mask;
return inst == Instruction.Lod || inst == Instruction.TextureQuerySamples || inst == Instruction.TextureQuerySize; return inst == Instruction.Lod || inst == Instruction.TextureQuerySamples || inst == Instruction.TextureQuerySize;
} }
public static bool IsImage(this Instruction inst)
{
inst &= Instruction.Mask;
return inst == Instruction.ImageAtomic || inst == Instruction.ImageLoad || inst == Instruction.ImageStore;
}
public static bool IsImageStore(this Instruction inst)
{
inst &= Instruction.Mask;
return inst == Instruction.ImageAtomic || inst == Instruction.ImageStore;
}
} }
} }

View File

@ -20,13 +20,13 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
} }
set set
{ {
if (value != null && value.Type == OperandType.LocalVariable)
{
value.AsgOp = this;
}
if (value != null) if (value != null)
{ {
if (value.Type == OperandType.LocalVariable)
{
value.AsgOp = this;
}
_dests = new[] { value }; _dests = new[] { value };
} }
else else

View File

@ -26,9 +26,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
Binding = binding; Binding = binding;
} }
public void TurnIntoIndexed(int binding) public void TurnIntoArray(int binding)
{ {
Type |= SamplerType.Indexed;
Flags &= ~TextureFlags.Bindless; Flags &= ~TextureFlags.Bindless;
Binding = binding; Binding = binding;
} }

View File

@ -16,9 +16,8 @@ namespace Ryujinx.Graphics.Shader
Mask = 0xff, Mask = 0xff,
Array = 1 << 8, Array = 1 << 8,
Indexed = 1 << 9, Multisample = 1 << 9,
Multisample = 1 << 10, Shadow = 1 << 10,
Shadow = 1 << 11,
} }
static class SamplerTypeExtensions static class SamplerTypeExtensions
@ -36,6 +35,36 @@ namespace Ryujinx.Graphics.Shader
}; };
} }
public static string ToShortSamplerType(this SamplerType type)
{
string typeName = (type & SamplerType.Mask) switch
{
SamplerType.Texture1D => "1d",
SamplerType.TextureBuffer => "b",
SamplerType.Texture2D => "2d",
SamplerType.Texture3D => "3d",
SamplerType.TextureCube => "cube",
_ => throw new ArgumentException($"Invalid sampler type \"{type}\"."),
};
if ((type & SamplerType.Multisample) != 0)
{
typeName += "ms";
}
if ((type & SamplerType.Array) != 0)
{
typeName += "a";
}
if ((type & SamplerType.Shadow) != 0)
{
typeName += "s";
}
return typeName;
}
public static string ToGlslSamplerType(this SamplerType type) public static string ToGlslSamplerType(this SamplerType type)
{ {
string typeName = (type & SamplerType.Mask) switch string typeName = (type & SamplerType.Mask) switch

View File

@ -4,15 +4,17 @@ namespace Ryujinx.Graphics.Shader
{ {
public int Set { get; } public int Set { get; }
public int Binding { get; } public int Binding { get; }
public int ArrayLength { get; }
public string Name { get; } public string Name { get; }
public SamplerType Type { get; } public SamplerType Type { get; }
public TextureFormat Format { get; } public TextureFormat Format { get; }
public TextureUsageFlags Flags { get; } public TextureUsageFlags Flags { get; }
public TextureDefinition(int set, int binding, string name, SamplerType type, TextureFormat format, TextureUsageFlags flags) public TextureDefinition(int set, int binding, int arrayLength, string name, SamplerType type, TextureFormat format, TextureUsageFlags flags)
{ {
Set = set; Set = set;
Binding = binding; Binding = binding;
ArrayLength = arrayLength;
Name = name; Name = name;
Type = type; Type = type;
Format = format; Format = format;
@ -21,7 +23,7 @@ namespace Ryujinx.Graphics.Shader
public TextureDefinition SetFlag(TextureUsageFlags flag) public TextureDefinition SetFlag(TextureUsageFlags flag)
{ {
return new TextureDefinition(Set, Binding, Name, Type, Format, Flags | flag); return new TextureDefinition(Set, Binding, ArrayLength, Name, Type, Format, Flags | flag);
} }
} }
} }

View File

@ -11,16 +11,25 @@ namespace Ryujinx.Graphics.Shader
public readonly int CbufSlot; public readonly int CbufSlot;
public readonly int HandleIndex; public readonly int HandleIndex;
public readonly int ArrayLength;
public readonly TextureUsageFlags Flags; public readonly TextureUsageFlags Flags;
public TextureDescriptor(int binding, SamplerType type, TextureFormat format, int cbufSlot, int handleIndex, TextureUsageFlags flags) public TextureDescriptor(
int binding,
SamplerType type,
TextureFormat format,
int cbufSlot,
int handleIndex,
int arrayLength,
TextureUsageFlags flags)
{ {
Binding = binding; Binding = binding;
Type = type; Type = type;
Format = format; Format = format;
CbufSlot = cbufSlot; CbufSlot = cbufSlot;
HandleIndex = handleIndex; HandleIndex = handleIndex;
ArrayLength = arrayLength;
Flags = flags; Flags = flags;
} }
} }

View File

@ -88,7 +88,7 @@ namespace Ryujinx.Graphics.Shader
{ {
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = UnpackOffsets(wordOffset); (int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = UnpackOffsets(wordOffset);
int handle = cachedTextureBuffer.Length != 0 ? cachedTextureBuffer[textureWordOffset] : 0; int handle = textureWordOffset < cachedTextureBuffer.Length ? cachedTextureBuffer[textureWordOffset] : 0;
// The "wordOffset" (which is really the immediate value used on texture instructions on the shader) // The "wordOffset" (which is really the immediate value used on texture instructions on the shader)
// is a 13-bit value. However, in order to also support separate samplers and textures (which uses // is a 13-bit value. However, in order to also support separate samplers and textures (which uses
@ -102,7 +102,7 @@ namespace Ryujinx.Graphics.Shader
if (handleType != TextureHandleType.SeparateConstantSamplerHandle) if (handleType != TextureHandleType.SeparateConstantSamplerHandle)
{ {
samplerHandle = cachedSamplerBuffer.Length != 0 ? cachedSamplerBuffer[samplerWordOffset] : 0; samplerHandle = samplerWordOffset < cachedSamplerBuffer.Length ? cachedSamplerBuffer[samplerWordOffset] : 0;
} }
else else
{ {

View File

@ -15,8 +15,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// - The handle is a constant buffer value. // - The handle is a constant buffer value.
// - The handle is the result of a bitwise OR logical operation. // - The handle is the result of a bitwise OR logical operation.
// - Both sources of the OR operation comes from a constant buffer. // - Both sources of the OR operation comes from a constant buffer.
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next) LinkedListNode<INode> nextNode;
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = nextNode)
{ {
nextNode = node.Next;
if (node.Value is not TextureOperation texOp) if (node.Value is not TextureOperation texOp)
{ {
continue; continue;
@ -27,185 +31,207 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue; continue;
} }
if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery()) if (!TryConvertBindless(block, resourceManager, gpuAccessor, texOp))
{ {
Operand bindlessHandle = texOp.GetSource(0); // If we can't do bindless elimination, remove the texture operation.
// Set any destination variables to zero.
// In some cases the compiler uses a shuffle operation to get the handle, for (int destIndex = 0; destIndex < texOp.DestsCount; destIndex++)
// for some textureGrad implementations. In those cases, we can skip the shuffle.
if (bindlessHandle.AsgOp is Operation shuffleOp && shuffleOp.Inst == Instruction.Shuffle)
{ {
bindlessHandle = shuffleOp.GetSource(0); block.Operations.AddBefore(node, new Operation(Instruction.Copy, texOp.GetDest(destIndex), OperandHelper.Const(0)));
} }
bindlessHandle = Utils.FindLastOperation(bindlessHandle, block); Utils.DeleteNode(node, texOp);
}
}
}
// Some instructions do not encode an accurate sampler type: private static bool TryConvertBindless(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor, TextureOperation texOp)
// - Most instructions uses the same type for 1D and Buffer. {
// - Query instructions may not have any type. if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery())
// For those cases, we need to try getting the type from current GPU state, {
// as long bindless elimination is successful and we know where the texture descriptor is located. Operand bindlessHandle = texOp.GetSource(0);
bool rewriteSamplerType =
texOp.Type == SamplerType.TextureBuffer ||
texOp.Inst == Instruction.TextureQuerySamples ||
texOp.Inst == Instruction.TextureQuerySize;
if (bindlessHandle.Type == OperandType.ConstantBuffer) // In some cases the compiler uses a shuffle operation to get the handle,
// for some textureGrad implementations. In those cases, we can skip the shuffle.
if (bindlessHandle.AsgOp is Operation shuffleOp && shuffleOp.Inst == Instruction.Shuffle)
{
bindlessHandle = shuffleOp.GetSource(0);
}
bindlessHandle = Utils.FindLastOperation(bindlessHandle, block);
// Some instructions do not encode an accurate sampler type:
// - Most instructions uses the same type for 1D and Buffer.
// - Query instructions may not have any type.
// For those cases, we need to try getting the type from current GPU state,
// as long bindless elimination is successful and we know where the texture descriptor is located.
bool rewriteSamplerType =
texOp.Type == SamplerType.TextureBuffer ||
texOp.Inst == Instruction.TextureQuerySamples ||
texOp.Inst == Instruction.TextureQuerySize;
if (bindlessHandle.Type == OperandType.ConstantBuffer)
{
SetHandle(
resourceManager,
gpuAccessor,
texOp,
bindlessHandle.GetCbufOffset(),
bindlessHandle.GetCbufSlot(),
rewriteSamplerType,
isImage: false);
return true;
}
if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp))
{
return false;
}
if (handleCombineOp.Inst != Instruction.BitwiseOr)
{
return false;
}
Operand src0 = Utils.FindLastOperation(handleCombineOp.GetSource(0), block);
Operand src1 = Utils.FindLastOperation(handleCombineOp.GetSource(1), block);
// For cases where we have a constant, ensure that the constant is always
// the second operand.
// Since this is a commutative operation, both are fine,
// and having a "canonical" representation simplifies some checks below.
if (src0.Type == OperandType.Constant && src1.Type != OperandType.Constant)
{
(src0, src1) = (src1, src0);
}
TextureHandleType handleType = TextureHandleType.SeparateSamplerHandle;
// Try to match the following patterns:
// Masked pattern:
// - samplerHandle = samplerHandle & 0xFFF00000;
// - textureHandle = textureHandle & 0xFFFFF;
// - combinedHandle = samplerHandle | textureHandle;
// Where samplerHandle and textureHandle comes from a constant buffer.
// Shifted pattern:
// - samplerHandle = samplerId << 20;
// - combinedHandle = samplerHandle | textureHandle;
// Where samplerId and textureHandle comes from a constant buffer.
// Constant pattern:
// - combinedHandle = samplerHandleConstant | textureHandle;
// Where samplerHandleConstant is a constant value, and textureHandle comes from a constant buffer.
if (src0.AsgOp is Operation src0AsgOp)
{
if (src1.AsgOp is Operation src1AsgOp &&
src0AsgOp.Inst == Instruction.BitwiseAnd &&
src1AsgOp.Inst == Instruction.BitwiseAnd)
{ {
SetHandle( src0 = GetSourceForMaskedHandle(src0AsgOp, 0xFFFFF);
resourceManager, src1 = GetSourceForMaskedHandle(src1AsgOp, 0xFFF00000);
gpuAccessor,
texOp,
bindlessHandle.GetCbufOffset(),
bindlessHandle.GetCbufSlot(),
rewriteSamplerType,
isImage: false);
continue; // The OR operation is commutative, so we can also try to swap the operands to get a match.
} if (src0 == null || src1 == null)
if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp))
{
continue;
}
if (handleCombineOp.Inst != Instruction.BitwiseOr)
{
continue;
}
Operand src0 = Utils.FindLastOperation(handleCombineOp.GetSource(0), block);
Operand src1 = Utils.FindLastOperation(handleCombineOp.GetSource(1), block);
// For cases where we have a constant, ensure that the constant is always
// the second operand.
// Since this is a commutative operation, both are fine,
// and having a "canonical" representation simplifies some checks below.
if (src0.Type == OperandType.Constant && src1.Type != OperandType.Constant)
{
(src0, src1) = (src1, src0);
}
TextureHandleType handleType = TextureHandleType.SeparateSamplerHandle;
// Try to match the following patterns:
// Masked pattern:
// - samplerHandle = samplerHandle & 0xFFF00000;
// - textureHandle = textureHandle & 0xFFFFF;
// - combinedHandle = samplerHandle | textureHandle;
// Where samplerHandle and textureHandle comes from a constant buffer.
// Shifted pattern:
// - samplerHandle = samplerId << 20;
// - combinedHandle = samplerHandle | textureHandle;
// Where samplerId and textureHandle comes from a constant buffer.
// Constant pattern:
// - combinedHandle = samplerHandleConstant | textureHandle;
// Where samplerHandleConstant is a constant value, and textureHandle comes from a constant buffer.
if (src0.AsgOp is Operation src0AsgOp)
{
if (src1.AsgOp is Operation src1AsgOp &&
src0AsgOp.Inst == Instruction.BitwiseAnd &&
src1AsgOp.Inst == Instruction.BitwiseAnd)
{ {
src0 = GetSourceForMaskedHandle(src0AsgOp, 0xFFFFF); src0 = GetSourceForMaskedHandle(src1AsgOp, 0xFFFFF);
src1 = GetSourceForMaskedHandle(src1AsgOp, 0xFFF00000); src1 = GetSourceForMaskedHandle(src0AsgOp, 0xFFF00000);
// The OR operation is commutative, so we can also try to swap the operands to get a match.
if (src0 == null || src1 == null)
{
src0 = GetSourceForMaskedHandle(src1AsgOp, 0xFFFFF);
src1 = GetSourceForMaskedHandle(src0AsgOp, 0xFFF00000);
}
if (src0 == null || src1 == null)
{
continue;
}
} }
else if (src0AsgOp.Inst == Instruction.ShiftLeft)
{
Operand shift = src0AsgOp.GetSource(1);
if (shift.Type == OperandType.Constant && shift.Value == 20) if (src0 == null || src1 == null)
{ {
src0 = src1; return false;
src1 = src0AsgOp.GetSource(0);
handleType = TextureHandleType.SeparateSamplerId;
}
} }
} }
else if (src1.AsgOp is Operation src1AsgOp && src1AsgOp.Inst == Instruction.ShiftLeft) else if (src0AsgOp.Inst == Instruction.ShiftLeft)
{ {
Operand shift = src1AsgOp.GetSource(1); Operand shift = src0AsgOp.GetSource(1);
if (shift.Type == OperandType.Constant && shift.Value == 20) if (shift.Type == OperandType.Constant && shift.Value == 20)
{ {
src1 = src1AsgOp.GetSource(0); src0 = src1;
src1 = src0AsgOp.GetSource(0);
handleType = TextureHandleType.SeparateSamplerId; handleType = TextureHandleType.SeparateSamplerId;
} }
} }
else if (src1.Type == OperandType.Constant && (src1.Value & 0xfffff) == 0) }
{ else if (src1.AsgOp is Operation src1AsgOp && src1AsgOp.Inst == Instruction.ShiftLeft)
handleType = TextureHandleType.SeparateConstantSamplerHandle; {
} Operand shift = src1AsgOp.GetSource(1);
if (src0.Type != OperandType.ConstantBuffer) if (shift.Type == OperandType.Constant && shift.Value == 20)
{ {
continue; src1 = src1AsgOp.GetSource(0);
} handleType = TextureHandleType.SeparateSamplerId;
if (handleType == TextureHandleType.SeparateConstantSamplerHandle)
{
SetHandle(
resourceManager,
gpuAccessor,
texOp,
TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), 0),
rewriteSamplerType,
isImage: false);
}
else if (src1.Type == OperandType.ConstantBuffer)
{
SetHandle(
resourceManager,
gpuAccessor,
texOp,
TextureHandle.PackOffsets(src0.GetCbufOffset(), src1.GetCbufOffset(), handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), src1.GetCbufSlot()),
rewriteSamplerType,
isImage: false);
} }
} }
else if (texOp.Inst == Instruction.ImageLoad || else if (src1.Type == OperandType.Constant && (src1.Value & 0xfffff) == 0)
texOp.Inst == Instruction.ImageStore ||
texOp.Inst == Instruction.ImageAtomic)
{ {
Operand src0 = Utils.FindLastOperation(texOp.GetSource(0), block); handleType = TextureHandleType.SeparateConstantSamplerHandle;
}
if (src0.Type == OperandType.ConstantBuffer) if (src0.Type != OperandType.ConstantBuffer)
{ {
int cbufOffset = src0.GetCbufOffset(); return false;
int cbufSlot = src0.GetCbufSlot(); }
if (texOp.Format == TextureFormat.Unknown) if (handleType == TextureHandleType.SeparateConstantSamplerHandle)
{ {
if (texOp.Inst == Instruction.ImageAtomic) SetHandle(
{ resourceManager,
texOp.Format = ShaderProperties.GetTextureFormatAtomic(gpuAccessor, cbufOffset, cbufSlot); gpuAccessor,
} texOp,
else TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType),
{ TextureHandle.PackSlots(src0.GetCbufSlot(), 0),
texOp.Format = ShaderProperties.GetTextureFormat(gpuAccessor, cbufOffset, cbufSlot); rewriteSamplerType,
} isImage: false);
}
bool rewriteSamplerType = texOp.Type == SamplerType.TextureBuffer; return true;
}
else if (src1.Type == OperandType.ConstantBuffer)
{
SetHandle(
resourceManager,
gpuAccessor,
texOp,
TextureHandle.PackOffsets(src0.GetCbufOffset(), src1.GetCbufOffset(), handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), src1.GetCbufSlot()),
rewriteSamplerType,
isImage: false);
SetHandle(resourceManager, gpuAccessor, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true); return true;
}
} }
} }
else if (texOp.Inst.IsImage())
{
Operand src0 = Utils.FindLastOperation(texOp.GetSource(0), block);
if (src0.Type == OperandType.ConstantBuffer)
{
int cbufOffset = src0.GetCbufOffset();
int cbufSlot = src0.GetCbufSlot();
if (texOp.Format == TextureFormat.Unknown)
{
if (texOp.Inst == Instruction.ImageAtomic)
{
texOp.Format = ShaderProperties.GetTextureFormatAtomic(gpuAccessor, cbufOffset, cbufSlot);
}
else
{
texOp.Format = ShaderProperties.GetTextureFormat(gpuAccessor, cbufOffset, cbufSlot);
}
}
bool rewriteSamplerType = texOp.Type == SamplerType.TextureBuffer;
SetHandle(resourceManager, gpuAccessor, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true);
return true;
}
}
return false;
} }
private static bool TryGetOperation(INode asgOp, out Operation outOperation) private static bool TryGetOperation(INode asgOp, out Operation outOperation)

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