From f6196fe07ba460bd9168d32c47da6561f376e68e Mon Sep 17 00:00:00 2001 From: riperiperi Date: Tue, 22 Feb 2022 18:47:46 +0000 Subject: [PATCH] Multithreaded Pipeline Compilation --- .../Resources/ThreadedTexture.cs | 1 - .../Multithreading/ThreadedRenderer.cs | 7 + Ryujinx.Graphics.GAL/ProgramPipelineState.cs | 78 +++++ Ryujinx.Graphics.GAL/ShaderInfo.cs | 11 + .../Engine/Threed/DrawManager.cs | 14 +- .../Engine/Threed/StateUpdater.cs | 173 ++++++----- .../Engine/Threed/ThreedClass.cs | 11 +- Ryujinx.Graphics.Gpu/GraphicsConfig.cs | 2 +- Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs | 47 ++- Ryujinx.Graphics.Vulkan/HashTableSlim.cs | 7 +- Ryujinx.Graphics.Vulkan/PipelineBase.cs | 2 +- Ryujinx.Graphics.Vulkan/PipelineConverter.cs | 271 ++++++++++++++++++ Ryujinx.Graphics.Vulkan/PipelineState.cs | 1 + Ryujinx.Graphics.Vulkan/Shader.cs | 38 +-- Ryujinx.Graphics.Vulkan/ShaderCollection.cs | 135 ++++++++- .../VulkanGraphicsDevice.cs | 9 +- 16 files changed, 704 insertions(+), 103 deletions(-) create mode 100644 Ryujinx.Graphics.GAL/ProgramPipelineState.cs create mode 100644 Ryujinx.Graphics.Vulkan/PipelineConverter.cs diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs index 64d8aa3bd..6023ee388 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs @@ -14,7 +14,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources public ITexture Base; public int Width => _info.Width; - public int Height => _info.Height; public float ScaleFactor { get; } diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs index 6a123108a..e7faf474c 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs @@ -262,7 +262,14 @@ namespace Ryujinx.Graphics.GAL.Multithreading public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info) { var program = new ThreadedProgram(this); + + if (info.State.HasValue) + { + info.BackgroundCompile = true; + } + SourceProgramRequest request = new SourceProgramRequest(program, shaders, info); + Programs.Add(request); New().Set(Ref((IProgramRequest)request)); diff --git a/Ryujinx.Graphics.GAL/ProgramPipelineState.cs b/Ryujinx.Graphics.GAL/ProgramPipelineState.cs new file mode 100644 index 000000000..39665e86b --- /dev/null +++ b/Ryujinx.Graphics.GAL/ProgramPipelineState.cs @@ -0,0 +1,78 @@ +using Ryujinx.Common.Memory; +using System; + +namespace Ryujinx.Graphics.GAL +{ + /// + /// Descriptor for a pipeline buffer binding. + /// + public struct BufferPipelineDescriptor + { + public bool Enable { get; } + public int Stride { get; } + public int Divisor { get; } + + public BufferPipelineDescriptor(bool enable, int stride, int divisor) + { + Enable = enable; + Stride = stride; + Divisor = divisor; + } + } + + /// + /// State required for a program to compile shaders. + /// + public struct ProgramPipelineState + { + // Some state is considered always dynamic and should not be included: + // - Viewports/Scissors + // - Bias values (not enable) + + public int SamplesCount; + public Array8 AttachmentEnable; + public Array8 AttachmentFormats; + public bool DepthStencilEnable; + public Format DepthStencilFormat; + + public bool LogicOpEnable; + public LogicalOp LogicOp; + public Array8 BlendDescriptors; + public Array8 ColorWriteMask; + + public int VertexAttribCount; + public Array32 VertexAttribs; + + public int VertexBufferCount; + public Array33 VertexBuffers; + + // TODO: Min/max depth bounds. + public DepthTestDescriptor DepthTest; + public StencilTestDescriptor StencilTest; + public FrontFace FrontFace; + public Face CullMode; + public bool CullEnable; + + public PolygonModeMask BiasEnable; + + public float LineWidth; + // TODO: Polygon mode. + public bool DepthClampEnable; + public bool RasterizerDiscard; + public PrimitiveTopology Topology; + public bool PrimitiveRestartEnable; + public uint PatchControlPoints; + + public void SetVertexAttribs(ReadOnlySpan vertexAttribs) + { + VertexAttribCount = vertexAttribs.Length; + vertexAttribs.CopyTo(VertexAttribs.ToSpan()); + } + + public void SetLogicOpState(bool enable, LogicalOp op) + { + LogicOp = op; + LogicOpEnable = enable; + } + } +} diff --git a/Ryujinx.Graphics.GAL/ShaderInfo.cs b/Ryujinx.Graphics.GAL/ShaderInfo.cs index 0c187e066..f7be8b39b 100644 --- a/Ryujinx.Graphics.GAL/ShaderInfo.cs +++ b/Ryujinx.Graphics.GAL/ShaderInfo.cs @@ -3,10 +3,21 @@ namespace Ryujinx.Graphics.GAL public struct ShaderInfo { public int FragmentOutputMap { get; } + public ProgramPipelineState? State { get; } + public bool BackgroundCompile { get; set; } + + public ShaderInfo(int fragmentOutputMap, ProgramPipelineState state) + { + FragmentOutputMap = fragmentOutputMap; + State = state; + BackgroundCompile = false; + } public ShaderInfo(int fragmentOutputMap) { FragmentOutputMap = fragmentOutputMap; + State = null; + BackgroundCompile = false; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs index 6dc5dca5b..07134485c 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs @@ -200,8 +200,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// Starts draw. /// This sets primitive type and instanced draw parameters. /// + /// 3D engine where this method is being called /// Method call argument - public void DrawBegin(int argument) + public void DrawBegin(ThreedClass engine, int argument) { bool incrementInstance = (argument & (1 << 26)) != 0; bool resetInstance = (argument & (1 << 27)) == 0; @@ -209,12 +210,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed if (_state.State.PrimitiveTypeOverrideEnable) { PrimitiveTypeOverride typeOverride = _state.State.PrimitiveTypeOverride; - DrawBegin(incrementInstance, resetInstance, typeOverride.Convert()); + DrawBegin(engine, incrementInstance, resetInstance, typeOverride.Convert()); } else { PrimitiveType type = (PrimitiveType)(argument & 0xffff); - DrawBegin(incrementInstance, resetInstance, type.Convert()); + DrawBegin(engine, incrementInstance, resetInstance, type.Convert()); } } @@ -222,10 +223,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// Starts draw. /// This sets primitive type and instanced draw parameters. /// + /// 3D engine where this method is being called /// Indicates if the current instance should be incremented /// Indicates if the current instance should be set to zero /// Primitive topology - private void DrawBegin(bool incrementInstance, bool resetInstance, PrimitiveTopology topology) + private void DrawBegin(ThreedClass engine, bool incrementInstance, bool resetInstance, PrimitiveTopology topology) { if (incrementInstance) { @@ -240,6 +242,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed if (_drawState.Topology != topology || !_topologySet) { + engine.SetPipelineTopology(topology); _context.Renderer.Pipeline.SetPrimitiveTopology(topology); _drawState.Topology = topology; _topologySet = true; @@ -309,7 +312,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed { PrimitiveTypeOverride typeOverride = _state.State.PrimitiveTypeOverride; - DrawBegin(instanced, !instanced, typeOverride.Convert()); + DrawBegin(engine, instanced, !instanced, typeOverride.Convert()); int firstIndex = argument & 0xffff; int indexCount = (argument >> 16) & 0xfff; @@ -403,6 +406,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed { engine.Write(IndexBufferCountMethodOffset * 4, indexCount); + engine.SetPipelineTopology(topology); _context.Renderer.Pipeline.SetPrimitiveTopology(topology); _drawState.Topology = topology; _topologySet = true; diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index 8ae980576..33b34e6cb 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -15,11 +15,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// 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 = 17; + public const int VertexBufferStateIndex = 0; + public const int PrimitiveRestartStateIndex = 12; private readonly GpuContext _context; private readonly GpuChannel _channel; @@ -31,6 +31,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 +56,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 +65,39 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed // The order of the other state updates doesn't matter. _updateTracker = new StateUpdateTracker(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(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)), - new StateUpdateCallbackEntry(UpdateTessellationState, - nameof(ThreedClassState.TessOuterLevel), - nameof(ThreedClassState.TessInnerLevel), - nameof(ThreedClassState.PatchVertices)), + new StateUpdateCallbackEntry(UpdateBlendState, + nameof(ThreedClassState.BlendIndependent), + nameof(ThreedClassState.BlendConstant), + nameof(ThreedClassState.BlendStateCommon), + nameof(ThreedClassState.BlendEnableCommon), + nameof(ThreedClassState.BlendEnable), + nameof(ThreedClassState.BlendState)), - new StateUpdateCallbackEntry(UpdateTfBufferState, nameof(ThreedClassState.TfBufferState)), - new StateUpdateCallbackEntry(UpdateUserClipState, nameof(ThreedClassState.ClipDistanceEnable)), + new StateUpdateCallbackEntry(UpdateFaceState, nameof(ThreedClassState.FaceState)), - new StateUpdateCallbackEntry(UpdateRenderTargetState, - nameof(ThreedClassState.RtColorState), - nameof(ThreedClassState.RtDepthStencilState), - nameof(ThreedClassState.RtControl), - nameof(ThreedClassState.RtDepthStencilSize), - nameof(ThreedClassState.RtDepthStencilEnable)), - - new StateUpdateCallbackEntry(UpdateDepthClampState, nameof(ThreedClassState.ViewVolumeClipControl)), - - 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 +105,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 +119,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), @@ -151,22 +169,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed new StateUpdateCallbackEntry(UpdateIndexBufferState, 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)) }); } @@ -190,6 +192,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed _updateTracker.SetAllDirty(); } + /// + /// Sets topology for the current pipeline. + /// + /// New topology value + public void SetPipelineTopology(PrimitiveTopology topology) + { + _pipeline.Topology = topology; + } + /// /// Updates host state for any modified guest state, since the last time this function was called. /// @@ -320,6 +331,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// private void UpdateTessellationState() { + _pipeline.PatchControlPoints = (uint)_state.State.PatchVertices; + _context.Renderer.Pipeline.SetPatchParameters( _state.State.PatchVertices, _state.State.TessOuterLevel.ToSpan(), @@ -352,6 +365,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed private void UpdateRasterizerState() { bool enable = _state.State.RasterizeEnable; + _pipeline.RasterizerDiscard = !enable; _context.Renderer.Pipeline.SetRasterizerDiscard(!enable); } @@ -555,7 +569,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); } /// @@ -574,10 +591,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// 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); } /// @@ -728,6 +748,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); } @@ -769,7 +790,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, @@ -784,7 +805,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed backDpFail, backFuncRef, backFuncMask, - backMask)); + backMask); + + _pipeline.StencilTest = descriptor; + _context.Renderer.Pipeline.SetStencilTest(descriptor); } /// @@ -853,6 +877,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed format); } + _pipeline.SetVertexAttribs(vertexAttribs); _context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs); } @@ -864,6 +889,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); } @@ -890,6 +916,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); } @@ -936,6 +963,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; @@ -988,6 +1016,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed 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); } } @@ -1000,6 +1029,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); @@ -1019,6 +1050,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed frontFace = frontFace == FrontFace.CounterClockwise ? FrontFace.Clockwise : FrontFace.CounterClockwise; } + _pipeline.FrontFace = frontFace; _context.Renderer.Pipeline.SetFrontFace(frontFace); } @@ -1044,6 +1076,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed componentMask |= (colorMask.UnpackAlpha() ? 8u : 0u); componentMasks[index] = componentMask; + _pipeline.ColorWriteMask[index] = componentMask; } _context.Renderer.Pipeline.SetRenderTargetColorMasks(componentMasks); @@ -1092,6 +1125,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed blend.AlphaDstFactor); } + _pipeline.BlendDescriptors[index] = descriptor; _context.Renderer.Pipeline.SetBlendState(index, descriptor); } } @@ -1103,6 +1137,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); } @@ -1134,7 +1169,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; diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs index 764ba2394..8416ae9bf 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs @@ -109,6 +109,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed _state.State.SetMmeShadowRamControl = (uint)control; } + /// + /// Sets topology for the current pipeline. + /// + /// New topology value + public void SetPipelineTopology(PrimitiveTopology topology) + { + _stateUpdater.SetPipelineTopology(topology); + } + /// /// Updates current host state for all registers modified since the last call to this method. /// @@ -343,7 +352,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// Method call argument private void DrawBegin(int argument) { - _drawManager.DrawBegin(argument); + _drawManager.DrawBegin(this, argument); } /// diff --git a/Ryujinx.Graphics.Gpu/GraphicsConfig.cs b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs index 9f9de50f6..1365db4bc 100644 --- a/Ryujinx.Graphics.Gpu/GraphicsConfig.cs +++ b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs @@ -60,6 +60,6 @@ namespace Ryujinx.Graphics.Gpu /// /// Enables or disables shader SPIR-V compilation. /// - public static bool EnableSpirvCompilation; + public static bool EnableSpirvCompilation = true; } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index 8cacd8956..c5c23caa1 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -1,6 +1,8 @@ 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; @@ -230,6 +232,41 @@ namespace Ryujinx.Graphics.Gpu.Shader return cpShader; } + private void UpdatePipelineInfo(ref ThreedClassState state, ref ProgramPipelineState pipeline, 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; + } + /// /// Gets a graphics shader program from the shader cache. /// This includes all the specified shader stages. @@ -238,6 +275,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// This automatically translates, compiles and adds the code to the cache if not present. /// /// GPU state + /// Pipeline state /// GPU channel /// Texture pool state /// 3D engine state @@ -245,6 +283,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Compiled graphics shader code public CachedShaderProgram GetGraphicsShader( ref ThreedClassState state, + ref ProgramPipelineState pipeline, GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState, @@ -351,8 +390,10 @@ namespace Ryujinx.Graphics.Gpu.Shader } } + UpdatePipelineInfo(ref state, ref pipeline, channel); + int fragmentOutputMap = shaders[5]?.Info.FragmentOutputMap ?? -1; - IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources.ToArray(), new ShaderInfo(fragmentOutputMap)); + IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources.ToArray(), new ShaderInfo(fragmentOutputMap, pipeline)); gpShaders = new CachedShaderProgram(hostProgram, specState, shaders); @@ -614,12 +655,12 @@ namespace Ryujinx.Graphics.Gpu.Shader private static ShaderBindings GetBindings(ShaderProgramInfo info) { - static bool IsBuffer(TextureDescriptor descriptor) + static bool IsBuffer(Graphics.Shader.TextureDescriptor descriptor) { return (descriptor.Type & SamplerType.Mask) == SamplerType.TextureBuffer; } - static bool IsNotBuffer(TextureDescriptor descriptor) + static bool IsNotBuffer(Graphics.Shader.TextureDescriptor descriptor) { return !IsBuffer(descriptor); } diff --git a/Ryujinx.Graphics.Vulkan/HashTableSlim.cs b/Ryujinx.Graphics.Vulkan/HashTableSlim.cs index 195b6848b..5f3017301 100644 --- a/Ryujinx.Graphics.Vulkan/HashTableSlim.cs +++ b/Ryujinx.Graphics.Vulkan/HashTableSlim.cs @@ -27,9 +27,12 @@ namespace Ryujinx.Graphics.Vulkan { foreach (Entry[] bucket in _hashTable) { - foreach (Entry entry in bucket) + if (bucket != null) { - yield return entry.Key; + foreach (Entry entry in bucket) + { + yield return entry.Key; + } } } } diff --git a/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/Ryujinx.Graphics.Vulkan/PipelineBase.cs index e43735732..b9802ffb3 100644 --- a/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -30,7 +30,7 @@ namespace Ryujinx.Graphics.Vulkan protected PipelineBindPoint Pbp; - private PipelineCache _pipelineCache; + public PipelineCache _pipelineCache; protected CommandBufferScoped Cbs; protected CommandBufferScoped? PreloadCbs; diff --git a/Ryujinx.Graphics.Vulkan/PipelineConverter.cs b/Ryujinx.Graphics.Vulkan/PipelineConverter.cs new file mode 100644 index 000000000..30d6edeb7 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/PipelineConverter.cs @@ -0,0 +1,271 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + static class PipelineConverter + { + public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanGraphicsDevice gd, Device device) + { + const int MaxAttachments = Constants.MaxRenderTargets + 1; + + AttachmentDescription[] attachmentDescs = null; + + var subpass = new SubpassDescription() + { + PipelineBindPoint = PipelineBindPoint.Graphics + }; + + AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments]; + + Span attachmentIndices = stackalloc int[MaxAttachments]; + Span attachmentFormats = stackalloc Silk.NET.Vulkan.Format[MaxAttachments]; + + int attachmentCount = 0; + int colorCount = 0; + int maxColorAttachmentIndex = 0; + + for (int i = 0; i < state.AttachmentEnable.Length; i++) + { + if (state.AttachmentEnable[i]) + { + maxColorAttachmentIndex = i; + + attachmentFormats[attachmentCount] = gd.FormatCapabilities.ConvertToVkFormat(state.AttachmentFormats[i]); + + attachmentIndices[attachmentCount++] = i; + colorCount++; + } + } + + if (state.DepthStencilEnable) + { + attachmentFormats[attachmentCount++] = gd.FormatCapabilities.ConvertToVkFormat(state.DepthStencilFormat); + } + + if (attachmentCount != 0) + { + attachmentDescs = new AttachmentDescription[attachmentCount]; + + for (int i = 0; i < attachmentCount; i++) + { + int bindIndex = attachmentIndices[i]; + + var format = state.AttachmentFormats[i]; + + attachmentDescs[i] = new AttachmentDescription( + 0, + attachmentFormats[i], + SampleCountFlags.SampleCount1Bit, + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + ImageLayout.General, + ImageLayout.General); + } + + int colorAttachmentsCount = colorCount; + + if (colorAttachmentsCount > MaxAttachments - 1) + { + colorAttachmentsCount = MaxAttachments - 1; + } + + if (colorAttachmentsCount != 0) + { + int maxAttachmentIndex = Constants.MaxRenderTargets - 1; + subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1; + subpass.PColorAttachments = &attachmentReferences[0]; + + // Fill with VK_ATTACHMENT_UNUSED to cover any gaps. + for (int i = 0; i <= maxAttachmentIndex; i++) + { + subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined); + } + + for (int i = 0; i < colorAttachmentsCount; i++) + { + int bindIndex = attachmentIndices[i]; + + subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General); + } + } + + if (state.DepthStencilEnable) + { + uint dsIndex = (uint)attachmentCount - 1; + + subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1]; + *subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General); + } + } + + var subpassDependency = new SubpassDependency( + 0, + 0, + PipelineStageFlags.PipelineStageAllGraphicsBit, + PipelineStageFlags.PipelineStageAllGraphicsBit, + AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit, + AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit, + 0); + + fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs) + { + var renderPassCreateInfo = new RenderPassCreateInfo() + { + SType = StructureType.RenderPassCreateInfo, + PAttachments = pAttachmentDescs, + AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0, + PSubpasses = &subpass, + SubpassCount = 1, + PDependencies = &subpassDependency, + DependencyCount = 1 + }; + + gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError(); + + return new DisposableRenderPass(gd.Api, device, renderPass); + } + } + + public static unsafe PipelineState ToVulkanPipelineState(this ProgramPipelineState state, VulkanGraphicsDevice gd) + { + PipelineState pipeline = new PipelineState(); + pipeline.Initialize(); + + // It is assumed that Dynamic State is enabled when this conversion is used. + + pipeline.BlendConstantA = state.BlendDescriptors[0].BlendConstant.Alpha; + pipeline.BlendConstantB = state.BlendDescriptors[0].BlendConstant.Blue; + pipeline.BlendConstantG = state.BlendDescriptors[0].BlendConstant.Green; + pipeline.BlendConstantR = state.BlendDescriptors[0].BlendConstant.Red; + + pipeline.CullMode = state.CullEnable ? state.CullMode.Convert() : CullModeFlags.CullModeNone; + + pipeline.DepthBoundsTestEnable = false; // Not implemented. + + pipeline.DepthClampEnable = state.DepthClampEnable; + + pipeline.DepthTestEnable = state.DepthTest.TestEnable; + pipeline.DepthWriteEnable = state.DepthTest.WriteEnable; + pipeline.DepthCompareOp = state.DepthTest.Func.Convert(); + + pipeline.FrontFace = state.FrontFace.Convert(); + + pipeline.HasDepthStencil = state.DepthStencilEnable; + pipeline.LineWidth = state.LineWidth; + pipeline.LogicOpEnable = state.LogicOpEnable; + pipeline.LogicOp = state.LogicOp.Convert(); + + pipeline.MinDepthBounds = 0f; // Not implemented. + pipeline.MaxDepthBounds = 0f; // Not implemented. + + pipeline.PatchControlPoints = state.PatchControlPoints; + pipeline.PolygonMode = Silk.NET.Vulkan.PolygonMode.Fill; // Not implemented. + pipeline.PrimitiveRestartEnable = state.PrimitiveRestartEnable; + pipeline.RasterizerDiscardEnable = state.RasterizerDiscard; + pipeline.SamplesCount = (uint)state.SamplesCount; + pipeline.ScissorsCount = 16; + pipeline.ViewportsCount = 16; + + pipeline.DepthBiasEnable = state.BiasEnable != 0; + + // Stencil masks and ref are dynamic, so are 0 in the Vulkan pipeline. + + pipeline.StencilFrontFailOp = state.StencilTest.FrontSFail.Convert(); + pipeline.StencilFrontPassOp = state.StencilTest.FrontDpPass.Convert(); + pipeline.StencilFrontDepthFailOp = state.StencilTest.FrontDpFail.Convert(); + pipeline.StencilFrontCompareOp = state.StencilTest.FrontFunc.Convert(); + pipeline.StencilFrontCompareMask = 0; + pipeline.StencilFrontWriteMask = 0; + pipeline.StencilFrontReference = 0; + + pipeline.StencilBackFailOp = state.StencilTest.BackSFail.Convert(); + pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert(); + pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert(); + pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert(); + pipeline.StencilBackCompareMask = 0; + pipeline.StencilBackWriteMask = 0; + pipeline.StencilBackReference = 0; + + pipeline.StencilTestEnable = state.StencilTest.TestEnable; + + pipeline.Topology = state.Topology.Convert(); + + int vaCount = Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount); + + for (int i = 0; i < vaCount; i++) + { + var attribute = state.VertexAttribs[i]; + var bufferIndex = attribute.IsZero ? 0 : attribute.BufferIndex + 1; + + pipeline.Internal.VertexAttributeDescriptions[i] = new VertexInputAttributeDescription( + (uint)i, + (uint)bufferIndex, + FormatTable.GetFormat(attribute.Format), + (uint)attribute.Offset); + } + + int descriptorIndex = 1; + pipeline.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex); + + int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount); + + for (int i = 0; i < vbCount; i++) + { + var vertexBuffer = state.VertexBuffers[i]; + + if (vertexBuffer.Enable) + { + var inputRate = vertexBuffer.Divisor != 0 ? VertexInputRate.Instance : VertexInputRate.Vertex; + + // TODO: Support divisor > 1 + pipeline.Internal.VertexBindingDescriptions[descriptorIndex++] = new VertexInputBindingDescription( + (uint)i + 1, + (uint)vertexBuffer.Stride, + inputRate); + } + } + + pipeline.VertexBindingDescriptionsCount = (uint)descriptorIndex; + + // NOTE: Viewports, Scissors are dynamic. + + for (int i = 0; i < 8; i++) + { + var blend = state.BlendDescriptors[i]; + + pipeline.Internal.ColorBlendAttachmentState[i] = new PipelineColorBlendAttachmentState( + blend.Enable, + blend.ColorSrcFactor.Convert(), + blend.ColorDstFactor.Convert(), + blend.ColorOp.Convert(), + blend.AlphaSrcFactor.Convert(), + blend.AlphaDstFactor.Convert(), + blend.AlphaOp.Convert(), + (ColorComponentFlags)state.ColorWriteMask[i]); + } + + int maxAttachmentIndex = 0; + for (int i = 0; i < 8; i++) + { + if (state.AttachmentEnable[i]) + { + pipeline.Internal.AttachmentFormats[maxAttachmentIndex++] = gd.FormatCapabilities.ConvertToVkFormat(state.AttachmentFormats[i]); + } + } + + if (state.DepthStencilEnable) + { + pipeline.Internal.AttachmentFormats[maxAttachmentIndex++] = gd.FormatCapabilities.ConvertToVkFormat(state.DepthStencilFormat); + } + + pipeline.ColorBlendAttachmentStateCount = 8; + pipeline.VertexAttributeDescriptionsCount = (uint)Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount); + + return pipeline; + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/PipelineState.cs b/Ryujinx.Graphics.Vulkan/PipelineState.cs index 1e8cc2ae5..b5ba583fd 100644 --- a/Ryujinx.Graphics.Vulkan/PipelineState.cs +++ b/Ryujinx.Graphics.Vulkan/PipelineState.cs @@ -497,6 +497,7 @@ namespace Ryujinx.Graphics.Vulkan var pipelineCreateInfo = new GraphicsPipelineCreateInfo() { SType = StructureType.GraphicsPipelineCreateInfo, + Flags = 0, StageCount = StagesCount, PStages = Stages.Pointer, PVertexInputState = &vertexInputState, diff --git a/Ryujinx.Graphics.Vulkan/Shader.cs b/Ryujinx.Graphics.Vulkan/Shader.cs index 9ec4bac7f..3f1896386 100644 --- a/Ryujinx.Graphics.Vulkan/Shader.cs +++ b/Ryujinx.Graphics.Vulkan/Shader.cs @@ -19,7 +19,6 @@ namespace Ryujinx.Graphics.Vulkan private readonly Vk _api; private readonly Device _device; private readonly ShaderStageFlags _stage; - private readonly Task _compileTask; private IntPtr _entryPointName; private ShaderModule _module; @@ -30,6 +29,8 @@ namespace Ryujinx.Graphics.Vulkan public ProgramLinkStatus CompileStatus { private set; get; } + public readonly Task CompileTask; + public unsafe Shader(Vk api, Device device, ShaderStage stage, ShaderBindings bindings, string glsl) { _api = api; @@ -39,7 +40,7 @@ namespace Ryujinx.Graphics.Vulkan Bindings = bindings; - _compileTask = Task.Run(() => + CompileTask = Task.Run(() => { glsl = glsl.Replace("gl_VertexID", "(gl_VertexIndex - gl_BaseVertex)"); glsl = glsl.Replace("gl_InstanceID", "(gl_InstanceIndex - gl_BaseInstance)"); @@ -103,22 +104,27 @@ namespace Ryujinx.Graphics.Vulkan _device = device; Bindings = bindings; - CompileStatus = ProgramLinkStatus.Success; - - fixed (byte* pCode = spirv) - { - var shaderModuleCreateInfo = new ShaderModuleCreateInfo() - { - SType = StructureType.ShaderModuleCreateInfo, - CodeSize = (uint)spirv.Length, - PCode = (uint*)pCode - }; - - api.CreateShaderModule(device, shaderModuleCreateInfo, null, out _module).ThrowOnError(); - } + CompileStatus = ProgramLinkStatus.Incomplete; _stage = stage.Convert(); _entryPointName = Marshal.StringToHGlobalAnsi("main"); + + CompileTask = Task.Run(() => + { + fixed (byte* pCode = spirv) + { + var shaderModuleCreateInfo = new ShaderModuleCreateInfo() + { + SType = StructureType.ShaderModuleCreateInfo, + CodeSize = (uint)spirv.Length, + PCode = (uint*)pCode + }; + + api.CreateShaderModule(device, shaderModuleCreateInfo, null, out _module).ThrowOnError(); + } + + CompileStatus = ProgramLinkStatus.Success; + }); } private static uint[] LoadShaderData(string filePath, out int codeSize) @@ -169,7 +175,7 @@ namespace Ryujinx.Graphics.Vulkan public void WaitForCompile() { - _compileTask.Wait(); + CompileTask.Wait(); } public unsafe void Dispose() diff --git a/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/Ryujinx.Graphics.Vulkan/ShaderCollection.cs index 05beee70f..7f3b7dec7 100644 --- a/Ryujinx.Graphics.Vulkan/ShaderCollection.cs +++ b/Ryujinx.Graphics.Vulkan/ShaderCollection.cs @@ -1,8 +1,10 @@ -using Ryujinx.Graphics.GAL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; using Silk.NET.Vulkan; using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace Ryujinx.Graphics.Vulkan { @@ -40,8 +42,17 @@ namespace Ryujinx.Graphics.Vulkan private VulkanGraphicsDevice _gd; private Device _device; private bool _initialized; + private bool _isCompute; - public ShaderCollection(VulkanGraphicsDevice gd, Device device, IShader[] shaders) + private ProgramPipelineState _state; + private DisposableRenderPass _dummyRenderPass; + private Task _compileTask; + private bool _firstBackgroundUse; + + public ShaderCollection( + VulkanGraphicsDevice gd, + Device device, + IShader[] shaders) { _gd = gd; _device = device; @@ -70,6 +81,11 @@ namespace Ryujinx.Graphics.Vulkan _ => 0 }; + if (shader.StageFlags == ShaderStageFlags.ShaderStageComputeBit) + { + _isCompute = true; + } + internalShaders[i] = shader; } @@ -101,6 +117,42 @@ namespace Ryujinx.Graphics.Vulkan GrabAll(x => x.BufferTextureBindings), GrabAll(x => x.BufferImageBindings) }; + + _compileTask = Task.CompletedTask; + _firstBackgroundUse = false; + } + + public ShaderCollection( + VulkanGraphicsDevice gd, + Device device, + IShader[] shaders, + ProgramPipelineState state) : this(gd, device, shaders) + { + _state = state; + + _compileTask = BackgroundCompilation(); + _firstBackgroundUse = true; + } + + private async Task BackgroundCompilation() + { + await Task.WhenAll(_shaders.Select(shader => ((Shader)shader).CompileTask)); + + try + { + if (_isCompute) + { + CreateBackgroundComputePipeline(); + } + else + { + CreateBackgroundGraphicsPipeline(); + } + } + catch (VulkanException e) + { + Common.Logging.Logger.Error?.PrintMsg(Common.Logging.LogClass.Gpu, $"Background Compilation failed: {e.Message}"); + } } private void EnsureShadersReady() @@ -136,6 +188,54 @@ namespace Ryujinx.Graphics.Vulkan return _infos; } + protected unsafe DisposableRenderPass CreateDummyRenderPass() + { + if (_dummyRenderPass.Value.Handle != 0) + { + return _dummyRenderPass; + } + + return _dummyRenderPass = _state.ToRenderPass(_gd, _device); + } + + public unsafe void CreateBackgroundComputePipeline() + { + PipelineState pipeline = new PipelineState(); + pipeline.Initialize(); + + pipeline.Stages[0] = ((Shader)_shaders[0]).GetInfo(); + pipeline.StagesCount = 1; + + pipeline.CreateComputePipeline(_gd.Api, _device, this, (_gd.Pipeline as PipelineBase)._pipelineCache); + } + + public unsafe void CreateBackgroundGraphicsPipeline() + { + // To compile shaders in the background in Vulkan, we need to create valid pipelines using the shader modules. + // The GPU provides pipeline state via the GAL that can be converted into our internal Vulkan pipeline state. + // This should match the pipeline state at the time of the first draw. If it doesn't, then it'll likely be + // close enough that the GPU driver will reuse the compiled shader for the different state. + + // First, we need to create a render pass object compatible with the one that will be used at runtime. + // The active attachment formats have been provided by the abstraction layer. + var renderPass = CreateDummyRenderPass(); + + PipelineState pipeline = _state.ToVulkanPipelineState(_gd); + + // Copy the shader stage info to the pipeline. + var stages = pipeline.Stages.ToSpan(); + + for (int i = 0; i < _shaders.Length; i++) + { + stages[i] = ((Shader)_shaders[i]).GetInfo(); + } + + pipeline.StagesCount = (uint)_shaders.Length; + pipeline.PipelineLayout = PipelineLayout; + + pipeline.CreateGraphicsPipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase)._pipelineCache, renderPass.Value); + } + public ProgramLinkStatus CheckProgramLink(bool blocking) { if (LinkStatus == ProgramLinkStatus.Incomplete) @@ -163,6 +263,18 @@ namespace Ryujinx.Graphics.Vulkan } } + if (!_compileTask.IsCompleted) + { + if (blocking) + { + _compileTask.Wait(); + } + else + { + return ProgramLinkStatus.Incomplete; + } + } + return resultStatus; } @@ -203,7 +315,20 @@ namespace Ryujinx.Graphics.Vulkan return false; } - return _graphicsPipelineCache.TryGetValue(ref key, out pipeline); + if (!_graphicsPipelineCache.TryGetValue(ref key, out pipeline)) + { + if (_firstBackgroundUse) + { + Logger.Warning?.Print(LogClass.Gpu, "Background pipeline compile missed on draw - incorrect pipeline state?"); + _firstBackgroundUse = false; + } + + return false; + } + + _firstBackgroundUse = false; + + return true; } public Auto GetNewDescriptorSetCollection( @@ -238,6 +363,10 @@ namespace Ryujinx.Graphics.Vulkan } _computePipeline?.Dispose(); + if (_dummyRenderPass.Value.Handle != 0) + { + _dummyRenderPass.Dispose(); + } } } diff --git a/Ryujinx.Graphics.Vulkan/VulkanGraphicsDevice.cs b/Ryujinx.Graphics.Vulkan/VulkanGraphicsDevice.cs index 36f7ff6f6..c13205cbb 100644 --- a/Ryujinx.Graphics.Vulkan/VulkanGraphicsDevice.cs +++ b/Ryujinx.Graphics.Vulkan/VulkanGraphicsDevice.cs @@ -204,7 +204,14 @@ namespace Ryujinx.Graphics.Vulkan public IProgram CreateProgram(IShader[] shaders, ShaderInfo info) { - return new ShaderCollection(this, _device, shaders); + if (info.BackgroundCompile && info.State.HasValue && VulkanConfiguration.UseDynamicState) + { + return new ShaderCollection(this, _device, shaders, info.State.Value); + } + else + { + return new ShaderCollection(this, _device, shaders); + } } public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info)