Compare commits

...

20 Commits

Author SHA1 Message Date
Marco Carvalho
22fb8c9d4f Update to new standard for volatility operations (#6682) 2024-04-19 09:03:52 -03:00
gdkchan
2f93ae9a19 Fix unmapped address check when reading texture handles (#6679) 2024-04-18 19:28:16 -03:00
Marco Carvalho
3224ddeeb4 Update "SixLabors.ImageSharp" (#6680) 2024-04-18 19:52:57 +02:00
Isaac Marovitz
446f2854a5 Ava UI: Input Menu Refactor (#5826)
* Refactor

* Apply suggestions from code review

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update src/Ryujinx.Input/ButtonValueType.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Add empty line

* Requested renames

* Update src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs

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

* Make parent models private readonly

* Fix ControllerInputView

* Make line shorter

* Mac keys in locale

* Double line break

* Fix build

* Get rid of _isValid

* Fix potential race condition

* Rename HasAnyButtonPressed to IsAnyButtonPressed

* Use switches

* Simplify enumeration

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
2024-04-17 18:52:12 -03:00
Luke
8884d1fd73 Fix crash when changing controller config (#6654)
* fix needsMotionInputUpdate conditions

* Fix formatting

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

---------

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2024-04-15 18:02:09 -03:00
jhorv
268c9aecf8 Texture loading: reduce memory allocations (#6623)
* rebase

* add methods Ryyjinx.Common EmbeddedResources and SteamUtils

* GAL changes - change SetData() methods and ThreadedTexture commands to use IMemoryOwner<byte> instead of SpanOrArray<byte>

* Ryujinx.Graphics.Texture: change texture conversion methods to return IMemoryOwner<byte> and allocate from ByteMemoryPool

* Ryujinx.Graphics.OpenGL: update ITexture and Texture-like types with SetData() methods to take IMemoryOwner<byte> instead of SpanOrArray<byte>

* Ryujinx.Graphics.Vulkan: update ITexture and Texture-like types with SetData() methods to take IMemoryOwner<byte> instead of SpanOrArray<byte>

* Ryujinx.Graphics.Gpu: update ITexture and Texture-like types with SetData() methods to take IMemoryOwner<byte> instead of SpanOrArray<byte>

* Remove now-unused SpanOrArray<T>

* post-rebase cleanup

* PixelConverter: remove unsafe modifier on safe methods, and remove one unnecessary cast

* use ByteMemoryPool.Rent() in GetWritableRegion() impls

* fix formatting, rename `ReadRentedMemory()` to `ReadFileToRentedMemory()``

* Texture.ConvertToHostCompatibleFormat(): dispose of `result` in Astc decode branch
2024-04-14 17:06:14 -03:00
gdkchan
e916662b0f Account for swapchain image count change after re-creation (#6652) 2024-04-11 17:24:19 -03:00
gdkchan
2ddd3dd4a7 Allow BSD sockets Poll to exit when emulation ends (#6650) 2024-04-11 14:56:21 +02:00
gdkchan
a8f7ababb5 Revert "Update StoreConstantToMemory to match StoreConstantToAddress on value…" (#6649)
This reverts commit 22e3ff06b5.
2024-04-10 21:50:06 -03:00
WilliamWsyHK
22e3ff06b5 Update StoreConstantToMemory to match StoreConstantToAddress on value read (#6642) 2024-04-11 00:03:37 +02:00
Isaac Marovitz
9480e5c5ce Ava UI: Prevent Status Bar Backend Update (#6506)
* Prevent Status Bar Backend Update

* Make it a switch
2024-04-10 23:40:17 +02:00
dependabot[bot]
0652813b0f nuget: bump Microsoft.IdentityModel.JsonWebTokens from 7.4.0 to 7.5.1 (#6627)
Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 7.4.0 to 7.5.1.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/v7.4.0...7.5.1)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.JsonWebTokens
  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-10 23:30:17 +02:00
gdkchan
e7f2342eba Fix input consumed by audio renderer SplitterState.Update (#6640)
* Fix input consumed by audio renderer SplitterState.Update

* Use sizeof(int) to make clear what the value is
2024-04-10 12:07:31 -03:00
gdkchan
543d75a587 CPU: Produce non-inf results for RSQRTE instruction with subnormal inputs (#6634)
* CPU: Produce non-inf results for RSQRTE instruction with subnormal inputs

* PPTC version bump
2024-04-09 14:34:14 -03:00
gdkchan
338ff79e1e Use ResScaleUnsupported flag for texture arrays (#6626) 2024-04-09 14:24:46 -03:00
gdkchan
80201466ae Fast D32S8 2D depth texture copy (#6636) 2024-04-09 14:03:55 -03:00
gdkchan
7a971edb57 Pin audio renderer update output buffers (#6633) 2024-04-08 20:26:47 -03:00
MutantAura
c1b0ab805a Disable CLI setting persistence for HW-accelerated GUI. (#6620) 2024-04-07 20:58:05 -03:00
gdkchan
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
gdkchan
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
166 changed files with 7218 additions and 2585 deletions

View File

@@ -20,7 +20,7 @@
<PackageVersion Include="LibHac" Version="0.19.0" /> <PackageVersion Include="LibHac" Version="0.19.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" /> <PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.4.0" /> <PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.5.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" /> <PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" /> <PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
@@ -42,7 +42,7 @@
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.7" /> <PackageVersion Include="SixLabors.ImageSharp" Version="2.1.8" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" /> <PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
<PackageVersion Include="SPB" Version="0.0.4-build32" /> <PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" /> <PackageVersion Include="System.IO.Hashing" Version="8.0.0" />

View File

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

View File

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

View File

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

View File

@@ -159,6 +159,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
} }
} }
if (destinationCount < parameter.DestinationCount)
{
input.Advance((parameter.DestinationCount - destinationCount) * sizeof(int));
}
Debug.Assert(parameter.Id == Id); Debug.Assert(parameter.Id == Id);
if (parameter.Id == Id) if (parameter.Id == Id)

View File

@@ -1,7 +1,5 @@
namespace Ryujinx.Common.Configuration.Hid namespace Ryujinx.Common.Configuration.Hid
{ {
// NOTE: Please don't change this to struct.
// This breaks Avalonia's TwoWay binding, which makes us unable to save new KeyboardHotkeys.
public class KeyboardHotkeys public class KeyboardHotkeys
{ {
public Key ToggleVsync { get; set; } public Key ToggleVsync { get; set; }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
@@ -6,6 +5,7 @@ using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking; using Ryujinx.Memory.Tracking;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -445,7 +445,7 @@ namespace Ryujinx.Graphics.Gpu.Image
ReadOnlySpan<byte> data = dataSpan[(offset - spanBase)..]; ReadOnlySpan<byte> data = dataSpan[(offset - spanBase)..];
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true); IMemoryOwner<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level); Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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)

View File

@@ -0,0 +1,236 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
static class BindlessToArray
{
private const int NvnTextureBufferIndex = 2;
private const int HardcodedArrayLengthOgl = 4;
// 1 and 0 elements are not considered arrays anymore.
private const int MinimumArrayLength = 2;
public static void RunPassOgl(BasicBlock block, ResourceManager resourceManager)
{
// We can turn a bindless texture access into a indexed access,
// as long the following conditions are true:
// - The handle is loaded using a LDC instruction.
// - The handle is loaded from the constant buffer with the handles (CB2 for NVN).
// - The load has a constant offset.
// The base offset of the array of handles on the constant buffer is the constant offset.
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
{
if (node.Value is not TextureOperation texOp)
{
continue;
}
if ((texOp.Flags & TextureFlags.Bindless) == 0)
{
continue;
}
if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp)
{
continue;
}
if (handleAsgOp.Inst != Instruction.Load ||
handleAsgOp.StorageKind != StorageKind.ConstantBuffer ||
handleAsgOp.SourcesCount != 4)
{
continue;
}
Operand ldcSrc0 = handleAsgOp.GetSource(0);
if (ldcSrc0.Type != OperandType.Constant ||
!resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) ||
src0CbufSlot != NvnTextureBufferIndex)
{
continue;
}
Operand ldcSrc1 = handleAsgOp.GetSource(1);
// We expect field index 0 to be accessed.
if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0)
{
continue;
}
Operand ldcSrc2 = handleAsgOp.GetSource(2);
// FIXME: This is missing some checks, for example, a check to ensure that the shift value is 2.
// Might be not worth fixing since if that doesn't kick in, the result will be no texture
// to access anyway which is also wrong.
// Plus this whole transform is fundamentally flawed as-is since we have no way to know the array size.
// Eventually, this should be entirely removed in favor of a implementation that supports true bindless
// texture access.
if (ldcSrc2.AsgOp is not Operation shrOp || shrOp.Inst != Instruction.ShiftRightU32)
{
continue;
}
if (shrOp.GetSource(0).AsgOp is not Operation shrOp2 || shrOp2.Inst != Instruction.ShiftRightU32)
{
continue;
}
if (shrOp2.GetSource(0).AsgOp is not Operation addOp || addOp.Inst != Instruction.Add)
{
continue;
}
Operand addSrc1 = addOp.GetSource(1);
if (addSrc1.Type != OperandType.Constant)
{
continue;
}
TurnIntoArray(resourceManager, texOp, NvnTextureBufferIndex, addSrc1.Value / 4, HardcodedArrayLengthOgl);
Operand index = Local();
Operand source = addOp.GetSource(0);
Operation shrBy3 = new(Instruction.ShiftRightU32, index, source, Const(3));
block.Operations.AddBefore(node, shrBy3);
texOp.SetSource(0, index);
}
}
public static void RunPass(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
{
// We can turn a bindless texture access into a indexed access,
// as long the following conditions are true:
// - The handle is loaded using a LDC instruction.
// - The handle is loaded from the constant buffer with the handles (CB2 for NVN).
// - The load has a constant offset.
// The base offset of the array of handles on the constant buffer is the constant offset.
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
{
if (node.Value is not TextureOperation texOp)
{
continue;
}
if ((texOp.Flags & TextureFlags.Bindless) == 0)
{
continue;
}
if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp)
{
continue;
}
int secondaryCbufSlot = 0;
int secondaryCbufOffset = 0;
bool hasSecondaryHandle = false;
if (handleAsgOp.Inst == Instruction.BitwiseOr)
{
Operand src0 = handleAsgOp.GetSource(0);
Operand src1 = handleAsgOp.GetSource(1);
if (src0.Type == OperandType.ConstantBuffer && src1.AsgOp is Operation)
{
handleAsgOp = src1.AsgOp as Operation;
secondaryCbufSlot = src0.GetCbufSlot();
secondaryCbufOffset = src0.GetCbufOffset();
hasSecondaryHandle = true;
}
else if (src0.AsgOp is Operation && src1.Type == OperandType.ConstantBuffer)
{
handleAsgOp = src0.AsgOp as Operation;
secondaryCbufSlot = src1.GetCbufSlot();
secondaryCbufOffset = src1.GetCbufOffset();
hasSecondaryHandle = true;
}
}
if (handleAsgOp.Inst != Instruction.Load ||
handleAsgOp.StorageKind != StorageKind.ConstantBuffer ||
handleAsgOp.SourcesCount != 4)
{
continue;
}
Operand ldcSrc0 = handleAsgOp.GetSource(0);
if (ldcSrc0.Type != OperandType.Constant ||
!resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot))
{
continue;
}
Operand ldcSrc1 = handleAsgOp.GetSource(1);
// We expect field index 0 to be accessed.
if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0)
{
continue;
}
Operand ldcVecIndex = handleAsgOp.GetSource(2);
Operand ldcElemIndex = handleAsgOp.GetSource(3);
if (ldcVecIndex.Type != OperandType.LocalVariable || ldcElemIndex.Type != OperandType.LocalVariable)
{
continue;
}
int cbufSlot;
int handleIndex;
if (hasSecondaryHandle)
{
cbufSlot = TextureHandle.PackSlots(src0CbufSlot, secondaryCbufSlot);
handleIndex = TextureHandle.PackOffsets(0, secondaryCbufOffset, TextureHandleType.SeparateSamplerHandle);
}
else
{
cbufSlot = src0CbufSlot;
handleIndex = 0;
}
int length = Math.Max(MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromBuffer(src0CbufSlot));
TurnIntoArray(resourceManager, texOp, cbufSlot, handleIndex, length);
Operand vecIndex = Local();
Operand elemIndex = Local();
Operand index = Local();
Operand indexMin = Local();
block.Operations.AddBefore(node, new Operation(Instruction.ShiftLeft, vecIndex, ldcVecIndex, Const(1)));
block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, elemIndex, ldcElemIndex, Const(1)));
block.Operations.AddBefore(node, new Operation(Instruction.Add, index, vecIndex, elemIndex));
block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, indexMin, index, Const(length - 1)));
texOp.SetSource(0, indexMin);
}
}
private static void TurnIntoArray(ResourceManager resourceManager, TextureOperation texOp, int cbufSlot, int handleIndex, int length)
{
int binding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type,
texOp.Format,
texOp.Flags & ~TextureFlags.Bindless,
cbufSlot,
handleIndex,
length);
texOp.TurnIntoArray(binding);
}
}
}

View File

@@ -1,118 +0,0 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
static class BindlessToIndexed
{
private const int NvnTextureBufferIndex = 2;
public static void RunPass(BasicBlock block, ResourceManager resourceManager)
{
// We can turn a bindless texture access into a indexed access,
// as long the following conditions are true:
// - The handle is loaded using a LDC instruction.
// - The handle is loaded from the constant buffer with the handles (CB2 for NVN).
// - The load has a constant offset.
// The base offset of the array of handles on the constant buffer is the constant offset.
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
{
if (node.Value is not TextureOperation texOp)
{
continue;
}
if ((texOp.Flags & TextureFlags.Bindless) == 0)
{
continue;
}
if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp)
{
continue;
}
if (handleAsgOp.Inst != Instruction.Load ||
handleAsgOp.StorageKind != StorageKind.ConstantBuffer ||
handleAsgOp.SourcesCount != 4)
{
continue;
}
Operand ldcSrc0 = handleAsgOp.GetSource(0);
if (ldcSrc0.Type != OperandType.Constant ||
!resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) ||
src0CbufSlot != NvnTextureBufferIndex)
{
continue;
}
Operand ldcSrc1 = handleAsgOp.GetSource(1);
// We expect field index 0 to be accessed.
if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0)
{
continue;
}
Operand ldcSrc2 = handleAsgOp.GetSource(2);
// FIXME: This is missing some checks, for example, a check to ensure that the shift value is 2.
// Might be not worth fixing since if that doesn't kick in, the result will be no texture
// to access anyway which is also wrong.
// Plus this whole transform is fundamentally flawed as-is since we have no way to know the array size.
// Eventually, this should be entirely removed in favor of a implementation that supports true bindless
// texture access.
if (ldcSrc2.AsgOp is not Operation shrOp || shrOp.Inst != Instruction.ShiftRightU32)
{
continue;
}
if (shrOp.GetSource(0).AsgOp is not Operation shrOp2 || shrOp2.Inst != Instruction.ShiftRightU32)
{
continue;
}
if (shrOp2.GetSource(0).AsgOp is not Operation addOp || addOp.Inst != Instruction.Add)
{
continue;
}
Operand addSrc1 = addOp.GetSource(1);
if (addSrc1.Type != OperandType.Constant)
{
continue;
}
TurnIntoIndexed(resourceManager, texOp, addSrc1.Value / 4);
Operand index = Local();
Operand source = addOp.GetSource(0);
Operation shrBy3 = new(Instruction.ShiftRightU32, index, source, Const(3));
block.Operations.AddBefore(node, shrBy3);
texOp.SetSource(0, index);
}
}
private static void TurnIntoIndexed(ResourceManager resourceManager, TextureOperation texOp, int handle)
{
int binding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type | SamplerType.Indexed,
texOp.Format,
texOp.Flags & ~TextureFlags.Bindless,
NvnTextureBufferIndex,
handle);
texOp.TurnIntoIndexed(binding);
}
}
}

View File

@@ -20,7 +20,15 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// Those passes are looking for specific patterns and only needs to run once. // Those passes are looking for specific patterns and only needs to run once.
for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++) for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++)
{ {
BindlessToIndexed.RunPass(context.Blocks[blkIndex], context.ResourceManager); if (context.TargetApi == TargetApi.OpenGL)
{
BindlessToArray.RunPassOgl(context.Blocks[blkIndex], context.ResourceManager);
}
else
{
BindlessToArray.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor);
}
BindlessElimination.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor); BindlessElimination.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor);
// FragmentCoord only exists on fragment shaders, so we don't need to check other stages. // FragmentCoord only exists on fragment shaders, so we don't need to check other stages.

View File

@@ -14,9 +14,6 @@ namespace Ryujinx.Graphics.Shader.Translation
private const int DefaultLocalMemorySize = 128; private const int DefaultLocalMemorySize = 128;
private const int DefaultSharedMemorySize = 4096; private const int DefaultSharedMemorySize = 4096;
// TODO: Non-hardcoded array size.
public const int SamplerArraySize = 4;
private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" }; private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
private readonly IGpuAccessor _gpuAccessor; private readonly IGpuAccessor _gpuAccessor;
@@ -32,7 +29,7 @@ namespace Ryujinx.Graphics.Shader.Translation
private readonly HashSet<int> _usedConstantBufferBindings; private readonly HashSet<int> _usedConstantBufferBindings;
private readonly record struct TextureInfo(int CbufSlot, int Handle, bool Indexed, TextureFormat Format); private readonly record struct TextureInfo(int CbufSlot, int Handle, int ArrayLength, SamplerType Type, TextureFormat Format);
private struct TextureMeta private struct TextureMeta
{ {
@@ -152,7 +149,7 @@ namespace Ryujinx.Graphics.Shader.Translation
int binding = _cbSlotToBindingMap[slot]; int binding = _cbSlotToBindingMap[slot];
if (binding < 0) if (binding < 0)
{ {
binding = _gpuAccessor.QueryBindingConstantBuffer(slot); binding = _gpuAccessor.CreateConstantBufferBinding(slot);
_cbSlotToBindingMap[slot] = binding; _cbSlotToBindingMap[slot] = binding;
string slotNumber = slot.ToString(CultureInfo.InvariantCulture); string slotNumber = slot.ToString(CultureInfo.InvariantCulture);
AddNewConstantBuffer(binding, $"{_stagePrefix}_c{slotNumber}"); AddNewConstantBuffer(binding, $"{_stagePrefix}_c{slotNumber}");
@@ -173,7 +170,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (binding < 0) if (binding < 0)
{ {
binding = _gpuAccessor.QueryBindingStorageBuffer(slot); binding = _gpuAccessor.CreateStorageBufferBinding(slot);
_sbSlotToBindingMap[slot] = binding; _sbSlotToBindingMap[slot] = binding;
string slotNumber = slot.ToString(CultureInfo.InvariantCulture); string slotNumber = slot.ToString(CultureInfo.InvariantCulture);
AddNewStorageBuffer(binding, $"{_stagePrefix}_s{slotNumber}"); AddNewStorageBuffer(binding, $"{_stagePrefix}_s{slotNumber}");
@@ -227,11 +224,12 @@ namespace Ryujinx.Graphics.Shader.Translation
TextureFormat format, TextureFormat format,
TextureFlags flags, TextureFlags flags,
int cbufSlot, int cbufSlot,
int handle) int handle,
int arrayLength = 1)
{ {
inst &= Instruction.Mask; inst &= Instruction.Mask;
bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore || inst == Instruction.ImageAtomic; bool isImage = inst.IsImage();
bool isWrite = inst == Instruction.ImageStore || inst == Instruction.ImageAtomic; bool isWrite = inst.IsImageStore();
bool accurateType = !inst.IsTextureQuery(); bool accurateType = !inst.IsTextureQuery();
bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureQuerySize; bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureQuerySize;
bool coherent = flags.HasFlag(TextureFlags.Coherent); bool coherent = flags.HasFlag(TextureFlags.Coherent);
@@ -241,7 +239,7 @@ namespace Ryujinx.Graphics.Shader.Translation
format = TextureFormat.Unknown; format = TextureFormat.Unknown;
} }
int binding = GetTextureOrImageBinding(cbufSlot, handle, type, format, isImage, intCoords, isWrite, accurateType, coherent); int binding = GetTextureOrImageBinding(cbufSlot, handle, arrayLength, type, format, isImage, intCoords, isWrite, accurateType, coherent);
_gpuAccessor.RegisterTexture(handle, cbufSlot); _gpuAccessor.RegisterTexture(handle, cbufSlot);
@@ -251,6 +249,7 @@ namespace Ryujinx.Graphics.Shader.Translation
private int GetTextureOrImageBinding( private int GetTextureOrImageBinding(
int cbufSlot, int cbufSlot,
int handle, int handle,
int arrayLength,
SamplerType type, SamplerType type,
TextureFormat format, TextureFormat format,
bool isImage, bool isImage,
@@ -260,7 +259,6 @@ namespace Ryujinx.Graphics.Shader.Translation
bool coherent) bool coherent)
{ {
var dimensions = type.GetDimensions(); var dimensions = type.GetDimensions();
var isIndexed = type.HasFlag(SamplerType.Indexed);
var dict = isImage ? _usedImages : _usedTextures; var dict = isImage ? _usedImages : _usedTextures;
var usageFlags = TextureUsageFlags.None; var usageFlags = TextureUsageFlags.None;
@@ -269,7 +267,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
usageFlags |= TextureUsageFlags.NeedsScaleValue; usageFlags |= TextureUsageFlags.NeedsScaleValue;
var canScale = _stage.SupportsRenderScale() && !isIndexed && !write && dimensions == 2; var canScale = _stage.SupportsRenderScale() && arrayLength == 1 && !write && dimensions == 2;
if (!canScale) if (!canScale)
{ {
@@ -289,76 +287,75 @@ namespace Ryujinx.Graphics.Shader.Translation
usageFlags |= TextureUsageFlags.ImageCoherent; usageFlags |= TextureUsageFlags.ImageCoherent;
} }
int arraySize = isIndexed ? SamplerArraySize : 1; // For array textures, we also want to use type as key,
int firstBinding = -1; // since we may have texture handles stores in the same buffer, but for textures with different types.
var keyType = arrayLength > 1 ? type : SamplerType.None;
for (int layer = 0; layer < arraySize; layer++) var info = new TextureInfo(cbufSlot, handle, arrayLength, keyType, format);
var meta = new TextureMeta()
{ {
var info = new TextureInfo(cbufSlot, handle + layer * 2, isIndexed, format); AccurateType = accurateType,
var meta = new TextureMeta() Type = type,
{ UsageFlags = usageFlags,
AccurateType = accurateType, };
Type = type,
UsageFlags = usageFlags,
};
int binding; int binding;
if (dict.TryGetValue(info, out var existingMeta)) if (dict.TryGetValue(info, out var existingMeta))
{ {
dict[info] = MergeTextureMeta(meta, existingMeta); dict[info] = MergeTextureMeta(meta, existingMeta);
binding = existingMeta.Binding; binding = existingMeta.Binding;
} }
else else
{ {
bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer; bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer;
binding = isImage binding = isImage
? _gpuAccessor.QueryBindingImage(dict.Count, isBuffer) ? _gpuAccessor.CreateImageBinding(arrayLength, isBuffer)
: _gpuAccessor.QueryBindingTexture(dict.Count, isBuffer); : _gpuAccessor.CreateTextureBinding(arrayLength, isBuffer);
meta.Binding = binding; meta.Binding = binding;
dict.Add(info, meta); dict.Add(info, meta);
}
string nameSuffix;
if (isImage)
{
nameSuffix = cbufSlot < 0
? $"i_tcb_{handle:X}_{format.ToGlslFormat()}"
: $"i_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}";
}
else
{
nameSuffix = cbufSlot < 0 ? $"t_tcb_{handle:X}" : $"t_cb{cbufSlot}_{handle:X}";
}
var definition = new TextureDefinition(
isImage ? 3 : 2,
binding,
$"{_stagePrefix}_{nameSuffix}",
meta.Type,
info.Format,
meta.UsageFlags);
if (isImage)
{
Properties.AddOrUpdateImage(definition);
}
else
{
Properties.AddOrUpdateTexture(definition);
}
if (layer == 0)
{
firstBinding = binding;
}
} }
return firstBinding; string nameSuffix;
string prefix = isImage ? "i" : "t";
if (arrayLength != 1 && type != SamplerType.None)
{
prefix += type.ToShortSamplerType();
}
if (isImage)
{
nameSuffix = cbufSlot < 0
? $"{prefix}_tcb_{handle:X}_{format.ToGlslFormat()}"
: $"{prefix}_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}";
}
else
{
nameSuffix = cbufSlot < 0 ? $"{prefix}_tcb_{handle:X}" : $"{prefix}_cb{cbufSlot}_{handle:X}";
}
var definition = new TextureDefinition(
isImage ? 3 : 2,
binding,
arrayLength,
$"{_stagePrefix}_{nameSuffix}",
meta.Type,
info.Format,
meta.UsageFlags);
if (isImage)
{
Properties.AddOrUpdateImage(definition);
}
else
{
Properties.AddOrUpdateTexture(definition);
}
return binding;
} }
private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta) private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta)
@@ -399,8 +396,7 @@ namespace Ryujinx.Graphics.Shader.Translation
selectedMeta.UsageFlags |= TextureUsageFlags.NeedsScaleValue; selectedMeta.UsageFlags |= TextureUsageFlags.NeedsScaleValue;
var dimensions = type.GetDimensions(); var dimensions = type.GetDimensions();
var isIndexed = type.HasFlag(SamplerType.Indexed); var canScale = _stage.SupportsRenderScale() && selectedInfo.ArrayLength == 1 && dimensions == 2;
var canScale = _stage.SupportsRenderScale() && !isIndexed && dimensions == 2;
if (!canScale) if (!canScale)
{ {
@@ -468,34 +464,61 @@ namespace Ryujinx.Graphics.Shader.Translation
return descriptors; return descriptors;
} }
public TextureDescriptor[] GetTextureDescriptors() public TextureDescriptor[] GetTextureDescriptors(bool includeArrays = true)
{ {
return GetDescriptors(_usedTextures, _usedTextures.Count); return GetDescriptors(_usedTextures, includeArrays);
} }
public TextureDescriptor[] GetImageDescriptors() public TextureDescriptor[] GetImageDescriptors(bool includeArrays = true)
{ {
return GetDescriptors(_usedImages, _usedImages.Count); return GetDescriptors(_usedImages, includeArrays);
} }
private static TextureDescriptor[] GetDescriptors(IReadOnlyDictionary<TextureInfo, TextureMeta> usedResources, int count) private static TextureDescriptor[] GetDescriptors(IReadOnlyDictionary<TextureInfo, TextureMeta> usedResources, bool includeArrays)
{ {
TextureDescriptor[] descriptors = new TextureDescriptor[count]; List<TextureDescriptor> descriptors = new();
int descriptorIndex = 0; bool hasAnyArray = false;
foreach ((TextureInfo info, TextureMeta meta) in usedResources) foreach ((TextureInfo info, TextureMeta meta) in usedResources)
{ {
descriptors[descriptorIndex++] = new TextureDescriptor( if (info.ArrayLength > 1)
{
hasAnyArray = true;
continue;
}
descriptors.Add(new TextureDescriptor(
meta.Binding, meta.Binding,
meta.Type, meta.Type,
info.Format, info.Format,
info.CbufSlot, info.CbufSlot,
info.Handle, info.Handle,
meta.UsageFlags); info.ArrayLength,
meta.UsageFlags));
} }
return descriptors; if (hasAnyArray && includeArrays)
{
foreach ((TextureInfo info, TextureMeta meta) in usedResources)
{
if (info.ArrayLength <= 1)
{
continue;
}
descriptors.Add(new TextureDescriptor(
meta.Binding,
meta.Type,
info.Format,
info.CbufSlot,
info.Handle,
info.ArrayLength,
meta.UsageFlags));
}
}
return descriptors.ToArray();
} }
public bool TryGetCbufSlotAndHandleForTexture(int binding, out int cbufSlot, out int handle) public bool TryGetCbufSlotAndHandleForTexture(int binding, out int cbufSlot, out int handle)
@@ -531,6 +554,19 @@ namespace Ryujinx.Graphics.Shader.Translation
return FindDescriptorIndex(GetImageDescriptors(), binding); return FindDescriptorIndex(GetImageDescriptors(), binding);
} }
public bool IsArrayOfTexturesOrImages(int binding, bool isImage)
{
foreach ((TextureInfo info, TextureMeta meta) in isImage ? _usedImages : _usedTextures)
{
if (meta.Binding == binding)
{
return info.ArrayLength != 1;
}
}
return false;
}
private void AddNewConstantBuffer(int binding, string name) private void AddNewConstantBuffer(int binding, string name)
{ {
StructureType type = new(new[] StructureType type = new(new[]

View File

@@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public readonly ShaderDefinitions Definitions; public readonly ShaderDefinitions Definitions;
public readonly ResourceManager ResourceManager; public readonly ResourceManager ResourceManager;
public readonly IGpuAccessor GpuAccessor; public readonly IGpuAccessor GpuAccessor;
public readonly TargetApi TargetApi;
public readonly TargetLanguage TargetLanguage; public readonly TargetLanguage TargetLanguage;
public readonly ShaderStage Stage; public readonly ShaderStage Stage;
public readonly ref FeatureFlags UsedFeatures; public readonly ref FeatureFlags UsedFeatures;
@@ -19,6 +20,7 @@ namespace Ryujinx.Graphics.Shader.Translation
ShaderDefinitions definitions, ShaderDefinitions definitions,
ResourceManager resourceManager, ResourceManager resourceManager,
IGpuAccessor gpuAccessor, IGpuAccessor gpuAccessor,
TargetApi targetApi,
TargetLanguage targetLanguage, TargetLanguage targetLanguage,
ShaderStage stage, ShaderStage stage,
ref FeatureFlags usedFeatures) ref FeatureFlags usedFeatures)
@@ -28,6 +30,7 @@ namespace Ryujinx.Graphics.Shader.Translation
Definitions = definitions; Definitions = definitions;
ResourceManager = resourceManager; ResourceManager = resourceManager;
GpuAccessor = gpuAccessor; GpuAccessor = gpuAccessor;
TargetApi = targetApi;
TargetLanguage = targetLanguage; TargetLanguage = targetLanguage;
Stage = stage; Stage = stage;
UsedFeatures = ref usedFeatures; UsedFeatures = ref usedFeatures;

View File

@@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
{ {
node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage); node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage);
node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor); node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor);
node = InsertConstOffsets(node, context.GpuAccessor, context.Stage); node = InsertConstOffsets(node, context.ResourceManager, context.GpuAccessor, context.Stage);
if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat()) if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat())
{ {
@@ -45,13 +45,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = isBindless || isIndexed ? 1 : 0;
bool isImage = IsImageInstructionWithScale(texOp.Inst); bool isImage = IsImageInstructionWithScale(texOp.Inst);
bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage);
if ((texOp.Inst == Instruction.TextureSample || isImage) && if ((texOp.Inst == Instruction.TextureSample || isImage) &&
(intCoords || isImage) && (intCoords || isImage) &&
@@ -62,9 +58,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
{ {
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale); int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale);
int samplerIndex = isImage int samplerIndex = isImage
? resourceManager.GetTextureDescriptors().Length + resourceManager.FindImageDescriptorIndex(texOp.Binding) ? resourceManager.GetTextureDescriptors(includeArrays: false).Length + resourceManager.FindImageDescriptorIndex(texOp.Binding)
: resourceManager.FindTextureDescriptorIndex(texOp.Binding); : resourceManager.FindTextureDescriptorIndex(texOp.Binding);
int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = isBindless ? 1 : 0;
for (int index = 0; index < coordsCount; index++) for (int index = 0; index < coordsCount; index++)
{ {
Operand scaledCoord = Local(); Operand scaledCoord = Local();
@@ -97,7 +96,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
TextureOperation texOp = (TextureOperation)node.Value; TextureOperation texOp = (TextureOperation)node.Value;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false);
if (texOp.Inst == Instruction.TextureQuerySize && if (texOp.Inst == Instruction.TextureQuerySize &&
texOp.Index < 2 && texOp.Index < 2 &&
@@ -152,8 +151,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
TextureOperation texOp = (TextureOperation)node.Value; TextureOperation texOp = (TextureOperation)node.Value;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false);
if (isBindless || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle)) if (isBindless || isIndexed || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle))
{ {
return node; return node;
} }
@@ -167,10 +167,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
return node; return node;
} }
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = isBindless || isIndexed ? 1 : 0;
int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount; int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
@@ -178,16 +175,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
{ {
Operand coordSize = Local(); Operand coordSize = Local();
Operand[] texSizeSources; Operand[] texSizeSources = new Operand[] { Const(0) };
if (isBindless || isIndexed)
{
texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) };
}
else
{
texSizeSources = new Operand[] { Const(0) };
}
LinkedListNode<INode> textureSizeNode = node.List.AddBefore(node, new TextureOperation( LinkedListNode<INode> textureSizeNode = node.List.AddBefore(node, new TextureOperation(
Instruction.TextureQuerySize, Instruction.TextureQuerySize,
@@ -201,13 +189,13 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
resourceManager.SetUsageFlagsForTextureQuery(texOp.Binding, texOp.Type); resourceManager.SetUsageFlagsForTextureQuery(texOp.Binding, texOp.Type);
Operand source = texOp.GetSource(coordsIndex + index); Operand source = texOp.GetSource(index);
Operand coordNormalized = Local(); Operand coordNormalized = Local();
node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, GenerateI2f(node, coordSize))); node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, GenerateI2f(node, coordSize)));
texOp.SetSource(coordsIndex + index, coordNormalized); texOp.SetSource(index, coordNormalized);
InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage); InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage);
} }
@@ -234,7 +222,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
return node; return node;
} }
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false);
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = isBindless || isIndexed ? 1 : 0; int coordsIndex = isBindless || isIndexed ? 1 : 0;
@@ -287,7 +275,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
return node; return node;
} }
private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, IGpuAccessor gpuAccessor, ShaderStage stage) private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor, ShaderStage stage)
{ {
// Non-constant texture offsets are not allowed (according to the spec), // Non-constant texture offsets are not allowed (according to the spec),
// however some GPUs does support that. // however some GPUs does support that.
@@ -321,7 +309,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 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;
@@ -342,6 +329,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
offsetsCount = 0; offsetsCount = 0;
} }
bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false);
Operand[] offsets = new Operand[offsetsCount]; Operand[] offsets = new Operand[offsetsCount];
Operand[] sources = new Operand[texOp.SourcesCount - offsetsCount]; Operand[] sources = new Operand[texOp.SourcesCount - offsetsCount];

View File

@@ -294,6 +294,7 @@ namespace Ryujinx.Graphics.Shader.Translation
Definitions, Definitions,
resourceManager, resourceManager,
GpuAccessor, GpuAccessor,
Options.TargetApi,
Options.TargetLanguage, Options.TargetLanguage,
Definitions.Stage, Definitions.Stage,
ref usedFeatures); ref usedFeatures);
@@ -412,7 +413,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (Stage == ShaderStage.Vertex) if (Stage == ShaderStage.Vertex)
{ {
int ibBinding = resourceManager.Reservations.IndexBufferTextureBinding; int ibBinding = resourceManager.Reservations.IndexBufferTextureBinding;
TextureDefinition indexBuffer = new(2, ibBinding, "ib_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); TextureDefinition indexBuffer = new(2, ibBinding, 1, "ib_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None);
resourceManager.Properties.AddOrUpdateTexture(indexBuffer); resourceManager.Properties.AddOrUpdateTexture(indexBuffer);
int inputMap = _program.AttributeUsage.UsedInputAttributes; int inputMap = _program.AttributeUsage.UsedInputAttributes;
@@ -421,7 +422,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
int location = BitOperations.TrailingZeroCount(inputMap); int location = BitOperations.TrailingZeroCount(inputMap);
int binding = resourceManager.Reservations.GetVertexBufferTextureBinding(location); int binding = resourceManager.Reservations.GetVertexBufferTextureBinding(location);
TextureDefinition vaBuffer = new(2, binding, $"vb_data{location}", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); TextureDefinition vaBuffer = new(2, binding, 1, $"vb_data{location}", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None);
resourceManager.Properties.AddOrUpdateTexture(vaBuffer); resourceManager.Properties.AddOrUpdateTexture(vaBuffer);
inputMap &= ~(1 << location); inputMap &= ~(1 << location);
@@ -430,7 +431,7 @@ namespace Ryujinx.Graphics.Shader.Translation
else if (Stage == ShaderStage.Geometry) else if (Stage == ShaderStage.Geometry)
{ {
int trbBinding = resourceManager.Reservations.TopologyRemapBufferTextureBinding; int trbBinding = resourceManager.Reservations.TopologyRemapBufferTextureBinding;
TextureDefinition remapBuffer = new(2, trbBinding, "trb_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); TextureDefinition remapBuffer = new(2, trbBinding, 1, "trb_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None);
resourceManager.Properties.AddOrUpdateTexture(remapBuffer); resourceManager.Properties.AddOrUpdateTexture(remapBuffer);
int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding; int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding;

View File

@@ -1,5 +1,7 @@
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using System; using System;
using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -291,16 +293,14 @@ namespace Ryujinx.Graphics.Texture.Astc
int depth, int depth,
int levels, int levels,
int layers, int layers,
out byte[] decoded) out IMemoryOwner<byte> decoded)
{ {
byte[] output = new byte[QueryDecompressedSize(width, height, depth, levels, layers)]; decoded = ByteMemoryPool.Rent(QueryDecompressedSize(width, height, depth, levels, layers));
AstcDecoder decoder = new(data, output, blockWidth, blockHeight, width, height, depth, levels, layers); AstcDecoder decoder = new(data, decoded.Memory, blockWidth, blockHeight, width, height, depth, levels, layers);
Enumerable.Range(0, decoder.TotalBlockCount).AsParallel().ForAll(x => decoder.ProcessBlock(x)); Enumerable.Range(0, decoder.TotalBlockCount).AsParallel().ForAll(x => decoder.ProcessBlock(x));
decoded = output;
return decoder.Success; return decoder.Success;
} }

View File

@@ -1,5 +1,7 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
@@ -12,7 +14,7 @@ namespace Ryujinx.Graphics.Texture
private const int BlockWidth = 4; private const int BlockWidth = 4;
private const int BlockHeight = 4; private const int BlockHeight = 4;
public static byte[] DecodeBC1(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers) public static IMemoryOwner<byte> DecodeBC1(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{ {
int size = 0; int size = 0;
@@ -21,12 +23,12 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
} }
byte[] output = new byte[size]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4]; Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4];
Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile); Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile);
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output); Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile); Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile);
@@ -100,7 +102,7 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public static byte[] DecodeBC2(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers) public static IMemoryOwner<byte> DecodeBC2(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{ {
int size = 0; int size = 0;
@@ -109,12 +111,12 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
} }
byte[] output = new byte[size]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4]; Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4];
Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile); Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile);
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output); Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile); Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile);
@@ -195,7 +197,7 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public static byte[] DecodeBC3(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers) public static IMemoryOwner<byte> DecodeBC3(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{ {
int size = 0; int size = 0;
@@ -204,13 +206,13 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
} }
byte[] output = new byte[size]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4]; Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4];
Span<byte> rPal = stackalloc byte[8]; Span<byte> rPal = stackalloc byte[8];
Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile); Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile);
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output); Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile); Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile);
@@ -292,7 +294,7 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public static byte[] DecodeBC4(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed) public static IMemoryOwner<byte> DecodeBC4(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
{ {
int size = 0; int size = 0;
@@ -304,8 +306,8 @@ namespace Ryujinx.Graphics.Texture
// Backends currently expect a stride alignment of 4 bytes, so output width must be aligned. // Backends currently expect a stride alignment of 4 bytes, so output width must be aligned.
int alignedWidth = BitUtils.AlignUp(width, 4); int alignedWidth = BitUtils.AlignUp(width, 4);
byte[] output = new byte[size]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Span<byte> outputSpan = new(output); Span<byte> outputSpan = output.Memory.Span;
ReadOnlySpan<ulong> data64 = MemoryMarshal.Cast<byte, ulong>(data); ReadOnlySpan<ulong> data64 = MemoryMarshal.Cast<byte, ulong>(data);
@@ -400,7 +402,7 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public static byte[] DecodeBC5(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed) public static IMemoryOwner<byte> DecodeBC5(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
{ {
int size = 0; int size = 0;
@@ -412,7 +414,7 @@ namespace Ryujinx.Graphics.Texture
// Backends currently expect a stride alignment of 4 bytes, so output width must be aligned. // Backends currently expect a stride alignment of 4 bytes, so output width must be aligned.
int alignedWidth = BitUtils.AlignUp(width, 2); int alignedWidth = BitUtils.AlignUp(width, 2);
byte[] output = new byte[size]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
ReadOnlySpan<ulong> data64 = MemoryMarshal.Cast<byte, ulong>(data); ReadOnlySpan<ulong> data64 = MemoryMarshal.Cast<byte, ulong>(data);
@@ -421,7 +423,7 @@ namespace Ryujinx.Graphics.Texture
Span<byte> rPal = stackalloc byte[8]; Span<byte> rPal = stackalloc byte[8];
Span<byte> gPal = stackalloc byte[8]; Span<byte> gPal = stackalloc byte[8];
Span<ushort> outputAsUshort = MemoryMarshal.Cast<byte, ushort>(output); Span<ushort> outputAsUshort = MemoryMarshal.Cast<byte, ushort>(output.Memory.Span);
Span<uint> rTileAsUint = MemoryMarshal.Cast<byte, uint>(rTile); Span<uint> rTileAsUint = MemoryMarshal.Cast<byte, uint>(rTile);
Span<uint> gTileAsUint = MemoryMarshal.Cast<byte, uint>(gTile); Span<uint> gTileAsUint = MemoryMarshal.Cast<byte, uint>(gTile);
@@ -525,7 +527,7 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public static byte[] DecodeBC6(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed) public static IMemoryOwner<byte> DecodeBC6(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
{ {
int size = 0; int size = 0;
@@ -534,7 +536,8 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 8; size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 8;
} }
byte[] output = new byte[size]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Span<byte> outputSpan = output.Memory.Span;
int inputOffset = 0; int inputOffset = 0;
int outputOffset = 0; int outputOffset = 0;
@@ -548,7 +551,7 @@ namespace Ryujinx.Graphics.Texture
{ {
for (int z = 0; z < depth; z++) for (int z = 0; z < depth; z++)
{ {
BC6Decoder.Decode(output.AsSpan()[outputOffset..], data[inputOffset..], width, height, signed); BC6Decoder.Decode(outputSpan[outputOffset..], data[inputOffset..], width, height, signed);
inputOffset += w * h * 16; inputOffset += w * h * 16;
outputOffset += width * height * 8; outputOffset += width * height * 8;
@@ -563,7 +566,7 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public static byte[] DecodeBC7(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers) public static IMemoryOwner<byte> DecodeBC7(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{ {
int size = 0; int size = 0;
@@ -572,7 +575,8 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
} }
byte[] output = new byte[size]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Span<byte> outputSpan = output.Memory.Span;
int inputOffset = 0; int inputOffset = 0;
int outputOffset = 0; int outputOffset = 0;
@@ -586,7 +590,7 @@ namespace Ryujinx.Graphics.Texture
{ {
for (int z = 0; z < depth; z++) for (int z = 0; z < depth; z++)
{ {
BC7Decoder.Decode(output.AsSpan()[outputOffset..], data[inputOffset..], width, height); BC7Decoder.Decode(outputSpan[outputOffset..], data[inputOffset..], width, height);
inputOffset += w * h * 16; inputOffset += w * h * 16;
outputOffset += width * height * 4; outputOffset += width * height * 4;

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