diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs index 1eec80e51..86ca0d66c 100644 --- a/src/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs @@ -50,6 +50,10 @@ namespace Ryujinx.Graphics.GAL public readonly bool SupportsViewportSwizzle; public readonly bool SupportsIndirectParameters; public readonly bool SupportsDepthClipControl; + public readonly bool SupportsExtendedDynamicState; + public readonly bool SupportsExtendedDynamicState2; + public readonly bool SupportsLogicOpDynamicState; + public readonly bool SupportsPatchControlPointsDynamicState; public readonly int UniformBufferSetIndex; public readonly int StorageBufferSetIndex; @@ -118,6 +122,10 @@ namespace Ryujinx.Graphics.GAL bool supportsViewportSwizzle, bool supportsIndirectParameters, bool supportsDepthClipControl, + bool supportsExtendedDynamicState, + bool supportsExtendedDynamicState2, + bool supportsLogicOpDynamicState, + bool supportsPatchControlPointsDynamicState, int uniformBufferSetIndex, int storageBufferSetIndex, int textureSetIndex, @@ -180,6 +188,10 @@ namespace Ryujinx.Graphics.GAL SupportsViewportSwizzle = supportsViewportSwizzle; SupportsIndirectParameters = supportsIndirectParameters; SupportsDepthClipControl = supportsDepthClipControl; + SupportsExtendedDynamicState = supportsExtendedDynamicState; + SupportsExtendedDynamicState2 = supportsExtendedDynamicState2; + SupportsLogicOpDynamicState = supportsLogicOpDynamicState; + SupportsPatchControlPointsDynamicState = supportsPatchControlPointsDynamicState; UniformBufferSetIndex = uniformBufferSetIndex; StorageBufferSetIndex = storageBufferSetIndex; TextureSetIndex = textureSetIndex; diff --git a/src/Ryujinx.Graphics.GAL/Face.cs b/src/Ryujinx.Graphics.GAL/Face.cs index 73e43cf23..e204278eb 100644 --- a/src/Ryujinx.Graphics.GAL/Face.cs +++ b/src/Ryujinx.Graphics.GAL/Face.cs @@ -2,6 +2,7 @@ namespace Ryujinx.Graphics.GAL { public enum Face { + None = 0, Front = 0x404, Back = 0x405, FrontAndBack = 0x408, diff --git a/src/Ryujinx.Graphics.GAL/IPipeline.cs b/src/Ryujinx.Graphics.GAL/IPipeline.cs index b8409a573..f10db1c2d 100644 --- a/src/Ryujinx.Graphics.GAL/IPipeline.cs +++ b/src/Ryujinx.Graphics.GAL/IPipeline.cs @@ -50,9 +50,9 @@ namespace Ryujinx.Graphics.GAL void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp); void SetDepthClamp(bool clamp); void SetDepthMode(DepthMode mode); - void SetDepthTest(DepthTestDescriptor depthTest); + void SetDepthTest(DepthTestDescriptor depthTest, bool signalChange = true); - void SetFaceCulling(bool enable, Face face); + void SetFaceCulling(Face face); void SetFrontFace(FrontFace frontFace); @@ -75,16 +75,16 @@ namespace Ryujinx.Graphics.GAL void SetPrimitiveRestart(bool enable, int index); - void SetPrimitiveTopology(PrimitiveTopology topology); + void SetPrimitiveTopology(PrimitiveTopology topology, bool signalChange = true); - void SetProgram(IProgram program); + void SetProgram(IProgram program, bool signalChange = true); void SetRasterizerDiscard(bool discard); - void SetRenderTargetColorMasks(ReadOnlySpan componentMask); + void SetRenderTargetColorMasks(ReadOnlySpan componentMask, bool signalChange = true); void SetRenderTargets(ITexture[] colors, ITexture depthStencil); - void SetScissors(ReadOnlySpan> regions); + void SetScissors(ReadOnlySpan> regions, bool signalChange = true); void SetStencilTest(StencilTestDescriptor stencilTest); @@ -102,7 +102,7 @@ namespace Ryujinx.Graphics.GAL void SetVertexAttribs(ReadOnlySpan vertexAttribs); void SetVertexBuffers(ReadOnlySpan vertexBuffers); - void SetViewports(ReadOnlySpan viewports); + void SetViewports(ReadOnlySpan viewports, bool signalChange = true); void TextureBarrier(); void TextureBarrierTiled(); diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs index 611168f85..c88ddba63 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs @@ -3,18 +3,16 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands struct SetFaceCullingCommand : IGALCommand, IGALCommand { public readonly CommandType CommandType => CommandType.SetFaceCulling; - private bool _enable; private Face _face; - public void Set(bool enable, Face face) + public void Set(Face face) { - _enable = enable; _face = face; } public static void Run(ref SetFaceCullingCommand command, ThreadedRenderer threaded, IRenderer renderer) { - renderer.Pipeline.SetFaceCulling(command._enable, command._face); + renderer.Pipeline.SetFaceCulling(command._face); } } } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs index deec36648..7d4bda1c4 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs @@ -159,15 +159,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading _renderer.QueueCommand(); } - public void SetDepthTest(DepthTestDescriptor depthTest) + public void SetDepthTest(DepthTestDescriptor depthTest, bool signalChange = true) { _renderer.New().Set(depthTest); _renderer.QueueCommand(); } - public void SetFaceCulling(bool enable, Face face) + public void SetFaceCulling(Face face) { - _renderer.New().Set(enable, face); + _renderer.New().Set(face); _renderer.QueueCommand(); } @@ -243,13 +243,13 @@ namespace Ryujinx.Graphics.GAL.Multithreading _renderer.QueueCommand(); } - public void SetPrimitiveTopology(PrimitiveTopology topology) + public void SetPrimitiveTopology(PrimitiveTopology topology, bool signalChange = true) { _renderer.New().Set(topology); _renderer.QueueCommand(); } - public void SetProgram(IProgram program) + public void SetProgram(IProgram program, bool signalChange = true) { _renderer.New().Set(Ref(program)); _renderer.QueueCommand(); @@ -261,7 +261,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading _renderer.QueueCommand(); } - public void SetRenderTargetColorMasks(ReadOnlySpan componentMask) + public void SetRenderTargetColorMasks(ReadOnlySpan componentMask, bool signalChange = true) { _renderer.New().Set(_renderer.CopySpan(componentMask)); _renderer.QueueCommand(); @@ -273,7 +273,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading _renderer.QueueCommand(); } - public void SetScissors(ReadOnlySpan> scissors) + public void SetScissors(ReadOnlySpan> scissors, bool signalChange = true) { _renderer.New().Set(_renderer.CopySpan(scissors)); _renderer.QueueCommand(); @@ -339,7 +339,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading _renderer.QueueCommand(); } - public void SetViewports(ReadOnlySpan viewports) + public void SetViewports(ReadOnlySpan viewports, bool signalChange = true) { _renderer.New().Set(_renderer.CopySpan(viewports)); _renderer.QueueCommand(); diff --git a/src/Ryujinx.Graphics.GAL/ProgramPipelineState.cs b/src/Ryujinx.Graphics.GAL/ProgramPipelineState.cs index c16722af7..5bc8e00b7 100644 --- a/src/Ryujinx.Graphics.GAL/ProgramPipelineState.cs +++ b/src/Ryujinx.Graphics.GAL/ProgramPipelineState.cs @@ -51,11 +51,12 @@ namespace Ryujinx.Graphics.GAL public StencilTestDescriptor StencilTest; public FrontFace FrontFace; public Face CullMode; - public bool CullEnable; public PolygonModeMask BiasEnable; - public float LineWidth; + public bool AlphaToCoverageEnable; + public bool AlphaToOneEnable; + // TODO: Polygon mode. public bool DepthClampEnable; public bool RasterizerDiscard; @@ -63,6 +64,9 @@ namespace Ryujinx.Graphics.GAL public bool PrimitiveRestartEnable; public uint PatchControlPoints; + public float DepthBiasUnits; + public float DepthBiasFactor; + public DepthMode DepthMode; public void SetVertexAttribs(ReadOnlySpan vertexAttribs) diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index 1dc77b52d..a5ab82978 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -854,6 +854,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0); _pipeline.BiasEnable = enables; + _pipeline.DepthBiasUnits = units / 2f; + _pipeline.DepthBiasFactor = factor; + _context.Renderer.Pipeline.SetDepthBias(enables, factor, units / 2f, clamp); } @@ -1026,7 +1029,6 @@ 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); } @@ -1196,9 +1198,16 @@ 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); + if (face.CullEnable) + { + _pipeline.CullMode = face.CullFace; + _context.Renderer.Pipeline.SetFaceCulling(face.CullFace); + } + else + { + _pipeline.CullMode = Face.None; + _context.Renderer.Pipeline.SetFaceCulling(Face.None); + } UpdateFrontFace(yControl, face.FrontFace); } @@ -1388,6 +1397,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed bool alphaToCoverageEnable = (_state.State.MultisampleControl & 1) != 0; bool alphaToOneEnable = (_state.State.MultisampleControl & 0x10) != 0; + _pipeline.AlphaToCoverageEnable = alphaToCoverageEnable; + _pipeline.AlphaToOneEnable = alphaToOneEnable; _context.Renderer.Pipeline.SetMultisampleState(new MultisampleDescriptor( alphaToCoverageEnable, _state.State.AlphaToCoverageDitherEnable, diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index c36fc0ada..32a592cb9 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 7353; + private const uint CodeGenVersion = 6877; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs index 20f96462e..1aec895f0 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs @@ -22,6 +22,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private readonly CancellationToken _cancellationToken; private readonly Action _stateChangeCallback; + private readonly HashSet _pipelineStateSet = new(); + /// /// Indicates if the cache should be loaded. /// @@ -232,10 +234,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache for (int index = 0; index < ThreadCount; index++) { - workThreads[index] = new Thread(ProcessAsyncQueue) - { - Name = $"GPU.AsyncTranslationThread.{index}", - }; + workThreads[index] = new Thread(ProcessAsyncQueue) { Name = $"GPU.AsyncTranslationThread.{index}", }; } int programCount = _hostStorage.GetProgramCount(); @@ -305,6 +304,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache using var streams = _hostStorage.GetOutputStreams(_context); int packagedShaders = 0; + ProgramPipelineState currentPipelineState = default; + foreach (var kv in _programList) { if (!Active) @@ -314,12 +315,53 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache (CachedShaderProgram program, byte[] binaryCode) = kv.Value; - _hostStorage.AddShader(_context, program, binaryCode, streams); + if (program.SpecializationState.PipelineState.HasValue && _context.Capabilities.SupportsExtendedDynamicState) + { + currentPipelineState = program.SpecializationState.PipelineState.Value; - _stateChangeCallback(ShaderCacheState.Packaging, ++packagedShaders, _programList.Count); + if (_context.Capabilities.SupportsExtendedDynamicState) + { + currentPipelineState.StencilTest = default; + + currentPipelineState.CullMode = 0; + currentPipelineState.FrontFace = 0; + currentPipelineState.DepthTest = default; + currentPipelineState.Topology = ConvertToClass(currentPipelineState.Topology); + } + + if (_context.Capabilities.SupportsExtendedDynamicState2) + { + currentPipelineState.PrimitiveRestartEnable = false; + currentPipelineState.BiasEnable = 0; + currentPipelineState.RasterizerDiscard = false; + } + + if (_context.Capabilities.SupportsLogicOpDynamicState) + { + currentPipelineState.LogicOp = 0; + } + + if (_context.Capabilities.SupportsPatchControlPointsDynamicState) + { + currentPipelineState.PatchControlPoints = 0; + } + } + + if (!_pipelineStateSet.Contains(currentPipelineState) || + !_context.Capabilities.SupportsExtendedDynamicState || + !program.SpecializationState.PipelineState.HasValue) + { + _hostStorage.AddShader(_context, program, binaryCode, streams); + + _stateChangeCallback(ShaderCacheState.Packaging, ++packagedShaders, _programList.Count); + + if (_context.Capabilities.SupportsExtendedDynamicState) + { + _pipelineStateSet.Add(currentPipelineState); + } + } } - - Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {_programList.Count} shaders successfully."); + Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {packagedShaders} shaders successfully."); } else { @@ -343,6 +385,29 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache _stateChangeCallback(ShaderCacheState.Loaded, programCount, programCount); } + private PrimitiveTopology ConvertToClass(PrimitiveTopology topology) + { + return topology switch + { + PrimitiveTopology.Points => PrimitiveTopology.Points, + PrimitiveTopology.Lines or + PrimitiveTopology.LineStrip or + PrimitiveTopology.LinesAdjacency or + PrimitiveTopology.LineStripAdjacency => PrimitiveTopology.Lines, + PrimitiveTopology.Triangles or + PrimitiveTopology.TriangleStrip or + PrimitiveTopology.TriangleFan or + PrimitiveTopology.TrianglesAdjacency or + PrimitiveTopology.TriangleStripAdjacency or + PrimitiveTopology.Polygon => PrimitiveTopology.TriangleStrip, + PrimitiveTopology.Patches => PrimitiveTopology.Patches, + PrimitiveTopology.Quads => PrimitiveTopology.Quads, + PrimitiveTopology.QuadStrip => PrimitiveTopology.QuadStrip, + PrimitiveTopology.LineLoop => PrimitiveTopology.LineLoop, + _ => PrimitiveTopology.TriangleStrip, + }; + } + /// /// Enqueues a host program for compilation. /// diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index 6ead314fd..916f17c1c 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -191,6 +191,10 @@ namespace Ryujinx.Graphics.OpenGL supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle, supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters, supportsDepthClipControl: true, + supportsExtendedDynamicState: false, + supportsExtendedDynamicState2: false, + supportsLogicOpDynamicState: false, + supportsPatchControlPointsDynamicState: false, uniformBufferSetIndex: 0, storageBufferSetIndex: 1, textureSetIndex: 2, diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs index 096e2e5eb..700416427 100644 --- a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -898,7 +898,7 @@ namespace Ryujinx.Graphics.OpenGL } } - public void SetDepthTest(DepthTestDescriptor depthTest) + public void SetDepthTest(DepthTestDescriptor depthTest, bool signalChange = true) { if (depthTest.TestEnable) { @@ -915,11 +915,11 @@ namespace Ryujinx.Graphics.OpenGL _depthTestEnable = depthTest.TestEnable; } - public void SetFaceCulling(bool enable, Face face) + public void SetFaceCulling(Face face) { - _cullEnable = enable; + _cullEnable = face != Face.None; - if (!enable) + if (!_cullEnable) { GL.Disable(EnableCap.CullFace); return; @@ -1107,12 +1107,12 @@ namespace Ryujinx.Graphics.OpenGL GL.Enable(EnableCap.PrimitiveRestart); } - public void SetPrimitiveTopology(PrimitiveTopology topology) + public void SetPrimitiveTopology(PrimitiveTopology topology, bool signalChange = true) { _primitiveType = topology.Convert(); } - public void SetProgram(IProgram program) + public void SetProgram(IProgram program, bool signalChange = true) { Program prg = (Program)program; @@ -1154,7 +1154,7 @@ namespace Ryujinx.Graphics.OpenGL _rasterizerDiscard = discard; } - public void SetRenderTargetColorMasks(ReadOnlySpan componentMasks) + public void SetRenderTargetColorMasks(ReadOnlySpan componentMasks, bool signalChange = true) { _componentMasks = 0; @@ -1195,7 +1195,7 @@ namespace Ryujinx.Graphics.OpenGL _framebuffer.SetDrawBuffers(colors.Length); } - public void SetScissors(ReadOnlySpan> regions) + public void SetScissors(ReadOnlySpan> regions, bool signalChange = true) { int count = Math.Min(regions.Length, Constants.MaxViewports); @@ -1388,7 +1388,7 @@ namespace Ryujinx.Graphics.OpenGL _vertexArray.SetVertexBuffers(vertexBuffers); } - public void SetViewports(ReadOnlySpan viewports) + public void SetViewports(ReadOnlySpan viewports, bool signalChange = true) { Array.Resize(ref _viewportArray, viewports.Length * 4); Array.Resize(ref _depthRangeArray, viewports.Length * 2); diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs index a8e68f429..555151992 100644 --- a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs +++ b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs @@ -257,8 +257,8 @@ namespace Ryujinx.Graphics.Vulkan.Effects scissors[0] = new Rectangle(0, 0, texture.Width, texture.Height); - _pipeline.SetRenderTarget(texture, (uint)texture.Width, (uint)texture.Height); - _pipeline.SetRenderTargetColorMasks(colorMasks); + _pipeline.SetRenderTarget(texture, (uint)texture.Width, (uint)texture.Height, false); + _pipeline.SetRenderTargetColorMasks(colorMasks, false); _pipeline.SetScissors(scissors); _pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f)); } diff --git a/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs b/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs index 9d1fd9ffd..753a2fd43 100644 --- a/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs +++ b/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs @@ -238,6 +238,7 @@ namespace Ryujinx.Graphics.Vulkan Face.Back => CullModeFlags.BackBit, Face.Front => CullModeFlags.FrontBit, Face.FrontAndBack => CullModeFlags.FrontAndBack, + Face.None => CullModeFlags.None, _ => LogInvalidAndReturn(face, nameof(Face), CullModeFlags.BackBit), }; } @@ -310,6 +311,25 @@ namespace Ryujinx.Graphics.Vulkan }; } + public static PrimitiveTopology ConvertToClass(this PrimitiveTopology topology) + { + return topology switch + { + PrimitiveTopology.PointList => PrimitiveTopology.PointList, + PrimitiveTopology.LineList or + PrimitiveTopology.LineStrip or + PrimitiveTopology.LineListWithAdjacency or + PrimitiveTopology.LineStripWithAdjacency => PrimitiveTopology.LineList, + PrimitiveTopology.TriangleList or + PrimitiveTopology.TriangleStrip or + PrimitiveTopology.TriangleFan or + PrimitiveTopology.TriangleListWithAdjacency or + PrimitiveTopology.TriangleStripWithAdjacency => PrimitiveTopology.TriangleStrip, + PrimitiveTopology.PatchList => PrimitiveTopology.PatchList, + _ => LogInvalidAndReturn(topology, nameof(PrimitiveTopology), PrimitiveTopology.TriangleStrip), + }; + } + public static StencilOp Convert(this GAL.StencilOp op) { return op switch diff --git a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs index bd17867b1..5128a7c1c 100644 --- a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs +++ b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs @@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.Vulkan public readonly bool SupportsShaderStorageImageMultisample; public readonly bool SupportsConditionalRendering; public readonly bool SupportsExtendedDynamicState; + public readonly PhysicalDeviceExtendedDynamicState2FeaturesEXT SupportsExtendedDynamicState2; public readonly bool SupportsMultiView; public readonly bool SupportsNullDescriptors; public readonly bool SupportsPushDescriptors; @@ -46,6 +47,7 @@ namespace Ryujinx.Graphics.Vulkan public readonly bool SupportsViewportArray2; public readonly bool SupportsHostImportedMemory; public readonly bool SupportsDepthClipControl; + public readonly bool SupportsWideLines; public readonly bool SupportsAttachmentFeedbackLoop; public readonly bool SupportsDynamicAttachmentFeedbackLoop; public readonly uint SubgroupSize; @@ -54,6 +56,7 @@ namespace Ryujinx.Graphics.Vulkan public readonly uint VertexBufferAlignment; public readonly uint SubTexelPrecisionBits; public readonly ulong MinResourceAlignment; + public readonly uint MaxTessellationPatchSize; public HardwareCapabilities( bool supportsIndexTypeUint8, @@ -71,6 +74,8 @@ namespace Ryujinx.Graphics.Vulkan bool supportsShaderStorageImageMultisample, bool supportsConditionalRendering, bool supportsExtendedDynamicState, + PhysicalDeviceExtendedDynamicState2FeaturesEXT supportsExtendedDynamicState2, + uint maxTessellationPatchSize, bool supportsMultiView, bool supportsNullDescriptors, bool supportsPushDescriptors, @@ -86,6 +91,7 @@ namespace Ryujinx.Graphics.Vulkan bool supportsViewportArray2, bool supportsHostImportedMemory, bool supportsDepthClipControl, + bool supportsWideLines, bool supportsAttachmentFeedbackLoop, bool supportsDynamicAttachmentFeedbackLoop, uint subgroupSize, @@ -110,6 +116,8 @@ namespace Ryujinx.Graphics.Vulkan SupportsShaderStorageImageMultisample = supportsShaderStorageImageMultisample; SupportsConditionalRendering = supportsConditionalRendering; SupportsExtendedDynamicState = supportsExtendedDynamicState; + SupportsExtendedDynamicState2 = supportsExtendedDynamicState2; + MaxTessellationPatchSize = maxTessellationPatchSize; SupportsMultiView = supportsMultiView; SupportsNullDescriptors = supportsNullDescriptors; SupportsPushDescriptors = supportsPushDescriptors; @@ -125,6 +133,7 @@ namespace Ryujinx.Graphics.Vulkan SupportsViewportArray2 = supportsViewportArray2; SupportsHostImportedMemory = supportsHostImportedMemory; SupportsDepthClipControl = supportsDepthClipControl; + SupportsWideLines = supportsWideLines; SupportsAttachmentFeedbackLoop = supportsAttachmentFeedbackLoop; SupportsDynamicAttachmentFeedbackLoop = supportsDynamicAttachmentFeedbackLoop; SubgroupSize = subgroupSize; diff --git a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs index b7c42aff0..ce4f58057 100644 --- a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs +++ b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs @@ -429,35 +429,35 @@ namespace Ryujinx.Graphics.Vulkan if (dstIsDepthOrStencil) { - _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit); - _pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always)); + _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit, false); + _pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always), false); } else if (src.Info.Target.IsMultisample()) { - _pipeline.SetProgram(_programColorBlitMs); + _pipeline.SetProgram(_programColorBlitMs, false); } else if (clearAlpha) { - _pipeline.SetProgram(_programColorBlitClearAlpha); + _pipeline.SetProgram(_programColorBlitClearAlpha, false); } else { - _pipeline.SetProgram(_programColorBlit); + _pipeline.SetProgram(_programColorBlit, false); } int dstWidth = dst.Width; int dstHeight = dst.Height; - _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); - _pipeline.SetRenderTargetColorMasks(new uint[] { 0xf }); - _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dstWidth, dstHeight) }); + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false); + _pipeline.SetRenderTargetColorMasks(new uint[] { 0xf }, false); + _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dstWidth, dstHeight) }, false); if (clearAlpha) { _pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f)); } - _pipeline.SetViewports(viewports); + _pipeline.SetViewports(viewports, false); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); _pipeline.Draw(4, 1, 0, 0); @@ -524,10 +524,10 @@ namespace Ryujinx.Graphics.Vulkan int dstWidth = dst.Width; int dstHeight = dst.Height; - _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); - _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dstWidth, dstHeight) }); - _pipeline.SetViewports(viewports); - _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false); + _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dstWidth, dstHeight) }, false); + _pipeline.SetViewports(viewports, false); + _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip, false); var aspectFlags = src.Info.Format.ConvertAspectFlags(); @@ -589,12 +589,12 @@ namespace Ryujinx.Graphics.Vulkan if (isDepth) { - _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit); + _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit, false); _pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always)); } else { - _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programStencilBlitMs : _programStencilBlit); + _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programStencilBlitMs : _programStencilBlit, false); _pipeline.SetStencilTest(CreateStencilTestDescriptor(true)); } @@ -684,11 +684,11 @@ namespace Ryujinx.Graphics.Vulkan program = _programColorClearF; } - _pipeline.SetProgram(program); - _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); - _pipeline.SetRenderTargetColorMasks(new[] { componentMask }); - _pipeline.SetViewports(viewports); - _pipeline.SetScissors(stackalloc Rectangle[] { scissor }); + _pipeline.SetProgram(program, false); + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false); + _pipeline.SetRenderTargetColorMasks(new[] { componentMask }, false); + _pipeline.SetViewports(viewports, false); + _pipeline.SetScissors(stackalloc Rectangle[] { scissor }, false); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); _pipeline.Draw(4, 1, 0, 0); _pipeline.Finish(); @@ -731,12 +731,12 @@ namespace Ryujinx.Graphics.Vulkan 0f, 1f); - _pipeline.SetProgram(_programDepthStencilClear); - _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); - _pipeline.SetViewports(viewports); - _pipeline.SetScissors(stackalloc Rectangle[] { scissor }); - _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); - _pipeline.SetDepthTest(new DepthTestDescriptor(true, depthMask, CompareOp.Always)); + _pipeline.SetProgram(_programDepthStencilClear, false); + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false); + _pipeline.SetViewports(viewports, false); + _pipeline.SetScissors(stackalloc Rectangle[] { scissor }, false); + _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip, false); + _pipeline.SetDepthTest(new DepthTestDescriptor(true, depthMask, CompareOp.Always), false); _pipeline.SetStencilTest(CreateStencilTestDescriptor(stencilMask != 0, stencilValue, 0xff, stencilMask)); _pipeline.Draw(4, 1, 0, 0); _pipeline.Finish(); @@ -794,8 +794,8 @@ namespace Ryujinx.Graphics.Vulkan 0f, 1f); - pipeline.SetProgram(_programColorBlit); - pipeline.SetViewports(viewports); + pipeline.SetProgram(_programColorBlit, false); + pipeline.SetViewports(viewports, false); pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); pipeline.Draw(4, 1, 0, 0); @@ -1129,16 +1129,16 @@ namespace Ryujinx.Graphics.Vulkan 0f, 1f); - _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dst.Width, dst.Height) }); - _pipeline.SetViewports(viewports); - _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); + _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dst.Width, dst.Height) }, false); + _pipeline.SetViewports(viewports, false); + _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip, false); for (int z = 0; z < depth; z++) { var srcView = Create2DLayerView(src, srcLayer + z, 0); var dstView = Create2DLayerView(dst, dstLayer + z, 0); - _pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height); + _pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height, false); CopyMSDraw(srcView, aspectFlags, fromMS: true); @@ -1251,9 +1251,9 @@ namespace Ryujinx.Graphics.Vulkan 1f); _pipeline.SetRenderTargetColorMasks(new uint[] { 0xf }); - _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dst.Width, dst.Height) }); - _pipeline.SetViewports(viewports); - _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); + _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dst.Width, dst.Height) }, false); + _pipeline.SetViewports(viewports, false); + _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip, false); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, buffer.Range) }); @@ -1264,7 +1264,7 @@ namespace Ryujinx.Graphics.Vulkan var srcView = Create2DLayerView(src, srcLayer + z, 0); var dstView = Create2DLayerView(dst, dstLayer + z, 0); - _pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height); + _pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height, false); CopyMSDraw(srcView, aspectFlags, fromMS: false); @@ -1281,7 +1281,7 @@ namespace Ryujinx.Graphics.Vulkan } else { - _pipeline.SetProgram(_programColorDrawToMs); + _pipeline.SetProgram(_programColorDrawToMs, false); var format = GetFormat(src.Info.BytesPerPixel); var vkFormat = FormatTable.GetFormat(format); @@ -1358,12 +1358,12 @@ namespace Ryujinx.Graphics.Vulkan if (isDepth) { - _pipeline.SetProgram(fromMS ? _programDepthDrawToNonMs : _programDepthDrawToMs); + _pipeline.SetProgram(fromMS ? _programDepthDrawToNonMs : _programDepthDrawToMs, false); _pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always)); } else { - _pipeline.SetProgram(fromMS ? _programStencilDrawToNonMs : _programStencilDrawToMs); + _pipeline.SetProgram(fromMS ? _programStencilDrawToNonMs : _programStencilDrawToMs, false); _pipeline.SetStencilTest(CreateStencilTestDescriptor(true)); } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index addad83fd..fe5d13b59 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -1,3 +1,5 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; using Silk.NET.Vulkan; @@ -74,6 +76,7 @@ namespace Ryujinx.Graphics.Vulkan private readonly BufferState[] _transformFeedbackBuffers; private readonly VertexBufferState[] _vertexBuffers; private ulong _vertexBuffersDirty; + private bool _bindingsSet; protected Rectangle ClearScissor; private readonly VertexBufferUpdater _vertexBufferUpdater; @@ -87,6 +90,9 @@ namespace Ryujinx.Graphics.Vulkan private bool _tfEnabled; private bool _tfActive; + private readonly bool _supportExtDynamic; + private readonly bool _supportExtDynamic2; + private FeedbackLoopAspects _feedbackLoop; private bool _passWritesDepthStencil; @@ -94,6 +100,8 @@ namespace Ryujinx.Graphics.Vulkan public ulong DrawCount { get; private set; } public bool RenderPassActive { get; private set; } + private readonly int[] _vertexBufferBindings; + public unsafe PipelineBase(VulkanRenderer gd, Device device) { Gd = gd; @@ -126,7 +134,19 @@ namespace Ryujinx.Graphics.Vulkan _storedBlend = new PipelineColorBlendAttachmentState[Constants.MaxRenderTargets]; - _newState.Initialize(); + _supportExtDynamic = gd.Capabilities.SupportsExtendedDynamicState; + + _supportExtDynamic2 = gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2; + + _bindingsSet = false; + + _vertexBufferBindings = new int[Constants.MaxVertexBuffers]; + for (int i = 0; i < Constants.MaxVertexBuffers; i++) + { + _vertexBufferBindings[i] = i + 1; + } + + _newState.Initialize(gd.Capabilities); } public void Initialize() @@ -632,19 +652,46 @@ namespace Ryujinx.Graphics.Vulkan { if (texture is TextureView srcTexture) { - var oldCullMode = _newState.CullMode; - var oldStencilTestEnable = _newState.StencilTestEnable; - var oldDepthTestEnable = _newState.DepthTestEnable; - var oldDepthWriteEnable = _newState.DepthWriteEnable; - var oldViewports = DynamicState.Viewports; - var oldViewportsCount = _newState.ViewportsCount; - var oldTopology = _topology; + CullModeFlags oldCullMode; + bool oldStencilTestEnable; + bool oldDepthTestEnable; + bool oldDepthWriteEnable; + PrimitiveTopology oldTopology; + Array16 oldViewports = DynamicState.Viewports; + uint oldViewportsCount; - _newState.CullMode = CullModeFlags.None; - _newState.StencilTestEnable = false; - _newState.DepthTestEnable = false; - _newState.DepthWriteEnable = false; - SignalStateChange(); + if (_supportExtDynamic) + { + oldCullMode = DynamicState.CullMode; + oldStencilTestEnable = DynamicState.StencilTestEnable; + oldDepthTestEnable = DynamicState.DepthTestEnable; + oldDepthWriteEnable = DynamicState.DepthWriteEnable; + oldTopology = _topology; + oldViewportsCount = DynamicState.ViewportsCount; + } + else + { + oldCullMode = _newState.CullMode; + oldStencilTestEnable = _newState.StencilTestEnable; + oldDepthTestEnable = _newState.DepthTestEnable; + oldDepthWriteEnable = _newState.DepthWriteEnable; + oldTopology = _topology; + oldViewportsCount = _newState.ViewportsCount; + } + + if (_supportExtDynamic) + { + DynamicState.SetCullMode(CullModeFlags.None); + DynamicState.SetDepthTestBool(false, false); + DynamicState.SetStencilTest(false); + } + else + { + _newState.CullMode = CullModeFlags.None; + _newState.StencilTestEnable = false; + _newState.DepthTestEnable = false; + _newState.DepthWriteEnable = false; + } Gd.HelperShader.DrawTexture( Gd, @@ -654,16 +701,24 @@ namespace Ryujinx.Graphics.Vulkan srcRegion, dstRegion); - _newState.CullMode = oldCullMode; - _newState.StencilTestEnable = oldStencilTestEnable; - _newState.DepthTestEnable = oldDepthTestEnable; - _newState.DepthWriteEnable = oldDepthWriteEnable; + if (_supportExtDynamic) + { + DynamicState.SetCullMode(oldCullMode); + DynamicState.SetStencilTest(oldStencilTestEnable); + DynamicState.SetDepthTestBool(oldDepthTestEnable, oldDepthWriteEnable); + } + else + { + _newState.CullMode = oldCullMode; + _newState.StencilTestEnable = oldStencilTestEnable; + _newState.DepthTestEnable = oldDepthTestEnable; + _newState.DepthWriteEnable = oldDepthWriteEnable; + _newState.ViewportsCount = oldViewportsCount; + } + SetPrimitiveTopology(oldTopology); DynamicState.SetViewports(ref oldViewports, oldViewportsCount); - - _newState.ViewportsCount = oldViewportsCount; - SignalStateChange(); } } @@ -792,48 +847,102 @@ namespace Ryujinx.Graphics.Vulkan public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp) { - DynamicState.SetDepthBias(factor, units, clamp); + bool depthBiasEnable = (enables != 0) && (factor != 0 && units != 0); + bool changed = false; - _newState.DepthBiasEnable = enables != 0; - SignalStateChange(); - } + if (_supportExtDynamic2) + { + DynamicState.SetDepthBiasEnable(depthBiasEnable); + } + else if (_newState.DepthBiasEnable != depthBiasEnable) + { + _newState.DepthBiasEnable = depthBiasEnable; + changed = true; + } - public void SetDepthClamp(bool clamp) - { - _newState.DepthClampEnable = clamp; - SignalStateChange(); - } + if (depthBiasEnable) + { + DynamicState.SetDepthBias(factor, units, clamp); + } - public void SetDepthMode(DepthMode mode) - { - bool oldMode = _newState.DepthMode; - _newState.DepthMode = mode == DepthMode.MinusOneToOne; - if (_newState.DepthMode != oldMode) + if (changed) { SignalStateChange(); } } - public void SetDepthTest(DepthTestDescriptor depthTest) + public void SetDepthClamp(bool clamp) { - _newState.DepthTestEnable = depthTest.TestEnable; - _newState.DepthWriteEnable = depthTest.WriteEnable; - _newState.DepthCompareOp = depthTest.Func.Convert(); + _newState.DepthClampEnable = clamp; - UpdatePassDepthStencil(); SignalStateChange(); } - public void SetFaceCulling(bool enable, Face face) + public void SetDepthMode(DepthMode mode) { - _newState.CullMode = enable ? face.Convert() : CullModeFlags.None; + bool newMode = mode == DepthMode.MinusOneToOne; + + if (_newState.DepthMode == newMode) + { + return; + } + + _newState.DepthMode = newMode; + SignalStateChange(); } + public void SetDepthTest(DepthTestDescriptor depthTest, bool signalChange = true) + { + if (_supportExtDynamic) + { + DynamicState.SetDepthTestBool(depthTest.TestEnable, depthTest.WriteEnable); + if (depthTest.TestEnable) + { + DynamicState.SetDepthTestCompareOp(depthTest.Func.Convert()); + } + } + else + { + _newState.DepthTestEnable = depthTest.TestEnable; + _newState.DepthWriteEnable = depthTest.WriteEnable; + _newState.DepthCompareOp = depthTest.Func.Convert(); + + if (signalChange) + { + SignalStateChange(); + } + } + + UpdatePassDepthStencil(); + } + + public void SetFaceCulling(Face face) + { + if (_supportExtDynamic) + { + DynamicState.SetCullMode(face.Convert()); + } + else + { + _newState.CullMode = face.Convert(); + + SignalStateChange(); + } + } + public void SetFrontFace(FrontFace frontFace) { - _newState.FrontFace = frontFace.Convert(); - SignalStateChange(); + if (_supportExtDynamic) + { + DynamicState.SetFrontFace(frontFace.Convert()); + } + else + { + _newState.FrontFace = frontFace.Convert(); + + SignalStateChange(); + } } public void SetImage(ShaderStage stage, int binding, ITexture image) @@ -872,28 +981,56 @@ namespace Ryujinx.Graphics.Vulkan public void SetLineParameters(float width, bool smooth) { - _newState.LineWidth = width; - SignalStateChange(); + if (!Gd.IsMoltenVk) + { + DynamicState.SetLineWidth(Gd.Capabilities.SupportsWideLines ? width : 1.0f); + } } public void SetLogicOpState(bool enable, LogicalOp op) { + // Vendors other than NVIDIA have a bug where it enables logical operations even for float formats, + // so we need to force disable them here. + bool logicOpEnable = enable && (Gd.Vendor == Vendor.Nvidia || _newState.Internal.LogicOpsAllowed); + _newState.LogicOpEnable = enable; - _newState.LogicOp = op.Convert(); + + if (Gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2LogicOp) + { + if (logicOpEnable) + { + DynamicState.SetLogicOp(op.Convert()); + } + } + else + { + _newState.LogicOp = op.Convert(); + } + SignalStateChange(); } public void SetMultisampleState(MultisampleDescriptor multisample) { _newState.AlphaToCoverageEnable = multisample.AlphaToCoverageEnable; + _newState.AlphaToOneEnable = multisample.AlphaToOneEnable; + SignalStateChange(); } public void SetPatchParameters(int vertices, ReadOnlySpan defaultOuterLevel, ReadOnlySpan defaultInnerLevel) { - _newState.PatchControlPoints = (uint)vertices; - SignalStateChange(); + if (Gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints) + { + DynamicState.SetPatchControlPoints((uint)vertices); + } + else + { + _newState.PatchControlPoints = (uint)vertices; + + SignalStateChange(); + } // TODO: Default levels (likely needs emulation on shaders?) } @@ -910,12 +1047,21 @@ namespace Ryujinx.Graphics.Vulkan public void SetPrimitiveRestart(bool enable, int index) { - _newState.PrimitiveRestartEnable = enable; + if (_supportExtDynamic2) + { + DynamicState.SetPrimitiveRestartEnable(enable); + } + else + { + _newState.PrimitiveRestartEnable = enable; + + SignalStateChange(); + } + // TODO: What to do about the index? - SignalStateChange(); } - public void SetPrimitiveTopology(PrimitiveTopology topology) + public void SetPrimitiveTopology(PrimitiveTopology topology, bool signalChange = true) { _topology = topology; @@ -923,10 +1069,18 @@ namespace Ryujinx.Graphics.Vulkan _newState.Topology = vkTopology; - SignalStateChange(); + if (_supportExtDynamic) + { + DynamicState.SetPrimitiveTopology(vkTopology); + } + + if (signalChange) + { + SignalStateChange(); + } } - public void SetProgram(IProgram program) + public void SetProgram(IProgram program, bool signalChange = true) { var internalProgram = (ShaderCollection)program; var stages = internalProgram.GetInfos(); @@ -942,7 +1096,10 @@ namespace Ryujinx.Graphics.Vulkan stages.CopyTo(_newState.Stages.AsSpan()[..stages.Length]); - SignalStateChange(); + if (signalChange) + { + SignalStateChange(); + } if (internalProgram.IsCompute) { @@ -968,18 +1125,26 @@ namespace Ryujinx.Graphics.Vulkan public void SetRasterizerDiscard(bool discard) { - _newState.RasterizerDiscardEnable = discard; - SignalStateChange(); + if (_supportExtDynamic2) + { + DynamicState.SetRasterizerDiscard(discard); + } + else + { + _newState.RasterizerDiscardEnable = discard; + + SignalStateChange(); + } if (!discard && Gd.IsQualcommProprietary) { // On Adreno, enabling rasterizer discard somehow corrupts the viewport state. // Force it to be updated on next use to work around this bug. - DynamicState.ForceAllDirty(); + DynamicState.ForceAllDirty(Gd); } } - public void SetRenderTargetColorMasks(ReadOnlySpan componentMask) + public void SetRenderTargetColorMasks(ReadOnlySpan componentMask, bool signalChange = true) { int count = Math.Min(Constants.MaxRenderTargets, componentMask.Length); int writtenAttachments = 0; @@ -1019,7 +1184,10 @@ namespace Ryujinx.Graphics.Vulkan } else { - SignalStateChange(); + if (signalChange) + { + SignalStateChange(); + } if (writtenAttachments != _writtenAttachmentCount) { @@ -1043,7 +1211,7 @@ namespace Ryujinx.Graphics.Vulkan SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR); } - public void SetScissors(ReadOnlySpan> regions) + public void SetScissors(ReadOnlySpan> regions, bool signalChange = true) { int maxScissors = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1; int count = Math.Min(maxScissors, regions.Length); @@ -1052,6 +1220,8 @@ namespace Ryujinx.Graphics.Vulkan ClearScissor = regions[0]; } + DynamicState.ScissorsCount = count; + for (int i = 0; i < count; i++) { var region = regions[i]; @@ -1061,34 +1231,56 @@ namespace Ryujinx.Graphics.Vulkan DynamicState.SetScissor(i, new Rect2D(offset, extent)); } - DynamicState.ScissorsCount = count; + if (!_supportExtDynamic) + { + _newState.ScissorsCount = (uint)count; - _newState.ScissorsCount = (uint)count; - SignalStateChange(); + if (signalChange) + { + SignalStateChange(); + } + } } public void SetStencilTest(StencilTestDescriptor stencilTest) { - DynamicState.SetStencilMasks( - (uint)stencilTest.BackFuncMask, + if (_supportExtDynamic) + { + DynamicState.SetStencilTestandOp( + stencilTest.BackSFail.Convert(), + stencilTest.BackDpPass.Convert(), + stencilTest.BackDpFail.Convert(), + stencilTest.BackFunc.Convert(), + stencilTest.FrontSFail.Convert(), + stencilTest.FrontDpPass.Convert(), + stencilTest.FrontDpFail.Convert(), + stencilTest.FrontFunc.Convert(), + stencilTest.TestEnable); + + UpdatePassDepthStencil(); + } + else + { + _newState.StencilBackFailOp = stencilTest.BackSFail.Convert(); + _newState.StencilBackPassOp = stencilTest.BackDpPass.Convert(); + _newState.StencilBackDepthFailOp = stencilTest.BackDpFail.Convert(); + _newState.StencilBackCompareOp = stencilTest.BackFunc.Convert(); + _newState.StencilFrontFailOp = stencilTest.FrontSFail.Convert(); + _newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert(); + _newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert(); + _newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert(); + _newState.StencilTestEnable = stencilTest.TestEnable; + + UpdatePassDepthStencil(); + SignalStateChange(); + } + + DynamicState.SetStencilMask((uint)stencilTest.BackFuncMask, (uint)stencilTest.BackMask, (uint)stencilTest.BackFuncRef, (uint)stencilTest.FrontFuncMask, (uint)stencilTest.FrontMask, (uint)stencilTest.FrontFuncRef); - - _newState.StencilTestEnable = stencilTest.TestEnable; - _newState.StencilBackFailOp = stencilTest.BackSFail.Convert(); - _newState.StencilBackPassOp = stencilTest.BackDpPass.Convert(); - _newState.StencilBackDepthFailOp = stencilTest.BackDpFail.Convert(); - _newState.StencilBackCompareOp = stencilTest.BackFunc.Convert(); - _newState.StencilFrontFailOp = stencilTest.FrontSFail.Convert(); - _newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert(); - _newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert(); - _newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert(); - - UpdatePassDepthStencil(); - SignalStateChange(); } public void SetStorageBuffers(ReadOnlySpan buffers) @@ -1207,12 +1399,24 @@ namespace Ryujinx.Graphics.Vulkan { int count = Math.Min(Constants.MaxVertexBuffers, vertexBuffers.Length); - _newState.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex); - int validCount = 1; + if (!_bindingsSet) + { + _newState.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, _supportExtDynamic && (!Gd.IsMoltenVk || Gd.SupportsMTL31) ? null : 0, VertexInputRate.Vertex); + + for (int i = 1; i < count; i++) + { + _newState.Internal.VertexBindingDescriptions[i] = new VertexInputBindingDescription((uint)i); + } + + _bindingsSet = true; + } + BufferHandle lastHandle = default; Auto lastBuffer = default; + bool vertexBindingDescriptionChanged = false; + bool vertexDescriptionCountChanged = false; for (int i = 0; i < count; i++) { @@ -1231,13 +1435,32 @@ namespace Ryujinx.Graphics.Vulkan if (vb != null) { - int binding = i + 1; int descriptorIndex = validCount++; - _newState.Internal.VertexBindingDescriptions[descriptorIndex] = new VertexInputBindingDescription( - (uint)binding, - (uint)vertexBuffer.Stride, - inputRate); + if (_supportExtDynamic && (!Gd.IsMoltenVk || Gd.SupportsMTL31)) + { + if (_newState.Internal.VertexBindingDescriptions[descriptorIndex].InputRate != inputRate || + _newState.Internal.VertexBindingDescriptions[descriptorIndex].Binding != _vertexBufferBindings[i]) + { + _newState.Internal.VertexBindingDescriptions[descriptorIndex].InputRate = inputRate; + _newState.Internal.VertexBindingDescriptions[descriptorIndex].Binding = (uint)_vertexBufferBindings[i]; + + vertexBindingDescriptionChanged = true; + } + } + else + { + if (_newState.Internal.VertexBindingDescriptions[descriptorIndex].InputRate != inputRate || + _newState.Internal.VertexBindingDescriptions[descriptorIndex].Stride != vertexBuffer.Stride || + _newState.Internal.VertexBindingDescriptions[descriptorIndex].Binding != _vertexBufferBindings[i]) + { + _newState.Internal.VertexBindingDescriptions[descriptorIndex].Binding = (uint)_vertexBufferBindings[i]; + _newState.Internal.VertexBindingDescriptions[descriptorIndex].Stride = (uint)vertexBuffer.Stride; + _newState.Internal.VertexBindingDescriptions[descriptorIndex].InputRate = inputRate; + + vertexBindingDescriptionChanged = true; + } + } int vbSize = vertexBuffer.Buffer.Size; @@ -1253,7 +1476,7 @@ namespace Ryujinx.Graphics.Vulkan } } - ref var buffer = ref _vertexBuffers[binding]; + ref var buffer = ref _vertexBuffers[_vertexBufferBindings[i]]; int oldScalarAlign = buffer.AttributeScalarAlignment; if (Gd.Capabilities.VertexBufferAlignment < 2 && @@ -1270,7 +1493,7 @@ namespace Ryujinx.Graphics.Vulkan vbSize, vertexBuffer.Stride); - buffer.BindVertexBuffer(Gd, Cbs, (uint)binding, ref _newState, _vertexBufferUpdater); + buffer.BindVertexBuffer(Gd, Cbs, (uint)_vertexBufferBindings[i], ref _newState, _vertexBufferUpdater); } } else @@ -1286,7 +1509,7 @@ namespace Ryujinx.Graphics.Vulkan vbSize, vertexBuffer.Stride); - _vertexBuffersDirty |= 1UL << binding; + _vertexBuffersDirty |= 1UL << _vertexBufferBindings[i]; } buffer.AttributeScalarAlignment = oldScalarAlign; @@ -1296,11 +1519,19 @@ namespace Ryujinx.Graphics.Vulkan _vertexBufferUpdater.Commit(Cbs); - _newState.VertexBindingDescriptionsCount = (uint)validCount; - SignalStateChange(); + if (_newState.VertexBindingDescriptionsCount != validCount) + { + _newState.VertexBindingDescriptionsCount = (uint)validCount; + vertexDescriptionCountChanged = true; + } + + if (vertexDescriptionCountChanged || vertexBindingDescriptionChanged) + { + SignalStateChange(); + } } - public void SetViewports(ReadOnlySpan viewports) + public void SetViewports(ReadOnlySpan viewports, bool signalChange = true) { int maxViewports = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1; int count = Math.Min(maxViewports, viewports.Length); @@ -1325,8 +1556,15 @@ namespace Ryujinx.Graphics.Vulkan Clamp(viewport.DepthFar))); } - _newState.ViewportsCount = (uint)count; - SignalStateChange(); + if (!_supportExtDynamic) + { + _newState.ViewportsCount = (uint)count; + + if (signalChange) + { + SignalStateChange(); + } + } } public void SwapBuffer(Auto from, Auto to) @@ -1375,7 +1613,7 @@ namespace Ryujinx.Graphics.Vulkan _vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length); _descriptorSetUpdater.SignalCommandBufferChange(); - DynamicState.ForceAllDirty(); + DynamicState.ForceAllDirty(Gd); _currentPipelineHandle = 0; } @@ -1529,9 +1767,10 @@ namespace Ryujinx.Graphics.Vulkan { if (Gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop) { + _newState.FeedbackLoopDynamicState = true; DynamicState.SetFeedbackLoop(aspects); } - else + else if (Gd.Capabilities.SupportsAttachmentFeedbackLoop) { _newState.FeedbackLoopAspects = aspects; } @@ -1591,7 +1830,14 @@ namespace Ryujinx.Graphics.Vulkan } // Stencil test being enabled doesn't necessarily mean a write, but it's not critical to check. - _passWritesDepthStencil |= (_newState.DepthTestEnable && _newState.DepthWriteEnable) || _newState.StencilTestEnable; + if (_supportExtDynamic) + { + _passWritesDepthStencil |= (DynamicState.DepthTestEnable && DynamicState.DepthWriteEnable) || DynamicState.StencilTestEnable; + } + else + { + _passWritesDepthStencil |= (_newState.DepthTestEnable && _newState.DepthWriteEnable) || _newState.StencilTestEnable; + } } private bool RecreateGraphicsPipelineIfNeeded() diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs index 85069c6b2..acf42a9bc 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs @@ -4,6 +4,7 @@ using Silk.NET.Vulkan; using System; using Format = Silk.NET.Vulkan.Format; using PolygonMode = Silk.NET.Vulkan.PolygonMode; +using PrimitiveTopology = Ryujinx.Graphics.GAL.PrimitiveTopology; namespace Ryujinx.Graphics.Vulkan { @@ -154,64 +155,84 @@ namespace Ryujinx.Graphics.Vulkan public static PipelineState ToVulkanPipelineState(this ProgramPipelineState state, VulkanRenderer gd) { + var extendedDynamicState2 = gd.Capabilities.SupportsExtendedDynamicState2; + var extendedDynamicState = gd.Capabilities.SupportsExtendedDynamicState; + PipelineState pipeline = new(); - pipeline.Initialize(); + pipeline.Initialize(gd.Capabilities); // It is assumed that Dynamic State is enabled when this conversion is used. - - pipeline.CullMode = state.CullEnable ? state.CullMode.Convert() : CullModeFlags.None; - 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.AlphaToCoverageEnable = state.AlphaToCoverageEnable; + pipeline.AlphaToOneEnable = state.AlphaToOneEnable; + pipeline.DepthMode = state.DepthMode == DepthMode.MinusOneToOne; - pipeline.FrontFace = state.FrontFace.Convert(); - pipeline.HasDepthStencil = state.DepthStencilEnable; - pipeline.LineWidth = state.LineWidth; - pipeline.LogicOpEnable = state.LogicOpEnable; - pipeline.LogicOp = state.LogicOp.Convert(); - pipeline.PatchControlPoints = state.PatchControlPoints; pipeline.PolygonMode = PolygonMode.Fill; // Not implemented. - pipeline.PrimitiveRestartEnable = state.PrimitiveRestartEnable; - pipeline.RasterizerDiscardEnable = state.RasterizerDiscard; - pipeline.SamplesCount = (uint)state.SamplesCount; - - if (gd.Capabilities.SupportsMultiView) - { - pipeline.ScissorsCount = Constants.MaxViewports; - pipeline.ViewportsCount = Constants.MaxViewports; - } - else - { - pipeline.ScissorsCount = 1; - pipeline.ViewportsCount = 1; - } - - 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.StencilBackFailOp = state.StencilTest.BackSFail.Convert(); - pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert(); - pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert(); - pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert(); - - pipeline.StencilTestEnable = state.StencilTest.TestEnable; pipeline.Topology = gd.TopologyRemap(state.Topology).Convert(); + if (!extendedDynamicState) + { + pipeline.DepthCompareOp = state.DepthTest.Func.Convert(); + pipeline.CullMode = state.CullMode.Convert(); + + pipeline.DepthTestEnable = state.DepthTest.TestEnable; + pipeline.DepthWriteEnable = state.DepthTest.WriteEnable; + + pipeline.FrontFace = state.FrontFace.Convert(); + + if (gd.Capabilities.SupportsMultiView) + { + pipeline.ScissorsCount = Constants.MaxViewports; + pipeline.ViewportsCount = Constants.MaxViewports; + } + else + { + pipeline.ScissorsCount = 1; + pipeline.ViewportsCount = 1; + } + + pipeline.StencilTestEnable = state.StencilTest.TestEnable; + + 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.StencilBackFailOp = state.StencilTest.BackSFail.Convert(); + pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert(); + pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert(); + pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert(); + } + + if (!extendedDynamicState2.ExtendedDynamicState2) + { + pipeline.PrimitiveRestartEnable = state.PrimitiveRestartEnable; + pipeline.RasterizerDiscardEnable = state.RasterizerDiscard; + pipeline.DepthBiasEnable = (state.BiasEnable != 0) && + (state.DepthBiasFactor != 0 && state.DepthBiasUnits != 0); + } + + if (!extendedDynamicState2.ExtendedDynamicState2LogicOp) + { + pipeline.LogicOp = state.LogicOp.Convert(); + } + + if (!extendedDynamicState2.ExtendedDynamicState2PatchControlPoints) + { + pipeline.PatchControlPoints = state.PatchControlPoints; + } + + pipeline.SamplesCount = (uint)state.SamplesCount; + + pipeline.LogicOpEnable = state.LogicOpEnable; + int vaCount = Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount); int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount); @@ -235,7 +256,7 @@ namespace Ryujinx.Graphics.Vulkan } int descriptorIndex = 1; - pipeline.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex); + pipeline.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, extendedDynamicState && (!gd.IsMoltenVk || gd.SupportsMTL31) ? null : 0, VertexInputRate.Vertex); for (int i = 0; i < vbCount; i++) { @@ -255,7 +276,7 @@ namespace Ryujinx.Graphics.Vulkan // TODO: Support divisor > 1 pipeline.Internal.VertexBindingDescriptions[descriptorIndex++] = new VertexInputBindingDescription( (uint)i + 1, - (uint)alignedStride, + extendedDynamicState && (!gd.IsMoltenVk || gd.SupportsMTL31) ? null : (uint)alignedStride, inputRate); } } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs index ad26ff7b3..251378dd7 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs @@ -1,6 +1,8 @@ using Ryujinx.Common.Memory; using Silk.NET.Vulkan; using Silk.NET.Vulkan.Extensions.EXT; +using System; +using System.Numerics; namespace Ryujinx.Graphics.Vulkan { @@ -9,6 +11,7 @@ namespace Ryujinx.Graphics.Vulkan private float _depthBiasSlopeFactor; private float _depthBiasConstantFactor; private float _depthBiasClamp; + private bool _depthBiasEnable; public int ScissorsCount; private Array16 _scissors; @@ -20,6 +23,23 @@ namespace Ryujinx.Graphics.Vulkan private uint _frontWriteMask; private uint _frontReference; + private StencilOp _backFailOp; + private StencilOp _backPassOp; + private StencilOp _backDepthFailOp; + private CompareOp _backCompareOp; + private StencilOp _frontFailOp; + private StencilOp _frontPassOp; + private StencilOp _frontDepthFailOp; + private CompareOp _frontCompareOp; + + private float _lineWidth; + + public bool StencilTestEnable; + + public bool DepthTestEnable; + public bool DepthWriteEnable; + private CompareOp _depthCompareOp; + private Array4 _blendConstants; private FeedbackLoopAspects _feedbackLoopAspects; @@ -27,6 +47,20 @@ namespace Ryujinx.Graphics.Vulkan public uint ViewportsCount; public Array16 Viewports; + public CullModeFlags CullMode; + private FrontFace _frontFace; + + private bool _discard; + + private LogicOp _logicOp; + + private uint _patchControlPoints; + + public PrimitiveTopology Topology; + + private bool _primitiveRestartEnable; + + [Flags] private enum DirtyFlags { None = 0, @@ -36,7 +70,21 @@ namespace Ryujinx.Graphics.Vulkan Stencil = 1 << 3, Viewport = 1 << 4, FeedbackLoop = 1 << 5, - All = Blend | DepthBias | Scissor | Stencil | Viewport | FeedbackLoop, + CullMode = 1 << 6, + FrontFace = 1 << 7, + DepthTestBool = 1 << 8, + DepthTestCompareOp = 1 << 9, + StencilTestEnableAndStencilOp = 1 << 10, + LineWidth = 1 << 11, + RasterDiscard = 1 << 12, + LogicOp = 1 << 13, + PatchControlPoints = 1 << 14, + PrimitiveRestart = 1 << 15, + PrimitiveTopology = 1 << 16, + DepthBiasEnable = 1 << 17, + Standard = Blend | DepthBias | Scissor | Stencil | Viewport, + Extended = CullMode | FrontFace | DepthTestBool | DepthTestCompareOp | StencilTestEnableAndStencilOp | PrimitiveTopology, + Extended2 = RasterDiscard | PrimitiveRestart | DepthBiasEnable, } private DirtyFlags _dirty; @@ -47,7 +95,6 @@ namespace Ryujinx.Graphics.Vulkan _blendConstants[1] = g; _blendConstants[2] = b; _blendConstants[3] = a; - _dirty |= DirtyFlags.Blend; } @@ -60,15 +107,64 @@ namespace Ryujinx.Graphics.Vulkan _dirty |= DirtyFlags.DepthBias; } + public void SetDepthBiasEnable(bool enable) + { + _depthBiasEnable = enable; + _dirty |= DirtyFlags.DepthBiasEnable; + } + public void SetScissor(int index, Rect2D scissor) { _scissors[index] = scissor; - _dirty |= DirtyFlags.Scissor; } - public void SetStencilMasks( - uint backCompareMask, + public void SetDepthTestBool(bool testEnable, bool writeEnable) + { + DepthTestEnable = testEnable; + DepthWriteEnable = writeEnable; + _dirty |= DirtyFlags.DepthTestBool; + } + + public void SetDepthTestCompareOp(CompareOp depthTestOp) + { + _depthCompareOp = depthTestOp; + _dirty |= DirtyFlags.DepthTestCompareOp; + } + + public void SetStencilTestandOp( + StencilOp backFailOp, + StencilOp backPassOp, + StencilOp backDepthFailOp, + CompareOp backCompareOp, + StencilOp frontFailOp, + StencilOp frontPassOp, + StencilOp frontDepthFailOp, + CompareOp frontCompareOp, + bool stencilTestEnable) + { + _backFailOp = backFailOp; + _backPassOp = backPassOp; + _backDepthFailOp = backDepthFailOp; + _backCompareOp = backCompareOp; + _frontFailOp = frontFailOp; + _frontPassOp = frontPassOp; + _frontDepthFailOp = frontDepthFailOp; + _frontCompareOp = frontCompareOp; + + StencilTestEnable = stencilTestEnable; + + _dirty |= DirtyFlags.StencilTestEnableAndStencilOp; + } + + public void SetStencilTest(bool stencilTestEnable) + { + StencilTestEnable = stencilTestEnable; + + _dirty |= DirtyFlags.StencilTestEnableAndStencilOp; + } + + public void SetStencilMask(uint backCompareMask, uint backWriteMask, uint backReference, uint frontCompareMask, @@ -81,28 +177,46 @@ namespace Ryujinx.Graphics.Vulkan _frontCompareMask = frontCompareMask; _frontWriteMask = frontWriteMask; _frontReference = frontReference; - _dirty |= DirtyFlags.Stencil; } public void SetViewport(int index, Viewport viewport) { Viewports[index] = viewport; - _dirty |= DirtyFlags.Viewport; } public void SetViewports(ref Array16 viewports, uint viewportsCount) { - Viewports = viewports; - ViewportsCount = viewportsCount; - - if (ViewportsCount != 0) + if (!Viewports.Equals(viewports) || ViewportsCount != viewportsCount) { - _dirty |= DirtyFlags.Viewport; + Viewports = viewports; + ViewportsCount = viewportsCount; + if (ViewportsCount != 0) + { + _dirty |= DirtyFlags.Viewport; + } } } + public void SetCullMode(CullModeFlags cullMode) + { + CullMode = cullMode; + _dirty |= DirtyFlags.CullMode; + } + + public void SetFrontFace(FrontFace frontFace) + { + _frontFace = frontFace; + _dirty |= DirtyFlags.FrontFace; + } + + public void SetLineWidth(float width) + { + _lineWidth = width; + _dirty |= DirtyFlags.LineWidth; + } + public void SetFeedbackLoop(FeedbackLoopAspects aspects) { _feedbackLoopAspects = aspects; @@ -110,43 +224,149 @@ namespace Ryujinx.Graphics.Vulkan _dirty |= DirtyFlags.FeedbackLoop; } - public void ForceAllDirty() + public void SetRasterizerDiscard(bool discard) { - _dirty = DirtyFlags.All; + _discard = discard; + _dirty |= DirtyFlags.RasterDiscard; + } + + public void SetPrimitiveRestartEnable(bool primitiveRestart) + { + _primitiveRestartEnable = primitiveRestart; + _dirty |= DirtyFlags.PrimitiveRestart; + } + + public void SetPrimitiveTopology(PrimitiveTopology primitiveTopology) + { + Topology = primitiveTopology; + _dirty |= DirtyFlags.PrimitiveTopology; + } + + public void SetLogicOp(LogicOp op) + { + _logicOp = op; + _dirty |= DirtyFlags.LogicOp; + } + + public void SetPatchControlPoints(uint points) + { + _patchControlPoints = points; + _dirty |= DirtyFlags.PatchControlPoints; + } + + public void ForceAllDirty(VulkanRenderer gd) + { + _dirty = DirtyFlags.Standard; + + if (gd.Capabilities.SupportsExtendedDynamicState) + { + _dirty |= DirtyFlags.Extended; + } + + if (gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2) + { + _dirty |= DirtyFlags.Extended2; + + if (gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2LogicOp) + { + _dirty |= DirtyFlags.LogicOp; + } + + if (gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints) + { + _dirty |= DirtyFlags.PatchControlPoints; + } + } + + if (!gd.IsMoltenVk) + { + _dirty |= DirtyFlags.LineWidth; + } + + if (gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop) + { + _dirty |= DirtyFlags.FeedbackLoop; + } } public void ReplayIfDirty(VulkanRenderer gd, CommandBuffer commandBuffer) { - Vk api = gd.Api; - - if (_dirty.HasFlag(DirtyFlags.Blend)) + if (_dirty == DirtyFlags.None) { - RecordBlend(api, commandBuffer); + return; } - if (_dirty.HasFlag(DirtyFlags.DepthBias)) - { - RecordDepthBias(api, commandBuffer); - } + var api = gd.Api; + var extendedStateApi = gd.ExtendedDynamicStateApi; + var extendedState2Api = gd.ExtendedDynamicState2Api; + var dynamicFeedbackLoopApi = gd.DynamicFeedbackLoopApi; - if (_dirty.HasFlag(DirtyFlags.Scissor)) - { - RecordScissor(api, commandBuffer); - } + DirtyFlags dirtyFlags = _dirty; - if (_dirty.HasFlag(DirtyFlags.Stencil)) + while (dirtyFlags != DirtyFlags.None) { - RecordStencilMasks(api, commandBuffer); - } + int bitIndex = BitOperations.TrailingZeroCount((uint)dirtyFlags); + DirtyFlags currentFlag = (DirtyFlags)(1 << bitIndex); - if (_dirty.HasFlag(DirtyFlags.Viewport)) - { - RecordViewport(api, commandBuffer); - } + switch (currentFlag) + { + case DirtyFlags.Blend: + RecordBlend(api, commandBuffer); + break; + case DirtyFlags.DepthBias: + RecordDepthBias(api, commandBuffer); + break; + case DirtyFlags.Scissor: + RecordScissor(gd, commandBuffer); + break; + case DirtyFlags.Stencil: + RecordStencil(api, commandBuffer); + break; + case DirtyFlags.Viewport: + RecordViewport(gd, commandBuffer); + break; + case DirtyFlags.FeedbackLoop: + RecordFeedbackLoop(dynamicFeedbackLoopApi, commandBuffer); + break; + case DirtyFlags.CullMode: + RecordCullMode(extendedStateApi, commandBuffer); + break; + case DirtyFlags.FrontFace: + RecordFrontFace(extendedStateApi, commandBuffer); + break; + case DirtyFlags.DepthTestBool: + RecordDepthTestBool(extendedStateApi, commandBuffer); + break; + case DirtyFlags.DepthTestCompareOp: + RecordDepthTestCompareOp(extendedStateApi, commandBuffer); + break; + case DirtyFlags.StencilTestEnableAndStencilOp: + RecordStencilTestAndOp(extendedStateApi, commandBuffer); + break; + case DirtyFlags.LineWidth: + RecordLineWidth(api, commandBuffer); + break; + case DirtyFlags.RasterDiscard: + RecordRasterizationDiscard(extendedState2Api, commandBuffer); + break; + case DirtyFlags.LogicOp: + RecordLogicOp(extendedState2Api, commandBuffer); + break; + case DirtyFlags.PatchControlPoints: + RecordPatchControlPoints(extendedState2Api, commandBuffer); + break; + case DirtyFlags.PrimitiveRestart: + RecordPrimitiveRestartEnable(gd, commandBuffer); + break; + case DirtyFlags.PrimitiveTopology: + RecordPrimitiveTopology(extendedStateApi, commandBuffer); + break; + case DirtyFlags.DepthBiasEnable: + RecordDepthBiasEnable(extendedState2Api, commandBuffer); + break; + } - if (_dirty.HasFlag(DirtyFlags.FeedbackLoop) && gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop) - { - RecordFeedbackLoop(gd.DynamicFeedbackLoopApi, commandBuffer); + dirtyFlags &= ~currentFlag; } _dirty = DirtyFlags.None; @@ -162,15 +382,27 @@ namespace Ryujinx.Graphics.Vulkan api.CmdSetDepthBias(commandBuffer, _depthBiasConstantFactor, _depthBiasClamp, _depthBiasSlopeFactor); } - private void RecordScissor(Vk api, CommandBuffer commandBuffer) + private readonly void RecordDepthBiasEnable(ExtExtendedDynamicState2 gd, CommandBuffer commandBuffer) + { + gd.CmdSetDepthBiasEnable(commandBuffer, _depthBiasEnable); + } + + private void RecordScissor(VulkanRenderer gd, CommandBuffer commandBuffer) { if (ScissorsCount != 0) { - api.CmdSetScissor(commandBuffer, 0, (uint)ScissorsCount, _scissors.AsSpan()); + if (gd.Capabilities.SupportsExtendedDynamicState) + { + gd.ExtendedDynamicStateApi.CmdSetScissorWithCount(commandBuffer, (uint)ScissorsCount, _scissors.AsSpan()); + } + else + { + gd.Api.CmdSetScissor(commandBuffer, 0, (uint)ScissorsCount, _scissors.AsSpan()); + } } } - private readonly void RecordStencilMasks(Vk api, CommandBuffer commandBuffer) + private readonly void RecordStencil(Vk api, CommandBuffer commandBuffer) { api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backCompareMask); api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backWriteMask); @@ -180,12 +412,107 @@ namespace Ryujinx.Graphics.Vulkan api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontReference); } - private void RecordViewport(Vk api, CommandBuffer commandBuffer) + private readonly void RecordStencilTestAndOp(ExtExtendedDynamicState api, CommandBuffer commandBuffer) { - if (ViewportsCount != 0) + api.CmdSetStencilTestEnable(commandBuffer, StencilTestEnable); + + api.CmdSetStencilOp(commandBuffer, StencilFaceFlags.FaceBackBit, _backFailOp, _backPassOp, _backDepthFailOp, _backCompareOp); + api.CmdSetStencilOp(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontFailOp, _frontPassOp, _frontDepthFailOp, _frontCompareOp); + } + + private void RecordViewport(VulkanRenderer gd, CommandBuffer commandBuffer) + { + if (ViewportsCount == 0) { - api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan()); + return; } + + if (gd.Capabilities.SupportsExtendedDynamicState) + { + gd.ExtendedDynamicStateApi.CmdSetViewportWithCount(commandBuffer, ViewportsCount, Viewports.AsSpan()); + } + else + { + gd.Api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan()); + } + } + + private readonly void RecordCullMode(ExtExtendedDynamicState api, CommandBuffer commandBuffer) + { + api.CmdSetCullMode(commandBuffer, CullMode); + } + + private readonly void RecordFrontFace(ExtExtendedDynamicState api, CommandBuffer commandBuffer) + { + api.CmdSetFrontFace(commandBuffer, _frontFace); + } + + private readonly void RecordDepthTestBool(ExtExtendedDynamicState api, CommandBuffer commandBuffer) + { + api.CmdSetDepthTestEnable(commandBuffer, DepthTestEnable); + + api.CmdSetDepthWriteEnable(commandBuffer, DepthWriteEnable); + } + + private readonly void RecordDepthTestCompareOp(ExtExtendedDynamicState api, CommandBuffer commandBuffer) + { + api.CmdSetDepthCompareOp(commandBuffer, _depthCompareOp); + } + + private readonly void RecordRasterizationDiscard(ExtExtendedDynamicState2 extendedDynamicState2Api, CommandBuffer commandBuffer) + { + extendedDynamicState2Api.CmdSetRasterizerDiscardEnable(commandBuffer, _discard); + } + + private readonly void RecordPrimitiveRestartEnable(VulkanRenderer gd, CommandBuffer commandBuffer) + { + bool primitiveRestartEnable = _primitiveRestartEnable; + + bool topologySupportsRestart; + + if (gd.Capabilities.SupportsPrimitiveTopologyListRestart) + { + topologySupportsRestart = gd.Capabilities.SupportsPrimitiveTopologyPatchListRestart || + Topology != PrimitiveTopology.PatchList; + } + else + { + topologySupportsRestart = Topology == PrimitiveTopology.LineStrip || + Topology == PrimitiveTopology.TriangleStrip || + Topology == PrimitiveTopology.TriangleFan || + Topology == PrimitiveTopology.LineStripWithAdjacency || + Topology == PrimitiveTopology.TriangleStripWithAdjacency; + } + + primitiveRestartEnable &= topologySupportsRestart; + + // Cannot disable primitiveRestartEnable for these Topologies on MacOS. + if (gd.IsMoltenVk) + { + primitiveRestartEnable = true; + } + + gd.ExtendedDynamicState2Api.CmdSetPrimitiveRestartEnable(commandBuffer, primitiveRestartEnable); + } + + private readonly void RecordPrimitiveTopology(ExtExtendedDynamicState extendedDynamicStateApi, CommandBuffer commandBuffer) + { + extendedDynamicStateApi.CmdSetPrimitiveTopology(commandBuffer, Topology); + } + + private readonly void RecordLogicOp(ExtExtendedDynamicState2 extendedDynamicState2Api, CommandBuffer commandBuffer) + { + extendedDynamicState2Api.CmdSetLogicOp(commandBuffer, _logicOp); + } + + private readonly void RecordPatchControlPoints(ExtExtendedDynamicState2 extendedDynamicState2Api, CommandBuffer commandBuffer) + { + extendedDynamicState2Api.CmdSetPatchControlPoints(commandBuffer, _patchControlPoints); + } + + private readonly void RecordLineWidth(Vk api, CommandBuffer commandBuffer) + { + api.CmdSetLineWidth(commandBuffer, _lineWidth); } private readonly void RecordFeedbackLoop(ExtAttachmentFeedbackLoopDynamicState api, CommandBuffer commandBuffer) diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs b/src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs index dfbf19013..9a29d3fc9 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs @@ -9,11 +9,15 @@ namespace Ryujinx.Graphics.Vulkan { } - public void SetRenderTarget(TextureView view, uint width, uint height) + public void SetRenderTarget(TextureView view, uint width, uint height, bool signalChange = true) { CreateFramebuffer(view, width, height); CreateRenderPass(); - SignalStateChange(); + + if (signalChange) + { + SignalStateChange(); + } } private void CreateFramebuffer(TextureView view, uint width, uint height) diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs index a726b9edb..50f24f7d2 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs @@ -7,313 +7,258 @@ namespace Ryujinx.Graphics.Vulkan { struct PipelineState : IDisposable { - private const int RequiredSubgroupSize = 32; - private const int MaxDynamicStatesCount = 9; + private const int MaxDynamicStatesCount = 23; public PipelineUid Internal; - public float LineWidth + public PolygonMode PolygonMode { - readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id0 >> 0) & 0xFFFFFFFF)); - set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0); + readonly get => (PolygonMode)((Internal.Id0 >> 0) & 0x3FFFFFFF); + set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFFC0000000) | ((ulong)value << 0); } - public float DepthBiasClamp + public uint StagesCount { - readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id0 >> 32) & 0xFFFFFFFF)); - set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32); + readonly get => (byte)((Internal.Id0 >> 30) & 0xFF); + set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30); } - public float DepthBiasConstantFactor + public uint VertexAttributeDescriptionsCount { - readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id1 >> 0) & 0xFFFFFFFF)); - set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0); + readonly get => (byte)((Internal.Id0 >> 38) & 0xFF); + set => Internal.Id0 = (Internal.Id0 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38); } - public float DepthBiasSlopeFactor + public uint VertexBindingDescriptionsCount { - readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id1 >> 32) & 0xFFFFFFFF)); - set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32); + readonly get => (byte)((Internal.Id0 >> 46) & 0xFF); + set => Internal.Id0 = (Internal.Id0 & 0xFFC03FFFFFFFFFFF) | ((ulong)value << 46); } - public uint StencilFrontCompareMask + public uint ViewportsCount + { + readonly get => (byte)((Internal.Id0 >> 54) & 0xFF); + set => Internal.Id0 = (Internal.Id0 & 0xC03FFFFFFFFFFFFF) | ((ulong)value << 54); + } + + public uint ScissorsCount + { + readonly get => (byte)((Internal.Id1 >> 0) & 0xFF); + set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0); + } + + public uint ColorBlendAttachmentStateCount + { + readonly get => (byte)((Internal.Id1 >> 8) & 0xFF); + set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8); + } + + public PrimitiveTopology Topology + { + readonly get => (PrimitiveTopology)((Internal.Id1 >> 16) & 0xF); + set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16); + } + + public LogicOp LogicOp + { + readonly get => (LogicOp)((Internal.Id1 >> 20) & 0xF); + set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFFFF0FFFFF) | ((ulong)value << 20); + } + + public CompareOp DepthCompareOp + { + readonly get => (CompareOp)((Internal.Id1 >> 24) & 0x7); + set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFFF8FFFFFF) | ((ulong)value << 24); + } + + public StencilOp StencilFrontFailOp + { + readonly get => (StencilOp)((Internal.Id1 >> 27) & 0x7); + set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFFC7FFFFFF) | ((ulong)value << 27); + } + + public StencilOp StencilFrontPassOp + { + readonly get => (StencilOp)((Internal.Id1 >> 30) & 0x7); + set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFE3FFFFFFF) | ((ulong)value << 30); + } + + public StencilOp StencilFrontDepthFailOp + { + readonly get => (StencilOp)((Internal.Id1 >> 33) & 0x7); + set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFF1FFFFFFFF) | ((ulong)value << 33); + } + + public CompareOp StencilFrontCompareOp + { + readonly get => (CompareOp)((Internal.Id1 >> 36) & 0x7); + set => Internal.Id1 = (Internal.Id1 & 0xFFFFFF8FFFFFFFFF) | ((ulong)value << 36); + } + + public StencilOp StencilBackFailOp + { + readonly get => (StencilOp)((Internal.Id1 >> 39) & 0x7); + set => Internal.Id1 = (Internal.Id1 & 0xFFFFFC7FFFFFFFFF) | ((ulong)value << 39); + } + + public StencilOp StencilBackPassOp + { + readonly get => (StencilOp)((Internal.Id1 >> 42) & 0x7); + set => Internal.Id1 = (Internal.Id1 & 0xFFFFE3FFFFFFFFFF) | ((ulong)value << 42); + } + + public StencilOp StencilBackDepthFailOp + { + readonly get => (StencilOp)((Internal.Id1 >> 45) & 0x7); + set => Internal.Id1 = (Internal.Id1 & 0xFFFF1FFFFFFFFFFF) | ((ulong)value << 45); + } + + public CompareOp StencilBackCompareOp + { + readonly get => (CompareOp)((Internal.Id1 >> 48) & 0x7); + set => Internal.Id1 = (Internal.Id1 & 0xFFF8FFFFFFFFFFFF) | ((ulong)value << 48); + } + + public CullModeFlags CullMode + { + readonly get => (CullModeFlags)((Internal.Id1 >> 51) & 0x3); + set => Internal.Id1 = (Internal.Id1 & 0xFFE7FFFFFFFFFFFF) | ((ulong)value << 51); + } + + public bool PrimitiveRestartEnable + { + readonly get => ((Internal.Id1 >> 53) & 0x1) != 0UL; + set => Internal.Id1 = (Internal.Id1 & 0xFFDFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 53); + } + + public bool DepthClampEnable + { + readonly get => ((Internal.Id1 >> 54) & 0x1) != 0UL; + set => Internal.Id1 = (Internal.Id1 & 0xFFBFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 54); + } + + public bool RasterizerDiscardEnable + { + readonly get => ((Internal.Id1 >> 55) & 0x1) != 0UL; + set => Internal.Id1 = (Internal.Id1 & 0xFF7FFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 55); + } + + public FrontFace FrontFace + { + readonly get => (FrontFace)((Internal.Id1 >> 56) & 0x1); + set => Internal.Id1 = (Internal.Id1 & 0xFEFFFFFFFFFFFFFF) | ((ulong)value << 56); + } + + public bool DepthBiasEnable + { + readonly get => ((Internal.Id1 >> 57) & 0x1) != 0UL; + set => Internal.Id1 = (Internal.Id1 & 0xFDFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 57); + } + + public bool DepthTestEnable + { + readonly get => ((Internal.Id1 >> 58) & 0x1) != 0UL; + set => Internal.Id1 = (Internal.Id1 & 0xFBFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 58); + } + + public bool DepthWriteEnable + { + readonly get => ((Internal.Id1 >> 59) & 0x1) != 0UL; + set => Internal.Id1 = (Internal.Id1 & 0xF7FFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 59); + } + + public bool DepthBoundsTestEnable + { + readonly get => ((Internal.Id1 >> 60) & 0x1) != 0UL; + set => Internal.Id1 = (Internal.Id1 & 0xEFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 60); + } + + public bool StencilTestEnable + { + readonly get => ((Internal.Id1 >> 61) & 0x1) != 0UL; + set => Internal.Id1 = (Internal.Id1 & 0xDFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 61); + } + + public bool LogicOpEnable + { + readonly get => ((Internal.Id1 >> 62) & 0x1) != 0UL; + set => Internal.Id1 = (Internal.Id1 & 0xBFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 62); + } + + public bool HasDepthStencil + { + readonly get => ((Internal.Id1 >> 63) & 0x1) != 0UL; + set => Internal.Id1 = (Internal.Id1 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63); + } + + public uint PatchControlPoints { readonly get => (uint)((Internal.Id2 >> 0) & 0xFFFFFFFF); set => Internal.Id2 = (Internal.Id2 & 0xFFFFFFFF00000000) | ((ulong)value << 0); } - public uint StencilFrontWriteMask + public uint SamplesCount { readonly get => (uint)((Internal.Id2 >> 32) & 0xFFFFFFFF); set => Internal.Id2 = (Internal.Id2 & 0xFFFFFFFF) | ((ulong)value << 32); } - public uint StencilFrontReference - { - readonly get => (uint)((Internal.Id3 >> 0) & 0xFFFFFFFF); - set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFF00000000) | ((ulong)value << 0); - } - - public uint StencilBackCompareMask - { - readonly get => (uint)((Internal.Id3 >> 32) & 0xFFFFFFFF); - set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFF) | ((ulong)value << 32); - } - - public uint StencilBackWriteMask - { - readonly get => (uint)((Internal.Id4 >> 0) & 0xFFFFFFFF); - set => Internal.Id4 = (Internal.Id4 & 0xFFFFFFFF00000000) | ((ulong)value << 0); - } - - public uint StencilBackReference - { - readonly get => (uint)((Internal.Id4 >> 32) & 0xFFFFFFFF); - set => Internal.Id4 = (Internal.Id4 & 0xFFFFFFFF) | ((ulong)value << 32); - } - - public PolygonMode PolygonMode - { - readonly get => (PolygonMode)((Internal.Id5 >> 0) & 0x3FFFFFFF); - set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFFFC0000000) | ((ulong)value << 0); - } - - public uint StagesCount - { - readonly get => (byte)((Internal.Id5 >> 30) & 0xFF); - set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30); - } - - public uint VertexAttributeDescriptionsCount - { - readonly get => (byte)((Internal.Id5 >> 38) & 0xFF); - set => Internal.Id5 = (Internal.Id5 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38); - } - - public uint VertexBindingDescriptionsCount - { - readonly get => (byte)((Internal.Id5 >> 46) & 0xFF); - set => Internal.Id5 = (Internal.Id5 & 0xFFC03FFFFFFFFFFF) | ((ulong)value << 46); - } - - public uint ViewportsCount - { - readonly get => (byte)((Internal.Id5 >> 54) & 0xFF); - set => Internal.Id5 = (Internal.Id5 & 0xC03FFFFFFFFFFFFF) | ((ulong)value << 54); - } - - public uint ScissorsCount - { - readonly get => (byte)((Internal.Id6 >> 0) & 0xFF); - set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0); - } - - public uint ColorBlendAttachmentStateCount - { - readonly get => (byte)((Internal.Id6 >> 8) & 0xFF); - set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8); - } - - public PrimitiveTopology Topology - { - readonly get => (PrimitiveTopology)((Internal.Id6 >> 16) & 0xF); - set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16); - } - - public LogicOp LogicOp - { - readonly get => (LogicOp)((Internal.Id6 >> 20) & 0xF); - set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFF0FFFFF) | ((ulong)value << 20); - } - - public CompareOp DepthCompareOp - { - readonly get => (CompareOp)((Internal.Id6 >> 24) & 0x7); - set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFF8FFFFFF) | ((ulong)value << 24); - } - - public StencilOp StencilFrontFailOp - { - readonly get => (StencilOp)((Internal.Id6 >> 27) & 0x7); - set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFC7FFFFFF) | ((ulong)value << 27); - } - - public StencilOp StencilFrontPassOp - { - readonly get => (StencilOp)((Internal.Id6 >> 30) & 0x7); - set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFE3FFFFFFF) | ((ulong)value << 30); - } - - public StencilOp StencilFrontDepthFailOp - { - readonly get => (StencilOp)((Internal.Id6 >> 33) & 0x7); - set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFF1FFFFFFFF) | ((ulong)value << 33); - } - - public CompareOp StencilFrontCompareOp - { - readonly get => (CompareOp)((Internal.Id6 >> 36) & 0x7); - set => Internal.Id6 = (Internal.Id6 & 0xFFFFFF8FFFFFFFFF) | ((ulong)value << 36); - } - - public StencilOp StencilBackFailOp - { - readonly get => (StencilOp)((Internal.Id6 >> 39) & 0x7); - set => Internal.Id6 = (Internal.Id6 & 0xFFFFFC7FFFFFFFFF) | ((ulong)value << 39); - } - - public StencilOp StencilBackPassOp - { - readonly get => (StencilOp)((Internal.Id6 >> 42) & 0x7); - set => Internal.Id6 = (Internal.Id6 & 0xFFFFE3FFFFFFFFFF) | ((ulong)value << 42); - } - - public StencilOp StencilBackDepthFailOp - { - readonly get => (StencilOp)((Internal.Id6 >> 45) & 0x7); - set => Internal.Id6 = (Internal.Id6 & 0xFFFF1FFFFFFFFFFF) | ((ulong)value << 45); - } - - public CompareOp StencilBackCompareOp - { - readonly get => (CompareOp)((Internal.Id6 >> 48) & 0x7); - set => Internal.Id6 = (Internal.Id6 & 0xFFF8FFFFFFFFFFFF) | ((ulong)value << 48); - } - - public CullModeFlags CullMode - { - readonly get => (CullModeFlags)((Internal.Id6 >> 51) & 0x3); - set => Internal.Id6 = (Internal.Id6 & 0xFFE7FFFFFFFFFFFF) | ((ulong)value << 51); - } - - public bool PrimitiveRestartEnable - { - readonly get => ((Internal.Id6 >> 53) & 0x1) != 0UL; - set => Internal.Id6 = (Internal.Id6 & 0xFFDFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 53); - } - - public bool DepthClampEnable - { - readonly get => ((Internal.Id6 >> 54) & 0x1) != 0UL; - set => Internal.Id6 = (Internal.Id6 & 0xFFBFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 54); - } - - public bool RasterizerDiscardEnable - { - readonly get => ((Internal.Id6 >> 55) & 0x1) != 0UL; - set => Internal.Id6 = (Internal.Id6 & 0xFF7FFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 55); - } - - public FrontFace FrontFace - { - readonly get => (FrontFace)((Internal.Id6 >> 56) & 0x1); - set => Internal.Id6 = (Internal.Id6 & 0xFEFFFFFFFFFFFFFF) | ((ulong)value << 56); - } - - public bool DepthBiasEnable - { - readonly get => ((Internal.Id6 >> 57) & 0x1) != 0UL; - set => Internal.Id6 = (Internal.Id6 & 0xFDFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 57); - } - - public bool DepthTestEnable - { - readonly get => ((Internal.Id6 >> 58) & 0x1) != 0UL; - set => Internal.Id6 = (Internal.Id6 & 0xFBFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 58); - } - - public bool DepthWriteEnable - { - readonly get => ((Internal.Id6 >> 59) & 0x1) != 0UL; - set => Internal.Id6 = (Internal.Id6 & 0xF7FFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 59); - } - - public bool DepthBoundsTestEnable - { - readonly get => ((Internal.Id6 >> 60) & 0x1) != 0UL; - set => Internal.Id6 = (Internal.Id6 & 0xEFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 60); - } - - public bool StencilTestEnable - { - readonly get => ((Internal.Id6 >> 61) & 0x1) != 0UL; - set => Internal.Id6 = (Internal.Id6 & 0xDFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 61); - } - - public bool LogicOpEnable - { - readonly get => ((Internal.Id6 >> 62) & 0x1) != 0UL; - set => Internal.Id6 = (Internal.Id6 & 0xBFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 62); - } - - public bool HasDepthStencil - { - readonly get => ((Internal.Id6 >> 63) & 0x1) != 0UL; - set => Internal.Id6 = (Internal.Id6 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63); - } - - public uint PatchControlPoints - { - readonly get => (uint)((Internal.Id7 >> 0) & 0xFFFFFFFF); - set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF00000000) | ((ulong)value << 0); - } - - public uint SamplesCount - { - readonly get => (uint)((Internal.Id7 >> 32) & 0xFFFFFFFF); - set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF) | ((ulong)value << 32); - } - public bool AlphaToCoverageEnable { - readonly get => ((Internal.Id8 >> 0) & 0x1) != 0UL; - set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0); + readonly get => ((Internal.Id3 >> 0) & 0x1) != 0UL; + set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0); } public bool AlphaToOneEnable { - readonly get => ((Internal.Id8 >> 1) & 0x1) != 0UL; - set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1); + readonly get => ((Internal.Id3 >> 1) & 0x1) != 0UL; + set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1); } public bool AdvancedBlendSrcPreMultiplied { - readonly get => ((Internal.Id8 >> 2) & 0x1) != 0UL; - set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFB) | ((value ? 1UL : 0UL) << 2); + readonly get => ((Internal.Id3 >> 2) & 0x1) != 0UL; + set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFFB) | ((value ? 1UL : 0UL) << 2); } public bool AdvancedBlendDstPreMultiplied { - readonly get => ((Internal.Id8 >> 3) & 0x1) != 0UL; - set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFF7) | ((value ? 1UL : 0UL) << 3); + readonly get => ((Internal.Id3 >> 3) & 0x1) != 0UL; + set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFF7) | ((value ? 1UL : 0UL) << 3); } public BlendOverlapEXT AdvancedBlendOverlap { - readonly get => (BlendOverlapEXT)((Internal.Id8 >> 4) & 0x3); - set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4); + readonly get => (BlendOverlapEXT)((Internal.Id3 >> 4) & 0x3); + set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4); } public bool DepthMode { - readonly get => ((Internal.Id8 >> 6) & 0x1) != 0UL; - set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6); + readonly get => ((Internal.Id3 >> 6) & 0x1) != 0UL; + set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6); } public FeedbackLoopAspects FeedbackLoopAspects { - readonly get => (FeedbackLoopAspects)((Internal.Id8 >> 7) & 0x3); - set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFE7F) | (((ulong)value) << 7); + readonly get => (FeedbackLoopAspects)((Internal.Id3 >> 7) & 0x3); + set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFE7F) | (((ulong)value) << 7); } public bool HasTessellationControlShader; + public bool FeedbackLoopDynamicState; public NativeArray Stages; public PipelineLayout PipelineLayout; public SpecData SpecializationData; private Array32 _vertexAttributeDescriptions2; - public void Initialize() + private bool _supportsExtDynamicState; + private PhysicalDeviceExtendedDynamicState2FeaturesEXT _supportsExtDynamicState2; + private bool _supportsFeedBackLoopDynamicState; + private uint _blendEnables; + + public void Initialize(HardwareCapabilities capabilities) { HasTessellationControlShader = false; Stages = new NativeArray(Constants.MaxShaderStages); @@ -322,9 +267,54 @@ namespace Ryujinx.Graphics.Vulkan AdvancedBlendDstPreMultiplied = true; AdvancedBlendOverlap = BlendOverlapEXT.UncorrelatedExt; - LineWidth = 1f; - SamplesCount = 1; DepthMode = true; + + PolygonMode = PolygonMode.Fill; + DepthBoundsTestEnable = false; + + _supportsExtDynamicState = capabilities.SupportsExtendedDynamicState; + _supportsExtDynamicState2 = capabilities.SupportsExtendedDynamicState2; + _supportsFeedBackLoopDynamicState = capabilities.SupportsDynamicAttachmentFeedbackLoop; + + if (_supportsExtDynamicState) + { + StencilFrontFailOp = 0; + StencilFrontPassOp = 0; + StencilFrontDepthFailOp = 0; + StencilFrontCompareOp = 0; + + StencilBackFailOp = 0; + StencilBackPassOp = 0; + StencilBackDepthFailOp = 0; + StencilBackCompareOp = 0; + + ViewportsCount = 0; + ScissorsCount = 0; + + CullMode = 0; + FrontFace = 0; + DepthTestEnable = false; + DepthWriteEnable = false; + DepthCompareOp = 0; + StencilTestEnable = false; + } + + if (_supportsExtDynamicState2.ExtendedDynamicState2) + { + PrimitiveRestartEnable = false; + DepthBiasEnable = false; + RasterizerDiscardEnable = false; + + if (_supportsExtDynamicState2.ExtendedDynamicState2LogicOp) + { + LogicOp = 0; + } + + if (_supportsExtDynamicState2.ExtendedDynamicState2PatchControlPoints) + { + PatchControlPoints = 0; + } + } } public unsafe Auto CreateComputePipeline( @@ -342,7 +332,6 @@ namespace Ryujinx.Graphics.Vulkan { SType = StructureType.ComputePipelineCreateInfo, Stage = Stages[0], - BasePipelineIndex = -1, Layout = PipelineLayout, }; @@ -378,6 +367,74 @@ namespace Ryujinx.Graphics.Vulkan return pipeline; } + + private void CheckCapability(VulkanRenderer gd) + { + // Vendors other than NVIDIA have a bug where it enables logical operations even for float formats, + // so we need to force disable them here. + LogicOpEnable = LogicOpEnable && (gd.Vendor == Vendor.Nvidia || Internal.LogicOpsAllowed); + + if (!_supportsExtDynamicState) + { + DepthWriteEnable = DepthWriteEnable && DepthTestEnable; + DepthCompareOp = DepthTestEnable ? DepthCompareOp : default; + } + + if (!_supportsExtDynamicState2.ExtendedDynamicState2LogicOp) + { + LogicOp = LogicOpEnable ? LogicOp : default; + } + + if (!_supportsExtDynamicState2.ExtendedDynamicState2) + { + bool topologySupportsRestart; + + if (gd.Capabilities.SupportsPrimitiveTopologyListRestart) + { + topologySupportsRestart = gd.Capabilities.SupportsPrimitiveTopologyPatchListRestart || + Topology != PrimitiveTopology.PatchList; + } + else + { + topologySupportsRestart = Topology == PrimitiveTopology.LineStrip || + Topology == PrimitiveTopology.TriangleStrip || + Topology == PrimitiveTopology.TriangleFan || + Topology == PrimitiveTopology.LineStripWithAdjacency || + Topology == PrimitiveTopology.TriangleStripWithAdjacency; + } + + PrimitiveRestartEnable &= topologySupportsRestart; + } + + if (_supportsExtDynamicState) + { + Topology = Topology.ConvertToClass(); + } + + Topology = HasTessellationControlShader ? PrimitiveTopology.PatchList : Topology; + + if (gd.IsMoltenVk && Internal.AttachmentIntegerFormatMask != 0) + { + _blendEnables = 0; + + // Blend can't be enabled for integer formats, so let's make sure it is disabled. + uint attachmentIntegerFormatMask = Internal.AttachmentIntegerFormatMask; + + while (attachmentIntegerFormatMask != 0) + { + int i = BitOperations.TrailingZeroCount(attachmentIntegerFormatMask); + + if (Internal.ColorBlendAttachmentState[i].BlendEnable) + { + _blendEnables |= 1u << i; + } + + Internal.ColorBlendAttachmentState[i].BlendEnable = false; + attachmentIntegerFormatMask &= ~(1u << i); + } + } + } + public unsafe Auto CreateGraphicsPipeline( VulkanRenderer gd, Device device, @@ -386,6 +443,17 @@ namespace Ryujinx.Graphics.Vulkan RenderPass renderPass, bool throwOnError = false) { + CheckCapability(gd); + + // Using patches topology without a tessellation shader is invalid. + // If we find such a case, return null pipeline to skip the draw. + if (Topology == PrimitiveTopology.PatchList && !HasTessellationControlShader) + { + program.AddGraphicsPipeline(ref Internal, null); + + return null; + } + if (program.TryGetGraphicsPipeline(ref Internal, out var pipeline)) { return pipeline; @@ -395,7 +463,7 @@ namespace Ryujinx.Graphics.Vulkan bool isMoltenVk = gd.IsMoltenVk; - if (isMoltenVk) + if (isMoltenVk && !_supportsExtDynamicState) { UpdateVertexAttributeDescriptions(gd); } @@ -409,69 +477,30 @@ namespace Ryujinx.Graphics.Vulkan { SType = StructureType.PipelineVertexInputStateCreateInfo, VertexAttributeDescriptionCount = VertexAttributeDescriptionsCount, - PVertexAttributeDescriptions = isMoltenVk ? pVertexAttributeDescriptions2 : pVertexAttributeDescriptions, + PVertexAttributeDescriptions = isMoltenVk && !_supportsExtDynamicState ? pVertexAttributeDescriptions2 : pVertexAttributeDescriptions, VertexBindingDescriptionCount = VertexBindingDescriptionsCount, PVertexBindingDescriptions = pVertexBindingDescriptions, }; - // Using patches topology without a tessellation shader is invalid. - // If we find such a case, return null pipeline to skip the draw. - if (Topology == PrimitiveTopology.PatchList && !HasTessellationControlShader) - { - program.AddGraphicsPipeline(ref Internal, null); - - return null; - } - - bool primitiveRestartEnable = PrimitiveRestartEnable; - - bool topologySupportsRestart; - - if (gd.Capabilities.SupportsPrimitiveTopologyListRestart) - { - topologySupportsRestart = gd.Capabilities.SupportsPrimitiveTopologyPatchListRestart || Topology != PrimitiveTopology.PatchList; - } - else - { - topologySupportsRestart = Topology == PrimitiveTopology.LineStrip || - Topology == PrimitiveTopology.TriangleStrip || - Topology == PrimitiveTopology.TriangleFan || - Topology == PrimitiveTopology.LineStripWithAdjacency || - Topology == PrimitiveTopology.TriangleStripWithAdjacency; - } - - primitiveRestartEnable &= topologySupportsRestart; - var inputAssemblyState = new PipelineInputAssemblyStateCreateInfo { SType = StructureType.PipelineInputAssemblyStateCreateInfo, - PrimitiveRestartEnable = primitiveRestartEnable, - Topology = HasTessellationControlShader ? PrimitiveTopology.PatchList : Topology, + Topology = Topology, }; - var tessellationState = new PipelineTessellationStateCreateInfo - { - SType = StructureType.PipelineTessellationStateCreateInfo, - PatchControlPoints = PatchControlPoints, - }; + PipelineTessellationStateCreateInfo tessellationState; var rasterizationState = new PipelineRasterizationStateCreateInfo { SType = StructureType.PipelineRasterizationStateCreateInfo, DepthClampEnable = DepthClampEnable, - RasterizerDiscardEnable = RasterizerDiscardEnable, - PolygonMode = PolygonMode, - LineWidth = LineWidth, - CullMode = CullMode, - FrontFace = FrontFace, - DepthBiasEnable = DepthBiasEnable, + // When widelines feature is not supported it must be 1.0f, this will be ignored if Line Width dynamic state is supported + LineWidth = 1.0f, }; var viewportState = new PipelineViewportStateCreateInfo { SType = StructureType.PipelineViewportStateCreateInfo, - ViewportCount = ViewportsCount, - ScissorCount = ScissorsCount, }; if (gd.Capabilities.SupportsDepthClipControl) @@ -495,71 +524,75 @@ namespace Ryujinx.Graphics.Vulkan AlphaToOneEnable = AlphaToOneEnable, }; - var stencilFront = new StencilOpState( - StencilFrontFailOp, - StencilFrontPassOp, - StencilFrontDepthFailOp, - StencilFrontCompareOp); - - var stencilBack = new StencilOpState( - StencilBackFailOp, - StencilBackPassOp, - StencilBackDepthFailOp, - StencilBackCompareOp); - var depthStencilState = new PipelineDepthStencilStateCreateInfo { SType = StructureType.PipelineDepthStencilStateCreateInfo, - DepthTestEnable = DepthTestEnable, - DepthWriteEnable = DepthWriteEnable, - DepthCompareOp = DepthCompareOp, - DepthBoundsTestEnable = false, - StencilTestEnable = StencilTestEnable, - Front = stencilFront, - Back = stencilBack, + DepthBoundsTestEnable = DepthBoundsTestEnable, }; - uint blendEnables = 0; - - if (gd.IsMoltenVk && Internal.AttachmentIntegerFormatMask != 0) + if (!_supportsExtDynamicState) { - // Blend can't be enabled for integer formats, so let's make sure it is disabled. - uint attachmentIntegerFormatMask = Internal.AttachmentIntegerFormatMask; + rasterizationState.CullMode = CullMode; + rasterizationState.FrontFace = FrontFace; - while (attachmentIntegerFormatMask != 0) - { - int i = BitOperations.TrailingZeroCount(attachmentIntegerFormatMask); + viewportState.ViewportCount = ViewportsCount; + viewportState.ScissorCount = ScissorsCount; - if (Internal.ColorBlendAttachmentState[i].BlendEnable) - { - blendEnables |= 1u << i; - } + var stencilFront = new StencilOpState( + StencilFrontFailOp, + StencilFrontPassOp, + StencilFrontDepthFailOp, + StencilFrontCompareOp); - Internal.ColorBlendAttachmentState[i].BlendEnable = false; - attachmentIntegerFormatMask &= ~(1u << i); - } + var stencilBack = new StencilOpState( + StencilBackFailOp, + StencilBackPassOp, + StencilBackDepthFailOp, + StencilBackCompareOp); + + depthStencilState.Front = stencilFront; + depthStencilState.Back = stencilBack; + depthStencilState.StencilTestEnable = StencilTestEnable; + depthStencilState.DepthTestEnable = DepthTestEnable; + depthStencilState.DepthWriteEnable = DepthWriteEnable; + depthStencilState.DepthCompareOp = DepthCompareOp; } - // Vendors other than NVIDIA have a bug where it enables logical operations even for float formats, - // so we need to force disable them here. - bool logicOpEnable = LogicOpEnable && (gd.Vendor == Vendor.Nvidia || Internal.LogicOpsAllowed); + if (!_supportsExtDynamicState2.ExtendedDynamicState2) + { + + inputAssemblyState.PrimitiveRestartEnable = PrimitiveRestartEnable; + rasterizationState.DepthBiasEnable = DepthBiasEnable; + rasterizationState.RasterizerDiscardEnable = RasterizerDiscardEnable; + } + + if (!gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints) + { + tessellationState = new PipelineTessellationStateCreateInfo + { + SType = StructureType.PipelineTessellationStateCreateInfo, + PatchControlPoints = PatchControlPoints, + }; + } var colorBlendState = new PipelineColorBlendStateCreateInfo { SType = StructureType.PipelineColorBlendStateCreateInfo, - LogicOpEnable = logicOpEnable, - LogicOp = LogicOp, AttachmentCount = ColorBlendAttachmentStateCount, PAttachments = pColorBlendAttachmentState, + LogicOpEnable = LogicOpEnable, }; - PipelineColorBlendAdvancedStateCreateInfoEXT colorBlendAdvancedState; + if (!gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2LogicOp) + { + colorBlendState.LogicOp = LogicOp; + } if (!AdvancedBlendSrcPreMultiplied || !AdvancedBlendDstPreMultiplied || AdvancedBlendOverlap != BlendOverlapEXT.UncorrelatedExt) { - colorBlendAdvancedState = new PipelineColorBlendAdvancedStateCreateInfoEXT + PipelineColorBlendAdvancedStateCreateInfoEXT colorBlendAdvancedState = new PipelineColorBlendAdvancedStateCreateInfoEXT { SType = StructureType.PipelineColorBlendAdvancedStateCreateInfoExt, SrcPremultiplied = AdvancedBlendSrcPreMultiplied, @@ -570,64 +603,99 @@ namespace Ryujinx.Graphics.Vulkan colorBlendState.PNext = &colorBlendAdvancedState; } - bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState; - bool supportsFeedbackLoopDynamicState = gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop; - DynamicState* dynamicStates = stackalloc DynamicState[MaxDynamicStatesCount]; - int dynamicStatesCount = 7; + uint dynamicStatesCount = 7; dynamicStates[0] = DynamicState.Viewport; dynamicStates[1] = DynamicState.Scissor; - dynamicStates[2] = DynamicState.DepthBias; - dynamicStates[3] = DynamicState.StencilCompareMask; - dynamicStates[4] = DynamicState.StencilWriteMask; - dynamicStates[5] = DynamicState.StencilReference; - dynamicStates[6] = DynamicState.BlendConstants; + dynamicStates[2] = DynamicState.StencilCompareMask; + dynamicStates[3] = DynamicState.StencilWriteMask; + dynamicStates[4] = DynamicState.StencilReference; + dynamicStates[5] = DynamicState.BlendConstants; + dynamicStates[6] = DynamicState.DepthBias; - if (supportsExtDynamicState) + if (!isMoltenVk) { - dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt; + //LineWidth dynamic state is only supported on macOS when using Metal Private API on newer version of MoltenVK + dynamicStates[dynamicStatesCount++] = DynamicState.LineWidth; } - if (supportsFeedbackLoopDynamicState) + if (_supportsExtDynamicState) { - dynamicStates[dynamicStatesCount++] = DynamicState.AttachmentFeedbackLoopEnableExt; + if (gd.SupportsMTL31 || !gd.IsMoltenVk) + { + // Requires Metal 3.1 and new MoltenVK, however extended dynamic states extension is not + // available on older versions of MVK, so we can safely check only OS version. + dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt; + } + dynamicStates[0] = DynamicState.ViewportWithCountExt; + dynamicStates[1] = DynamicState.ScissorWithCountExt; + dynamicStates[dynamicStatesCount++] = DynamicState.CullModeExt; + dynamicStates[dynamicStatesCount++] = DynamicState.FrontFaceExt; + dynamicStates[dynamicStatesCount++] = DynamicState.DepthTestEnableExt; + dynamicStates[dynamicStatesCount++] = DynamicState.DepthWriteEnableExt; + + dynamicStates[dynamicStatesCount++] = DynamicState.DepthCompareOpExt; + dynamicStates[dynamicStatesCount++] = DynamicState.StencilTestEnableExt; + dynamicStates[dynamicStatesCount++] = DynamicState.StencilOpExt; + dynamicStates[dynamicStatesCount++] = DynamicState.PrimitiveTopologyExt; } - var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo + if (_supportsExtDynamicState2.ExtendedDynamicState2) { - SType = StructureType.PipelineDynamicStateCreateInfo, - DynamicStateCount = (uint)dynamicStatesCount, - PDynamicStates = dynamicStates, - }; + dynamicStates[dynamicStatesCount++] = DynamicState.DepthBiasEnableExt; + dynamicStates[dynamicStatesCount++] = DynamicState.RasterizerDiscardEnableExt; + dynamicStates[dynamicStatesCount++] = DynamicState.PrimitiveRestartEnableExt; - PipelineCreateFlags flags = 0; + if (_supportsExtDynamicState2.ExtendedDynamicState2LogicOp) + { + dynamicStates[dynamicStatesCount++] = DynamicState.LogicOpExt; + } + if (_supportsExtDynamicState2.ExtendedDynamicState2PatchControlPoints) + { + dynamicStates[dynamicStatesCount++] = DynamicState.PatchControlPointsExt; + } + } - if (gd.Capabilities.SupportsAttachmentFeedbackLoop) + PipelineCreateFlags pipelineCreateFlags = 0; + + if (gd.Capabilities.SupportsAttachmentFeedbackLoop && !_supportsFeedBackLoopDynamicState) { FeedbackLoopAspects aspects = FeedbackLoopAspects; if ((aspects & FeedbackLoopAspects.Color) != 0) { - flags |= PipelineCreateFlags.CreateColorAttachmentFeedbackLoopBitExt; + pipelineCreateFlags |= PipelineCreateFlags.CreateColorAttachmentFeedbackLoopBitExt; } if ((aspects & FeedbackLoopAspects.Depth) != 0) { - flags |= PipelineCreateFlags.CreateDepthStencilAttachmentFeedbackLoopBitExt; + pipelineCreateFlags |= PipelineCreateFlags.CreateDepthStencilAttachmentFeedbackLoopBitExt; } } + if (_supportsFeedBackLoopDynamicState && FeedbackLoopDynamicState) + { + dynamicStates[dynamicStatesCount++] = DynamicState.AttachmentFeedbackLoopEnableExt; + FeedbackLoopDynamicState = false; + } + + var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo + { + SType = StructureType.PipelineDynamicStateCreateInfo, + DynamicStateCount = dynamicStatesCount, + PDynamicStates = dynamicStates, + }; + var pipelineCreateInfo = new GraphicsPipelineCreateInfo { SType = StructureType.GraphicsPipelineCreateInfo, - Flags = flags, StageCount = StagesCount, + Flags = pipelineCreateFlags, PStages = Stages.Pointer, PVertexInputState = &vertexInputState, PInputAssemblyState = &inputAssemblyState, - PTessellationState = &tessellationState, PViewportState = &viewportState, PRasterizationState = &rasterizationState, PMultisampleState = &multisampleState, @@ -638,6 +706,11 @@ namespace Ryujinx.Graphics.Vulkan RenderPass = renderPass, }; + if (!gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints) + { + pipelineCreateInfo.PTessellationState = &tessellationState; + } + Result result = gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle); if (throwOnError) @@ -650,21 +723,21 @@ namespace Ryujinx.Graphics.Vulkan return null; } - - // Restore previous blend enable values if we changed it. - while (blendEnables != 0) - { - int i = BitOperations.TrailingZeroCount(blendEnables); - - Internal.ColorBlendAttachmentState[i].BlendEnable = true; - blendEnables &= ~(1u << i); - } } pipeline = new Auto(new DisposablePipeline(gd.Api, device, pipelineHandle)); program.AddGraphicsPipeline(ref Internal, pipeline); + // Restore previous blend enable values if we changed it. + while (_blendEnables != 0) + { + int i = BitOperations.TrailingZeroCount(_blendEnables); + + Internal.ColorBlendAttachmentState[i].BlendEnable = true; + _blendEnables &= ~(1u << i); + } + return pipeline; } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs b/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs index c56224216..1a9e58d88 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs @@ -11,23 +11,17 @@ namespace Ryujinx.Graphics.Vulkan { public ulong Id0; public ulong Id1; + public ulong Id2; public ulong Id3; - public ulong Id4; - public ulong Id5; - public ulong Id6; - - public ulong Id7; - public ulong Id8; - - private readonly uint VertexAttributeDescriptionsCount => (byte)((Id5 >> 38) & 0xFF); - private readonly uint VertexBindingDescriptionsCount => (byte)((Id5 >> 46) & 0xFF); - private readonly uint ColorBlendAttachmentStateCount => (byte)((Id6 >> 8) & 0xFF); - private readonly bool HasDepthStencil => ((Id6 >> 63) & 0x1) != 0UL; + private readonly uint VertexAttributeDescriptionsCount => (byte)((Id0 >> 38) & 0xFF); + private readonly uint VertexBindingDescriptionsCount => (byte)((Id0 >> 46) & 0xFF); + private readonly uint ColorBlendAttachmentStateCount => (byte)((Id1 >> 8) & 0xFF); + private readonly bool HasDepthStencil => ((Id1 >> 63) & 0x1) != 0UL; public Array32 VertexAttributeDescriptions; - public Array33 VertexBindingDescriptions; + public Array32 VertexBindingDescriptions; public Array8 ColorBlendAttachmentState; public Array9 AttachmentFormats; public uint AttachmentIntegerFormatMask; @@ -40,9 +34,7 @@ namespace Ryujinx.Graphics.Vulkan public bool Equals(ref PipelineUid other) { - if (!Unsafe.As>(ref Id0).Equals(Unsafe.As>(ref other.Id0)) || - !Unsafe.As>(ref Id4).Equals(Unsafe.As>(ref other.Id4)) || - !Unsafe.As>(ref Id7).Equals(Unsafe.As>(ref other.Id7))) + if (!Unsafe.As>(ref Id0).Equals(Unsafe.As>(ref other.Id0))) { return false; } @@ -80,12 +72,7 @@ namespace Ryujinx.Graphics.Vulkan ulong hash64 = Id0 * 23 ^ Id1 * 23 ^ Id2 * 23 ^ - Id3 * 23 ^ - Id4 * 23 ^ - Id5 * 23 ^ - Id6 * 23 ^ - Id7 * 23 ^ - Id8 * 23; + Id3 * 23; for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++) { diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs index c9aab4018..ae4a2faac 100644 --- a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs +++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; +using PrimitiveTopology = Silk.NET.Vulkan.PrimitiveTopology; namespace Ryujinx.Graphics.Vulkan { @@ -528,7 +529,7 @@ namespace Ryujinx.Graphics.Vulkan public void CreateBackgroundComputePipeline() { PipelineState pipeline = new(); - pipeline.Initialize(); + pipeline.Initialize(_gd.Capabilities); pipeline.Stages[0] = _shaders[0].GetInfo(); pipeline.StagesCount = 1; diff --git a/src/Ryujinx.Graphics.Vulkan/VertexBufferState.cs b/src/Ryujinx.Graphics.Vulkan/VertexBufferState.cs index ce1293589..db23499e8 100644 --- a/src/Ryujinx.Graphics.Vulkan/VertexBufferState.cs +++ b/src/Ryujinx.Graphics.Vulkan/VertexBufferState.cs @@ -73,7 +73,10 @@ namespace Ryujinx.Graphics.Vulkan _buffer = autoBuffer; - state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)stride; + if (!gd.Capabilities.SupportsExtendedDynamicState) + { + state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)stride; + } } return; @@ -81,8 +84,11 @@ namespace Ryujinx.Graphics.Vulkan autoBuffer = gd.BufferManager.GetBuffer(cbs.CommandBuffer, _handle, false, out int size); - // The original stride must be reapplied in case it was rewritten. - state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)_stride; + if (!gd.Capabilities.SupportsExtendedDynamicState) + { + // The original stride must be reapplied in case it was rewritten. + state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)_stride; + } if (_offset >= size) { diff --git a/src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs b/src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs index 94269dd76..7dbdfcb05 100644 --- a/src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs +++ b/src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs @@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.Vulkan { if (_count != 0) { - if (_gd.Capabilities.SupportsExtendedDynamicState) + if (_gd.Capabilities.SupportsExtendedDynamicState && (_gd.SupportsMTL31 || !_gd.IsMoltenVk)) { _gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2( cbs.CommandBuffer, diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs index 352f271cc..8e2ed659a 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs @@ -23,6 +23,7 @@ namespace Ryujinx.Graphics.Vulkan private static readonly string[] _desirableExtensions = { ExtConditionalRendering.ExtensionName, ExtExtendedDynamicState.ExtensionName, + ExtExtendedDynamicState2.ExtensionName, ExtTransformFeedback.ExtensionName, KhrDrawIndirectCount.ExtensionName, KhrPushDescriptor.ExtensionName, @@ -314,6 +315,17 @@ namespace Ryujinx.Graphics.Vulkan features2.PNext = &supportedFeaturesCustomBorderColor; } + PhysicalDeviceExtendedDynamicState2FeaturesEXT supportedFeaturesExtExtendedDynamicState2 = new() + { + SType = StructureType.PhysicalDeviceExtendedDynamicState2FeaturesExt, + PNext = features2.PNext, + }; + + if (physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState2.ExtensionName)) + { + features2.PNext = &supportedFeaturesExtExtendedDynamicState2; + } + PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT supportedFeaturesPrimitiveTopologyListRestart = new() { SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt, @@ -416,6 +428,7 @@ namespace Ryujinx.Graphics.Vulkan TessellationShader = supportedFeatures.TessellationShader, VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics, RobustBufferAccess = useRobustBufferAccess, + WideLines = supportedFeatures.WideLines, SampleRateShading = supportedFeatures.SampleRateShading, }; @@ -473,6 +486,20 @@ namespace Ryujinx.Graphics.Vulkan pExtendedFeatures = &featuresExtendedDynamicState; + if (physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState2.ExtensionName)) + { + var featuresExtendedDynamicState2 = new PhysicalDeviceExtendedDynamicState2FeaturesEXT() + { + SType = StructureType.PhysicalDeviceExtendedDynamicState2FeaturesExt, + PNext = pExtendedFeatures, + ExtendedDynamicState2 = supportedFeaturesExtExtendedDynamicState2.ExtendedDynamicState2, + ExtendedDynamicState2LogicOp = supportedFeaturesExtExtendedDynamicState2.ExtendedDynamicState2LogicOp, + ExtendedDynamicState2PatchControlPoints = supportedFeaturesExtExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints, + }; + + pExtendedFeatures = &featuresExtendedDynamicState2; + } + var featuresVk11 = new PhysicalDeviceVulkan11Features { SType = StructureType.PhysicalDeviceVulkan11Features, diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index cc2bc36c2..e3fc7ec49 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -37,6 +37,7 @@ namespace Ryujinx.Graphics.Vulkan internal KhrSwapchain SwapchainApi { get; private set; } internal ExtConditionalRendering ConditionalRenderingApi { get; private set; } internal ExtExtendedDynamicState ExtendedDynamicStateApi { get; private set; } + internal ExtExtendedDynamicState2 ExtendedDynamicState2Api { get; private set; } internal KhrPushDescriptor PushDescriptorApi { get; private set; } internal ExtTransformFeedback TransformFeedbackApi { get; private set; } internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; } @@ -92,6 +93,7 @@ namespace Ryujinx.Graphics.Vulkan internal bool IsIntelArc { get; private set; } internal bool IsQualcommProprietary { get; private set; } internal bool IsMoltenVk { get; private set; } + internal bool SupportsMTL31 { get; private set; } internal bool IsTBDR { get; private set; } internal bool IsSharedMemory { get; private set; } @@ -117,6 +119,8 @@ namespace Ryujinx.Graphics.Vulkan // Any device running on MacOS is using MoltenVK, even Intel and AMD vendors. if (IsMoltenVk = OperatingSystem.IsMacOS()) MVKInitialization.Initialize(); + + SupportsMTL31 = OperatingSystem.IsMacOSVersionAtLeast(14); } public static VulkanRenderer Create( @@ -139,6 +143,11 @@ namespace Ryujinx.Graphics.Vulkan ExtendedDynamicStateApi = extendedDynamicStateApi; } + if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExtendedDynamicState2 extendedDynamicState2Api)) + { + ExtendedDynamicState2Api = extendedDynamicState2Api; + } + if (Api.TryGetDeviceExtension(_instance.Instance, _device, out KhrPushDescriptor pushDescriptorApi)) { PushDescriptorApi = pushDescriptorApi; @@ -233,6 +242,11 @@ namespace Ryujinx.Graphics.Vulkan SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt, }; + PhysicalDeviceExtendedDynamicState2FeaturesEXT featuresExtendedDynamicState2 = new() + { + SType = StructureType.PhysicalDeviceExtendedDynamicState2FeaturesExt, + }; + PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2 = new() { SType = StructureType.PhysicalDeviceRobustness2FeaturesExt, @@ -273,6 +287,12 @@ namespace Ryujinx.Graphics.Vulkan features2.PNext = &featuresPrimitiveTopologyListRestart; } + if (_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState2.ExtensionName)) + { + featuresExtendedDynamicState2.PNext = features2.PNext; + features2.PNext = &featuresExtendedDynamicState2; + } + if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2")) { featuresRobustness2.PNext = features2.PNext; @@ -406,6 +426,10 @@ namespace Ryujinx.Graphics.Vulkan properties.Limits.FramebufferDepthSampleCounts & properties.Limits.FramebufferStencilSampleCounts; + // Temporarily disable this, can be added back at a later date, make it easy to re-enable. + // Disabled because currently causing Device Lost error on NVIDIA. + featuresExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints = false; + Capabilities = new HardwareCapabilities( _physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"), supportsCustomBorderColor, @@ -422,6 +446,8 @@ namespace Ryujinx.Graphics.Vulkan features2.Features.ShaderStorageImageMultisample, _physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName), _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName), + featuresExtendedDynamicState2, + _physicalDevice.PhysicalDeviceProperties.Limits.MaxTessellationPatchSize, features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue featuresRobustness2.NullDescriptor || IsMoltenVk, supportsPushDescriptors && !IsMoltenVk, @@ -437,6 +463,7 @@ namespace Ryujinx.Graphics.Vulkan _physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"), _physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName), supportsDepthClipControl && featuresDepthClipControl.DepthClipControl, + _physicalDevice.PhysicalDeviceFeatures.WideLines, supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout, supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState, propertiesSubgroup.SubgroupSize, @@ -772,6 +799,10 @@ namespace Ryujinx.Graphics.Vulkan supportsViewportSwizzle: false, supportsIndirectParameters: true, supportsDepthClipControl: Capabilities.SupportsDepthClipControl, + supportsExtendedDynamicState: Capabilities.SupportsExtendedDynamicState, + supportsExtendedDynamicState2: Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2, + supportsLogicOpDynamicState: Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2LogicOp, + supportsPatchControlPointsDynamicState: Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints, uniformBufferSetIndex: PipelineBase.UniformSetIndex, storageBufferSetIndex: PipelineBase.StorageSetIndex, textureSetIndex: PipelineBase.TextureSetIndex,