diff options
Diffstat (limited to 'Ryujinx.Graphics.Gpu')
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; |