aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Ryujinx.Graphics.GAL/Capabilities.cs9
-rw-r--r--Ryujinx.Graphics.GAL/IRenderer.cs4
-rw-r--r--Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs6
-rw-r--r--Ryujinx.Graphics.GAL/Multithreading/CommandType.cs3
-rw-r--r--Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CompileShaderCommand.cs22
-rw-r--r--Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs5
-rw-r--r--Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs4
-rw-r--r--Ryujinx.Graphics.GAL/Multithreading/Commands/Shader/ShaderDisposeCommand.cs21
-rw-r--r--Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs13
-rw-r--r--Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedShader.cs38
-rw-r--r--Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs1
-rw-r--r--Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs11
-rw-r--r--Ryujinx.Graphics.GAL/ShaderSource.cs29
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs16
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs156
-rw-r--r--Ryujinx.Graphics.Gpu/GpuContext.cs4
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs67
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs4
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs331
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs2
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs175
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntry.cs3
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs255
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/TransformFeedbackDescriptorOld.cs19
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/CachedGpuAccessor.cs222
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs (renamed from Ryujinx.Graphics.Gpu/Shader/ShaderBundle.cs)18
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/CachedShaderStage.cs38
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ComputeShaderCacheHashTable.cs68
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs138
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs216
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs18
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs57
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs202
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs459
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs763
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadException.cs48
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs72
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheOutputStreams.cs57
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs672
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs249
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs (renamed from Ryujinx.Graphics.Gpu/Shader/TextureDescriptorCapableGpuAccessor.cs)84
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs71
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/GpuChannelComputeState.cs57
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs41
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs36
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/HashTable/HashState.cs113
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/HashTable/IDataAccessor.cs27
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionHashTable.cs452
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionedHashTable.cs244
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/HashTable/SmartDataAccessor.cs96
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs36
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs17
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs1133
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs280
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ShaderCodeAccessor.cs32
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ShaderCodeHolder.cs52
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ShaderCompileTask.cs95
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs76
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs615
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/TransformFeedbackDescriptor.cs53
-rw-r--r--Ryujinx.Graphics.OpenGL/EnumConversion.cs15
-rw-r--r--Ryujinx.Graphics.OpenGL/Program.cs72
-rw-r--r--Ryujinx.Graphics.OpenGL/Renderer.cs14
-rw-r--r--Ryujinx.Graphics.OpenGL/Shader.cs42
-rw-r--r--Ryujinx.Graphics.Shader/BufferDescriptor.cs2
-rw-r--r--Ryujinx.Graphics.Shader/Decoders/Decoder.cs2
-rw-r--r--Ryujinx.Graphics.Shader/IGpuAccessor.cs190
-rw-r--r--Ryujinx.Graphics.Shader/ShaderProgram.cs13
-rw-r--r--Ryujinx.Graphics.Shader/ShaderProgramInfo.cs3
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs2
-rw-r--r--Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs2
-rw-r--r--Ryujinx.Graphics.Shader/TextureDescriptor.cs2
-rw-r--r--Ryujinx.Graphics.Shader/Translation/Rewriter.cs16
-rw-r--r--Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs80
-rw-r--r--Ryujinx.Graphics.Shader/Translation/TranslationCounts.cs36
-rw-r--r--Ryujinx.Graphics.Shader/Translation/Translator.cs52
-rw-r--r--Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs20
-rw-r--r--Ryujinx.Headless.SDL2/WindowBase.cs6
-rw-r--r--Ryujinx.ShaderTools/Program.cs2
-rw-r--r--Ryujinx/Ui/RendererWidgetBase.cs10
-rw-r--r--Ryujinx/Ui/Widgets/GameTableContextMenu.cs41
81 files changed, 6371 insertions, 2356 deletions
diff --git a/Ryujinx.Graphics.GAL/Capabilities.cs b/Ryujinx.Graphics.GAL/Capabilities.cs
index 4e5dff59..af8541fb 100644
--- a/Ryujinx.Graphics.GAL/Capabilities.cs
+++ b/Ryujinx.Graphics.GAL/Capabilities.cs
@@ -1,7 +1,12 @@
+using Ryujinx.Graphics.Shader.Translation;
+
namespace Ryujinx.Graphics.GAL
{
public struct Capabilities
{
+ public readonly TargetApi Api;
+ public readonly string VendorName;
+
public readonly bool HasFrontFacingBug;
public readonly bool HasVectorIndexingBug;
@@ -24,6 +29,8 @@ namespace Ryujinx.Graphics.GAL
public readonly int StorageBufferOffsetAlignment;
public Capabilities(
+ TargetApi api,
+ string vendorName,
bool hasFrontFacingBug,
bool hasVectorIndexingBug,
bool supportsAstcCompression,
@@ -43,6 +50,8 @@ namespace Ryujinx.Graphics.GAL
float maximumSupportedAnisotropy,
int storageBufferOffsetAlignment)
{
+ Api = api;
+ VendorName = vendorName;
HasFrontFacingBug = hasFrontFacingBug;
HasVectorIndexingBug = hasVectorIndexingBug;
SupportsAstcCompression = supportsAstcCompression;
diff --git a/Ryujinx.Graphics.GAL/IRenderer.cs b/Ryujinx.Graphics.GAL/IRenderer.cs
index a36d999d..b051e9dc 100644
--- a/Ryujinx.Graphics.GAL/IRenderer.cs
+++ b/Ryujinx.Graphics.GAL/IRenderer.cs
@@ -16,11 +16,9 @@ namespace Ryujinx.Graphics.GAL
void BackgroundContextAction(Action action, bool alwaysBackground = false);
- IShader CompileShader(ShaderStage stage, string code);
-
BufferHandle CreateBuffer(int size);
- IProgram CreateProgram(IShader[] shaders, ShaderInfo info);
+ IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
ISampler CreateSampler(SamplerCreateInfo info);
ITexture CreateTexture(TextureCreateInfo info, float scale);
diff --git a/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
index 67e8315b..442a9045 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
@@ -4,7 +4,6 @@ using Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Program;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler;
-using Ryujinx.Graphics.GAL.Multithreading.Commands.Shader;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
using System;
@@ -53,8 +52,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
{
_lookup[(int)CommandType.Action] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
ActionCommand.Run(ref GetCommand<ActionCommand>(memory), threaded, renderer);
- _lookup[(int)CommandType.CompileShader] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
- CompileShaderCommand.Run(ref GetCommand<CompileShaderCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.CreateBuffer] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
CreateBufferCommand.Run(ref GetCommand<CreateBufferCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.CreateProgram] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
@@ -98,9 +95,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_lookup[(int)CommandType.SamplerDispose] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SamplerDisposeCommand.Run(ref GetCommand<SamplerDisposeCommand>(memory), threaded, renderer);
- _lookup[(int)CommandType.ShaderDispose] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
- ShaderDisposeCommand.Run(ref GetCommand<ShaderDisposeCommand>(memory), threaded, renderer);
-
_lookup[(int)CommandType.TextureCopyTo] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
TextureCopyToCommand.Run(ref GetCommand<TextureCopyToCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.TextureCopyToScaled] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
diff --git a/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
index e0a03ce7..5c42abd1 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
@@ -3,7 +3,6 @@
enum CommandType : byte
{
Action,
- CompileShader,
CreateBuffer,
CreateProgram,
CreateSampler,
@@ -29,8 +28,6 @@
SamplerDispose,
- ShaderDispose,
-
TextureCopyTo,
TextureCopyToScaled,
TextureCopyToSlice,
diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CompileShaderCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CompileShaderCommand.cs
deleted file mode 100644
index 2bd9725d..00000000
--- a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CompileShaderCommand.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using Ryujinx.Graphics.GAL.Multithreading.Model;
-using Ryujinx.Graphics.GAL.Multithreading.Resources;
-
-namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
-{
- struct CompileShaderCommand : IGALCommand
- {
- public CommandType CommandType => CommandType.CompileShader;
- private TableRef<ThreadedShader> _shader;
-
- public void Set(TableRef<ThreadedShader> shader)
- {
- _shader = shader;
- }
-
- public static void Run(ref CompileShaderCommand command, ThreadedRenderer threaded, IRenderer renderer)
- {
- ThreadedShader shader = command._shader.Get(threaded);
- shader.EnsureCreated();
- }
- }
-}
diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs
index 4d1cbb28..a96b3cef 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs
@@ -1,7 +1,4 @@
-using Ryujinx.Graphics.GAL.Multithreading.Resources;
-using Ryujinx.Graphics.Shader;
-
-namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{
struct CreateBufferCommand : IGALCommand
{
diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs
index 67cafd18..1048dc9e 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs
@@ -1,6 +1,4 @@
-using System;
-
-namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{
struct PreFrameCommand : IGALCommand
{
diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Shader/ShaderDisposeCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Shader/ShaderDisposeCommand.cs
deleted file mode 100644
index ebb2c927..00000000
--- a/Ryujinx.Graphics.GAL/Multithreading/Commands/Shader/ShaderDisposeCommand.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Ryujinx.Graphics.GAL.Multithreading.Model;
-using Ryujinx.Graphics.GAL.Multithreading.Resources;
-
-namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Shader
-{
- struct ShaderDisposeCommand : IGALCommand
- {
- public CommandType CommandType => CommandType.ShaderDispose;
- private TableRef<ThreadedShader> _shader;
-
- public void Set(TableRef<ThreadedShader> shader)
- {
- _shader = shader;
- }
-
- public static void Run(ref ShaderDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
- {
- command._shader.Get(threaded).Base.Dispose();
- }
- }
-}
diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs
index d808fe22..7c5f0363 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs
@@ -6,10 +6,10 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs
{
public ThreadedProgram Threaded { get; set; }
- private IShader[] _shaders;
+ private ShaderSource[] _shaders;
private ShaderInfo _info;
- public SourceProgramRequest(ThreadedProgram program, IShader[] shaders, ShaderInfo info)
+ public SourceProgramRequest(ThreadedProgram program, ShaderSource[] shaders, ShaderInfo info)
{
Threaded = program;
@@ -19,14 +19,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs
public IProgram Create(IRenderer renderer)
{
- IShader[] shaders = _shaders.Select(shader =>
- {
- var threaded = (ThreadedShader)shader;
- threaded?.EnsureCreated();
- return threaded?.Base;
- }).ToArray();
-
- return renderer.CreateProgram(shaders, _info);
+ return renderer.CreateProgram(_shaders, _info);
}
}
}
diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedShader.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedShader.cs
deleted file mode 100644
index dcbecf38..00000000
--- a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedShader.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using Ryujinx.Graphics.GAL.Multithreading.Commands.Shader;
-using Ryujinx.Graphics.GAL.Multithreading.Model;
-using Ryujinx.Graphics.Shader;
-
-namespace Ryujinx.Graphics.GAL.Multithreading.Resources
-{
- class ThreadedShader : IShader
- {
- private ThreadedRenderer _renderer;
- private ShaderStage _stage;
- private string _code;
-
- public IShader Base;
-
- public ThreadedShader(ThreadedRenderer renderer, ShaderStage stage, string code)
- {
- _renderer = renderer;
-
- _stage = stage;
- _code = code;
- }
-
- internal void EnsureCreated()
- {
- if (_code != null && Base == null)
- {
- Base = _renderer.BaseRenderer.CompileShader(_stage, _code);
- _code = null;
- }
- }
-
- public void Dispose()
- {
- _renderer.New<ShaderDisposeCommand>().Set(new TableRef<ThreadedShader>(_renderer, this));
- _renderer.QueueCommand();
- }
- }
-}
diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
index 6dc8ef38..b6acfaa8 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
@@ -1,7 +1,6 @@
using Ryujinx.Graphics.GAL.Multithreading.Commands;
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
-using Ryujinx.Graphics.Shader;
using System;
using System.Linq;
diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
index 5030fee6..63b668ba 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
@@ -250,15 +250,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
}
}
- public IShader CompileShader(ShaderStage stage, string code)
- {
- var shader = new ThreadedShader(this, stage, code);
- New<CompileShaderCommand>().Set(Ref(shader));
- QueueCommand();
-
- return shader;
- }
-
public BufferHandle CreateBuffer(int size)
{
BufferHandle handle = Buffers.CreateBufferHandle();
@@ -268,7 +259,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return handle;
}
- public IProgram CreateProgram(IShader[] shaders, ShaderInfo info)
+ public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
{
var program = new ThreadedProgram(this);
SourceProgramRequest request = new SourceProgramRequest(program, shaders, info);
diff --git a/Ryujinx.Graphics.GAL/ShaderSource.cs b/Ryujinx.Graphics.GAL/ShaderSource.cs
new file mode 100644
index 00000000..13b92f20
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/ShaderSource.cs
@@ -0,0 +1,29 @@
+using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public struct ShaderSource
+ {
+ public string Code { get; }
+ public byte[] BinaryCode { get; }
+ public ShaderStage Stage { get; }
+ public TargetLanguage Language { get; }
+
+ public ShaderSource(string code, byte[] binaryCode, ShaderStage stage, TargetLanguage language)
+ {
+ Code = code;
+ BinaryCode = binaryCode;
+ Stage = stage;
+ Language = language;
+ }
+
+ public ShaderSource(string code, ShaderStage stage, TargetLanguage language) : this(code, null, stage, language)
+ {
+ }
+
+ public ShaderSource(byte[] binaryCode, ShaderStage stage, TargetLanguage language) : this(null, binaryCode, stage, language)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs b/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs
index 00015c40..87c14da8 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs
@@ -124,24 +124,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
ulong samplerPoolGpuVa = ((ulong)_state.State.SetTexSamplerPoolAOffsetUpper << 32) | _state.State.SetTexSamplerPoolB;
ulong texturePoolGpuVa = ((ulong)_state.State.SetTexHeaderPoolAOffsetUpper << 32) | _state.State.SetTexHeaderPoolB;
- GpuAccessorState gas = new GpuAccessorState(
+ GpuChannelPoolState poolState = new GpuChannelPoolState(
texturePoolGpuVa,
_state.State.SetTexHeaderPoolCMaximumIndex,
- _state.State.SetBindlessTextureConstantBufferSlotSelect,
- false,
- PrimitiveTopology.Points,
- default);
-
- ShaderBundle cs = memoryManager.Physical.ShaderCache.GetComputeShader(
- _channel,
- gas,
- shaderGpuVa,
+ _state.State.SetBindlessTextureConstantBufferSlotSelect);
+
+ GpuChannelComputeState computeState = new GpuChannelComputeState(
qmd.CtaThreadDimension0,
qmd.CtaThreadDimension1,
qmd.CtaThreadDimension2,
localMemorySize,
sharedMemorySize);
+ CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
+
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
_channel.TextureManager.SetComputeSamplerPool(samplerPoolGpuVa, _state.State.SetTexSamplerPoolCMaximumIndex, qmd.SamplerIndex);
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
index 8d67d0fd..c9a18f14 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
@@ -7,7 +7,6 @@ using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Texture;
using System;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
@@ -30,6 +29,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
private readonly StateUpdateTracker<ThreedClassState> _updateTracker;
private readonly ShaderProgramInfo[] _currentProgramInfo;
+ private ShaderSpecializationState _shaderSpecState;
private bool _vtgWritesRtLayer;
private byte _vsClipDistancesWritten;
@@ -195,6 +195,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update()
{
+ // If any state that the shader depends on changed,
+ // then we may need to compile/bind a different version
+ // of the shader for the new state.
+ if (_shaderSpecState != null)
+ {
+ if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState()))
+ {
+ ForceShaderUpdate();
+ }
+ }
+
// The vertex buffer size is calculated using a different
// method when doing indexed draws, so we need to make sure
// to update the vertex buffers if we are doing a regular
@@ -1065,106 +1076,125 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
private void UpdateShaderState()
{
- ShaderAddresses addresses = new ShaderAddresses();
+ var shaderCache = _channel.MemoryManager.Physical.ShaderCache;
- Span<ShaderAddresses> addressesSpan = MemoryMarshal.CreateSpan(ref addresses, 1);
+ _vtgWritesRtLayer = false;
- Span<ulong> addressesArray = MemoryMarshal.Cast<ShaderAddresses, ulong>(addressesSpan);
+ ShaderAddresses addresses = new ShaderAddresses();
+ Span<ulong> addressesSpan = addresses.AsSpan();
ulong baseAddress = _state.State.ShaderBaseAddress.Pack();
for (int index = 0; index < 6; index++)
{
var shader = _state.State.ShaderState[index];
-
if (!shader.UnpackEnable() && index != 1)
{
continue;
}
- addressesArray[index] = baseAddress + shader.Offset;
+ addressesSpan[index] = baseAddress + shader.Offset;
}
- GpuAccessorState gas = new GpuAccessorState(
- _state.State.TexturePoolState.Address.Pack(),
- _state.State.TexturePoolState.MaximumId,
- (int)_state.State.TextureBufferIndex,
- _state.State.EarlyZForce,
- _drawState.Topology,
- _state.State.TessMode);
+ GpuChannelPoolState poolState = GetPoolState();
+ GpuChannelGraphicsState graphicsState = GetGraphicsState();
- ShaderBundle gs = _channel.MemoryManager.Physical.ShaderCache.GetGraphicsShader(ref _state.State, _channel, gas, addresses);
+ CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, _channel, poolState, graphicsState, addresses);
+
+ _shaderSpecState = gs.SpecializationState;
byte oldVsClipDistancesWritten = _vsClipDistancesWritten;
- _drawState.VsUsesInstanceId = gs.Shaders[0]?.Info.UsesInstanceId ?? false;
- _vsClipDistancesWritten = gs.Shaders[0]?.Info.ClipDistancesWritten ?? 0;
- _vtgWritesRtLayer = false;
+ _drawState.VsUsesInstanceId = gs.Shaders[1]?.Info.UsesInstanceId ?? false;
+ _vsClipDistancesWritten = gs.Shaders[1]?.Info.ClipDistancesWritten ?? 0;
if (oldVsClipDistancesWritten != _vsClipDistancesWritten)
{
UpdateUserClipState();
}
- for (int stage = 0; stage < Constants.ShaderStages; stage++)
+ for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
{
- ShaderProgramInfo info = gs.Shaders[stage]?.Info;
+ UpdateStageBindings(stageIndex, gs.Shaders[stageIndex + 1]?.Info);
+ }
- _currentProgramInfo[stage] = info;
+ _context.Renderer.Pipeline.SetProgram(gs.HostProgram);
+ }
- if (info == null)
- {
- _channel.TextureManager.RentGraphicsTextureBindings(stage, 0);
- _channel.TextureManager.RentGraphicsImageBindings(stage, 0);
- _channel.BufferManager.SetGraphicsStorageBufferBindings(stage, null);
- _channel.BufferManager.SetGraphicsUniformBufferBindings(stage, null);
- continue;
- }
+ private void UpdateStageBindings(int stage, ShaderProgramInfo info)
+ {
+ _currentProgramInfo[stage] = info;
- Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
+ if (info == null)
+ {
+ _channel.TextureManager.RentGraphicsTextureBindings(stage, 0);
+ _channel.TextureManager.RentGraphicsImageBindings(stage, 0);
+ _channel.BufferManager.SetGraphicsStorageBufferBindings(stage, null);
+ _channel.BufferManager.SetGraphicsUniformBufferBindings(stage, null);
+ return;
+ }
- if (info.UsesRtLayer)
- {
- _vtgWritesRtLayer = true;
- }
+ Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
- for (int index = 0; index < info.Textures.Count; index++)
- {
- var descriptor = info.Textures[index];
+ if (info.UsesRtLayer)
+ {
+ _vtgWritesRtLayer = true;
+ }
- Target target = ShaderTexture.GetTarget(descriptor.Type);
+ for (int index = 0; index < info.Textures.Count; index++)
+ {
+ var descriptor = info.Textures[index];
- textureBindings[index] = new TextureBindingInfo(
- target,
- descriptor.Binding,
- descriptor.CbufSlot,
- descriptor.HandleIndex,
- descriptor.Flags);
- }
+ Target target = ShaderTexture.GetTarget(descriptor.Type);
- TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
+ textureBindings[index] = new TextureBindingInfo(
+ target,
+ descriptor.Binding,
+ descriptor.CbufSlot,
+ descriptor.HandleIndex,
+ descriptor.Flags);
+ }
- for (int index = 0; index < info.Images.Count; index++)
- {
- var descriptor = info.Images[index];
-
- Target target = ShaderTexture.GetTarget(descriptor.Type);
- Format format = ShaderTexture.GetFormat(descriptor.Format);
-
- imageBindings[index] = new TextureBindingInfo(
- target,
- format,
- descriptor.Binding,
- descriptor.CbufSlot,
- descriptor.HandleIndex,
- descriptor.Flags);
- }
+ TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
- _channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
- _channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
+ for (int index = 0; index < info.Images.Count; index++)
+ {
+ var descriptor = info.Images[index];
+
+ Target target = ShaderTexture.GetTarget(descriptor.Type);
+ Format format = ShaderTexture.GetFormat(descriptor.Format);
+
+ imageBindings[index] = new TextureBindingInfo(
+ target,
+ format,
+ descriptor.Binding,
+ descriptor.CbufSlot,
+ descriptor.HandleIndex,
+ descriptor.Flags);
}
- _context.Renderer.Pipeline.SetProgram(gs.HostProgram);
+ _channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
+ _channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
+ }
+
+ private GpuChannelPoolState GetPoolState()
+ {
+ return new GpuChannelPoolState(
+ _state.State.TexturePoolState.Address.Pack(),
+ _state.State.TexturePoolState.MaximumId,
+ (int)_state.State.TextureBufferIndex);
+ }
+
+ /// <summary>
+ /// Gets the current GPU channel state for shader creation or compatibility verification.
+ /// </summary>
+ /// <returns>Current GPU channel state</returns>
+ private GpuChannelGraphicsState GetGraphicsState()
+ {
+ return new GpuChannelGraphicsState(
+ _state.State.EarlyZForce,
+ _drawState.Topology,
+ _state.State.TessMode);
}
/// <summary>
diff --git a/Ryujinx.Graphics.Gpu/GpuContext.cs b/Ryujinx.Graphics.Gpu/GpuContext.cs
index e6697b3a..66077c3b 100644
--- a/Ryujinx.Graphics.Gpu/GpuContext.cs
+++ b/Ryujinx.Graphics.Gpu/GpuContext.cs
@@ -238,13 +238,13 @@ namespace Ryujinx.Graphics.Gpu
/// <summary>
/// Initialize the GPU shader cache.
/// </summary>
- public void InitializeShaderCache()
+ public void InitializeShaderCache(CancellationToken cancellationToken)
{
HostInitalized.WaitOne();
foreach (var physicalMemory in PhysicalMemoryRegistry.Values)
{
- physicalMemory.ShaderCache.Initialize();
+ physicalMemory.ShaderCache.Initialize(cancellationToken);
}
}
diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
index ae27c712..0ac6160d 100644
--- a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
@@ -116,6 +116,73 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
+ /// Gets a read-only span of data from GPU mapped memory, up to the entire range specified,
+ /// or the last mapped page if the range is not fully mapped.
+ /// </summary>
+ /// <param name="va">GPU virtual address where the data is located</param>
+ /// <param name="size">Size of the data</param>
+ /// <param name="tracked">True if read tracking is triggered on the span</param>
+ /// <returns>The span of the data at the specified memory location</returns>
+ public ReadOnlySpan<byte> GetSpanMapped(ulong va, int size, bool tracked = false)
+ {
+ bool isContiguous = true;
+ int mappedSize;
+
+ if (ValidateAddress(va) && GetPte(va) != PteUnmapped && Physical.IsMapped(Translate(va)))
+ {
+ ulong endVa = va + (ulong)size;
+ ulong endVaAligned = (endVa + PageMask) & ~PageMask;
+ ulong currentVa = va & ~PageMask;
+
+ int pages = (int)((endVaAligned - currentVa) / PageSize);
+
+ for (int page = 0; page < pages - 1; page++)
+ {
+ ulong nextVa = currentVa + PageSize;
+ ulong nextPa = Translate(nextVa);
+
+ if (!ValidateAddress(nextVa) || GetPte(nextVa) == PteUnmapped || !Physical.IsMapped(nextPa))
+ {
+ break;
+ }
+
+ if (Translate(currentVa) + PageSize != nextPa)
+ {
+ isContiguous = false;
+ }
+
+ currentVa += PageSize;
+ }
+
+ currentVa += PageSize;
+
+ if (currentVa > endVa)
+ {
+ currentVa = endVa;
+ }
+
+ mappedSize = (int)(currentVa - va);
+ }
+ else
+ {
+ return ReadOnlySpan<byte>.Empty;
+ }
+
+ if (isContiguous)
+ {
+ return Physical.GetSpan(Translate(va), mappedSize, tracked);
+ }
+ else
+ {
+ Span<byte> data = new byte[mappedSize];
+
+ ReadImpl(va, data, tracked);
+
+ return data;
+ }
+ }
+
+ /// <summary>
/// Reads data from a possibly non-contiguous region of GPU mapped memory.
/// </summary>
/// <param name="va">GPU virtual address of the data</param>
diff --git a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
index 57590fb3..155cba0f 100644
--- a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
@@ -341,9 +341,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
- /// Checks if the page at a given address is mapped on CPU memory.
+ /// Checks if a given memory page is mapped.
/// </summary>
- /// <param name="address">CPU virtual address of the page to check</param>
+ /// <param name="address">CPU virtual address of the page</param>
/// <returns>True if mapped, false otherwise</returns>
public bool IsMapped(ulong address)
{
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs
index 464436ea..d16afb65 100644
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs
@@ -2,11 +2,8 @@
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.GAL;
-using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
using Ryujinx.Graphics.Shader;
-using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.IO;
@@ -21,70 +18,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
static class CacheHelper
{
/// <summary>
- /// Try to read the manifest header from a given file path.
- /// </summary>
- /// <param name="manifestPath">The path to the manifest file</param>
- /// <param name="header">The manifest header read</param>
- /// <returns>Return true if the manifest header was read</returns>
- public static bool TryReadManifestHeader(string manifestPath, out CacheManifestHeader header)
- {
- header = default;
-
- if (File.Exists(manifestPath))
- {
- Memory<byte> rawManifest = File.ReadAllBytes(manifestPath);
-
- if (MemoryMarshal.TryRead(rawManifest.Span, out header))
- {
- return true;
- }
- }
-
- return false;
- }
-
- /// <summary>
- /// Try to read the manifest from a given file path.
- /// </summary>
- /// <param name="manifestPath">The path to the manifest file</param>
- /// <param name="graphicsApi">The graphics api used by the cache</param>
- /// <param name="hashType">The hash type of the cache</param>
- /// <param name="header">The manifest header read</param>
- /// <param name="entries">The entries read from the cache manifest</param>
- /// <returns>Return true if the manifest was read</returns>
- public static bool TryReadManifestFile(string manifestPath, CacheGraphicsApi graphicsApi, CacheHashType hashType, out CacheManifestHeader header, out HashSet<Hash128> entries)
- {
- header = default;
- entries = new HashSet<Hash128>();
-
- if (File.Exists(manifestPath))
- {
- Memory<byte> rawManifest = File.ReadAllBytes(manifestPath);
-
- if (MemoryMarshal.TryRead(rawManifest.Span, out header))
- {
- Memory<byte> hashTableRaw = rawManifest.Slice(Unsafe.SizeOf<CacheManifestHeader>());
-
- bool isValid = header.IsValid(graphicsApi, hashType, hashTableRaw.Span);
-
- if (isValid)
- {
- ReadOnlySpan<Hash128> hashTable = MemoryMarshal.Cast<byte, Hash128>(hashTableRaw.Span);
-
- foreach (Hash128 hash in hashTable)
- {
- entries.Add(hash);
- }
- }
-
- return isValid;
- }
- }
-
- return false;
- }
-
- /// <summary>
/// Compute a cache manifest from runtime data.
/// </summary>
/// <param name="version">The version of the cache</param>
@@ -247,81 +180,22 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
}
/// <summary>
- /// Compute the guest program code for usage while dumping to disk or hash.
- /// </summary>
- /// <param name="cachedShaderEntries">The guest shader entries to use</param>
- /// <param name="tfd">The transform feedback descriptors</param>
- /// <param name="forHashCompute">Used to determine if the guest program code is generated for hashing</param>
- /// <returns>The guest program code for usage while dumping to disk or hash</returns>
- private static byte[] ComputeGuestProgramCode(ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries, TransformFeedbackDescriptor[] tfd, bool forHashCompute = false)
- {
- using (MemoryStream stream = new MemoryStream())
- {
- BinaryWriter writer = new BinaryWriter(stream);
-
- foreach (GuestShaderCacheEntry cachedShaderEntry in cachedShaderEntries)
- {
- if (cachedShaderEntry != null)
- {
- // Code (and Code A if present)
- stream.Write(cachedShaderEntry.Code);
-
- if (forHashCompute)
- {
- // Guest GPU accessor header (only write this for hashes, already present in the header for dumps)
- writer.WriteStruct(cachedShaderEntry.Header.GpuAccessorHeader);
- }
-
- // Texture descriptors
- foreach (GuestTextureDescriptor textureDescriptor in cachedShaderEntry.TextureDescriptors.Values)
- {
- writer.WriteStruct(textureDescriptor);
- }
- }
- }
-
- // Transform feedback
- if (tfd != null)
- {
- foreach (TransformFeedbackDescriptor transform in tfd)
- {
- writer.WriteStruct(new GuestShaderCacheTransformFeedbackHeader(transform.BufferIndex, transform.Stride, transform.VaryingLocations.Length));
- writer.Write(transform.VaryingLocations);
- }
- }
-
- return stream.ToArray();
- }
- }
-
- /// <summary>
- /// Compute a guest hash from shader entries.
- /// </summary>
- /// <param name="cachedShaderEntries">The guest shader entries to use</param>
- /// <param name="tfd">The optional transform feedback descriptors</param>
- /// <returns>A guest hash from shader entries</returns>
- public static Hash128 ComputeGuestHashFromCache(ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries, TransformFeedbackDescriptor[] tfd = null)
- {
- return XXHash128.ComputeHash(ComputeGuestProgramCode(cachedShaderEntries, tfd, true));
- }
-
- /// <summary>
/// Read transform feedback descriptors from guest.
/// </summary>
/// <param name="data">The raw guest transform feedback descriptors</param>
/// <param name="header">The guest shader program header</param>
/// <returns>The transform feedback descriptors read from guest</returns>
- public static TransformFeedbackDescriptor[] ReadTransformFeedbackInformation(ref ReadOnlySpan<byte> data, GuestShaderCacheHeader header)
+ public static TransformFeedbackDescriptorOld[] ReadTransformFeedbackInformation(ref ReadOnlySpan<byte> data, GuestShaderCacheHeader header)
{
if (header.TransformFeedbackCount != 0)
{
- TransformFeedbackDescriptor[] result = new TransformFeedbackDescriptor[header.TransformFeedbackCount];
+ TransformFeedbackDescriptorOld[] result = new TransformFeedbackDescriptorOld[header.TransformFeedbackCount];
for (int i = 0; i < result.Length; i++)
{
GuestShaderCacheTransformFeedbackHeader feedbackHeader = MemoryMarshal.Read<GuestShaderCacheTransformFeedbackHeader>(data);
- result[i] = new TransformFeedbackDescriptor(feedbackHeader.BufferIndex, feedbackHeader.Stride, data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>(), feedbackHeader.VaryingLocationsLength).ToArray());
+ result[i] = new TransformFeedbackDescriptorOld(feedbackHeader.BufferIndex, feedbackHeader.Stride, data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>(), feedbackHeader.VaryingLocationsLength).ToArray());
data = data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>() + feedbackHeader.VaryingLocationsLength);
}
@@ -333,205 +207,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
}
/// <summary>
- /// Builds gpu state flags using information from the given gpu accessor.
- /// </summary>
- /// <param name="gpuAccessor">The gpu accessor</param>
- /// <returns>The gpu state flags</returns>
- private static GuestGpuStateFlags GetGpuStateFlags(IGpuAccessor gpuAccessor)
- {
- GuestGpuStateFlags flags = 0;
-
- if (gpuAccessor.QueryEarlyZForce())
- {
- flags |= GuestGpuStateFlags.EarlyZForce;
- }
-
- return flags;
- }
-
- /// <summary>
- /// Packs the tessellation parameters from the gpu accessor.
- /// </summary>
- /// <param name="gpuAccessor">The gpu accessor</param>
- /// <returns>The packed tessellation parameters</returns>
- private static byte GetTessellationModePacked(IGpuAccessor gpuAccessor)
- {
- byte value;
-
- value = (byte)((int)gpuAccessor.QueryTessPatchType() & 3);
- value |= (byte)(((int)gpuAccessor.QueryTessSpacing() & 3) << 2);
-
- if (gpuAccessor.QueryTessCw())
- {
- value |= 0x10;
- }
-
- return value;
- }
-
- /// <summary>
- /// Create a new instance of <see cref="GuestGpuAccessorHeader"/> from an gpu accessor.
- /// </summary>
- /// <param name="gpuAccessor">The gpu accessor</param>
- /// <returns>A new instance of <see cref="GuestGpuAccessorHeader"/></returns>
- public static GuestGpuAccessorHeader CreateGuestGpuAccessorCache(IGpuAccessor gpuAccessor)
- {
- return new GuestGpuAccessorHeader
- {
- ComputeLocalSizeX = gpuAccessor.QueryComputeLocalSizeX(),
- ComputeLocalSizeY = gpuAccessor.QueryComputeLocalSizeY(),
- ComputeLocalSizeZ = gpuAccessor.QueryComputeLocalSizeZ(),
- ComputeLocalMemorySize = gpuAccessor.QueryComputeLocalMemorySize(),
- ComputeSharedMemorySize = gpuAccessor.QueryComputeSharedMemorySize(),
- PrimitiveTopology = gpuAccessor.QueryPrimitiveTopology(),
- TessellationModePacked = GetTessellationModePacked(gpuAccessor),
- StateFlags = GetGpuStateFlags(gpuAccessor)
- };
- }
-
- /// <summary>
- /// Create guest shader cache entries from the runtime contexts.
- /// </summary>
- /// <param name="channel">The GPU channel in use</param>
- /// <param name="shaderContexts">The runtime contexts</param>
- /// <returns>Guest shader cahe entries from the runtime contexts</returns>
- public static GuestShaderCacheEntry[] CreateShaderCacheEntries(GpuChannel channel, ReadOnlySpan<TranslatorContext> shaderContexts)
- {
- MemoryManager memoryManager = channel.MemoryManager;
-
- int startIndex = shaderContexts.Length > 1 ? 1 : 0;
-
- GuestShaderCacheEntry[] entries = new GuestShaderCacheEntry[shaderContexts.Length - startIndex];
-
- for (int i = startIndex; i < shaderContexts.Length; i++)
- {
- TranslatorContext context = shaderContexts[i];
-
- if (context == null)
- {
- continue;
- }
-
- GpuAccessor gpuAccessor = context.GpuAccessor as GpuAccessor;
-
- ulong cb1DataAddress;
- int cb1DataSize = gpuAccessor?.Cb1DataSize ?? 0;
-
- if (context.Stage == ShaderStage.Compute)
- {
- cb1DataAddress = channel.BufferManager.GetComputeUniformBufferAddress(1);
- }
- else
- {
- int stageIndex = context.Stage switch
- {
- ShaderStage.TessellationControl => 1,
- ShaderStage.TessellationEvaluation => 2,
- ShaderStage.Geometry => 3,
- ShaderStage.Fragment => 4,
- _ => 0
- };
-
- cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, 1);
- }
-
- int size = context.Size;
-
- TranslatorContext translatorContext2 = i == 1 ? shaderContexts[0] : null;
-
- int sizeA = translatorContext2 != null ? translatorContext2.Size : 0;
-
- byte[] code = new byte[size + cb1DataSize + sizeA];
-
- memoryManager.GetSpan(context.Address, size).CopyTo(code);
-
- if (cb1DataAddress != 0 && cb1DataSize != 0)
- {
- memoryManager.Physical.GetSpan(cb1DataAddress, cb1DataSize).CopyTo(code.AsSpan(size, cb1DataSize));
- }
-
- if (translatorContext2 != null)
- {
- memoryManager.GetSpan(translatorContext2.Address, sizeA).CopyTo(code.AsSpan(size + cb1DataSize, sizeA));
- }
-
- GuestGpuAccessorHeader gpuAccessorHeader = CreateGuestGpuAccessorCache(context.GpuAccessor);
-
- if (gpuAccessor != null)
- {
- gpuAccessorHeader.TextureDescriptorCount = context.TextureHandlesForCache.Count;
- }
-
- GuestShaderCacheEntryHeader header = new GuestShaderCacheEntryHeader(
- context.Stage,
- size + cb1DataSize,
- sizeA,
- cb1DataSize,
- gpuAccessorHeader);
-
- GuestShaderCacheEntry entry = new GuestShaderCacheEntry(header, code);
-
- if (gpuAccessor != null)
- {
- foreach (int textureHandle in context.TextureHandlesForCache)
- {
- GuestTextureDescriptor textureDescriptor = ((Image.TextureDescriptor)gpuAccessor.GetTextureDescriptor(textureHandle, -1)).ToCache();
-
- textureDescriptor.Handle = (uint)textureHandle;
-
- entry.TextureDescriptors.Add(textureHandle, textureDescriptor);
- }
- }
-
- entries[i - startIndex] = entry;
- }
-
- return entries;
- }
-
- /// <summary>
- /// Create a guest shader program.
- /// </summary>
- /// <param name="shaderCacheEntries">The entries composing the guest program dump</param>
- /// <param name="tfd">The transform feedback descriptors in use</param>
- /// <returns>The resulting guest shader program</returns>
- public static byte[] CreateGuestProgramDump(GuestShaderCacheEntry[] shaderCacheEntries, TransformFeedbackDescriptor[] tfd = null)
- {
- using (MemoryStream resultStream = new MemoryStream())
- {
- BinaryWriter resultStreamWriter = new BinaryWriter(resultStream);
-
- byte transformFeedbackCount = 0;
-
- if (tfd != null)
- {
- transformFeedbackCount = (byte)tfd.Length;
- }
-
- // Header
- resultStreamWriter.WriteStruct(new GuestShaderCacheHeader((byte)shaderCacheEntries.Length, transformFeedbackCount));
-
- // Write all entries header
- foreach (GuestShaderCacheEntry entry in shaderCacheEntries)
- {
- if (entry == null)
- {
- resultStreamWriter.WriteStruct(new GuestShaderCacheEntryHeader());
- }
- else
- {
- resultStreamWriter.WriteStruct(entry.Header);
- }
- }
-
- // Finally, write all program code and all transform feedback information.
- resultStreamWriter.Write(ComputeGuestProgramCode(shaderCacheEntries, tfd));
-
- return resultStream.ToArray();
- }
- }
-
- /// <summary>
/// Save temporary files not in archive.
/// </summary>
/// <param name="baseCacheDirectory">The base of the cache directory</param>
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs
index 3fc11e82..e67221e7 100644
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs
@@ -47,8 +47,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
string baseCacheDirectory = CacheHelper.GetBaseCacheDirectory(titleId);
- CacheMigration.Run(baseCacheDirectory, graphicsApi, hashType, shaderProvider);
-
_guestProgramCache = new CacheCollection(baseCacheDirectory, _hashType, CacheGraphicsApi.Guest, "", "program", GuestCacheVersion);
_hostProgramCache = new CacheCollection(baseCacheDirectory, _hashType, _graphicsApi, _shaderProvider, "host", shaderCodeGenVersion);
}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs
deleted file mode 100644
index 5b4a1713..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs
+++ /dev/null
@@ -1,175 +0,0 @@
-using ICSharpCode.SharpZipLib.Zip;
-using Ryujinx.Common;
-using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.GAL;
-using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace Ryujinx.Graphics.Gpu.Shader.Cache
-{
- /// <summary>
- /// Class handling shader cache migrations.
- /// </summary>
- static class CacheMigration
- {
- /// <summary>
- /// Check if the given cache version need to recompute its hash.
- /// </summary>
- /// <param name="version">The version in use</param>
- /// <param name="newVersion">The new version after migration</param>
- /// <returns>True if a hash recompute is needed</returns>
- public static bool NeedHashRecompute(ulong version, out ulong newVersion)
- {
- const ulong TargetBrokenVersion = 1717;
- const ulong TargetFixedVersion = 1759;
-
- newVersion = TargetFixedVersion;
-
- if (version == TargetBrokenVersion)
- {
- return true;
- }
-
- return false;
- }
-
- private class StreamZipEntryDataSource : IStaticDataSource
- {
- private readonly ZipFile Archive;
- private readonly ZipEntry Entry;
- public StreamZipEntryDataSource(ZipFile archive, ZipEntry entry)
- {
- Archive = archive;
- Entry = entry;
- }
-
- public Stream GetSource()
- {
- return Archive.GetInputStream(Entry);
- }
- }
-
- /// <summary>
- /// Move a file with the name of a given hash to another in the cache archive.
- /// </summary>
- /// <param name="archive">The archive in use</param>
- /// <param name="oldKey">The old key</param>
- /// <param name="newKey">The new key</param>
- private static void MoveEntry(ZipFile archive, Hash128 oldKey, Hash128 newKey)
- {
- ZipEntry oldGuestEntry = archive.GetEntry($"{oldKey}");
-
- if (oldGuestEntry != null)
- {
- archive.Add(new StreamZipEntryDataSource(archive, oldGuestEntry), $"{newKey}", CompressionMethod.Deflated);
- archive.Delete(oldGuestEntry);
- }
- }
-
- /// <summary>
- /// Recompute all the hashes of a given cache.
- /// </summary>
- /// <param name="guestBaseCacheDirectory">The guest cache directory path</param>
- /// <param name="hostBaseCacheDirectory">The host cache directory path</param>
- /// <param name="graphicsApi">The graphics api in use</param>
- /// <param name="hashType">The hash type in use</param>
- /// <param name="newVersion">The version to write in the host and guest manifest after migration</param>
- private static void RecomputeHashes(string guestBaseCacheDirectory, string hostBaseCacheDirectory, CacheGraphicsApi graphicsApi, CacheHashType hashType, ulong newVersion)
- {
- string guestManifestPath = CacheHelper.GetManifestPath(guestBaseCacheDirectory);
- string hostManifestPath = CacheHelper.GetManifestPath(hostBaseCacheDirectory);
-
- if (CacheHelper.TryReadManifestFile(guestManifestPath, CacheGraphicsApi.Guest, hashType, out _, out HashSet<Hash128> guestEntries))
- {
- CacheHelper.TryReadManifestFile(hostManifestPath, graphicsApi, hashType, out _, out HashSet<Hash128> hostEntries);
-
- Logger.Info?.Print(LogClass.Gpu, "Shader cache hashes need to be recomputed, performing migration...");
-
- string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
- string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
-
- ZipFile guestArchive = new ZipFile(File.Open(guestArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
- ZipFile hostArchive = new ZipFile(File.Open(hostArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
-
- CacheHelper.EnsureArchiveUpToDate(guestBaseCacheDirectory, guestArchive, guestEntries);
- CacheHelper.EnsureArchiveUpToDate(hostBaseCacheDirectory, hostArchive, hostEntries);
-
- int programIndex = 0;
-
- HashSet<Hash128> newEntries = new HashSet<Hash128>();
-
- foreach (Hash128 oldHash in guestEntries)
- {
- byte[] guestProgram = CacheHelper.ReadFromArchive(guestArchive, oldHash);
-
- Logger.Info?.Print(LogClass.Gpu, $"Migrating shader {oldHash} ({programIndex + 1} / {guestEntries.Count})");
-
- if (guestProgram != null)
- {
- ReadOnlySpan<byte> guestProgramReadOnlySpan = guestProgram;
-
- ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader);
-
- TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader);
-
- Hash128 newHash = CacheHelper.ComputeGuestHashFromCache(cachedShaderEntries, tfd);
-
- if (newHash != oldHash)
- {
- MoveEntry(guestArchive, oldHash, newHash);
- MoveEntry(hostArchive, oldHash, newHash);
- }
- else
- {
- Logger.Warning?.Print(LogClass.Gpu, $"Same hashes for shader {oldHash}");
- }
-
- newEntries.Add(newHash);
- }
-
- programIndex++;
- }
-
- byte[] newGuestManifestContent = CacheHelper.ComputeManifest(newVersion, CacheGraphicsApi.Guest, hashType, newEntries);
- byte[] newHostManifestContent = CacheHelper.ComputeManifest(newVersion, graphicsApi, hashType, newEntries);
-
- File.WriteAllBytes(guestManifestPath, newGuestManifestContent);
- File.WriteAllBytes(hostManifestPath, newHostManifestContent);
-
- guestArchive.CommitUpdate();
- hostArchive.CommitUpdate();
-
- guestArchive.Close();
- hostArchive.Close();
- }
- }
-
- /// <summary>
- /// Check and run cache migration if needed.
- /// </summary>
- /// <param name="baseCacheDirectory">The base path of the cache</param>
- /// <param name="graphicsApi">The graphics api in use</param>
- /// <param name="hashType">The hash type in use</param>
- /// <param name="shaderProvider">The shader provider name of the cache</param>
- public static void Run(string baseCacheDirectory, CacheGraphicsApi graphicsApi, CacheHashType hashType, string shaderProvider)
- {
- string guestBaseCacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, CacheGraphicsApi.Guest, "", "program");
- string hostBaseCacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, graphicsApi, shaderProvider, "host");
-
- string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
- string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
-
- bool isReadOnly = CacheHelper.IsArchiveReadOnly(guestArchivePath) || CacheHelper.IsArchiveReadOnly(hostArchivePath);
-
- if (!isReadOnly && CacheHelper.TryReadManifestHeader(CacheHelper.GetManifestPath(guestBaseCacheDirectory), out CacheManifestHeader header))
- {
- if (NeedHashRecompute(header.Version, out ulong newVersion))
- {
- RecomputeHashes(guestBaseCacheDirectory, hostBaseCacheDirectory, graphicsApi, hashType, newVersion);
- }
- }
- }
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntry.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntry.cs
index 819c6bcc..fe79acb3 100644
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntry.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntry.cs
@@ -96,6 +96,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
SBuffers,
Textures,
Images,
+ default,
Header.UseFlags.HasFlag(UseFlags.InstanceId),
Header.UseFlags.HasFlag(UseFlags.RtLayer),
Header.ClipDistancesWritten,
@@ -160,7 +161,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
/// <param name="programCode">The host shader program</param>
/// <param name="codeHolders">The shaders code holder</param>
/// <returns>Raw data of a new host shader cache file</returns>
- internal static byte[] Create(ReadOnlySpan<byte> programCode, ShaderCodeHolder[] codeHolders)
+ internal static byte[] Create(ReadOnlySpan<byte> programCode, CachedShaderStage[] codeHolders)
{
HostShaderCacheHeader header = new HostShaderCacheHeader((byte)codeHolders.Length, programCode.Length);
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs
new file mode 100644
index 00000000..27fac8f3
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs
@@ -0,0 +1,255 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Memory;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Engine.Threed;
+using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
+using Ryujinx.Graphics.Gpu.Shader.DiskCache;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader.Cache
+{
+ /// <summary>
+ /// Class handling shader cache migrations.
+ /// </summary>
+ static class Migration
+ {
+ // Last codegen version before the migration to the new cache.
+ private const ulong ShaderCodeGenVersion = 3054;
+
+ /// <summary>
+ /// Migrates from the old cache format to the new one.
+ /// </summary>
+ /// <param name="context">GPU context</param>
+ /// <param name="hostStorage">Disk cache host storage (used to create the new shader files)</param>
+ /// <returns>Number of migrated shaders</returns>
+ public static int MigrateFromLegacyCache(GpuContext context, DiskCacheHostStorage hostStorage)
+ {
+ string baseCacheDirectory = CacheHelper.GetBaseCacheDirectory(GraphicsConfig.TitleId);
+ string cacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, CacheGraphicsApi.Guest, "", "program");
+
+ // If the directory does not exist, we have no old cache.
+ // Exist early as the CacheManager constructor will create the directories.
+ if (!Directory.Exists(cacheDirectory))
+ {
+ return 0;
+ }
+
+ if (GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null)
+ {
+ CacheManager cacheManager = new CacheManager(CacheGraphicsApi.OpenGL, CacheHashType.XxHash128, "glsl", GraphicsConfig.TitleId, ShaderCodeGenVersion);
+
+ bool isReadOnly = cacheManager.IsReadOnly;
+
+ HashSet<Hash128> invalidEntries = null;
+
+ if (isReadOnly)
+ {
+ Logger.Warning?.Print(LogClass.Gpu, "Loading shader cache in read-only mode (cache in use by another program!)");
+ }
+ else
+ {
+ invalidEntries = new HashSet<Hash128>();
+ }
+
+ ReadOnlySpan<Hash128> guestProgramList = cacheManager.GetGuestProgramList();
+
+ for (int programIndex = 0; programIndex < guestProgramList.Length; programIndex++)
+ {
+ Hash128 key = guestProgramList[programIndex];
+
+ byte[] guestProgram = cacheManager.GetGuestProgramByHash(ref key);
+
+ if (guestProgram == null)
+ {
+ Logger.Error?.Print(LogClass.Gpu, $"Ignoring orphan shader hash {key} in cache (is the cache incomplete?)");
+
+ continue;
+ }
+
+ ReadOnlySpan<byte> guestProgramReadOnlySpan = guestProgram;
+
+ ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader);
+
+ if (cachedShaderEntries[0].Header.Stage == ShaderStage.Compute)
+ {
+ Debug.Assert(cachedShaderEntries.Length == 1);
+
+ GuestShaderCacheEntry entry = cachedShaderEntries[0];
+
+ byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
+
+ Span<byte> codeSpan = entry.Code;
+ byte[] cb1Data = codeSpan.Slice(codeSpan.Length - entry.Header.Cb1DataSize).ToArray();
+
+ ShaderProgramInfo info = new ShaderProgramInfo(
+ Array.Empty<BufferDescriptor>(),
+ Array.Empty<BufferDescriptor>(),
+ Array.Empty<TextureDescriptor>(),
+ Array.Empty<TextureDescriptor>(),
+ ShaderStage.Compute,
+ false,
+ false,
+ 0,
+ 0);
+
+ GpuChannelComputeState computeState = new GpuChannelComputeState(
+ entry.Header.GpuAccessorHeader.ComputeLocalSizeX,
+ entry.Header.GpuAccessorHeader.ComputeLocalSizeY,
+ entry.Header.GpuAccessorHeader.ComputeLocalSizeZ,
+ entry.Header.GpuAccessorHeader.ComputeLocalMemorySize,
+ entry.Header.GpuAccessorHeader.ComputeSharedMemorySize);
+
+ ShaderSpecializationState specState = new ShaderSpecializationState(computeState);
+
+ foreach (var td in entry.TextureDescriptors)
+ {
+ var handle = td.Key;
+ var data = td.Value;
+
+ specState.RegisterTexture(
+ 0,
+ handle,
+ -1,
+ data.UnpackFormat(),
+ data.UnpackSrgb(),
+ data.UnpackTextureTarget(),
+ data.UnpackTextureCoordNormalized());
+ }
+
+ CachedShaderStage shader = new CachedShaderStage(info, code, cb1Data);
+ CachedShaderProgram program = new CachedShaderProgram(null, specState, shader);
+
+ hostStorage.AddShader(context, program, ReadOnlySpan<byte>.Empty);
+ }
+ else
+ {
+ Debug.Assert(cachedShaderEntries.Length == Constants.ShaderStages);
+
+ CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1];
+ List<ShaderProgram> shaderPrograms = new List<ShaderProgram>();
+
+ TransformFeedbackDescriptorOld[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader);
+
+ GuestShaderCacheEntry[] entries = cachedShaderEntries.ToArray();
+
+ GuestGpuAccessorHeader accessorHeader = entries[0].Header.GpuAccessorHeader;
+
+ TessMode tessMode = new TessMode();
+
+ int tessPatchType = accessorHeader.TessellationModePacked & 3;
+ int tessSpacing = (accessorHeader.TessellationModePacked >> 2) & 3;
+ bool tessCw = (accessorHeader.TessellationModePacked & 0x10) != 0;
+
+ tessMode.Packed = (uint)tessPatchType;
+ tessMode.Packed |= (uint)(tessSpacing << 4);
+
+ if (tessCw)
+ {
+ tessMode.Packed |= 0x100;
+ }
+
+ PrimitiveTopology topology = accessorHeader.PrimitiveTopology switch
+ {
+ InputTopology.Lines => PrimitiveTopology.Lines,
+ InputTopology.LinesAdjacency => PrimitiveTopology.LinesAdjacency,
+ InputTopology.Triangles => PrimitiveTopology.Triangles,
+ InputTopology.TrianglesAdjacency => PrimitiveTopology.TrianglesAdjacency,
+ _ => PrimitiveTopology.Points
+ };
+
+ GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState(
+ accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce),
+ topology,
+ tessMode);
+
+ TransformFeedbackDescriptor[] tfdNew = null;
+
+ if (tfd != null)
+ {
+ tfdNew = new TransformFeedbackDescriptor[tfd.Length];
+
+ for (int tfIndex = 0; tfIndex < tfd.Length; tfIndex++)
+ {
+ Array32<uint> varyingLocations = new Array32<uint>();
+ Span<byte> varyingLocationsSpan = MemoryMarshal.Cast<uint, byte>(varyingLocations.ToSpan());
+ tfd[tfIndex].VaryingLocations.CopyTo(varyingLocationsSpan.Slice(0, tfd[tfIndex].VaryingLocations.Length));
+
+ tfdNew[tfIndex] = new TransformFeedbackDescriptor(
+ tfd[tfIndex].BufferIndex,
+ tfd[tfIndex].Stride,
+ tfd[tfIndex].VaryingLocations.Length,
+ ref varyingLocations);
+ }
+ }
+
+ ShaderSpecializationState specState = new ShaderSpecializationState(graphicsState, tfdNew);
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ GuestShaderCacheEntry entry = entries[i];
+
+ if (entry == null)
+ {
+ continue;
+ }
+
+ ShaderProgramInfo info = new ShaderProgramInfo(
+ Array.Empty<BufferDescriptor>(),
+ Array.Empty<BufferDescriptor>(),
+ Array.Empty<TextureDescriptor>(),
+ Array.Empty<TextureDescriptor>(),
+ (ShaderStage)(i + 1),
+ false,
+ false,
+ 0,
+ 0);
+
+ // NOTE: Vertex B comes first in the shader cache.
+ byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
+ byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan(entry.Header.Size, entry.Header.SizeA).ToArray() : null;
+
+ Span<byte> codeSpan = entry.Code;
+ byte[] cb1Data = codeSpan.Slice(codeSpan.Length - entry.Header.Cb1DataSize).ToArray();
+
+ shaders[i + 1] = new CachedShaderStage(info, code, cb1Data);
+
+ if (code2 != null)
+ {
+ shaders[0] = new CachedShaderStage(null, code2, cb1Data);
+ }
+
+ foreach (var td in entry.TextureDescriptors)
+ {
+ var handle = td.Key;
+ var data = td.Value;
+
+ specState.RegisterTexture(
+ i,
+ handle,
+ -1,
+ data.UnpackFormat(),
+ data.UnpackSrgb(),
+ data.UnpackTextureTarget(),
+ data.UnpackTextureCoordNormalized());
+ }
+ }
+
+ CachedShaderProgram program = new CachedShaderProgram(null, specState, shaders);
+
+ hostStorage.AddShader(context, program, ReadOnlySpan<byte>.Empty);
+ }
+ }
+
+ return guestProgramList.Length;
+ }
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/TransformFeedbackDescriptorOld.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/TransformFeedbackDescriptorOld.cs
new file mode 100644
index 00000000..5e9c6711
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/TransformFeedbackDescriptorOld.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Shader.Cache
+{
+ struct TransformFeedbackDescriptorOld
+ {
+ public int BufferIndex { get; }
+ public int Stride { get; }
+
+ public byte[] VaryingLocations { get; }
+
+ public TransformFeedbackDescriptorOld(int bufferIndex, int stride, byte[] varyingLocations)
+ {
+ BufferIndex = bufferIndex;
+ Stride = stride;
+ VaryingLocations = varyingLocations ?? throw new ArgumentNullException(nameof(varyingLocations));
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/CachedGpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/CachedGpuAccessor.cs
deleted file mode 100644
index d65349a5..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/CachedGpuAccessor.cs
+++ /dev/null
@@ -1,222 +0,0 @@
-using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
-using Ryujinx.Graphics.Shader;
-using System;
-using System.Collections.Generic;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.Graphics.Gpu.Shader
-{
- class CachedGpuAccessor : TextureDescriptorCapableGpuAccessor, IGpuAccessor
- {
- private readonly ReadOnlyMemory<byte> _data;
- private readonly ReadOnlyMemory<byte> _cb1Data;
- private readonly GuestGpuAccessorHeader _header;
- private readonly Dictionary<int, GuestTextureDescriptor> _textureDescriptors;
- private readonly TransformFeedbackDescriptor[] _tfd;
-
- /// <summary>
- /// Creates a new instance of the cached GPU state accessor for shader translation.
- /// </summary>
- /// <param name="context">GPU context</param>
- /// <param name="data">The data of the shader</param>
- /// <param name="cb1Data">The constant buffer 1 data of the shader</param>
- /// <param name="header">The cache of the GPU accessor</param>
- /// <param name="guestTextureDescriptors">The cache of the texture descriptors</param>
- public CachedGpuAccessor(
- GpuContext context,
- ReadOnlyMemory<byte> data,
- ReadOnlyMemory<byte> cb1Data,
- GuestGpuAccessorHeader header,
- IReadOnlyDictionary<int, GuestTextureDescriptor> guestTextureDescriptors,
- TransformFeedbackDescriptor[] tfd) : base(context)
- {
- _data = data;
- _cb1Data = cb1Data;
- _header = header;
- _textureDescriptors = new Dictionary<int, GuestTextureDescriptor>();
-
- foreach (KeyValuePair<int, GuestTextureDescriptor> guestTextureDescriptor in guestTextureDescriptors)
- {
- _textureDescriptors.Add(guestTextureDescriptor.Key, guestTextureDescriptor.Value);
- }
-
- _tfd = tfd;
- }
-
- /// <summary>
- /// Reads data from the constant buffer 1.
- /// </summary>
- /// <param name="offset">Offset in bytes to read from</param>
- /// <returns>Value at the given offset</returns>
- public uint ConstantBuffer1Read(int offset)
- {
- return MemoryMarshal.Cast<byte, uint>(_cb1Data.Span.Slice(offset))[0];
- }
-
- /// <summary>
- /// Prints a log message.
- /// </summary>
- /// <param name="message">Message to print</param>
- public void Log(string message)
- {
- Logger.Warning?.Print(LogClass.Gpu, $"Shader translator: {message}");
- }
-
- /// <summary>
- /// Gets a span of the specified memory location, containing shader code.
- /// </summary>
- /// <param name="address">GPU virtual address of the data</param>
- /// <param name="minimumSize">Minimum size that the returned span may have</param>
- /// <returns>Span of the memory location</returns>
- public override ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize)
- {
- return MemoryMarshal.Cast<byte, ulong>(_data.Span.Slice((int)address));
- }
-
- /// <summary>
- /// Checks if a given memory address is mapped.
- /// </summary>
- /// <param name="address">GPU virtual address to be checked</param>
- /// <returns>True if the address is mapped, false otherwise</returns>
- public bool MemoryMapped(ulong address)
- {
- return address < (ulong)_data.Length;
- }
-
- /// <summary>
- /// Queries Local Size X for compute shaders.
- /// </summary>
- /// <returns>Local Size X</returns>
- public int QueryComputeLocalSizeX()
- {
- return _header.ComputeLocalSizeX;
- }
-
- /// <summary>
- /// Queries Local Size Y for compute shaders.
- /// </summary>
- /// <returns>Local Size Y</returns>
- public int QueryComputeLocalSizeY()
- {
- return _header.ComputeLocalSizeY;
- }
-
- /// <summary>
- /// Queries Local Size Z for compute shaders.
- /// </summary>
- /// <returns>Local Size Z</returns>
- public int QueryComputeLocalSizeZ()
- {
- return _header.ComputeLocalSizeZ;
- }
-
- /// <summary>
- /// Queries Local Memory size in bytes for compute shaders.
- /// </summary>
- /// <returns>Local Memory size in bytes</returns>
- public int QueryComputeLocalMemorySize()
- {
- return _header.ComputeLocalMemorySize;
- }
-
- /// <summary>
- /// Queries Shared Memory size in bytes for compute shaders.
- /// </summary>
- /// <returns>Shared Memory size in bytes</returns>
- public int QueryComputeSharedMemorySize()
- {
- return _header.ComputeSharedMemorySize;
- }
-
- /// <summary>
- /// Queries current primitive topology for geometry shaders.
- /// </summary>
- /// <returns>Current primitive topology</returns>
- public InputTopology QueryPrimitiveTopology()
- {
- return _header.PrimitiveTopology;
- }
-
- /// <summary>
- /// Queries the tessellation evaluation shader primitive winding order.
- /// </summary>
- /// <returns>True if the primitive winding order is clockwise, false if counter-clockwise</returns>
- public bool QueryTessCw()
- {
- return (_header.TessellationModePacked & 0x10) != 0;
- }
-
- /// <summary>
- /// Queries the tessellation evaluation shader abstract patch type.
- /// </summary>
- /// <returns>Abstract patch type</returns>
- public TessPatchType QueryTessPatchType()
- {
- return (TessPatchType)(_header.TessellationModePacked & 3);
- }
-
- /// <summary>
- /// Queries the tessellation evaluation shader spacing between tessellated vertices of the patch.
- /// </summary>
- /// <returns>Spacing between tessellated vertices of the patch</returns>
- public TessSpacing QueryTessSpacing()
- {
- return (TessSpacing)((_header.TessellationModePacked >> 2) & 3);
- }
-
- /// <summary>
- /// Gets the texture descriptor for a given texture on the pool.
- /// </summary>
- /// <param name="handle">Index of the texture (this is the word offset of the handle in the constant buffer)</param>
- /// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
- /// <returns>Texture descriptor</returns>
- public override Image.ITextureDescriptor GetTextureDescriptor(int handle, int cbufSlot)
- {
- if (!_textureDescriptors.TryGetValue(handle, out GuestTextureDescriptor textureDescriptor))
- {
- throw new ArgumentException();
- }
-
- return textureDescriptor;
- }
-
- /// <summary>
- /// Queries transform feedback enable state.
- /// </summary>
- /// <returns>True if the shader uses transform feedback, false otherwise</returns>
- public bool QueryTransformFeedbackEnabled()
- {
- return _tfd != null;
- }
-
- /// <summary>
- /// Queries the varying locations that should be written to the transform feedback buffer.
- /// </summary>
- /// <param name="bufferIndex">Index of the transform feedback buffer</param>
- /// <returns>Varying locations for the specified buffer</returns>
- public ReadOnlySpan<byte> QueryTransformFeedbackVaryingLocations(int bufferIndex)
- {
- return _tfd[bufferIndex].VaryingLocations;
- }
-
- /// <summary>
- /// Queries the stride (in bytes) of the per vertex data written into the transform feedback buffer.
- /// </summary>
- /// <param name="bufferIndex">Index of the transform feedback buffer</param>
- /// <returns>Stride for the specified buffer</returns>
- public int QueryTransformFeedbackStride(int bufferIndex)
- {
- return _tfd[bufferIndex].Stride;
- }
-
- /// <summary>
- /// Queries if host state forces early depth testing.
- /// </summary>
- /// <returns>True if early depth testing is forced</returns>
- public bool QueryEarlyZForce()
- {
- return (_header.StateFlags & GuestGpuStateFlags.EarlyZForce) != 0;
- }
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderBundle.cs b/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs
index efdbc3eb..3b4c65f3 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderBundle.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs
@@ -7,7 +7,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Represents a program composed of one or more shader stages (for graphics shaders),
/// or a single shader (for compute shaders).
/// </summary>
- class ShaderBundle : IDisposable
+ class CachedShaderProgram : IDisposable
{
/// <summary>
/// Host shader program object.
@@ -15,18 +15,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
public IProgram HostProgram { get; }
/// <summary>
+ /// GPU state used to create this version of the shader.
+ /// </summary>
+ public ShaderSpecializationState SpecializationState { get; }
+
+ /// <summary>
/// Compiled shader for each shader stage.
/// </summary>
- public ShaderCodeHolder[] Shaders { get; }
+ public CachedShaderStage[] Shaders { get; }
/// <summary>
/// Creates a new instance of the shader bundle.
/// </summary>
/// <param name="hostProgram">Host program with all the shader stages</param>
+ /// <param name="specializationState">GPU state used to create this version of the shader</param>
/// <param name="shaders">Shaders</param>
- public ShaderBundle(IProgram hostProgram, params ShaderCodeHolder[] shaders)
+ public CachedShaderProgram(IProgram hostProgram, ShaderSpecializationState specializationState, params CachedShaderStage[] shaders)
{
HostProgram = hostProgram;
+ SpecializationState = specializationState;
Shaders = shaders;
}
@@ -36,11 +43,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
public void Dispose()
{
HostProgram.Dispose();
-
- foreach (ShaderCodeHolder holder in Shaders)
- {
- holder?.HostShader?.Dispose();
- }
}
}
}
diff --git a/Ryujinx.Graphics.Gpu/Shader/CachedShaderStage.cs b/Ryujinx.Graphics.Gpu/Shader/CachedShaderStage.cs
new file mode 100644
index 00000000..22b08dd5
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/CachedShaderStage.cs
@@ -0,0 +1,38 @@
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ /// <summary>
+ /// Cached shader code for a single shader stage.
+ /// </summary>
+ class CachedShaderStage
+ {
+ /// <summary>
+ /// Shader program information.
+ /// </summary>
+ public ShaderProgramInfo Info { get; }
+
+ /// <summary>
+ /// Maxwell binary shader code.
+ /// </summary>
+ public byte[] Code { get; }
+
+ /// <summary>
+ /// Constant buffer 1 data accessed by the shader.
+ /// </summary>
+ public byte[] Cb1Data { get; }
+
+ /// <summary>
+ /// Creates a new instance of the shader code holder.
+ /// </summary>
+ /// <param name="info">Shader program information</param>
+ /// <param name="code">Maxwell binary shader code</param>
+ /// <param name="cb1Data">Constant buffer 1 data accessed by the shader</param>
+ public CachedShaderStage(ShaderProgramInfo info, byte[] code, byte[] cb1Data)
+ {
+ Info = info;
+ Code = code;
+ Cb1Data = cb1Data;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ComputeShaderCacheHashTable.cs b/Ryujinx.Graphics.Gpu/Shader/ComputeShaderCacheHashTable.cs
new file mode 100644
index 00000000..08154df3
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/ComputeShaderCacheHashTable.cs
@@ -0,0 +1,68 @@
+using Ryujinx.Graphics.Gpu.Shader.HashTable;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ /// <summary>
+ /// Compute shader cache hash table.
+ /// </summary>
+ class ComputeShaderCacheHashTable
+ {
+ private readonly PartitionedHashTable<ShaderSpecializationList> _cache;
+ private readonly List<CachedShaderProgram> _shaderPrograms;
+
+ /// <summary>
+ /// Creates a new compute shader cache hash table.
+ /// </summary>
+ public ComputeShaderCacheHashTable()
+ {
+ _cache = new PartitionedHashTable<ShaderSpecializationList>();
+ _shaderPrograms = new List<CachedShaderProgram>();
+ }
+
+ /// <summary>
+ /// Adds a program to the cache.
+ /// </summary>
+ /// <param name="program">Program to be added</param>
+ public void Add(CachedShaderProgram program)
+ {
+ var specList = _cache.GetOrAdd(program.Shaders[0].Code, new ShaderSpecializationList());
+ specList.Add(program);
+ _shaderPrograms.Add(program);
+ }
+
+ /// <summary>
+ /// Tries to find a cached program.
+ /// </summary>
+ /// <param name="channel">GPU channel</param>
+ /// <param name="poolState">Texture pool state</param>
+ /// <param name="gpuVa">GPU virtual address of the compute shader</param>
+ /// <param name="program">Cached host program for the given state, if found</param>
+ /// <param name="cachedGuestCode">Cached guest code, if any found</param>
+ /// <returns>True if a cached host program was found, false otherwise</returns>
+ public bool TryFind(
+ GpuChannel channel,
+ GpuChannelPoolState poolState,
+ ulong gpuVa,
+ out CachedShaderProgram program,
+ out byte[] cachedGuestCode)
+ {
+ program = null;
+ ShaderCodeAccessor codeAccessor = new ShaderCodeAccessor(channel.MemoryManager, gpuVa);
+ bool hasSpecList = _cache.TryFindItem(codeAccessor, out var specList, out cachedGuestCode);
+ return hasSpecList && specList.TryFindForCompute(channel, poolState, out program);
+ }
+
+ /// <summary>
+ /// Gets all programs that have been added to the table.
+ /// </summary>
+ /// <returns>Programs added to the table</returns>
+ public IEnumerable<CachedShaderProgram> GetPrograms()
+ {
+ foreach (var program in _shaderPrograms)
+ {
+ yield return program;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs
new file mode 100644
index 00000000..5c5e41c6
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs
@@ -0,0 +1,138 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using System;
+using System.IO;
+
+namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
+{
+ /// <summary>
+ /// Represents a background disk cache writer.
+ /// </summary>
+ class BackgroundDiskCacheWriter : IDisposable
+ {
+ /// <summary>
+ /// Possible operation to do on the <see cref="_fileWriterWorkerQueue"/>.
+ /// </summary>
+ private enum CacheFileOperation
+ {
+ /// <summary>
+ /// Operation to add a shader to the cache.
+ /// </summary>
+ AddShader
+ }
+
+ /// <summary>
+ /// Represents an operation to perform on the <see cref="_fileWriterWorkerQueue"/>.
+ /// </summary>
+ private struct CacheFileOperationTask
+ {
+ /// <summary>
+ /// The type of operation to perform.
+ /// </summary>
+ public readonly CacheFileOperation Type;
+
+ /// <summary>
+ /// The data associated to this operation or null.
+ /// </summary>
+ public readonly object Data;
+
+ public CacheFileOperationTask(CacheFileOperation type, object data)
+ {
+ Type = type;
+ Data = data;
+ }
+ }
+
+ /// <summary>
+ /// Background shader cache write information.
+ /// </summary>
+ private struct AddShaderData
+ {
+ /// <summary>
+ /// Cached shader program.
+ /// </summary>
+ public readonly CachedShaderProgram Program;
+
+ /// <summary>
+ /// Binary host code.
+ /// </summary>
+ public readonly byte[] HostCode;
+
+ /// <summary>
+ /// Creates a new background shader cache write information.
+ /// </summary>
+ /// <param name="program">Cached shader program</param>
+ /// <param name="hostCode">Binary host code</param>
+ public AddShaderData(CachedShaderProgram program, byte[] hostCode)
+ {
+ Program = program;
+ HostCode = hostCode;
+ }
+ }
+
+ private readonly GpuContext _context;
+ private readonly DiskCacheHostStorage _hostStorage;
+ private readonly AsyncWorkQueue<CacheFileOperationTask> _fileWriterWorkerQueue;
+
+ /// <summary>
+ /// Creates a new background disk cache writer.
+ /// </summary>
+ /// <param name="context">GPU context</param>
+ /// <param name="hostStorage">Disk cache host storage</param>
+ public BackgroundDiskCacheWriter(GpuContext context, DiskCacheHostStorage hostStorage)
+ {
+ _context = context;
+ _hostStorage = hostStorage;
+ _fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(ProcessTask, "Gpu.BackgroundDiskCacheWriter");
+ }
+
+ /// <summary>
+ /// Processes a shader cache background operation.
+ /// </summary>
+ /// <param name="task">Task to process</param>
+ private void ProcessTask(CacheFileOperationTask task)
+ {
+ switch (task.Type)
+ {
+ case CacheFileOperation.AddShader:
+ AddShaderData data = (AddShaderData)task.Data;
+ try
+ {
+ _hostStorage.AddShader(_context, data.Program, data.HostCode);
+ }
+ catch (DiskCacheLoadException diskCacheLoadException)
+ {
+ Logger.Error?.Print(LogClass.Gpu, $"Error writing shader to disk cache. {diskCacheLoadException.Message}");
+ }
+ catch (IOException ioException)
+ {
+ Logger.Error?.Print(LogClass.Gpu, $"Error writing shader to disk cache. {ioException.Message}");
+ }
+ break;
+ }
+ }
+
+ /// <summary>
+ /// Adds a shader program to be cached in the background.
+ /// </summary>
+ /// <param name="program">Shader program to cache</param>
+ /// <param name="hostCode">Host binary code of the program</param>
+ public void AddShader(CachedShaderProgram program, byte[] hostCode)
+ {
+ _fileWriterWorkerQueue.Add(new CacheFileOperationTask(CacheFileOperation.AddShader, new AddShaderData(program, hostCode)));
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _fileWriterWorkerQueue.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs
new file mode 100644
index 00000000..50e37033
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs
@@ -0,0 +1,216 @@
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
+{
+ /// <summary>
+ /// Binary data serializer.
+ /// </summary>
+ struct BinarySerializer
+ {
+ private readonly Stream _stream;
+ private Stream _activeStream;
+
+ /// <summary>
+ /// Creates a new binary serializer.
+ /// </summary>
+ /// <param name="stream">Stream to read from or write into</param>
+ public BinarySerializer(Stream stream)
+ {
+ _stream = stream;
+ _activeStream = stream;
+ }
+
+ /// <summary>
+ /// Reads data from the stream.
+ /// </summary>
+ /// <typeparam name="T">Type of the data</typeparam>
+ /// <param name="data">Data read</param>
+ public void Read<T>(ref T data) where T : unmanaged
+ {
+ Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1));
+ for (int offset = 0; offset < buffer.Length;)
+ {
+ offset += _activeStream.Read(buffer.Slice(offset));
+ }
+ }
+
+ /// <summary>
+ /// Tries to read data from the stream.
+ /// </summary>
+ /// <typeparam name="T">Type of the data</typeparam>
+ /// <param name="data">Data read</param>
+ /// <returns>True if the read was successful, false otherwise</returns>
+ public bool TryRead<T>(ref T data) where T : unmanaged
+ {
+ // Length is unknown on compressed streams.
+ if (_activeStream == _stream)
+ {
+ int size = Unsafe.SizeOf<T>();
+ if (_activeStream.Length - _activeStream.Position < size)
+ {
+ return false;
+ }
+ }
+
+ Read(ref data);
+ return true;
+ }
+
+ /// <summary>
+ /// Reads data prefixed with a magic and size from the stream.
+ /// </summary>
+ /// <typeparam name="T">Type of the data</typeparam>
+ /// <param name="data">Data read</param>
+ /// <param name="magic">Expected magic value, for validation</param>
+ public void ReadWithMagicAndSize<T>(ref T data, uint magic) where T : unmanaged
+ {
+ uint actualMagic = 0;
+ int size = 0;
+ Read(ref actualMagic);
+ Read(ref size);
+
+ if (actualMagic != magic)
+ {
+ throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedInvalidMagic);
+ }
+
+ // Structs are expected to expand but not shrink between versions.
+ if (size > Unsafe.SizeOf<T>())
+ {
+ throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedInvalidLength);
+ }
+
+ Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1)).Slice(0, size);
+ for (int offset = 0; offset < buffer.Length;)
+ {
+ offset += _activeStream.Read(buffer.Slice(offset));
+ }
+ }
+
+ /// <summary>
+ /// Writes data into the stream.
+ /// </summary>
+ /// <typeparam name="T">Type of the data</typeparam>
+ /// <param name="data">Data to be written</param>
+ public void Write<T>(ref T data) where T : unmanaged
+ {
+ Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1));
+ _activeStream.Write(buffer);
+ }
+
+ /// <summary>
+ /// Writes data prefixed with a magic and size into the stream.
+ /// </summary>
+ /// <typeparam name="T">Type of the data</typeparam>
+ /// <param name="data">Data to write</param>
+ /// <param name="magic">Magic value to write</param>
+ public void WriteWithMagicAndSize<T>(ref T data, uint magic) where T : unmanaged
+ {
+ int size = Unsafe.SizeOf<T>();
+ Write(ref magic);
+ Write(ref size);
+ Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1));
+ _activeStream.Write(buffer);
+ }
+
+ /// <summary>
+ /// Indicates that all data that will be read from the stream has been compressed.
+ /// </summary>
+ public void BeginCompression()
+ {
+ CompressionAlgorithm algorithm = CompressionAlgorithm.None;
+ Read(ref algorithm);
+
+ if (algorithm == CompressionAlgorithm.Deflate)
+ {
+ _activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true);
+ }
+ }
+
+ /// <summary>
+ /// Indicates that all data that will be written into the stream should be compressed.
+ /// </summary>
+ /// <param name="algorithm">Compression algorithm that should be used</param>
+ public void BeginCompression(CompressionAlgorithm algorithm)
+ {
+ Write(ref algorithm);
+
+ if (algorithm == CompressionAlgorithm.Deflate)
+ {
+ _activeStream = new DeflateStream(_stream, CompressionLevel.SmallestSize, true);
+ }
+ }
+
+ /// <summary>
+ /// Indicates the end of a compressed chunck.
+ /// </summary>
+ /// <remarks>
+ /// Any data written after this will not be compressed unless <see cref="BeginCompression(CompressionAlgorithm)"/> is called again.
+ /// Any data read after this will be assumed to be uncompressed unless <see cref="BeginCompression"/> is called again.
+ /// </remarks>
+ public void EndCompression()
+ {
+ if (_activeStream != _stream)
+ {
+ _activeStream.Dispose();
+ _activeStream = _stream;
+ }
+ }
+
+ /// <summary>
+ /// Reads compressed data from the stream.
+ /// </summary>
+ /// <remarks>
+ /// <paramref name="data"/> must have the exact length of the uncompressed data,
+ /// otherwise decompression will fail.
+ /// </remarks>
+ /// <param name="stream">Stream to read from</param>
+ /// <param name="data">Buffer to write the uncompressed data into</param>
+ public static void ReadCompressed(Stream stream, Span<byte> data)
+ {
+ CompressionAlgorithm algorithm = (CompressionAlgorithm)stream.ReadByte();
+
+ switch (algorithm)
+ {
+ case CompressionAlgorithm.None:
+ stream.Read(data);
+ break;
+ case CompressionAlgorithm.Deflate:
+ stream = new DeflateStream(stream, CompressionMode.Decompress, true);
+ for (int offset = 0; offset < data.Length;)
+ {
+ offset += stream.Read(data.Slice(offset));
+ }
+ stream.Dispose();
+ break;
+ }
+ }
+
+ /// <summary>
+ /// Compresses and writes the compressed data into the stream.
+ /// </summary>
+ /// <param name="stream">Stream to write into</param>
+ /// <param name="data">Data to compress</param>
+ /// <param name="algorithm">Compression algorithm to be used</param>
+ public static void WriteCompressed(Stream stream, ReadOnlySpan<byte> data, CompressionAlgorithm algorithm)
+ {
+ stream.WriteByte((byte)algorithm);
+
+ switch (algorithm)
+ {
+ case CompressionAlgorithm.None:
+ stream.Write(data);
+ break;
+ case CompressionAlgorithm.Deflate:
+ stream = new DeflateStream(stream, CompressionLevel.SmallestSize, true);
+ stream.Write(data);
+ stream.Dispose();
+ break;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs
new file mode 100644
index 00000000..a46e1ef7
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
+{
+ /// <summary>
+ /// Algorithm used to compress the cache.
+ /// </summary>
+ enum CompressionAlgorithm : byte
+ {
+ /// <summary>
+ /// No compression, the data is stored as-is.
+ /// </summary>
+ None,
+
+ /// <summary>
+ /// Deflate compression (RFC 1951).
+ /// </summary>
+ Deflate
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs
new file mode 100644
index 00000000..c8a9f7ff
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs
@@ -0,0 +1,57 @@
+using Ryujinx.Common.Logging;
+using System.IO;
+
+namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
+{
+ /// <summary>
+ /// Common disk cache utility methods.
+ /// </summary>
+ static class DiskCacheCommon
+ {
+ /// <summary>
+ /// Opens a file for read or write.
+ /// </summary>
+ /// <param name="basePath">Base path of the file (should not include the file name)</param>
+ /// <param name="fileName">Name of the file</param>
+ /// <param name="writable">Indicates if the file will be read or written</param>
+ /// <returns>File stream</returns>
+ public static FileStream OpenFile(string basePath, string fileName, bool writable)
+ {
+ string fullPath = Path.Combine(basePath, fileName);
+
+ FileMode mode;
+ FileAccess access;
+
+ if (writable)
+ {
+ mode = FileMode.OpenOrCreate;
+ access = FileAccess.ReadWrite;
+ }
+ else
+ {
+ mode = FileMode.Open;
+ access = FileAccess.Read;
+ }
+
+ try
+ {
+ return new FileStream(fullPath, mode, access, FileShare.Read);
+ }
+ catch (IOException ioException)
+ {
+ Logger.Error?.Print(LogClass.Gpu, $"Could not access file \"{fullPath}\". {ioException.Message}");
+
+ throw new DiskCacheLoadException(DiskCacheLoadResult.NoAccess);
+ }
+ }
+
+ /// <summary>
+ /// Gets the compression algorithm that should be used when writing the disk cache.
+ /// </summary>
+ /// <returns>Compression algorithm</returns>
+ public static CompressionAlgorithm GetCompressionAlgorithm()
+ {
+ return CompressionAlgorithm.Deflate;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
new file mode 100644
index 00000000..b1c04eac
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
@@ -0,0 +1,202 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
+{
+ /// <summary>
+ /// Represents a GPU state and memory accessor.
+ /// </summary>
+ class DiskCacheGpuAccessor : GpuAccessorBase, IGpuAccessor
+ {
+ private readonly ReadOnlyMemory<byte> _data;
+ private readonly ReadOnlyMemory<byte> _cb1Data;
+ private readonly ShaderSpecializationState _oldSpecState;
+ private readonly ShaderSpecializationState _newSpecState;
+ private readonly int _stageIndex;
+ private ResourceCounts _resourceCounts;
+
+ /// <summary>
+ /// Creates a new instance of the cached GPU state accessor for shader translation.
+ /// </summary>
+ /// <param name="context">GPU context</param>
+ /// <param name="data">The 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="newSpecState">Shader specialization state of the recompiled shader</param>
+ /// <param name="stageIndex">Shader stage index</param>
+ public DiskCacheGpuAccessor(
+ GpuContext context,
+ ReadOnlyMemory<byte> data,
+ ReadOnlyMemory<byte> cb1Data,
+ ShaderSpecializationState oldSpecState,
+ ShaderSpecializationState newSpecState,
+ ResourceCounts counts,
+ int stageIndex) : base(context)
+ {
+ _data = data;
+ _cb1Data = cb1Data;
+ _oldSpecState = oldSpecState;
+ _newSpecState = newSpecState;
+ _stageIndex = stageIndex;
+ _resourceCounts = counts;
+ }
+
+ /// <inheritdoc/>
+ public uint ConstantBuffer1Read(int offset)
+ {
+ if (offset + sizeof(uint) > _cb1Data.Length)
+ {
+ throw new DiskCacheLoadException(DiskCacheLoadResult.InvalidCb1DataLength);
+ }
+
+ return MemoryMarshal.Cast<byte, uint>(_cb1Data.Span.Slice(offset))[0];
+ }
+
+ /// <inheritdoc/>
+ public void Log(string message)
+ {
+ Logger.Warning?.Print(LogClass.Gpu, $"Shader translator: {message}");
+ }
+
+ /// <inheritdoc/>
+ public ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize)
+ {
+ return MemoryMarshal.Cast<byte, ulong>(_data.Span.Slice((int)address));
+ }
+
+ /// <inheritdoc/>
+ public int QueryBindingConstantBuffer(int index)
+ {
+ return _resourceCounts.UniformBuffersCount++;
+ }
+
+ /// <inheritdoc/>
+ public int QueryBindingStorageBuffer(int index)
+ {
+ return _resourceCounts.StorageBuffersCount++;
+ }
+
+ /// <inheritdoc/>
+ public int QueryBindingTexture(int index)
+ {
+ return _resourceCounts.TexturesCount++;
+ }
+
+ /// <inheritdoc/>
+ public int QueryBindingImage(int index)
+ {
+ return _resourceCounts.ImagesCount++;
+ }
+
+ /// <inheritdoc/>
+ public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX;
+
+ /// <inheritdoc/>
+ public int QueryComputeLocalSizeY() => _oldSpecState.ComputeState.LocalSizeY;
+
+ /// <inheritdoc/>
+ public int QueryComputeLocalSizeZ() => _oldSpecState.ComputeState.LocalSizeZ;
+
+ /// <inheritdoc/>
+ public int QueryComputeLocalMemorySize() => _oldSpecState.ComputeState.LocalMemorySize;
+
+ /// <inheritdoc/>
+ public int QueryComputeSharedMemorySize() => _oldSpecState.ComputeState.SharedMemorySize;
+
+ /// <inheritdoc/>
+ public uint QueryConstantBufferUse()
+ {
+ _newSpecState.RecordConstantBufferUse(_stageIndex, _oldSpecState.ConstantBufferUse[_stageIndex]);
+ return _oldSpecState.ConstantBufferUse[_stageIndex];
+ }
+
+ /// <inheritdoc/>
+ public InputTopology QueryPrimitiveTopology()
+ {
+ _newSpecState.RecordPrimitiveTopology();
+ return ConvertToInputTopology(_oldSpecState.GraphicsState.Topology, _oldSpecState.GraphicsState.TessellationMode);
+ }
+
+ /// <inheritdoc/>
+ public bool QueryTessCw()
+ {
+ return _oldSpecState.GraphicsState.TessellationMode.UnpackCw();
+ }
+
+ /// <inheritdoc/>
+ public TessPatchType QueryTessPatchType()
+ {
+ return _oldSpecState.GraphicsState.TessellationMode.UnpackPatchType();
+ }
+
+ /// <inheritdoc/>
+ public TessSpacing QueryTessSpacing()
+ {
+ return _oldSpecState.GraphicsState.TessellationMode.UnpackSpacing();
+ }
+
+ /// <inheritdoc/>
+ public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
+ {
+ _newSpecState.RecordTextureFormat(_stageIndex, handle, cbufSlot);
+ (uint format, bool formatSrgb) = _oldSpecState.GetFormat(_stageIndex, handle, cbufSlot);
+ return ConvertToTextureFormat(format, formatSrgb);
+ }
+
+ /// <inheritdoc/>
+ public SamplerType QuerySamplerType(int handle, int cbufSlot)
+ {
+ _newSpecState.RecordTextureSamplerType(_stageIndex, handle, cbufSlot);
+ return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType();
+ }
+
+ /// <inheritdoc/>
+ public bool QueryTextureCoordNormalized(int handle, int cbufSlot)
+ {
+ _newSpecState.RecordTextureCoordNormalized(_stageIndex, handle, cbufSlot);
+ return _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot);
+ }
+
+ /// <inheritdoc/>
+ public bool QueryTransformFeedbackEnabled()
+ {
+ return _oldSpecState.TransformFeedbackDescriptors != null;
+ }
+
+ /// <inheritdoc/>
+ public ReadOnlySpan<byte> QueryTransformFeedbackVaryingLocations(int bufferIndex)
+ {
+ return _oldSpecState.TransformFeedbackDescriptors[bufferIndex].AsSpan();
+ }
+
+ /// <inheritdoc/>
+ public int QueryTransformFeedbackStride(int bufferIndex)
+ {
+ return _oldSpecState.TransformFeedbackDescriptors[bufferIndex].Stride;
+ }
+
+ /// <inheritdoc/>
+ public bool QueryEarlyZForce()
+ {
+ _newSpecState.RecordEarlyZForce();
+ return _oldSpecState.GraphicsState.EarlyZForce;
+ }
+
+ /// <inheritdoc/>
+ public void RegisterTexture(int handle, int cbufSlot)
+ {
+ if (!_oldSpecState.TextureRegistered(_stageIndex, handle, cbufSlot))
+ {
+ throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureDescriptor);
+ }
+
+ (uint format, bool formatSrgb) = _oldSpecState.GetFormat(_stageIndex, handle, cbufSlot);
+ TextureTarget target = _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot);
+ bool coordNormalized = _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot);
+ _newSpecState.RegisterTexture(_stageIndex, handle, cbufSlot, format, formatSrgb, target, coordNormalized);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
new file mode 100644
index 00000000..4e338094
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
@@ -0,0 +1,459 @@
+using Ryujinx.Common;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
+{
+ /// <summary>
+ /// On-disk shader cache storage for guest code.
+ /// </summary>
+ class DiskCacheGuestStorage
+ {
+ private const uint TocMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'G' << 24);
+
+ private const ushort VersionMajor = 1;
+ private const ushort VersionMinor = 0;
+ private const uint VersionPacked = ((uint)VersionMajor << 16) | VersionMinor;
+
+ private const string TocFileName = "guest.toc";
+ private const string DataFileName = "guest.data";
+
+ private readonly string _basePath;
+
+ /// <summary>
+ /// TOC (Table of contents) file header.
+ /// </summary>
+ private struct TocHeader
+ {
+ /// <summary>
+ /// Magic value, for validation and identification purposes.
+ /// </summary>
+ public uint Magic;
+
+ /// <summary>
+ /// File format version.
+ /// </summary>
+ public uint Version;
+
+ /// <summary>
+ /// Header padding.
+ /// </summary>
+ public uint Padding;
+
+ /// <summary>
+ /// Number of modifications to the file, also the shaders count.
+ /// </summary>
+ public uint ModificationsCount;
+
+ /// <summary>
+ /// Reserved space, to be used in the future. Write as zero.
+ /// </summary>
+ public ulong Reserved;
+
+ /// <summary>
+ /// Reserved space, to be used in the future. Write as zero.
+ /// </summary>
+ public ulong Reserved2;
+ }
+
+ /// <summary>
+ /// TOC (Table of contents) file entry.
+ /// </summary>
+ private struct TocEntry
+ {
+ /// <summary>
+ /// Offset of the data on the data file.
+ /// </summary>
+ public uint Offset;
+
+ /// <summary>
+ /// Code size.
+ /// </summary>
+ public uint CodeSize;
+
+ /// <summary>
+ /// Constant buffer 1 data size.
+ /// </summary>
+ public uint Cb1DataSize;
+
+ /// <summary>
+ /// Hash of the code and constant buffer data.
+ /// </summary>
+ public uint Hash;
+ }
+
+ /// <summary>
+ /// TOC (Table of contents) memory cache entry.
+ /// </summary>
+ private struct TocMemoryEntry
+ {
+ /// <summary>
+ /// Offset of the data on the data file.
+ /// </summary>
+ public uint Offset;
+
+ /// <summary>
+ /// Code size.
+ /// </summary>
+ public uint CodeSize;
+
+ /// <summary>
+ /// Constant buffer 1 data size.
+ /// </summary>
+ public uint Cb1DataSize;
+
+ /// <summary>
+ /// Index of the shader on the cache.
+ /// </summary>
+ public readonly int Index;
+
+ /// <summary>
+ /// Creates a new TOC memory entry.
+ /// </summary>
+ /// <param name="offset">Offset of the data on the data file</param>
+ /// <param name="codeSize">Code size</param>
+ /// <param name="cb1DataSize">Constant buffer 1 data size</param>
+ /// <param name="index">Index of the shader on the cache</param>
+ public TocMemoryEntry(uint offset, uint codeSize, uint cb1DataSize, int index)
+ {
+ Offset = offset;
+ CodeSize = codeSize;
+ Cb1DataSize = cb1DataSize;
+ Index = index;
+ }
+ }
+
+ private Dictionary<uint, List<TocMemoryEntry>> _toc;
+ private uint _tocModificationsCount;
+
+ private (byte[], byte[])[] _cache;
+
+ /// <summary>
+ /// Creates a new disk cache guest storage.
+ /// </summary>
+ /// <param name="basePath">Base path of the disk shader cache</param>
+ public DiskCacheGuestStorage(string basePath)
+ {
+ _basePath = basePath;
+ }
+
+ /// <summary>
+ /// Checks if the TOC (table of contents) file for the guest cache exists.
+ /// </summary>
+ /// <returns>True if the file exists, false otherwise</returns>
+ public bool TocFileExists()
+ {
+ return File.Exists(Path.Combine(_basePath, TocFileName));
+ }
+
+ /// <summary>
+ /// Checks if the data file for the guest cache exists.
+ /// </summary>
+ /// <returns>True if the file exists, false otherwise</returns>
+ public bool DataFileExists()
+ {
+ return File.Exists(Path.Combine(_basePath, DataFileName));
+ }
+
+ /// <summary>
+ /// Opens the guest cache TOC (table of contents) file.
+ /// </summary>
+ /// <returns>File stream</returns>
+ public Stream OpenTocFileStream()
+ {
+ return DiskCacheCommon.OpenFile(_basePath, TocFileName, writable: false);
+ }
+
+ /// <summary>
+ /// Opens the guest cache data file.
+ /// </summary>
+ /// <returns>File stream</returns>
+ public Stream OpenDataFileStream()
+ {
+ return DiskCacheCommon.OpenFile(_basePath, DataFileName, writable: false);
+ }
+
+ /// <summary>
+ /// Clear all content from the guest cache files.
+ /// </summary>
+ public void ClearCache()
+ {
+ using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, TocFileName, writable: true);
+ using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, DataFileName, writable: true);
+
+ tocFileStream.SetLength(0);
+ dataFileStream.SetLength(0);
+ }
+
+ /// <summary>
+ /// Loads the guest cache from file or memory cache.
+ /// </summary>
+ /// <param name="tocFileStream">Guest TOC file stream</param>
+ /// <param name="dataFileStream">Guest data file stream</param>
+ /// <param name="index">Guest shader index</param>
+ /// <returns>Tuple with the guest code and constant buffer 1 data, respectively</returns>
+ public (byte[], byte[]) LoadShader(Stream tocFileStream, Stream dataFileStream, int index)
+ {
+ if (_cache == null || index >= _cache.Length)
+ {
+ _cache = new (byte[], byte[])[Math.Max(index + 1, GetShadersCountFromLength(tocFileStream.Length))];
+ }
+
+ (byte[] guestCode, byte[] cb1Data) = _cache[index];
+
+ if (guestCode == null || cb1Data == null)
+ {
+ BinarySerializer tocReader = new BinarySerializer(tocFileStream);
+ tocFileStream.Seek(Unsafe.SizeOf<TocHeader>() + index * Unsafe.SizeOf<TocEntry>(), SeekOrigin.Begin);
+
+ TocEntry entry = new TocEntry();
+ tocReader.Read(ref entry);
+
+ guestCode = new byte[entry.CodeSize];
+ cb1Data = new byte[entry.Cb1DataSize];
+
+ if (entry.Offset >= (ulong)dataFileStream.Length)
+ {
+ throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
+ }
+
+ dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
+ dataFileStream.Read(cb1Data);
+ BinarySerializer.ReadCompressed(dataFileStream, guestCode);
+
+ _cache[index] = (guestCode, cb1Data);
+ }
+
+ return (guestCode, cb1Data);
+ }
+
+ /// <summary>
+ /// Clears guest code memory cache, forcing future loads to be from file.
+ /// </summary>
+ public void ClearMemoryCache()
+ {
+ _cache = null;
+ }
+
+ /// <summary>
+ /// Calculates the guest shaders count from the TOC file length.
+ /// </summary>
+ /// <param name="length">TOC file length</param>
+ /// <returns>Shaders count</returns>
+ private static int GetShadersCountFromLength(long length)
+ {
+ return (int)((length - Unsafe.SizeOf<TocHeader>()) / Unsafe.SizeOf<TocEntry>());
+ }
+
+ /// <summary>
+ /// Adds a guest shader to the cache.
+ /// </summary>
+ /// <remarks>
+ /// If the shader is already on the cache, the existing index will be returned and nothing will be written.
+ /// </remarks>
+ /// <param name="data">Guest code</param>
+ /// <param name="cb1Data">Constant buffer 1 data accessed by the code</param>
+ /// <returns>Index of the shader on the cache</returns>
+ public int AddShader(ReadOnlySpan<byte> data, ReadOnlySpan<byte> cb1Data)
+ {
+ using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, TocFileName, writable: true);
+ using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, DataFileName, writable: true);
+
+ TocHeader header = new TocHeader();
+
+ LoadOrCreateToc(tocFileStream, ref header);
+
+ uint hash = CalcHash(data, cb1Data);
+
+ if (_toc.TryGetValue(hash, out var list))
+ {
+ foreach (var entry in list)
+ {
+ if (data.Length != entry.CodeSize || cb1Data.Length != entry.Cb1DataSize)
+ {
+ continue;
+ }
+
+ dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
+ byte[] cachedCode = new byte[entry.CodeSize];
+ byte[] cachedCb1Data = new byte[entry.Cb1DataSize];
+ dataFileStream.Read(cachedCb1Data);
+ BinarySerializer.ReadCompressed(dataFileStream, cachedCode);
+
+ if (data.SequenceEqual(cachedCode) && cb1Data.SequenceEqual(cachedCb1Data))
+ {
+ return entry.Index;
+ }
+ }
+ }
+
+ return WriteNewEntry(tocFileStream, dataFileStream, ref header, data, cb1Data, hash);
+ }
+
+ /// <summary>
+ /// Loads the guest cache TOC file, or create a new one if not present.
+ /// </summary>
+ /// <param name="tocFileStream">Guest TOC file stream</param>
+ /// <param name="header">Set to the TOC file header</param>
+ private void LoadOrCreateToc(Stream tocFileStream, ref TocHeader header)
+ {
+ BinarySerializer reader = new BinarySerializer(tocFileStream);
+
+ if (!reader.TryRead(ref header) || header.Magic != TocMagic || header.Version != VersionPacked)
+ {
+ CreateToc(tocFileStream, ref header);
+ }
+
+ if (_toc == null || header.ModificationsCount != _tocModificationsCount)
+ {
+ if (!LoadTocEntries(tocFileStream, ref reader))
+ {
+ CreateToc(tocFileStream, ref header);
+ }
+
+ _tocModificationsCount = header.ModificationsCount;
+ }
+ }
+
+ /// <summary>
+ /// Creates a new guest cache TOC file.
+ /// </summary>
+ /// <param name="tocFileStream">Guest TOC file stream</param>
+ /// <param name="header">Set to the TOC header</param>
+ private void CreateToc(Stream tocFileStream, ref TocHeader header)
+ {
+ BinarySerializer writer = new BinarySerializer(tocFileStream);
+
+ header.Magic = TocMagic;
+ header.Version = VersionPacked;
+ header.Padding = 0;
+ header.ModificationsCount = 0;
+ header.Reserved = 0;
+ header.Reserved2 = 0;
+
+ if (tocFileStream.Length > 0)
+ {
+ tocFileStream.Seek(0, SeekOrigin.Begin);
+ tocFileStream.SetLength(0);
+ }
+
+ writer.Write(ref header);
+ }
+
+ /// <summary>
+ /// Reads all the entries on the guest TOC file.
+ /// </summary>
+ /// <param name="tocFileStream">Guest TOC file stream</param>
+ /// <param name="reader">TOC file reader</param>
+ /// <returns>True if the operation was successful, false otherwise</returns>
+ private bool LoadTocEntries(Stream tocFileStream, ref BinarySerializer reader)
+ {
+ _toc = new Dictionary<uint, List<TocMemoryEntry>>();
+
+ TocEntry entry = new TocEntry();
+ int index = 0;
+
+ while (tocFileStream.Position < tocFileStream.Length)
+ {
+ if (!reader.TryRead(ref entry))
+ {
+ return false;
+ }
+
+ AddTocMemoryEntry(entry.Offset, entry.CodeSize, entry.Cb1DataSize, entry.Hash, index++);
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Writes a new guest code entry into the file.
+ /// </summary>
+ /// <param name="tocFileStream">TOC file stream</param>
+ /// <param name="dataFileStream">Data file stream</param>
+ /// <param name="header">TOC header, to be updated with the new count</param>
+ /// <param name="data">Guest code</param>
+ /// <param name="cb1Data">Constant buffer 1 data accessed by the guest code</param>
+ /// <param name="hash">Code and constant buffer data hash</param>
+ /// <returns>Entry index</returns>
+ private int WriteNewEntry(
+ Stream tocFileStream,
+ Stream dataFileStream,
+ ref TocHeader header,
+ ReadOnlySpan<byte> data,
+ ReadOnlySpan<byte> cb1Data,
+ uint hash)
+ {
+ BinarySerializer tocWriter = new BinarySerializer(tocFileStream);
+
+ dataFileStream.Seek(0, SeekOrigin.End);
+ uint dataOffset = checked((uint)dataFileStream.Position);
+ uint codeSize = (uint)data.Length;
+ uint cb1DataSize = (uint)cb1Data.Length;
+ dataFileStream.Write(cb1Data);
+ BinarySerializer.WriteCompressed(dataFileStream, data, DiskCacheCommon.GetCompressionAlgorithm());
+
+ _tocModificationsCount = ++header.ModificationsCount;
+ tocFileStream.Seek(0, SeekOrigin.Begin);
+ tocWriter.Write(ref header);
+
+ TocEntry entry = new TocEntry()
+ {
+ Offset = dataOffset,
+ CodeSize = codeSize,
+ Cb1DataSize = cb1DataSize,
+ Hash = hash
+ };
+
+ tocFileStream.Seek(0, SeekOrigin.End);
+ int index = (int)((tocFileStream.Position - Unsafe.SizeOf<TocHeader>()) / Unsafe.SizeOf<TocEntry>());
+
+ tocWriter.Write(ref entry);
+
+ AddTocMemoryEntry(dataOffset, codeSize, cb1DataSize, hash, index);
+
+ return index;
+ }
+
+ /// <summary>
+ /// Adds an entry to the memory TOC cache. This can be used to avoid reading the TOC file all the time.
+ /// </summary>
+ /// <param name="dataOffset">Offset of the code and constant buffer data in the data file</param>
+ /// <param name="codeSize">Code size</param>
+ /// <param name="cb1DataSize">Constant buffer 1 data size</param>
+ /// <param name="hash">Code and constant buffer data hash</param>
+ /// <param name="index">Index of the data on the cache</param>
+ private void AddTocMemoryEntry(uint dataOffset, uint codeSize, uint cb1DataSize, uint hash, int index)
+ {
+ if (!_toc.TryGetValue(hash, out var list))
+ {
+ _toc.Add(hash, list = new List<TocMemoryEntry>());
+ }
+
+ list.Add(new TocMemoryEntry(dataOffset, codeSize, cb1DataSize, index));
+ }
+
+ /// <summary>
+ /// Calculates the hash for a data pair.
+ /// </summary>
+ /// <param name="data">Data 1</param>
+ /// <param name="data2">Data 2</param>
+ /// <returns>Hash of both data</returns>
+ private static uint CalcHash(ReadOnlySpan<byte> data, ReadOnlySpan<byte> data2)
+ {
+ return CalcHash(data2) * 23 ^ CalcHash(data);
+ }
+
+ /// <summary>
+ /// Calculates the hash for data.
+ /// </summary>
+ /// <param name="data">Data to be hashed</param>
+ /// <returns>Hash of the data</returns>
+ private static uint CalcHash(ReadOnlySpan<byte> data)
+ {
+ return (uint)XXHash128.ComputeHash(data).Low;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
new file mode 100644
index 00000000..0028e879
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
@@ -0,0 +1,763 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.IO;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
+{
+ /// <summary>
+ /// On-disk shader cache storage for host code.
+ /// </summary>
+ class DiskCacheHostStorage
+ {
+ private const uint TocsMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'S' << 24);
+ private const uint TochMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'H' << 24);
+ private const uint ShdiMagic = (byte)'S' | ((byte)'H' << 8) | ((byte)'D' << 16) | ((byte)'I' << 24);
+ private const uint BufdMagic = (byte)'B' | ((byte)'U' << 8) | ((byte)'F' << 16) | ((byte)'D' << 24);
+ private const uint TexdMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'D' << 24);
+
+ private const ushort FileFormatVersionMajor = 1;
+ private const ushort FileFormatVersionMinor = 1;
+ private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
+ private const uint CodeGenVersion = 0;
+
+ private const string SharedTocFileName = "shared.toc";
+ private const string SharedDataFileName = "shared.data";
+
+ private readonly string _basePath;
+
+ public bool CacheEnabled => !string.IsNullOrEmpty(_basePath);
+
+ /// <summary>
+ /// TOC (Table of contents) file header.
+ /// </summary>
+ private struct TocHeader
+ {
+ /// <summary>
+ /// Magic value, for validation and identification.
+ /// </summary>
+ public uint Magic;
+
+ /// <summary>
+ /// File format version.
+ /// </summary>
+ public uint FormatVersion;
+
+ /// <summary>
+ /// Generated shader code version.
+ /// </summary>
+ public uint CodeGenVersion;
+
+ /// <summary>
+ /// Header padding.
+ /// </summary>
+ public uint Padding;
+
+ /// <summary>
+ /// Reserved space, to be used in the future. Write as zero.
+ /// </summary>
+ public ulong Reserved;
+
+ /// <summary>
+ /// Reserved space, to be used in the future. Write as zero.
+ /// </summary>
+ public ulong Reserved2;
+ }
+
+ /// <summary>
+ /// Offset and size pair.
+ /// </summary>
+ private struct OffsetAndSize
+ {
+ /// <summary>
+ /// Offset.
+ /// </summary>
+ public ulong Offset;
+
+ /// <summary>
+ /// Size.
+ /// </summary>
+ public uint Size;
+ }
+
+ /// <summary>
+ /// Per-stage data entry.
+ /// </summary>
+ private struct DataEntryPerStage
+ {
+ /// <summary>
+ /// Index of the guest code on the guest code cache TOC file.
+ /// </summary>
+ public int GuestCodeIndex;
+ }
+
+ /// <summary>
+ /// Per-program data entry.
+ /// </summary>
+ private struct DataEntry
+ {
+ /// <summary>
+ /// Bit mask where each bit set is a used shader stage. Should be zero for compute shaders.
+ /// </summary>
+ public uint StagesBitMask;
+ }
+
+ /// <summary>
+ /// Per-stage shader information, returned by the translator.
+ /// </summary>
+ private struct DataShaderInfo
+ {
+ /// <summary>
+ /// Total constant buffers used.
+ /// </summary>
+ public ushort CBuffersCount;
+
+ /// <summary>
+ /// Total storage buffers used.
+ /// </summary>
+ public ushort SBuffersCount;
+
+ /// <summary>
+ /// Total textures used.
+ /// </summary>
+ public ushort TexturesCount;
+
+ /// <summary>
+ /// Total images used.
+ /// </summary>
+ public ushort ImagesCount;
+
+ /// <summary>
+ /// Shader stage.
+ /// </summary>
+ public ShaderStage Stage;
+
+ /// <summary>
+ /// Indicates if the shader accesses the Instance ID built-in variable.
+ /// </summary>
+ public bool UsesInstanceId;
+
+ /// <summary>
+ /// Indicates if the shader modifies the Layer built-in variable.
+ /// </summary>
+ public bool UsesRtLayer;
+
+ /// <summary>
+ /// Bit mask with the clip distances written on the vertex stage.
+ /// </summary>
+ public byte ClipDistancesWritten;
+
+ /// <summary>
+ /// Bit mask of the render target components written by the fragment stage.
+ /// </summary>
+ public int FragmentOutputMap;
+ }
+
+ private readonly DiskCacheGuestStorage _guestStorage;
+
+ /// <summary>
+ /// Creates a disk cache host storage.
+ /// </summary>
+ /// <param name="basePath">Base path of the shader cache</param>
+ public DiskCacheHostStorage(string basePath)
+ {
+ _basePath = basePath;
+ _guestStorage = new DiskCacheGuestStorage(basePath);
+
+ if (CacheEnabled)
+ {
+ Directory.CreateDirectory(basePath);
+ }
+ }
+
+ /// <summary>
+ /// Gets the total of host programs on the cache.
+ /// </summary>
+ /// <returns>Host programs count</returns>
+ public int GetProgramCount()
+ {
+ string tocFilePath = Path.Combine(_basePath, SharedTocFileName);
+
+ if (!File.Exists(tocFilePath))
+ {
+ return 0;
+ }
+
+ return (int)((new FileInfo(tocFilePath).Length - Unsafe.SizeOf<TocHeader>()) / sizeof(ulong));
+ }
+
+ /// <summary>
+ /// Guest the name of the host program cache file, with extension.
+ /// </summary>
+ /// <param name="context">GPU context</param>
+ /// <returns>Name of the file, without extension</returns>
+ private static string GetHostFileName(GpuContext context)
+ {
+ string apiName = context.Capabilities.Api.ToString().ToLowerInvariant();
+ string vendorName = RemoveInvalidCharacters(context.Capabilities.VendorName.ToLowerInvariant());
+ return $"{apiName}_{vendorName}";
+ }
+
+ /// <summary>
+ /// Removes invalid path characters and spaces from a file name.
+ /// </summary>
+ /// <param name="fileName">File name</param>
+ /// <returns>Filtered file name</returns>
+ private static string RemoveInvalidCharacters(string fileName)
+ {
+ int indexOfSpace = fileName.IndexOf(' ');
+ if (indexOfSpace >= 0)
+ {
+ fileName = fileName.Substring(0, indexOfSpace);
+ }
+
+ return string.Concat(fileName.Split(Path.GetInvalidFileNameChars(), StringSplitOptions.RemoveEmptyEntries));
+ }
+
+ /// <summary>
+ /// Gets the name of the TOC host file.
+ /// </summary>
+ /// <param name="context">GPU context</param>
+ /// <returns>File name</returns>
+ private static string GetHostTocFileName(GpuContext context)
+ {
+ return GetHostFileName(context) + ".toc";
+ }
+
+ /// <summary>
+ /// Gets the name of the data host file.
+ /// </summary>
+ /// <param name="context">GPU context</param>
+ /// <returns>File name</returns>
+ private static string GetHostDataFileName(GpuContext context)
+ {
+ return GetHostFileName(context) + ".data";
+ }
+
+ /// <summary>
+ /// Checks if a disk cache exists for the current application.
+ /// </summary>
+ /// <returns>True if a disk cache exists, false otherwise</returns>
+ public bool CacheExists()
+ {
+ string tocFilePath = Path.Combine(_basePath, SharedTocFileName);
+ string dataFilePath = Path.Combine(_basePath, SharedDataFileName);
+
+ if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath) || !_guestStorage.TocFileExists() || !_guestStorage.DataFileExists())
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Loads all shaders from the cache.
+ /// </summary>
+ /// <param name="context">GPU context</param>
+ /// <param name="loader">Parallel disk cache loader</param>
+ public void LoadShaders(GpuContext context, ParallelDiskCacheLoader loader)
+ {
+ if (!CacheExists())
+ {
+ return;
+ }
+
+ Stream hostTocFileStream = null;
+ Stream hostDataFileStream = null;
+
+ try
+ {
+ using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: false);
+ using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: false);
+
+ using var guestTocFileStream = _guestStorage.OpenTocFileStream();
+ using var guestDataFileStream = _guestStorage.OpenDataFileStream();
+
+ BinarySerializer tocReader = new BinarySerializer(tocFileStream);
+ BinarySerializer dataReader = new BinarySerializer(dataFileStream);
+
+ TocHeader header = new TocHeader();
+
+ if (!tocReader.TryRead(ref header) || header.Magic != TocsMagic)
+ {
+ throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
+ }
+
+ if (header.FormatVersion != FileFormatVersionPacked)
+ {
+ throw new DiskCacheLoadException(DiskCacheLoadResult.IncompatibleVersion);
+ }
+
+ bool loadHostCache = header.CodeGenVersion == CodeGenVersion;
+
+ int programIndex = 0;
+
+ DataEntry entry = new DataEntry();
+
+ while (tocFileStream.Position < tocFileStream.Length && loader.Active)
+ {
+ ulong dataOffset = 0;
+ tocReader.Read(ref dataOffset);
+
+ if ((ulong)dataOffset >= (ulong)dataFileStream.Length)
+ {
+ throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
+ }
+
+ dataFileStream.Seek((long)dataOffset, SeekOrigin.Begin);
+
+ dataReader.BeginCompression();
+ dataReader.Read(ref entry);
+ uint stagesBitMask = entry.StagesBitMask;
+
+ if ((stagesBitMask & ~0x3fu) != 0)
+ {
+ throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
+ }
+
+ bool isCompute = stagesBitMask == 0;
+ if (isCompute)
+ {
+ stagesBitMask = 1;
+ }
+
+ CachedShaderStage[] shaders = new CachedShaderStage[isCompute ? 1 : Constants.ShaderStages + 1];
+
+ DataEntryPerStage stageEntry = new DataEntryPerStage();
+
+ while (stagesBitMask != 0)
+ {
+ int stageIndex = BitOperations.TrailingZeroCount(stagesBitMask);
+
+ dataReader.Read(ref stageEntry);
+
+ ShaderProgramInfo info = stageIndex != 0 || isCompute ? ReadShaderProgramInfo(ref dataReader) : null;
+
+ (byte[] guestCode, byte[] cb1Data) = _guestStorage.LoadShader(
+ guestTocFileStream,
+ guestDataFileStream,
+ stageEntry.GuestCodeIndex);
+
+ shaders[stageIndex] = new CachedShaderStage(info, guestCode, cb1Data);
+
+ stagesBitMask &= ~(1u << stageIndex);
+ }
+
+ ShaderSpecializationState specState = ShaderSpecializationState.Read(ref dataReader);
+ dataReader.EndCompression();
+
+ if (loadHostCache)
+ {
+ byte[] hostCode = ReadHostCode(context, ref hostTocFileStream, ref hostDataFileStream, programIndex);
+
+ if (hostCode != null)
+ {
+ bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
+ int fragmentOutputMap = hasFragmentShader ? shaders[5].Info.FragmentOutputMap : -1;
+ IProgram hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, new ShaderInfo(fragmentOutputMap));
+
+ CachedShaderProgram program = new CachedShaderProgram(hostProgram, specState, shaders);
+
+ loader.QueueHostProgram(program, hostProgram, programIndex, isCompute);
+ }
+ else
+ {
+ loadHostCache = false;
+ }
+ }
+
+ if (!loadHostCache)
+ {
+ loader.QueueGuestProgram(shaders, specState, programIndex, isCompute);
+ }
+
+ loader.CheckCompilation();
+ programIndex++;
+ }
+ }
+ finally
+ {
+ _guestStorage.ClearMemoryCache();
+
+ hostTocFileStream?.Dispose();
+ hostDataFileStream?.Dispose();
+ }
+ }
+
+ /// <summary>
+ /// Reads the host code for a given shader, if existent.
+ /// </summary>
+ /// <param name="context">GPU context</param>
+ /// <param name="tocFileStream">Host TOC file stream, intialized if needed</param>
+ /// <param name="dataFileStream">Host data file stream, initialized if needed</param>
+ /// <param name="programIndex">Index of the program on the cache</param>
+ /// <returns>Host binary code, or null if not found</returns>
+ private byte[] ReadHostCode(GpuContext context, ref Stream tocFileStream, ref Stream dataFileStream, int programIndex)
+ {
+ if (tocFileStream == null && dataFileStream == null)
+ {
+ string tocFilePath = Path.Combine(_basePath, GetHostTocFileName(context));
+ string dataFilePath = Path.Combine(_basePath, GetHostDataFileName(context));
+
+ if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath))
+ {
+ return null;
+ }
+
+ tocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: false);
+ dataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: false);
+ }
+
+ int offset = Unsafe.SizeOf<TocHeader>() + programIndex * Unsafe.SizeOf<OffsetAndSize>();
+ if (offset + Unsafe.SizeOf<OffsetAndSize>() > tocFileStream.Length)
+ {
+ return null;
+ }
+
+ if ((ulong)offset >= (ulong)dataFileStream.Length)
+ {
+ throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
+ }
+
+ tocFileStream.Seek(offset, SeekOrigin.Begin);
+
+ BinarySerializer tocReader = new BinarySerializer(tocFileStream);
+
+ OffsetAndSize offsetAndSize = new OffsetAndSize();
+ tocReader.Read(ref offsetAndSize);
+
+ if (offsetAndSize.Offset >= (ulong)dataFileStream.Length)
+ {
+ throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
+ }
+
+ dataFileStream.Seek((long)offsetAndSize.Offset, SeekOrigin.Begin);
+
+ byte[] hostCode = new byte[offsetAndSize.Size];
+
+ BinarySerializer.ReadCompressed(dataFileStream, hostCode);
+
+ return hostCode;
+ }
+
+ /// <summary>
+ /// Gets output streams for the disk cache, for faster batch writing.
+ /// </summary>
+ /// <param name="context">The GPU context, used to determine the host disk cache</param>
+ /// <returns>A collection of disk cache output streams</returns>
+ public DiskCacheOutputStreams GetOutputStreams(GpuContext context)
+ {
+ var tocFileStream = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true);
+ var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true);
+
+ var hostTocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true);
+ var hostDataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true);
+
+ return new DiskCacheOutputStreams(tocFileStream, dataFileStream, hostTocFileStream, hostDataFileStream);
+ }
+
+ /// <summary>
+ /// Adds a shader to the cache.
+ /// </summary>
+ /// <param name="context">GPU context</param>
+ /// <param name="program">Cached program</param>
+ /// <param name="hostCode">Optional host binary code</param>
+ /// <param name="streams">Output streams to use</param>
+ public void AddShader(GpuContext context, CachedShaderProgram program, ReadOnlySpan<byte> hostCode, DiskCacheOutputStreams streams = null)
+ {
+ uint stagesBitMask = 0;
+
+ for (int index = 0; index < program.Shaders.Length; index++)
+ {
+ var shader = program.Shaders[index];
+ if (shader == null || (shader.Info != null && shader.Info.Stage == ShaderStage.Compute))
+ {
+ continue;
+ }
+
+ stagesBitMask |= 1u << index;
+ }
+
+ var tocFileStream = streams != null ? streams.TocFileStream : DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true);
+ var dataFileStream = streams != null ? streams.DataFileStream : DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true);
+
+ if (tocFileStream.Length == 0)
+ {
+ TocHeader header = new TocHeader();
+ CreateToc(tocFileStream, ref header, TocsMagic, CodeGenVersion);
+ }
+
+ tocFileStream.Seek(0, SeekOrigin.End);
+ dataFileStream.Seek(0, SeekOrigin.End);
+
+ BinarySerializer tocWriter = new BinarySerializer(tocFileStream);
+ BinarySerializer dataWriter = new BinarySerializer(dataFileStream);
+
+ ulong dataOffset = (ulong)dataFileStream.Position;
+ tocWriter.Write(ref dataOffset);
+
+ DataEntry entry = new DataEntry();
+
+ entry.StagesBitMask = stagesBitMask;
+
+ dataWriter.BeginCompression(DiskCacheCommon.GetCompressionAlgorithm());
+ dataWriter.Write(ref entry);
+
+ DataEntryPerStage stageEntry = new DataEntryPerStage();
+
+ for (int index = 0; index < program.Shaders.Length; index++)
+ {
+ var shader = program.Shaders[index];
+ if (shader == null)
+ {
+ continue;
+ }
+
+ stageEntry.GuestCodeIndex = _guestStorage.AddShader(shader.Code, shader.Cb1Data);
+
+ dataWriter.Write(ref stageEntry);
+
+ WriteShaderProgramInfo(ref dataWriter, shader.Info);
+ }
+
+ program.SpecializationState.Write(ref dataWriter);
+ dataWriter.EndCompression();
+
+ if (streams == null)
+ {
+ tocFileStream.Dispose();
+ dataFileStream.Dispose();
+ }
+
+ if (hostCode.IsEmpty)
+ {
+ return;
+ }
+
+ WriteHostCode(context, hostCode, -1, streams);
+ }
+
+ /// <summary>
+ /// Clears all content from the guest cache files.
+ /// </summary>
+ public void ClearGuestCache()
+ {
+ _guestStorage.ClearCache();
+ }
+
+ /// <summary>
+ /// Clears all content from the shared cache files.
+ /// </summary>
+ /// <param name="context">GPU context</param>
+ public void ClearSharedCache()
+ {
+ using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true);
+ using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true);
+
+ tocFileStream.SetLength(0);
+ dataFileStream.SetLength(0);
+ }
+
+ /// <summary>
+ /// Deletes all content from the host cache files.
+ /// </summary>
+ /// <param name="context">GPU context</param>
+ public void ClearHostCache(GpuContext context)
+ {
+ using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true);
+ using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true);
+
+ tocFileStream.SetLength(0);
+ dataFileStream.SetLength(0);
+ }
+
+ /// <summary>
+ /// Adds a host binary shader to the host cache.
+ /// </summary>
+ /// <remarks>
+ /// This only modifies the host cache. The shader must already exist in the other caches.
+ /// This method should only be used for rebuilding the host cache after a clear.
+ /// </remarks>
+ /// <param name="context">GPU context</param>
+ /// <param name="hostCode">Host binary code</param>
+ /// <param name="programIndex">Index of the program in the cache</param>
+ public void AddHostShader(GpuContext context, ReadOnlySpan<byte> hostCode, int programIndex)
+ {
+ WriteHostCode(context, hostCode, programIndex);
+ }
+
+ /// <summary>
+ /// Writes the host binary code on the host cache.
+ /// </summary>
+ /// <param name="context">GPU context</param>
+ /// <param name="hostCode">Host binary code</param>
+ /// <param name="programIndex">Index of the program in the cache</param>
+ /// <param name="streams">Output streams to use</param>
+ private void WriteHostCode(GpuContext context, ReadOnlySpan<byte> hostCode, int programIndex, DiskCacheOutputStreams streams = null)
+ {
+ var tocFileStream = streams != null ? streams.HostTocFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true);
+ var dataFileStream = streams != null ? streams.HostDataFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true);
+
+ if (tocFileStream.Length == 0)
+ {
+ TocHeader header = new TocHeader();
+ CreateToc(tocFileStream, ref header, TochMagic, 0);
+ }
+
+ if (programIndex == -1)
+ {
+ tocFileStream.Seek(0, SeekOrigin.End);
+ }
+ else
+ {
+ tocFileStream.Seek(Unsafe.SizeOf<TocHeader>() + (programIndex * Unsafe.SizeOf<OffsetAndSize>()), SeekOrigin.Begin);
+ }
+
+ dataFileStream.Seek(0, SeekOrigin.End);
+
+ BinarySerializer tocWriter = new BinarySerializer(tocFileStream);
+
+ OffsetAndSize offsetAndSize = new OffsetAndSize();
+ offsetAndSize.Offset = (ulong)dataFileStream.Position;
+ offsetAndSize.Size = (uint)hostCode.Length;
+ tocWriter.Write(ref offsetAndSize);
+
+ BinarySerializer.WriteCompressed(dataFileStream, hostCode, DiskCacheCommon.GetCompressionAlgorithm());
+
+ if (streams == null)
+ {
+ tocFileStream.Dispose();
+ dataFileStream.Dispose();
+ }
+ }
+
+ /// <summary>
+ /// Creates a TOC file for the host or shared cache.
+ /// </summary>
+ /// <param name="tocFileStream">TOC file stream</param>
+ /// <param name="header">Set to the TOC file header</param>
+ /// <param name="magic">Magic value to be written</param>
+ /// <param name="codegenVersion">Shader codegen version, only valid for the host file</param>
+ private void CreateToc(Stream tocFileStream, ref TocHeader header, uint magic, uint codegenVersion)
+ {
+ BinarySerializer writer = new BinarySerializer(tocFileStream);
+
+ header.Magic = magic;
+ header.FormatVersion = FileFormatVersionPacked;
+ header.CodeGenVersion = codegenVersion;
+ header.Padding = 0;
+ header.Reserved = 0;
+ header.Reserved2 = 0;
+
+ if (tocFileStream.Length > 0)
+ {
+ tocFileStream.Seek(0, SeekOrigin.Begin);
+ tocFileStream.SetLength(0);
+ }
+
+ writer.Write(ref header);
+ }
+
+ /// <summary>
+ /// Reads the shader program info from the cache.
+ /// </summary>
+ /// <param name="dataReader">Cache data reader</param>
+ /// <returns>Shader program info</returns>
+ private static ShaderProgramInfo ReadShaderProgramInfo(ref BinarySerializer dataReader)
+ {
+ DataShaderInfo dataInfo = new DataShaderInfo();
+
+ dataReader.ReadWithMagicAndSize(ref dataInfo, ShdiMagic);
+
+ BufferDescriptor[] cBuffers = new BufferDescriptor[dataInfo.CBuffersCount];
+ BufferDescriptor[] sBuffers = new BufferDescriptor[dataInfo.SBuffersCount];
+ TextureDescriptor[] textures = new TextureDescriptor[dataInfo.TexturesCount];
+ TextureDescriptor[] images = new TextureDescriptor[dataInfo.ImagesCount];
+
+ for (int index = 0; index < dataInfo.CBuffersCount; index++)
+ {
+ dataReader.ReadWithMagicAndSize(ref cBuffers[index], BufdMagic);
+ }
+
+ for (int index = 0; index < dataInfo.SBuffersCount; index++)
+ {
+ dataReader.ReadWithMagicAndSize(ref sBuffers[index], BufdMagic);
+ }
+
+ for (int index = 0; index < dataInfo.TexturesCount; index++)
+ {
+ dataReader.ReadWithMagicAndSize(ref textures[index], TexdMagic);
+ }
+
+ for (int index = 0; index < dataInfo.ImagesCount; index++)
+ {
+ dataReader.ReadWithMagicAndSize(ref images[index], TexdMagic);
+ }
+
+ return new ShaderProgramInfo(
+ cBuffers,
+ sBuffers,
+ textures,
+ images,
+ dataInfo.Stage,
+ dataInfo.UsesInstanceId,
+ dataInfo.UsesRtLayer,
+ dataInfo.ClipDistancesWritten,
+ dataInfo.FragmentOutputMap);
+ }
+
+ /// <summary>
+ /// Writes the shader program info into the cache.
+ /// </summary>
+ /// <param name="dataWriter">Cache data writer</param>
+ /// <param name="info">Program info</param>
+ private static void WriteShaderProgramInfo(ref BinarySerializer dataWriter, ShaderProgramInfo info)
+ {
+ if (info == null)
+ {
+ return;
+ }
+
+ DataShaderInfo dataInfo = new DataShaderInfo();
+
+ dataInfo.CBuffersCount = (ushort)info.CBuffers.Count;
+ dataInfo.SBuffersCount = (ushort)info.SBuffers.Count;
+ dataInfo.TexturesCount = (ushort)info.Textures.Count;
+ dataInfo.ImagesCount = (ushort)info.Images.Count;
+ dataInfo.Stage = info.Stage;
+ dataInfo.UsesInstanceId = info.UsesInstanceId;
+ dataInfo.UsesRtLayer = info.UsesRtLayer;
+ dataInfo.ClipDistancesWritten = info.ClipDistancesWritten;
+ dataInfo.FragmentOutputMap = info.FragmentOutputMap;
+
+ dataWriter.WriteWithMagicAndSize(ref dataInfo, ShdiMagic);
+
+ for (int index = 0; index < info.CBuffers.Count; index++)
+ {
+ var entry = info.CBuffers[index];
+ dataWriter.WriteWithMagicAndSize(ref entry, BufdMagic);
+ }
+
+ for (int index = 0; index < info.SBuffers.Count; index++)
+ {
+ var entry = info.SBuffers[index];
+ dataWriter.WriteWithMagicAndSize(ref entry, BufdMagic);
+ }
+
+ for (int index = 0; index < info.Textures.Count; index++)
+ {
+ var entry = info.Textures[index];
+ dataWriter.WriteWithMagicAndSize(ref entry, TexdMagic);
+ }
+
+ for (int index = 0; index < info.Images.Count; index++)
+ {
+ var entry = info.Images[index];
+ dataWriter.WriteWithMagicAndSize(ref entry, TexdMagic);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadException.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadException.cs
new file mode 100644
index 00000000..d6e23302
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadException.cs
@@ -0,0 +1,48 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
+{
+ /// <summary>
+ /// Disk cache load exception.
+ /// </summary>
+ class DiskCacheLoadException : Exception
+ {
+ /// <summary>
+ /// Result of the cache load operation.
+ /// </summary>
+ public DiskCacheLoadResult Result { get; }
+
+ /// <summary>
+ /// Creates a new instance of the disk cache load exception.
+ /// </summary>
+ public DiskCacheLoadException()
+ {
+ }
+
+ /// <summary>
+ /// Creates a new instance of the disk cache load exception.
+ /// </summary>
+ /// <param name="message">Exception message</param>
+ public DiskCacheLoadException(string message) : base(message)
+ {
+ }
+
+ /// <summary>
+ /// Creates a new instance of the disk cache load exception.
+ /// </summary>
+ /// <param name="message">Exception message</param>
+ /// <param name="inner">Inner exception</param>
+ public DiskCacheLoadException(string message, Exception inner) : base(message, inner)
+ {
+ }
+
+ /// <summary>
+ /// Creates a new instance of the disk cache load exception.
+ /// </summary>
+ /// <param name="result">Result code</param>
+ public DiskCacheLoadException(DiskCacheLoadResult result) : base(result.GetMessage())
+ {
+ Result = result;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs
new file mode 100644
index 00000000..b3ffa4a7
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs
@@ -0,0 +1,72 @@
+namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
+{
+ /// <summary>
+ /// Result of a shader cache load operation.
+ /// </summary>
+ enum DiskCacheLoadResult
+ {
+ /// <summary>
+ /// No error.
+ /// </summary>
+ Success,
+
+ /// <summary>
+ /// File can't be accessed.
+ /// </summary>
+ NoAccess,
+
+ /// <summary>
+ /// The constant buffer 1 data length is too low for the translation of the guest shader.
+ /// </summary>
+ InvalidCb1DataLength,
+
+ /// <summary>
+ /// The cache is missing the descriptor of a texture used by the shader.
+ /// </summary>
+ MissingTextureDescriptor,
+
+ /// <summary>
+ /// File is corrupted.
+ /// </summary>
+ FileCorruptedGeneric,
+
+ /// <summary>
+ /// File is corrupted, detected by magic value check.
+ /// </summary>
+ FileCorruptedInvalidMagic,
+
+ /// <summary>
+ /// File is corrupted, detected by length check.
+ /// </summary>
+ FileCorruptedInvalidLength,
+
+ /// <summary>
+ /// File might be valid, but is incompatible with the current emulator version.
+ /// </summary>
+ IncompatibleVersion
+ }
+
+ static class DiskCacheLoadResultExtensions
+ {
+ /// <summary>
+ /// Gets an error message from a result code.
+ /// </summary>
+ /// <param name="result">Result code</param>
+ /// <returns>Error message</returns>
+ public static string GetMessage(this DiskCacheLoadResult result)
+ {
+ return result switch
+ {
+ DiskCacheLoadResult.Success => "No error.",
+ DiskCacheLoadResult.NoAccess => "Could not access the cache file.",
+ DiskCacheLoadResult.InvalidCb1DataLength => "Constant buffer 1 data length is too low.",
+ DiskCacheLoadResult.MissingTextureDescriptor => "Texture descriptor missing from the cache file.",
+ DiskCacheLoadResult.FileCorruptedGeneric => "The cache file is corrupted.",
+ DiskCacheLoadResult.FileCorruptedInvalidMagic => "Magic check failed, the cache file is corrupted.",
+ DiskCacheLoadResult.FileCorruptedInvalidLength => "Length check failed, the cache file is corrupted.",
+ DiskCacheLoadResult.IncompatibleVersion => "The version of the disk cache is not compatible with this version of the emulator.",
+ _ => "Unknown error."
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheOutputStreams.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheOutputStreams.cs
new file mode 100644
index 00000000..1e0df264
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheOutputStreams.cs
@@ -0,0 +1,57 @@
+using System;
+using System.IO;
+
+namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
+{
+ /// <summary>
+ /// Output streams for the disk shader cache.
+ /// </summary>
+ class DiskCacheOutputStreams : IDisposable
+ {
+ /// <summary>
+ /// Shared table of contents (TOC) file stream.
+ /// </summary>
+ public readonly FileStream TocFileStream;
+
+ /// <summary>
+ /// Shared data file stream.
+ /// </summary>
+ public readonly FileStream DataFileStream;
+
+ /// <summary>
+ /// Host table of contents (TOC) file stream.
+ /// </summary>
+ public readonly FileStream HostTocFileStream;
+
+ /// <summary>
+ /// Host data file stream.
+ /// </summary>
+ public readonly FileStream HostDataFileStream;
+
+ /// <summary>
+ /// Creates a new instance of a disk cache output stream container.
+ /// </summary>
+ /// <param name="tocFileStream">Stream for the shared table of contents file</param>
+ /// <param name="dataFileStream">Stream for the shared data file</param>
+ /// <param name="hostTocFileStream">Stream for the host table of contents file</param>
+ /// <param name="hostDataFileStream">Stream for the host data file</param>
+ public DiskCacheOutputStreams(FileStream tocFileStream, FileStream dataFileStream, FileStream hostTocFileStream, FileStream hostDataFileStream)
+ {
+ TocFileStream = tocFileStream;
+ DataFileStream = dataFileStream;
+ HostTocFileStream = hostTocFileStream;
+ HostDataFileStream = hostDataFileStream;
+ }
+
+ /// <summary>
+ /// Disposes the output file streams.
+ /// </summary>
+ public void Dispose()
+ {
+ TocFileStream.Dispose();
+ DataFileStream.Dispose();
+ HostTocFileStream.Dispose();
+ HostDataFileStream.Dispose();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
new file mode 100644
index 00000000..af7579d5
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
@@ -0,0 +1,672 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using static Ryujinx.Graphics.Gpu.Shader.ShaderCache;
+
+namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
+{
+ class ParallelDiskCacheLoader
+ {
+ private const int ThreadCount = 8;
+
+ private readonly GpuContext _context;
+ private readonly ShaderCacheHashTable _graphicsCache;
+ private readonly ComputeShaderCacheHashTable _computeCache;
+ private readonly DiskCacheHostStorage _hostStorage;
+ private readonly CancellationToken _cancellationToken;
+ private readonly Action<ShaderCacheState, int, int> _stateChangeCallback;
+
+ /// <summary>
+ /// Indicates if the cache should be loaded.
+ /// </summary>
+ public bool Active => !_cancellationToken.IsCancellationRequested;
+
+ private bool _needsHostRegen;
+
+ /// <summary>
+ /// Number of shaders that failed to compile from the cache.
+ /// </summary>
+ public int ErrorCount { get; private set; }
+
+ /// <summary>
+ /// Program validation entry.
+ /// </summary>
+ private struct ProgramEntry
+ {
+ /// <summary>
+ /// Cached shader program.
+ /// </summary>
+ public readonly CachedShaderProgram CachedProgram;
+
+ /// <summary>
+ /// Host program.
+ /// </summary>
+ public readonly IProgram HostProgram;
+
+ /// <summary>
+ /// Program index.
+ /// </summary>
+ public readonly int ProgramIndex;
+
+ /// <summary>
+ /// Indicates if the program is a compute shader.
+ /// </summary>
+ public readonly bool IsCompute;
+
+ /// <summary>
+ /// Indicates if the program is a host binary shader.
+ /// </summary>
+ public readonly bool IsBinary;
+
+ /// <summary>
+ /// Creates a new program validation entry.
+ /// </summary>
+ /// <param name="cachedProgram">Cached shader program</param>
+ /// <param name="hostProgram">Host program</param>
+ /// <param name="programIndex">Program index</param>
+ /// <param name="isCompute">Indicates if the program is a compute shader</param>
+ /// <param name="isBinary">Indicates if the program is a host binary shader</param>
+ public ProgramEntry(
+ CachedShaderProgram cachedProgram,
+ IProgram hostProgram,
+ int programIndex,
+ bool isCompute,
+ bool isBinary)
+ {
+ CachedProgram = cachedProgram;
+ HostProgram = hostProgram;
+ ProgramIndex = programIndex;
+ IsCompute = isCompute;
+ IsBinary = isBinary;
+ }
+ }
+
+ /// <summary>
+ /// Translated shader compilation entry.
+ /// </summary>
+ private struct ProgramCompilation
+ {
+ /// <summary>
+ /// Translated shader stages.
+ /// </summary>
+ public readonly ShaderProgram[] TranslatedStages;
+
+ /// <summary>
+ /// Cached shaders.
+ /// </summary>
+ public readonly CachedShaderStage[] Shaders;
+
+ /// <summary>
+ /// Specialization state.
+ /// </summary>
+ public readonly ShaderSpecializationState SpecializationState;
+
+ /// <summary>
+ /// Program index.
+ /// </summary>
+ public readonly int ProgramIndex;
+
+ /// <summary>
+ /// Indicates if the program is a compute shader.
+ /// </summary>
+ public readonly bool IsCompute;
+
+ /// <summary>
+ /// Creates a new translated shader compilation entry.
+ /// </summary>
+ /// <param name="translatedStages">Translated shader stages</param>
+ /// <param name="shaders">Cached shaders</param>
+ /// <param name="specState">Specialization state</param>
+ /// <param name="programIndex">Program index</param>
+ /// <param name="isCompute">Indicates if the program is a compute shader</param>
+ public ProgramCompilation(
+ ShaderProgram[] translatedStages,
+ CachedShaderStage[] shaders,
+ ShaderSpecializationState specState,
+ int programIndex,
+ bool isCompute)
+ {
+ TranslatedStages = translatedStages;
+ Shaders = shaders;
+ SpecializationState = specState;
+ ProgramIndex = programIndex;
+ IsCompute = isCompute;
+ }
+ }
+
+ /// <summary>
+ /// Program translation entry.
+ /// </summary>
+ private struct AsyncProgramTranslation
+ {
+ /// <summary>
+ /// Cached shader stages.
+ /// </summary>
+ public readonly CachedShaderStage[] Shaders;
+
+ /// <summary>
+ /// Specialization state.
+ /// </summary>
+ public readonly ShaderSpecializationState SpecializationState;
+
+ /// <summary>
+ /// Program index.
+ /// </summary>
+ public readonly int ProgramIndex;
+
+ /// <summary>
+ /// Indicates if the program is a compute shader.
+ /// </summary>
+ public readonly bool IsCompute;
+
+ /// <summary>
+ /// Creates a new program translation entry.
+ /// </summary>
+ /// <param name="shaders">Cached shader stages</param>
+ /// <param name="specState">Specialization state</param>
+ /// <param name="programIndex">Program index</param>
+ /// <param name="isCompute">Indicates if the program is a compute shader</param>
+ public AsyncProgramTranslation(
+ CachedShaderStage[] shaders,
+ ShaderSpecializationState specState,
+ int programIndex,
+ bool isCompute)
+ {
+ Shaders = shaders;
+ SpecializationState = specState;
+ ProgramIndex = programIndex;
+ IsCompute = isCompute;
+ }
+ }
+
+ private readonly Queue<ProgramEntry> _validationQueue;
+ private readonly ConcurrentQueue<ProgramCompilation> _compilationQueue;
+ private readonly BlockingCollection<AsyncProgramTranslation> _asyncTranslationQueue;
+ private readonly SortedList<int, CachedShaderProgram> _programList;
+
+ private int _backendParallelCompileThreads;
+ private int _compiledCount;
+ private int _totalCount;
+
+ /// <summary>
+ /// Creates a new parallel disk cache loader.
+ /// </summary>
+ /// <param name="context">GPU context</param>
+ /// <param name="graphicsCache">Graphics shader cache</param>
+ /// <param name="computeCache">Compute shader cache</param>
+ /// <param name="hostStorage">Disk cache host storage</param>
+ /// <param name="cancellationToken">Cancellation token</param>
+ /// <param name="stateChangeCallback">Function to be called when there is a state change, reporting state, compiled and total shaders count</param>
+ public ParallelDiskCacheLoader(
+ GpuContext context,
+ ShaderCacheHashTable graphicsCache,
+ ComputeShaderCacheHashTable computeCache,
+ DiskCacheHostStorage hostStorage,
+ CancellationToken cancellationToken,
+ Action<ShaderCacheState, int, int> stateChangeCallback)
+ {
+ _context = context;
+ _graphicsCache = graphicsCache;
+ _computeCache = computeCache;
+ _hostStorage = hostStorage;
+ _cancellationToken = cancellationToken;
+ _stateChangeCallback = stateChangeCallback;
+ _validationQueue = new Queue<ProgramEntry>();
+ _compilationQueue = new ConcurrentQueue<ProgramCompilation>();
+ _asyncTranslationQueue = new BlockingCollection<AsyncProgramTranslation>(ThreadCount);
+ _programList = new SortedList<int, CachedShaderProgram>();
+ _backendParallelCompileThreads = Math.Min(Environment.ProcessorCount, 8); // Must be kept in sync with the backend code.
+ }
+
+ /// <summary>
+ /// Loads all shaders from the cache.
+ /// </summary>
+ public void LoadShaders()
+ {
+ Thread[] workThreads = new Thread[ThreadCount];
+
+ for (int index = 0; index < ThreadCount; index++)
+ {
+ workThreads[index] = new Thread(ProcessAsyncQueue)
+ {
+ Name = $"Gpu.AsyncTranslationThread.{index}"
+ };
+ }
+
+ int programCount = _hostStorage.GetProgramCount();
+
+ _compiledCount = 0;
+ _totalCount = programCount;
+
+ _stateChangeCallback(ShaderCacheState.Start, 0, programCount);
+
+ Logger.Info?.Print(LogClass.Gpu, $"Loading {programCount} shaders from the cache...");
+
+ for (int index = 0; index < ThreadCount; index++)
+ {
+ workThreads[index].Start(_cancellationToken);
+ }
+
+ try
+ {
+ _hostStorage.LoadShaders(_context, this);
+ }
+ catch (DiskCacheLoadException diskCacheLoadException)
+ {
+ Logger.Warning?.Print(LogClass.Gpu, $"Error loading the shader cache. {diskCacheLoadException.Message}");
+
+ // If we can't even access the file, then we also can't rebuild.
+ if (diskCacheLoadException.Result != DiskCacheLoadResult.NoAccess)
+ {
+ _needsHostRegen = true;
+ }
+ }
+ catch (InvalidDataException invalidDataException)
+ {
+ Logger.Warning?.Print(LogClass.Gpu, $"Error decompressing the shader cache file. {invalidDataException.Message}");
+ _needsHostRegen = true;
+ }
+ catch (IOException ioException)
+ {
+ Logger.Warning?.Print(LogClass.Gpu, $"Error reading the shader cache file. {ioException.Message}");
+ _needsHostRegen = true;
+ }
+
+ _asyncTranslationQueue.CompleteAdding();
+
+ for (int index = 0; index < ThreadCount; index++)
+ {
+ workThreads[index].Join();
+ }
+
+ CheckCompilationBlocking();
+
+ if (_needsHostRegen)
+ {
+ // Rebuild both shared and host cache files.
+ // Rebuilding shared is required because the shader information returned by the translator
+ // might have changed, and so we have to reconstruct the file with the new information.
+ try
+ {
+ _hostStorage.ClearSharedCache();
+ _hostStorage.ClearHostCache(_context);
+
+ if (_programList.Count != 0)
+ {
+ Logger.Info?.Print(LogClass.Gpu, $"Rebuilding {_programList.Count} shaders...");
+
+ using var streams = _hostStorage.GetOutputStreams(_context);
+
+ foreach (var kv in _programList)
+ {
+ if (!Active)
+ {
+ break;
+ }
+
+ CachedShaderProgram program = kv.Value;
+ _hostStorage.AddShader(_context, program, program.HostProgram.GetBinary(), streams);
+ }
+
+ Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {_programList.Count} shaders successfully.");
+ }
+ else
+ {
+ _hostStorage.ClearGuestCache();
+
+ Logger.Info?.Print(LogClass.Gpu, "Shader cache deleted due to corruption.");
+ }
+ }
+ catch (DiskCacheLoadException diskCacheLoadException)
+ {
+ Logger.Warning?.Print(LogClass.Gpu, $"Error deleting the shader cache. {diskCacheLoadException.Message}");
+ }
+ catch (IOException ioException)
+ {
+ Logger.Warning?.Print(LogClass.Gpu, $"Error deleting the shader cache file. {ioException.Message}");
+ }
+ }
+
+ Logger.Info?.Print(LogClass.Gpu, "Shader cache loaded.");
+
+ _stateChangeCallback(ShaderCacheState.Loaded, programCount, programCount);
+ }
+
+ /// <summary>
+ /// Enqueues a host program for compilation.
+ /// </summary>
+ /// <param name="cachedProgram">Cached program</param>
+ /// <param name="hostProgram">Host program to be compiled</param>
+ /// <param name="programIndex">Program index</param>
+ /// <param name="isCompute">Indicates if the program is a compute shader</param>
+ public void QueueHostProgram(CachedShaderProgram cachedProgram, IProgram hostProgram, int programIndex, bool isCompute)
+ {
+ EnqueueForValidation(new ProgramEntry(cachedProgram, hostProgram, programIndex, isCompute, isBinary: true));
+ }
+
+ /// <summary>
+ /// Enqueues a guest program for compilation.
+ /// </summary>
+ /// <param name="shaders">Cached shader stages</param>
+ /// <param name="specState">Specialization state</param>
+ /// <param name="programIndex">Program index</param>
+ /// <param name="isCompute">Indicates if the program is a compute shader</param>
+ public void QueueGuestProgram(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
+ {
+ _asyncTranslationQueue.Add(new AsyncProgramTranslation(shaders, specState, programIndex, isCompute));
+ }
+
+ /// <summary>
+ /// Check the state of programs that have already been compiled,
+ /// and add to the cache if the compilation was successful.
+ /// </summary>
+ public void CheckCompilation()
+ {
+ ProcessCompilationQueue();
+
+ // Process programs that already finished compiling.
+ // If not yet compiled, do nothing. This avoids blocking to wait for shader compilation.
+ while (_validationQueue.TryPeek(out ProgramEntry entry))
+ {
+ ProgramLinkStatus result = entry.HostProgram.CheckProgramLink(false);
+
+ if (result != ProgramLinkStatus.Incomplete)
+ {
+ ProcessCompiledProgram(ref entry, result);
+ _validationQueue.Dequeue();
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Waits until all programs finishes compiling, then adds the ones
+ /// with successful compilation to the cache.
+ /// </summary>
+ private void CheckCompilationBlocking()
+ {
+ ProcessCompilationQueue();
+
+ while (_validationQueue.TryDequeue(out ProgramEntry entry) && Active)
+ {
+ ProcessCompiledProgram(ref entry, entry.HostProgram.CheckProgramLink(true), asyncCompile: false);
+ }
+ }
+
+ /// <summary>
+ /// Process a compiled program result.
+ /// </summary>
+ /// <param name="entry">Compiled program entry</param>
+ /// <param name="result">Compilation result</param>
+ /// <param name="asyncCompile">For failed host compilations, indicates if a guest compilation should be done asynchronously</param>
+ private void ProcessCompiledProgram(ref ProgramEntry entry, ProgramLinkStatus result, bool asyncCompile = true)
+ {
+ if (result == ProgramLinkStatus.Success)
+ {
+ // Compilation successful, add to memory cache.
+ if (entry.IsCompute)
+ {
+ _computeCache.Add(entry.CachedProgram);
+ }
+ else
+ {
+ _graphicsCache.Add(entry.CachedProgram);
+ }
+
+ if (!entry.IsBinary)
+ {
+ _needsHostRegen = true;
+ }
+
+ _programList.Add(entry.ProgramIndex, entry.CachedProgram);
+ SignalCompiled();
+ }
+ else if (entry.IsBinary)
+ {
+ // If this is a host binary and compilation failed,
+ // we still have a chance to recompile from the guest binary.
+ CachedShaderProgram program = entry.CachedProgram;
+
+ if (asyncCompile)
+ {
+ QueueGuestProgram(program.Shaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
+ }
+ else
+ {
+ RecompileFromGuestCode(program.Shaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
+ ProcessCompilationQueue();
+ }
+ }
+ else
+ {
+ // Failed to compile from both host and guest binary.
+ ErrorCount++;
+ SignalCompiled();
+ }
+ }
+
+ /// <summary>
+ /// Processes the queue of translated guest programs that should be compiled on the host.
+ /// </summary>
+ private void ProcessCompilationQueue()
+ {
+ while (_compilationQueue.TryDequeue(out ProgramCompilation compilation) && Active)
+ {
+ ShaderSource[] shaderSources = new ShaderSource[compilation.TranslatedStages.Length];
+
+ int fragmentOutputMap = -1;
+
+ for (int index = 0; index < compilation.TranslatedStages.Length; index++)
+ {
+ ShaderProgram shader = compilation.TranslatedStages[index];
+ shaderSources[index] = CreateShaderSource(shader);
+
+ if (shader.Info.Stage == ShaderStage.Fragment)
+ {
+ fragmentOutputMap = shader.Info.FragmentOutputMap;
+ }
+ }
+
+ IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, new ShaderInfo(fragmentOutputMap));
+ CachedShaderProgram program = new CachedShaderProgram(hostProgram, compilation.SpecializationState, compilation.Shaders);
+
+ EnqueueForValidation(new ProgramEntry(program, hostProgram, compilation.ProgramIndex, compilation.IsCompute, isBinary: false));
+ }
+ }
+
+ /// <summary>
+ /// Enqueues a program for validation, which will check if the program was compiled successfully.
+ /// </summary>
+ /// <param name="newEntry">Program entry to be validated</param>
+ private void EnqueueForValidation(ProgramEntry newEntry)
+ {
+ _validationQueue.Enqueue(newEntry);
+
+ // Do not allow more than N shader compilation in-flight, where N is the maximum number of threads
+ // the driver will be using for parallel compilation.
+ // Submitting more seems to cause NVIDIA OpenGL driver to crash.
+ if (_validationQueue.Count >= _backendParallelCompileThreads && _validationQueue.TryDequeue(out ProgramEntry entry))
+ {
+ ProcessCompiledProgram(ref entry, entry.HostProgram.CheckProgramLink(true), asyncCompile: false);
+ }
+ }
+
+ /// <summary>
+ /// Processses the queue of programs that should be translated from guest code.
+ /// </summary>
+ /// <param name="state">Cancellation token</param>
+ private void ProcessAsyncQueue(object state)
+ {
+ CancellationToken ct = (CancellationToken)state;
+
+ try
+ {
+ foreach (AsyncProgramTranslation asyncCompilation in _asyncTranslationQueue.GetConsumingEnumerable(ct))
+ {
+ RecompileFromGuestCode(
+ asyncCompilation.Shaders,
+ asyncCompilation.SpecializationState,
+ asyncCompilation.ProgramIndex,
+ asyncCompilation.IsCompute);
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ }
+
+ /// <summary>
+ /// Recompiles a program from guest code.
+ /// </summary>
+ /// <param name="shaders">Shader stages</param>
+ /// <param name="specState">Specialization state</param>
+ /// <param name="programIndex">Program index</param>
+ /// <param name="isCompute">Indicates if the program is a compute shader</param>
+ private void RecompileFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
+ {
+ try
+ {
+ if (isCompute)
+ {
+ RecompileComputeFromGuestCode(shaders, specState, programIndex);
+ }
+ else
+ {
+ RecompileGraphicsFromGuestCode(shaders, specState, programIndex);
+ }
+ }
+ catch (DiskCacheLoadException diskCacheLoadException)
+ {
+ Logger.Error?.Print(LogClass.Gpu, $"Error translating guest shader. {diskCacheLoadException.Message}");
+
+ ErrorCount++;
+ SignalCompiled();
+ }
+ }
+
+ /// <summary>
+ /// Recompiles a graphics program from guest code.
+ /// </summary>
+ /// <param name="shaders">Shader stages</param>
+ /// <param name="specState">Specialization state</param>
+ /// <param name="programIndex">Program index</param>
+ private void RecompileGraphicsFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex)
+ {
+ ShaderSpecializationState newSpecState = new ShaderSpecializationState(specState.GraphicsState, specState.TransformFeedbackDescriptors);
+ ResourceCounts counts = new ResourceCounts();
+
+ TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
+ TranslatorContext nextStage = null;
+
+ for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
+ {
+ CachedShaderStage shader = shaders[stageIndex + 1];
+
+ if (shader != null)
+ {
+ byte[] guestCode = shader.Code;
+ byte[] cb1Data = shader.Cb1Data;
+
+ DiskCacheGpuAccessor gpuAccessor = new DiskCacheGpuAccessor(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex);
+ TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, DefaultFlags, 0);
+
+ if (nextStage != null)
+ {
+ currentStage.SetNextStage(nextStage);
+ }
+
+ if (stageIndex == 0 && shaders[0] != null)
+ {
+ byte[] guestCodeA = shaders[0].Code;
+ byte[] cb1DataA = shaders[0].Cb1Data;
+
+ DiskCacheGpuAccessor gpuAccessorA = new DiskCacheGpuAccessor(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0);
+ translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, DefaultFlags | TranslationFlags.VertexA, 0);
+ }
+
+ translatorContexts[stageIndex + 1] = currentStage;
+ nextStage = currentStage;
+ }
+ }
+
+ List<ShaderProgram> translatedStages = new List<ShaderProgram>();
+
+ for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
+ {
+ TranslatorContext currentStage = translatorContexts[stageIndex + 1];
+
+ if (currentStage != null)
+ {
+ ShaderProgram program;
+
+ byte[] guestCode = shaders[stageIndex + 1].Code;
+ byte[] cb1Data = shaders[stageIndex + 1].Cb1Data;
+
+ if (stageIndex == 0 && shaders[0] != null)
+ {
+ program = currentStage.Translate(translatorContexts[0]);
+
+ byte[] guestCodeA = shaders[0].Code;
+ byte[] cb1DataA = shaders[0].Cb1Data;
+
+ shaders[0] = new CachedShaderStage(null, guestCodeA, cb1DataA);
+ shaders[1] = new CachedShaderStage(program.Info, guestCode, cb1Data);
+ }
+ else
+ {
+ program = currentStage.Translate();
+
+ shaders[stageIndex + 1] = new CachedShaderStage(program.Info, guestCode, cb1Data);
+ }
+
+ if (program != null)
+ {
+ translatedStages.Add(program);
+ }
+ }
+ }
+
+ _compilationQueue.Enqueue(new ProgramCompilation(translatedStages.ToArray(), shaders, newSpecState, programIndex, isCompute: false));
+ }
+
+ /// <summary>
+ /// Recompiles a compute program from guest code.
+ /// </summary>
+ /// <param name="shaders">Shader stages</param>
+ /// <param name="specState">Specialization state</param>
+ /// <param name="programIndex">Program index</param>
+ private void RecompileComputeFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex)
+ {
+ CachedShaderStage shader = shaders[0];
+ ResourceCounts counts = new ResourceCounts();
+ ShaderSpecializationState newSpecState = new ShaderSpecializationState(specState.ComputeState);
+ DiskCacheGpuAccessor gpuAccessor = new DiskCacheGpuAccessor(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0);
+
+ TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, 0);
+
+ ShaderProgram program = translatorContext.Translate();
+
+ shaders[0] = new CachedShaderStage(program.Info, shader.Code, shader.Cb1Data);
+
+ _compilationQueue.Enqueue(new ProgramCompilation(new[] { program }, shaders, newSpecState, programIndex, isCompute: true));
+ }
+
+ /// <summary>
+ /// Signals that compilation of a program has been finished successfully,
+ /// or that it failed and guest recompilation has also been attempted.
+ /// </summary>
+ private void SignalCompiled()
+ {
+ _stateChangeCallback(ShaderCacheState.Loading, ++_compiledCount, _totalCount);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
index a5c7575f..192467b7 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
@@ -1,5 +1,5 @@
using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
using System;
using System.Runtime.InteropServices;
@@ -9,19 +9,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary>
/// Represents a GPU state and memory accessor.
/// </summary>
- class GpuAccessor : TextureDescriptorCapableGpuAccessor, IGpuAccessor
+ class GpuAccessor : GpuAccessorBase, IGpuAccessor
{
private readonly GpuChannel _channel;
private readonly GpuAccessorState _state;
private readonly int _stageIndex;
private readonly bool _compute;
- private readonly int _localSizeX;
- private readonly int _localSizeY;
- private readonly int _localSizeZ;
- private readonly int _localMemorySize;
- private readonly int _sharedMemorySize;
-
- public int Cb1DataSize { get; private set; }
/// <summary>
/// Creates a new instance of the GPU state accessor for graphics shader translation.
@@ -43,43 +36,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="context">GPU context</param>
/// <param name="channel">GPU channel</param>
/// <param name="state">Current GPU state</param>
- /// <param name="localSizeX">Local group size X of the compute shader</param>
- /// <param name="localSizeY">Local group size Y of the compute shader</param>
- /// <param name="localSizeZ">Local group size Z of the compute shader</param>
- /// <param name="localMemorySize">Local memory size of the compute shader</param>
- /// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
- public GpuAccessor(
- GpuContext context,
- GpuChannel channel,
- GpuAccessorState state,
- int localSizeX,
- int localSizeY,
- int localSizeZ,
- int localMemorySize,
- int sharedMemorySize) : base(context)
+ public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context)
{
_channel = channel;
_state = state;
_compute = true;
- _localSizeX = localSizeX;
- _localSizeY = localSizeY;
- _localSizeZ = localSizeZ;
- _localMemorySize = localMemorySize;
- _sharedMemorySize = sharedMemorySize;
}
- /// <summary>
- /// Reads data from the constant buffer 1.
- /// </summary>
- /// <param name="offset">Offset in bytes to read from</param>
- /// <returns>Value at the given offset</returns>
+ /// <inheritdoc/>
public uint ConstantBuffer1Read(int offset)
{
- if (Cb1DataSize < offset + 4)
- {
- Cb1DataSize = offset + 4;
- }
-
ulong baseAddress = _compute
? _channel.BufferManager.GetComputeUniformBufferAddress(1)
: _channel.BufferManager.GetGraphicsUniformBufferAddress(_stageIndex, 1);
@@ -87,111 +53,115 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _channel.MemoryManager.Physical.Read<uint>(baseAddress + (ulong)offset);
}
- /// <summary>
- /// Prints a log message.
- /// </summary>
- /// <param name="message">Message to print</param>
+ /// <inheritdoc/>
public void Log(string message)
{
Logger.Warning?.Print(LogClass.Gpu, $"Shader translator: {message}");
}
- /// <summary>
- /// Gets a span of the specified memory location, containing shader code.
- /// </summary>
- /// <param name="address">GPU virtual address of the data</param>
- /// <param name="minimumSize">Minimum size that the returned span may have</param>
- /// <returns>Span of the memory location</returns>
- public override ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize)
+ /// <inheritdoc/>
+ public ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize)
{
int size = Math.Max(minimumSize, 0x1000 - (int)(address & 0xfff));
return MemoryMarshal.Cast<byte, ulong>(_channel.MemoryManager.GetSpan(address, size));
}
- /// <summary>
- /// Queries Local Size X for compute shaders.
- /// </summary>
- /// <returns>Local Size X</returns>
- public int QueryComputeLocalSizeX() => _localSizeX;
+ /// <inheritdoc/>
+ public int QueryBindingConstantBuffer(int index)
+ {
+ return _state.ResourceCounts.UniformBuffersCount++;
+ }
- /// <summary>
- /// Queries Local Size Y for compute shaders.
- /// </summary>
- /// <returns>Local Size Y</returns>
- public int QueryComputeLocalSizeY() => _localSizeY;
+ /// <inheritdoc/>
+ public int QueryBindingStorageBuffer(int index)
+ {
+ return _state.ResourceCounts.StorageBuffersCount++;
+ }
- /// <summary>
- /// Queries Local Size Z for compute shaders.
- /// </summary>
- /// <returns>Local Size Z</returns>
- public int QueryComputeLocalSizeZ() => _localSizeZ;
+ /// <inheritdoc/>
+ public int QueryBindingTexture(int index)
+ {
+ return _state.ResourceCounts.TexturesCount++;
+ }
- /// <summary>
- /// Queries Local Memory size in bytes for compute shaders.
- /// </summary>
- /// <returns>Local Memory size in bytes</returns>
- public int QueryComputeLocalMemorySize() => _localMemorySize;
+ /// <inheritdoc/>
+ public int QueryBindingImage(int index)
+ {
+ return _state.ResourceCounts.ImagesCount++;
+ }
- /// <summary>
- /// Queries Shared Memory size in bytes for compute shaders.
- /// </summary>
- /// <returns>Shared Memory size in bytes</returns>
- public int QueryComputeSharedMemorySize() => _sharedMemorySize;
+ /// <inheritdoc/>
+ public int QueryComputeLocalSizeX() => _state.ComputeState.LocalSizeX;
- /// <summary>
- /// Queries Constant Buffer usage information.
- /// </summary>
- /// <returns>A mask where each bit set indicates a bound constant buffer</returns>
+ /// <inheritdoc/>
+ public int QueryComputeLocalSizeY() => _state.ComputeState.LocalSizeY;
+
+ /// <inheritdoc/>
+ public int QueryComputeLocalSizeZ() => _state.ComputeState.LocalSizeZ;
+
+ /// <inheritdoc/>
+ public int QueryComputeLocalMemorySize() => _state.ComputeState.LocalMemorySize;
+
+ /// <inheritdoc/>
+ public int QueryComputeSharedMemorySize() => _state.ComputeState.SharedMemorySize;
+
+ /// <inheritdoc/>
public uint QueryConstantBufferUse()
{
- return _compute
+ uint useMask = _compute
? _channel.BufferManager.GetComputeUniformBufferUseMask()
: _channel.BufferManager.GetGraphicsUniformBufferUseMask(_stageIndex);
+
+ _state.SpecializationState?.RecordConstantBufferUse(_stageIndex, useMask);
+ return useMask;
}
- /// <summary>
- /// Queries current primitive topology for geometry shaders.
- /// </summary>
- /// <returns>Current primitive topology</returns>
+ /// <inheritdoc/>
public InputTopology QueryPrimitiveTopology()
{
- return _state.Topology switch
- {
- PrimitiveTopology.Points => InputTopology.Points,
- PrimitiveTopology.Lines or
- PrimitiveTopology.LineLoop or
- PrimitiveTopology.LineStrip => InputTopology.Lines,
- PrimitiveTopology.LinesAdjacency or
- PrimitiveTopology.LineStripAdjacency => InputTopology.LinesAdjacency,
- PrimitiveTopology.Triangles or
- PrimitiveTopology.TriangleStrip or
- PrimitiveTopology.TriangleFan => InputTopology.Triangles,
- PrimitiveTopology.TrianglesAdjacency or
- PrimitiveTopology.TriangleStripAdjacency => InputTopology.TrianglesAdjacency,
- PrimitiveTopology.Patches => _state.TessellationMode.UnpackPatchType() == TessPatchType.Isolines
- ? InputTopology.Lines
- : InputTopology.Triangles,
- _ => InputTopology.Points
- };
+ _state.SpecializationState?.RecordPrimitiveTopology();
+ return ConvertToInputTopology(_state.GraphicsState.Topology, _state.GraphicsState.TessellationMode);
}
- /// <summary>
- /// Queries the tessellation evaluation shader primitive winding order.
- /// </summary>
- /// <returns>True if the primitive winding order is clockwise, false if counter-clockwise</returns>
- public bool QueryTessCw() => _state.TessellationMode.UnpackCw();
+ /// <inheritdoc/>
+ public bool QueryTessCw()
+ {
+ return _state.GraphicsState.TessellationMode.UnpackCw();
+ }
- /// <summary>
- /// Queries the tessellation evaluation shader abstract patch type.
- /// </summary>
- /// <returns>Abstract patch type</returns>
- public TessPatchType QueryTessPatchType() => _state.TessellationMode.UnpackPatchType();
+ /// <inheritdoc/>
+ public TessPatchType QueryTessPatchType()
+ {
+ return _state.GraphicsState.TessellationMode.UnpackPatchType();
+ }
- /// <summary>
- /// Queries the tessellation evaluation shader spacing between tessellated vertices of the patch.
- /// </summary>
- /// <returns>Spacing between tessellated vertices of the patch</returns>
- public TessSpacing QueryTessSpacing() => _state.TessellationMode.UnpackSpacing();
+ /// <inheritdoc/>
+ public TessSpacing QueryTessSpacing()
+ {
+ return _state.GraphicsState.TessellationMode.UnpackSpacing();
+ }
+
+ //// <inheritdoc/>
+ public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
+ {
+ _state.SpecializationState?.RecordTextureFormat(_stageIndex, handle, cbufSlot);
+ var descriptor = GetTextureDescriptor(handle, cbufSlot);
+ return ConvertToTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb());
+ }
+
+ /// <inheritdoc/>
+ public SamplerType QuerySamplerType(int handle, int cbufSlot)
+ {
+ _state.SpecializationState?.RecordTextureSamplerType(_stageIndex, handle, cbufSlot);
+ return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType();
+ }
+
+ /// <inheritdoc/>
+ public bool QueryTextureCoordNormalized(int handle, int cbufSlot)
+ {
+ _state.SpecializationState?.RecordTextureCoordNormalized(_stageIndex, handle, cbufSlot);
+ return GetTextureDescriptor(handle, cbufSlot).UnpackTextureCoordNormalized();
+ }
/// <summary>
/// Gets the texture descriptor for a given texture on the pool.
@@ -199,65 +169,58 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="handle">Index of the texture (this is the word offset of the handle in the constant buffer)</param>
/// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
/// <returns>Texture descriptor</returns>
- public override Image.ITextureDescriptor GetTextureDescriptor(int handle, int cbufSlot)
+ private Image.TextureDescriptor GetTextureDescriptor(int handle, int cbufSlot)
{
if (_compute)
{
return _channel.TextureManager.GetComputeTextureDescriptor(
- _state.TexturePoolGpuVa,
- _state.TextureBufferIndex,
- _state.TexturePoolMaximumId,
+ _state.PoolState.TexturePoolGpuVa,
+ _state.PoolState.TextureBufferIndex,
+ _state.PoolState.TexturePoolMaximumId,
handle,
cbufSlot);
}
else
{
return _channel.TextureManager.GetGraphicsTextureDescriptor(
- _state.TexturePoolGpuVa,
- _state.TextureBufferIndex,
- _state.TexturePoolMaximumId,
+ _state.PoolState.TexturePoolGpuVa,
+ _state.PoolState.TextureBufferIndex,
+ _state.PoolState.TexturePoolMaximumId,
_stageIndex,
handle,
cbufSlot);
}
}
- /// <summary>
- /// Queries transform feedback enable state.
- /// </summary>
- /// <returns>True if the shader uses transform feedback, false otherwise</returns>
+ /// <inheritdoc/>
public bool QueryTransformFeedbackEnabled()
{
return _state.TransformFeedbackDescriptors != null;
}
- /// <summary>
- /// Queries the varying locations that should be written to the transform feedback buffer.
- /// </summary>
- /// <param name="bufferIndex">Index of the transform feedback buffer</param>
- /// <returns>Varying locations for the specified buffer</returns>
+ /// <inheritdoc/>
public ReadOnlySpan<byte> QueryTransformFeedbackVaryingLocations(int bufferIndex)
{
- return _state.TransformFeedbackDescriptors[bufferIndex].VaryingLocations;
+ return _state.TransformFeedbackDescriptors[bufferIndex].AsSpan();
}
- /// <summary>
- /// Queries the stride (in bytes) of the per vertex data written into the transform feedback buffer.
- /// </summary>
- /// <param name="bufferIndex">Index of the transform feedback buffer</param>
- /// <returns>Stride for the specified buffer</returns>
+ /// <inheritdoc/>
public int QueryTransformFeedbackStride(int bufferIndex)
{
return _state.TransformFeedbackDescriptors[bufferIndex].Stride;
}
- /// <summary>
- /// Queries if host state forces early depth testing.
- /// </summary>
- /// <returns>True if early depth testing is forced</returns>
+ /// <inheritdoc/>
public bool QueryEarlyZForce()
{
- return _state.EarlyZForce;
+ _state.SpecializationState?.RecordEarlyZForce();
+ return _state.GraphicsState.EarlyZForce;
+ }
+
+ /// <inheritdoc/>
+ public void RegisterTexture(int handle, int cbufSlot)
+ {
+ _state.SpecializationState?.RegisterTexture(_stageIndex, handle, cbufSlot, GetTextureDescriptor(handle, cbufSlot));
}
}
}
diff --git a/Ryujinx.Graphics.Gpu/Shader/TextureDescriptorCapableGpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
index 3a8ee67a..5f9dd588 100644
--- a/Ryujinx.Graphics.Gpu/Shader/TextureDescriptorCapableGpuAccessor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
@@ -1,23 +1,26 @@
-using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
-using System;
namespace Ryujinx.Graphics.Gpu.Shader
{
- abstract class TextureDescriptorCapableGpuAccessor : IGpuAccessor
+ /// <summary>
+ /// GPU accessor.
+ /// </summary>
+ class GpuAccessorBase
{
private readonly GpuContext _context;
- public TextureDescriptorCapableGpuAccessor(GpuContext context)
+ /// <summary>
+ /// Creates a new GPU accessor.
+ /// </summary>
+ /// <param name="context">GPU context</param>
+ public GpuAccessorBase(GpuContext context)
{
_context = context;
}
- public abstract ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize);
-
- public abstract ITextureDescriptor GetTextureDescriptor(int handle, int cbufSlot);
-
/// <summary>
/// Queries host about the presence of the FrontFacing built-in variable bug.
/// </summary>
@@ -79,20 +82,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
/// <summary>
- /// Queries texture format information, for shaders using image load or store.
+ /// Converts a packed Maxwell texture format to the shader translator texture format.
/// </summary>
- /// <remarks>
- /// This only returns non-compressed color formats.
- /// If the format of the texture is a compressed, depth or unsupported format, then a default value is returned.
- /// </remarks>
- /// <param name="handle">Texture handle</param>
- /// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
- /// <returns>Color format of the non-compressed texture</returns>
- public TextureFormat QueryTextureFormat(int handle, int cbufSlot = -1)
+ /// <param name="format">Packed maxwell format</param>
+ /// <param name="formatSrgb">Indicates if the format is sRGB</param>
+ /// <returns>Shader translator texture format</returns>
+ protected static TextureFormat ConvertToTextureFormat(uint format, bool formatSrgb)
{
- var descriptor = GetTextureDescriptor(handle, cbufSlot);
-
- if (!FormatTable.TryGetTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb(), out FormatInfo formatInfo))
+ if (!FormatTable.TryGetTextureFormat(format, formatSrgb, out FormatInfo formatInfo))
{
return TextureFormat.Unknown;
}
@@ -144,32 +141,31 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
- /// Queries sampler type information.
+ /// Converts the Maxwell primitive topology to the shader translator topology.
/// </summary>
- /// <param name="handle">Texture handle</param>
- /// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
- /// <returns>The sampler type value for the given handle</returns>
- public SamplerType QuerySamplerType(int handle, int cbufSlot = -1)
+ /// <param name="topology">Maxwell primitive topology</param>
+ /// <param name="tessellationMode">Maxwell tessellation mode</param>
+ /// <returns>Shader translator topology</returns>
+ protected static InputTopology ConvertToInputTopology(PrimitiveTopology topology, TessMode tessellationMode)
{
- return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType();
- }
-
- /// <summary>
- /// Queries texture target information.
- /// </summary>
- /// <param name="handle">Texture handle</param>
- /// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
- /// <returns>True if the texture is a rectangle texture, false otherwise</returns>
- public bool QueryIsTextureRectangle(int handle, int cbufSlot = -1)
- {
- var descriptor = GetTextureDescriptor(handle, cbufSlot);
-
- TextureTarget target = descriptor.UnpackTextureTarget();
-
- bool is2DTexture = target == TextureTarget.Texture2D ||
- target == TextureTarget.Texture2DRect;
-
- return !descriptor.UnpackTextureCoordNormalized() && is2DTexture;
+ return topology switch
+ {
+ PrimitiveTopology.Points => InputTopology.Points,
+ PrimitiveTopology.Lines or
+ PrimitiveTopology.LineLoop or
+ PrimitiveTopology.LineStrip => InputTopology.Lines,
+ PrimitiveTopology.LinesAdjacency or
+ PrimitiveTopology.LineStripAdjacency => InputTopology.LinesAdjacency,
+ PrimitiveTopology.Triangles or
+ PrimitiveTopology.TriangleStrip or
+ PrimitiveTopology.TriangleFan => InputTopology.Triangles,
+ PrimitiveTopology.TrianglesAdjacency or
+ PrimitiveTopology.TriangleStripAdjacency => InputTopology.TrianglesAdjacency,
+ PrimitiveTopology.Patches => tessellationMode.UnpackPatchType() == TessPatchType.Isolines
+ ? InputTopology.Lines
+ : InputTopology.Triangles,
+ _ => InputTopology.Points
+ };
}
}
}
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs
index 6818072b..0e8e979c 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs
@@ -1,72 +1,61 @@
-using Ryujinx.Graphics.GAL;
-using Ryujinx.Graphics.Gpu.Engine.Threed;
-
namespace Ryujinx.Graphics.Gpu.Shader
{
/// <summary>
/// State used by the <see cref="GpuAccessor"/>.
/// </summary>
- struct GpuAccessorState
+ class GpuAccessorState
{
/// <summary>
- /// GPU virtual address of the texture pool.
- /// </summary>
- public ulong TexturePoolGpuVa { get; }
-
- /// <summary>
- /// Maximum ID of the texture pool.
+ /// GPU texture pool state.
/// </summary>
- public int TexturePoolMaximumId { get; }
+ public readonly GpuChannelPoolState PoolState;
/// <summary>
- /// Constant buffer slot where the texture handles are located.
+ /// GPU compute state, for compute shaders.
/// </summary>
- public int TextureBufferIndex { get; }
+ public readonly GpuChannelComputeState ComputeState;
/// <summary>
- /// Early Z force enable.
+ /// GPU graphics state, for vertex, tessellation, geometry and fragment shaders.
/// </summary>
- public bool EarlyZForce { get; }
+ public readonly GpuChannelGraphicsState GraphicsState;
/// <summary>
- /// Primitive topology of current draw.
+ /// Shader specialization state (shared by all stages).
/// </summary>
- public PrimitiveTopology Topology { get; }
+ public readonly ShaderSpecializationState SpecializationState;
/// <summary>
- /// Tessellation mode.
+ /// Transform feedback information, if the shader uses transform feedback. Otherwise, should be null.
/// </summary>
- public TessMode TessellationMode { get; }
+ public readonly TransformFeedbackDescriptor[] TransformFeedbackDescriptors;
/// <summary>
- /// Transform feedback information, if the shader uses transform feedback. Otherwise, should be null.
+ /// Shader resource counts (shared by all stages).
/// </summary>
- public TransformFeedbackDescriptor[] TransformFeedbackDescriptors { get; set; }
+ public readonly ResourceCounts ResourceCounts;
/// <summary>
- /// Creates a new instance of the GPU accessor state.
+ /// Creates a new GPU accessor state.
/// </summary>
- /// <param name="texturePoolGpuVa">GPU virtual address of the texture pool</param>
- /// <param name="texturePoolMaximumId">Maximum ID of the texture pool</param>
- /// <param name="textureBufferIndex">Constant buffer slot where the texture handles are located</param>
- /// <param name="earlyZForce">Early Z force enable</param>
- /// <param name="topology">Primitive topology</param>
- /// <param name="tessellationMode">Tessellation mode</param>
+ /// <param name="poolState">GPU texture pool state</param>
+ /// <param name="computeState">GPU compute state, for compute shaders</param>
+ /// <param name="graphicsState">GPU graphics state, for vertex, tessellation, geometry and fragment shaders</param>
+ /// <param name="specializationState">Shader specialization state (shared by all stages)</param>
+ /// <param name="transformFeedbackDescriptors">Transform feedback information, if the shader uses transform feedback. Otherwise, should be null</param>
public GpuAccessorState(
- ulong texturePoolGpuVa,
- int texturePoolMaximumId,
- int textureBufferIndex,
- bool earlyZForce,
- PrimitiveTopology topology,
- TessMode tessellationMode)
+ GpuChannelPoolState poolState,
+ GpuChannelComputeState computeState,
+ GpuChannelGraphicsState graphicsState,
+ ShaderSpecializationState specializationState,
+ TransformFeedbackDescriptor[] transformFeedbackDescriptors = null)
{
- TexturePoolGpuVa = texturePoolGpuVa;
- TexturePoolMaximumId = texturePoolMaximumId;
- TextureBufferIndex = textureBufferIndex;
- EarlyZForce = earlyZForce;
- Topology = topology;
- TessellationMode = tessellationMode;
- TransformFeedbackDescriptors = null;
+ PoolState = poolState;
+ GraphicsState = graphicsState;
+ ComputeState = computeState;
+ SpecializationState = specializationState;
+ TransformFeedbackDescriptors = transformFeedbackDescriptors;
+ ResourceCounts = new ResourceCounts();
}
}
} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuChannelComputeState.cs b/Ryujinx.Graphics.Gpu/Shader/GpuChannelComputeState.cs
new file mode 100644
index 00000000..89a3db71
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuChannelComputeState.cs
@@ -0,0 +1,57 @@
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ /// <summary>
+ /// State used by the <see cref="GpuAccessor"/>.
+ /// </summary>
+ struct GpuChannelComputeState
+ {
+ // New fields should be added to the end of the struct to keep disk shader cache compatibility.
+
+ /// <summary>
+ /// Local group size X of the compute shader.
+ /// </summary>
+ public readonly int LocalSizeX;
+
+ /// <summary>
+ /// Local group size Y of the compute shader.
+ /// </summary>
+ public readonly int LocalSizeY;
+
+ /// <summary>
+ /// Local group size Z of the compute shader.
+ /// </summary>
+ public readonly int LocalSizeZ;
+
+ /// <summary>
+ /// Local memory size of the compute shader.
+ /// </summary>
+ public readonly int LocalMemorySize;
+
+ /// <summary>
+ /// Shared memory size of the compute shader.
+ /// </summary>
+ public readonly int SharedMemorySize;
+
+ /// <summary>
+ /// Creates a new GPU compute state.
+ /// </summary>
+ /// <param name="localSizeX">Local group size X of the compute shader</param>
+ /// <param name="localSizeY">Local group size Y of the compute shader</param>
+ /// <param name="localSizeZ">Local group size Z of the compute shader</param>
+ /// <param name="localMemorySize">Local memory size of the compute shader</param>
+ /// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
+ public GpuChannelComputeState(
+ int localSizeX,
+ int localSizeY,
+ int localSizeZ,
+ int localMemorySize,
+ int sharedMemorySize)
+ {
+ LocalSizeX = localSizeX;
+ LocalSizeY = localSizeY;
+ LocalSizeZ = localSizeZ;
+ LocalMemorySize = localMemorySize;
+ SharedMemorySize = sharedMemorySize;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
new file mode 100644
index 00000000..5eb31db6
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
@@ -0,0 +1,41 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Engine.Threed;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ /// <summary>
+ /// State used by the <see cref="GpuAccessor"/>.
+ /// </summary>
+ struct GpuChannelGraphicsState
+ {
+ // New fields should be added to the end of the struct to keep disk shader cache compatibility.
+
+ /// <summary>
+ /// Early Z force enable.
+ /// </summary>
+ public readonly bool EarlyZForce;
+
+ /// <summary>
+ /// Primitive topology of current draw.
+ /// </summary>
+ public readonly PrimitiveTopology Topology;
+
+ /// <summary>
+ /// Tessellation mode.
+ /// </summary>
+ public readonly TessMode TessellationMode;
+
+ /// <summary>
+ /// Creates a new GPU graphics state.
+ /// </summary>
+ /// <param name="earlyZForce">Early Z force enable</param>
+ /// <param name="topology">Primitive topology</param>
+ /// <param name="tessellationMode">Tessellation mode</param>
+ public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode)
+ {
+ EarlyZForce = earlyZForce;
+ Topology = topology;
+ TessellationMode = tessellationMode;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs b/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs
new file mode 100644
index 00000000..0b36227a
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs
@@ -0,0 +1,36 @@
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ /// <summary>
+ /// State used by the <see cref="GpuAccessor"/>.
+ /// </summary>
+ struct GpuChannelPoolState
+ {
+ /// <summary>
+ /// GPU virtual address of the texture pool.
+ /// </summary>
+ public readonly ulong TexturePoolGpuVa;
+
+ /// <summary>
+ /// Maximum ID of the texture pool.
+ /// </summary>
+ public readonly int TexturePoolMaximumId;
+
+ /// <summary>
+ /// Constant buffer slot where the texture handles are located.
+ /// </summary>
+ public readonly int TextureBufferIndex;
+
+ /// <summary>
+ /// Creates a new GPU texture pool state.
+ /// </summary>
+ /// <param name="texturePoolGpuVa">GPU virtual address of the texture pool</param>
+ /// <param name="texturePoolMaximumId">Maximum ID of the texture pool</param>
+ /// <param name="textureBufferIndex">Constant buffer slot where the texture handles are located</param>
+ public GpuChannelPoolState(ulong texturePoolGpuVa, int texturePoolMaximumId, int textureBufferIndex)
+ {
+ TexturePoolGpuVa = texturePoolGpuVa;
+ TexturePoolMaximumId = texturePoolMaximumId;
+ TextureBufferIndex = textureBufferIndex;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/HashTable/HashState.cs b/Ryujinx.Graphics.Gpu/Shader/HashTable/HashState.cs
new file mode 100644
index 00000000..584eefdc
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/HashTable/HashState.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader.HashTable
+{
+ /// <summary>
+ /// State of a hash calculation.
+ /// </summary>
+ struct HashState
+ {
+ // This is using a slightly modified implementation of FastHash64.
+ // Reference: https://github.com/ztanml/fast-hash/blob/master/fasthash.c
+ private const ulong M = 0x880355f21e6d1965UL;
+ private ulong _hash;
+ private int _start;
+
+ /// <summary>
+ /// One shot hash calculation for a given data.
+ /// </summary>
+ /// <param name="data">Data to be hashed</param>
+ /// <returns>Hash of the given data</returns>
+ public static uint CalcHash(ReadOnlySpan<byte> data)
+ {
+ HashState state = new HashState();
+
+ state.Initialize();
+ state.Continue(data);
+ return state.Finalize(data);
+ }
+
+ /// <summary>
+ /// Initializes the hash state.
+ /// </summary>
+ public void Initialize()
+ {
+ _hash = 23;
+ }
+
+ /// <summary>
+ /// Calculates the hash of the given data.
+ /// </summary>
+ /// <remarks>
+ /// The full data must be passed on <paramref name="data"/>.
+ /// If this is not the first time the method is called, then <paramref name="data"/> must start with the data passed on the last call.
+ /// If a smaller slice of the data was already hashed before, only the additional data will be hashed.
+ /// This can be used for additive hashing of data in chuncks.
+ /// </remarks>
+ /// <param name="data">Data to be hashed</param>
+ public void Continue(ReadOnlySpan<byte> data)
+ {
+ ulong h = _hash;
+
+ ReadOnlySpan<ulong> dataAsUlong = MemoryMarshal.Cast<byte, ulong>(data.Slice(_start));
+
+ for (int i = 0; i < dataAsUlong.Length; i++)
+ {
+ ulong value = dataAsUlong[i];
+
+ h ^= Mix(value);
+ h *= M;
+ }
+
+ _hash = h;
+ _start = data.Length & ~7;
+ }
+
+ /// <summary>
+ /// Performs the hash finalization step, and returns the calculated hash.
+ /// </summary>
+ /// <remarks>
+ /// The full data must be passed on <paramref name="data"/>.
+ /// <paramref name="data"/> must start with the data passed on the last call to <see cref="Continue"/>.
+ /// No internal state is changed, so one can still continue hashing data with <see cref="Continue"/>
+ /// after calling this method.
+ /// </remarks>
+ /// <param name="data">Data to be hashed</param>
+ /// <returns>Hash of all the data hashed with this <see cref="HashState"/></returns>
+ public uint Finalize(ReadOnlySpan<byte> data)
+ {
+ ulong h = _hash;
+
+ int remainder = data.Length & 7;
+ if (remainder != 0)
+ {
+ ulong v = 0;
+
+ for (int i = data.Length - remainder; i < data.Length; i++)
+ {
+ v |= (ulong)data[i] << ((i - remainder) * 8);
+ }
+
+ h ^= Mix(v);
+ h *= M;
+ }
+
+ h = Mix(h);
+ return (uint)(h - (h >> 32));
+ }
+
+ /// <summary>
+ /// Hash mix function.
+ /// </summary>
+ /// <param name="h">Hash to mix</param>
+ /// <returns>Mixed hash</returns>
+ private static ulong Mix(ulong h)
+ {
+ h ^= h >> 23;
+ h *= 0x2127599bf4325c37UL;
+ h ^= h >> 47;
+ return h;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/HashTable/IDataAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/HashTable/IDataAccessor.cs
new file mode 100644
index 00000000..c982cd9f
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/HashTable/IDataAccessor.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Shader.HashTable
+{
+ /// <summary>
+ /// Data accessor, used by <see cref="PartitionedHashTable{T}"/> to access data of unknown length.
+ /// </summary>
+ /// <remarks>
+ /// This will be used to access chuncks of data and try finding a match on the table.
+ /// This is necessary because the data size is assumed to be unknown, and so the
+ /// hash table must try to "guess" the size of the data based on the entries on the table.
+ /// </remarks>
+ public interface IDataAccessor
+ {
+ /// <summary>
+ /// Gets a span of shader code at the specified offset, with at most the specified size.
+ /// </summary>
+ /// <remarks>
+ /// This might return a span smaller than the requested <paramref name="length"/> if there's
+ /// no more code available.
+ /// </remarks>
+ /// <param name="offset">Offset in shader code</param>
+ /// <param name="length">Size in bytes</param>
+ /// <returns>Shader code span</returns>
+ ReadOnlySpan<byte> GetSpan(int offset, int length);
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionHashTable.cs b/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionHashTable.cs
new file mode 100644
index 00000000..6a563c16
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionHashTable.cs
@@ -0,0 +1,452 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+namespace Ryujinx.Graphics.Gpu.Shader.HashTable
+{
+ /// <summary>
+ /// Partitioned hash table.
+ /// </summary>
+ /// <typeparam name="T">Hash table entry type</typeparam>
+ class PartitionHashTable<T>
+ {
+ /// <summary>
+ /// Hash table entry.
+ /// </summary>
+ private struct Entry
+ {
+ /// <summary>
+ /// Hash <see cref="OwnSize"/> bytes of <see cref="Data"/>.
+ /// </summary>
+ public readonly uint Hash;
+
+ /// <summary>
+ /// If this entry is only a sub-region of <see cref="Data"/>, this indicates the size in bytes
+ /// of that region. Otherwise, it should be zero.
+ /// </summary>
+ public readonly int OwnSize;
+
+ /// <summary>
+ /// Data used to compute the hash for this entry.
+ /// </summary>
+ /// <remarks>
+ /// To avoid additional allocations, this might be a instance of the full entry data,
+ /// and only a sub-region of it might be actually used by this entry. Such sub-region
+ /// has its size indicated by <see cref="OwnSize"/> in this case.
+ /// </remarks>
+ public readonly byte[] Data;
+
+ /// <summary>
+ /// Item associated with this entry.
+ /// </summary>
+ public T Item;
+
+ /// <summary>
+ /// Indicates if the entry is partial, which means that this entry is only for a sub-region of the data.
+ /// </summary>
+ /// <remarks>
+ /// Partial entries have no items associated with them. They just indicates that the data might be present on
+ /// the table, and one must keep looking for the full entry on other tables of larger data size.
+ /// </remarks>
+ public bool IsPartial => OwnSize != 0;
+
+ /// <summary>
+ /// Creates a new partial hash table entry.
+ /// </summary>
+ /// <param name="hash">Hash of the data</param>
+ /// <param name="ownerData">Full data</param>
+ /// <param name="ownSize">Size of the sub-region of data that belongs to this entry</param>
+ public Entry(uint hash, byte[] ownerData, int ownSize)
+ {
+ Hash = hash;
+ OwnSize = ownSize;
+ Data = ownerData;
+ Item = default;
+ }
+
+ /// <summary>
+ /// Creates a new full hash table entry.
+ /// </summary>
+ /// <param name="hash">Hash of the data</param>
+ /// <param name="data">Data</param>
+ /// <param name="item">Item associated with this entry</param>
+ public Entry(uint hash, byte[] data, T item)
+ {
+ Hash = hash;
+ OwnSize = 0;
+ Data = data;
+ Item = item;
+ }
+
+ /// <summary>
+ /// Gets the data for this entry, either full or partial.
+ /// </summary>
+ /// <returns>Data sub-region</returns>
+ public ReadOnlySpan<byte> GetData()
+ {
+ if (OwnSize != 0)
+ {
+ return new ReadOnlySpan<byte>(Data).Slice(0, OwnSize);
+ }
+
+ return Data;
+ }
+ }
+
+ /// <summary>
+ /// Hash table bucket.
+ /// </summary>
+ private struct Bucket
+ {
+ /// <summary>
+ /// Inline entry, to avoid allocations for the common single entry case.
+ /// </summary>
+ public Entry InlineEntry;
+
+ /// <summary>
+ /// List of additional entries for the not-so-common multiple entries case.
+ /// </summary>
+ public List<Entry> MoreEntries;
+ }
+
+ private Bucket[] _buckets;
+ private int _count;
+
+ /// <summary>
+ /// Total amount of entries on the hash table.
+ /// </summary>
+ public int Count => _count;
+
+ /// <summary>
+ /// Creates a new instance of the partitioned hash table.
+ /// </summary>
+ public PartitionHashTable()
+ {
+ _buckets = Array.Empty<Bucket>();
+ }
+
+ /// <summary>
+ /// Gets an item on the table, or adds a new one if not present.
+ /// </summary>
+ /// <param name="data">Data</param>
+ /// <param name="dataHash">Hash of the data</param>
+ /// <param name="item">Item to be added if not found</param>
+ /// <returns>Existing item if found, or <paramref name="item"/> if not found</returns>
+ public T GetOrAdd(byte[] data, uint dataHash, T item)
+ {
+ if (TryFindItem(dataHash, data, out T existingItem))
+ {
+ return existingItem;
+ }
+
+ Entry entry = new Entry(dataHash, data, item);
+
+ AddToBucket(dataHash, ref entry);
+
+ return item;
+ }
+
+ /// <summary>
+ /// Adds an item to the hash table.
+ /// </summary>
+ /// <param name="data">Data</param>
+ /// <param name="dataHash">Hash of the data</param>
+ /// <param name="item">Item to be added</param>
+ /// <returns>True if the item was added, false due to an item associated with the data already being on the table</returns>
+ public bool Add(byte[] data, uint dataHash, T item)
+ {
+ if (TryFindItem(dataHash, data, out _))
+ {
+ return false;
+ }
+
+ Entry entry = new Entry(dataHash, data, item);
+
+ AddToBucket(dataHash, ref entry);
+
+ return true;
+ }
+
+ /// <summary>
+ /// Adds a partial entry to the hash table.
+ /// </summary>
+ /// <param name="ownerData">Full data</param>
+ /// <param name="ownSize">Size of the sub-region of <paramref name="ownerData"/> used by the partial entry</param>
+ /// <returns>True if added, false otherwise</returns>
+ public bool AddPartial(byte[] ownerData, int ownSize)
+ {
+ ReadOnlySpan<byte> data = new ReadOnlySpan<byte>(ownerData).Slice(0, ownSize);
+
+ return AddPartial(ownerData, HashState.CalcHash(data), ownSize);
+ }
+
+ /// <summary>
+ /// Adds a partial entry to the hash table.
+ /// </summary>
+ /// <param name="ownerData">Full data</param>
+ /// <param name="dataHash">Hash of the data sub-region</param>
+ /// <param name="ownSize">Size of the sub-region of <paramref name="ownerData"/> used by the partial entry</param>
+ /// <returns>True if added, false otherwise</returns>
+ public bool AddPartial(byte[] ownerData, uint dataHash, int ownSize)
+ {
+ ReadOnlySpan<byte> data = new ReadOnlySpan<byte>(ownerData).Slice(0, ownSize);
+
+ if (TryFindItem(dataHash, data, out _))
+ {
+ return false;
+ }
+
+ Entry entry = new Entry(dataHash, ownerData, ownSize);
+
+ AddToBucket(dataHash, ref entry);
+
+ return true;
+ }
+
+ /// <summary>
+ /// Adds entry with a given hash to the table.
+ /// </summary>
+ /// <param name="dataHash">Hash of the entry</param>
+ /// <param name="entry">Entry</param>
+ private void AddToBucket(uint dataHash, ref Entry entry)
+ {
+ int pow2Count = GetPow2Count(++_count);
+ if (pow2Count != _buckets.Length)
+ {
+ Rebuild(pow2Count);
+ }
+
+ ref Bucket bucket = ref GetBucketForHash(dataHash);
+
+ AddToBucket(ref bucket, ref entry);
+ }
+
+ /// <summary>
+ /// Adds an entry to a bucket.
+ /// </summary>
+ /// <param name="bucket">Bucket to add the entry into</param>
+ /// <param name="entry">Entry to be added</param>
+ private void AddToBucket(ref Bucket bucket, ref Entry entry)
+ {
+ if (bucket.InlineEntry.Data == null)
+ {
+ bucket.InlineEntry = entry;
+ }
+ else
+ {
+ (bucket.MoreEntries ??= new List<Entry>()).Add(entry);
+ }
+ }
+
+ /// <summary>
+ /// Creates partial entries on a new hash table for all existing full entries.
+ /// </summary>
+ /// <remarks>
+ /// This should be called every time a new hash table is created, and there are hash
+ /// tables with data sizes that are higher than that of the new table.
+ /// This will then fill the new hash table with "partial" entries of full entries
+ /// on the hash tables with higher size.
+ /// </remarks>
+ /// <param name="newTable">New hash table</param>
+ /// <param name="newEntrySize">Size of the data on the new hash table</param>
+ public void FillPartials(PartitionHashTable<T> newTable, int newEntrySize)
+ {
+ for (int i = 0; i < _buckets.Length; i++)
+ {
+ ref Bucket bucket = ref _buckets[i];
+ ref Entry inlineEntry = ref bucket.InlineEntry;
+
+ if (inlineEntry.Data != null)
+ {
+ if (!inlineEntry.IsPartial)
+ {
+ newTable.AddPartial(inlineEntry.Data, newEntrySize);
+ }
+
+ if (bucket.MoreEntries != null)
+ {
+ foreach (Entry entry in bucket.MoreEntries)
+ {
+ if (entry.IsPartial)
+ {
+ continue;
+ }
+
+ newTable.AddPartial(entry.Data, newEntrySize);
+ }
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Tries to find an item on the table.
+ /// </summary>
+ /// <param name="dataHash">Hash of <paramref name="data"/></param>
+ /// <param name="data">Data to find</param>
+ /// <param name="item">Item associated with the data</param>
+ /// <returns>True if an item was found, false otherwise</returns>
+ private bool TryFindItem(uint dataHash, ReadOnlySpan<byte> data, out T item)
+ {
+ if (_count == 0)
+ {
+ item = default;
+ return false;
+ }
+
+ ref Bucket bucket = ref GetBucketForHash(dataHash);
+
+ if (bucket.InlineEntry.Data != null)
+ {
+ if (bucket.InlineEntry.Hash == dataHash && bucket.InlineEntry.GetData().SequenceEqual(data))
+ {
+ item = bucket.InlineEntry.Item;
+ return true;
+ }
+
+ if (bucket.MoreEntries != null)
+ {
+ foreach (Entry entry in bucket.MoreEntries)
+ {
+ if (entry.Hash == dataHash && entry.GetData().SequenceEqual(data))
+ {
+ item = entry.Item;
+ return true;
+ }
+ }
+ }
+ }
+
+ item = default;
+ return false;
+ }
+
+ /// <summary>
+ /// Indicates the result of a hash table lookup.
+ /// </summary>
+ public enum SearchResult
+ {
+ /// <summary>
+ /// No entry was found, the search must continue on hash tables of lower size.
+ /// </summary>
+ NotFound,
+
+ /// <summary>
+ /// A partial entry was found, the search must continue on hash tables of higher size.
+ /// </summary>
+ FoundPartial,
+
+ /// <summary>
+ /// A full entry was found, the search was concluded and the item can be retrieved.
+ /// </summary>
+ FoundFull
+ }
+
+ /// <summary>
+ /// Tries to find an item on the table.
+ /// </summary>
+ /// <param name="dataAccessor">Data accessor</param>
+ /// <param name="size">Size of the hash table data</param>
+ /// <param name="item">The item on the table, if found, otherwise unmodified</param>
+ /// <param name="data">The data on the table, if found, otherwise unmodified</param>
+ /// <returns>Table lookup result</returns>
+ public SearchResult TryFindItem(ref SmartDataAccessor dataAccessor, int size, ref T item, ref byte[] data)
+ {
+ if (_count == 0)
+ {
+ return SearchResult.NotFound;
+ }
+
+ ReadOnlySpan<byte> dataSpan = dataAccessor.GetSpanAndHash(size, out uint dataHash);
+
+ if (dataSpan.Length != size)
+ {
+ return SearchResult.NotFound;
+ }
+
+ ref Bucket bucket = ref GetBucketForHash(dataHash);
+
+ if (bucket.InlineEntry.Data != null)
+ {
+ if (bucket.InlineEntry.Hash == dataHash && bucket.InlineEntry.GetData().SequenceEqual(dataSpan))
+ {
+ item = bucket.InlineEntry.Item;
+ data = bucket.InlineEntry.Data;
+ return bucket.InlineEntry.IsPartial ? SearchResult.FoundPartial : SearchResult.FoundFull;
+ }
+
+ if (bucket.MoreEntries != null)
+ {
+ foreach (Entry entry in bucket.MoreEntries)
+ {
+ if (entry.Hash == dataHash && entry.GetData().SequenceEqual(dataSpan))
+ {
+ item = entry.Item;
+ data = entry.Data;
+ return entry.IsPartial ? SearchResult.FoundPartial : SearchResult.FoundFull;
+ }
+ }
+ }
+ }
+
+ return SearchResult.NotFound;
+ }
+
+ /// <summary>
+ /// Rebuilds the table for a new count.
+ /// </summary>
+ /// <param name="newPow2Count">New power of two count of the table</param>
+ private void Rebuild(int newPow2Count)
+ {
+ Bucket[] newBuckets = new Bucket[newPow2Count];
+
+ uint mask = (uint)newPow2Count - 1;
+
+ for (int i = 0; i < _buckets.Length; i++)
+ {
+ ref Bucket bucket = ref _buckets[i];
+
+ if (bucket.InlineEntry.Data != null)
+ {
+ AddToBucket(ref newBuckets[(int)(bucket.InlineEntry.Hash & mask)], ref bucket.InlineEntry);
+
+ if (bucket.MoreEntries != null)
+ {
+ foreach (Entry entry in bucket.MoreEntries)
+ {
+ Entry entryCopy = entry;
+ AddToBucket(ref newBuckets[(int)(entry.Hash & mask)], ref entryCopy);
+ }
+ }
+ }
+ }
+
+ _buckets = newBuckets;
+ }
+
+ /// <summary>
+ /// Gets the bucket for a given hash.
+ /// </summary>
+ /// <param name="hash">Data hash</param>
+ /// <returns>Bucket for the hash</returns>
+ private ref Bucket GetBucketForHash(uint hash)
+ {
+ int index = (int)(hash & (_buckets.Length - 1));
+
+ return ref _buckets[index];
+ }
+
+ /// <summary>
+ /// Gets a power of two count from a regular count.
+ /// </summary>
+ /// <param name="count">Count</param>
+ /// <returns>Power of two count</returns>
+ private static int GetPow2Count(int count)
+ {
+ // This returns the nearest power of two that is lower than count.
+ // This was done to optimize memory usage rather than performance.
+ return 1 << BitOperations.Log2((uint)count);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionedHashTable.cs b/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionedHashTable.cs
new file mode 100644
index 00000000..4c9cc4d4
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionedHashTable.cs
@@ -0,0 +1,244 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Ryujinx.Graphics.Gpu.Shader.HashTable
+{
+ /// <summary>
+ /// Partitioned hash table.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ public class PartitionedHashTable<T>
+ {
+ /// <summary>
+ /// Entry for a given data size.
+ /// </summary>
+ private struct SizeEntry
+ {
+ /// <summary>
+ /// Size for the data that will be stored on the hash table on this entry.
+ /// </summary>
+ public int Size { get; }
+
+ /// <summary>
+ /// Number of entries on the hash table.
+ /// </summary>
+ public int TableCount => _table.Count;
+
+ private readonly PartitionHashTable<T> _table;
+
+ /// <summary>
+ /// Creates an entry for a given size.
+ /// </summary>
+ /// <param name="size">Size of the data to be stored on this entry</param>
+ public SizeEntry(int size)
+ {
+ Size = size;
+ _table = new PartitionHashTable<T>();
+ }
+
+ /// <summary>
+ /// Gets an item for existing data, or adds a new one.
+ /// </summary>
+ /// <param name="data">Data associated with the item</param>
+ /// <param name="dataHash">Hash of <paramref name="data"/></param>
+ /// <param name="item">Item to be added</param>
+ /// <returns>Existing item, or <paramref name="item"/> if not present</returns>
+ public T GetOrAdd(byte[] data, uint dataHash, T item)
+ {
+ Debug.Assert(data.Length == Size);
+ return _table.GetOrAdd(data, dataHash, item);
+ }
+
+ /// <summary>
+ /// Adds a new item.
+ /// </summary>
+ /// <param name="data">Data associated with the item</param>
+ /// <param name="dataHash">Hash of <paramref name="data"/></param>
+ /// <param name="item">Item to be added</param>
+ /// <returns>True if added, false otherwise</returns>
+ public bool Add(byte[] data, uint dataHash, T item)
+ {
+ Debug.Assert(data.Length == Size);
+ return _table.Add(data, dataHash, item);
+ }
+
+ /// <summary>
+ /// Adds a partial entry.
+ /// </summary>
+ /// <param name="ownerData">Full entry data</param>
+ /// <param name="dataHash">Hash of the sub-region of the data that belongs to this entry</param>
+ /// <returns>True if added, false otherwise</returns>
+ public bool AddPartial(byte[] ownerData, uint dataHash)
+ {
+ return _table.AddPartial(ownerData, dataHash, Size);
+ }
+
+ /// <summary>
+ /// Fills a new hash table with "partials" of existing full entries of higher size.
+ /// </summary>
+ /// <param name="newEntry">Entry with the new hash table</param>
+ public void FillPartials(SizeEntry newEntry)
+ {
+ Debug.Assert(newEntry.Size < Size);
+ _table.FillPartials(newEntry._table, newEntry.Size);
+ }
+
+ /// <summary>
+ /// Tries to find an item on the hash table.
+ /// </summary>
+ /// <param name="dataAccessor">Data accessor</param>
+ /// <param name="item">The item on the table, if found, otherwise unmodified</param>
+ /// <param name="data">The data on the table, if found, otherwise unmodified</param>
+ /// <returns>Table lookup result</returns>
+ public PartitionHashTable<T>.SearchResult TryFindItem(ref SmartDataAccessor dataAccessor, ref T item, ref byte[] data)
+ {
+ return _table.TryFindItem(ref dataAccessor, Size, ref item, ref data);
+ }
+ }
+
+ private readonly List<SizeEntry> _sizeTable;
+
+ /// <summary>
+ /// Creates a new partitioned hash table.
+ /// </summary>
+ public PartitionedHashTable()
+ {
+ _sizeTable = new List<SizeEntry>();
+ }
+
+ /// <summary>
+ /// Adds a new item to the table.
+ /// </summary>
+ /// <param name="data">Data</param>
+ /// <param name="item">Item associated with the data</param>
+ public void Add(byte[] data, T item)
+ {
+ GetOrAdd(data, item);
+ }
+
+ /// <summary>
+ /// Gets an existing item from the table, or adds a new one if not present.
+ /// </summary>
+ /// <param name="data">Data</param>
+ /// <param name="item">Item associated with the data</param>
+ /// <returns>Existing item, or <paramref name="item"/> if not present</returns>
+ public T GetOrAdd(byte[] data, T item)
+ {
+ SizeEntry sizeEntry;
+
+ int index = BinarySearch(_sizeTable, data.Length);
+ if (index < _sizeTable.Count && _sizeTable[index].Size == data.Length)
+ {
+ sizeEntry = _sizeTable[index];
+ }
+ else
+ {
+ if (index < _sizeTable.Count && _sizeTable[index].Size < data.Length)
+ {
+ index++;
+ }
+
+ sizeEntry = new SizeEntry(data.Length);
+
+ _sizeTable.Insert(index, sizeEntry);
+
+ for (int i = index + 1; i < _sizeTable.Count; i++)
+ {
+ _sizeTable[i].FillPartials(sizeEntry);
+ }
+ }
+
+ HashState hashState = new HashState();
+ hashState.Initialize();
+
+ for (int i = 0; i < index; i++)
+ {
+ ReadOnlySpan<byte> dataSlice = new ReadOnlySpan<byte>(data).Slice(0, _sizeTable[i].Size);
+ hashState.Continue(dataSlice);
+ _sizeTable[i].AddPartial(data, hashState.Finalize(dataSlice));
+ }
+
+ hashState.Continue(data);
+ return sizeEntry.GetOrAdd(data, hashState.Finalize(data), item);
+ }
+
+ /// <summary>
+ /// Performs binary search on a list of hash tables, each one with a fixed data size.
+ /// </summary>
+ /// <param name="entries">List of hash tables</param>
+ /// <param name="size">Size to search for</param>
+ /// <returns>Index of the hash table with the given size, or nearest one otherwise</returns>
+ private static int BinarySearch(List<SizeEntry> entries, int size)
+ {
+ int left = 0;
+ int middle = 0;
+ int right = entries.Count - 1;
+
+ while (left <= right)
+ {
+ middle = left + ((right - left) >> 1);
+
+ SizeEntry entry = entries[middle];
+
+ if (size == entry.Size)
+ {
+ break;
+ }
+
+ if (size < entry.Size)
+ {
+ right = middle - 1;
+ }
+ else
+ {
+ left = middle + 1;
+ }
+ }
+
+ return middle;
+ }
+
+ /// <summary>
+ /// Tries to find an item on the table.
+ /// </summary>
+ /// <param name="dataAccessor">Data accessor</param>
+ /// <param name="item">Item, if found</param>
+ /// <param name="data">Data, if found</param>
+ /// <returns>True if the item was found on the table, false otherwise</returns>
+ public bool TryFindItem(IDataAccessor dataAccessor, out T item, out byte[] data)
+ {
+ SmartDataAccessor sda = new SmartDataAccessor(dataAccessor);
+
+ item = default;
+ data = null;
+
+ int left = 0;
+ int right = _sizeTable.Count;
+
+ while (left != right)
+ {
+ int index = left + ((right - left) >> 1);
+
+ PartitionHashTable<T>.SearchResult result = _sizeTable[index].TryFindItem(ref sda, ref item, ref data);
+
+ if (result == PartitionHashTable<T>.SearchResult.FoundFull)
+ {
+ return true;
+ }
+
+ if (result == PartitionHashTable<T>.SearchResult.NotFound)
+ {
+ right = index;
+ }
+ else /* if (result == PartitionHashTable<T>.SearchResult.FoundPartial) */
+ {
+ left = index + 1;
+ }
+ }
+
+ data = null;
+ return false;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/HashTable/SmartDataAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/HashTable/SmartDataAccessor.cs
new file mode 100644
index 00000000..0632add6
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/HashTable/SmartDataAccessor.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Shader.HashTable
+{
+ /// <summary>
+ /// Smart data accessor that can cache data and hashes to avoid reading and re-hashing the same memory regions.
+ /// </summary>
+ ref struct SmartDataAccessor
+ {
+ private readonly IDataAccessor _dataAccessor;
+ private ReadOnlySpan<byte> _data;
+ private readonly SortedList<int, HashState> _cachedHashes;
+
+ /// <summary>
+ /// Creates a new smart data accessor.
+ /// </summary>
+ /// <param name="dataAccessor">Data accessor</param>
+ public SmartDataAccessor(IDataAccessor dataAccessor)
+ {
+ _dataAccessor = dataAccessor;
+ _data = ReadOnlySpan<byte>.Empty;
+ _cachedHashes = new SortedList<int, HashState>();
+ }
+
+ /// <summary>
+ /// Get a spans of a given size.
+ /// </summary>
+ /// <remarks>
+ /// The actual length of the span returned depends on the <see cref="IDataAccessor"/>
+ /// and might be less than requested.
+ /// </remarks>
+ /// <param name="length">Size in bytes</param>
+ /// <returns>Span with the requested size</returns>
+ public ReadOnlySpan<byte> GetSpan(int length)
+ {
+ if (_data.Length < length)
+ {
+ _data = _dataAccessor.GetSpan(0, length);
+ }
+ else if (_data.Length > length)
+ {
+ return _data.Slice(0, length);
+ }
+
+ return _data;
+ }
+
+ /// <summary>
+ /// Gets a span of the requested size, and a hash of its data.
+ /// </summary>
+ /// <param name="length">Length of the span</param>
+ /// <param name="hash">Hash of the span data</param>
+ /// <returns>Span of data</returns>
+ public ReadOnlySpan<byte> GetSpanAndHash(int length, out uint hash)
+ {
+ ReadOnlySpan<byte> data = GetSpan(length);
+ hash = data.Length == length ? CalcHashCached(data) : 0;
+ return data;
+ }
+
+ /// <summary>
+ /// Calculates the hash for a requested span.
+ /// This will try to use a cached hash if the data was already accessed before, to avoid re-hashing.
+ /// </summary>
+ /// <param name="data">Data to be hashed</param>
+ /// <returns>Hash of the data</returns>
+ private uint CalcHashCached(ReadOnlySpan<byte> data)
+ {
+ HashState state = default;
+ bool found = false;
+
+ for (int i = _cachedHashes.Count - 1; i >= 0; i--)
+ {
+ int cachedHashSize = _cachedHashes.Keys[i];
+
+ if (cachedHashSize < data.Length)
+ {
+ state = _cachedHashes.Values[i];
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ state = new HashState();
+ state.Initialize();
+ }
+
+ state.Continue(data);
+ _cachedHashes[data.Length & ~7] = state;
+ return state.Finalize(data);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs b/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs
new file mode 100644
index 00000000..b85423cb
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs
@@ -0,0 +1,36 @@
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ /// <summary>
+ /// Holds counts for the resources used by a shader.
+ /// </summary>
+ class ResourceCounts
+ {
+ /// <summary>
+ /// Total of uniform buffers used by the shaders.
+ /// </summary>
+ public int UniformBuffersCount;
+
+ /// <summary>
+ /// Total of storage buffers used by the shaders.
+ /// </summary>
+ public int StorageBuffersCount;
+
+ /// <summary>
+ /// Total of textures used by the shaders.
+ /// </summary>
+ public int TexturesCount;
+
+ /// <summary>
+ /// Total of images used by the shaders.
+ /// </summary>
+ public int ImagesCount;
+
+ /// <summary>
+ /// Creates a new instance of the shader resource counts class.
+ /// </summary>
+ public ResourceCounts()
+ {
+ UniformBuffersCount = 1; // The first binding is reserved for the support buffer.
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs
index 39bf10fa..651dfd26 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs
@@ -1,4 +1,6 @@
using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Shader
{
@@ -9,7 +11,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
#pragma warning disable CS0649
public ulong VertexA;
- public ulong Vertex;
+ public ulong VertexB;
public ulong TessControl;
public ulong TessEvaluation;
public ulong Geometry;
@@ -34,7 +36,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool Equals(ShaderAddresses other)
{
return VertexA == other.VertexA &&
- Vertex == other.Vertex &&
+ VertexB == other.VertexB &&
TessControl == other.TessControl &&
TessEvaluation == other.TessEvaluation &&
Geometry == other.Geometry &&
@@ -47,7 +49,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <returns>Hash code</returns>
public override int GetHashCode()
{
- return HashCode.Combine(VertexA, Vertex, TessControl, TessEvaluation, Geometry, Fragment);
+ return HashCode.Combine(VertexA, VertexB, TessControl, TessEvaluation, Geometry, Fragment);
+ }
+
+ /// <summary>
+ /// Gets a view of the structure as a span of addresses.
+ /// </summary>
+ /// <returns>Span of addresses</returns>
+ public Span<ulong> AsSpan()
+ {
+ return MemoryMarshal.CreateSpan(ref VertexA, Unsafe.SizeOf<ShaderAddresses>() / sizeof(ulong));
}
}
} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index f3870900..03d5ecad 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -1,18 +1,14 @@
-using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader.Cache;
-using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
+using Ryujinx.Graphics.Gpu.Shader.DiskCache;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
using System.Threading;
-using System.Threading.Tasks;
namespace Ryujinx.Graphics.Gpu.Shader
{
@@ -21,30 +17,66 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
class ShaderCache : IDisposable
{
- private const TranslationFlags DefaultFlags = TranslationFlags.DebugMode;
+ /// <summary>
+ /// Default flags used on the shader translation process.
+ /// </summary>
+ public const TranslationFlags DefaultFlags = TranslationFlags.DebugMode;
+
+ private struct TranslatedShader
+ {
+ public readonly CachedShaderStage Shader;
+ public readonly ShaderProgram Program;
+
+ public TranslatedShader(CachedShaderStage shader, ShaderProgram program)
+ {
+ Shader = shader;
+ Program = program;
+ }
+ }
+
+ private struct TranslatedShaderVertexPair
+ {
+ public readonly CachedShaderStage VertexA;
+ public readonly CachedShaderStage VertexB;
+ public readonly ShaderProgram Program;
+
+ public TranslatedShaderVertexPair(CachedShaderStage vertexA, CachedShaderStage vertexB, ShaderProgram program)
+ {
+ VertexA = vertexA;
+ VertexB = vertexB;
+ Program = program;
+ }
+ }
private readonly GpuContext _context;
private readonly ShaderDumper _dumper;
- private readonly Dictionary<ulong, List<ShaderBundle>> _cpPrograms;
- private readonly Dictionary<ShaderAddresses, List<ShaderBundle>> _gpPrograms;
+ private readonly Dictionary<ulong, CachedShaderProgram> _cpPrograms;
+ private readonly Dictionary<ShaderAddresses, CachedShaderProgram> _gpPrograms;
- private CacheManager _cacheManager;
+ private struct ProgramToSave
+ {
+ public readonly CachedShaderProgram CachedProgram;
+ public readonly IProgram HostProgram;
+
+ public ProgramToSave(CachedShaderProgram cachedProgram, IProgram hostProgram)
+ {
+ CachedProgram = cachedProgram;
+ HostProgram = hostProgram;
+ }
+ }
- private Dictionary<Hash128, ShaderBundle> _gpProgramsDiskCache;
- private Dictionary<Hash128, ShaderBundle> _cpProgramsDiskCache;
+ private Queue<ProgramToSave> _programsToSaveQueue;
- private Queue<(IProgram, Action<byte[]>)> _programsToSaveQueue;
+ private readonly ComputeShaderCacheHashTable _computeShaderCache;
+ private readonly ShaderCacheHashTable _graphicsShaderCache;
+ private readonly DiskCacheHostStorage _diskCacheHostStorage;
+ private readonly BackgroundDiskCacheWriter _cacheWriter;
/// <summary>
- /// Version of the codegen (to be changed when codegen or guest format change).
+ /// Event for signalling shader cache loading progress.
/// </summary>
- private const ulong ShaderCodeGenVersion = 3251;
-
- // Progress reporting helpers
- private volatile int _shaderCount;
- private volatile int _totalShaderCount;
public event Action<ShaderCacheState, int, int> ShaderCacheStateChanged;
/// <summary>
@@ -57,12 +89,23 @@ namespace Ryujinx.Graphics.Gpu.Shader
_dumper = new ShaderDumper();
- _cpPrograms = new Dictionary<ulong, List<ShaderBundle>>();
- _gpPrograms = new Dictionary<ShaderAddresses, List<ShaderBundle>>();
- _gpProgramsDiskCache = new Dictionary<Hash128, ShaderBundle>();
- _cpProgramsDiskCache = new Dictionary<Hash128, ShaderBundle>();
+ _cpPrograms = new Dictionary<ulong, CachedShaderProgram>();
+ _gpPrograms = new Dictionary<ShaderAddresses, CachedShaderProgram>();
+
+ _programsToSaveQueue = new Queue<ProgramToSave>();
+
+ string diskCacheTitleId = GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null
+ ? CacheHelper.GetBaseCacheDirectory(GraphicsConfig.TitleId)
+ : null;
+
+ _computeShaderCache = new ComputeShaderCacheHashTable();
+ _graphicsShaderCache = new ShaderCacheHashTable();
+ _diskCacheHostStorage = new DiskCacheHostStorage(diskCacheTitleId);
- _programsToSaveQueue = new Queue<(IProgram, Action<byte[]>)>();
+ if (_diskCacheHostStorage.CacheEnabled)
+ {
+ _cacheWriter = new BackgroundDiskCacheWriter(context, _diskCacheHostStorage);
+ }
}
/// <summary>
@@ -72,13 +115,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
// Check to see if the binaries for previously compiled shaders are ready, and save them out.
- while (_programsToSaveQueue.Count > 0)
+ while (_programsToSaveQueue.TryPeek(out ProgramToSave programToSave))
{
- (IProgram program, Action<byte[]> dataAction) = _programsToSaveQueue.Peek();
+ ProgramLinkStatus result = programToSave.HostProgram.CheckProgramLink(false);
- if (program.CheckProgramLink(false) != ProgramLinkStatus.Incomplete)
+ if (result != ProgramLinkStatus.Incomplete)
{
- dataAction(program.GetBinary());
+ if (result == ProgramLinkStatus.Success)
+ {
+ _cacheWriter.AddShader(programToSave.CachedProgram, programToSave.HostProgram.GetBinary());
+ }
+
_programsToSaveQueue.Dequeue();
}
else
@@ -91,463 +138,48 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary>
/// Initialize the cache.
/// </summary>
- internal void Initialize()
+ /// <param name="cancellationToken">Cancellation token to cancel the shader cache initialization process</param>
+ internal void Initialize(CancellationToken cancellationToken)
{
- if (GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null)
+ if (_diskCacheHostStorage.CacheEnabled)
{
- _cacheManager = new CacheManager(CacheGraphicsApi.OpenGL, CacheHashType.XxHash128, "glsl", GraphicsConfig.TitleId, ShaderCodeGenVersion);
-
- bool isReadOnly = _cacheManager.IsReadOnly;
-
- HashSet<Hash128> invalidEntries = null;
-
- if (isReadOnly)
+ if (!_diskCacheHostStorage.CacheExists())
{
- Logger.Warning?.Print(LogClass.Gpu, "Loading shader cache in read-only mode (cache in use by another program!)");
- }
- else
- {
- invalidEntries = new HashSet<Hash128>();
- }
-
- ReadOnlySpan<Hash128> guestProgramList = _cacheManager.GetGuestProgramList();
+ // If we don't have a shader cache on the new format, try to perform migration from the old shader cache.
+ Logger.Info?.Print(LogClass.Gpu, "No shader cache found, trying to migrate from legacy shader cache...");
- using AutoResetEvent progressReportEvent = new AutoResetEvent(false);
+ int migrationCount = Migration.MigrateFromLegacyCache(_context, _diskCacheHostStorage);
- _shaderCount = 0;
- _totalShaderCount = guestProgramList.Length;
-
- ShaderCacheStateChanged?.Invoke(ShaderCacheState.Start, _shaderCount, _totalShaderCount);
- Thread progressReportThread = null;
-
- if (guestProgramList.Length > 0)
- {
- progressReportThread = new Thread(ReportProgress)
- {
- Name = "ShaderCache.ProgressReporter",
- Priority = ThreadPriority.Lowest,
- IsBackground = true
- };
-
- progressReportThread.Start(progressReportEvent);
+ Logger.Info?.Print(LogClass.Gpu, $"Migrated {migrationCount} shaders.");
}
- // Make sure these are initialized before doing compilation.
- Capabilities caps = _context.Capabilities;
-
- int maxTaskCount = Math.Min(Environment.ProcessorCount, 8);
- int programIndex = 0;
- List<ShaderCompileTask> activeTasks = new List<ShaderCompileTask>();
-
- using AutoResetEvent taskDoneEvent = new AutoResetEvent(false);
-
- // This thread dispatches tasks to do shader translation, and creates programs that OpenGL will link in the background.
- // The program link status is checked in a non-blocking manner so that multiple shaders can be compiled at once.
-
- while (programIndex < guestProgramList.Length || activeTasks.Count > 0)
- {
- if (activeTasks.Count < maxTaskCount && programIndex < guestProgramList.Length)
- {
- // Begin a new shader compilation.
- Hash128 key = guestProgramList[programIndex];
-
- byte[] hostProgramBinary = _cacheManager.GetHostProgramByHash(ref key);
- bool hasHostCache = hostProgramBinary != null;
-
- IProgram hostProgram = null;
-
- // If the program sources aren't in the cache, compile from saved guest program.
- byte[] guestProgram = _cacheManager.GetGuestProgramByHash(ref key);
-
- if (guestProgram == null)
- {
- Logger.Error?.Print(LogClass.Gpu, $"Ignoring orphan shader hash {key} in cache (is the cache incomplete?)");
-
- // Should not happen, but if someone messed with the cache it's better to catch it.
- invalidEntries?.Add(key);
-
- _shaderCount = ++programIndex;
-
- continue;
- }
-
- ReadOnlySpan<byte> guestProgramReadOnlySpan = guestProgram;
-
- ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader);
-
- if (cachedShaderEntries[0].Header.Stage == ShaderStage.Compute)
- {
- Debug.Assert(cachedShaderEntries.Length == 1);
-
- GuestShaderCacheEntry entry = cachedShaderEntries[0];
-
- HostShaderCacheEntry[] hostShaderEntries = null;
-
- // Try loading host shader binary.
- if (hasHostCache)
- {
- hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan<byte> hostProgramBinarySpan);
- hostProgramBinary = hostProgramBinarySpan.ToArray();
- hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary, false, new ShaderInfo(-1));
- }
-
- ShaderCompileTask task = new ShaderCompileTask(taskDoneEvent);
- activeTasks.Add(task);
-
- task.OnCompiled(hostProgram, (bool isHostProgramValid, ShaderCompileTask task) =>
- {
- ShaderProgram program = null;
- ShaderProgramInfo shaderProgramInfo = null;
-
- if (isHostProgramValid)
- {
- // Reconstruct code holder.
-
- program = new ShaderProgram(entry.Header.Stage, "");
- shaderProgramInfo = hostShaderEntries[0].ToShaderProgramInfo();
-
- byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
-
- ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, code);
-
- _cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader));
-
- return true;
- }
- else
- {
- // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again.
-
- Task compileTask = Task.Run(() =>
- {
- var binaryCode = new Memory<byte>(entry.Code);
-
- var gpuAccessor = new CachedGpuAccessor(
- _context,
- binaryCode,
- binaryCode.Slice(binaryCode.Length - entry.Header.Cb1DataSize),
- entry.Header.GpuAccessorHeader,
- entry.TextureDescriptors,
- null);
-
- var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.Compute);
- program = Translator.CreateContext(0, gpuAccessor, options).Translate(out shaderProgramInfo);
- });
-
- task.OnTask(compileTask, (bool _, ShaderCompileTask task) =>
- {
- if (task.IsFaulted)
- {
- Logger.Warning?.Print(LogClass.Gpu, $"Host shader {key} is corrupted or incompatible, discarding...");
+ ParallelDiskCacheLoader loader = new ParallelDiskCacheLoader(
+ _context,
+ _graphicsShaderCache,
+ _computeShaderCache,
+ _diskCacheHostStorage,
+ cancellationToken,
+ ShaderCacheStateUpdate);
- _cacheManager.RemoveProgram(ref key);
- return true; // Exit early, the decoding step failed.
- }
+ loader.LoadShaders();
- byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
-
- ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, code);
-
- Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest...");
-
- // Compile shader and create program as the shader program binary got invalidated.
- shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, program.Code);
- hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, new ShaderInfo(-1));
-
- task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) =>
- {
- // As the host program was invalidated, save the new entry in the cache.
- hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), new ShaderCodeHolder[] { shader });
-
- if (!isReadOnly)
- {
- if (hasHostCache)
- {
- _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary);
- }
- else
- {
- Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)");
-
- _cacheManager.AddHostProgram(ref key, hostProgramBinary);
- }
- }
-
- _cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader));
-
- return true;
- });
-
- return false; // Not finished: still need to compile the host program.
- });
-
- return false; // Not finished: translating the program.
- }
- });
- }
- else
- {
- Debug.Assert(cachedShaderEntries.Length == Constants.ShaderStages);
-
- ShaderCodeHolder[] shaders = new ShaderCodeHolder[cachedShaderEntries.Length];
- List<ShaderProgram> shaderPrograms = new List<ShaderProgram>();
-
- TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader);
-
- TranslationCounts counts = new TranslationCounts();
-
- HostShaderCacheEntry[] hostShaderEntries = null;
-
- // Try loading host shader binary.
- if (hasHostCache)
- {
- hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan<byte> hostProgramBinarySpan);
- hostProgramBinary = hostProgramBinarySpan.ToArray();
-
- bool hasFragmentShader = false;
- int fragmentOutputMap = -1;
- int fragmentIndex = (int)ShaderStage.Fragment - 1;
-
- if (hostShaderEntries[fragmentIndex] != null && hostShaderEntries[fragmentIndex].Header.InUse)
- {
- hasFragmentShader = true;
- fragmentOutputMap = hostShaderEntries[fragmentIndex].Header.FragmentOutputMap;
- }
-
- hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary, hasFragmentShader, new ShaderInfo(fragmentOutputMap));
- }
-
- ShaderCompileTask task = new ShaderCompileTask(taskDoneEvent);
- activeTasks.Add(task);
-
- GuestShaderCacheEntry[] entries = cachedShaderEntries.ToArray();
-
- task.OnCompiled(hostProgram, (bool isHostProgramValid, ShaderCompileTask task) =>
- {
- Task compileTask = Task.Run(() =>
- {
- TranslatorContext[] shaderContexts = null;
-
- if (!isHostProgramValid)
- {
- shaderContexts = new TranslatorContext[1 + entries.Length];
-
- for (int i = 0; i < entries.Length; i++)
- {
- GuestShaderCacheEntry entry = entries[i];
-
- if (entry == null)
- {
- continue;
- }
-
- var binaryCode = new Memory<byte>(entry.Code);
-
- var gpuAccessor = new CachedGpuAccessor(
- _context,
- binaryCode,
- binaryCode.Slice(binaryCode.Length - entry.Header.Cb1DataSize),
- entry.Header.GpuAccessorHeader,
- entry.TextureDescriptors,
- tfd);
-
- var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags);
-
- shaderContexts[i + 1] = Translator.CreateContext(0, gpuAccessor, options, counts);
-
- if (entry.Header.SizeA != 0)
- {
- var options2 = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.VertexA);
-
- shaderContexts[0] = Translator.CreateContext((ulong)entry.Header.Size, gpuAccessor, options2, counts);
- }
- }
- }
-
- // Reconstruct code holder.
- for (int i = 0; i < entries.Length; i++)
- {
- GuestShaderCacheEntry entry = entries[i];
-
- if (entry == null)
- {
- continue;
- }
-
- ShaderProgram program;
- ShaderProgramInfo shaderProgramInfo;
-
- if (isHostProgramValid)
- {
- program = new ShaderProgram(entry.Header.Stage, "");
- shaderProgramInfo = hostShaderEntries[i].ToShaderProgramInfo();
- }
- else
- {
- int stageIndex = i + 1;
-
- TranslatorContext currentStage = shaderContexts[stageIndex];
- TranslatorContext nextStage = GetNextStageContext(shaderContexts, stageIndex);
- TranslatorContext vertexA = stageIndex == 1 ? shaderContexts[0] : null;
-
- program = currentStage.Translate(out shaderProgramInfo, nextStage, vertexA);
- }
-
- // NOTE: Vertex B comes first in the shader cache.
- byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
- byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan(entry.Header.Size, entry.Header.SizeA).ToArray() : null;
-
- shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, code, code2);
-
- shaderPrograms.Add(program);
- }
- });
-
- task.OnTask(compileTask, (bool _, ShaderCompileTask task) =>
- {
- if (task.IsFaulted)
- {
- Logger.Warning?.Print(LogClass.Gpu, $"Host shader {key} is corrupted or incompatible, discarding...");
-
- _cacheManager.RemoveProgram(ref key);
- return true; // Exit early, the decoding step failed.
- }
-
- // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again.
- if (!isHostProgramValid)
- {
- Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest...");
-
- List<IShader> hostShaders = new List<IShader>();
-
- // Compile shaders and create program as the shader program binary got invalidated.
- for (int stage = 0; stage < Constants.ShaderStages; stage++)
- {
- ShaderProgram program = shaders[stage]?.Program;
-
- if (program == null)
- {
- continue;
- }
-
- IShader hostShader = _context.Renderer.CompileShader(program.Stage, program.Code);
-
- shaders[stage].HostShader = hostShader;
-
- hostShaders.Add(hostShader);
- }
-
- int fragmentIndex = (int)ShaderStage.Fragment - 1;
- int fragmentOutputMap = -1;
-
- if (shaders[fragmentIndex] != null)
- {
- fragmentOutputMap = shaders[fragmentIndex].Info.FragmentOutputMap;
- }
-
- hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), new ShaderInfo(fragmentOutputMap));
-
- task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) =>
- {
- // As the host program was invalidated, save the new entry in the cache.
- hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders);
-
- if (!isReadOnly)
- {
- if (hasHostCache)
- {
- _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary);
- }
- else
- {
- Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)");
-
- _cacheManager.AddHostProgram(ref key, hostProgramBinary);
- }
- }
-
- _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders));
-
- return true;
- });
-
- return false; // Not finished: still need to compile the host program.
- }
- else
- {
- _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders));
-
- return true;
- }
- });
-
- return false; // Not finished: translating the program.
- });
- }
-
- _shaderCount = ++programIndex;
- }
-
- // Process the queue.
- for (int i = 0; i < activeTasks.Count; i++)
- {
- ShaderCompileTask task = activeTasks[i];
-
- if (task.IsDone())
- {
- activeTasks.RemoveAt(i--);
- }
- }
-
- if (activeTasks.Count == maxTaskCount)
- {
- // Wait for a task to be done, or for 1ms.
- // Host shader compilation cannot signal when it is done,
- // so the 1ms timeout is required to poll status.
-
- taskDoneEvent.WaitOne(1);
- }
- }
-
- if (!isReadOnly)
+ int errorCount = loader.ErrorCount;
+ if (errorCount != 0)
{
- // Remove entries that are broken in the cache
- _cacheManager.RemoveManifestEntries(invalidEntries);
- _cacheManager.FlushToArchive();
- _cacheManager.Synchronize();
+ Logger.Warning?.Print(LogClass.Gpu, $"Failed to load {errorCount} shaders from the disk cache.");
}
-
- progressReportEvent.Set();
- progressReportThread?.Join();
-
- ShaderCacheStateChanged?.Invoke(ShaderCacheState.Loaded, _shaderCount, _totalShaderCount);
-
- Logger.Info?.Print(LogClass.Gpu, $"Shader cache loaded {_shaderCount} entries.");
}
}
/// <summary>
- /// Raises ShaderCacheStateChanged events periodically.
+ /// Shader cache state update handler.
/// </summary>
- private void ReportProgress(object state)
+ /// <param name="state">Current state of the shader cache load process</param>
+ /// <param name="current">Number of the current shader being processed</param>
+ /// <param name="total">Total number of shaders to process</param>
+ private void ShaderCacheStateUpdate(ShaderCacheState state, int current, int total)
{
- const int refreshRate = 50; // ms
-
- AutoResetEvent endEvent = (AutoResetEvent)state;
-
- int count = 0;
-
- do
- {
- int newCount = _shaderCount;
-
- if (count != newCount)
- {
- ShaderCacheStateChanged?.Invoke(ShaderCacheState.Loading, newCount, _totalShaderCount);
- count = newCount;
- }
- }
- while (!endEvent.WaitOne(refreshRate));
+ ShaderCacheStateChanged?.Invoke(state, current, total);
}
/// <summary>
@@ -557,112 +189,42 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// This automatically translates, compiles and adds the code to the cache if not present.
/// </remarks>
/// <param name="channel">GPU channel</param>
- /// <param name="gas">GPU accessor state</param>
+ /// <param name="poolState">Texture pool state</param>
+ /// <param name="computeState">Compute engine state</param>
/// <param name="gpuVa">GPU virtual address of the binary shader code</param>
- /// <param name="localSizeX">Local group size X of the computer shader</param>
- /// <param name="localSizeY">Local group size Y of the computer shader</param>
- /// <param name="localSizeZ">Local group size Z of the computer shader</param>
- /// <param name="localMemorySize">Local memory size of the compute shader</param>
- /// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
/// <returns>Compiled compute shader code</returns>
- public ShaderBundle GetComputeShader(
+ public CachedShaderProgram GetComputeShader(
GpuChannel channel,
- GpuAccessorState gas,
- ulong gpuVa,
- int localSizeX,
- int localSizeY,
- int localSizeZ,
- int localMemorySize,
- int sharedMemorySize)
+ GpuChannelPoolState poolState,
+ GpuChannelComputeState computeState,
+ ulong gpuVa)
{
- bool isCached = _cpPrograms.TryGetValue(gpuVa, out List<ShaderBundle> list);
-
- if (isCached)
- {
- foreach (ShaderBundle cachedCpShader in list)
- {
- if (IsShaderEqual(channel.MemoryManager, cachedCpShader, gpuVa))
- {
- return cachedCpShader;
- }
- }
- }
-
- TranslatorContext[] shaderContexts = new TranslatorContext[1];
-
- shaderContexts[0] = DecodeComputeShader(
- channel,
- gas,
- gpuVa,
- localSizeX,
- localSizeY,
- localSizeZ,
- localMemorySize,
- sharedMemorySize);
-
- bool isShaderCacheEnabled = _cacheManager != null;
- bool isShaderCacheReadOnly = false;
-
- Hash128 programCodeHash = default;
- GuestShaderCacheEntry[] shaderCacheEntries = null;
-
- // Current shader cache doesn't support bindless textures
- if (shaderContexts[0].UsedFeatures.HasFlag(FeatureFlags.Bindless))
+ if (_cpPrograms.TryGetValue(gpuVa, out var cpShader) && IsShaderEqual(channel, poolState, cpShader, gpuVa))
{
- isShaderCacheEnabled = false;
+ return cpShader;
}
- if (isShaderCacheEnabled)
+ if (_computeShaderCache.TryFind(channel, poolState, gpuVa, out cpShader, out byte[] cachedGuestCode))
{
- isShaderCacheReadOnly = _cacheManager.IsReadOnly;
-
- // Compute hash and prepare data for shader disk cache comparison.
- shaderCacheEntries = CacheHelper.CreateShaderCacheEntries(channel, shaderContexts);
- programCodeHash = CacheHelper.ComputeGuestHashFromCache(shaderCacheEntries);
+ _cpPrograms[gpuVa] = cpShader;
+ return cpShader;
}
- ShaderBundle cpShader;
-
- // Search for the program hash in loaded shaders.
- if (!isShaderCacheEnabled || !_cpProgramsDiskCache.TryGetValue(programCodeHash, out cpShader))
- {
- if (isShaderCacheEnabled)
- {
- Logger.Debug?.Print(LogClass.Gpu, $"Shader {programCodeHash} not in cache, compiling!");
- }
-
- // The shader isn't currently cached, translate it and compile it.
- ShaderCodeHolder shader = TranslateShader(_dumper, channel.MemoryManager, shaderContexts[0], null, null);
-
- shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code);
+ ShaderSpecializationState specState = new ShaderSpecializationState(computeState);
+ GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, computeState, default, specState);
+ GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState);
- IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, new ShaderInfo(-1));
+ TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, gpuVa);
- cpShader = new ShaderBundle(hostProgram, shader);
-
- if (isShaderCacheEnabled)
- {
- _cpProgramsDiskCache.Add(programCodeHash, cpShader);
-
- if (!isShaderCacheReadOnly)
- {
- byte[] guestProgramDump = CacheHelper.CreateGuestProgramDump(shaderCacheEntries);
- _programsToSaveQueue.Enqueue((hostProgram, (byte[] hostProgramBinary) =>
- {
- _cacheManager.SaveProgram(ref programCodeHash, guestProgramDump, HostShaderCacheEntry.Create(hostProgramBinary, new ShaderCodeHolder[] { shader }));
- }));
- }
- }
- }
+ TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode);
- if (!isCached)
- {
- list = new List<ShaderBundle>();
+ IProgram hostProgram = _context.Renderer.CreateProgram(new ShaderSource[] { CreateShaderSource(translatedShader.Program) }, new ShaderInfo(-1));
- _cpPrograms.Add(gpuVa, list);
- }
+ cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader);
- list.Add(cpShader);
+ _computeShaderCache.Add(cpShader);
+ EnqueueProgramToSave(new ProgramToSave(cpShader, hostProgram));
+ _cpPrograms[gpuVa] = cpShader;
return cpShader;
}
@@ -676,144 +238,142 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </remarks>
/// <param name="state">GPU state</param>
/// <param name="channel">GPU channel</param>
- /// <param name="gas">GPU accessor state</param>
+ /// <param name="poolState">Texture pool state</param>
+ /// <param name="graphicsState">3D engine state</param>
/// <param name="addresses">Addresses of the shaders for each stage</param>
/// <returns>Compiled graphics shader code</returns>
- public ShaderBundle GetGraphicsShader(ref ThreedClassState state, GpuChannel channel, GpuAccessorState gas, ShaderAddresses addresses)
+ public CachedShaderProgram GetGraphicsShader(
+ ref ThreedClassState state,
+ GpuChannel channel,
+ GpuChannelPoolState poolState,
+ GpuChannelGraphicsState graphicsState,
+ ShaderAddresses addresses)
{
- bool isCached = _gpPrograms.TryGetValue(addresses, out List<ShaderBundle> list);
-
- if (isCached)
+ if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, poolState, gpShaders, addresses))
{
- foreach (ShaderBundle cachedGpShaders in list)
- {
- if (IsShaderEqual(channel.MemoryManager, cachedGpShaders, addresses))
- {
- return cachedGpShaders;
- }
- }
+ return gpShaders;
}
- TranslatorContext[] shaderContexts = new TranslatorContext[Constants.ShaderStages + 1];
-
- TransformFeedbackDescriptor[] tfd = GetTransformFeedbackDescriptors(ref state);
-
- gas.TransformFeedbackDescriptors = tfd;
-
- TranslationCounts counts = new TranslationCounts();
-
- if (addresses.VertexA != 0)
+ if (_graphicsShaderCache.TryFind(channel, poolState, addresses, out gpShaders, out var cachedGuestCode))
{
- shaderContexts[0] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags | TranslationFlags.VertexA, ShaderStage.Vertex, addresses.VertexA);
+ _gpPrograms[addresses] = gpShaders;
+ return gpShaders;
}
- shaderContexts[1] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.Vertex, addresses.Vertex);
- shaderContexts[2] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.TessellationControl, addresses.TessControl);
- shaderContexts[3] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.TessellationEvaluation, addresses.TessEvaluation);
- shaderContexts[4] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.Geometry, addresses.Geometry);
- shaderContexts[5] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.Fragment, addresses.Fragment);
-
- bool isShaderCacheEnabled = _cacheManager != null;
- bool isShaderCacheReadOnly = false;
-
- Hash128 programCodeHash = default;
- GuestShaderCacheEntry[] shaderCacheEntries = null;
-
- // Current shader cache doesn't support bindless textures
- for (int i = 0; i < shaderContexts.Length; i++)
- {
- if (shaderContexts[i] != null && shaderContexts[i].UsedFeatures.HasFlag(FeatureFlags.Bindless))
- {
- isShaderCacheEnabled = false;
- break;
- }
- }
+ TransformFeedbackDescriptor[] transformFeedbackDescriptors = GetTransformFeedbackDescriptors(ref state);
- if (isShaderCacheEnabled)
- {
- isShaderCacheReadOnly = _cacheManager.IsReadOnly;
+ ShaderSpecializationState specState = new ShaderSpecializationState(graphicsState, transformFeedbackDescriptors);
+ GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, default, graphicsState, specState, transformFeedbackDescriptors);
- // Compute hash and prepare data for shader disk cache comparison.
- shaderCacheEntries = CacheHelper.CreateShaderCacheEntries(channel, shaderContexts);
- programCodeHash = CacheHelper.ComputeGuestHashFromCache(shaderCacheEntries, tfd);
- }
+ ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();
- ShaderBundle gpShaders;
+ TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
+ TranslatorContext nextStage = null;
- // Search for the program hash in loaded shaders.
- if (!isShaderCacheEnabled || !_gpProgramsDiskCache.TryGetValue(programCodeHash, out gpShaders))
+ for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
{
- if (isShaderCacheEnabled)
- {
- Logger.Debug?.Print(LogClass.Gpu, $"Shader {programCodeHash} not in cache, compiling!");
- }
-
- // The shader isn't currently cached, translate it and compile it.
- ShaderCodeHolder[] shaders = new ShaderCodeHolder[Constants.ShaderStages];
-
- for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
- {
- shaders[stageIndex] = TranslateShader(_dumper, channel.MemoryManager, shaderContexts, stageIndex + 1);
- }
-
- List<IShader> hostShaders = new List<IShader>();
+ ulong gpuVa = addressesSpan[stageIndex + 1];
- for (int stage = 0; stage < Constants.ShaderStages; stage++)
+ if (gpuVa != 0)
{
- ShaderProgram program = shaders[stage]?.Program;
+ GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState, stageIndex);
+ TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, DefaultFlags, gpuVa);
- if (program == null)
+ if (nextStage != null)
{
- continue;
+ currentStage.SetNextStage(nextStage);
}
- IShader hostShader = _context.Renderer.CompileShader(program.Stage, program.Code);
-
- shaders[stage].HostShader = hostShader;
+ if (stageIndex == 0 && addresses.VertexA != 0)
+ {
+ translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA);
+ }
- hostShaders.Add(hostShader);
+ translatorContexts[stageIndex + 1] = currentStage;
+ nextStage = currentStage;
}
+ }
- int fragmentIndex = (int)ShaderStage.Fragment - 1;
- int fragmentOutputMap = -1;
+ CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1];
+ List<ShaderSource> shaderSources = new List<ShaderSource>();
+
+ for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
+ {
+ TranslatorContext currentStage = translatorContexts[stageIndex + 1];
- if (shaders[fragmentIndex] != null)
+ if (currentStage != null)
{
- fragmentOutputMap = shaders[fragmentIndex].Info.FragmentOutputMap;
- }
+ ShaderProgram program;
- IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), new ShaderInfo(fragmentOutputMap));
+ if (stageIndex == 0 && translatorContexts[0] != null)
+ {
+ TranslatedShaderVertexPair translatedShader = TranslateShader(
+ _dumper,
+ channel,
+ currentStage,
+ translatorContexts[0],
+ cachedGuestCode.VertexACode,
+ cachedGuestCode.VertexBCode);
+
+ shaders[0] = translatedShader.VertexA;
+ shaders[1] = translatedShader.VertexB;
+ program = translatedShader.Program;
+ }
+ else
+ {
+ byte[] code = cachedGuestCode.GetByIndex(stageIndex);
- gpShaders = new ShaderBundle(hostProgram, shaders);
+ TranslatedShader translatedShader = TranslateShader(_dumper, channel, currentStage, code);
- if (isShaderCacheEnabled)
- {
- _gpProgramsDiskCache.Add(programCodeHash, gpShaders);
+ shaders[stageIndex + 1] = translatedShader.Shader;
+ program = translatedShader.Program;
+ }
- if (!isShaderCacheReadOnly)
+ if (program != null)
{
- byte[] guestProgramDump = CacheHelper.CreateGuestProgramDump(shaderCacheEntries, tfd);
- _programsToSaveQueue.Enqueue((hostProgram, (byte[] hostProgramBinary) =>
- {
- _cacheManager.SaveProgram(ref programCodeHash, guestProgramDump, HostShaderCacheEntry.Create(hostProgramBinary, shaders));
- }));
+ shaderSources.Add(CreateShaderSource(program));
}
}
}
- if (!isCached)
- {
- list = new List<ShaderBundle>();
+ int fragmentOutputMap = shaders[5]?.Info.FragmentOutputMap ?? -1;
+ IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources.ToArray(), new ShaderInfo(fragmentOutputMap));
- _gpPrograms.Add(addresses, list);
- }
+ gpShaders = new CachedShaderProgram(hostProgram, specState, shaders);
- list.Add(gpShaders);
+ _graphicsShaderCache.Add(gpShaders);
+ EnqueueProgramToSave(new ProgramToSave(gpShaders, hostProgram));
+ _gpPrograms[addresses] = gpShaders;
return gpShaders;
}
/// <summary>
+ /// Creates a shader source for use with the backend from a translated shader program.
+ /// </summary>
+ /// <param name="program">Translated shader program</param>
+ /// <returns>Shader source</returns>
+ public static ShaderSource CreateShaderSource(ShaderProgram program)
+ {
+ return new ShaderSource(program.Code, program.BinaryCode, program.Info.Stage, program.Language);
+ }
+
+ /// <summary>
+ /// Puts a program on the queue of programs to be saved on the disk cache.
+ /// </summary>
+ /// <remarks>
+ /// This will not do anything if disk shader cache is disabled.
+ /// </remarks>
+ /// <param name="programToSave">Program to be saved on disk</param>
+ private void EnqueueProgramToSave(ProgramToSave programToSave)
+ {
+ if (_diskCacheHostStorage.CacheEnabled)
+ {
+ _programsToSaveQueue.Enqueue(programToSave);
+ }
+ }
+
+ /// <summary>
/// Gets transform feedback state from the current GPU state.
/// </summary>
/// <param name="state">Current GPU state</param>
@@ -821,7 +381,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
private static TransformFeedbackDescriptor[] GetTransformFeedbackDescriptors(ref ThreedClassState state)
{
bool tfEnable = state.TfEnable;
-
if (!tfEnable)
{
return null;
@@ -833,11 +392,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
var tf = state.TfState[i];
- int length = (int)Math.Min((uint)tf.VaryingsCount, 0x80);
-
- var varyingLocations = MemoryMarshal.Cast<uint, byte>(state.TfVaryingLocations[i].ToSpan()).Slice(0, length);
-
- descs[i] = new TransformFeedbackDescriptor(tf.BufferIndex, tf.Stride, varyingLocations.ToArray());
+ descs[i] = new TransformFeedbackDescriptor(
+ tf.BufferIndex,
+ tf.Stride,
+ tf.VaryingsCount,
+ ref state.TfVaryingLocations[i]);
}
return descs;
@@ -846,46 +405,54 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary>
/// Checks if compute shader code in memory is equal to the cached shader.
/// </summary>
- /// <param name="memoryManager">Memory manager used to access the GPU memory where the shader is located</param>
+ /// <param name="channel">GPU channel using the shader</param>
+ /// <param name="poolState">GPU channel state to verify shader compatibility</param>
/// <param name="cpShader">Cached compute shader</param>
/// <param name="gpuVa">GPU virtual address of the shader code in memory</param>
/// <returns>True if the code is different, false otherwise</returns>
- private static bool IsShaderEqual(MemoryManager memoryManager, ShaderBundle cpShader, ulong gpuVa)
+ private static bool IsShaderEqual(
+ GpuChannel channel,
+ GpuChannelPoolState poolState,
+ CachedShaderProgram cpShader,
+ ulong gpuVa)
{
- return IsShaderEqual(memoryManager, cpShader.Shaders[0], gpuVa);
+ if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa))
+ {
+ return cpShader.SpecializationState.MatchesCompute(channel, poolState);
+ }
+
+ return false;
}
/// <summary>
/// Checks if graphics shader code from all stages in memory are equal to the cached shaders.
/// </summary>
- /// <param name="memoryManager">Memory manager used to access the GPU memory where the shader is located</param>
+ /// <param name="channel">GPU channel using the shader</param>
+ /// <param name="poolState">GPU channel state to verify shader compatibility</param>
/// <param name="gpShaders">Cached graphics shaders</param>
/// <param name="addresses">GPU virtual addresses of all enabled shader stages</param>
/// <returns>True if the code is different, false otherwise</returns>
- private static bool IsShaderEqual(MemoryManager memoryManager, ShaderBundle gpShaders, ShaderAddresses addresses)
+ private static bool IsShaderEqual(
+ GpuChannel channel,
+ GpuChannelPoolState poolState,
+ CachedShaderProgram gpShaders,
+ ShaderAddresses addresses)
{
- for (int stage = 0; stage < gpShaders.Shaders.Length; stage++)
- {
- ShaderCodeHolder shader = gpShaders.Shaders[stage];
+ ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();
- ulong gpuVa = 0;
+ for (int stageIndex = 0; stageIndex < gpShaders.Shaders.Length; stageIndex++)
+ {
+ CachedShaderStage shader = gpShaders.Shaders[stageIndex];
- switch (stage)
- {
- case 0: gpuVa = addresses.Vertex; break;
- case 1: gpuVa = addresses.TessControl; break;
- case 2: gpuVa = addresses.TessEvaluation; break;
- case 3: gpuVa = addresses.Geometry; break;
- case 4: gpuVa = addresses.Fragment; break;
- }
+ ulong gpuVa = addressesSpan[stageIndex];
- if (!IsShaderEqual(memoryManager, shader, gpuVa, addresses.VertexA))
+ if (!IsShaderEqual(channel.MemoryManager, shader, gpuVa))
{
return false;
}
}
- return true;
+ return gpShaders.SpecializationState.MatchesGraphics(channel, poolState);
}
/// <summary>
@@ -894,9 +461,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="memoryManager">Memory manager used to access the GPU memory where the shader is located</param>
/// <param name="shader">Cached shader to compare with</param>
/// <param name="gpuVa">GPU virtual address of the binary shader code</param>
- /// <param name="gpuVaA">Optional GPU virtual address of the "Vertex A" binary shader code</param>
/// <returns>True if the code is different, false otherwise</returns>
- private static bool IsShaderEqual(MemoryManager memoryManager, ShaderCodeHolder shader, ulong gpuVa, ulong gpuVaA = 0)
+ private static bool IsShaderEqual(MemoryManager memoryManager, CachedShaderStage shader, ulong gpuVa)
{
if (shader == null)
{
@@ -905,47 +471,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
ReadOnlySpan<byte> memoryCode = memoryManager.GetSpan(gpuVa, shader.Code.Length);
- bool equals = memoryCode.SequenceEqual(shader.Code);
-
- if (equals && shader.Code2 != null)
- {
- memoryCode = memoryManager.GetSpan(gpuVaA, shader.Code2.Length);
-
- equals = memoryCode.SequenceEqual(shader.Code2);
- }
-
- return equals;
+ return memoryCode.SequenceEqual(shader.Code);
}
/// <summary>
/// Decode the binary Maxwell shader code to a translator context.
/// </summary>
- /// <param name="channel">GPU channel</param>
- /// <param name="gas">GPU accessor state</param>
+ /// <param name="gpuAccessor">GPU state accessor</param>
/// <param name="gpuVa">GPU virtual address of the binary shader code</param>
- /// <param name="localSizeX">Local group size X of the computer shader</param>
- /// <param name="localSizeY">Local group size Y of the computer shader</param>
- /// <param name="localSizeZ">Local group size Z of the computer shader</param>
- /// <param name="localMemorySize">Local memory size of the compute shader</param>
- /// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
/// <returns>The generated translator context</returns>
- private TranslatorContext DecodeComputeShader(
- GpuChannel channel,
- GpuAccessorState gas,
- ulong gpuVa,
- int localSizeX,
- int localSizeY,
- int localSizeZ,
- int localMemorySize,
- int sharedMemorySize)
+ public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, ulong gpuVa)
{
- if (gpuVa == 0)
- {
- return null;
- }
-
- GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gas, localSizeX, localSizeY, localSizeZ, localMemorySize, sharedMemorySize);
-
var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.Compute);
return Translator.CreateContext(gpuVa, gpuAccessor, options);
}
@@ -956,126 +492,105 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <remarks>
/// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader.
/// </remarks>
- /// <param name="channel">GPU channel</param>
- /// <param name="gas">GPU accessor state</param>
- /// <param name="counts">Cumulative shader resource counts</param>
+ /// <param name="gpuAccessor">GPU state accessor</param>
/// <param name="flags">Flags that controls shader translation</param>
- /// <param name="stage">Shader stage</param>
/// <param name="gpuVa">GPU virtual address of the shader code</param>
/// <returns>The generated translator context</returns>
- private TranslatorContext DecodeGraphicsShader(
- GpuChannel channel,
- GpuAccessorState gas,
- TranslationCounts counts,
- TranslationFlags flags,
- ShaderStage stage,
- ulong gpuVa)
+ public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TranslationFlags flags, ulong gpuVa)
{
- if (gpuVa == 0)
- {
- return null;
- }
-
- GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gas, (int)stage - 1);
-
var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags);
- return Translator.CreateContext(gpuVa, gpuAccessor, options, counts);
+ return Translator.CreateContext(gpuVa, gpuAccessor, options);
}
/// <summary>
/// Translates a previously generated translator context to something that the host API accepts.
/// </summary>
/// <param name="dumper">Optional shader code dumper</param>
- /// <param name="memoryManager">Memory manager used to access the GPU memory where the shader is located</param>
- /// <param name="stages">Translator context of all available shader stages</param>
- /// <param name="stageIndex">Index on the stages array to translate</param>
+ /// <param name="channel">GPU channel using the shader</param>
+ /// <param name="currentStage">Translator context of the stage to be translated</param>
+ /// <param name="vertexA">Optional translator context of the shader that should be combined</param>
+ /// <param name="codeA">Optional Maxwell binary code of the Vertex A shader, if present</param>
+ /// <param name="codeB">Optional Maxwell binary code of the Vertex B or current stage shader, if present on cache</param>
/// <returns>Compiled graphics shader code</returns>
- private static ShaderCodeHolder TranslateShader(
+ private static TranslatedShaderVertexPair TranslateShader(
ShaderDumper dumper,
- MemoryManager memoryManager,
- TranslatorContext[] stages,
- int stageIndex)
+ GpuChannel channel,
+ TranslatorContext currentStage,
+ TranslatorContext vertexA,
+ byte[] codeA,
+ byte[] codeB)
{
- TranslatorContext currentStage = stages[stageIndex];
- TranslatorContext nextStage = GetNextStageContext(stages, stageIndex);
- TranslatorContext vertexA = stageIndex == 1 ? stages[0] : null;
+ ulong cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
- return TranslateShader(dumper, memoryManager, currentStage, nextStage, vertexA);
- }
+ var memoryManager = channel.MemoryManager;
- /// <summary>
- /// Gets the next shader stage context, from an array of contexts and index of the current stage.
- /// </summary>
- /// <param name="stages">Translator context of all available shader stages</param>
- /// <param name="stageIndex">Index on the stages array to translate</param>
- /// <returns>The translator context of the next stage, or null if inexistent</returns>
- private static TranslatorContext GetNextStageContext(TranslatorContext[] stages, int stageIndex)
- {
- for (int nextStageIndex = stageIndex + 1; nextStageIndex < stages.Length; nextStageIndex++)
+ codeA ??= memoryManager.GetSpan(vertexA.Address, vertexA.Size).ToArray();
+ codeB ??= memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray();
+ byte[] cb1DataA = memoryManager.Physical.GetSpan(cb1DataAddress, vertexA.Cb1DataSize).ToArray();
+ byte[] cb1DataB = memoryManager.Physical.GetSpan(cb1DataAddress, currentStage.Cb1DataSize).ToArray();
+
+ ShaderDumpPaths pathsA = default;
+ ShaderDumpPaths pathsB = default;
+
+ if (dumper != null)
{
- if (stages[nextStageIndex] != null)
- {
- return stages[nextStageIndex];
- }
+ pathsA = dumper.Dump(codeA, compute: false);
+ pathsB = dumper.Dump(codeB, compute: false);
}
- return null;
+ ShaderProgram program = currentStage.Translate(vertexA);
+
+ pathsB.Prepend(program);
+ pathsA.Prepend(program);
+
+ CachedShaderStage vertexAStage = new CachedShaderStage(null, codeA, cb1DataA);
+ CachedShaderStage vertexBStage = new CachedShaderStage(program.Info, codeB, cb1DataB);
+
+ return new TranslatedShaderVertexPair(vertexAStage, vertexBStage, program);
}
/// <summary>
/// Translates a previously generated translator context to something that the host API accepts.
/// </summary>
/// <param name="dumper">Optional shader code dumper</param>
- /// <param name="memoryManager">Memory manager used to access the GPU memory where the shader is located</param>
- /// <param name="currentStage">Translator context of the stage to be translated</param>
- /// <param name="nextStage">Translator context of the next active stage, if existent</param>
- /// <param name="vertexA">Optional translator context of the shader that should be combined</param>
+ /// <param name="channel">GPU channel using the shader</param>
+ /// <param name="context">Translator context of the stage to be translated</param>
+ /// <param name="code">Optional Maxwell binary code of the current stage shader, if present on cache</param>
/// <returns>Compiled graphics shader code</returns>
- private static ShaderCodeHolder TranslateShader(
- ShaderDumper dumper,
- MemoryManager memoryManager,
- TranslatorContext currentStage,
- TranslatorContext nextStage,
- TranslatorContext vertexA)
+ private static TranslatedShader TranslateShader(ShaderDumper dumper, GpuChannel channel, TranslatorContext context, byte[] code)
{
- if (currentStage == null)
- {
- return null;
- }
+ var memoryManager = channel.MemoryManager;
- if (vertexA != null)
- {
- byte[] codeA = memoryManager.GetSpan(vertexA.Address, vertexA.Size).ToArray();
- byte[] codeB = memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray();
+ ulong cb1DataAddress = context.Stage == ShaderStage.Compute
+ ? channel.BufferManager.GetComputeUniformBufferAddress(1)
+ : channel.BufferManager.GetGraphicsUniformBufferAddress(StageToStageIndex(context.Stage), 1);
- ShaderDumpPaths pathsA = default;
- ShaderDumpPaths pathsB = default;
+ byte[] cb1Data = memoryManager.Physical.GetSpan(cb1DataAddress, context.Cb1DataSize).ToArray();
+ code ??= memoryManager.GetSpan(context.Address, context.Size).ToArray();
- if (dumper != null)
- {
- pathsA = dumper.Dump(codeA, compute: false);
- pathsB = dumper.Dump(codeB, compute: false);
- }
+ ShaderDumpPaths paths = dumper?.Dump(code, context.Stage == ShaderStage.Compute) ?? default;
+ ShaderProgram program = context.Translate();
- ShaderProgram program = currentStage.Translate(out ShaderProgramInfo shaderProgramInfo, nextStage, vertexA);
+ paths.Prepend(program);
- pathsB.Prepend(program);
- pathsA.Prepend(program);
+ return new TranslatedShader(new CachedShaderStage(program.Info, code, cb1Data), program);
+ }
- return new ShaderCodeHolder(program, shaderProgramInfo, codeB, codeA);
- }
- else
+ /// <summary>
+ /// Gets the index of a stage from a <see cref="ShaderStage"/>.
+ /// </summary>
+ /// <param name="stage">Stage to get the index from</param>
+ /// <returns>Stage index</returns>
+ private static int StageToStageIndex(ShaderStage stage)
+ {
+ return stage switch
{
- byte[] code = memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray();
-
- ShaderDumpPaths paths = dumper?.Dump(code, currentStage.Stage == ShaderStage.Compute) ?? default;
-
- ShaderProgram program = currentStage.Translate(out ShaderProgramInfo shaderProgramInfo, nextStage);
-
- paths.Prepend(program);
-
- return new ShaderCodeHolder(program, shaderProgramInfo, code);
- }
+ ShaderStage.TessellationControl => 1,
+ ShaderStage.TessellationEvaluation => 2,
+ ShaderStage.Geometry => 3,
+ ShaderStage.Fragment => 4,
+ _ => 0
+ };
}
/// <summary>
@@ -1084,23 +599,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
public void Dispose()
{
- foreach (List<ShaderBundle> list in _cpPrograms.Values)
+ foreach (CachedShaderProgram program in _graphicsShaderCache.GetPrograms())
{
- foreach (ShaderBundle bundle in list)
- {
- bundle.Dispose();
- }
+ program.Dispose();
}
- foreach (List<ShaderBundle> list in _gpPrograms.Values)
+ foreach (CachedShaderProgram program in _computeShaderCache.GetPrograms())
{
- foreach (ShaderBundle bundle in list)
- {
- bundle.Dispose();
- }
+ program.Dispose();
}
- _cacheManager?.Dispose();
+ _cacheWriter?.Dispose();
}
}
}
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs
new file mode 100644
index 00000000..065f9ba9
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs
@@ -0,0 +1,280 @@
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Gpu.Shader.HashTable;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ /// <summary>
+ /// Holds already cached code for a guest shader.
+ /// </summary>
+ struct CachedGraphicsGuestCode
+ {
+ public byte[] VertexACode;
+ public byte[] VertexBCode;
+ public byte[] TessControlCode;
+ public byte[] TessEvaluationCode;
+ public byte[] GeometryCode;
+ public byte[] FragmentCode;
+
+ /// <summary>
+ /// Gets the guest code of a shader stage by its index.
+ /// </summary>
+ /// <param name="stageIndex">Index of the shader stage</param>
+ /// <returns>Guest code, or null if not present</returns>
+ public byte[] GetByIndex(int stageIndex)
+ {
+ return stageIndex switch
+ {
+ 1 => TessControlCode,
+ 2 => TessEvaluationCode,
+ 3 => GeometryCode,
+ 4 => FragmentCode,
+ _ => VertexBCode
+ };
+ }
+ }
+
+ /// <summary>
+ /// Graphics shader cache hash table.
+ /// </summary>
+ class ShaderCacheHashTable
+ {
+ /// <summary>
+ /// Shader ID cache.
+ /// </summary>
+ private struct IdCache
+ {
+ private PartitionedHashTable<int> _cache;
+ private int _id;
+
+ /// <summary>
+ /// Initializes the state.
+ /// </summary>
+ public void Initialize()
+ {
+ _cache = new PartitionedHashTable<int>();
+ _id = 0;
+ }
+
+ /// <summary>
+ /// Adds guest code to the cache.
+ /// </summary>
+ /// <remarks>
+ /// If the code was already cached, it will just return the existing ID.
+ /// </remarks>
+ /// <param name="code">Code to add</param>
+ /// <returns>Unique ID for the guest code</returns>
+ public int Add(byte[] code)
+ {
+ int id = ++_id;
+ int cachedId = _cache.GetOrAdd(code, id);
+ if (cachedId != id)
+ {
+ --_id;
+ }
+
+ return cachedId;
+ }
+
+ /// <summary>
+ /// Tries to find cached guest code.
+ /// </summary>
+ /// <param name="dataAccessor">Code accessor used to read guest code to find a match on the hash table</param>
+ /// <param name="id">ID of the guest code, if found</param>
+ /// <param name="data">Cached guest code, if found</param>
+ /// <returns>True if found, false otherwise</returns>
+ public bool TryFind(IDataAccessor dataAccessor, out int id, out byte[] data)
+ {
+ return _cache.TryFindItem(dataAccessor, out id, out data);
+ }
+ }
+
+ /// <summary>
+ /// Guest code IDs of the guest shaders that when combined forms a single host program.
+ /// </summary>
+ private struct IdTable : IEquatable<IdTable>
+ {
+ public int VertexAId;
+ public int VertexBId;
+ public int TessControlId;
+ public int TessEvaluationId;
+ public int GeometryId;
+ public int FragmentId;
+
+ public override bool Equals(object obj)
+ {
+ return obj is IdTable other && Equals(other);
+ }
+
+ public bool Equals(IdTable other)
+ {
+ return other.VertexAId == VertexAId &&
+ other.VertexBId == VertexBId &&
+ other.TessControlId == TessControlId &&
+ other.TessEvaluationId == TessEvaluationId &&
+ other.GeometryId == GeometryId &&
+ other.FragmentId == FragmentId;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(VertexAId, VertexBId, TessControlId, TessEvaluationId, GeometryId, FragmentId);
+ }
+ }
+
+ private IdCache _vertexACache;
+ private IdCache _vertexBCache;
+ private IdCache _tessControlCache;
+ private IdCache _tessEvaluationCache;
+ private IdCache _geometryCache;
+ private IdCache _fragmentCache;
+
+ private readonly Dictionary<IdTable, ShaderSpecializationList> _shaderPrograms;
+
+ /// <summary>
+ /// Creates a new graphics shader cache hash table.
+ /// </summary>
+ public ShaderCacheHashTable()
+ {
+ _vertexACache.Initialize();
+ _vertexBCache.Initialize();
+ _tessControlCache.Initialize();
+ _tessEvaluationCache.Initialize();
+ _geometryCache.Initialize();
+ _fragmentCache.Initialize();
+
+ _shaderPrograms = new Dictionary<IdTable, ShaderSpecializationList>();
+ }
+
+ /// <summary>
+ /// Adds a program to the cache.
+ /// </summary>
+ /// <param name="program">Program to be added</param>
+ public void Add(CachedShaderProgram program)
+ {
+ IdTable idTable = new IdTable();
+
+ foreach (var shader in program.Shaders)
+ {
+ if (shader == null)
+ {
+ continue;
+ }
+
+ if (shader.Info != null)
+ {
+ switch (shader.Info.Stage)
+ {
+ case ShaderStage.Vertex:
+ idTable.VertexBId = _vertexBCache.Add(shader.Code);
+ break;
+ case ShaderStage.TessellationControl:
+ idTable.TessControlId = _tessControlCache.Add(shader.Code);
+ break;
+ case ShaderStage.TessellationEvaluation:
+ idTable.TessEvaluationId = _tessEvaluationCache.Add(shader.Code);
+ break;
+ case ShaderStage.Geometry:
+ idTable.GeometryId = _geometryCache.Add(shader.Code);
+ break;
+ case ShaderStage.Fragment:
+ idTable.FragmentId = _fragmentCache.Add(shader.Code);
+ break;
+ }
+ }
+ else
+ {
+ idTable.VertexAId = _vertexACache.Add(shader.Code);
+ }
+ }
+
+ if (!_shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList))
+ {
+ specList = new ShaderSpecializationList();
+ _shaderPrograms.Add(idTable, specList);
+ }
+
+ specList.Add(program);
+ }
+
+ /// <summary>
+ /// Tries to find a cached program.
+ /// </summary>
+ /// <remarks>
+ /// Even if false is returned, <paramref name="guestCode"/> might still contain cached guest code.
+ /// This can be used to avoid additional allocations for guest code that was already cached.
+ /// </remarks>
+ /// <param name="channel">GPU channel</param>
+ /// <param name="poolState">Texture pool state</param>
+ /// <param name="addresses">Guest addresses of the shaders to find</param>
+ /// <param name="program">Cached host program for the given state, if found</param>
+ /// <param name="guestCode">Cached guest code, if any found</param>
+ /// <returns>True if a cached host program was found, false otherwise</returns>
+ public bool TryFind(
+ GpuChannel channel,
+ GpuChannelPoolState poolState,
+ ShaderAddresses addresses,
+ out CachedShaderProgram program,
+ out CachedGraphicsGuestCode guestCode)
+ {
+ var memoryManager = channel.MemoryManager;
+ IdTable idTable = new IdTable();
+ guestCode = new CachedGraphicsGuestCode();
+
+ program = null;
+
+ bool found = TryGetId(_vertexACache, memoryManager, addresses.VertexA, out idTable.VertexAId, out guestCode.VertexACode);
+ found &= TryGetId(_vertexBCache, memoryManager, addresses.VertexB, out idTable.VertexBId, out guestCode.VertexBCode);
+ found &= TryGetId(_tessControlCache, memoryManager, addresses.TessControl, out idTable.TessControlId, out guestCode.TessControlCode);
+ found &= TryGetId(_tessEvaluationCache, memoryManager, addresses.TessEvaluation, out idTable.TessEvaluationId, out guestCode.TessEvaluationCode);
+ found &= TryGetId(_geometryCache, memoryManager, addresses.Geometry, out idTable.GeometryId, out guestCode.GeometryCode);
+ found &= TryGetId(_fragmentCache, memoryManager, addresses.Fragment, out idTable.FragmentId, out guestCode.FragmentCode);
+
+ if (found && _shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList))
+ {
+ return specList.TryFindForGraphics(channel, poolState, out program);
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Tries to get the ID of a single cached shader stage.
+ /// </summary>
+ /// <param name="idCache">ID cache of the stage</param>
+ /// <param name="memoryManager">GPU memory manager</param>
+ /// <param name="baseAddress">Base address of the shader</param>
+ /// <param name="id">ID, if found</param>
+ /// <param name="data">Cached guest code, if found</param>
+ /// <returns>True if a cached shader is found, false otherwise</returns>
+ private static bool TryGetId(IdCache idCache, MemoryManager memoryManager, ulong baseAddress, out int id, out byte[] data)
+ {
+ if (baseAddress == 0)
+ {
+ id = 0;
+ data = null;
+ return true;
+ }
+
+ ShaderCodeAccessor codeAccessor = new ShaderCodeAccessor(memoryManager, baseAddress);
+ return idCache.TryFind(codeAccessor, out id, out data);
+ }
+
+ /// <summary>
+ /// Gets all programs that have been added to the table.
+ /// </summary>
+ /// <returns>Programs added to the table</returns>
+ public IEnumerable<CachedShaderProgram> GetPrograms()
+ {
+ foreach (var specList in _shaderPrograms.Values)
+ {
+ foreach (var program in specList)
+ {
+ yield return program;
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCodeAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCodeAccessor.cs
new file mode 100644
index 00000000..dbb33d22
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCodeAccessor.cs
@@ -0,0 +1,32 @@
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Gpu.Shader.HashTable;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ /// <summary>
+ /// Shader code accessor.
+ /// </summary>
+ struct ShaderCodeAccessor : IDataAccessor
+ {
+ private readonly MemoryManager _memoryManager;
+ private readonly ulong _baseAddress;
+
+ /// <summary>
+ /// Creates a new shader code accessor.
+ /// </summary>
+ /// <param name="memoryManager">Memory manager used to access the shader code</param>
+ /// <param name="baseAddress">Base address of the shader in memory</param>
+ public ShaderCodeAccessor(MemoryManager memoryManager, ulong baseAddress)
+ {
+ _memoryManager = memoryManager;
+ _baseAddress = baseAddress;
+ }
+
+ /// <inheritdoc/>
+ public ReadOnlySpan<byte> GetSpan(int offset, int length)
+ {
+ return _memoryManager.GetSpanMapped(_baseAddress + (ulong)offset, length);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCodeHolder.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCodeHolder.cs
deleted file mode 100644
index dbf2d6f5..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCodeHolder.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using Ryujinx.Graphics.GAL;
-using Ryujinx.Graphics.Shader;
-
-namespace Ryujinx.Graphics.Gpu.Shader
-{
- /// <summary>
- /// Cached shader code for a single shader stage.
- /// </summary>
- class ShaderCodeHolder
- {
- /// <summary>
- /// Shader program containing translated code.
- /// </summary>
- public ShaderProgram Program { get; }
-
- /// <summary>
- /// Shader program information.
- /// </summary>
- public ShaderProgramInfo Info { get; }
-
- /// <summary>
- /// Host shader object.
- /// </summary>
- /// <remarks>Null if the host shader program cache is in use.</remarks>
- public IShader HostShader { get; set; }
-
- /// <summary>
- /// Maxwell binary shader code.
- /// </summary>
- public byte[] Code { get; }
-
- /// <summary>
- /// Optional maxwell binary shader code for "Vertex A" shader.
- /// </summary>
- public byte[] Code2 { get; }
-
- /// <summary>
- /// Creates a new instace of the shader code holder.
- /// </summary>
- /// <param name="program">Shader program</param>
- /// <param name="info">Shader program information</param>
- /// <param name="code">Maxwell binary shader code</param>
- /// <param name="code2">Optional binary shader code of the "Vertex A" shader, when combined with "Vertex B"</param>
- public ShaderCodeHolder(ShaderProgram program, ShaderProgramInfo info, byte[] code, byte[] code2 = null)
- {
- Program = program;
- Info = info;
- Code = code;
- Code2 = code2;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCompileTask.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCompileTask.cs
deleted file mode 100644
index a9283de2..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCompileTask.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-using Ryujinx.Graphics.GAL;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Ryujinx.Graphics.Gpu.Shader
-{
- delegate bool ShaderCompileTaskCallback(bool success, ShaderCompileTask task);
-
- /// <summary>
- /// A class that represents a shader compilation.
- /// </summary>
- class ShaderCompileTask
- {
- private bool _compiling;
-
- private Task _programsTask;
- private IProgram _program;
-
- private ShaderCompileTaskCallback _action;
- private AutoResetEvent _taskDoneEvent;
-
- public bool IsFaulted => _programsTask.IsFaulted;
-
- /// <summary>
- /// Create a new shader compile task, with an event to signal whenever a subtask completes.
- /// </summary>
- /// <param name="taskDoneEvent">Event to signal when a subtask completes</param>
- public ShaderCompileTask(AutoResetEvent taskDoneEvent)
- {
- _taskDoneEvent = taskDoneEvent;
- }
-
- /// <summary>
- /// Check the completion status of the shader compile task, and run callbacks on step completion.
- /// Calling this periodically is required to progress through steps of the compilation.
- /// </summary>
- /// <returns>True if the task is complete, false if it is in progress</returns>
- public bool IsDone()
- {
- if (_compiling)
- {
- ProgramLinkStatus status = _program.CheckProgramLink(false);
-
- if (status != ProgramLinkStatus.Incomplete)
- {
- return _action(status == ProgramLinkStatus.Success, this);
- }
- }
- else
- {
- // Waiting on the task.
-
- if (_programsTask.IsCompleted)
- {
- return _action(true, this);
- }
- }
-
- return false;
- }
-
- /// <summary>
- /// Run a callback when the specified task has completed.
- /// </summary>
- /// <param name="task">The task object that needs to complete</param>
- /// <param name="action">The action to perform when it is complete</param>
- public void OnTask(Task task, ShaderCompileTaskCallback action)
- {
- _compiling = false;
-
- _programsTask = task;
- _action = action;
-
- task.ContinueWith(task => _taskDoneEvent.Set());
- }
-
- /// <summary>
- /// Run a callback when the specified program has been linked.
- /// </summary>
- /// <param name="task">The program that needs to be linked</param>
- /// <param name="action">The action to perform when linking is complete</param>
- public void OnCompiled(IProgram program, ShaderCompileTaskCallback action)
- {
- _compiling = true;
-
- _program = program;
- _action = action;
-
- if (program == null)
- {
- action(false, this);
- }
- }
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs
new file mode 100644
index 00000000..87e08754
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs
@@ -0,0 +1,76 @@
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ /// <summary>
+ /// List of cached shader programs that differs only by specialization state.
+ /// </summary>
+ class ShaderSpecializationList : IEnumerable<CachedShaderProgram>
+ {
+ private readonly List<CachedShaderProgram> _entries = new List<CachedShaderProgram>();
+
+ /// <summary>
+ /// Adds a program to the list.
+ /// </summary>
+ /// <param name="program">Program to be added</param>
+ public void Add(CachedShaderProgram program)
+ {
+ _entries.Add(program);
+ }
+
+ /// <summary>
+ /// Tries to find an existing 3D program on the cache.
+ /// </summary>
+ /// <param name="channel">GPU channel</param>
+ /// <param name="poolState">Texture pool state</param>
+ /// <param name="program">Cached program, if found</param>
+ /// <returns>True if a compatible program is found, false otherwise</returns>
+ public bool TryFindForGraphics(GpuChannel channel, GpuChannelPoolState poolState, out CachedShaderProgram program)
+ {
+ foreach (var entry in _entries)
+ {
+ if (entry.SpecializationState.MatchesGraphics(channel, poolState))
+ {
+ program = entry;
+ return true;
+ }
+ }
+
+ program = default;
+ return false;
+ }
+
+ /// <summary>
+ /// Tries to find an existing compute program on the cache.
+ /// </summary>
+ /// <param name="channel">GPU channel</param>
+ /// <param name="poolState">Texture pool state</param>
+ /// <param name="program">Cached program, if found</param>
+ /// <returns>True if a compatible program is found, false otherwise</returns>
+ public bool TryFindForCompute(GpuChannel channel, GpuChannelPoolState poolState, out CachedShaderProgram program)
+ {
+ foreach (var entry in _entries)
+ {
+ if (entry.SpecializationState.MatchesCompute(channel, poolState))
+ {
+ program = entry;
+ return true;
+ }
+ }
+
+ program = default;
+ return false;
+ }
+
+ public IEnumerator<CachedShaderProgram> GetEnumerator()
+ {
+ return _entries.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
new file mode 100644
index 00000000..2bbc3d2c
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
@@ -0,0 +1,615 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.Graphics.Gpu.Shader.DiskCache;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ class ShaderSpecializationState
+ {
+ private const uint ComsMagic = (byte)'C' | ((byte)'O' << 8) | ((byte)'M' << 16) | ((byte)'S' << 24);
+ private const uint GfxsMagic = (byte)'G' | ((byte)'F' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24);
+ private const uint TfbdMagic = (byte)'T' | ((byte)'F' << 8) | ((byte)'B' << 16) | ((byte)'D' << 24);
+ private const uint TexkMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'K' << 24);
+ private const uint TexsMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24);
+
+ /// <summary>
+ /// Flags indicating GPU state that is used by the shader.
+ /// </summary>
+ [Flags]
+ private enum QueriedStateFlags
+ {
+ EarlyZForce = 1 << 0,
+ PrimitiveTopology = 1 << 1,
+ TessellationMode = 1 << 2,
+ TransformFeedback = 1 << 3
+ }
+
+ private QueriedStateFlags _queriedState;
+ private bool _compute;
+ private byte _constantBufferUsePerStage;
+
+ /// <summary>
+ /// Compute engine state.
+ /// </summary>
+ public GpuChannelComputeState ComputeState;
+
+ /// <summary>
+ /// 3D engine state.
+ /// </summary>
+ public GpuChannelGraphicsState GraphicsState;
+
+ /// <summary>
+ /// Contant buffers bound at the time the shader was compiled, per stage.
+ /// </summary>
+ public Array5<uint> ConstantBufferUse;
+
+ /// <summary>
+ /// Transform feedback buffers active at the time the shader was compiled.
+ /// </summary>
+ public TransformFeedbackDescriptor[] TransformFeedbackDescriptors;
+
+ /// <summary>
+ /// Flags indicating texture state that is used by the shader.
+ /// </summary>
+ [Flags]
+ private enum QueriedTextureStateFlags
+ {
+ TextureFormat = 1 << 0,
+ SamplerType = 1 << 1,
+ CoordNormalized = 1 << 2
+ }
+
+ /// <summary>
+ /// Reference type wrapping a value.
+ /// </summary>
+ private class Box<T>
+ {
+ /// <summary>
+ /// Wrapped value.
+ /// </summary>
+ public T Value;
+ }
+
+ /// <summary>
+ /// State of a texture or image that is accessed by the shader.
+ /// </summary>
+ private struct TextureSpecializationState
+ {
+ // New fields should be added to the end of the struct to keep disk shader cache compatibility.
+
+ /// <summary>
+ /// Flags indicating which state of the texture the shader depends on.
+ /// </summary>
+ public QueriedTextureStateFlags QueriedFlags;
+
+ /// <summary>
+ /// Encoded texture format value.
+ /// </summary>
+ public uint Format;
+
+ /// <summary>
+ /// True if the texture format is sRGB, false otherwise.
+ /// </summary>
+ public bool FormatSrgb;
+
+ /// <summary>
+ /// Texture target.
+ /// </summary>
+ public Image.TextureTarget TextureTarget;
+
+ /// <summary>
+ /// Indicates if the coordinates used to sample the texture are normalized or not (0.0..1.0 or 0..Width/Height).
+ /// </summary>
+ public bool CoordNormalized;
+ }
+
+ /// <summary>
+ /// Texture binding information, used to identify each texture accessed by the shader.
+ /// </summary>
+ private struct TextureKey : IEquatable<TextureKey>
+ {
+ // New fields should be added to the end of the struct to keep disk shader cache compatibility.
+
+ /// <summary>
+ /// Shader stage where the texture is used.
+ /// </summary>
+ public readonly int StageIndex;
+
+ /// <summary>
+ /// Texture handle offset in words on the texture buffer.
+ /// </summary>
+ public readonly int Handle;
+
+ /// <summary>
+ /// Constant buffer slot of the texture buffer (-1 to use the texture buffer index GPU register).
+ /// </summary>
+ public readonly int CbufSlot;
+
+ /// <summary>
+ /// Creates a new texture key.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Texture handle offset in words on the texture buffer</param>
+ /// <param name="cbufSlot">Constant buffer slot of the texture buffer (-1 to use the texture buffer index GPU register)</param>
+ public TextureKey(int stageIndex, int handle, int cbufSlot)
+ {
+ StageIndex = stageIndex;
+ Handle = handle;
+ CbufSlot = cbufSlot;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is TextureKey textureKey && Equals(textureKey);
+ }
+
+ public bool Equals(TextureKey other)
+ {
+ return StageIndex == other.StageIndex && Handle == other.Handle && CbufSlot == other.CbufSlot;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(StageIndex, Handle, CbufSlot);
+ }
+ }
+
+ private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
+
+ /// <summary>
+ /// Creates a new instance of the shader specialization state.
+ /// </summary>
+ private ShaderSpecializationState()
+ {
+ _textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>();
+ }
+
+ /// <summary>
+ /// Creates a new instance of the shader specialization state.
+ /// </summary>
+ /// <param name="state">Current compute engine state</param>
+ public ShaderSpecializationState(GpuChannelComputeState state) : this()
+ {
+ ComputeState = state;
+ _compute = true;
+ }
+
+ /// <summary>
+ /// Creates a new instance of the shader specialization state.
+ /// </summary>
+ /// <param name="state">Current 3D engine state</param>
+ /// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
+ public ShaderSpecializationState(GpuChannelGraphicsState state, TransformFeedbackDescriptor[] descriptors) : this()
+ {
+ GraphicsState = state;
+ _compute = false;
+
+ if (descriptors != null)
+ {
+ TransformFeedbackDescriptors = descriptors;
+ _queriedState |= QueriedStateFlags.TransformFeedback;
+ }
+ }
+
+ /// <summary>
+ /// Indicates that the shader accesses the early Z force state.
+ /// </summary>
+ public void RecordEarlyZForce()
+ {
+ _queriedState |= QueriedStateFlags.EarlyZForce;
+ }
+
+ /// <summary>
+ /// Indicates that the shader accesses the primitive topology state.
+ /// </summary>
+ public void RecordPrimitiveTopology()
+ {
+ _queriedState |= QueriedStateFlags.PrimitiveTopology;
+ }
+
+ /// <summary>
+ /// Indicates that the shader accesses the tessellation mode state.
+ /// </summary>
+ public void RecordTessellationMode()
+ {
+ _queriedState |= QueriedStateFlags.TessellationMode;
+ }
+
+ /// <summary>
+ /// Indicates that the shader accesses the constant buffer use state.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage index</param>
+ /// <param name="useMask">Mask indicating the constant buffers bound at the time of the shader compilation</param>
+ public void RecordConstantBufferUse(int stageIndex, uint useMask)
+ {
+ ConstantBufferUse[stageIndex] = useMask;
+ _constantBufferUsePerStage |= (byte)(1 << stageIndex);
+ }
+
+ /// <summary>
+ /// Indicates that a given texture is accessed by the shader.
+ /// </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="descriptor">Descriptor of the texture</param>
+ public void RegisterTexture(int stageIndex, int handle, int cbufSlot, Image.TextureDescriptor descriptor)
+ {
+ Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
+ state.Value.Format = descriptor.UnpackFormat();
+ state.Value.FormatSrgb = descriptor.UnpackSrgb();
+ state.Value.TextureTarget = descriptor.UnpackTextureTarget();
+ state.Value.CoordNormalized = descriptor.UnpackTextureCoordNormalized();
+ }
+
+ /// <summary>
+ /// Indicates that a given texture is accessed by the shader.
+ /// </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="format">Maxwell texture format value</param>
+ /// <param name="formatSrgb">Whenever the texture format is a sRGB format</param>
+ /// <param name="target">Texture target type</param>
+ /// <param name="coordNormalized">Whenever the texture coordinates used on the shader are considered normalized</param>
+ public void RegisterTexture(
+ int stageIndex,
+ int handle,
+ int cbufSlot,
+ uint format,
+ bool formatSrgb,
+ Image.TextureTarget target,
+ bool coordNormalized)
+ {
+ Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
+ state.Value.Format = format;
+ state.Value.FormatSrgb = formatSrgb;
+ state.Value.TextureTarget = target;
+ state.Value.CoordNormalized = coordNormalized;
+ }
+
+ /// <summary>
+ /// Indicates that the format 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>
+ public void RecordTextureFormat(int stageIndex, int handle, int cbufSlot)
+ {
+ Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
+ state.Value.QueriedFlags |= QueriedTextureStateFlags.TextureFormat;
+ }
+
+ /// <summary>
+ /// Indicates that the target 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>
+ public void RecordTextureSamplerType(int stageIndex, int handle, int cbufSlot)
+ {
+ Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
+ state.Value.QueriedFlags |= QueriedTextureStateFlags.SamplerType;
+ }
+
+ /// <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>
+ public void RecordTextureCoordNormalized(int stageIndex, int handle, int cbufSlot)
+ {
+ Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
+ state.Value.QueriedFlags |= QueriedTextureStateFlags.CoordNormalized;
+ }
+
+ /// <summary>
+ /// Checks if a given texture 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 TextureRegistered(int stageIndex, int handle, int cbufSlot)
+ {
+ return GetTextureSpecState(stageIndex, handle, cbufSlot) != null;
+ }
+
+ /// <summary>
+ /// Gets the recorded format of a given texture.
+ /// </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 (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot)
+ {
+ TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value;
+ return (state.Format, state.FormatSrgb);
+ }
+
+ /// <summary>
+ /// Gets the recorded target of a given texture.
+ /// </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 Image.TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot)
+ {
+ return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget;
+ }
+
+ /// <summary>
+ /// Gets the recorded coordinate normalization state of a given texture.
+ /// </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 GetCoordNormalized(int stageIndex, int handle, int cbufSlot)
+ {
+ return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized;
+ }
+
+ /// <summary>
+ /// Gets texture specialization state for a given texture, or create a new one if not present.
+ /// </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>
+ /// <returns>Texture specialization state</returns>
+ private Box<TextureSpecializationState> GetOrCreateTextureSpecState(int stageIndex, int handle, int cbufSlot)
+ {
+ TextureKey key = new TextureKey(stageIndex, handle, cbufSlot);
+
+ if (!_textureSpecialization.TryGetValue(key, out Box<TextureSpecializationState> state))
+ {
+ _textureSpecialization.Add(key, state = new Box<TextureSpecializationState>());
+ }
+
+ return state;
+ }
+
+ /// <summary>
+ /// Gets texture specialization state for a given texture.
+ /// </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>
+ /// <returns>Texture specialization state</returns>
+ private Box<TextureSpecializationState> GetTextureSpecState(int stageIndex, int handle, int cbufSlot)
+ {
+ TextureKey key = new TextureKey(stageIndex, handle, cbufSlot);
+
+ if (_textureSpecialization.TryGetValue(key, out Box<TextureSpecializationState> state))
+ {
+ return state;
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Checks if the recorded state matches the current GPU 3D engine state.
+ /// </summary>
+ /// <param name="channel">GPU channel</param>
+ /// <param name="poolState">Texture pool state</param>
+ /// <returns>True if the state matches, false otherwise</returns>
+ public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState)
+ {
+ return Matches(channel, poolState, isCompute: false);
+ }
+
+ /// <summary>
+ /// Checks if the recorded state matches the current GPU compute engine state.
+ /// </summary>
+ /// <param name="channel">GPU channel</param>
+ /// <param name="poolState">Texture pool state</param>
+ /// <returns>True if the state matches, false otherwise</returns>
+ public bool MatchesCompute(GpuChannel channel, GpuChannelPoolState poolState)
+ {
+ return Matches(channel, poolState, isCompute: true);
+ }
+
+ /// <summary>
+ /// Checks if the recorded state matches the current GPU state.
+ /// </summary>
+ /// <param name="channel">GPU channel</param>
+ /// <param name="poolState">Texture pool state</param>
+ /// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param>
+ /// <returns>True if the state matches, false otherwise</returns>
+ private bool Matches(GpuChannel channel, GpuChannelPoolState poolState, bool isCompute)
+ {
+ int constantBufferUsePerStageMask = _constantBufferUsePerStage;
+
+ while (constantBufferUsePerStageMask != 0)
+ {
+ int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask);
+
+ uint useMask = isCompute
+ ? channel.BufferManager.GetComputeUniformBufferUseMask()
+ : channel.BufferManager.GetGraphicsUniformBufferUseMask(index);
+
+ if (ConstantBufferUse[index] != useMask)
+ {
+ return false;
+ }
+
+ constantBufferUsePerStageMask &= ~(1 << index);
+ }
+
+ foreach (var kv in _textureSpecialization)
+ {
+ TextureKey textureKey = kv.Key;
+
+ (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex);
+
+ ulong textureCbAddress;
+ ulong samplerCbAddress;
+
+ if (isCompute)
+ {
+ textureCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex);
+ samplerCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex);
+ }
+ else
+ {
+ textureCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, textureBufferIndex);
+ samplerCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, samplerBufferIndex);
+ }
+
+ if (!channel.MemoryManager.Physical.IsMapped(textureCbAddress) || !channel.MemoryManager.Physical.IsMapped(samplerCbAddress))
+ {
+ continue;
+ }
+
+ Image.TextureDescriptor descriptor;
+
+ if (isCompute)
+ {
+ descriptor = channel.TextureManager.GetComputeTextureDescriptor(
+ poolState.TexturePoolGpuVa,
+ poolState.TextureBufferIndex,
+ poolState.TexturePoolMaximumId,
+ textureKey.Handle,
+ textureKey.CbufSlot);
+ }
+ else
+ {
+ descriptor = channel.TextureManager.GetGraphicsTextureDescriptor(
+ poolState.TexturePoolGpuVa,
+ poolState.TextureBufferIndex,
+ poolState.TexturePoolMaximumId,
+ textureKey.StageIndex,
+ textureKey.Handle,
+ textureKey.CbufSlot);
+ }
+
+ Box<TextureSpecializationState> specializationState = kv.Value;
+
+ if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) &&
+ specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized())
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Reads shader specialization state that has been serialized.
+ /// </summary>
+ /// <param name="dataReader">Data reader</param>
+ /// <returns>Shader specialization state</returns>
+ public static ShaderSpecializationState Read(ref BinarySerializer dataReader)
+ {
+ ShaderSpecializationState specState = new ShaderSpecializationState();
+
+ dataReader.Read(ref specState._queriedState);
+ dataReader.Read(ref specState._compute);
+
+ if (specState._compute)
+ {
+ dataReader.ReadWithMagicAndSize(ref specState.ComputeState, ComsMagic);
+ }
+ else
+ {
+ dataReader.ReadWithMagicAndSize(ref specState.GraphicsState, GfxsMagic);
+ }
+
+ dataReader.Read(ref specState._constantBufferUsePerStage);
+
+ int constantBufferUsePerStageMask = specState._constantBufferUsePerStage;
+
+ while (constantBufferUsePerStageMask != 0)
+ {
+ int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask);
+ dataReader.Read(ref specState.ConstantBufferUse[index]);
+ constantBufferUsePerStageMask &= ~(1 << index);
+ }
+
+ if (specState._queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
+ {
+ ushort tfCount = 0;
+ dataReader.Read(ref tfCount);
+ specState.TransformFeedbackDescriptors = new TransformFeedbackDescriptor[tfCount];
+
+ for (int index = 0; index < tfCount; index++)
+ {
+ dataReader.ReadWithMagicAndSize(ref specState.TransformFeedbackDescriptors[index], TfbdMagic);
+ }
+ }
+
+ ushort count = 0;
+ dataReader.Read(ref count);
+
+ for (int index = 0; index < count; index++)
+ {
+ TextureKey textureKey = default;
+ Box<TextureSpecializationState> textureState = new Box<TextureSpecializationState>();
+
+ dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic);
+ dataReader.ReadWithMagicAndSize(ref textureState.Value, TexsMagic);
+
+ specState._textureSpecialization[textureKey] = textureState;
+ }
+
+ return specState;
+ }
+
+ /// <summary>
+ /// Serializes the shader specialization state.
+ /// </summary>
+ /// <param name="dataWriter">Data writer</param>
+ public void Write(ref BinarySerializer dataWriter)
+ {
+ dataWriter.Write(ref _queriedState);
+ dataWriter.Write(ref _compute);
+
+ if (_compute)
+ {
+ dataWriter.WriteWithMagicAndSize(ref ComputeState, ComsMagic);
+ }
+ else
+ {
+ dataWriter.WriteWithMagicAndSize(ref GraphicsState, GfxsMagic);
+ }
+
+ dataWriter.Write(ref _constantBufferUsePerStage);
+
+ int constantBufferUsePerStageMask = _constantBufferUsePerStage;
+
+ while (constantBufferUsePerStageMask != 0)
+ {
+ int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask);
+ dataWriter.Write(ref ConstantBufferUse[index]);
+ constantBufferUsePerStageMask &= ~(1 << index);
+ }
+
+ if (_queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
+ {
+ ushort tfCount = (ushort)TransformFeedbackDescriptors.Length;
+ dataWriter.Write(ref tfCount);
+
+ for (int index = 0; index < TransformFeedbackDescriptors.Length; index++)
+ {
+ dataWriter.WriteWithMagicAndSize(ref TransformFeedbackDescriptors[index], TfbdMagic);
+ }
+ }
+
+ ushort count = (ushort)_textureSpecialization.Count;
+ dataWriter.Write(ref count);
+
+ foreach (var kv in _textureSpecialization)
+ {
+ var textureKey = kv.Key;
+ var textureState = kv.Value;
+
+ dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic);
+ dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/TransformFeedbackDescriptor.cs b/Ryujinx.Graphics.Gpu/Shader/TransformFeedbackDescriptor.cs
index eaa889cc..09f1df76 100644
--- a/Ryujinx.Graphics.Gpu/Shader/TransformFeedbackDescriptor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/TransformFeedbackDescriptor.cs
@@ -1,19 +1,58 @@
+using Ryujinx.Common.Memory;
using System;
+using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Shader
{
+ /// <summary>
+ /// Transform feedback descriptor.
+ /// </summary>
struct TransformFeedbackDescriptor
{
- public int BufferIndex { get; }
- public int Stride { get; }
+ // New fields should be added to the end of the struct to keep disk shader cache compatibility.
- public byte[] VaryingLocations { get; }
+ /// <summary>
+ /// Index of the transform feedback.
+ /// </summary>
+ public readonly int BufferIndex;
- public TransformFeedbackDescriptor(int bufferIndex, int stride, byte[] varyingLocations)
+ /// <summary>
+ /// Amount of bytes consumed per vertex.
+ /// </summary>
+ public readonly int Stride;
+
+ /// <summary>
+ /// Number of varyings written into the buffer.
+ /// </summary>
+ public readonly int VaryingCount;
+
+ /// <summary>
+ /// Location of varyings to be written into the buffer. Each byte is one location.
+ /// </summary>
+ public Array32<uint> VaryingLocations; // Making this readonly breaks AsSpan
+
+ /// <summary>
+ /// Creates a new transform feedback descriptor.
+ /// </summary>
+ /// <param name="bufferIndex">Index of the transform feedback</param>
+ /// <param name="stride">Amount of bytes consumed per vertex</param>
+ /// <param name="varyingCount">Number of varyings written into the buffer. Indicates size in bytes of <paramref name="varyingLocations"/></param>
+ /// <param name="varyingLocations">Location of varyings to be written into the buffer. Each byte is one location</param>
+ public TransformFeedbackDescriptor(int bufferIndex, int stride, int varyingCount, ref Array32<uint> varyingLocations)
+ {
+ BufferIndex = bufferIndex;
+ Stride = stride;
+ VaryingCount = varyingCount;
+ VaryingLocations = varyingLocations;
+ }
+
+ /// <summary>
+ /// Gets a span of the <see cref="VaryingLocations"/>.
+ /// </summary>
+ /// <returns>Span of varying locations</returns>
+ public ReadOnlySpan<byte> AsSpan()
{
- BufferIndex = bufferIndex;
- Stride = stride;
- VaryingLocations = varyingLocations ?? throw new ArgumentNullException(nameof(varyingLocations));
+ return MemoryMarshal.Cast<uint, byte>(VaryingLocations.ToSpan()).Slice(0, Math.Min(128, VaryingCount));
}
}
}
diff --git a/Ryujinx.Graphics.OpenGL/EnumConversion.cs b/Ryujinx.Graphics.OpenGL/EnumConversion.cs
index 22e81605..24cf1fc4 100644
--- a/Ryujinx.Graphics.OpenGL/EnumConversion.cs
+++ b/Ryujinx.Graphics.OpenGL/EnumConversion.cs
@@ -1,6 +1,7 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.OpenGL
{
@@ -528,5 +529,19 @@ namespace Ryujinx.Graphics.OpenGL
return All.Never;
}
+
+ public static ShaderType Convert(this ShaderStage stage)
+ {
+ return stage switch
+ {
+ ShaderStage.Compute => ShaderType.ComputeShader,
+ ShaderStage.Vertex => ShaderType.VertexShader,
+ ShaderStage.TessellationControl => ShaderType.TessControlShader,
+ ShaderStage.TessellationEvaluation => ShaderType.TessEvaluationShader,
+ ShaderStage.Geometry => ShaderType.GeometryShader,
+ ShaderStage.Fragment => ShaderType.FragmentShader,
+ _ => ShaderType.VertexShader
+ };
+ }
}
}
diff --git a/Ryujinx.Graphics.OpenGL/Program.cs b/Ryujinx.Graphics.OpenGL/Program.cs
index d2b559a8..0cc722e6 100644
--- a/Ryujinx.Graphics.OpenGL/Program.cs
+++ b/Ryujinx.Graphics.OpenGL/Program.cs
@@ -1,6 +1,8 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Buffers.Binary;
@@ -24,46 +26,66 @@ namespace Ryujinx.Graphics.OpenGL
}
private ProgramLinkStatus _status = ProgramLinkStatus.Incomplete;
- private IShader[] _shaders;
+ private int[] _shaderHandles;
public bool HasFragmentShader;
public int FragmentOutputMap { get; }
- public Program(IShader[] shaders, int fragmentOutputMap)
+ public Program(ShaderSource[] shaders, int fragmentOutputMap)
{
Handle = GL.CreateProgram();
GL.ProgramParameter(Handle, ProgramParameterName.ProgramBinaryRetrievableHint, 1);
+ _shaderHandles = new int[shaders.Length];
+
for (int index = 0; index < shaders.Length; index++)
{
- Shader shader = (Shader)shaders[index];
+ ShaderSource shader = shaders[index];
- if (shader.IsFragment)
+ if (shader.Stage == ShaderStage.Fragment)
{
HasFragmentShader = true;
}
- GL.AttachShader(Handle, shader.Handle);
+ int shaderHandle = GL.CreateShader(shader.Stage.Convert());
+
+ switch (shader.Language)
+ {
+ case TargetLanguage.Glsl:
+ GL.ShaderSource(shaderHandle, shader.Code);
+ GL.CompileShader(shaderHandle);
+ break;
+ case TargetLanguage.Spirv:
+ GL.ShaderBinary(1, ref shaderHandle, (BinaryFormat)All.ShaderBinaryFormatSpirVArb, shader.BinaryCode, shader.BinaryCode.Length);
+ GL.SpecializeShader(shaderHandle, "main", 0, (int[])null, (int[])null);
+ break;
+ }
+
+ GL.AttachShader(Handle, shaderHandle);
+
+ _shaderHandles[index] = shaderHandle;
}
GL.LinkProgram(Handle);
- _shaders = shaders;
FragmentOutputMap = fragmentOutputMap;
}
public Program(ReadOnlySpan<byte> code, bool hasFragmentShader, int fragmentOutputMap)
{
- BinaryFormat binaryFormat = (BinaryFormat)BinaryPrimitives.ReadInt32LittleEndian(code.Slice(code.Length - 4, 4));
-
Handle = GL.CreateProgram();
- unsafe
+ if (code.Length >= 4)
{
- fixed (byte* ptr = code)
+ BinaryFormat binaryFormat = (BinaryFormat)BinaryPrimitives.ReadInt32LittleEndian(code.Slice(code.Length - 4, 4));
+
+ unsafe
{
- GL.ProgramBinary(Handle, binaryFormat, (IntPtr)ptr, code.Length - 4);
+ fixed (byte* ptr = code)
+ {
+ GL.ProgramBinary(Handle, binaryFormat, (IntPtr)ptr, code.Length - 4);
+ }
}
}
@@ -89,18 +111,7 @@ namespace Ryujinx.Graphics.OpenGL
}
GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out int status);
-
- if (_shaders != null)
- {
- for (int index = 0; index < _shaders.Length; index++)
- {
- int shaderHandle = ((Shader)_shaders[index]).Handle;
-
- GL.DetachShader(Handle, shaderHandle);
- }
-
- _shaders = null;
- }
+ DeleteShaders();
if (status == 0)
{
@@ -129,10 +140,25 @@ namespace Ryujinx.Graphics.OpenGL
return data;
}
+ private void DeleteShaders()
+ {
+ if (_shaderHandles != null)
+ {
+ foreach (int shaderHandle in _shaderHandles)
+ {
+ GL.DetachShader(Handle, shaderHandle);
+ GL.DeleteShader(shaderHandle);
+ }
+
+ _shaderHandles = null;
+ }
+ }
+
public void Dispose()
{
if (Handle != 0)
{
+ DeleteShaders();
GL.DeleteProgram(Handle);
Handle = 0;
diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs
index 8a6b4689..2a9ab422 100644
--- a/Ryujinx.Graphics.OpenGL/Renderer.cs
+++ b/Ryujinx.Graphics.OpenGL/Renderer.cs
@@ -1,11 +1,10 @@
-using OpenTK.Graphics;
-using OpenTK.Graphics.OpenGL;
+using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Image;
using Ryujinx.Graphics.OpenGL.Queries;
-using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
using System;
namespace Ryujinx.Graphics.OpenGL
@@ -54,11 +53,6 @@ namespace Ryujinx.Graphics.OpenGL
ResourcePool = new ResourcePool();
}
- public IShader CompileShader(ShaderStage stage, string code)
- {
- return new Shader(stage, code);
- }
-
public BufferHandle CreateBuffer(int size)
{
BufferCount++;
@@ -66,7 +60,7 @@ namespace Ryujinx.Graphics.OpenGL
return Buffer.Create(size);
}
- public IProgram CreateProgram(IShader[] shaders, ShaderInfo info)
+ public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
{
return new Program(shaders, info.FragmentOutputMap);
}
@@ -101,6 +95,8 @@ namespace Ryujinx.Graphics.OpenGL
public Capabilities GetCapabilities()
{
return new Capabilities(
+ api: TargetApi.OpenGL,
+ vendorName: GpuVendor,
hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
diff --git a/Ryujinx.Graphics.OpenGL/Shader.cs b/Ryujinx.Graphics.OpenGL/Shader.cs
deleted file mode 100644
index 8374fa62..00000000
--- a/Ryujinx.Graphics.OpenGL/Shader.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using OpenTK.Graphics.OpenGL;
-using Ryujinx.Graphics.GAL;
-using Ryujinx.Graphics.Shader;
-
-namespace Ryujinx.Graphics.OpenGL
-{
- class Shader : IShader
- {
- public int Handle { get; private set; }
- public bool IsFragment { get; }
-
- public Shader(ShaderStage stage, string code)
- {
- ShaderType type = stage switch
- {
- ShaderStage.Compute => ShaderType.ComputeShader,
- ShaderStage.Vertex => ShaderType.VertexShader,
- ShaderStage.TessellationControl => ShaderType.TessControlShader,
- ShaderStage.TessellationEvaluation => ShaderType.TessEvaluationShader,
- ShaderStage.Geometry => ShaderType.GeometryShader,
- ShaderStage.Fragment => ShaderType.FragmentShader,
- _ => ShaderType.VertexShader
- };
-
- Handle = GL.CreateShader(type);
- IsFragment = stage == ShaderStage.Fragment;
-
- GL.ShaderSource(Handle, code);
- GL.CompileShader(Handle);
- }
-
- public void Dispose()
- {
- if (Handle != 0)
- {
- GL.DeleteShader(Handle);
-
- Handle = 0;
- }
- }
- }
-}
diff --git a/Ryujinx.Graphics.Shader/BufferDescriptor.cs b/Ryujinx.Graphics.Shader/BufferDescriptor.cs
index a3af6e41..4ce8a896 100644
--- a/Ryujinx.Graphics.Shader/BufferDescriptor.cs
+++ b/Ryujinx.Graphics.Shader/BufferDescriptor.cs
@@ -2,6 +2,8 @@ namespace Ryujinx.Graphics.Shader
{
public struct BufferDescriptor
{
+ // New fields should be added to the end of the struct to keep disk shader cache compatibility.
+
public readonly int Binding;
public readonly int Slot;
public BufferUsageFlags Flags;
diff --git a/Ryujinx.Graphics.Shader/Decoders/Decoder.cs b/Ryujinx.Graphics.Shader/Decoders/Decoder.cs
index 6fa4055a..60ad540c 100644
--- a/Ryujinx.Graphics.Shader/Decoders/Decoder.cs
+++ b/Ryujinx.Graphics.Shader/Decoders/Decoder.cs
@@ -373,7 +373,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
for (int i = 0; i < cbOffsetsCount; i++)
{
- uint targetOffset = config.GpuAccessor.ConstantBuffer1Read(cbBaseOffset + i * 4);
+ uint targetOffset = config.ConstantBuffer1Read(cbBaseOffset + i * 4);
Block target = getBlock(baseOffset + targetOffset);
target.Predecessors.Add(block);
block.Successors.Add(target);
diff --git a/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/Ryujinx.Graphics.Shader/IGpuAccessor.cs
index b2512868..9c624d90 100644
--- a/Ryujinx.Graphics.Shader/IGpuAccessor.cs
+++ b/Ryujinx.Graphics.Shader/IGpuAccessor.cs
@@ -2,153 +2,341 @@
namespace Ryujinx.Graphics.Shader
{
+ /// <summary>
+ /// GPU state access interface.
+ /// </summary>
public interface IGpuAccessor
{
+ /// <summary>
+ /// Prints a log message.
+ /// </summary>
+ /// <param name="message">Message to print</param>
void Log(string message)
{
// No default log output.
}
+ /// <summary>
+ /// Reads data from the constant buffer 1.
+ /// </summary>
+ /// <param name="offset">Offset in bytes to read from</param>
+ /// <returns>Value at the given offset</returns>
uint ConstantBuffer1Read(int offset)
{
return 0;
}
+ /// <summary>
+ /// Gets a span of the specified memory location, containing shader code.
+ /// </summary>
+ /// <param name="address">GPU virtual address of the data</param>
+ /// <param name="minimumSize">Minimum size that the returned span may have</param>
+ /// <returns>Span of the memory location</returns>
ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize);
+ /// <summary>
+ /// Queries the binding number of a constant buffer.
+ /// </summary>
+ /// <param name="index">Constant buffer index</param>
+ /// <returns>Binding number</returns>
+ int QueryBindingConstantBuffer(int index)
+ {
+ return index;
+ }
+
+ /// <summary>
+ /// Queries the binding number of a storage buffer.
+ /// </summary>
+ /// <param name="index">Storage buffer index</param>
+ /// <returns>Binding number</returns>
+ int QueryBindingStorageBuffer(int index)
+ {
+ return index;
+ }
+
+ /// <summary>
+ /// Queries the binding number of a texture.
+ /// </summary>
+ /// <param name="index">Texture index</param>
+ /// <returns>Binding number</returns>
+ int QueryBindingTexture(int index)
+ {
+ return index;
+ }
+
+ /// <summary>
+ /// Queries the binding number of an image.
+ /// </summary>
+ /// <param name="index">Image index</param>
+ /// <returns>Binding number</returns>
+ int QueryBindingImage(int index)
+ {
+ return index;
+ }
+
+ /// <summary>
+ /// Queries Local Size X for compute shaders.
+ /// </summary>
+ /// <returns>Local Size X</returns>
int QueryComputeLocalSizeX()
{
return 1;
}
+ /// <summary>
+ /// Queries Local Size Y for compute shaders.
+ /// </summary>
+ /// <returns>Local Size Y</returns>
int QueryComputeLocalSizeY()
{
return 1;
}
+ /// <summary>
+ /// Queries Local Size Z for compute shaders.
+ /// </summary>
+ /// <returns>Local Size Z</returns>
int QueryComputeLocalSizeZ()
{
return 1;
}
+ /// <summary>
+ /// Queries Local Memory size in bytes for compute shaders.
+ /// </summary>
+ /// <returns>Local Memory size in bytes</returns>
int QueryComputeLocalMemorySize()
{
return 0x1000;
}
+ /// <summary>
+ /// Queries Shared Memory size in bytes for compute shaders.
+ /// </summary>
+ /// <returns>Shared Memory size in bytes</returns>
int QueryComputeSharedMemorySize()
{
return 0xc000;
}
+ /// <summary>
+ /// Queries Constant Buffer usage information.
+ /// </summary>
+ /// <returns>A mask where each bit set indicates a bound constant buffer</returns>
uint QueryConstantBufferUse()
{
return 0;
}
+ /// <summary>
+ /// Queries host about the presence of the FrontFacing built-in variable bug.
+ /// </summary>
+ /// <returns>True if the bug is present on the host device used, false otherwise</returns>
bool QueryHostHasFrontFacingBug()
{
return false;
}
+ /// <summary>
+ /// Queries host about the presence of the vector indexing bug.
+ /// </summary>
+ /// <returns>True if the bug is present on the host device used, false otherwise</returns>
bool QueryHostHasVectorIndexingBug()
{
return false;
}
+ /// <summary>
+ /// Queries host storage buffer alignment required.
+ /// </summary>
+ /// <returns>Host storage buffer alignment in bytes</returns>
int QueryHostStorageBufferOffsetAlignment()
{
return 16;
}
+ /// <summary>
+ /// Queries host support for texture formats with BGRA component order (such as BGRA8).
+ /// </summary>
+ /// <returns>True if BGRA formats are supported, false otherwise</returns>
bool QueryHostSupportsBgraFormat()
{
return true;
}
+ /// <summary>
+ /// Queries host support for fragment shader ordering critical sections on the shader code.
+ /// </summary>
+ /// <returns>True if fragment shader interlock is supported, false otherwise</returns>
bool QueryHostSupportsFragmentShaderInterlock()
{
return true;
}
+ /// <summary>
+ /// Queries host support for fragment shader ordering scoped critical sections on the shader code.
+ /// </summary>
+ /// <returns>True if fragment shader ordering is supported, false otherwise</returns>
bool QueryHostSupportsFragmentShaderOrderingIntel()
{
return false;
}
+ /// <summary>
+ /// Queries host support for readable images without a explicit format declaration on the shader.
+ /// </summary>
+ /// <returns>True if formatted image load is supported, false otherwise</returns>
bool QueryHostSupportsImageLoadFormatted()
{
return true;
}
+ /// <summary>
+ /// Queries host GPU non-constant texture offset support.
+ /// </summary>
+ /// <returns>True if the GPU and driver supports non-constant texture offsets, false otherwise</returns>
bool QueryHostSupportsNonConstantTextureOffset()
{
return true;
}
+ /// <summary>
+ /// Queries host GPU shader ballot support.
+ /// </summary>
+ /// <returns>True if the GPU and driver supports shader ballot, false otherwise</returns>
bool QueryHostSupportsShaderBallot()
{
return true;
}
+ /// <summary>
+ /// Queries host GPU texture shadow LOD support.
+ /// </summary>
+ /// <returns>True if the GPU and driver supports texture shadow LOD, false otherwise</returns>
bool QueryHostSupportsTextureShadowLod()
{
return true;
}
+ /// <summary>
+ /// Queries sampler type information.
+ /// </summary>
+ /// <param name="handle">Texture handle</param>
+ /// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
+ /// <returns>The sampler type value for the given handle</returns>
SamplerType QuerySamplerType(int handle, int cbufSlot = -1)
{
return SamplerType.Texture2D;
}
- bool QueryIsTextureRectangle(int handle, int cbufSlot = -1)
+ /// <summary>
+ /// Queries texture coordinate normalization information.
+ /// </summary>
+ /// <param name="handle">Texture handle</param>
+ /// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
+ /// <returns>True if the coordinates are normalized, false otherwise</returns>
+ bool QueryTextureCoordNormalized(int handle, int cbufSlot = -1)
{
return false;
}
+ /// <summary>
+ /// Queries current primitive topology for geometry shaders.
+ /// </summary>
+ /// <returns>Current primitive topology</returns>
InputTopology QueryPrimitiveTopology()
{
return InputTopology.Points;
}
+ /// <summary>
+ /// Queries the tessellation evaluation shader primitive winding order.
+ /// </summary>
+ /// <returns>True if the primitive winding order is clockwise, false if counter-clockwise</returns>
bool QueryTessCw()
{
return false;
}
+ /// <summary>
+ /// Queries the tessellation evaluation shader abstract patch type.
+ /// </summary>
+ /// <returns>Abstract patch type</returns>
TessPatchType QueryTessPatchType()
{
return TessPatchType.Triangles;
}
+ /// <summary>
+ /// Queries the tessellation evaluation shader spacing between tessellated vertices of the patch.
+ /// </summary>
+ /// <returns>Spacing between tessellated vertices of the patch</returns>
TessSpacing QueryTessSpacing()
{
return TessSpacing.EqualSpacing;
}
+ /// <summary>
+ /// Queries texture format information, for shaders using image load or store.
+ /// </summary>
+ /// <remarks>
+ /// This only returns non-compressed color formats.
+ /// If the format of the texture is a compressed, depth or unsupported format, then a default value is returned.
+ /// </remarks>
+ /// <param name="handle">Texture handle</param>
+ /// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
+ /// <returns>Color format of the non-compressed texture</returns>
TextureFormat QueryTextureFormat(int handle, int cbufSlot = -1)
{
return TextureFormat.R8G8B8A8Unorm;
}
+ /// <summary>
+ /// Queries transform feedback enable state.
+ /// </summary>
+ /// <returns>True if the shader uses transform feedback, false otherwise</returns>
bool QueryTransformFeedbackEnabled()
{
return false;
}
+ /// <summary>
+ /// Queries the varying locations that should be written to the transform feedback buffer.
+ /// </summary>
+ /// <param name="bufferIndex">Index of the transform feedback buffer</param>
+ /// <returns>Varying locations for the specified buffer</returns>
ReadOnlySpan<byte> QueryTransformFeedbackVaryingLocations(int bufferIndex)
{
return ReadOnlySpan<byte>.Empty;
}
+ /// <summary>
+ /// Queries the stride (in bytes) of the per vertex data written into the transform feedback buffer.
+ /// </summary>
+ /// <param name="bufferIndex">Index of the transform feedback buffer</param>
+ /// <returns>Stride for the specified buffer</returns>
int QueryTransformFeedbackStride(int bufferIndex)
{
return 0;
}
+ /// <summary>
+ /// Queries if host state forces early depth testing.
+ /// </summary>
+ /// <returns>True if early depth testing is forced</returns>
bool QueryEarlyZForce()
{
return false;
}
+
+ /// <summary>
+ /// Registers a texture used by the shader.
+ /// </summary>
+ /// <param name="handle">Texture handle word offset</param>
+ /// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
+ void RegisterTexture(int handle, int cbufSlot)
+ {
+ // Only useful when recording information for a disk shader cache.
+ }
}
}
diff --git a/Ryujinx.Graphics.Shader/ShaderProgram.cs b/Ryujinx.Graphics.Shader/ShaderProgram.cs
index dd87b67d..29fff21e 100644
--- a/Ryujinx.Graphics.Shader/ShaderProgram.cs
+++ b/Ryujinx.Graphics.Shader/ShaderProgram.cs
@@ -1,25 +1,28 @@
+using Ryujinx.Graphics.Shader.Translation;
using System;
namespace Ryujinx.Graphics.Shader
{
public class ShaderProgram
{
- public ShaderStage Stage { get; }
+ public ShaderProgramInfo Info { get; }
+ public TargetLanguage Language { get; }
public string Code { get; private set; }
public byte[] BinaryCode { get; }
- private ShaderProgram(ShaderStage stage)
+ private ShaderProgram(ShaderProgramInfo info, TargetLanguage language)
{
- Stage = stage;
+ Info = info;
+ Language = language;
}
- public ShaderProgram(ShaderStage stage, string code) : this(stage)
+ public ShaderProgram(ShaderProgramInfo info, TargetLanguage language, string code) : this(info, language)
{
Code = code;
}
- public ShaderProgram(ShaderStage stage, byte[] binaryCode) : this(stage)
+ public ShaderProgram(ShaderProgramInfo info, TargetLanguage language, byte[] binaryCode) : this(info, language)
{
BinaryCode = binaryCode;
}
diff --git a/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs b/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs
index d1c1b945..659f6167 100644
--- a/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs
+++ b/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs
@@ -10,6 +10,7 @@ namespace Ryujinx.Graphics.Shader
public ReadOnlyCollection<TextureDescriptor> Textures { get; }
public ReadOnlyCollection<TextureDescriptor> Images { get; }
+ public ShaderStage Stage { get; }
public bool UsesInstanceId { get; }
public bool UsesRtLayer { get; }
public byte ClipDistancesWritten { get; }
@@ -20,6 +21,7 @@ namespace Ryujinx.Graphics.Shader
BufferDescriptor[] sBuffers,
TextureDescriptor[] textures,
TextureDescriptor[] images,
+ ShaderStage stage,
bool usesInstanceId,
bool usesRtLayer,
byte clipDistancesWritten,
@@ -30,6 +32,7 @@ namespace Ryujinx.Graphics.Shader
Textures = Array.AsReadOnly(textures);
Images = Array.AsReadOnly(images);
+ Stage = stage;
UsesInstanceId = usesInstanceId;
UsesRtLayer = usesRtLayer;
ClipDistancesWritten = clipDistancesWritten;
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
index 31c71f20..ce79f3b8 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
@@ -74,7 +74,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
for (int j = 0; j < locations.Length; j++)
{
byte location = locations[j];
- if (location < 0x80)
+ if (location < 0xc0)
{
context.Info.TransformFeedbackOutputs[location] = new TransformFeedbackOutput(tfbIndex, j * 4, stride);
}
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
index 933f265f..2dc23964 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
@@ -30,7 +30,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
Functions = new List<StructuredFunction>();
- TransformFeedbackOutputs = new TransformFeedbackOutput[0x80];
+ TransformFeedbackOutputs = new TransformFeedbackOutput[0xc0];
}
}
} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/TextureDescriptor.cs b/Ryujinx.Graphics.Shader/TextureDescriptor.cs
index b7b0ae12..85ea9adb 100644
--- a/Ryujinx.Graphics.Shader/TextureDescriptor.cs
+++ b/Ryujinx.Graphics.Shader/TextureDescriptor.cs
@@ -2,6 +2,8 @@ namespace Ryujinx.Graphics.Shader
{
public struct TextureDescriptor
{
+ // New fields should be added to the end of the struct to keep disk shader cache compatibility.
+
public readonly int Binding;
public readonly SamplerType Type;
diff --git a/Ryujinx.Graphics.Shader/Translation/Rewriter.cs b/Ryujinx.Graphics.Shader/Translation/Rewriter.cs
index 910faf1c..e9b073ab 100644
--- a/Ryujinx.Graphics.Shader/Translation/Rewriter.cs
+++ b/Ryujinx.Graphics.Shader/Translation/Rewriter.cs
@@ -164,9 +164,9 @@ namespace Ryujinx.Graphics.Shader.Translation
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
- bool isRect = !isBindless && config.GpuAccessor.QueryIsTextureRectangle(texOp.Handle, texOp.CbufSlot);
+ bool isCoordNormalized = !isBindless && config.GpuAccessor.QueryTextureCoordNormalized(texOp.Handle, texOp.CbufSlot);
- if (!(hasInvalidOffset || isRect))
+ if (!hasInvalidOffset && isCoordNormalized)
{
return node;
}
@@ -263,7 +263,7 @@ namespace Ryujinx.Graphics.Shader.Translation
hasInvalidOffset &= !areAllOffsetsConstant;
- if (!(hasInvalidOffset || isRect))
+ if (!hasInvalidOffset && isCoordNormalized)
{
return node;
}
@@ -300,15 +300,17 @@ namespace Ryujinx.Graphics.Shader.Translation
return res;
}
- // Emulate texture rectangle by normalizing the coordinates on the shader.
- // When sampler*Rect is used, the coords are expected to the in the [0, W or H] range,
+ // Emulate non-normalized coordinates by normalizing the coordinates on the shader.
+ // Without normalization, the coordinates are expected to the in the [0, W or H] range,
// and otherwise, it is expected to be in the [0, 1] range.
// We normalize by dividing the coords by the texture size.
- if (isRect && !intCoords)
+ if (!isCoordNormalized && !intCoords)
{
config.SetUsedFeature(FeatureFlags.IntegerSampling);
- for (int index = 0; index < coordsCount; index++)
+ int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
+
+ for (int index = 0; index < normCoordsCount; index++)
{
Operand coordSize = Local();
diff --git a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
index 6bb045ec..23b8b951 100644
--- a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
+++ b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
@@ -41,9 +41,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public FeatureFlags UsedFeatures { get; private set; }
- public HashSet<int> TextureHandlesForCache { get; }
-
- private readonly TranslationCounts _counts;
+ public int Cb1DataSize { get; private set; }
public bool NextUsesFixedFuncAttributes { get; private set; }
public int UsedInputAttributes { get; private set; }
@@ -109,21 +107,22 @@ namespace Ryujinx.Graphics.Shader.Translation
private TextureDescriptor[] _cachedTextureDescriptors;
private TextureDescriptor[] _cachedImageDescriptors;
- public int FirstConstantBufferBinding { get; private set; }
- public int FirstStorageBufferBinding { get; private set; }
+ private int _firstConstantBufferBinding;
+ private int _firstStorageBufferBinding;
+
+ public int FirstConstantBufferBinding => _firstConstantBufferBinding;
+ public int FirstStorageBufferBinding => _firstStorageBufferBinding;
- public ShaderConfig(IGpuAccessor gpuAccessor, TranslationOptions options, TranslationCounts counts)
+ public ShaderConfig(IGpuAccessor gpuAccessor, TranslationOptions options)
{
- Stage = ShaderStage.Compute;
- GpuAccessor = gpuAccessor;
- Options = options;
- _counts = counts;
- TextureHandlesForCache = new HashSet<int>();
- _usedTextures = new Dictionary<TextureInfo, TextureMeta>();
- _usedImages = new Dictionary<TextureInfo, TextureMeta>();
+ Stage = ShaderStage.Compute;
+ GpuAccessor = gpuAccessor;
+ Options = options;
+ _usedTextures = new Dictionary<TextureInfo, TextureMeta>();
+ _usedImages = new Dictionary<TextureInfo, TextureMeta>();
}
- public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationOptions options, TranslationCounts counts) : this(gpuAccessor, options, counts)
+ public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationOptions options) : this(gpuAccessor, options)
{
Stage = header.Stage;
GpPassthrough = header.Stage == ShaderStage.Geometry && header.GpPassthrough;
@@ -144,6 +143,16 @@ namespace Ryujinx.Graphics.Shader.Translation
return BitOperations.PopCount((uint)OmapTargets) + 1;
}
+ public uint ConstantBuffer1Read(int offset)
+ {
+ if (Cb1DataSize < offset + 4)
+ {
+ Cb1DataSize = offset + 4;
+ }
+
+ return GpuAccessor.ConstantBuffer1Read(offset);
+ }
+
public TextureFormat GetTextureFormat(int handle, int cbufSlot = -1)
{
// When the formatted load extension is supported, we don't need to
@@ -197,8 +206,6 @@ namespace Ryujinx.Graphics.Shader.Translation
ClipDistancesWritten |= other.ClipDistancesWritten;
UsedFeatures |= other.UsedFeatures;
- TextureHandlesForCache.UnionWith(other.TextureHandlesForCache);
-
UsedInputAttributes |= other.UsedInputAttributes;
UsedOutputAttributes |= other.UsedOutputAttributes;
_usedConstantBuffers |= other._usedConstantBuffers;
@@ -391,6 +398,8 @@ namespace Ryujinx.Graphics.Shader.Translation
bool intCoords = flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureSize;
SetUsedTextureOrImage(_usedTextures, cbufSlot, handle, type, TextureFormat.Unknown, intCoords, false, accurateType, coherent);
}
+
+ GpuAccessor.RegisterTexture(handle, cbufSlot);
}
private void SetUsedTextureOrImage(
@@ -485,13 +494,12 @@ namespace Ryujinx.Graphics.Shader.Translation
usedMask |= (int)GpuAccessor.QueryConstantBufferUse();
}
- FirstConstantBufferBinding = _counts.UniformBuffersCount;
-
return _cachedConstantBufferDescriptors = GetBufferDescriptors(
usedMask,
0,
UsedFeatures.HasFlag(FeatureFlags.CbIndexing),
- _counts.IncrementUniformBuffersCount);
+ out _firstConstantBufferBinding,
+ GpuAccessor.QueryBindingConstantBuffer);
}
public BufferDescriptor[] GetStorageBufferDescriptors()
@@ -501,21 +509,23 @@ namespace Ryujinx.Graphics.Shader.Translation
return _cachedStorageBufferDescriptors;
}
- FirstStorageBufferBinding = _counts.StorageBuffersCount;
-
return _cachedStorageBufferDescriptors = GetBufferDescriptors(
_usedStorageBuffers,
_usedStorageBuffersWrite,
true,
- _counts.IncrementStorageBuffersCount);
+ out _firstStorageBufferBinding,
+ GpuAccessor.QueryBindingStorageBuffer);
}
private static BufferDescriptor[] GetBufferDescriptors(
int usedMask,
int writtenMask,
bool isArray,
- Func<int> getBindingCallback)
+ out int firstBinding,
+ Func<int, int> getBindingCallback)
{
+ firstBinding = 0;
+ bool hasFirstBinding = false;
var descriptors = new BufferDescriptor[BitOperations.PopCount((uint)usedMask)];
int lastSlot = -1;
@@ -529,13 +539,25 @@ namespace Ryujinx.Graphics.Shader.Translation
// The next array entries also consumes bindings, even if they are unused.
for (int j = lastSlot + 1; j < slot; j++)
{
- getBindingCallback();
+ int binding = getBindingCallback(j);
+
+ if (!hasFirstBinding)
+ {
+ firstBinding = binding;
+ hasFirstBinding = true;
+ }
}
}
lastSlot = slot;
- descriptors[i] = new BufferDescriptor(getBindingCallback(), slot);
+ descriptors[i] = new BufferDescriptor(getBindingCallback(slot), slot);
+
+ if (!hasFirstBinding)
+ {
+ firstBinding = descriptors[i].Binding;
+ hasFirstBinding = true;
+ }
if ((writtenMask & (1 << slot)) != 0)
{
@@ -550,15 +572,15 @@ namespace Ryujinx.Graphics.Shader.Translation
public TextureDescriptor[] GetTextureDescriptors()
{
- return _cachedTextureDescriptors ??= GetTextureOrImageDescriptors(_usedTextures, _counts.IncrementTexturesCount);
+ return _cachedTextureDescriptors ??= GetTextureOrImageDescriptors(_usedTextures, GpuAccessor.QueryBindingTexture);
}
public TextureDescriptor[] GetImageDescriptors()
{
- return _cachedImageDescriptors ??= GetTextureOrImageDescriptors(_usedImages, _counts.IncrementImagesCount);
+ return _cachedImageDescriptors ??= GetTextureOrImageDescriptors(_usedImages, GpuAccessor.QueryBindingImage);
}
- private static TextureDescriptor[] GetTextureOrImageDescriptors(Dictionary<TextureInfo, TextureMeta> dict, Func<int> getBindingCallback)
+ private static TextureDescriptor[] GetTextureOrImageDescriptors(Dictionary<TextureInfo, TextureMeta> dict, Func<int, int> getBindingCallback)
{
var descriptors = new TextureDescriptor[dict.Count];
@@ -568,7 +590,7 @@ namespace Ryujinx.Graphics.Shader.Translation
var info = kv.Key;
var meta = kv.Value;
- int binding = getBindingCallback();
+ int binding = getBindingCallback(i);
descriptors[i] = new TextureDescriptor(binding, meta.Type, info.Format, info.CbufSlot, info.Handle);
descriptors[i].SetFlag(meta.UsageFlags);
diff --git a/Ryujinx.Graphics.Shader/Translation/TranslationCounts.cs b/Ryujinx.Graphics.Shader/Translation/TranslationCounts.cs
deleted file mode 100644
index 6751d7ea..00000000
--- a/Ryujinx.Graphics.Shader/Translation/TranslationCounts.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-namespace Ryujinx.Graphics.Shader.Translation
-{
- public class TranslationCounts
- {
- public int UniformBuffersCount { get; private set; }
- public int StorageBuffersCount { get; private set; }
- public int TexturesCount { get; private set; }
- public int ImagesCount { get; private set; }
-
- public TranslationCounts()
- {
- // The first binding is reserved for the support buffer.
- UniformBuffersCount = 1;
- }
-
- internal int IncrementUniformBuffersCount()
- {
- return UniformBuffersCount++;
- }
-
- internal int IncrementStorageBuffersCount()
- {
- return StorageBuffersCount++;
- }
-
- internal int IncrementTexturesCount()
- {
- return TexturesCount++;
- }
-
- internal int IncrementImagesCount()
- {
- return ImagesCount++;
- }
- }
-}
diff --git a/Ryujinx.Graphics.Shader/Translation/Translator.cs b/Ryujinx.Graphics.Shader/Translation/Translator.cs
index 5119dfb6..e1614e66 100644
--- a/Ryujinx.Graphics.Shader/Translation/Translator.cs
+++ b/Ryujinx.Graphics.Shader/Translation/Translator.cs
@@ -25,18 +25,12 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
- public static TranslatorContext CreateContext(
- ulong address,
- IGpuAccessor gpuAccessor,
- TranslationOptions options,
- TranslationCounts counts = null)
+ public static TranslatorContext CreateContext(ulong address, IGpuAccessor gpuAccessor, TranslationOptions options)
{
- counts ??= new TranslationCounts();
-
- return DecodeShader(address, gpuAccessor, options, counts);
+ return DecodeShader(address, gpuAccessor, options);
}
- internal static ShaderProgram Translate(FunctionCode[] functions, ShaderConfig config, out ShaderProgramInfo shaderProgramInfo)
+ internal static ShaderProgram Translate(FunctionCode[] functions, ShaderConfig config)
{
var cfgs = new ControlFlowGraph[functions.Length];
var frus = new RegisterUsage.FunctionRegisterUsage[functions.Length];
@@ -87,31 +81,25 @@ namespace Ryujinx.Graphics.Shader.Translation
StructuredProgramInfo sInfo = StructuredProgram.MakeStructuredProgram(funcs, config);
- ShaderProgram program;
-
- switch (config.Options.TargetLanguage)
- {
- case TargetLanguage.Glsl:
- program = new ShaderProgram(config.Stage, GlslGenerator.Generate(sInfo, config));
- break;
- default:
- throw new NotImplementedException(config.Options.TargetLanguage.ToString());
- }
-
- shaderProgramInfo = new ShaderProgramInfo(
+ ShaderProgramInfo info = new ShaderProgramInfo(
config.GetConstantBufferDescriptors(),
config.GetStorageBufferDescriptors(),
config.GetTextureDescriptors(),
config.GetImageDescriptors(),
+ config.Stage,
config.UsedFeatures.HasFlag(FeatureFlags.InstanceId),
config.UsedFeatures.HasFlag(FeatureFlags.RtLayer),
config.ClipDistancesWritten,
config.OmapTargets);
- return program;
+ return config.Options.TargetLanguage switch
+ {
+ TargetLanguage.Glsl => new ShaderProgram(info, TargetLanguage.Glsl, GlslGenerator.Generate(sInfo, config)),
+ _ => throw new NotImplementedException(config.Options.TargetLanguage.ToString())
+ };
}
- private static TranslatorContext DecodeShader(ulong address, IGpuAccessor gpuAccessor, TranslationOptions options, TranslationCounts counts)
+ private static TranslatorContext DecodeShader(ulong address, IGpuAccessor gpuAccessor, TranslationOptions options)
{
ShaderConfig config;
DecodedProgram program;
@@ -119,13 +107,13 @@ namespace Ryujinx.Graphics.Shader.Translation
if ((options.Flags & TranslationFlags.Compute) != 0)
{
- config = new ShaderConfig(gpuAccessor, options, counts);
+ config = new ShaderConfig(gpuAccessor, options);
program = Decoder.Decode(config, address);
}
else
{
- config = new ShaderConfig(new ShaderHeader(gpuAccessor, address), gpuAccessor, options, counts);
+ config = new ShaderConfig(new ShaderHeader(gpuAccessor, address), gpuAccessor, options);
program = Decoder.Decode(config, address + HeaderSize);
}
@@ -138,20 +126,6 @@ namespace Ryujinx.Graphics.Shader.Translation
{
maxEndAddress = block.EndAddress;
}
-
- if (!config.UsedFeatures.HasFlag(FeatureFlags.Bindless))
- {
- for (int index = 0; index < block.OpCodes.Count; index++)
- {
- InstOp op = block.OpCodes[index];
-
- if (op.Props.HasFlag(InstProps.Tex))
- {
- int tidB = (int)((op.RawOpCode >> 36) & 0x1fff);
- config.TextureHandlesForCache.Add(tidB);
- }
- }
- }
}
}
diff --git a/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
index b4e61cb6..8900f9fe 100644
--- a/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
+++ b/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
@@ -16,10 +16,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public ShaderStage Stage => _config.Stage;
public int Size => _config.Size;
-
- public FeatureFlags UsedFeatures => _config.UsedFeatures;
-
- public HashSet<int> TextureHandlesForCache => _config.TextureHandlesForCache;
+ public int Cb1DataSize => _config.Cb1DataSize;
public IGpuAccessor GpuAccessor => _config.GpuAccessor;
@@ -129,16 +126,13 @@ namespace Ryujinx.Graphics.Shader.Translation
return output;
}
- public ShaderProgram Translate(
- out ShaderProgramInfo shaderProgramInfo,
- TranslatorContext nextStage = null,
- TranslatorContext other = null)
+ public void SetNextStage(TranslatorContext nextStage)
{
- if (nextStage != null)
- {
- _config.MergeFromtNextStage(nextStage._config);
- }
+ _config.MergeFromtNextStage(nextStage._config);
+ }
+ public ShaderProgram Translate(TranslatorContext other = null)
+ {
FunctionCode[] code = EmitShader(_program, _config, initializeOutputs: other == null, out _);
if (other != null)
@@ -152,7 +146,7 @@ namespace Ryujinx.Graphics.Shader.Translation
_config.InheritFrom(other._config);
}
- return Translator.Translate(code, _config, out shaderProgramInfo);
+ return Translator.Translate(code, _config);
}
}
}
diff --git a/Ryujinx.Headless.SDL2/WindowBase.cs b/Ryujinx.Headless.SDL2/WindowBase.cs
index 3fbd9bc3..74eb0d31 100644
--- a/Ryujinx.Headless.SDL2/WindowBase.cs
+++ b/Ryujinx.Headless.SDL2/WindowBase.cs
@@ -43,6 +43,7 @@ namespace Ryujinx.Headless.SDL2
private GraphicsDebugLevel _glLogLevel;
private readonly Stopwatch _chrono;
private readonly long _ticksPerFrame;
+ private readonly CancellationTokenSource _gpuCancellationTokenSource;
private readonly ManualResetEvent _exitEvent;
private long _ticks;
@@ -66,6 +67,7 @@ namespace Ryujinx.Headless.SDL2
_glLogLevel = glLogLevel;
_chrono = new Stopwatch();
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
+ _gpuCancellationTokenSource = new CancellationTokenSource();
_exitEvent = new ManualResetEvent(false);
_aspectRatio = aspectRatio;
_enableMouse = enableMouse;
@@ -162,7 +164,7 @@ namespace Ryujinx.Headless.SDL2
Device.Gpu.Renderer.RunLoop(() =>
{
- Device.Gpu.InitializeShaderCache();
+ Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
Translator.IsReadyForTranslation.Set();
while (_isActive)
@@ -223,6 +225,8 @@ namespace Ryujinx.Headless.SDL2
return;
}
+ _gpuCancellationTokenSource.Cancel();
+
_isStopped = true;
_isActive = false;
diff --git a/Ryujinx.ShaderTools/Program.cs b/Ryujinx.ShaderTools/Program.cs
index 43b9494e..746b780c 100644
--- a/Ryujinx.ShaderTools/Program.cs
+++ b/Ryujinx.ShaderTools/Program.cs
@@ -55,7 +55,7 @@ namespace Ryujinx.ShaderTools
TranslationOptions translationOptions = new TranslationOptions(options.TargetLanguage, options.TargetApi, flags);
- ShaderProgram program = Translator.CreateContext(0, new GpuAccessor(data), translationOptions).Translate(out _);
+ ShaderProgram program = Translator.CreateContext(0, new GpuAccessor(data), translationOptions).Translate();
if (options.OutputPath == null)
{
diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs
index cdbf5d6c..12340308 100644
--- a/Ryujinx/Ui/RendererWidgetBase.cs
+++ b/Ryujinx/Ui/RendererWidgetBase.cs
@@ -60,6 +60,8 @@ namespace Ryujinx.Ui
private readonly ManualResetEvent _exitEvent;
+ private readonly CancellationTokenSource _gpuCancellationTokenSource;
+
// Hide Cursor
const int CursorHideIdleTime = 8; // seconds
private static readonly Cursor _invisibleCursor = new Cursor(Display.Default, CursorType.BlankCursor);
@@ -105,6 +107,8 @@ namespace Ryujinx.Ui
_exitEvent = new ManualResetEvent(false);
+ _gpuCancellationTokenSource = new CancellationTokenSource();
+
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
_lastCursorMoveTime = Stopwatch.GetTimestamp();
@@ -387,7 +391,7 @@ namespace Ryujinx.Ui
Device.Gpu.Renderer.RunLoop(() =>
{
Device.Gpu.SetGpuThread();
- Device.Gpu.InitializeShaderCache();
+ Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
Translator.IsReadyForTranslation.Set();
(Toplevel as MainWindow)?.ActivatePauseMenu();
@@ -499,6 +503,8 @@ namespace Ryujinx.Ui
return;
}
+ _gpuCancellationTokenSource.Cancel();
+
_isStopped = true;
_isActive = false;
@@ -603,7 +609,7 @@ namespace Ryujinx.Ui
if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ToggleMute) &&
!_prevHotkeyState.HasFlag(KeyboardHotkeyState.ToggleMute))
{
- if (Device.IsAudioMuted())
+ if (Device.IsAudioMuted())
{
Device.SetVolume(ConfigurationState.Instance.System.AudioVolume);
}
diff --git a/Ryujinx/Ui/Widgets/GameTableContextMenu.cs b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
index 0e3b4892..8bf8af36 100644
--- a/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
+++ b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
@@ -172,7 +172,7 @@ namespace Ryujinx.Ui.Widgets
ResponseType response = (ResponseType)fileChooser.Run();
string destination = fileChooser.Filename;
-
+
fileChooser.Dispose();
if (response == ResponseType.Accept)
@@ -490,7 +490,7 @@ namespace Ryujinx.Ui.Widgets
private void OpenPtcDir_Clicked(object sender, EventArgs args)
{
string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu");
-
+
string mainPath = System.IO.Path.Combine(ptcDir, "0");
string backupPath = System.IO.Path.Combine(ptcDir, "1");
@@ -515,7 +515,7 @@ namespace Ryujinx.Ui.Widgets
OpenHelper.OpenFolder(shaderCacheDir);
}
-
+
private void PurgePtcCache_Clicked(object sender, EventArgs args)
{
DirectoryInfo mainDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "0"));
@@ -526,7 +526,7 @@ namespace Ryujinx.Ui.Widgets
List<FileInfo> cacheFiles = new List<FileInfo>();
if (mainDir.Exists)
- {
+ {
cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
}
@@ -539,9 +539,9 @@ namespace Ryujinx.Ui.Widgets
{
foreach (FileInfo file in cacheFiles)
{
- try
- {
- file.Delete();
+ try
+ {
+ file.Delete();
}
catch(Exception e)
{
@@ -557,18 +557,21 @@ namespace Ryujinx.Ui.Widgets
{
DirectoryInfo shaderCacheDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader"));
- MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
+ using MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
- List<DirectoryInfo> cacheDirectory = new List<DirectoryInfo>();
+ List<DirectoryInfo> oldCacheDirectories = new List<DirectoryInfo>();
+ List<FileInfo> newCacheFiles = new List<FileInfo>();
if (shaderCacheDir.Exists)
{
- cacheDirectory.AddRange(shaderCacheDir.EnumerateDirectories("*"));
+ oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*"));
+ newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc"));
+ newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data"));
}
- if (cacheDirectory.Count > 0 && warningDialog.Run() == (int)ResponseType.Yes)
+ if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0) && warningDialog.Run() == (int)ResponseType.Yes)
{
- foreach (DirectoryInfo directory in cacheDirectory)
+ foreach (DirectoryInfo directory in oldCacheDirectories)
{
try
{
@@ -579,9 +582,19 @@ namespace Ryujinx.Ui.Widgets
GtkDialog.CreateErrorDialog($"Error purging shader cache at {directory.Name}: {e}");
}
}
- }
- warningDialog.Dispose();
+ foreach (FileInfo file in newCacheFiles)
+ {
+ try
+ {
+ file.Delete();
+ }
+ catch (Exception e)
+ {
+ GtkDialog.CreateErrorDialog($"Error purging shader cache at {file.Name}: {e}");
+ }
+ }
+ }
}
}
}