aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Gpu
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Graphics.Gpu')
-rw-r--r--Ryujinx.Graphics.Gpu/Constants.cs22
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs4
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs1
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs17
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs2
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs7
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs287
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs14
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs13
-rw-r--r--Ryujinx.Graphics.Gpu/GraphicsConfig.cs14
-rw-r--r--Ryujinx.Graphics.Gpu/Image/Texture.cs29
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs96
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs76
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs1
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/BufferManager.cs14
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs17
-rw-r--r--Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj4
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs617
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs273
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs168
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheGraphicsApi.cs38
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheHashType.cs13
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheManifestHeader.cs97
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuAccessorHeader.cs67
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuStateFlags.cs10
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntry.cs88
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntryHeader.cs69
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheHeader.cs42
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheTransformFeedbackHeader.cs38
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestTextureDescriptor.cs41
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntry.cs222
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntryHeader.cs114
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheHeader.cs42
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs258
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/Cache/TransformFeedbackDescriptorOld.cs19
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs2
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs58
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs8
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs183
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs31
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs146
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs49
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs61
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs150
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs80
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs167
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs82
47 files changed, 1224 insertions, 2627 deletions
diff --git a/Ryujinx.Graphics.Gpu/Constants.cs b/Ryujinx.Graphics.Gpu/Constants.cs
index 026d12a9..d580049f 100644
--- a/Ryujinx.Graphics.Gpu/Constants.cs
+++ b/Ryujinx.Graphics.Gpu/Constants.cs
@@ -18,7 +18,7 @@ namespace Ryujinx.Graphics.Gpu
/// Maximum number of compute storage buffers.
/// </summary>
/// <remarks>
- /// The maximum number of storage buffers is API limited, the hardware supports a unlimited amount.
+ /// The maximum number of storage buffers is API limited, the hardware supports an unlimited amount.
/// </remarks>
public const int TotalCpStorageBuffers = 16;
@@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.Gpu
/// Maximum number of graphics storage buffers.
/// </summary>
/// <remarks>
- /// The maximum number of storage buffers is API limited, the hardware supports a unlimited amount.
+ /// The maximum number of storage buffers is API limited, the hardware supports an unlimited amount.
/// </remarks>
public const int TotalGpStorageBuffers = 16;
@@ -41,6 +41,22 @@ namespace Ryujinx.Graphics.Gpu
public const int TotalTransformFeedbackBuffers = 4;
/// <summary>
+ /// Maximum number of textures on a single shader stage.
+ /// </summary>
+ /// <remarks>
+ /// The maximum number of textures is API limited, the hardware supports an unlimited amount.
+ /// </remarks>
+ public const int TotalTextures = 32;
+
+ /// <summary>
+ /// Maximum number of images on a single shader stage.
+ /// </summary>
+ /// <remarks>
+ /// The maximum number of images is API limited, the hardware supports an unlimited amount.
+ /// </remarks>
+ public const int TotalImages = 8;
+
+ /// <summary>
/// Maximum number of render target color buffers.
/// </summary>
public const int TotalRenderTargets = 8;
@@ -53,7 +69,7 @@ namespace Ryujinx.Graphics.Gpu
/// <summary>
/// Maximum number of vertex attributes.
/// </summary>
- public const int TotalVertexAttribs = 16;
+ public const int TotalVertexAttribs = 16; // FIXME: Should be 32, but OpenGL only supports 16.
/// <summary>
/// Maximum number of vertex buffers.
diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs b/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs
index a1a9b481..84de779c 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs
@@ -238,8 +238,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
_channel.TextureManager.SetComputeMaxBindings(maxTextureBinding, maxImageBinding);
// Should never return false for mismatching spec state, since the shader was fetched above.
- _channel.TextureManager.CommitComputeBindings(cs.SpecializationState);
-
+ _channel.TextureManager.CommitComputeBindings(cs.SpecializationState);
+
_channel.BufferManager.CommitComputeBindings();
_context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth);
diff --git a/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs b/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs
index 133b3075..5814eeb7 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs
@@ -147,6 +147,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
int xCount = (int)_state.State.LineLengthIn;
int yCount = (int)_state.State.LineCount;
+ _3dEngine.CreatePendingSyncs();
_3dEngine.FlushUboDirty();
if (copy2D)
diff --git a/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs b/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs
index 686c2a9b..9cb97983 100644
--- a/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs
@@ -15,6 +15,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
private readonly GPFifoProcessor _parent;
private readonly DeviceState<GPFifoClassState> _state;
+ private int _previousSubChannel;
+ private bool _createSyncPending;
+
private const int MacrosCount = 0x80;
// Note: The size of the macro memory is unknown, we just make
@@ -49,6 +52,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
}
/// <summary>
+ /// Create any syncs from WaitForIdle command that are currently pending.
+ /// </summary>
+ public void CreatePendingSyncs()
+ {
+ if (_createSyncPending)
+ {
+ _createSyncPending = false;
+ _context.CreateHostSyncIfNeeded(false);
+ }
+ }
+
+ /// <summary>
/// Reads data from the class registers.
/// </summary>
/// <param name="offset">Register byte offset</param>
@@ -158,7 +173,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
_parent.PerformDeferredDraws();
_context.Renderer.Pipeline.Barrier();
- _context.CreateHostSyncIfNeeded(false);
+ _createSyncPending = true;
}
/// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs b/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs
index 096b795c..3fb3feee 100644
--- a/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs
@@ -65,7 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
_channel = channel;
_fifoClass = new GPFifoClass(context, this);
- _3dClass = new ThreedClass(context, channel);
+ _3dClass = new ThreedClass(context, channel, _fifoClass);
_computeClass = new ComputeClass(context, channel, _3dClass);
_i2mClass = new InlineToMemoryClass(context, channel);
_2dClass = new TwodClass(channel);
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
index f90baf99..e6a64205 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
@@ -559,7 +559,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
scissorH = (int)MathF.Ceiling(scissorH * scale);
}
- _context.Renderer.Pipeline.SetScissor(0, true, scissorX, scissorY, scissorW, scissorH);
+ Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[]
+ {
+ new Rectangle<int>(scissorX, scissorY, scissorW, scissorH)
+ };
+
+ _context.Renderer.Pipeline.SetScissors(scissors);
}
if (clipMismatch)
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
index 2f5d4fc5..ccbccca1 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
@@ -1,4 +1,5 @@
using Ryujinx.Common.Logging;
+using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Image;
@@ -15,11 +16,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
class StateUpdater
{
- public const int ShaderStateIndex = 0;
- public const int RasterizerStateIndex = 1;
- public const int ScissorStateIndex = 2;
- public const int VertexBufferStateIndex = 3;
- public const int PrimitiveRestartStateIndex = 4;
+ public const int ShaderStateIndex = 16;
+ public const int RasterizerStateIndex = 15;
+ public const int ScissorStateIndex = 18;
+ public const int VertexBufferStateIndex = 0;
+ public const int PrimitiveRestartStateIndex = 12;
private readonly GpuContext _context;
private readonly GpuChannel _channel;
@@ -31,6 +32,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
private readonly ShaderProgramInfo[] _currentProgramInfo;
private ShaderSpecializationState _shaderSpecState;
+ private ProgramPipelineState _pipeline;
+
private bool _vtgWritesRtLayer;
private byte _vsClipDistancesWritten;
@@ -54,7 +57,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_drawState = drawState;
_currentProgramInfo = new ShaderProgramInfo[Constants.ShaderStages];
- // ShaderState must be the first, as other state updates depends on information from the currently bound shader.
+ // ShaderState must be updated after other state updates, as pipeline state is sent to the backend when compiling new shaders.
+ // Render target state must appear after shader state as it depends on information from the currently bound shader.
// Rasterizer and scissor states are checked by render target clear, their indexes
// must be updated on the constants "RasterizerStateIndex" and "ScissorStateIndex" if modified.
// The vertex buffer state may be forced dirty when a indexed draw starts, the "VertexBufferStateIndex"
@@ -62,53 +66,39 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
// The order of the other state updates doesn't matter.
_updateTracker = new StateUpdateTracker<ThreedClassState>(new[]
{
- new StateUpdateCallbackEntry(UpdateShaderState,
- nameof(ThreedClassState.ShaderBaseAddress),
- nameof(ThreedClassState.ShaderState)),
-
- new StateUpdateCallbackEntry(UpdateRasterizerState, nameof(ThreedClassState.RasterizeEnable)),
-
- new StateUpdateCallbackEntry(UpdateScissorState,
- nameof(ThreedClassState.ScissorState),
- nameof(ThreedClassState.ScreenScissorState)),
-
new StateUpdateCallbackEntry(UpdateVertexBufferState,
nameof(ThreedClassState.VertexBufferDrawState),
nameof(ThreedClassState.VertexBufferInstanced),
nameof(ThreedClassState.VertexBufferState),
nameof(ThreedClassState.VertexBufferEndAddress)),
- new StateUpdateCallbackEntry(UpdatePrimitiveRestartState,
- nameof(ThreedClassState.PrimitiveRestartDrawArrays),
- nameof(ThreedClassState.PrimitiveRestartState)),
-
- new StateUpdateCallbackEntry(UpdateTessellationState,
- nameof(ThreedClassState.TessOuterLevel),
- nameof(ThreedClassState.TessInnerLevel),
- nameof(ThreedClassState.PatchVertices)),
-
- new StateUpdateCallbackEntry(UpdateTfBufferState, nameof(ThreedClassState.TfBufferState)),
- new StateUpdateCallbackEntry(UpdateUserClipState, nameof(ThreedClassState.ClipDistanceEnable)),
+ new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)),
- new StateUpdateCallbackEntry(UpdateRenderTargetState,
- nameof(ThreedClassState.RtColorState),
- nameof(ThreedClassState.RtDepthStencilState),
- nameof(ThreedClassState.RtControl),
- nameof(ThreedClassState.RtDepthStencilSize),
- nameof(ThreedClassState.RtDepthStencilEnable)),
+ new StateUpdateCallbackEntry(UpdateBlendState,
+ nameof(ThreedClassState.BlendIndependent),
+ nameof(ThreedClassState.BlendConstant),
+ nameof(ThreedClassState.BlendStateCommon),
+ nameof(ThreedClassState.BlendEnableCommon),
+ nameof(ThreedClassState.BlendEnable),
+ nameof(ThreedClassState.BlendState)),
- new StateUpdateCallbackEntry(UpdateDepthClampState, nameof(ThreedClassState.ViewVolumeClipControl)),
+ new StateUpdateCallbackEntry(UpdateFaceState, nameof(ThreedClassState.FaceState)),
- new StateUpdateCallbackEntry(UpdateAlphaTestState,
- nameof(ThreedClassState.AlphaTestEnable),
- nameof(ThreedClassState.AlphaTestRef),
- nameof(ThreedClassState.AlphaTestFunc)),
+ new StateUpdateCallbackEntry(UpdateStencilTestState,
+ nameof(ThreedClassState.StencilBackMasks),
+ nameof(ThreedClassState.StencilTestState),
+ nameof(ThreedClassState.StencilBackTestState)),
new StateUpdateCallbackEntry(UpdateDepthTestState,
nameof(ThreedClassState.DepthTestEnable),
nameof(ThreedClassState.DepthWriteEnable),
nameof(ThreedClassState.DepthTestFunc)),
+ new StateUpdateCallbackEntry(UpdateTessellationState,
+ nameof(ThreedClassState.TessOuterLevel),
+ nameof(ThreedClassState.TessInnerLevel),
+ nameof(ThreedClassState.PatchVertices)),
+
new StateUpdateCallbackEntry(UpdateViewportTransform,
nameof(ThreedClassState.DepthMode),
nameof(ThreedClassState.ViewportTransform),
@@ -116,6 +106,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
nameof(ThreedClassState.YControl),
nameof(ThreedClassState.ViewportTransformEnable)),
+ new StateUpdateCallbackEntry(UpdateLogicOpState, nameof(ThreedClassState.LogicOpState)),
+
+ new StateUpdateCallbackEntry(UpdateDepthClampState, nameof(ThreedClassState.ViewVolumeClipControl)),
+
new StateUpdateCallbackEntry(UpdatePolygonMode,
nameof(ThreedClassState.PolygonModeFront),
nameof(ThreedClassState.PolygonModeBack)),
@@ -126,21 +120,46 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
nameof(ThreedClassState.DepthBiasUnits),
nameof(ThreedClassState.DepthBiasClamp)),
- new StateUpdateCallbackEntry(UpdateStencilTestState,
- nameof(ThreedClassState.StencilBackMasks),
- nameof(ThreedClassState.StencilTestState),
- nameof(ThreedClassState.StencilBackTestState)),
+ new StateUpdateCallbackEntry(UpdatePrimitiveRestartState, nameof(ThreedClassState.PrimitiveRestartState)),
+
+ new StateUpdateCallbackEntry(UpdateLineState,
+ nameof(ThreedClassState.LineWidthSmooth),
+ nameof(ThreedClassState.LineSmoothEnable)),
+
+ new StateUpdateCallbackEntry(UpdateRtColorMask,
+ nameof(ThreedClassState.RtColorMaskShared),
+ nameof(ThreedClassState.RtColorMask)),
+
+ new StateUpdateCallbackEntry(UpdateRasterizerState, nameof(ThreedClassState.RasterizeEnable)),
+
+ new StateUpdateCallbackEntry(UpdateShaderState,
+ nameof(ThreedClassState.ShaderBaseAddress),
+ nameof(ThreedClassState.ShaderState)),
+
+ new StateUpdateCallbackEntry(UpdateRenderTargetState,
+ nameof(ThreedClassState.RtColorState),
+ nameof(ThreedClassState.RtDepthStencilState),
+ nameof(ThreedClassState.RtControl),
+ nameof(ThreedClassState.RtDepthStencilSize),
+ nameof(ThreedClassState.RtDepthStencilEnable)),
+
+ new StateUpdateCallbackEntry(UpdateScissorState,
+ nameof(ThreedClassState.ScissorState),
+ nameof(ThreedClassState.ScreenScissorState)),
+
+ new StateUpdateCallbackEntry(UpdateTfBufferState, nameof(ThreedClassState.TfBufferState)),
+ new StateUpdateCallbackEntry(UpdateUserClipState, nameof(ThreedClassState.ClipDistanceEnable)),
+
+ new StateUpdateCallbackEntry(UpdateAlphaTestState,
+ nameof(ThreedClassState.AlphaTestEnable),
+ nameof(ThreedClassState.AlphaTestRef),
+ nameof(ThreedClassState.AlphaTestFunc)),
new StateUpdateCallbackEntry(UpdateSamplerPoolState,
nameof(ThreedClassState.SamplerPoolState),
nameof(ThreedClassState.SamplerIndex)),
new StateUpdateCallbackEntry(UpdateTexturePoolState, nameof(ThreedClassState.TexturePoolState)),
- new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)),
-
- new StateUpdateCallbackEntry(UpdateLineState,
- nameof(ThreedClassState.LineWidthSmooth),
- nameof(ThreedClassState.LineSmoothEnable)),
new StateUpdateCallbackEntry(UpdatePointState,
nameof(ThreedClassState.PointSize),
@@ -152,22 +171,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
nameof(ThreedClassState.IndexBufferState),
nameof(ThreedClassState.IndexBufferCount)),
- new StateUpdateCallbackEntry(UpdateFaceState, nameof(ThreedClassState.FaceState)),
-
- new StateUpdateCallbackEntry(UpdateRtColorMask,
- nameof(ThreedClassState.RtColorMaskShared),
- nameof(ThreedClassState.RtColorMask)),
-
- new StateUpdateCallbackEntry(UpdateBlendState,
- nameof(ThreedClassState.BlendIndependent),
- nameof(ThreedClassState.BlendConstant),
- nameof(ThreedClassState.BlendStateCommon),
- nameof(ThreedClassState.BlendEnableCommon),
- nameof(ThreedClassState.BlendEnable),
- nameof(ThreedClassState.BlendState)),
-
- new StateUpdateCallbackEntry(UpdateLogicOpState, nameof(ThreedClassState.LogicOpState)),
-
new StateUpdateCallbackEntry(UpdateMultisampleState,
nameof(ThreedClassState.AlphaToCoverageDitherEnable),
nameof(ThreedClassState.MultisampleControl))
@@ -324,6 +327,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
private void UpdateTessellationState()
{
+ _pipeline.PatchControlPoints = (uint)_state.State.PatchVertices;
+
_context.Renderer.Pipeline.SetPatchParameters(
_state.State.PatchVertices,
_state.State.TessOuterLevel.ToSpan(),
@@ -356,6 +361,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
private void UpdateRasterizerState()
{
bool enable = _state.State.RasterizeEnable;
+ _pipeline.RasterizerDiscard = !enable;
_context.Renderer.Pipeline.SetRasterizerDiscard(!enable);
}
@@ -497,11 +503,21 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
public void UpdateScissorState()
{
+ const int MinX = 0;
+ const int MinY = 0;
+ const int MaxW = 0xffff;
+ const int MaxH = 0xffff;
+
+ Span<Rectangle<int>> regions = stackalloc Rectangle<int>[Constants.TotalViewports];
+
for (int index = 0; index < Constants.TotalViewports; index++)
{
ScissorState scissor = _state.State.ScissorState[index];
- bool enable = scissor.Enable && (scissor.X1 != 0 || scissor.Y1 != 0 || scissor.X2 != 0xffff || scissor.Y2 != 0xffff);
+ bool enable = scissor.Enable && (scissor.X1 != MinX ||
+ scissor.Y1 != MinY ||
+ scissor.X2 != MaxW ||
+ scissor.Y2 != MaxH);
if (enable)
{
@@ -531,13 +547,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
height = (int)MathF.Ceiling(height * scale);
}
- _context.Renderer.Pipeline.SetScissor(index, true, x, y, width, height);
+ regions[index] = new Rectangle<int>(x, y, width, height);
}
else
{
- _context.Renderer.Pipeline.SetScissor(index, false, 0, 0, 0, 0);
+ regions[index] = new Rectangle<int>(MinX, MinY, MaxW, MaxH);
}
}
+
+ _context.Renderer.Pipeline.SetScissors(regions);
}
/// <summary>
@@ -547,7 +565,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
private void UpdateDepthClampState()
{
ViewVolumeClipControl clip = _state.State.ViewVolumeClipControl;
- _context.Renderer.Pipeline.SetDepthClamp((clip & ViewVolumeClipControl.DepthClampDisabled) == 0);
+ bool clamp = (clip & ViewVolumeClipControl.DepthClampDisabled) == 0;
+
+ _pipeline.DepthClampEnable = clamp;
+ _context.Renderer.Pipeline.SetDepthClamp(clamp);
}
/// <summary>
@@ -566,10 +587,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
private void UpdateDepthTestState()
{
- _context.Renderer.Pipeline.SetDepthTest(new DepthTestDescriptor(
+ DepthTestDescriptor descriptor = new DepthTestDescriptor(
_state.State.DepthTestEnable,
_state.State.DepthWriteEnable,
- _state.State.DepthTestFunc));
+ _state.State.DepthTestFunc);
+
+ _pipeline.DepthTest = descriptor;
+ _context.Renderer.Pipeline.SetDepthTest(descriptor);
}
/// <summary>
@@ -596,7 +620,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
ref var scissor = ref _state.State.ScreenScissorState;
float rScale = _channel.TextureManager.RenderTargetScale;
- var scissorRect = new RectangleF(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale);
+ var scissorRect = new Rectangle<float>(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale);
viewports[index] = new Viewport(scissorRect, ViewportSwizzle.PositiveX, ViewportSwizzle.PositiveY, ViewportSwizzle.PositiveZ, ViewportSwizzle.PositiveW, 0, 1);
continue;
@@ -633,7 +657,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
height *= scale;
}
- RectangleF region = new RectangleF(x, y, width, height);
+ Rectangle<float> region = new Rectangle<float>(x, y, width, height);
ViewportSwizzle swizzleX = transform.UnpackSwizzleX();
ViewportSwizzle swizzleY = transform.UnpackSwizzleY();
@@ -653,7 +677,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar);
}
- _context.Renderer.Pipeline.SetViewports(0, viewports, disableTransform);
+ _context.Renderer.Pipeline.SetDepthMode(GetDepthMode());
+ _context.Renderer.Pipeline.SetViewports(viewports, disableTransform);
}
/// <summary>
@@ -661,37 +686,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
private void UpdateDepthMode()
{
- ref var transform = ref _state.State.ViewportTransform[0];
- ref var extents = ref _state.State.ViewportExtents[0];
-
- DepthMode depthMode;
-
- if (!float.IsInfinity(extents.DepthNear) &&
- !float.IsInfinity(extents.DepthFar) &&
- (extents.DepthFar - extents.DepthNear) != 0)
- {
- // Try to guess the depth mode being used on the high level API
- // based on current transform.
- // It is setup like so by said APIs:
- // If depth mode is ZeroToOne:
- // TranslateZ = Near
- // ScaleZ = Far - Near
- // If depth mode is MinusOneToOne:
- // TranslateZ = (Near + Far) / 2
- // ScaleZ = (Far - Near) / 2
- // DepthNear/Far are sorted such as that Near is always less than Far.
- depthMode = extents.DepthNear != transform.TranslateZ &&
- extents.DepthFar != transform.TranslateZ
- ? DepthMode.MinusOneToOne
- : DepthMode.ZeroToOne;
- }
- else
- {
- // If we can't guess from the viewport transform, then just use the depth mode register.
- depthMode = (DepthMode)(_state.State.DepthMode & 1);
- }
-
- _context.Renderer.Pipeline.SetDepthMode(depthMode);
+ _context.Renderer.Pipeline.SetDepthMode(GetDepthMode());
}
/// <summary>
@@ -719,6 +714,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
enables |= (depthBias.LineEnable ? PolygonModeMask.Line : 0);
enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0);
+ _pipeline.BiasEnable = enables;
_context.Renderer.Pipeline.SetDepthBias(enables, factor, units / 2f, clamp);
}
@@ -760,7 +756,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
backMask = test.FrontMask;
}
- _context.Renderer.Pipeline.SetStencilTest(new StencilTestDescriptor(
+ StencilTestDescriptor descriptor = new StencilTestDescriptor(
test.Enable,
test.FrontFunc,
test.FrontSFail,
@@ -775,7 +771,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
backDpFail,
backFuncRef,
backFuncMask,
- backMask));
+ backMask);
+
+ _pipeline.StencilTest = descriptor;
+ _context.Renderer.Pipeline.SetStencilTest(descriptor);
}
/// <summary>
@@ -844,6 +843,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
format);
}
+ _pipeline.SetVertexAttribs(vertexAttribs);
_context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs);
}
@@ -855,6 +855,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
float width = _state.State.LineWidthSmooth;
bool smooth = _state.State.LineSmoothEnable;
+ _pipeline.LineWidth = width;
_context.Renderer.Pipeline.SetLineParameters(width, smooth);
}
@@ -881,6 +882,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
PrimitiveRestartState primitiveRestart = _state.State.PrimitiveRestartState;
bool enable = primitiveRestart.Enable && (_drawState.DrawIndexed || _state.State.PrimitiveRestartDrawArrays);
+ _pipeline.PrimitiveRestartEnable = enable;
_context.Renderer.Pipeline.SetPrimitiveRestart(enable, primitiveRestart.Index);
}
@@ -927,6 +929,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (!vertexBuffer.UnpackEnable())
{
+ _pipeline.VertexBuffers[index] = new BufferPipelineDescriptor(false, 0, 0);
_channel.BufferManager.SetVertexBuffer(index, 0, 0, 0, 0);
continue;
@@ -944,6 +947,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_drawState.IsAnyVbInstanced |= divisor != 0;
+ ulong vbSize = endAddress.Pack() - address + 1;
ulong size;
if (_drawState.IbStreamer.HasInlineIndexData || _drawState.DrawIndexed || stride == 0 || instanced)
@@ -951,7 +955,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
// This size may be (much) larger than the real vertex buffer size.
// Avoid calculating it this way, unless we don't have any other option.
- size = endAddress.Pack() - address + 1;
+ size = vbSize;
if (stride > 0 && indexTypeSmall && _drawState.DrawIndexed && !instanced)
{
@@ -975,9 +979,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
var drawState = _state.State.VertexBufferDrawState;
- size = (ulong)((firstInstance + drawState.First + drawState.Count) * stride);
+ size = Math.Min(vbSize, (ulong)((firstInstance + drawState.First + drawState.Count) * stride));
}
+ _pipeline.VertexBuffers[index] = new BufferPipelineDescriptor(_channel.MemoryManager.IsMapped(address), stride, divisor);
_channel.BufferManager.SetVertexBuffer(index, address, size, stride, divisor);
}
}
@@ -990,6 +995,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
var yControl = _state.State.YControl;
var face = _state.State.FaceState;
+ _pipeline.CullEnable = face.CullEnable;
+ _pipeline.CullMode = face.CullFace;
_context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace);
UpdateFrontFace(yControl, face.FrontFace);
@@ -1009,6 +1016,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
frontFace = frontFace == FrontFace.CounterClockwise ? FrontFace.Clockwise : FrontFace.CounterClockwise;
}
+ _pipeline.FrontFace = frontFace;
_context.Renderer.Pipeline.SetFrontFace(frontFace);
}
@@ -1034,6 +1042,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
componentMask |= (colorMask.UnpackAlpha() ? 8u : 0u);
componentMasks[index] = componentMask;
+ _pipeline.ColorWriteMask[index] = componentMask;
}
_context.Renderer.Pipeline.SetRenderTargetColorMasks(componentMasks);
@@ -1082,6 +1091,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
blend.AlphaDstFactor);
}
+ _pipeline.BlendDescriptors[index] = descriptor;
_context.Renderer.Pipeline.SetBlendState(index, descriptor);
}
}
@@ -1093,6 +1103,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
LogicalOpState logicOpState = _state.State.LogicOpState;
+ _pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp);
_context.Renderer.Pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp);
}
@@ -1138,7 +1149,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
GpuChannelPoolState poolState = GetPoolState();
GpuChannelGraphicsState graphicsState = GetGraphicsState();
- CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, _channel, poolState, graphicsState, addresses);
+ CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, ref _pipeline, _channel, poolState, graphicsState, addresses);
_shaderSpecState = gs.SpecializationState;
@@ -1245,13 +1256,69 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// <returns>Current GPU channel state</returns>
private GpuChannelGraphicsState GetGraphicsState()
{
+ ref var vertexAttribState = ref _state.State.VertexAttribState;
+
+ Array32<AttributeType> attributeTypes = new Array32<AttributeType>();
+
+ for (int location = 0; location < attributeTypes.Length; location++)
+ {
+ attributeTypes[location] = vertexAttribState[location].UnpackType() switch
+ {
+ 3 => AttributeType.Sint,
+ 4 => AttributeType.Uint,
+ _ => AttributeType.Float
+ };
+ }
+
return new GpuChannelGraphicsState(
_state.State.EarlyZForce,
_drawState.Topology,
_state.State.TessMode,
- _state.State.ViewportTransformEnable == 0,
(_state.State.MultisampleControl & 1) != 0,
- _state.State.AlphaToCoverageDitherEnable);
+ _state.State.AlphaToCoverageDitherEnable,
+ _state.State.ViewportTransformEnable == 0,
+ GetDepthMode() == DepthMode.MinusOneToOne,
+ _state.State.VertexProgramPointSize,
+ _state.State.PointSize,
+ _state.State.AlphaTestEnable,
+ _state.State.AlphaTestFunc,
+ _state.State.AlphaTestRef,
+ ref attributeTypes);
+ }
+
+ private DepthMode GetDepthMode()
+ {
+ ref var transform = ref _state.State.ViewportTransform[0];
+ ref var extents = ref _state.State.ViewportExtents[0];
+
+ DepthMode depthMode;
+
+ if (!float.IsInfinity(extents.DepthNear) &&
+ !float.IsInfinity(extents.DepthFar) &&
+ (extents.DepthFar - extents.DepthNear) != 0)
+ {
+ // Try to guess the depth mode being used on the high level API
+ // based on current transform.
+ // It is setup like so by said APIs:
+ // If depth mode is ZeroToOne:
+ // TranslateZ = Near
+ // ScaleZ = Far - Near
+ // If depth mode is MinusOneToOne:
+ // TranslateZ = (Near + Far) / 2
+ // ScaleZ = (Far - Near) / 2
+ // DepthNear/Far are sorted such as that Near is always less than Far.
+ depthMode = extents.DepthNear != transform.TranslateZ &&
+ extents.DepthFar != transform.TranslateZ
+ ? DepthMode.MinusOneToOne
+ : DepthMode.ZeroToOne;
+ }
+ else
+ {
+ // If we can't guess from the viewport transform, then just use the depth mode register.
+ depthMode = (DepthMode)(_state.State.DepthMode & 1);
+ }
+
+ return depthMode;
}
/// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs
index 764ba239..95763910 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs
@@ -1,5 +1,6 @@
using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Engine.GPFifo;
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
using System;
using System.Collections.Generic;
@@ -13,6 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
class ThreedClass : IDeviceState
{
private readonly GpuContext _context;
+ private readonly GPFifoClass _fifoClass;
private readonly DeviceStateWithShadow<ThreedClassState> _state;
private readonly InlineToMemoryClass _i2mClass;
@@ -26,9 +28,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
/// <param name="context">GPU context</param>
/// <param name="channel">GPU channel</param>
- public ThreedClass(GpuContext context, GpuChannel channel)
+ public ThreedClass(GpuContext context, GpuChannel channel, GPFifoClass fifoClass)
{
_context = context;
+ _fifoClass = fifoClass;
_state = new DeviceStateWithShadow<ThreedClassState>(new Dictionary<string, RwCallback>
{
{ nameof(ThreedClassState.LaunchDma), new RwCallback(LaunchDma, null) },
@@ -114,6 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
public void UpdateState()
{
+ _fifoClass.CreatePendingSyncs();
_cbUpdater.FlushUboDirty();
_stateUpdater.Update();
}
@@ -173,6 +177,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
}
/// <summary>
+ /// Create any syncs from WaitForIdle command that are currently pending.
+ /// </summary>
+ public void CreatePendingSyncs()
+ {
+ _fifoClass.CreatePendingSyncs();
+ }
+
+ /// <summary>
/// Flushes any queued UBO updates.
/// </summary>
public void FlushUboDirty()
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
index 2a831356..c90dea41 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
@@ -311,6 +311,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
return Attribute & 0x3fe00000;
}
+
+ /// <summary>
+ /// Unpacks the Maxwell attribute component type.
+ /// </summary>
+ /// <returns>Attribute component type</returns>
+ public uint UnpackType()
+ {
+ return (Attribute >> 27) & 7;
+ }
}
/// <summary>
@@ -759,8 +768,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
public fixed uint Reserved10B0[18];
public uint ClearFlags;
public fixed uint Reserved10FC[25];
- public Array16<VertexAttribState> VertexAttribState;
- public fixed uint Reserved11A0[31];
+ public Array32<VertexAttribState> VertexAttribState;
+ public fixed uint Reserved11E0[15];
public RtControl RtControl;
public fixed uint Reserved1220[2];
public Size3D RtDepthStencilSize;
diff --git a/Ryujinx.Graphics.Gpu/GraphicsConfig.cs b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
index 493dbd7b..d2f98c7f 100644
--- a/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
+++ b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
@@ -30,8 +30,8 @@ namespace Ryujinx.Graphics.Gpu
/// <summary>
/// Enables or disables fast 2d engine texture copies entirely on CPU when possible.
- /// Reduces stuttering and # of textures in games that copy textures around for streaming,
- /// as textures will not need to be created for the copy, and the data does not need to be
+ /// Reduces stuttering and # of textures in games that copy textures around for streaming,
+ /// as textures will not need to be created for the copy, and the data does not need to be
/// flushed from GPU.
/// </summary>
public static bool Fast2DCopy = true;
@@ -56,5 +56,15 @@ namespace Ryujinx.Graphics.Gpu
/// Enables or disables the shader cache.
/// </summary>
public static bool EnableShaderCache;
+
+ /// <summary>
+ /// Enables or disables shader SPIR-V compilation.
+ /// </summary>
+ public static bool EnableSpirvCompilationOnVulkan = true;
+
+ /// <summary>
+ /// Enables or disables recompression of compressed textures that are not natively supported by the host.
+ /// </summary>
+ public static bool EnableTextureRecompression = false;
}
} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index cb10f456..a598f212 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -826,20 +826,25 @@ namespace Ryujinx.Graphics.Gpu.Image
depth,
levels,
layers,
- out Span<byte> decoded))
+ out byte[] decoded))
{
string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}";
Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo}).");
}
+ if (GraphicsConfig.EnableTextureRecompression)
+ {
+ decoded = BCnEncoder.EncodeBC7(decoded, width, height, depth, levels, layers);
+ }
+
data = decoded;
}
else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm)
{
data = PixelConverter.ConvertR4G4ToR4G4B4A4(data);
}
- else if (!_context.Capabilities.Supports3DTextureCompression && Target == Target.Texture3D)
+ else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities))
{
switch (Format)
{
@@ -863,6 +868,14 @@ namespace Ryujinx.Graphics.Gpu.Image
case Format.Bc5Unorm:
data = BCnDecoder.DecodeBC5(data, width, height, depth, levels, layers, Format == Format.Bc5Snorm);
break;
+ case Format.Bc6HSfloat:
+ case Format.Bc6HUfloat:
+ data = BCnDecoder.DecodeBC6(data, width, height, depth, levels, layers, Format == Format.Bc6HSfloat);
+ break;
+ case Format.Bc7Srgb:
+ case Format.Bc7Unorm:
+ data = BCnDecoder.DecodeBC7(data, width, height, depth, levels, layers);
+ break;
}
}
@@ -1151,7 +1164,7 @@ namespace Ryujinx.Graphics.Gpu.Image
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps));
if (result != TextureViewCompatibility.Incompatible)
{
- result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
+ result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info, ref caps));
bool bothMs = Info.Target.IsMultisample() && info.Target.IsMultisample();
if (bothMs && (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY))
@@ -1216,16 +1229,18 @@ namespace Ryujinx.Graphics.Gpu.Image
if (_arrayViewTexture == null && IsSameDimensionsTarget(target))
{
+ FormatInfo formatInfo = TextureCompatibility.ToHostCompatibleFormat(Info, _context.Capabilities);
+
TextureCreateInfo createInfo = new TextureCreateInfo(
Info.Width,
Info.Height,
target == Target.CubemapArray ? 6 : 1,
Info.Levels,
Info.Samples,
- Info.FormatInfo.BlockWidth,
- Info.FormatInfo.BlockHeight,
- Info.FormatInfo.BytesPerPixel,
- Info.FormatInfo.Format,
+ formatInfo.BlockWidth,
+ formatInfo.BlockHeight,
+ formatInfo.BytesPerPixel,
+ formatInfo.Format,
Info.DepthStencilMode,
target,
Info.SwizzleR,
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
index 067a1f9f..6c122124 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
@@ -281,6 +281,30 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Determines if the vertex stage requires a scale value.
+ /// </summary>
+ private bool VertexRequiresScale()
+ {
+ for (int i = 0; i < _textureBindingsCount[0]; i++)
+ {
+ if ((_textureBindings[0][i].Flags & TextureUsageFlags.NeedsScaleValue) != 0)
+ {
+ return true;
+ }
+ }
+
+ for (int i = 0; i < _imageBindingsCount[0]; i++)
+ {
+ if ((_imageBindings[0][i].Flags & TextureUsageFlags.NeedsScaleValue) != 0)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// <summary>
/// Uploads texture and image scales to the backend when they are used.
/// </summary>
private void CommitRenderScale()
@@ -291,10 +315,10 @@ namespace Ryujinx.Graphics.Gpu.Image
int fragmentIndex = (int)ShaderStage.Fragment - 1;
int fragmentTotal = _isCompute ? 0 : (_textureBindingsCount[fragmentIndex] + _imageBindingsCount[fragmentIndex]);
- if (total != 0 && fragmentTotal != _lastFragmentTotal)
+ if (total != 0 && fragmentTotal != _lastFragmentTotal && VertexRequiresScale())
{
// Must update scales in the support buffer if:
- // - Vertex stage has bindings.
+ // - Vertex stage has bindings that require scale.
// - Fragment stage binding count has been updated since last render scale update.
_scaleChanged = true;
@@ -421,6 +445,25 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Counts the total number of texture bindings used by all shader stages.
+ /// </summary>
+ /// <returns>The total amount of textures used</returns>
+ private int GetTextureBindingsCount()
+ {
+ int count = 0;
+
+ for (int i = 0; i < _textureBindings.Length; i++)
+ {
+ if (_textureBindings[i] != null)
+ {
+ count += _textureBindings[i].Length;
+ }
+ }
+
+ return count;
+ }
+
+ /// <summary>
/// Ensures that the texture bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API.
/// </summary>
@@ -501,7 +544,7 @@ namespace Ryujinx.Graphics.Gpu.Image
state.ScaleIndex = index;
state.UsageFlags = usageFlags;
- _context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTextureRebind);
+ _context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTextureRebind, state.Sampler);
}
continue;
@@ -514,44 +557,42 @@ namespace Ryujinx.Graphics.Gpu.Image
specStateMatches &= specState.MatchesTexture(stage, index, descriptor);
+ Sampler sampler = _samplerPool?.Get(samplerId);
+
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
+ ISampler hostSampler = sampler?.GetHostSampler(texture);
if (hostTexture != null && texture.Target == Target.TextureBuffer)
{
// Ensure that the buffer texture is using the correct buffer as storage.
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted.
- _channel.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false);
+ _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false);
}
else
{
- if (state.Texture != hostTexture)
+ bool textureOrSamplerChanged = state.Texture != hostTexture || state.Sampler != hostSampler;
+
+ if ((state.ScaleIndex != index || state.UsageFlags != usageFlags || textureOrSamplerChanged) &&
+ UpdateScale(texture, usageFlags, index, stage))
{
- if (UpdateScale(texture, usageFlags, index, stage))
- {
- hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
- }
+ hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
+ textureOrSamplerChanged = true;
+ }
+ if (textureOrSamplerChanged)
+ {
state.Texture = hostTexture;
state.ScaleIndex = index;
state.UsageFlags = usageFlags;
- _context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
- }
-
- Sampler sampler = samplerPool?.Get(samplerId);
- state.CachedSampler = sampler;
-
- ISampler hostSampler = sampler?.GetHostSampler(texture);
-
- if (state.Sampler != hostSampler)
- {
state.Sampler = hostSampler;
- _context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler);
+ _context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTexture, hostSampler);
}
state.CachedTexture = texture;
+ state.CachedSampler = sampler;
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
}
}
@@ -625,7 +666,7 @@ namespace Ryujinx.Graphics.Gpu.Image
cachedTexture?.SignalModified();
}
- if ((state.ScaleIndex != index || state.UsageFlags != usageFlags) &&
+ if ((state.ScaleIndex != scaleIndex || state.UsageFlags != usageFlags) &&
UpdateScale(state.CachedTexture, usageFlags, scaleIndex, stage))
{
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
@@ -663,7 +704,7 @@ namespace Ryujinx.Graphics.Gpu.Image
format = texture.Format;
}
- _channel.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true);
+ _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true);
}
else
{
@@ -672,13 +713,14 @@ namespace Ryujinx.Graphics.Gpu.Image
texture?.SignalModified();
}
- if (state.Texture != hostTexture)
+ if ((state.ScaleIndex != scaleIndex || state.UsageFlags != usageFlags || state.Texture != hostTexture) &&
+ UpdateScale(texture, usageFlags, scaleIndex, stage))
{
- if (UpdateScale(texture, usageFlags, scaleIndex, stage))
- {
- hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
- }
+ hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
+ }
+ if (state.Texture != hostTexture)
+ {
state.Texture = hostTexture;
state.ScaleIndex = scaleIndex;
state.UsageFlags = usageFlags;
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
index 61b48dc4..5ea9ee2f 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
@@ -71,11 +71,15 @@ namespace Ryujinx.Graphics.Gpu.Image
{
if (info.FormatInfo.Format.IsAstcUnorm())
{
- return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
+ return GraphicsConfig.EnableTextureRecompression
+ ? new FormatInfo(Format.Bc7Unorm, 4, 4, 16, 4)
+ : new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
}
else if (info.FormatInfo.Format.IsAstcSrgb())
{
- return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
+ return GraphicsConfig.EnableTextureRecompression
+ ? new FormatInfo(Format.Bc7Srgb, 4, 4, 16, 4)
+ : new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
}
}
@@ -84,9 +88,9 @@ namespace Ryujinx.Graphics.Gpu.Image
return new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4);
}
- if (!caps.Supports3DTextureCompression && info.Target == Target.Texture3D)
+ if (!HostSupportsBcFormat(info.FormatInfo.Format, info.Target, caps))
{
- // The host API does not support 3D compressed formats.
+ // The host API does not this compressed format.
// We assume software decompression will be done for those textures,
// and so we adjust the format here to match the decompressor output.
switch (info.FormatInfo.Format)
@@ -94,10 +98,12 @@ namespace Ryujinx.Graphics.Gpu.Image
case Format.Bc1RgbaSrgb:
case Format.Bc2Srgb:
case Format.Bc3Srgb:
+ case Format.Bc7Srgb:
return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
case Format.Bc1RgbaUnorm:
case Format.Bc2Unorm:
case Format.Bc3Unorm:
+ case Format.Bc7Unorm:
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
case Format.Bc4Unorm:
return new FormatInfo(Format.R8Unorm, 1, 1, 1, 1);
@@ -107,6 +113,9 @@ namespace Ryujinx.Graphics.Gpu.Image
return new FormatInfo(Format.R8G8Unorm, 1, 1, 2, 2);
case Format.Bc5Snorm:
return new FormatInfo(Format.R8G8Snorm, 1, 1, 2, 2);
+ case Format.Bc6HSfloat:
+ case Format.Bc6HUfloat:
+ return new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4);
}
}
@@ -114,6 +123,41 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Checks if the host API supports a given texture compression format of the BC family.
+ /// </summary>
+ /// <param name="format">BC format to be checked</param>
+ /// <param name="target">Target usage of the texture</param>
+ /// <param name="caps">Host GPU Capabilities</param>
+ /// <returns>True if the texture host supports the format with the given target usage, false otherwise</returns>
+ public static bool HostSupportsBcFormat(Format format, Target target, Capabilities caps)
+ {
+ bool not3DOr3DCompressionSupported = target != Target.Texture3D || caps.Supports3DTextureCompression;
+
+ switch (format)
+ {
+ case Format.Bc1RgbaSrgb:
+ case Format.Bc1RgbaUnorm:
+ case Format.Bc2Srgb:
+ case Format.Bc2Unorm:
+ case Format.Bc3Srgb:
+ case Format.Bc3Unorm:
+ return caps.SupportsBc123Compression && not3DOr3DCompressionSupported;
+ case Format.Bc4Unorm:
+ case Format.Bc4Snorm:
+ case Format.Bc5Unorm:
+ case Format.Bc5Snorm:
+ return caps.SupportsBc45Compression && not3DOr3DCompressionSupported;
+ case Format.Bc6HSfloat:
+ case Format.Bc6HUfloat:
+ case Format.Bc7Srgb:
+ case Format.Bc7Unorm:
+ return caps.SupportsBc67Compression && not3DOr3DCompressionSupported;
+ }
+
+ return true;
+ }
+
+ /// <summary>
/// Determines whether a texture can flush its data back to guest memory.
/// </summary>
/// <param name="info">Texture information</param>
@@ -627,9 +671,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
/// <param name="lhs">Texture information of the texture view</param
/// <param name="rhs">Texture information of the texture view</param>
- /// <param name="isCopy">True to check for copy rather than view compatibility</param>
+ /// <param name="caps">Host GPU capabilities</param>
/// <returns>True if the targets are compatible, false otherwise</returns>
- public static TextureViewCompatibility ViewTargetCompatible(TextureInfo lhs, TextureInfo rhs)
+ public static TextureViewCompatibility ViewTargetCompatible(TextureInfo lhs, TextureInfo rhs, ref Capabilities caps)
{
bool result = false;
switch (lhs.Target)
@@ -646,14 +690,24 @@ namespace Ryujinx.Graphics.Gpu.Image
break;
case Target.Texture2DArray:
+ result = rhs.Target == Target.Texture2D ||
+ rhs.Target == Target.Texture2DArray;
+
+ if (rhs.Target == Target.Cubemap || rhs.Target == Target.CubemapArray)
+ {
+ return caps.SupportsCubemapView ? TextureViewCompatibility.Full : TextureViewCompatibility.CopyOnly;
+ }
+ break;
case Target.Cubemap:
case Target.CubemapArray:
- result = rhs.Target == Target.Texture2D ||
- rhs.Target == Target.Texture2DArray ||
- rhs.Target == Target.Cubemap ||
+ result = rhs.Target == Target.Cubemap ||
rhs.Target == Target.CubemapArray;
- break;
+ if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray)
+ {
+ return caps.SupportsCubemapView ? TextureViewCompatibility.Full : TextureViewCompatibility.CopyOnly;
+ }
+ break;
case Target.Texture2DMultisample:
case Target.Texture2DMultisampleArray:
if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray)
@@ -744,7 +798,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>True if the texture target and samples count matches, false otherwise</returns>
public static bool TargetAndSamplesCompatible(TextureInfo lhs, TextureInfo rhs)
{
- return lhs.Target == rhs.Target &&
+ return lhs.Target == rhs.Target &&
lhs.SamplesInX == rhs.SamplesInX &&
lhs.SamplesInY == rhs.SamplesInY;
}
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
index 73b1232e..52cc8ee0 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
@@ -1,4 +1,3 @@
-using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
index 9f5f39a9..9f1f88b1 100644
--- a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
@@ -435,7 +435,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
else
{
- _context.Renderer.Pipeline.SetTexture(binding.BindingInfo.Binding, binding.Texture);
+ _context.Renderer.Pipeline.SetTextureAndSampler(binding.Stage, binding.BindingInfo.Binding, binding.Texture, null);
}
}
@@ -719,17 +719,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// Sets the buffer storage of a buffer texture. This will be bound when the buffer manager commits bindings.
/// </summary>
+ /// <param name="stage">Shader stage accessing the texture</param>
/// <param name="texture">Buffer texture</param>
/// <param name="address">Address of the buffer in memory</param>
/// <param name="size">Size of the buffer in bytes</param>
/// <param name="bindingInfo">Binding info for the buffer texture</param>
/// <param name="format">Format of the buffer texture</param>
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
- public void SetBufferTextureStorage(ITexture texture, ulong address, ulong size, TextureBindingInfo bindingInfo, Format format, bool isImage)
+ public void SetBufferTextureStorage(
+ ShaderStage stage,
+ ITexture texture,
+ ulong address,
+ ulong size,
+ TextureBindingInfo bindingInfo,
+ Format format,
+ bool isImage)
{
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(address, size);
- _bufferTextures.Add(new BufferTextureBinding(texture, address, size, bindingInfo, format, isImage));
+ _bufferTextures.Add(new BufferTextureBinding(stage, texture, address, size, bindingInfo, format, isImage));
}
/// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs b/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs
index cf0d225e..2a140870 100644
--- a/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs
@@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.Gpu.Memory
{
@@ -9,6 +10,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
struct BufferTextureBinding
{
/// <summary>
+ /// Shader stage accessing the texture.
+ /// </summary>
+ public ShaderStage Stage { get; }
+
+ /// <summary>
/// The buffer texture.
/// </summary>
public ITexture Texture { get; }
@@ -41,14 +47,23 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// Create a new buffer texture binding.
/// </summary>
+ /// <param name="stage">Shader stage accessing the texture</param>
/// <param name="texture">Buffer texture</param>
/// <param name="address">Base address</param>
/// <param name="size">Size in bytes</param>
/// <param name="bindingInfo">Binding info</param>
/// <param name="format">Binding format</param>
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
- public BufferTextureBinding(ITexture texture, ulong address, ulong size, TextureBindingInfo bindingInfo, Format format, bool isImage)
+ public BufferTextureBinding(
+ ShaderStage stage,
+ ITexture texture,
+ ulong address,
+ ulong size,
+ TextureBindingInfo bindingInfo,
+ Format format,
+ bool isImage)
{
+ Stage = stage;
Texture = texture;
Address = address;
Size = size;
diff --git a/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj b/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
index e3645668..7b5d73b6 100644
--- a/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
+++ b/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
@@ -14,8 +14,4 @@
<ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
</ItemGroup>
- <ItemGroup>
- <PackageReference Include="SharpZipLib" Version="1.3.3" />
- </ItemGroup>
-
</Project>
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs
deleted file mode 100644
index a98531f6..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs
+++ /dev/null
@@ -1,617 +0,0 @@
-using ICSharpCode.SharpZipLib.Zip;
-using Ryujinx.Common;
-using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Threading;
-
-namespace Ryujinx.Graphics.Gpu.Shader.Cache
-{
- /// <summary>
- /// Represent a cache collection handling one shader cache.
- /// </summary>
- class CacheCollection : IDisposable
- {
- /// <summary>
- /// Possible operation to do on the <see cref="_fileWriterWorkerQueue"/>.
- /// </summary>
- private enum CacheFileOperation
- {
- /// <summary>
- /// Save a new entry in the temp cache.
- /// </summary>
- SaveTempEntry,
-
- /// <summary>
- /// Save the hash manifest.
- /// </summary>
- SaveManifest,
-
- /// <summary>
- /// Remove entries from the hash manifest and save it.
- /// </summary>
- RemoveManifestEntries,
-
- /// <summary>
- /// Remove entries from the hash manifest and save it, and also deletes the temporary file.
- /// </summary>
- RemoveManifestEntryAndTempFile,
-
- /// <summary>
- /// Flush temporary cache to archive.
- /// </summary>
- FlushToArchive,
-
- /// <summary>
- /// Signal when hitting this point. This is useful to know if all previous operations were performed.
- /// </summary>
- Synchronize
- }
-
- /// <summary>
- /// Represent an operation to perform on the <see cref="_fileWriterWorkerQueue"/>.
- /// </summary>
- private class CacheFileOperationTask
- {
- /// <summary>
- /// The type of operation to perform.
- /// </summary>
- public CacheFileOperation Type;
-
- /// <summary>
- /// The data associated to this operation or null.
- /// </summary>
- public object Data;
- }
-
- /// <summary>
- /// Data associated to the <see cref="CacheFileOperation.SaveTempEntry"/> operation.
- /// </summary>
- private class CacheFileSaveEntryTaskData
- {
- /// <summary>
- /// The key of the entry to cache.
- /// </summary>
- public Hash128 Key;
-
- /// <summary>
- /// The value of the entry to cache.
- /// </summary>
- public byte[] Value;
- }
-
- /// <summary>
- /// The directory of the shader cache.
- /// </summary>
- private readonly string _cacheDirectory;
-
- /// <summary>
- /// The version of the cache.
- /// </summary>
- private readonly ulong _version;
-
- /// <summary>
- /// The hash type of the cache.
- /// </summary>
- private readonly CacheHashType _hashType;
-
- /// <summary>
- /// The graphics API of the cache.
- /// </summary>
- private readonly CacheGraphicsApi _graphicsApi;
-
- /// <summary>
- /// The table of all the hash registered in the cache.
- /// </summary>
- private HashSet<Hash128> _hashTable;
-
- /// <summary>
- /// The queue of operations to be performed by the file writer worker.
- /// </summary>
- private AsyncWorkQueue<CacheFileOperationTask> _fileWriterWorkerQueue;
-
- /// <summary>
- /// Main storage of the cache collection.
- /// </summary>
- private ZipFile _cacheArchive;
-
- /// <summary>
- /// Indicates if the cache collection supports modification.
- /// </summary>
- public bool IsReadOnly { get; }
-
- /// <summary>
- /// Immutable copy of the hash table.
- /// </summary>
- public ReadOnlySpan<Hash128> HashTable => _hashTable.ToArray();
-
- /// <summary>
- /// Get the temp path to the cache data directory.
- /// </summary>
- /// <returns>The temp path to the cache data directory</returns>
- private string GetCacheTempDataPath() => CacheHelper.GetCacheTempDataPath(_cacheDirectory);
-
- /// <summary>
- /// The path to the cache archive file.
- /// </summary>
- /// <returns>The path to the cache archive file</returns>
- private string GetArchivePath() => CacheHelper.GetArchivePath(_cacheDirectory);
-
- /// <summary>
- /// The path to the cache manifest file.
- /// </summary>
- /// <returns>The path to the cache manifest file</returns>
- private string GetManifestPath() => CacheHelper.GetManifestPath(_cacheDirectory);
-
- /// <summary>
- /// Create a new temp path to the given cached file via its hash.
- /// </summary>
- /// <param name="key">The hash of the cached data</param>
- /// <returns>New path to the given cached file</returns>
- private string GenCacheTempFilePath(Hash128 key) => CacheHelper.GenCacheTempFilePath(_cacheDirectory, key);
-
- /// <summary>
- /// Create a new cache collection.
- /// </summary>
- /// <param name="baseCacheDirectory">The directory of the shader cache</param>
- /// <param name="hashType">The hash type of the shader cache</param>
- /// <param name="graphicsApi">The graphics api of the shader cache</param>
- /// <param name="shaderProvider">The shader provider name of the shader cache</param>
- /// <param name="cacheName">The name of the cache</param>
- /// <param name="version">The version of the cache</param>
- public CacheCollection(string baseCacheDirectory, CacheHashType hashType, CacheGraphicsApi graphicsApi, string shaderProvider, string cacheName, ulong version)
- {
- if (hashType != CacheHashType.XxHash128)
- {
- throw new NotImplementedException($"{hashType}");
- }
-
- _cacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, graphicsApi, shaderProvider, cacheName);
- _graphicsApi = graphicsApi;
- _hashType = hashType;
- _version = version;
- _hashTable = new HashSet<Hash128>();
- IsReadOnly = CacheHelper.IsArchiveReadOnly(GetArchivePath());
-
- Load();
-
- _fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(HandleCacheTask, $"CacheCollection.Worker.{cacheName}");
- }
-
- /// <summary>
- /// Load the cache manifest file and recreate it if invalid.
- /// </summary>
- private void Load()
- {
- bool isValid = false;
-
- if (Directory.Exists(_cacheDirectory))
- {
- string manifestPath = GetManifestPath();
-
- if (File.Exists(manifestPath))
- {
- Memory<byte> rawManifest = File.ReadAllBytes(manifestPath);
-
- if (MemoryMarshal.TryRead(rawManifest.Span, out CacheManifestHeader manifestHeader))
- {
- Memory<byte> hashTableRaw = rawManifest.Slice(Unsafe.SizeOf<CacheManifestHeader>());
-
- isValid = manifestHeader.IsValid(_graphicsApi, _hashType, hashTableRaw.Span) && _version == manifestHeader.Version;
-
- if (isValid)
- {
- ReadOnlySpan<Hash128> hashTable = MemoryMarshal.Cast<byte, Hash128>(hashTableRaw.Span);
-
- foreach (Hash128 hash in hashTable)
- {
- _hashTable.Add(hash);
- }
- }
- }
- }
- }
-
- if (!isValid)
- {
- Logger.Warning?.Print(LogClass.Gpu, $"Shader collection \"{_cacheDirectory}\" got invalidated, cache will need to be rebuilt.");
-
- if (Directory.Exists(_cacheDirectory))
- {
- Directory.Delete(_cacheDirectory, true);
- }
-
- Directory.CreateDirectory(_cacheDirectory);
-
- SaveManifest();
- }
-
- FlushToArchive();
- }
-
- /// <summary>
- /// Queue a task to remove entries from the hash manifest.
- /// </summary>
- /// <param name="entries">Entries to remove from the manifest</param>
- public void RemoveManifestEntriesAsync(HashSet<Hash128> entries)
- {
- if (IsReadOnly)
- {
- Logger.Warning?.Print(LogClass.Gpu, "Trying to remove manifest entries on a read-only cache, ignoring.");
-
- return;
- }
-
- _fileWriterWorkerQueue.Add(new CacheFileOperationTask
- {
- Type = CacheFileOperation.RemoveManifestEntries,
- Data = entries
- });
- }
-
- /// <summary>
- /// Remove given entries from the manifest.
- /// </summary>
- /// <param name="entries">Entries to remove from the manifest</param>
- private void RemoveManifestEntries(HashSet<Hash128> entries)
- {
- lock (_hashTable)
- {
- foreach (Hash128 entry in entries)
- {
- _hashTable.Remove(entry);
- }
-
- SaveManifest();
- }
- }
-
- /// <summary>
- /// Remove given entry from the manifest and delete the temporary file.
- /// </summary>
- /// <param name="entry">Entry to remove from the manifest</param>
- private void RemoveManifestEntryAndTempFile(Hash128 entry)
- {
- lock (_hashTable)
- {
- _hashTable.Remove(entry);
- SaveManifest();
- }
-
- File.Delete(GenCacheTempFilePath(entry));
- }
-
- /// <summary>
- /// Queue a task to flush temporary files to the archive on the worker.
- /// </summary>
- public void FlushToArchiveAsync()
- {
- _fileWriterWorkerQueue.Add(new CacheFileOperationTask
- {
- Type = CacheFileOperation.FlushToArchive
- });
- }
-
- /// <summary>
- /// Wait for all tasks before this given point to be done.
- /// </summary>
- public void Synchronize()
- {
- using (ManualResetEvent evnt = new ManualResetEvent(false))
- {
- _fileWriterWorkerQueue.Add(new CacheFileOperationTask
- {
- Type = CacheFileOperation.Synchronize,
- Data = evnt
- });
-
- evnt.WaitOne();
- }
- }
-
- /// <summary>
- /// Flush temporary files to the archive.
- /// </summary>
- /// <remarks>This dispose <see cref="_cacheArchive"/> if not null and reinstantiate it.</remarks>
- private void FlushToArchive()
- {
- EnsureArchiveUpToDate();
-
- // Open the zip in readonly to avoid anyone modifying/corrupting it during normal operations.
- _cacheArchive = new ZipFile(File.OpenRead(GetArchivePath()));
- }
-
- /// <summary>
- /// Save temporary files not in archive.
- /// </summary>
- /// <remarks>This dispose <see cref="_cacheArchive"/> if not null.</remarks>
- public void EnsureArchiveUpToDate()
- {
- // First close previous opened instance if found.
- if (_cacheArchive != null)
- {
- _cacheArchive.Close();
- }
-
- string archivePath = GetArchivePath();
-
- if (IsReadOnly)
- {
- Logger.Warning?.Print(LogClass.Gpu, $"Cache collection archive in read-only, archiving task skipped.");
-
- return;
- }
-
- if (CacheHelper.IsArchiveReadOnly(archivePath))
- {
- Logger.Warning?.Print(LogClass.Gpu, $"Cache collection archive in use, archiving task skipped.");
-
- return;
- }
-
- if (!File.Exists(archivePath))
- {
- using (ZipFile newZip = ZipFile.Create(archivePath))
- {
- // Workaround for SharpZipLib issue #395
- newZip.BeginUpdate();
- newZip.CommitUpdate();
- }
- }
-
- // Open the zip in read/write.
- _cacheArchive = new ZipFile(File.Open(archivePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None));
-
- Logger.Info?.Print(LogClass.Gpu, $"Updating cache collection archive {archivePath}...");
-
- // Update the content of the zip.
- lock (_hashTable)
- {
- CacheHelper.EnsureArchiveUpToDate(_cacheDirectory, _cacheArchive, _hashTable);
-
- // Close the instance to force a flush.
- _cacheArchive.Close();
- _cacheArchive = null;
-
- string cacheTempDataPath = GetCacheTempDataPath();
-
- // Create the cache data path if missing.
- if (!Directory.Exists(cacheTempDataPath))
- {
- Directory.CreateDirectory(cacheTempDataPath);
- }
- }
-
- Logger.Info?.Print(LogClass.Gpu, $"Updated cache collection archive {archivePath}.");
- }
-
- /// <summary>
- /// Save the manifest file.
- /// </summary>
- private void SaveManifest()
- {
- byte[] data;
-
- lock (_hashTable)
- {
- data = CacheHelper.ComputeManifest(_version, _graphicsApi, _hashType, _hashTable);
- }
-
- File.WriteAllBytes(GetManifestPath(), data);
- }
-
- /// <summary>
- /// Get a cached file with the given hash.
- /// </summary>
- /// <param name="keyHash">The given hash</param>
- /// <returns>The cached file if present or null</returns>
- public byte[] GetValueRaw(ref Hash128 keyHash)
- {
- return GetValueRawFromArchive(ref keyHash) ?? GetValueRawFromFile(ref keyHash);
- }
-
- /// <summary>
- /// Get a cached file with the given hash that is present in the archive.
- /// </summary>
- /// <param name="keyHash">The given hash</param>
- /// <returns>The cached file if present or null</returns>
- private byte[] GetValueRawFromArchive(ref Hash128 keyHash)
- {
- bool found;
-
- lock (_hashTable)
- {
- found = _hashTable.Contains(keyHash);
- }
-
- if (found)
- {
- return CacheHelper.ReadFromArchive(_cacheArchive, keyHash);
- }
-
- return null;
- }
-
- /// <summary>
- /// Get a cached file with the given hash that is not present in the archive.
- /// </summary>
- /// <param name="keyHash">The given hash</param>
- /// <returns>The cached file if present or null</returns>
- private byte[] GetValueRawFromFile(ref Hash128 keyHash)
- {
- bool found;
-
- lock (_hashTable)
- {
- found = _hashTable.Contains(keyHash);
- }
-
- if (found)
- {
- return CacheHelper.ReadFromFile(GetCacheTempDataPath(), keyHash);
- }
-
- return null;
- }
-
- private void HandleCacheTask(CacheFileOperationTask task)
- {
- switch (task.Type)
- {
- case CacheFileOperation.SaveTempEntry:
- SaveTempEntry((CacheFileSaveEntryTaskData)task.Data);
- break;
- case CacheFileOperation.SaveManifest:
- SaveManifest();
- break;
- case CacheFileOperation.RemoveManifestEntries:
- RemoveManifestEntries((HashSet<Hash128>)task.Data);
- break;
- case CacheFileOperation.RemoveManifestEntryAndTempFile:
- RemoveManifestEntryAndTempFile((Hash128)task.Data);
- break;
- case CacheFileOperation.FlushToArchive:
- FlushToArchive();
- break;
- case CacheFileOperation.Synchronize:
- ((ManualResetEvent)task.Data).Set();
- break;
- default:
- throw new NotImplementedException($"{task.Type}");
- }
-
- }
-
- /// <summary>
- /// Save a new entry in the temp cache.
- /// </summary>
- /// <param name="entry">The entry to save in the temp cache</param>
- private void SaveTempEntry(CacheFileSaveEntryTaskData entry)
- {
- string tempPath = GenCacheTempFilePath(entry.Key);
-
- File.WriteAllBytes(tempPath, entry.Value);
- }
-
- /// <summary>
- /// Add a new value in the cache with a given hash.
- /// </summary>
- /// <param name="keyHash">The hash to use for the value in the cache</param>
- /// <param name="value">The value to cache</param>
- public void AddValue(ref Hash128 keyHash, byte[] value)
- {
- if (IsReadOnly)
- {
- Logger.Warning?.Print(LogClass.Gpu, $"Trying to add {keyHash} on a read-only cache, ignoring.");
-
- return;
- }
-
- Debug.Assert(value != null);
-
- bool isAlreadyPresent;
-
- lock (_hashTable)
- {
- isAlreadyPresent = !_hashTable.Add(keyHash);
- }
-
- if (isAlreadyPresent)
- {
- // NOTE: Used for debug
- File.WriteAllBytes(GenCacheTempFilePath(new Hash128()), value);
-
- throw new InvalidOperationException($"Cache collision found on {GenCacheTempFilePath(keyHash)}");
- }
-
- // Queue file change operations
- _fileWriterWorkerQueue.Add(new CacheFileOperationTask
- {
- Type = CacheFileOperation.SaveTempEntry,
- Data = new CacheFileSaveEntryTaskData
- {
- Key = keyHash,
- Value = value
- }
- });
-
- // Save the manifest changes
- _fileWriterWorkerQueue.Add(new CacheFileOperationTask
- {
- Type = CacheFileOperation.SaveManifest,
- });
- }
-
- /// <summary>
- /// Replace a value at the given hash in the cache.
- /// </summary>
- /// <param name="keyHash">The hash to use for the value in the cache</param>
- /// <param name="value">The value to cache</param>
- public void ReplaceValue(ref Hash128 keyHash, byte[] value)
- {
- if (IsReadOnly)
- {
- Logger.Warning?.Print(LogClass.Gpu, $"Trying to replace {keyHash} on a read-only cache, ignoring.");
-
- return;
- }
-
- Debug.Assert(value != null);
-
- // Only queue file change operations
- _fileWriterWorkerQueue.Add(new CacheFileOperationTask
- {
- Type = CacheFileOperation.SaveTempEntry,
- Data = new CacheFileSaveEntryTaskData
- {
- Key = keyHash,
- Value = value
- }
- });
- }
-
- /// <summary>
- /// Removes a value at the given hash from the cache.
- /// </summary>
- /// <param name="keyHash">The hash of the value in the cache</param>
- public void RemoveValue(ref Hash128 keyHash)
- {
- if (IsReadOnly)
- {
- Logger.Warning?.Print(LogClass.Gpu, $"Trying to remove {keyHash} on a read-only cache, ignoring.");
-
- return;
- }
-
- // Only queue file change operations
- _fileWriterWorkerQueue.Add(new CacheFileOperationTask
- {
- Type = CacheFileOperation.RemoveManifestEntryAndTempFile,
- Data = keyHash
- });
- }
-
- public void Dispose()
- {
- Dispose(true);
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- // Make sure all operations on _fileWriterWorkerQueue are done.
- Synchronize();
-
- _fileWriterWorkerQueue.Dispose();
- EnsureArchiveUpToDate();
- }
- }
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs
deleted file mode 100644
index d16afb65..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs
+++ /dev/null
@@ -1,273 +0,0 @@
-using ICSharpCode.SharpZipLib.Zip;
-using Ryujinx.Common;
-using Ryujinx.Common.Configuration;
-using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
-using Ryujinx.Graphics.Shader;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.Graphics.Gpu.Shader.Cache
-{
- /// <summary>
- /// Helper to manipulate the disk shader cache.
- /// </summary>
- static class CacheHelper
- {
- /// <summary>
- /// Compute a cache manifest from runtime data.
- /// </summary>
- /// <param name="version">The version of the cache</param>
- /// <param name="graphicsApi">The graphics api used by the cache</param>
- /// <param name="hashType">The hash type of the cache</param>
- /// <param name="entries">The entries in the cache</param>
- /// <returns>The cache manifest from runtime data</returns>
- public static byte[] ComputeManifest(ulong version, CacheGraphicsApi graphicsApi, CacheHashType hashType, HashSet<Hash128> entries)
- {
- if (hashType != CacheHashType.XxHash128)
- {
- throw new NotImplementedException($"{hashType}");
- }
-
- CacheManifestHeader manifestHeader = new CacheManifestHeader(version, graphicsApi, hashType);
-
- byte[] data = new byte[Unsafe.SizeOf<CacheManifestHeader>() + entries.Count * Unsafe.SizeOf<Hash128>()];
-
- // CacheManifestHeader has the same size as a Hash128.
- Span<Hash128> dataSpan = MemoryMarshal.Cast<byte, Hash128>(data.AsSpan()).Slice(1);
-
- int i = 0;
-
- foreach (Hash128 hash in entries)
- {
- dataSpan[i++] = hash;
- }
-
- manifestHeader.UpdateChecksum(data.AsSpan(Unsafe.SizeOf<CacheManifestHeader>()));
-
- MemoryMarshal.Write(data, ref manifestHeader);
-
- return data;
- }
-
- /// <summary>
- /// Get the base directory of the shader cache for a given title id.
- /// </summary>
- /// <param name="titleId">The title id of the target application</param>
- /// <returns>The base directory of the shader cache for a given title id</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static string GetBaseCacheDirectory(string titleId) => Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader");
-
- /// <summary>
- /// Get the temp path to the cache data directory.
- /// </summary>
- /// <param name="cacheDirectory">The cache directory</param>
- /// <returns>The temp path to the cache data directory</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static string GetCacheTempDataPath(string cacheDirectory) => Path.Combine(cacheDirectory, "temp");
-
- /// <summary>
- /// The path to the cache archive file.
- /// </summary>
- /// <param name="cacheDirectory">The cache directory</param>
- /// <returns>The path to the cache archive file</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static string GetArchivePath(string cacheDirectory) => Path.Combine(cacheDirectory, "cache.zip");
-
- /// <summary>
- /// The path to the cache manifest file.
- /// </summary>
- /// <param name="cacheDirectory">The cache directory</param>
- /// <returns>The path to the cache manifest file</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static string GetManifestPath(string cacheDirectory) => Path.Combine(cacheDirectory, "cache.info");
-
- /// <summary>
- /// Create a new temp path to the given cached file via its hash.
- /// </summary>
- /// <param name="cacheDirectory">The cache directory</param>
- /// <param name="key">The hash of the cached data</param>
- /// <returns>New path to the given cached file</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static string GenCacheTempFilePath(string cacheDirectory, Hash128 key) => Path.Combine(GetCacheTempDataPath(cacheDirectory), key.ToString());
-
- /// <summary>
- /// Generate the path to the cache directory.
- /// </summary>
- /// <param name="baseCacheDirectory">The base of the cache directory</param>
- /// <param name="graphicsApi">The graphics api in use</param>
- /// <param name="shaderProvider">The name of the shader provider in use</param>
- /// <param name="cacheName">The name of the cache</param>
- /// <returns>The path to the cache directory</returns>
- public static string GenerateCachePath(string baseCacheDirectory, CacheGraphicsApi graphicsApi, string shaderProvider, string cacheName)
- {
- string graphicsApiName = graphicsApi switch
- {
- CacheGraphicsApi.OpenGL => "opengl",
- CacheGraphicsApi.OpenGLES => "opengles",
- CacheGraphicsApi.Vulkan => "vulkan",
- CacheGraphicsApi.DirectX => "directx",
- CacheGraphicsApi.Metal => "metal",
- CacheGraphicsApi.Guest => "guest",
- _ => throw new NotImplementedException(graphicsApi.ToString()),
- };
-
- return Path.Combine(baseCacheDirectory, graphicsApiName, shaderProvider, cacheName);
- }
-
- /// <summary>
- /// Read a cached file with the given hash that is present in the archive.
- /// </summary>
- /// <param name="archive">The archive in use</param>
- /// <param name="entry">The given hash</param>
- /// <returns>The cached file if present or null</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static byte[] ReadFromArchive(ZipFile archive, Hash128 entry)
- {
- if (archive != null)
- {
- ZipEntry archiveEntry = archive.GetEntry($"{entry}");
-
- if (archiveEntry != null)
- {
- try
- {
- byte[] result = new byte[archiveEntry.Size];
-
- using (Stream archiveStream = archive.GetInputStream(archiveEntry))
- {
- archiveStream.Read(result);
-
- return result;
- }
- }
- catch (Exception e)
- {
- Logger.Error?.Print(LogClass.Gpu, $"Cannot load cache file {entry} from archive");
- Logger.Error?.Print(LogClass.Gpu, e.ToString());
- }
- }
- }
-
- return null;
- }
-
- /// <summary>
- /// Read a cached file with the given hash that is not present in the archive.
- /// </summary>
- /// <param name="cacheDirectory">The cache directory</param>
- /// <param name="entry">The given hash</param>
- /// <returns>The cached file if present or null</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static byte[] ReadFromFile(string cacheDirectory, Hash128 entry)
- {
- string cacheTempFilePath = GenCacheTempFilePath(cacheDirectory, entry);
-
- try
- {
- return File.ReadAllBytes(cacheTempFilePath);
- }
- catch (Exception e)
- {
- Logger.Error?.Print(LogClass.Gpu, $"Cannot load cache file at {cacheTempFilePath}");
- Logger.Error?.Print(LogClass.Gpu, e.ToString());
- }
-
- return null;
- }
-
- /// <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 TransformFeedbackDescriptorOld[] ReadTransformFeedbackInformation(ref ReadOnlySpan<byte> data, GuestShaderCacheHeader header)
- {
- if (header.TransformFeedbackCount != 0)
- {
- TransformFeedbackDescriptorOld[] result = new TransformFeedbackDescriptorOld[header.TransformFeedbackCount];
-
- for (int i = 0; i < result.Length; i++)
- {
- GuestShaderCacheTransformFeedbackHeader feedbackHeader = MemoryMarshal.Read<GuestShaderCacheTransformFeedbackHeader>(data);
-
- result[i] = new TransformFeedbackDescriptorOld(feedbackHeader.BufferIndex, feedbackHeader.Stride, data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>(), feedbackHeader.VaryingLocationsLength).ToArray());
-
- data = data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>() + feedbackHeader.VaryingLocationsLength);
- }
-
- return result;
- }
-
- return null;
- }
-
- /// <summary>
- /// Save temporary files not in archive.
- /// </summary>
- /// <param name="baseCacheDirectory">The base of the cache directory</param>
- /// <param name="archive">The archive to use</param>
- /// <param name="entries">The entries in the cache</param>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void EnsureArchiveUpToDate(string baseCacheDirectory, ZipFile archive, HashSet<Hash128> entries)
- {
- List<string> filesToDelete = new List<string>();
-
- archive.BeginUpdate();
-
- foreach (Hash128 hash in entries)
- {
- string cacheTempFilePath = GenCacheTempFilePath(baseCacheDirectory, hash);
-
- if (File.Exists(cacheTempFilePath))
- {
- string cacheHash = $"{hash}";
-
- ZipEntry entry = archive.GetEntry(cacheHash);
-
- if (entry != null)
- {
- archive.Delete(entry);
- }
-
- // We enforce deflate compression here to avoid possible incompatibilities on older version of Ryujinx that use System.IO.Compression.
- archive.Add(new StaticDiskDataSource(cacheTempFilePath), cacheHash, CompressionMethod.Deflated);
- filesToDelete.Add(cacheTempFilePath);
- }
- }
-
- archive.CommitUpdate();
-
- foreach (string filePath in filesToDelete)
- {
- File.Delete(filePath);
- }
- }
-
- public static bool IsArchiveReadOnly(string archivePath)
- {
- FileInfo info = new FileInfo(archivePath);
-
- if (!info.Exists)
- {
- return false;
- }
-
- try
- {
- using (FileStream stream = info.Open(FileMode.Open, FileAccess.Read, FileShare.None))
- {
- return false;
- }
- }
- catch (IOException)
- {
- return true;
- }
- }
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs
deleted file mode 100644
index e67221e7..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs
+++ /dev/null
@@ -1,168 +0,0 @@
-using Ryujinx.Common;
-using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
-using System;
-using System.Collections.Generic;
-
-namespace Ryujinx.Graphics.Gpu.Shader.Cache
-{
- /// <summary>
- /// Global Manager of the shader cache.
- /// </summary>
- class CacheManager : IDisposable
- {
- private CacheGraphicsApi _graphicsApi;
- private CacheHashType _hashType;
- private string _shaderProvider;
-
- /// <summary>
- /// Cache storing raw Maxwell shaders as programs.
- /// </summary>
- private CacheCollection _guestProgramCache;
-
- /// <summary>
- /// Cache storing raw host programs.
- /// </summary>
- private CacheCollection _hostProgramCache;
-
- /// <summary>
- /// Version of the guest cache shader (to increment when guest cache structure change).
- /// </summary>
- private const ulong GuestCacheVersion = 1759;
-
- public bool IsReadOnly => _guestProgramCache.IsReadOnly || _hostProgramCache.IsReadOnly;
-
- /// <summary>
- /// Create a new cache manager instance
- /// </summary>
- /// <param name="graphicsApi">The graphics api in use</param>
- /// <param name="hashType">The hash type in use for the cache</param>
- /// <param name="shaderProvider">The name of the codegen provider</param>
- /// <param name="titleId">The guest application title ID</param>
- /// <param name="shaderCodeGenVersion">Version of the codegen</param>
- public CacheManager(CacheGraphicsApi graphicsApi, CacheHashType hashType, string shaderProvider, string titleId, ulong shaderCodeGenVersion)
- {
- _graphicsApi = graphicsApi;
- _hashType = hashType;
- _shaderProvider = shaderProvider;
-
- string baseCacheDirectory = CacheHelper.GetBaseCacheDirectory(titleId);
-
- _guestProgramCache = new CacheCollection(baseCacheDirectory, _hashType, CacheGraphicsApi.Guest, "", "program", GuestCacheVersion);
- _hostProgramCache = new CacheCollection(baseCacheDirectory, _hashType, _graphicsApi, _shaderProvider, "host", shaderCodeGenVersion);
- }
-
-
- /// <summary>
- /// Entries to remove from the manifest.
- /// </summary>
- /// <param name="entries">Entries to remove from the manifest of all caches</param>
- public void RemoveManifestEntries(HashSet<Hash128> entries)
- {
- _guestProgramCache.RemoveManifestEntriesAsync(entries);
- _hostProgramCache.RemoveManifestEntriesAsync(entries);
- }
-
- /// <summary>
- /// Queue a task to flush temporary files to the archives.
- /// </summary>
- public void FlushToArchive()
- {
- _guestProgramCache.FlushToArchiveAsync();
- _hostProgramCache.FlushToArchiveAsync();
- }
-
- /// <summary>
- /// Wait for all tasks before this given point to be done.
- /// </summary>
- public void Synchronize()
- {
- _guestProgramCache.Synchronize();
- _hostProgramCache.Synchronize();
- }
-
- /// <summary>
- /// Save a shader program not present in the program cache.
- /// </summary>
- /// <param name="programCodeHash">Target program code hash</param>
- /// <param name="guestProgram">Guest program raw data</param>
- /// <param name="hostProgram">Host program raw data</param>
- public void SaveProgram(ref Hash128 programCodeHash, byte[] guestProgram, byte[] hostProgram)
- {
- _guestProgramCache.AddValue(ref programCodeHash, guestProgram);
- _hostProgramCache.AddValue(ref programCodeHash, hostProgram);
- }
-
- /// <summary>
- /// Add a host shader program not present in the program cache.
- /// </summary>
- /// <param name="programCodeHash">Target program code hash</param>
- /// <param name="data">Host program raw data</param>
- public void AddHostProgram(ref Hash128 programCodeHash, byte[] data)
- {
- _hostProgramCache.AddValue(ref programCodeHash, data);
- }
-
- /// <summary>
- /// Replace a host shader program present in the program cache.
- /// </summary>
- /// <param name="programCodeHash">Target program code hash</param>
- /// <param name="data">Host program raw data</param>
- public void ReplaceHostProgram(ref Hash128 programCodeHash, byte[] data)
- {
- _hostProgramCache.ReplaceValue(ref programCodeHash, data);
- }
-
- /// <summary>
- /// Removes a shader program present in the program cache.
- /// </summary>
- /// <param name="programCodeHash">Target program code hash</param>
- public void RemoveProgram(ref Hash128 programCodeHash)
- {
- _guestProgramCache.RemoveValue(ref programCodeHash);
- _hostProgramCache.RemoveValue(ref programCodeHash);
- }
-
- /// <summary>
- /// Get all guest program hashes.
- /// </summary>
- /// <returns>All guest program hashes</returns>
- public ReadOnlySpan<Hash128> GetGuestProgramList()
- {
- return _guestProgramCache.HashTable;
- }
-
- /// <summary>
- /// Get a host program by hash.
- /// </summary>
- /// <param name="hash">The given hash</param>
- /// <returns>The host program if present or null</returns>
- public byte[] GetHostProgramByHash(ref Hash128 hash)
- {
- return _hostProgramCache.GetValueRaw(ref hash);
- }
-
- /// <summary>
- /// Get a guest program by hash.
- /// </summary>
- /// <param name="hash">The given hash</param>
- /// <returns>The guest program if present or null</returns>
- public byte[] GetGuestProgramByHash(ref Hash128 hash)
- {
- return _guestProgramCache.GetValueRaw(ref hash);
- }
-
- public void Dispose()
- {
- Dispose(true);
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- _guestProgramCache.Dispose();
- _hostProgramCache.Dispose();
- }
- }
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheGraphicsApi.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheGraphicsApi.cs
deleted file mode 100644
index 9f8b5c39..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheGraphicsApi.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
-{
- /// <summary>
- /// Graphics API type accepted by the shader cache.
- /// </summary>
- enum CacheGraphicsApi : byte
- {
- /// <summary>
- /// OpenGL Core
- /// </summary>
- OpenGL,
-
- /// <summary>
- /// OpenGL ES
- /// </summary>
- OpenGLES,
-
- /// <summary>
- /// Vulkan
- /// </summary>
- Vulkan,
-
- /// <summary>
- /// DirectX
- /// </summary>
- DirectX,
-
- /// <summary>
- /// Metal
- /// </summary>
- Metal,
-
- /// <summary>
- /// Guest, used to cache games raw shader programs.
- /// </summary>
- Guest
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheHashType.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheHashType.cs
deleted file mode 100644
index e4ebe416..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheHashType.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
-{
- /// <summary>
- /// Hash algorithm accepted by the shader cache.
- /// </summary>
- enum CacheHashType : byte
- {
- /// <summary>
- /// xxHash128
- /// </summary>
- XxHash128
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheManifestHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheManifestHeader.cs
deleted file mode 100644
index 0601451d..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheManifestHeader.cs
+++ /dev/null
@@ -1,97 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
-{
- /// <summary>
- /// Header of the shader cache manifest.
- /// </summary>
- [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
- struct CacheManifestHeader
- {
- /// <summary>
- /// The version of the cache.
- /// </summary>
- public ulong Version;
-
- /// <summary>
- /// The graphics api used for this cache.
- /// </summary>
- public CacheGraphicsApi GraphicsApi;
-
- /// <summary>
- /// The hash type used for this cache.
- /// </summary>
- public CacheHashType HashType;
-
- /// <summary>
- /// CRC-16 checksum over the data in the file.
- /// </summary>
- public ushort TableChecksum;
-
- /// <summary>
- /// Construct a new cache manifest header.
- /// </summary>
- /// <param name="version">The version of the cache</param>
- /// <param name="graphicsApi">The graphics api used for this cache</param>
- /// <param name="hashType">The hash type used for this cache</param>
- public CacheManifestHeader(ulong version, CacheGraphicsApi graphicsApi, CacheHashType hashType)
- {
- Version = version;
- GraphicsApi = graphicsApi;
- HashType = hashType;
- TableChecksum = 0;
- }
-
- /// <summary>
- /// Update the checksum in the header.
- /// </summary>
- /// <param name="data">The data to perform the checksum on</param>
- public void UpdateChecksum(ReadOnlySpan<byte> data)
- {
- TableChecksum = CalculateCrc16(data);
- }
-
- /// <summary>
- /// Calculate a CRC-16 over data.
- /// </summary>
- /// <param name="data">The data to perform the CRC-16 on</param>
- /// <returns>A CRC-16 over data</returns>
- private static ushort CalculateCrc16(ReadOnlySpan<byte> data)
- {
- int crc = 0;
-
- const ushort poly = 0x1021;
-
- for (int i = 0; i < data.Length; i++)
- {
- crc ^= data[i] << 8;
-
- for (int j = 0; j < 8; j++)
- {
- crc <<= 1;
-
- if ((crc & 0x10000) != 0)
- {
- crc = (crc ^ poly) & 0xFFFF;
- }
- }
- }
-
- return (ushort)crc;
- }
-
- /// <summary>
- /// Check the validity of the header.
- /// </summary>
- /// <param name="graphicsApi">The target graphics api in use</param>
- /// <param name="hashType">The target hash type in use</param>
- /// <param name="data">The data after this header</param>
- /// <returns>True if the header is valid</returns>
- /// <remarks>This doesn't check that versions match</remarks>
- public bool IsValid(CacheGraphicsApi graphicsApi, CacheHashType hashType, ReadOnlySpan<byte> data)
- {
- return GraphicsApi == graphicsApi && HashType == hashType && TableChecksum == CalculateCrc16(data);
- }
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuAccessorHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuAccessorHeader.cs
deleted file mode 100644
index 2e044750..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuAccessorHeader.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using Ryujinx.Graphics.Shader;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
-{
- /// <summary>
- /// Header of a cached guest gpu accessor.
- /// </summary>
- [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 1)]
- struct GuestGpuAccessorHeader
- {
- /// <summary>
- /// The count of texture descriptors.
- /// </summary>
- public int TextureDescriptorCount;
-
- /// <summary>
- /// Local Size X for compute shaders.
- /// </summary>
- public int ComputeLocalSizeX;
-
- /// <summary>
- /// Local Size Y for compute shaders.
- /// </summary>
- public int ComputeLocalSizeY;
-
- /// <summary>
- /// Local Size Z for compute shaders.
- /// </summary>
- public int ComputeLocalSizeZ;
-
- /// <summary>
- /// Local Memory size in bytes for compute shaders.
- /// </summary>
- public int ComputeLocalMemorySize;
-
- /// <summary>
- /// Shared Memory size in bytes for compute shaders.
- /// </summary>
- public int ComputeSharedMemorySize;
-
- /// <summary>
- /// Unused/reserved.
- /// </summary>
- public int Reserved1;
-
- /// <summary>
- /// Current primitive topology for geometry shaders.
- /// </summary>
- public InputTopology PrimitiveTopology;
-
- /// <summary>
- /// Tessellation parameters (packed to fit on a byte).
- /// </summary>
- public byte TessellationModePacked;
-
- /// <summary>
- /// Unused/reserved.
- /// </summary>
- public byte Reserved2;
-
- /// <summary>
- /// GPU boolean state that can influence shader compilation.
- /// </summary>
- public GuestGpuStateFlags StateFlags;
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuStateFlags.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuStateFlags.cs
deleted file mode 100644
index 4b1fbb06..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuStateFlags.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System;
-
-namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
-{
- [Flags]
- enum GuestGpuStateFlags : byte
- {
- EarlyZForce = 1 << 0
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntry.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntry.cs
deleted file mode 100644
index 373fa6c6..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntry.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
-{
- /// <summary>
- /// Represent a cached shader entry in a guest shader program.
- /// </summary>
- class GuestShaderCacheEntry
- {
- /// <summary>
- /// The header of the cached shader entry.
- /// </summary>
- public GuestShaderCacheEntryHeader Header { get; }
-
- /// <summary>
- /// The code of this shader.
- /// </summary>
- /// <remarks>If a Vertex A is present, this also contains the code 2 section.</remarks>
- public byte[] Code { get; }
-
- /// <summary>
- /// The textures descriptors used for this shader.
- /// </summary>
- public Dictionary<int, GuestTextureDescriptor> TextureDescriptors { get; }
-
- /// <summary>
- /// Create a new instance of <see cref="GuestShaderCacheEntry"/>.
- /// </summary>
- /// <param name="header">The header of the cached shader entry</param>
- /// <param name="code">The code of this shader</param>
- public GuestShaderCacheEntry(GuestShaderCacheEntryHeader header, byte[] code)
- {
- Header = header;
- Code = code;
- TextureDescriptors = new Dictionary<int, GuestTextureDescriptor>();
- }
-
- /// <summary>
- /// Parse a raw cached user shader program into an array of shader cache entry.
- /// </summary>
- /// <param name="data">The raw cached user shader program</param>
- /// <param name="fileHeader">The user shader program header</param>
- /// <returns>An array of shader cache entry</returns>
- public static GuestShaderCacheEntry[] Parse(ref ReadOnlySpan<byte> data, out GuestShaderCacheHeader fileHeader)
- {
- fileHeader = MemoryMarshal.Read<GuestShaderCacheHeader>(data);
-
- data = data.Slice(Unsafe.SizeOf<GuestShaderCacheHeader>());
-
- ReadOnlySpan<GuestShaderCacheEntryHeader> entryHeaders = MemoryMarshal.Cast<byte, GuestShaderCacheEntryHeader>(data.Slice(0, fileHeader.Count * Unsafe.SizeOf<GuestShaderCacheEntryHeader>()));
-
- data = data.Slice(fileHeader.Count * Unsafe.SizeOf<GuestShaderCacheEntryHeader>());
-
- GuestShaderCacheEntry[] result = new GuestShaderCacheEntry[fileHeader.Count];
-
- for (int i = 0; i < result.Length; i++)
- {
- GuestShaderCacheEntryHeader header = entryHeaders[i];
-
- // Ignore empty entries
- if (header.Size == 0 && header.SizeA == 0)
- {
- continue;
- }
-
- byte[] code = data.Slice(0, header.Size + header.SizeA).ToArray();
-
- data = data.Slice(header.Size + header.SizeA);
-
- result[i] = new GuestShaderCacheEntry(header, code);
-
- ReadOnlySpan<GuestTextureDescriptor> textureDescriptors = MemoryMarshal.Cast<byte, GuestTextureDescriptor>(data.Slice(0, header.GpuAccessorHeader.TextureDescriptorCount * Unsafe.SizeOf<GuestTextureDescriptor>()));
-
- foreach (GuestTextureDescriptor textureDescriptor in textureDescriptors)
- {
- result[i].TextureDescriptors.Add((int)textureDescriptor.Handle, textureDescriptor);
- }
-
- data = data.Slice(header.GpuAccessorHeader.TextureDescriptorCount * Unsafe.SizeOf<GuestTextureDescriptor>());
- }
-
- return result;
- }
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntryHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntryHeader.cs
deleted file mode 100644
index 9b22cac5..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntryHeader.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using Ryujinx.Graphics.Shader;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
-{
- /// <summary>
- /// The header of a guest shader entry in a guest shader program.
- /// </summary>
- [StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = 0x30)]
- struct GuestShaderCacheEntryHeader
- {
- /// <summary>
- /// The stage of this shader.
- /// </summary>
- public ShaderStage Stage;
-
- /// <summary>
- /// Unused/reserved.
- /// </summary>
- public byte Reserved1;
-
- /// <summary>
- /// Unused/reserved.
- /// </summary>
- public byte Reserved2;
-
- /// <summary>
- /// Unused/reserved.
- /// </summary>
- public byte Reserved3;
-
- /// <summary>
- /// The size of the code section.
- /// </summary>
- public int Size;
-
- /// <summary>
- /// The size of the code2 section if present. (Vertex A)
- /// </summary>
- public int SizeA;
-
- /// <summary>
- /// Constant buffer 1 data size.
- /// </summary>
- public int Cb1DataSize;
-
- /// <summary>
- /// The header of the cached gpu accessor.
- /// </summary>
- public GuestGpuAccessorHeader GpuAccessorHeader;
-
- /// <summary>
- /// Create a new guest shader entry header.
- /// </summary>
- /// <param name="stage">The stage of this shader</param>
- /// <param name="size">The size of the code section</param>
- /// <param name="sizeA">The size of the code2 section if present (Vertex A)</param>
- /// <param name="cb1DataSize">Constant buffer 1 data size</param>
- /// <param name="gpuAccessorHeader">The header of the cached gpu accessor</param>
- public GuestShaderCacheEntryHeader(ShaderStage stage, int size, int sizeA, int cb1DataSize, GuestGpuAccessorHeader gpuAccessorHeader) : this()
- {
- Stage = stage;
- Size = size;
- SizeA = sizeA;
- Cb1DataSize = cb1DataSize;
- GpuAccessorHeader = gpuAccessorHeader;
- }
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheHeader.cs
deleted file mode 100644
index 700be47d..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheHeader.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
-{
- /// <summary>
- /// The header of a shader program in the guest cache.
- /// </summary>
- [StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = 0x10)]
- struct GuestShaderCacheHeader
- {
- /// <summary>
- /// The count of shaders defining this program.
- /// </summary>
- public byte Count;
-
- /// <summary>
- /// The count of transform feedback data used in this program.
- /// </summary>
- public byte TransformFeedbackCount;
-
- /// <summary>
- /// Unused/reserved.
- /// </summary>
- public ushort Reserved1;
-
- /// <summary>
- /// Unused/reserved.
- /// </summary>
- public ulong Reserved2;
-
- /// <summary>
- /// Create a new guest shader cache header.
- /// </summary>
- /// <param name="count">The count of shaders defining this program</param>
- /// <param name="transformFeedbackCount">The count of transform feedback data used in this program</param>
- public GuestShaderCacheHeader(byte count, byte transformFeedbackCount) : this()
- {
- Count = count;
- TransformFeedbackCount = transformFeedbackCount;
- }
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheTransformFeedbackHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheTransformFeedbackHeader.cs
deleted file mode 100644
index 18cfdf55..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheTransformFeedbackHeader.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
-{
- /// <summary>
- /// Header for transform feedback.
- /// </summary>
- [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
- struct GuestShaderCacheTransformFeedbackHeader
- {
- /// <summary>
- /// The buffer index of the transform feedback.
- /// </summary>
- public int BufferIndex;
-
- /// <summary>
- /// The stride of the transform feedback.
- /// </summary>
- public int Stride;
-
- /// <summary>
- /// The length of the varying location buffer of the transform feedback.
- /// </summary>
- public int VaryingLocationsLength;
-
- /// <summary>
- /// Reserved/unused.
- /// </summary>
- public int Reserved1;
-
- public GuestShaderCacheTransformFeedbackHeader(int bufferIndex, int stride, int varyingLocationsLength) : this()
- {
- BufferIndex = bufferIndex;
- Stride = stride;
- VaryingLocationsLength = varyingLocationsLength;
- }
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestTextureDescriptor.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestTextureDescriptor.cs
deleted file mode 100644
index 9491496d..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestTextureDescriptor.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using Ryujinx.Graphics.Gpu.Image;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
-{
- /// <summary>
- /// Contains part of TextureDescriptor from <see cref="Image"/> used for shader codegen.
- /// </summary>
- [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 1)]
- struct GuestTextureDescriptor : ITextureDescriptor
- {
- public uint Handle;
- public uint Format;
- public TextureTarget Target;
- [MarshalAs(UnmanagedType.I1)]
- public bool IsSrgb;
- [MarshalAs(UnmanagedType.I1)]
- public bool IsTextureCoordNormalized;
- public byte Reserved;
-
- public uint UnpackFormat()
- {
- return Format;
- }
-
- public bool UnpackSrgb()
- {
- return IsSrgb;
- }
-
- public bool UnpackTextureCoordNormalized()
- {
- return IsTextureCoordNormalized;
- }
-
- public TextureTarget UnpackTextureTarget()
- {
- return Target;
- }
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntry.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntry.cs
deleted file mode 100644
index fe79acb3..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntry.cs
+++ /dev/null
@@ -1,222 +0,0 @@
-using Ryujinx.Common;
-using Ryujinx.Graphics.Shader;
-using System;
-using System.IO;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
-{
- /// <summary>
- /// Host shader entry used for binding information.
- /// </summary>
- class HostShaderCacheEntry
- {
- /// <summary>
- /// The header of the cached shader entry.
- /// </summary>
- public HostShaderCacheEntryHeader Header { get; }
-
- /// <summary>
- /// Cached constant buffers.
- /// </summary>
- public BufferDescriptor[] CBuffers { get; }
-
- /// <summary>
- /// Cached storage buffers.
- /// </summary>
- public BufferDescriptor[] SBuffers { get; }
-
- /// <summary>
- /// Cached texture descriptors.
- /// </summary>
- public TextureDescriptor[] Textures { get; }
-
- /// <summary>
- /// Cached image descriptors.
- /// </summary>
- public TextureDescriptor[] Images { get; }
-
- /// <summary>
- /// Create a new instance of <see cref="HostShaderCacheEntry"/>.
- /// </summary>
- /// <param name="header">The header of the cached shader entry</param>
- /// <param name="cBuffers">Cached constant buffers</param>
- /// <param name="sBuffers">Cached storage buffers</param>
- /// <param name="textures">Cached texture descriptors</param>
- /// <param name="images">Cached image descriptors</param>
- private HostShaderCacheEntry(
- HostShaderCacheEntryHeader header,
- BufferDescriptor[] cBuffers,
- BufferDescriptor[] sBuffers,
- TextureDescriptor[] textures,
- TextureDescriptor[] images)
- {
- Header = header;
- CBuffers = cBuffers;
- SBuffers = sBuffers;
- Textures = textures;
- Images = images;
- }
-
- private HostShaderCacheEntry()
- {
- Header = new HostShaderCacheEntryHeader();
- CBuffers = new BufferDescriptor[0];
- SBuffers = new BufferDescriptor[0];
- Textures = new TextureDescriptor[0];
- Images = new TextureDescriptor[0];
- }
-
- private HostShaderCacheEntry(ShaderProgramInfo programInfo)
- {
- Header = new HostShaderCacheEntryHeader(programInfo.CBuffers.Count,
- programInfo.SBuffers.Count,
- programInfo.Textures.Count,
- programInfo.Images.Count,
- programInfo.UsesInstanceId,
- programInfo.UsesRtLayer,
- programInfo.ClipDistancesWritten,
- programInfo.FragmentOutputMap);
- CBuffers = programInfo.CBuffers.ToArray();
- SBuffers = programInfo.SBuffers.ToArray();
- Textures = programInfo.Textures.ToArray();
- Images = programInfo.Images.ToArray();
- }
-
- /// <summary>
- /// Convert the host shader entry to a <see cref="ShaderProgramInfo"/>.
- /// </summary>
- /// <returns>A new <see cref="ShaderProgramInfo"/> from this instance</returns>
- internal ShaderProgramInfo ToShaderProgramInfo()
- {
- return new ShaderProgramInfo(
- CBuffers,
- SBuffers,
- Textures,
- Images,
- default,
- Header.UseFlags.HasFlag(UseFlags.InstanceId),
- Header.UseFlags.HasFlag(UseFlags.RtLayer),
- Header.ClipDistancesWritten,
- Header.FragmentOutputMap);
- }
-
- /// <summary>
- /// Parse a raw cached user shader program into an array of shader cache entry.
- /// </summary>
- /// <param name="data">The raw cached host shader</param>
- /// <param name="programCode">The host shader program</param>
- /// <returns>An array of shader cache entry</returns>
- internal static HostShaderCacheEntry[] Parse(ReadOnlySpan<byte> data, out ReadOnlySpan<byte> programCode)
- {
- HostShaderCacheHeader fileHeader = MemoryMarshal.Read<HostShaderCacheHeader>(data);
-
- data = data.Slice(Unsafe.SizeOf<HostShaderCacheHeader>());
-
- ReadOnlySpan<HostShaderCacheEntryHeader> entryHeaders = MemoryMarshal.Cast<byte, HostShaderCacheEntryHeader>(data.Slice(0, fileHeader.Count * Unsafe.SizeOf<HostShaderCacheEntryHeader>()));
-
- data = data.Slice(fileHeader.Count * Unsafe.SizeOf<HostShaderCacheEntryHeader>());
-
- HostShaderCacheEntry[] result = new HostShaderCacheEntry[fileHeader.Count];
-
- for (int i = 0; i < result.Length; i++)
- {
- HostShaderCacheEntryHeader header = entryHeaders[i];
-
- if (!header.InUse)
- {
- continue;
- }
-
- int cBufferDescriptorsSize = header.CBuffersCount * Unsafe.SizeOf<BufferDescriptor>();
- int sBufferDescriptorsSize = header.SBuffersCount * Unsafe.SizeOf<BufferDescriptor>();
- int textureDescriptorsSize = header.TexturesCount * Unsafe.SizeOf<TextureDescriptor>();
- int imageDescriptorsSize = header.ImagesCount * Unsafe.SizeOf<TextureDescriptor>();
-
- ReadOnlySpan<BufferDescriptor> cBuffers = MemoryMarshal.Cast<byte, BufferDescriptor>(data.Slice(0, cBufferDescriptorsSize));
- data = data.Slice(cBufferDescriptorsSize);
-
- ReadOnlySpan<BufferDescriptor> sBuffers = MemoryMarshal.Cast<byte, BufferDescriptor>(data.Slice(0, sBufferDescriptorsSize));
- data = data.Slice(sBufferDescriptorsSize);
-
- ReadOnlySpan<TextureDescriptor> textureDescriptors = MemoryMarshal.Cast<byte, TextureDescriptor>(data.Slice(0, textureDescriptorsSize));
- data = data.Slice(textureDescriptorsSize);
-
- ReadOnlySpan<TextureDescriptor> imageDescriptors = MemoryMarshal.Cast<byte, TextureDescriptor>(data.Slice(0, imageDescriptorsSize));
- data = data.Slice(imageDescriptorsSize);
-
- result[i] = new HostShaderCacheEntry(header, cBuffers.ToArray(), sBuffers.ToArray(), textureDescriptors.ToArray(), imageDescriptors.ToArray());
- }
-
- programCode = data.Slice(0, fileHeader.CodeSize);
-
- return result;
- }
-
- /// <summary>
- /// Create a new host shader cache file.
- /// </summary>
- /// <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, CachedShaderStage[] codeHolders)
- {
- HostShaderCacheHeader header = new HostShaderCacheHeader((byte)codeHolders.Length, programCode.Length);
-
- HostShaderCacheEntry[] entries = new HostShaderCacheEntry[codeHolders.Length];
-
- for (int i = 0; i < codeHolders.Length; i++)
- {
- if (codeHolders[i] == null)
- {
- entries[i] = new HostShaderCacheEntry();
- }
- else
- {
- entries[i] = new HostShaderCacheEntry(codeHolders[i].Info);
- }
- }
-
- using (MemoryStream stream = new MemoryStream())
- {
- BinaryWriter writer = new BinaryWriter(stream);
-
- writer.WriteStruct(header);
-
- foreach (HostShaderCacheEntry entry in entries)
- {
- writer.WriteStruct(entry.Header);
- }
-
- foreach (HostShaderCacheEntry entry in entries)
- {
- foreach (BufferDescriptor cBuffer in entry.CBuffers)
- {
- writer.WriteStruct(cBuffer);
- }
-
- foreach (BufferDescriptor sBuffer in entry.SBuffers)
- {
- writer.WriteStruct(sBuffer);
- }
-
- foreach (TextureDescriptor texture in entry.Textures)
- {
- writer.WriteStruct(texture);
- }
-
- foreach (TextureDescriptor image in entry.Images)
- {
- writer.WriteStruct(image);
- }
- }
-
- writer.Write(programCode);
-
- return stream.ToArray();
- }
- }
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntryHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntryHeader.cs
deleted file mode 100644
index c3c0de22..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntryHeader.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
-{
- /// <summary>
- /// Flags indicating if the shader accesses certain built-ins, such as the instance ID.
- /// </summary>
- enum UseFlags : byte
- {
- /// <summary>
- /// None of the built-ins are used.
- /// </summary>
- None = 0,
-
- /// <summary>
- /// Indicates whenever the vertex shader reads the gl_InstanceID built-in.
- /// </summary>
- InstanceId = 1 << 0,
-
- /// <summary>
- /// Indicates whenever any of the VTG stages writes to the gl_Layer built-in.
- /// </summary>
- RtLayer = 1 << 1
- }
-
- /// <summary>
- /// Host shader entry header used for binding information.
- /// </summary>
- [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)]
- struct HostShaderCacheEntryHeader
- {
- /// <summary>
- /// Count of constant buffer descriptors.
- /// </summary>
- public int CBuffersCount;
-
- /// <summary>
- /// Count of storage buffer descriptors.
- /// </summary>
- public int SBuffersCount;
-
- /// <summary>
- /// Count of texture descriptors.
- /// </summary>
- public int TexturesCount;
-
- /// <summary>
- /// Count of image descriptors.
- /// </summary>
- public int ImagesCount;
-
- /// <summary>
- /// Flags indicating if the shader accesses certain built-ins, such as the instance ID.
- /// </summary>
- public UseFlags UseFlags;
-
- /// <summary>
- /// Set to true if this entry is in use.
- /// </summary>
- [MarshalAs(UnmanagedType.I1)]
- public bool InUse;
-
- /// <summary>
- /// Mask of clip distances that are written to on the shader.
- /// </summary>
- public byte ClipDistancesWritten;
-
- /// <summary>
- /// Reserved / unused.
- /// </summary>
- public byte Reserved;
-
- /// <summary>
- /// Mask of components written by the fragment shader stage.
- /// </summary>
- public int FragmentOutputMap;
-
- /// <summary>
- /// Create a new host shader cache entry header.
- /// </summary>
- /// <param name="cBuffersCount">Count of constant buffer descriptors</param>
- /// <param name="sBuffersCount">Count of storage buffer descriptors</param>
- /// <param name="texturesCount">Count of texture descriptors</param>
- /// <param name="imagesCount">Count of image descriptors</param>
- /// <param name="usesInstanceId">Set to true if the shader uses instance id</param>
- /// <param name="clipDistancesWritten">Mask of clip distances that are written to on the shader</param>
- /// <param name="fragmentOutputMap">Mask of components written by the fragment shader stage</param>
- public HostShaderCacheEntryHeader(
- int cBuffersCount,
- int sBuffersCount,
- int texturesCount,
- int imagesCount,
- bool usesInstanceId,
- bool usesRtLayer,
- byte clipDistancesWritten,
- int fragmentOutputMap) : this()
- {
- CBuffersCount = cBuffersCount;
- SBuffersCount = sBuffersCount;
- TexturesCount = texturesCount;
- ImagesCount = imagesCount;
- ClipDistancesWritten = clipDistancesWritten;
- FragmentOutputMap = fragmentOutputMap;
- InUse = true;
-
- UseFlags = usesInstanceId ? UseFlags.InstanceId : UseFlags.None;
-
- if (usesRtLayer)
- {
- UseFlags |= UseFlags.RtLayer;
- }
- }
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheHeader.cs
deleted file mode 100644
index 27f216cc..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheHeader.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
-{
- /// <summary>
- /// The header of a shader program in the guest cache.
- /// </summary>
- [StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = 0x10)]
- struct HostShaderCacheHeader
- {
- /// <summary>
- /// The count of shaders defining this program.
- /// </summary>
- public byte Count;
-
- /// <summary>
- /// Unused/reserved.
- /// </summary>
- public byte Reserved1;
-
- /// <summary>
- /// Unused/reserved.
- /// </summary>
- public ushort Reserved2;
-
- /// <summary>
- /// Size of the shader binary.
- /// </summary>
- public int CodeSize;
-
- /// <summary>
- /// Create a new host shader cache header.
- /// </summary>
- /// <param name="count">The count of shaders defining this program</param>
- /// <param name="codeSize">The size of the shader binary</param>
- public HostShaderCacheHeader(byte count, int codeSize) : this()
- {
- Count = count;
- CodeSize = codeSize;
- }
- }
-}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs
deleted file mode 100644
index 885bcd09..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs
+++ /dev/null
@@ -1,258 +0,0 @@
-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,
- false,
- false,
- false);
-
- 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
deleted file mode 100644
index 5e9c6711..00000000
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/TransformFeedbackDescriptorOld.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-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/DiskCache/BackgroundDiskCacheWriter.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs
index 5c5e41c6..98655ed6 100644
--- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs
@@ -83,7 +83,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{
_context = context;
_hostStorage = hostStorage;
- _fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(ProcessTask, "Gpu.BackgroundDiskCacheWriter");
+ _fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(ProcessTask, "GPU.BackgroundDiskCacheWriter");
}
/// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
index e5476426..68ff4f2a 100644
--- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
@@ -1,6 +1,8 @@
using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Runtime.InteropServices;
@@ -16,7 +18,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private readonly ShaderSpecializationState _oldSpecState;
private readonly ShaderSpecializationState _newSpecState;
private readonly int _stageIndex;
- private ResourceCounts _resourceCounts;
+ private readonly bool _isVulkan;
+ private readonly ResourceCounts _resourceCounts;
/// <summary>
/// Creates a new instance of the cached GPU state accessor for shader translation.
@@ -34,13 +37,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
ShaderSpecializationState oldSpecState,
ShaderSpecializationState newSpecState,
ResourceCounts counts,
- int stageIndex) : base(context)
+ int stageIndex) : base(context, counts, stageIndex)
{
_data = data;
_cb1Data = cb1Data;
_oldSpecState = oldSpecState;
_newSpecState = newSpecState;
_stageIndex = stageIndex;
+ _isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
_resourceCounts = counts;
}
@@ -74,27 +78,33 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
/// <inheritdoc/>
- public int QueryBindingConstantBuffer(int index)
+ public AlphaTestOp QueryAlphaTestCompare()
{
- return _resourceCounts.UniformBuffersCount++;
- }
+ if (!_isVulkan || !_oldSpecState.GraphicsState.AlphaTestEnable)
+ {
+ return AlphaTestOp.Always;
+ }
- /// <inheritdoc/>
- public int QueryBindingStorageBuffer(int index)
- {
- return _resourceCounts.StorageBuffersCount++;
+ return _oldSpecState.GraphicsState.AlphaTestCompare switch
+ {
+ CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never,
+ CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less,
+ CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal,
+ CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual,
+ CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater,
+ CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual,
+ CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual,
+ _ => AlphaTestOp.Always
+ };
}
/// <inheritdoc/>
- public int QueryBindingTexture(int index)
- {
- return _resourceCounts.TexturesCount++;
- }
+ public float QueryAlphaTestReference() => _oldSpecState.GraphicsState.AlphaTestReference;
/// <inheritdoc/>
- public int QueryBindingImage(int index)
+ public AttributeType QueryAttributeType(int location)
{
- return _resourceCounts.ImagesCount++;
+ return _oldSpecState.GraphicsState.AttributeTypes[location];
}
/// <inheritdoc/>
@@ -127,6 +137,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
/// <inheritdoc/>
+ public bool QueryProgramPointSize()
+ {
+ return _oldSpecState.GraphicsState.ProgramPointSizeEnable;
+ }
+
+ /// <inheritdoc/>
+ public float QueryPointSize()
+ {
+ return _oldSpecState.GraphicsState.PointSize;
+ }
+
+ /// <inheritdoc/>
public bool QueryTessCw()
{
return _oldSpecState.GraphicsState.TessellationMode.UnpackCw();
@@ -167,6 +189,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
/// <inheritdoc/>
+ public bool QueryTransformDepthMinusOneToOne()
+ {
+ return _oldSpecState.GraphicsState.DepthMode;
+ }
+
+ /// <inheritdoc/>
public bool QueryTransformFeedbackEnabled()
{
return _oldSpecState.TransformFeedbackDescriptors != null;
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
index 4e338094..01034b49 100644
--- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
@@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
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 ushort VersionMinor = 1;
private const uint VersionPacked = ((uint)VersionMajor << 16) | VersionMinor;
private const string TocFileName = "guest.toc";
@@ -193,8 +193,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <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)
+ /// <returns>Guest code and constant buffer 1 data</returns>
+ public GuestCodeAndCbData LoadShader(Stream tocFileStream, Stream dataFileStream, int index)
{
if (_cache == null || index >= _cache.Length)
{
@@ -226,7 +226,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
_cache[index] = (guestCode, cb1Data);
}
- return (guestCode, cb1Data);
+ return new GuestCodeAndCbData(guestCode, cb1Data);
}
/// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
index a47af942..b625835c 100644
--- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
@@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
using System;
using System.IO;
using System.Numerics;
@@ -19,9 +20,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
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 ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
- private const uint CodeGenVersion = 3469;
+ private const uint CodeGenVersion = 13;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";
@@ -56,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
public uint Padding;
/// <summary>
- /// Reserved space, to be used in the future. Write as zero.
+ /// Timestamp of when the file was first created.
/// </summary>
- public ulong Reserved;
+ public ulong Timestamp;
/// <summary>
/// Reserved space, to be used in the future. Write as zero.
/// </summary>
- public ulong Reserved2;
+ public ulong Reserved;
}
/// <summary>
@@ -77,9 +78,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
public ulong Offset;
/// <summary>
- /// Size.
+ /// Size of uncompressed data.
+ /// </summary>
+ public uint UncompressedSize;
+
+ /// <summary>
+ /// Size of compressed data.
/// </summary>
- public uint Size;
+ public uint CompressedSize;
}
/// <summary>
@@ -185,7 +191,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return 0;
}
- return (int)((new FileInfo(tocFilePath).Length - Unsafe.SizeOf<TocHeader>()) / sizeof(ulong));
+ return Math.Max((int)((new FileInfo(tocFilePath).Length - Unsafe.SizeOf<TocHeader>()) / sizeof(ulong)), 0);
}
/// <summary>
@@ -324,7 +330,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
stagesBitMask = 1;
}
- CachedShaderStage[] shaders = new CachedShaderStage[isCompute ? 1 : Constants.ShaderStages + 1];
+ GuestCodeAndCbData?[] guestShaders = new GuestCodeAndCbData?[isCompute ? 1 : Constants.ShaderStages + 1];
DataEntryPerStage stageEntry = new DataEntryPerStage();
@@ -334,15 +340,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
dataReader.Read(ref stageEntry);
- ShaderProgramInfo info = stageIndex != 0 || isCompute ? ReadShaderProgramInfo(ref dataReader) : null;
-
- (byte[] guestCode, byte[] cb1Data) = _guestStorage.LoadShader(
+ guestShaders[stageIndex] = _guestStorage.LoadShader(
guestTocFileStream,
guestDataFileStream,
stageEntry.GuestCodeIndex);
- shaders[stageIndex] = new CachedShaderStage(info, guestCode, cb1Data);
-
stagesBitMask &= ~(1u << stageIndex);
}
@@ -351,17 +353,39 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
if (loadHostCache)
{
- byte[] hostCode = ReadHostCode(context, ref hostTocFileStream, ref hostDataFileStream, programIndex);
+ (byte[] hostCode, CachedShaderStage[] shaders) = ReadHostCode(
+ context,
+ ref hostTocFileStream,
+ ref hostDataFileStream,
+ guestShaders,
+ programIndex,
+ header.Timestamp);
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));
+
+ ShaderInfo shaderInfo = specState.PipelineState.HasValue
+ ? new ShaderInfo(fragmentOutputMap, specState.PipelineState.Value, fromCache: true)
+ : new ShaderInfo(fragmentOutputMap, fromCache: true);
+
+ IProgram hostProgram;
+
+ if (context.Capabilities.Api == TargetApi.Vulkan)
+ {
+ ShaderSource[] shaderSources = ShaderBinarySerializer.Unpack(shaders, hostCode, isCompute);
+
+ hostProgram = context.Renderer.CreateProgram(shaderSources, shaderInfo);
+ }
+ else
+ {
+ hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, shaderInfo);
+ }
CachedShaderProgram program = new CachedShaderProgram(hostProgram, specState, shaders);
- loader.QueueHostProgram(program, hostProgram, programIndex, isCompute);
+ loader.QueueHostProgram(program, hostCode, programIndex, isCompute);
}
else
{
@@ -371,7 +395,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
if (!loadHostCache)
{
- loader.QueueGuestProgram(shaders, specState, programIndex, isCompute);
+ loader.QueueGuestProgram(guestShaders, specState, programIndex, isCompute);
}
loader.CheckCompilation();
@@ -393,9 +417,17 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <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="guestShaders">Guest shader code for each active stage</param>
/// <param name="programIndex">Index of the program on the cache</param>
+ /// <param name="expectedTimestamp">Timestamp of the shared cache file. The host file must be newer than it</param>
/// <returns>Host binary code, or null if not found</returns>
- private byte[] ReadHostCode(GpuContext context, ref Stream tocFileStream, ref Stream dataFileStream, int programIndex)
+ private (byte[], CachedShaderStage[]) ReadHostCode(
+ GpuContext context,
+ ref Stream tocFileStream,
+ ref Stream dataFileStream,
+ GuestCodeAndCbData?[] guestShaders,
+ int programIndex,
+ ulong expectedTimestamp)
{
if (tocFileStream == null && dataFileStream == null)
{
@@ -404,17 +436,28 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath))
{
- return null;
+ return (null, null);
}
tocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: false);
dataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: false);
+
+ BinarySerializer tempTocReader = new BinarySerializer(tocFileStream);
+
+ TocHeader header = new TocHeader();
+
+ tempTocReader.Read(ref header);
+
+ if (header.Timestamp < expectedTimestamp)
+ {
+ return (null, null);
+ }
}
int offset = Unsafe.SizeOf<TocHeader>() + programIndex * Unsafe.SizeOf<OffsetAndSize>();
if (offset + Unsafe.SizeOf<OffsetAndSize>() > tocFileStream.Length)
{
- return null;
+ return (null, null);
}
if ((ulong)offset >= (ulong)dataFileStream.Length)
@@ -436,11 +479,33 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
dataFileStream.Seek((long)offsetAndSize.Offset, SeekOrigin.Begin);
- byte[] hostCode = new byte[offsetAndSize.Size];
+ byte[] hostCode = new byte[offsetAndSize.UncompressedSize];
BinarySerializer.ReadCompressed(dataFileStream, hostCode);
- return hostCode;
+ CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length];
+ BinarySerializer dataReader = new BinarySerializer(dataFileStream);
+
+ dataFileStream.Seek((long)(offsetAndSize.Offset + offsetAndSize.CompressedSize), SeekOrigin.Begin);
+
+ dataReader.BeginCompression();
+
+ for (int index = 0; index < guestShaders.Length; index++)
+ {
+ if (!guestShaders[index].HasValue)
+ {
+ continue;
+ }
+
+ GuestCodeAndCbData guestShader = guestShaders[index].Value;
+ ShaderProgramInfo info = index != 0 || guestShaders.Length == 1 ? ReadShaderProgramInfo(ref dataReader) : null;
+
+ shaders[index] = new CachedShaderStage(info, guestShader.Code, guestShader.Cb1Data);
+ }
+
+ dataReader.EndCompression();
+
+ return (hostCode, shaders);
}
/// <summary>
@@ -484,10 +549,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
var tocFileStream = streams != null ? streams.TocFileStream : DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true);
var dataFileStream = streams != null ? streams.DataFileStream : DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true);
+ ulong timestamp = (ulong)DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds;
+
if (tocFileStream.Length == 0)
{
TocHeader header = new TocHeader();
- CreateToc(tocFileStream, ref header, TocsMagic, CodeGenVersion);
+ CreateToc(tocFileStream, ref header, TocsMagic, CodeGenVersion, timestamp);
}
tocFileStream.Seek(0, SeekOrigin.End);
@@ -519,8 +586,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
stageEntry.GuestCodeIndex = _guestStorage.AddShader(shader.Code, shader.Cb1Data);
dataWriter.Write(ref stageEntry);
-
- WriteShaderProgramInfo(ref dataWriter, shader.Info);
}
program.SpecializationState.Write(ref dataWriter);
@@ -537,7 +602,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return;
}
- WriteHostCode(context, hostCode, -1, streams);
+ WriteHostCode(context, hostCode, program.Shaders, streams, timestamp);
}
/// <summary>
@@ -575,28 +640,19 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
/// <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="shaders">Shader stages to be added to the host cache</param>
/// <param name="streams">Output streams to use</param>
- private void WriteHostCode(GpuContext context, ReadOnlySpan<byte> hostCode, int programIndex, DiskCacheOutputStreams streams = null)
+ /// <param name="timestamp">File creation timestamp</param>
+ private void WriteHostCode(
+ GpuContext context,
+ ReadOnlySpan<byte> hostCode,
+ CachedShaderStage[] shaders,
+ DiskCacheOutputStreams streams,
+ ulong timestamp)
{
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);
@@ -604,29 +660,39 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
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);
+ CreateToc(tocFileStream, ref header, TochMagic, 0, timestamp);
}
+ tocFileStream.Seek(0, SeekOrigin.End);
dataFileStream.Seek(0, SeekOrigin.End);
BinarySerializer tocWriter = new BinarySerializer(tocFileStream);
+ BinarySerializer dataWriter = new BinarySerializer(dataFileStream);
OffsetAndSize offsetAndSize = new OffsetAndSize();
offsetAndSize.Offset = (ulong)dataFileStream.Position;
- offsetAndSize.Size = (uint)hostCode.Length;
- tocWriter.Write(ref offsetAndSize);
+ offsetAndSize.UncompressedSize = (uint)hostCode.Length;
+
+ long dataStartPosition = dataFileStream.Position;
BinarySerializer.WriteCompressed(dataFileStream, hostCode, DiskCacheCommon.GetCompressionAlgorithm());
+ offsetAndSize.CompressedSize = (uint)(dataFileStream.Position - dataStartPosition);
+
+ tocWriter.Write(ref offsetAndSize);
+
+ dataWriter.BeginCompression(DiskCacheCommon.GetCompressionAlgorithm());
+
+ for (int index = 0; index < shaders.Length; index++)
+ {
+ if (shaders[index] != null)
+ {
+ WriteShaderProgramInfo(ref dataWriter, shaders[index].Info);
+ }
+ }
+
+ dataWriter.EndCompression();
+
if (streams == null)
{
tocFileStream.Dispose();
@@ -641,7 +707,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <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)
+ /// <param name="timestamp">File creation timestamp</param>
+ private void CreateToc(Stream tocFileStream, ref TocHeader header, uint magic, uint codegenVersion, ulong timestamp)
{
BinarySerializer writer = new BinarySerializer(tocFileStream);
@@ -650,7 +717,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
header.CodeGenVersion = codegenVersion;
header.Padding = 0;
header.Reserved = 0;
- header.Reserved2 = 0;
+ header.Timestamp = timestamp;
if (tocFileStream.Length > 0)
{
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs
new file mode 100644
index 00000000..b1ac819e
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs
@@ -0,0 +1,31 @@
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
+{
+ /// <summary>
+ /// Guest shader code and constant buffer data accessed by the shader.
+ /// </summary>
+ struct GuestCodeAndCbData
+ {
+ /// <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 guest shader code and constant buffer data.
+ /// </summary>
+ /// <param name="code">Maxwell binary shader code</param>
+ /// <param name="cb1Data">Constant buffer 1 data accessed by the shader</param>
+ public GuestCodeAndCbData(byte[] code, byte[] cb1Data)
+ {
+ Code = code;
+ Cb1Data = cb1Data;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
index af7579d5..7bf1cf4b 100644
--- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
@@ -45,9 +45,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
public readonly CachedShaderProgram CachedProgram;
/// <summary>
- /// Host program.
+ /// Optional binary code. If not null, it is used instead of the backend host binary.
/// </summary>
- public readonly IProgram HostProgram;
+ public readonly byte[] BinaryCode;
/// <summary>
/// Program index.
@@ -68,19 +68,19 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// Creates a new program validation entry.
/// </summary>
/// <param name="cachedProgram">Cached shader program</param>
- /// <param name="hostProgram">Host program</param>
+ /// <param name="binaryCode">Optional binary code. If not null, it is used instead of the backend host binary</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,
+ byte[] binaryCode,
int programIndex,
bool isCompute,
bool isBinary)
{
CachedProgram = cachedProgram;
- HostProgram = hostProgram;
+ BinaryCode = binaryCode;
ProgramIndex = programIndex;
IsCompute = isCompute;
IsBinary = isBinary;
@@ -146,9 +146,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private struct AsyncProgramTranslation
{
/// <summary>
- /// Cached shader stages.
+ /// Guest code for each active stage.
/// </summary>
- public readonly CachedShaderStage[] Shaders;
+ public readonly GuestCodeAndCbData?[] GuestShaders;
/// <summary>
/// Specialization state.
@@ -168,17 +168,17 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <summary>
/// Creates a new program translation entry.
/// </summary>
- /// <param name="shaders">Cached shader stages</param>
+ /// <param name="guestShaders">Guest code for each active stage</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,
+ GuestCodeAndCbData?[] guestShaders,
ShaderSpecializationState specState,
int programIndex,
bool isCompute)
{
- Shaders = shaders;
+ GuestShaders = guestShaders;
SpecializationState = specState;
ProgramIndex = programIndex;
IsCompute = isCompute;
@@ -188,7 +188,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private readonly Queue<ProgramEntry> _validationQueue;
private readonly ConcurrentQueue<ProgramCompilation> _compilationQueue;
private readonly BlockingCollection<AsyncProgramTranslation> _asyncTranslationQueue;
- private readonly SortedList<int, CachedShaderProgram> _programList;
+ private readonly SortedList<int, (CachedShaderProgram, byte[])> _programList;
private int _backendParallelCompileThreads;
private int _compiledCount;
@@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
_validationQueue = new Queue<ProgramEntry>();
_compilationQueue = new ConcurrentQueue<ProgramCompilation>();
_asyncTranslationQueue = new BlockingCollection<AsyncProgramTranslation>(ThreadCount);
- _programList = new SortedList<int, CachedShaderProgram>();
+ _programList = new SortedList<int, (CachedShaderProgram, byte[])>();
_backendParallelCompileThreads = Math.Min(Environment.ProcessorCount, 8); // Must be kept in sync with the backend code.
}
@@ -235,7 +235,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{
workThreads[index] = new Thread(ProcessAsyncQueue)
{
- Name = $"Gpu.AsyncTranslationThread.{index}"
+ Name = $"GPU.AsyncTranslationThread.{index}"
};
}
@@ -287,7 +287,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
CheckCompilationBlocking();
- if (_needsHostRegen)
+ if (_needsHostRegen && Active)
{
// Rebuild both shared and host cache files.
// Rebuilding shared is required because the shader information returned by the translator
@@ -310,8 +310,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
break;
}
- CachedShaderProgram program = kv.Value;
- _hostStorage.AddShader(_context, program, program.HostProgram.GetBinary(), streams);
+ (CachedShaderProgram program, byte[] binaryCode) = kv.Value;
+ _hostStorage.AddShader(_context, program, binaryCode, streams);
}
Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {_programList.Count} shaders successfully.");
@@ -342,24 +342,31 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// Enqueues a host program for compilation.
/// </summary>
/// <param name="cachedProgram">Cached program</param>
- /// <param name="hostProgram">Host program to be compiled</param>
+ /// <param name="binaryCode">Host binary code</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)
+ public void QueueHostProgram(CachedShaderProgram cachedProgram, byte[] binaryCode, int programIndex, bool isCompute)
{
- EnqueueForValidation(new ProgramEntry(cachedProgram, hostProgram, programIndex, isCompute, isBinary: true));
+ EnqueueForValidation(new ProgramEntry(cachedProgram, binaryCode, programIndex, isCompute, isBinary: true));
}
/// <summary>
/// Enqueues a guest program for compilation.
/// </summary>
- /// <param name="shaders">Cached shader stages</param>
+ /// <param name="guestShaders">Guest code for each active stage</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)
+ public void QueueGuestProgram(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
{
- _asyncTranslationQueue.Add(new AsyncProgramTranslation(shaders, specState, programIndex, isCompute));
+ try
+ {
+ AsyncProgramTranslation asyncTranslation = new AsyncProgramTranslation(guestShaders, specState, programIndex, isCompute);
+ _asyncTranslationQueue.Add(asyncTranslation, _cancellationToken);
+ }
+ catch (OperationCanceledException)
+ {
+ }
}
/// <summary>
@@ -374,7 +381,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
// 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);
+ ProgramLinkStatus result = entry.CachedProgram.HostProgram.CheckProgramLink(false);
if (result != ProgramLinkStatus.Incomplete)
{
@@ -398,7 +405,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
while (_validationQueue.TryDequeue(out ProgramEntry entry) && Active)
{
- ProcessCompiledProgram(ref entry, entry.HostProgram.CheckProgramLink(true), asyncCompile: false);
+ ProcessCompiledProgram(ref entry, entry.CachedProgram.HostProgram.CheckProgramLink(true), asyncCompile: false);
}
}
@@ -427,7 +434,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
_needsHostRegen = true;
}
- _programList.Add(entry.ProgramIndex, entry.CachedProgram);
+ _programList.Add(entry.ProgramIndex, (entry.CachedProgram, entry.BinaryCode));
SignalCompiled();
}
else if (entry.IsBinary)
@@ -436,13 +443,25 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
// we still have a chance to recompile from the guest binary.
CachedShaderProgram program = entry.CachedProgram;
+ GuestCodeAndCbData?[] guestShaders = new GuestCodeAndCbData?[program.Shaders.Length];
+
+ for (int index = 0; index < program.Shaders.Length; index++)
+ {
+ CachedShaderStage shader = program.Shaders[index];
+
+ if (shader != null)
+ {
+ guestShaders[index] = new GuestCodeAndCbData(shader.Code, shader.Cb1Data);
+ }
+ }
+
if (asyncCompile)
{
- QueueGuestProgram(program.Shaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
+ QueueGuestProgram(guestShaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
}
else
{
- RecompileFromGuestCode(program.Shaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
+ RecompileFromGuestCode(guestShaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
ProcessCompilationQueue();
}
}
@@ -476,10 +495,16 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
}
- IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, new ShaderInfo(fragmentOutputMap));
+ ShaderInfo shaderInfo = compilation.SpecializationState.PipelineState.HasValue
+ ? new ShaderInfo(fragmentOutputMap, compilation.SpecializationState.PipelineState.Value, fromCache: true)
+ : new ShaderInfo(fragmentOutputMap, fromCache: true);
+
+ IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, shaderInfo);
CachedShaderProgram program = new CachedShaderProgram(hostProgram, compilation.SpecializationState, compilation.Shaders);
- EnqueueForValidation(new ProgramEntry(program, hostProgram, compilation.ProgramIndex, compilation.IsCompute, isBinary: false));
+ byte[] binaryCode = _context.Capabilities.Api == TargetApi.Vulkan ? ShaderBinarySerializer.Pack(shaderSources) : hostProgram.GetBinary();
+
+ EnqueueForValidation(new ProgramEntry(program, binaryCode, compilation.ProgramIndex, compilation.IsCompute, isBinary: false));
}
}
@@ -496,7 +521,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
// 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);
+ ProcessCompiledProgram(ref entry, entry.CachedProgram.HostProgram.CheckProgramLink(true), asyncCompile: false);
}
}
@@ -513,7 +538,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
foreach (AsyncProgramTranslation asyncCompilation in _asyncTranslationQueue.GetConsumingEnumerable(ct))
{
RecompileFromGuestCode(
- asyncCompilation.Shaders,
+ asyncCompilation.GuestShaders,
asyncCompilation.SpecializationState,
asyncCompilation.ProgramIndex,
asyncCompilation.IsCompute);
@@ -527,21 +552,21 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <summary>
/// Recompiles a program from guest code.
/// </summary>
- /// <param name="shaders">Shader stages</param>
+ /// <param name="guestShaders">Guest code for each active stage</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)
+ private void RecompileFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
{
try
{
if (isCompute)
{
- RecompileComputeFromGuestCode(shaders, specState, programIndex);
+ RecompileComputeFromGuestCode(guestShaders, specState, programIndex);
}
else
{
- RecompileGraphicsFromGuestCode(shaders, specState, programIndex);
+ RecompileGraphicsFromGuestCode(guestShaders, specState, programIndex);
}
}
catch (DiskCacheLoadException diskCacheLoadException)
@@ -556,41 +581,47 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <summary>
/// Recompiles a graphics program from guest code.
/// </summary>
- /// <param name="shaders">Shader stages</param>
+ /// <param name="guestShaders">Guest code for each active stage</param>
/// <param name="specState">Specialization state</param>
/// <param name="programIndex">Program index</param>
- private void RecompileGraphicsFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex)
+ private void RecompileGraphicsFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex)
{
- ShaderSpecializationState newSpecState = new ShaderSpecializationState(specState.GraphicsState, specState.TransformFeedbackDescriptors);
+ ShaderSpecializationState newSpecState = new ShaderSpecializationState(
+ ref specState.GraphicsState,
+ specState.PipelineState,
+ specState.TransformFeedbackDescriptors);
+
ResourceCounts counts = new ResourceCounts();
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
TranslatorContext nextStage = null;
+ TargetApi api = _context.Capabilities.Api;
+
for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
{
- CachedShaderStage shader = shaders[stageIndex + 1];
-
- if (shader != null)
+ if (guestShaders[stageIndex + 1].HasValue)
{
+ GuestCodeAndCbData shader = guestShaders[stageIndex + 1].Value;
+
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);
+ TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, 0);
if (nextStage != null)
{
currentStage.SetNextStage(nextStage);
}
- if (stageIndex == 0 && shaders[0] != null)
+ if (stageIndex == 0 && guestShaders[0].HasValue)
{
- byte[] guestCodeA = shaders[0].Code;
- byte[] cb1DataA = shaders[0].Cb1Data;
+ byte[] guestCodeA = guestShaders[0].Value.Code;
+ byte[] cb1DataA = guestShaders[0].Value.Cb1Data;
DiskCacheGpuAccessor gpuAccessorA = new DiskCacheGpuAccessor(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0);
- translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, DefaultFlags | TranslationFlags.VertexA, 0);
+ translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0);
}
translatorContexts[stageIndex + 1] = currentStage;
@@ -598,6 +629,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
}
+ CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length];
List<ShaderProgram> translatedStages = new List<ShaderProgram>();
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
@@ -608,15 +640,15 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{
ShaderProgram program;
- byte[] guestCode = shaders[stageIndex + 1].Code;
- byte[] cb1Data = shaders[stageIndex + 1].Cb1Data;
+ byte[] guestCode = guestShaders[stageIndex + 1].Value.Code;
+ byte[] cb1Data = guestShaders[stageIndex + 1].Value.Cb1Data;
- if (stageIndex == 0 && shaders[0] != null)
+ if (stageIndex == 0 && guestShaders[0].HasValue)
{
program = currentStage.Translate(translatorContexts[0]);
- byte[] guestCodeA = shaders[0].Code;
- byte[] cb1DataA = shaders[0].Cb1Data;
+ byte[] guestCodeA = guestShaders[0].Value.Code;
+ byte[] cb1DataA = guestShaders[0].Value.Cb1Data;
shaders[0] = new CachedShaderStage(null, guestCodeA, cb1DataA);
shaders[1] = new CachedShaderStage(program.Info, guestCode, cb1Data);
@@ -641,21 +673,21 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <summary>
/// Recompiles a compute program from guest code.
/// </summary>
- /// <param name="shaders">Shader stages</param>
+ /// <param name="guestShaders">Guest code for each active stage</param>
/// <param name="specState">Specialization state</param>
/// <param name="programIndex">Program index</param>
- private void RecompileComputeFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex)
+ private void RecompileComputeFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex)
{
- CachedShaderStage shader = shaders[0];
+ GuestCodeAndCbData shader = guestShaders[0].Value;
ResourceCounts counts = new ResourceCounts();
- ShaderSpecializationState newSpecState = new ShaderSpecializationState(specState.ComputeState);
+ ShaderSpecializationState newSpecState = new ShaderSpecializationState(ref specState.ComputeState);
DiskCacheGpuAccessor gpuAccessor = new DiskCacheGpuAccessor(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0);
- TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, 0);
+ TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0);
ShaderProgram program = translatorContext.Translate();
- shaders[0] = new CachedShaderStage(program.Info, shader.Code, shader.Cb1Data);
+ CachedShaderStage[] shaders = new[] { new CachedShaderStage(program.Info, shader.Code, shader.Cb1Data) };
_compilationQueue.Enqueue(new ProgramCompilation(new[] { program }, shaders, newSpecState, programIndex, isCompute: true));
}
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs
new file mode 100644
index 00000000..11e54220
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs
@@ -0,0 +1,49 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader.Translation;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
+{
+ static class ShaderBinarySerializer
+ {
+ public static byte[] Pack(ShaderSource[] sources)
+ {
+ using MemoryStream output = new MemoryStream();
+ using BinaryWriter writer = new BinaryWriter(output);
+
+ for (int i = 0; i < sources.Length; i++)
+ {
+ writer.Write(sources[i].BinaryCode.Length);
+ writer.Write(sources[i].BinaryCode);
+ }
+
+ return output.ToArray();
+ }
+
+ public static ShaderSource[] Unpack(CachedShaderStage[] stages, byte[] code, bool compute)
+ {
+ using MemoryStream input = new MemoryStream(code);
+ using BinaryReader reader = new BinaryReader(input);
+
+ List<ShaderSource> output = new List<ShaderSource>();
+
+ for (int i = compute ? 0 : 1; i < stages.Length; i++)
+ {
+ CachedShaderStage stage = stages[i];
+
+ if (stage == null)
+ {
+ continue;
+ }
+
+ int binaryCodeLength = reader.ReadInt32();
+ byte[] binaryCode = reader.ReadBytes(binaryCodeLength);
+
+ output.Add(new ShaderSource(binaryCode, ShaderCache.GetBindings(stage.Info), stage.Info.Stage, TargetLanguage.Spirv));
+ }
+
+ return output.ToArray();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
index 5317aab9..44c26efb 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
@@ -1,6 +1,8 @@
using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Runtime.InteropServices;
@@ -15,6 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
private readonly GpuAccessorState _state;
private readonly int _stageIndex;
private readonly bool _compute;
+ private readonly bool _isVulkan;
/// <summary>
/// Creates a new instance of the GPU state accessor for graphics shader translation.
@@ -23,8 +26,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="channel">GPU channel</param>
/// <param name="state">Current GPU state</param>
/// <param name="stageIndex">Graphics shader stage index (0 = Vertex, 4 = Fragment)</param>
- public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state, int stageIndex) : base(context)
+ public GpuAccessor(
+ GpuContext context,
+ GpuChannel channel,
+ GpuAccessorState state,
+ int stageIndex) : base(context, state.ResourceCounts, stageIndex)
{
+ _isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
_channel = channel;
_state = state;
_stageIndex = stageIndex;
@@ -36,7 +44,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="context">GPU context</param>
/// <param name="channel">GPU channel</param>
/// <param name="state">Current GPU state</param>
- public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context)
+ public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0)
{
_channel = channel;
_state = state;
@@ -73,27 +81,36 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <inheritdoc/>
- public int QueryBindingConstantBuffer(int index)
+ public AlphaTestOp QueryAlphaTestCompare()
{
- return _state.ResourceCounts.UniformBuffersCount++;
- }
+ if (!_isVulkan || !_state.GraphicsState.AlphaTestEnable)
+ {
+ return AlphaTestOp.Always;
+ }
- /// <inheritdoc/>
- public int QueryBindingStorageBuffer(int index)
- {
- return _state.ResourceCounts.StorageBuffersCount++;
+ return _state.GraphicsState.AlphaTestCompare switch
+ {
+ CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never,
+ CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less,
+ CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal,
+ CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual,
+ CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater,
+ CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual,
+ CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual,
+ _ => AlphaTestOp.Always
+ };
}
/// <inheritdoc/>
- public int QueryBindingTexture(int index)
+ public float QueryAlphaTestReference()
{
- return _state.ResourceCounts.TexturesCount++;
+ return _state.GraphicsState.AlphaTestReference;
}
/// <inheritdoc/>
- public int QueryBindingImage(int index)
+ public AttributeType QueryAttributeType(int location)
{
- return _state.ResourceCounts.ImagesCount++;
+ return _state.GraphicsState.AttributeTypes[location];
}
/// <inheritdoc/>
@@ -130,6 +147,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <inheritdoc/>
+ public bool QueryProgramPointSize()
+ {
+ return _state.GraphicsState.ProgramPointSizeEnable;
+ }
+
+ /// <inheritdoc/>
+ public float QueryPointSize()
+ {
+ return _state.GraphicsState.PointSize;
+ }
+
+ /// <inheritdoc/>
public bool QueryTessCw()
{
return _state.GraphicsState.TessellationMode.UnpackCw();
@@ -199,6 +228,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <inheritdoc/>
+ public bool QueryTransformDepthMinusOneToOne()
+ {
+ return _state.GraphicsState.DepthMode;
+ }
+
+ /// <inheritdoc/>
public bool QueryTransformFeedbackEnabled()
{
return _state.TransformFeedbackDescriptors != null;
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
index 5f9dd588..7243f643 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
@@ -1,7 +1,9 @@
+using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
namespace Ryujinx.Graphics.Gpu.Shader
{
@@ -11,74 +13,140 @@ namespace Ryujinx.Graphics.Gpu.Shader
class GpuAccessorBase
{
private readonly GpuContext _context;
+ private readonly ResourceCounts _resourceCounts;
+ private readonly int _stageIndex;
/// <summary>
/// Creates a new GPU accessor.
/// </summary>
/// <param name="context">GPU context</param>
- public GpuAccessorBase(GpuContext context)
+ public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex)
{
_context = context;
+ _resourceCounts = resourceCounts;
+ _stageIndex = stageIndex;
}
- /// <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>
+ /// <inheritdoc/>
+ public int QueryBindingConstantBuffer(int index)
+ {
+ if (_context.Capabilities.Api == TargetApi.Vulkan)
+ {
+ // We need to start counting from 1 since binding 0 is reserved for the support uniform buffer.
+ return GetBindingFromIndex(index, _context.Capabilities.MaximumUniformBuffersPerStage, "Uniform buffer") + 1;
+ }
+ else
+ {
+ return _resourceCounts.UniformBuffersCount++;
+ }
+ }
+
+ /// <inheritdoc/>
+ public int QueryBindingStorageBuffer(int index)
+ {
+ if (_context.Capabilities.Api == TargetApi.Vulkan)
+ {
+ return GetBindingFromIndex(index, _context.Capabilities.MaximumStorageBuffersPerStage, "Storage buffer");
+ }
+ else
+ {
+ return _resourceCounts.StorageBuffersCount++;
+ }
+ }
+
+ /// <inheritdoc/>
+ public int QueryBindingTexture(int index, bool isBuffer)
+ {
+ if (_context.Capabilities.Api == TargetApi.Vulkan)
+ {
+ if (isBuffer)
+ {
+ index += (int)_context.Capabilities.MaximumTexturesPerStage;
+ }
+
+ return GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture");
+ }
+ else
+ {
+ return _resourceCounts.TexturesCount++;
+ }
+ }
+
+ /// <inheritdoc/>
+ public int QueryBindingImage(int index, bool isBuffer)
+ {
+ if (_context.Capabilities.Api == TargetApi.Vulkan)
+ {
+ if (isBuffer)
+ {
+ index += (int)_context.Capabilities.MaximumImagesPerStage;
+ }
+
+ return GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image");
+ }
+ else
+ {
+ return _resourceCounts.ImagesCount++;
+ }
+ }
+
+ private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName)
+ {
+ if ((uint)index >= maxPerStage)
+ {
+ Logger.Error?.Print(LogClass.Gpu, $"{resourceName} index {index} exceeds per stage limit of {maxPerStage}.");
+ }
+
+ return GetStageIndex() * (int)maxPerStage + index;
+ }
+
+ private int GetStageIndex()
+ {
+ // This is just a simple remapping to ensure that most frequently used shader stages
+ // have the lowest binding numbers.
+ // This is useful because if we need to run on a system with a low limit on the bindings,
+ // then we can still get most games working as the most common shaders will have low binding numbers.
+ return _stageIndex switch
+ {
+ 4 => 1, // Fragment
+ 3 => 2, // Geometry
+ 1 => 3, // Tessellation control
+ 2 => 4, // Tessellation evaluation
+ _ => 0 // Vertex/Compute
+ };
+ }
+
+ /// <inheritdoc/>
public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug;
- /// <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>
+ /// <inheritdoc/>
public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug;
- /// <summary>
- /// Queries host storage buffer alignment required.
- /// </summary>
- /// <returns>Host storage buffer alignment in bytes</returns>
+ /// <inheritdoc/>
public int QueryHostStorageBufferOffsetAlignment() => _context.Capabilities.StorageBufferOffsetAlignment;
- /// <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>
+ /// <inheritdoc/>
public bool QueryHostSupportsBgraFormat() => _context.Capabilities.SupportsBgraFormat;
- /// <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>
+ /// <inheritdoc/>
public bool QueryHostSupportsFragmentShaderInterlock() => _context.Capabilities.SupportsFragmentShaderInterlock;
- /// <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>
+ /// <inheritdoc/>
public bool QueryHostSupportsFragmentShaderOrderingIntel() => _context.Capabilities.SupportsFragmentShaderOrderingIntel;
- /// <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>
+ /// <inheritdoc/>
+ public bool QueryHostSupportsGeometryShaderPassthrough() => _context.Capabilities.SupportsGeometryShaderPassthrough;
+
+ /// <inheritdoc/>
public bool QueryHostSupportsImageLoadFormatted() => _context.Capabilities.SupportsImageLoadFormatted;
- /// <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>
+ /// <inheritdoc/>
public bool QueryHostSupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset;
- /// <summary>
- /// Queries host GPU shader ballot support.
- /// </summary>
- /// <returns>True if the GPU and driver supports shader ballot, false otherwise</returns>
+ /// <inheritdoc/>
public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot;
- /// <summary>
- /// Queries host GPU texture shadow LOD support.
- /// </summary>
- /// <returns>True if the GPU and driver supports texture shadow LOD, false otherwise</returns>
+ /// <inheritdoc/>
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
/// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
index fae670ea..82252ced 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
@@ -1,5 +1,7 @@
+using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Threed;
+using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.Gpu.Shader
{
@@ -26,19 +28,54 @@ namespace Ryujinx.Graphics.Gpu.Shader
public readonly TessMode TessellationMode;
/// <summary>
- /// Indicates whenever the viewport transform is disabled.
+ /// Indicates whether alpha-to-coverage is enabled.
+ /// </summary>
+ public readonly bool AlphaToCoverageEnable;
+
+ /// <summary>
+ /// Indicates whether alpha-to-coverage dithering is enabled.
+ /// </summary>
+ public readonly bool AlphaToCoverageDitherEnable;
+
+ /// <summary>
+ /// Indicates whether the viewport transform is disabled.
/// </summary>
public readonly bool ViewportTransformDisable;
/// <summary>
- /// Indicates whenever alpha-to-coverage is enabled.
+ /// Depth mode zero to one or minus one to one.
/// </summary>
- public readonly bool AlphaToCoverageEnable;
+ public readonly bool DepthMode;
/// <summary>
- /// Indicates whenever alpha-to-coverage dithering is enabled.
+ /// Indicates if the point size is set on the shader or is fixed.
/// </summary>
- public readonly bool AlphaToCoverageDitherEnable;
+ public readonly bool ProgramPointSizeEnable;
+
+ /// <summary>
+ /// Point size used if <see cref="ProgramPointSizeEnable" /> is false.
+ /// </summary>
+ public readonly float PointSize;
+
+ /// <summary>
+ /// Indicates whether alpha test is enabled.
+ /// </summary>
+ public readonly bool AlphaTestEnable;
+
+ /// <summary>
+ /// When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded.
+ /// </summary>
+ public readonly CompareOp AlphaTestCompare;
+
+ /// <summary>
+ /// When alpha test is enabled, indicates the value to compare with the fragment output alpha.
+ /// </summary>
+ public readonly float AlphaTestReference;
+
+ /// <summary>
+ /// Type of the vertex attributes consumed by the shader.
+ /// </summary>
+ public Array32<AttributeType> AttributeTypes;
/// <summary>
/// Creates a new GPU graphics state.
@@ -46,23 +83,44 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="earlyZForce">Early Z force enable</param>
/// <param name="topology">Primitive topology</param>
/// <param name="tessellationMode">Tessellation mode</param>
- /// <param name="viewportTransformDisable">Indicates whenever the viewport transform is disabled</param>
- /// <param name="alphaToCoverageEnable">Indicates whenever alpha-to-coverage is enabled</param>
- /// <param name="alphaToCoverageDitherEnable">Indicates whenever alpha-to-coverage dithering is enabled</param>
+ /// <param name="alphaToCoverageEnable">Indicates whether alpha-to-coverage is enabled</param>
+ /// <param name="alphaToCoverageDitherEnable">Indicates whether alpha-to-coverage dithering is enabled</param>
+ /// <param name="viewportTransformDisable">Indicates whether the viewport transform is disabled</param>
+ /// <param name="depthMode">Depth mode zero to one or minus one to one</param>
+ /// <param name="programPointSizeEnable">Indicates if the point size is set on the shader or is fixed</param>
+ /// <param name="pointSize">Point size if not set from shader</param>
+ /// <param name="alphaTestEnable">Indicates whether alpha test is enabled</param>
+ /// <param name="alphaTestCompare">When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded</param>
+ /// <param name="alphaTestReference">When alpha test is enabled, indicates the value to compare with the fragment output alpha</param>
+ /// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
public GpuChannelGraphicsState(
bool earlyZForce,
PrimitiveTopology topology,
TessMode tessellationMode,
- bool viewportTransformDisable,
bool alphaToCoverageEnable,
- bool alphaToCoverageDitherEnable)
+ bool alphaToCoverageDitherEnable,
+ bool viewportTransformDisable,
+ bool depthMode,
+ bool programPointSizeEnable,
+ float pointSize,
+ bool alphaTestEnable,
+ CompareOp alphaTestCompare,
+ float alphaTestReference,
+ ref Array32<AttributeType> attributeTypes)
{
EarlyZForce = earlyZForce;
Topology = topology;
TessellationMode = tessellationMode;
- ViewportTransformDisable = viewportTransformDisable;
AlphaToCoverageEnable = alphaToCoverageEnable;
AlphaToCoverageDitherEnable = alphaToCoverageDitherEnable;
+ ViewportTransformDisable = viewportTransformDisable;
+ DepthMode = depthMode;
+ ProgramPointSizeEnable = programPointSizeEnable;
+ PointSize = pointSize;
+ AlphaTestEnable = alphaTestEnable;
+ AlphaTestCompare = alphaTestCompare;
+ AlphaTestReference = alphaTestReference;
+ AttributeTypes = attributeTypes;
}
}
} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index 0779bf2c..c998fe09 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -1,13 +1,17 @@
+using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Threed;
+using Ryujinx.Graphics.Gpu.Engine.Types;
+using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
-using Ryujinx.Graphics.Gpu.Shader.Cache;
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Shader
@@ -59,11 +63,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
public readonly CachedShaderProgram CachedProgram;
public readonly IProgram HostProgram;
+ public readonly byte[] BinaryCode;
- public ProgramToSave(CachedShaderProgram cachedProgram, IProgram hostProgram)
+ public ProgramToSave(CachedShaderProgram cachedProgram, IProgram hostProgram, byte[] binaryCode)
{
CachedProgram = cachedProgram;
HostProgram = hostProgram;
+ BinaryCode = binaryCode;
}
}
@@ -94,9 +100,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
_programsToSaveQueue = new Queue<ProgramToSave>();
- string diskCacheTitleId = GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null
- ? CacheHelper.GetBaseCacheDirectory(GraphicsConfig.TitleId)
- : null;
+ string diskCacheTitleId = GetDiskCachePath();
_computeShaderCache = new ComputeShaderCacheHashTable();
_graphicsShaderCache = new ShaderCacheHashTable();
@@ -109,6 +113,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
+ /// Gets the path where the disk cache for the current application is stored.
+ /// </summary>
+ private static string GetDiskCachePath()
+ {
+ return GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null
+ ? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId, "cache", "shader")
+ : null;
+ }
+
+ /// <summary>
/// Processes the queue of shaders that must save their binaries to the disk cache.
/// </summary>
public void ProcessShaderCacheQueue()
@@ -123,7 +137,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
if (result == ProgramLinkStatus.Success)
{
- _cacheWriter.AddShader(programToSave.CachedProgram, programToSave.HostProgram.GetBinary());
+ _cacheWriter.AddShader(programToSave.CachedProgram, programToSave.BinaryCode ?? programToSave.HostProgram.GetBinary());
}
_programsToSaveQueue.Dequeue();
@@ -143,16 +157,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
if (_diskCacheHostStorage.CacheEnabled)
{
- if (!_diskCacheHostStorage.CacheExists())
- {
- // 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...");
-
- int migrationCount = Migration.MigrateFromLegacyCache(_context, _diskCacheHostStorage);
-
- Logger.Info?.Print(LogClass.Gpu, $"Migrated {migrationCount} shaders.");
- }
-
ParallelDiskCacheLoader loader = new ParallelDiskCacheLoader(
_context,
_graphicsShaderCache,
@@ -210,26 +214,75 @@ namespace Ryujinx.Graphics.Gpu.Shader
return cpShader;
}
- ShaderSpecializationState specState = new ShaderSpecializationState(computeState);
+ ShaderSpecializationState specState = new ShaderSpecializationState(ref computeState);
GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, computeState, default, specState);
GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState);
- TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, gpuVa);
+ TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa);
TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode);
- IProgram hostProgram = _context.Renderer.CreateProgram(new ShaderSource[] { CreateShaderSource(translatedShader.Program) }, new ShaderInfo(-1));
+ ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) };
+
+ IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(-1));
cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader);
_computeShaderCache.Add(cpShader);
- EnqueueProgramToSave(new ProgramToSave(cpShader, hostProgram));
+ EnqueueProgramToSave(cpShader, hostProgram, shaderSourcesArray);
_cpPrograms[gpuVa] = cpShader;
return cpShader;
}
/// <summary>
+ /// Updates the shader pipeline state based on the current GPU state.
+ /// </summary>
+ /// <param name="state">Current GPU 3D engine state</param>
+ /// <param name="pipeline">Shader pipeline state to be updated</param>
+ /// <param name="graphicsState">Current graphics state</param>
+ /// <param name="channel">Current GPU channel</param>
+ private void UpdatePipelineInfo(
+ ref ThreedClassState state,
+ ref ProgramPipelineState pipeline,
+ GpuChannelGraphicsState graphicsState,
+ GpuChannel channel)
+ {
+ channel.TextureManager.UpdateRenderTargets();
+
+ var rtControl = state.RtControl;
+ var msaaMode = state.RtMsaaMode;
+
+ pipeline.SamplesCount = msaaMode.SamplesInX() * msaaMode.SamplesInY();
+
+ int count = rtControl.UnpackCount();
+
+ for (int index = 0; index < Constants.TotalRenderTargets; index++)
+ {
+ int rtIndex = rtControl.UnpackPermutationIndex(index);
+
+ var colorState = state.RtColorState[rtIndex];
+
+ if (index >= count || colorState.Format == 0 || colorState.WidthOrStride == 0)
+ {
+ pipeline.AttachmentEnable[index] = false;
+ pipeline.AttachmentFormats[index] = Format.R8G8B8A8Unorm;
+ }
+ else
+ {
+ pipeline.AttachmentEnable[index] = true;
+ pipeline.AttachmentFormats[index] = colorState.Format.Convert().Format;
+ }
+ }
+
+ pipeline.DepthStencilEnable = state.RtDepthStencilEnable;
+ pipeline.DepthStencilFormat = pipeline.DepthStencilEnable ? state.RtDepthStencilState.Format.Convert().Format : Format.D24UnormS8Uint;
+
+ pipeline.VertexBufferCount = Constants.TotalVertexBuffers;
+ pipeline.Topology = graphicsState.Topology;
+ }
+
+ /// <summary>
/// Gets a graphics shader program from the shader cache.
/// This includes all the specified shader stages.
/// </summary>
@@ -237,6 +290,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// This automatically translates, compiles and adds the code to the cache if not present.
/// </remarks>
/// <param name="state">GPU state</param>
+ /// <param name="pipeline">Pipeline state</param>
/// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">3D engine state</param>
@@ -244,6 +298,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <returns>Compiled graphics shader code</returns>
public CachedShaderProgram GetGraphicsShader(
ref ThreedClassState state,
+ ref ProgramPipelineState pipeline,
GpuChannel channel,
GpuChannelPoolState poolState,
GpuChannelGraphicsState graphicsState,
@@ -262,7 +317,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
TransformFeedbackDescriptor[] transformFeedbackDescriptors = GetTransformFeedbackDescriptors(ref state);
- ShaderSpecializationState specState = new ShaderSpecializationState(graphicsState, transformFeedbackDescriptors);
+ UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel);
+
+ ShaderSpecializationState specState = new ShaderSpecializationState(ref graphicsState, ref pipeline, transformFeedbackDescriptors);
GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, default, graphicsState, specState, transformFeedbackDescriptors);
ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();
@@ -270,6 +327,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
TranslatorContext nextStage = null;
+ TargetApi api = _context.Capabilities.Api;
+
for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
{
ulong gpuVa = addressesSpan[stageIndex + 1];
@@ -277,7 +336,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (gpuVa != 0)
{
GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState, stageIndex);
- TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, DefaultFlags, gpuVa);
+ TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, gpuVa);
if (nextStage != null)
{
@@ -286,7 +345,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (stageIndex == 0 && addresses.VertexA != 0)
{
- translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA);
+ translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA);
}
translatorContexts[stageIndex + 1] = currentStage;
@@ -336,13 +395,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
}
+ ShaderSource[] shaderSourcesArray = shaderSources.ToArray();
+
int fragmentOutputMap = shaders[5]?.Info.FragmentOutputMap ?? -1;
- IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources.ToArray(), new ShaderInfo(fragmentOutputMap));
+ IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(fragmentOutputMap, pipeline));
gpShaders = new CachedShaderProgram(hostProgram, specState, shaders);
_graphicsShaderCache.Add(gpShaders);
- EnqueueProgramToSave(new ProgramToSave(gpShaders, hostProgram));
+ EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray);
_gpPrograms[addresses] = gpShaders;
return gpShaders;
@@ -355,7 +416,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <returns>Shader source</returns>
public static ShaderSource CreateShaderSource(ShaderProgram program)
{
- return new ShaderSource(program.Code, program.BinaryCode, program.Info.Stage, program.Language);
+ return new ShaderSource(program.Code, program.BinaryCode, GetBindings(program.Info), program.Info.Stage, program.Language);
}
/// <summary>
@@ -364,11 +425,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <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)
+ /// <param name="program">Cached shader program</param>
+ /// <param name="hostProgram">Host program</param>
+ /// <param name="sources">Source for each shader stage</param>
+ private void EnqueueProgramToSave(CachedShaderProgram program, IProgram hostProgram, ShaderSource[] sources)
{
if (_diskCacheHostStorage.CacheEnabled)
{
+ byte[] binaryCode = _context.Capabilities.Api == TargetApi.Vulkan ? ShaderBinarySerializer.Pack(sources) : null;
+ ProgramToSave programToSave = new ProgramToSave(program, hostProgram, binaryCode);
+
_programsToSaveQueue.Enqueue(programToSave);
}
}
@@ -480,11 +546,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Decode the binary Maxwell shader code to a translator context.
/// </summary>
/// <param name="gpuAccessor">GPU state accessor</param>
+ /// <param name="api">Graphics API that will be used with the shader</param>
/// <param name="gpuVa">GPU virtual address of the binary shader code</param>
/// <returns>The generated translator context</returns>
- public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, ulong gpuVa)
+ public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, TargetApi api, ulong gpuVa)
{
- var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.Compute);
+ var options = CreateTranslationOptions(api, DefaultFlags | TranslationFlags.Compute);
return Translator.CreateContext(gpuVa, gpuAccessor, options);
}
@@ -495,12 +562,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader.
/// </remarks>
/// <param name="gpuAccessor">GPU state accessor</param>
+ /// <param name="api">Graphics API that will be used with the shader</param>
/// <param name="flags">Flags that controls shader translation</param>
/// <param name="gpuVa">GPU virtual address of the shader code</param>
/// <returns>The generated translator context</returns>
- public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TranslationFlags flags, ulong gpuVa)
+ public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TargetApi api, TranslationFlags flags, ulong gpuVa)
{
- var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags);
+ var options = CreateTranslationOptions(api, flags);
return Translator.CreateContext(gpuVa, gpuAccessor, options);
}
@@ -596,6 +664,41 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
+ /// Gets information about the bindings used by a shader program.
+ /// </summary>
+ /// <param name="info">Shader program information to get the information from</param>
+ /// <returns>Shader bindings</returns>
+ public static ShaderBindings GetBindings(ShaderProgramInfo info)
+ {
+ var uniformBufferBindings = info.CBuffers.Select(x => x.Binding).ToArray();
+ var storageBufferBindings = info.SBuffers.Select(x => x.Binding).ToArray();
+ var textureBindings = info.Textures.Select(x => x.Binding).ToArray();
+ var imageBindings = info.Images.Select(x => x.Binding).ToArray();
+
+ return new ShaderBindings(
+ uniformBufferBindings,
+ storageBufferBindings,
+ textureBindings,
+ imageBindings);
+ }
+
+ /// <summary>
+ /// Creates shader translation options with the requested graphics API and flags.
+ /// The shader language is choosen based on the current configuration and graphics API.
+ /// </summary>
+ /// <param name="api">Target graphics API</param>
+ /// <param name="flags">Translation flags</param>
+ /// <returns>Translation options</returns>
+ private static TranslationOptions CreateTranslationOptions(TargetApi api, TranslationFlags flags)
+ {
+ TargetLanguage lang = GraphicsConfig.EnableSpirvCompilationOnVulkan && api == TargetApi.Vulkan
+ ? TargetLanguage.Spirv
+ : TargetLanguage.Glsl;
+
+ return new TranslationOptions(lang, api, flags);
+ }
+
+ /// <summary>
/// Disposes the shader cache, deleting all the cached shaders.
/// It's an error to use the shader cache after disposal.
/// </summary>
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
index 7e39c8a3..c927f33d 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
@@ -1,6 +1,7 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
using Ryujinx.Graphics.Shader;
using System;
@@ -19,6 +20,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
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);
+ private const uint PgpsMagic = (byte)'P' | ((byte)'G' << 8) | ((byte)'P' << 16) | ((byte)'S' << 24);
/// <summary>
/// Flags indicating GPU state that is used by the shader.
@@ -52,6 +54,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
public Array5<uint> ConstantBufferUse;
/// <summary>
+ /// Pipeline state captured at the time of shader use.
+ /// </summary>
+ public ProgramPipelineState? PipelineState;
+
+ /// <summary>
/// Transform feedback buffers active at the time the shader was compiled.
/// </summary>
public TransformFeedbackDescriptor[] TransformFeedbackDescriptors;
@@ -179,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Creates a new instance of the shader specialization state.
/// </summary>
/// <param name="state">Current compute engine state</param>
- public ShaderSpecializationState(GpuChannelComputeState state) : this()
+ public ShaderSpecializationState(ref GpuChannelComputeState state) : this()
{
ComputeState = state;
_compute = true;
@@ -190,7 +197,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </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()
+ private ShaderSpecializationState(ref GpuChannelGraphicsState state, TransformFeedbackDescriptor[] descriptors) : this()
{
GraphicsState = state;
_compute = false;
@@ -245,6 +252,34 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
+ /// Creates a new instance of the shader specialization state.
+ /// </summary>
+ /// <param name="state">Current 3D engine state</param>
+ /// <param name="pipelineState">Current program pipeline state</param>
+ /// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
+ public ShaderSpecializationState(
+ ref GpuChannelGraphicsState state,
+ ref ProgramPipelineState pipelineState,
+ TransformFeedbackDescriptor[] descriptors) : this(ref state, descriptors)
+ {
+ PipelineState = pipelineState;
+ }
+
+ /// <summary>
+ /// Creates a new instance of the shader specialization state.
+ /// </summary>
+ /// <param name="state">Current 3D engine state</param>
+ /// <param name="pipelineState">Current program pipeline state</param>
+ /// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
+ public ShaderSpecializationState(
+ ref GpuChannelGraphicsState state,
+ ProgramPipelineState? pipelineState,
+ TransformFeedbackDescriptor[] descriptors) : this(ref state, descriptors)
+ {
+ PipelineState = pipelineState;
+ }
+
+ /// <summary>
/// Indicates that the shader accesses the early Z force state.
/// </summary>
public void RecordEarlyZForce()
@@ -463,6 +498,28 @@ namespace Ryujinx.Graphics.Gpu.Shader
return false;
}
+ if (graphicsState.DepthMode != GraphicsState.DepthMode)
+ {
+ return false;
+ }
+
+ if (graphicsState.AlphaTestEnable != GraphicsState.AlphaTestEnable)
+ {
+ return false;
+ }
+
+ if (graphicsState.AlphaTestEnable &&
+ (graphicsState.AlphaTestCompare != GraphicsState.AlphaTestCompare ||
+ graphicsState.AlphaTestReference != GraphicsState.AlphaTestReference))
+ {
+ return false;
+ }
+
+ if (!graphicsState.AttributeTypes.ToSpan().SequenceEqual(GraphicsState.AttributeTypes.ToSpan()))
+ {
+ return false;
+ }
+
return Matches(channel, poolState, checkTextures, isCompute: false);
}
@@ -685,6 +742,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
constantBufferUsePerStageMask &= ~(1 << index);
}
+ bool hasPipelineState = false;
+
+ dataReader.Read(ref hasPipelineState);
+
+ if (hasPipelineState)
+ {
+ ProgramPipelineState pipelineState = default;
+ dataReader.ReadWithMagicAndSize(ref pipelineState, PgpsMagic);
+ specState.PipelineState = pipelineState;
+ }
+
if (specState._queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
{
ushort tfCount = 0;
@@ -743,6 +811,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
constantBufferUsePerStageMask &= ~(1 << index);
}
+ bool hasPipelineState = PipelineState.HasValue;
+
+ dataWriter.Write(ref hasPipelineState);
+
+ if (hasPipelineState)
+ {
+ ProgramPipelineState pipelineState = PipelineState.Value;
+ dataWriter.WriteWithMagicAndSize(ref pipelineState, PgpsMagic);
+ }
+
if (_queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
{
ushort tfCount = (ushort)TransformFeedbackDescriptors.Length;