diff --git a/Ryujinx.Graphics.GAL/Capabilities.cs b/Ryujinx.Graphics.GAL/Capabilities.cs index 33c828d19..bbced2dde 100644 --- a/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/Ryujinx.Graphics.GAL/Capabilities.cs @@ -16,6 +16,7 @@ namespace Ryujinx.Graphics.GAL public readonly bool SupportsR4G4Format; public readonly bool SupportsFragmentShaderInterlock; public readonly bool SupportsFragmentShaderOrderingIntel; + public readonly bool SupportsGeometryShaderPassthrough; public readonly bool SupportsImageLoadFormatted; public readonly bool SupportsMismatchingViewFormat; public readonly bool SupportsNonConstantTextureOffset; @@ -44,6 +45,7 @@ namespace Ryujinx.Graphics.GAL bool supportsR4G4Format, bool supportsFragmentShaderInterlock, bool supportsFragmentShaderOrderingIntel, + bool supportsGeometryShaderPassthrough, bool supportsImageLoadFormatted, bool supportsMismatchingViewFormat, bool supportsNonConstantTextureOffset, @@ -69,6 +71,7 @@ namespace Ryujinx.Graphics.GAL SupportsR4G4Format = supportsR4G4Format; SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock; SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel; + SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough; SupportsImageLoadFormatted = supportsImageLoadFormatted; SupportsMismatchingViewFormat = supportsMismatchingViewFormat; SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset; diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 799838ec1..bed5b774c 100644 --- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/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 = 1; + private const uint CodeGenVersion = 2; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index 7cf7b6e99..8c28af5b8 100644 --- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -105,64 +105,37 @@ namespace Ryujinx.Graphics.Gpu.Shader }; } - /// - /// Queries host about the presence of the FrontFacing built-in variable bug. - /// - /// True if the bug is present on the host device used, false otherwise + /// public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug; - /// - /// Queries host about the presence of the vector indexing bug. - /// - /// True if the bug is present on the host device used, false otherwise + /// public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug; - /// - /// Queries host storage buffer alignment required. - /// - /// Host storage buffer alignment in bytes + /// public int QueryHostStorageBufferOffsetAlignment() => _context.Capabilities.StorageBufferOffsetAlignment; - /// - /// Queries host support for texture formats with BGRA component order (such as BGRA8). - /// - /// True if BGRA formats are supported, false otherwise + /// public bool QueryHostSupportsBgraFormat() => _context.Capabilities.SupportsBgraFormat; - /// - /// Queries host support for fragment shader ordering critical sections on the shader code. - /// - /// True if fragment shader interlock is supported, false otherwise + /// public bool QueryHostSupportsFragmentShaderInterlock() => _context.Capabilities.SupportsFragmentShaderInterlock; - /// - /// Queries host support for fragment shader ordering scoped critical sections on the shader code. - /// - /// True if fragment shader ordering is supported, false otherwise + /// public bool QueryHostSupportsFragmentShaderOrderingIntel() => _context.Capabilities.SupportsFragmentShaderOrderingIntel; - /// - /// Queries host support for readable images without a explicit format declaration on the shader. - /// - /// True if formatted image load is supported, false otherwise + /// + public bool QueryHostSupportsGeometryShaderPassthrough() => _context.Capabilities.SupportsGeometryShaderPassthrough; + + /// public bool QueryHostSupportsImageLoadFormatted() => _context.Capabilities.SupportsImageLoadFormatted; - /// - /// Queries host GPU non-constant texture offset support. - /// - /// True if the GPU and driver supports non-constant texture offsets, false otherwise + /// public bool QueryHostSupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset; - /// - /// Queries host GPU shader ballot support. - /// - /// True if the GPU and driver supports shader ballot, false otherwise + /// public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot; - /// - /// Queries host GPU texture shadow LOD support. - /// - /// True if the GPU and driver supports texture shadow LOD, false otherwise + /// public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod; /// diff --git a/Ryujinx.Graphics.OpenGL/HwCapabilities.cs b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs index 773c9f634..c20eccf8f 100644 --- a/Ryujinx.Graphics.OpenGL/HwCapabilities.cs +++ b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs @@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.OpenGL private static readonly Lazy _supportsDrawTexture = new Lazy(() => HasExtension("GL_NV_draw_texture")); private static readonly Lazy _supportsFragmentShaderInterlock = new Lazy(() => HasExtension("GL_ARB_fragment_shader_interlock")); private static readonly Lazy _supportsFragmentShaderOrdering = new Lazy(() => HasExtension("GL_INTEL_fragment_shader_ordering")); + private static readonly Lazy _supportsGeometryShaderPassthrough = new Lazy(() => HasExtension("GL_NV_geometry_shader_passthrough")); private static readonly Lazy _supportsImageLoadFormatted = new Lazy(() => HasExtension("GL_EXT_shader_image_load_formatted")); private static readonly Lazy _supportsIndirectParameters = new Lazy(() => HasExtension("GL_ARB_indirect_parameters")); private static readonly Lazy _supportsParallelShaderCompile = new Lazy(() => HasExtension("GL_ARB_parallel_shader_compile")); @@ -47,6 +48,7 @@ namespace Ryujinx.Graphics.OpenGL public static bool SupportsDrawTexture => _supportsDrawTexture.Value; public static bool SupportsFragmentShaderInterlock => _supportsFragmentShaderInterlock.Value; public static bool SupportsFragmentShaderOrdering => _supportsFragmentShaderOrdering.Value; + public static bool SupportsGeometryShaderPassthrough => _supportsGeometryShaderPassthrough.Value; public static bool SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value; public static bool SupportsIndirectParameters => _supportsIndirectParameters.Value; public static bool SupportsParallelShaderCompile => _supportsParallelShaderCompile.Value; diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs index f670fee07..78335a133 100644 --- a/Ryujinx.Graphics.OpenGL/Renderer.cs +++ b/Ryujinx.Graphics.OpenGL/Renderer.cs @@ -110,6 +110,7 @@ namespace Ryujinx.Graphics.OpenGL supportsR4G4Format: false, supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock, supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering, + supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough, supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted, supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat, supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset, diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs index c3402c582..d64c74520 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -48,7 +48,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl context.AppendLine("#extension GL_ARB_shader_viewport_layer_array : enable"); } - if (context.Config.GpPassthrough) + if (context.Config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough()) { context.AppendLine("#extension GL_NV_geometry_shader_passthrough : enable"); } @@ -127,11 +127,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { if (context.Config.Stage == ShaderStage.Geometry) { - string inPrimitive = context.Config.GpuAccessor.QueryPrimitiveTopology().ToGlslString(); + InputTopology inputTopology = context.Config.GpuAccessor.QueryPrimitiveTopology(); + string inPrimitive = inputTopology.ToGlslString(); context.AppendLine($"layout ({inPrimitive}) in;"); - if (context.Config.GpPassthrough) + if (context.Config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough()) { context.AppendLine($"layout (passthrough) in gl_PerVertex"); context.EnterScope(); @@ -144,7 +145,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { string outPrimitive = context.Config.OutputTopology.ToGlslString(); - int maxOutputVertices = context.Config.MaxOutputVertices; + int maxOutputVertices = context.Config.GpPassthrough + ? inputTopology.ToInputVertices() + : context.Config.MaxOutputVertices; context.AppendLine($"layout ({outPrimitive}, max_vertices = {maxOutputVertices}) out;"); } @@ -563,7 +566,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl }; } - string pass = (context.Config.PassthroughAttributes & (1 << attr)) != 0 ? "passthrough, " : string.Empty; + bool passthrough = (context.Config.PassthroughAttributes & (1 << attr)) != 0; + string pass = passthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough() ? "passthrough, " : string.Empty; string name = $"{DefaultNames.IAttributePrefix}{attr}"; if (context.Config.TransformFeedbackEnabled && context.Config.Stage != ShaderStage.Vertex) diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs index 44f8a5bd2..911137649 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs @@ -425,7 +425,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv var spvType = context.TypePointer(StorageClass.Input, attrType); var spvVar = context.Variable(spvType, StorageClass.Input); - if (context.Config.PassthroughAttributes != 0) + if (context.Config.PassthroughAttributes != 0 && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough()) { context.Decorate(spvVar, Decoration.PassthroughNV); } @@ -534,7 +534,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)context.InputVertices)); - if (context.Config.GpPassthrough) + if (context.Config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough()) { builtInPassthrough = true; } @@ -581,7 +581,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv if (!isOutAttr) { - if (!perPatch && (context.Config.PassthroughAttributes & (1 << location)) != 0) + if (!perPatch && + (context.Config.PassthroughAttributes & (1 << location)) != 0 && + context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough()) { context.Decorate(spvVar, Decoration.PassthroughNV); } @@ -646,7 +648,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv } else { - if ((context.Config.PassthroughAttributes & (1 << location)) != 0) + if ((context.Config.PassthroughAttributes & (1 << location)) != 0 && + context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough()) { context.Decorate(spvVar, Decoration.PassthroughNV); } diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs index 666fcc42e..2755d3ab0 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs @@ -70,7 +70,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { context.AddCapability(Capability.Geometry); - if (config.GpPassthrough) + if (config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough()) { context.AddExtension("SPV_NV_geometry_shader_passthrough"); context.AddCapability(Capability.GeometryShaderPassthroughNV); @@ -230,7 +230,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv _ => throw new InvalidOperationException($"Invalid output topology \"{context.Config.OutputTopology}\".") }); - context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)context.Config.MaxOutputVertices); + int maxOutputVertices = context.Config.GpPassthrough ? context.InputVertices : context.Config.MaxOutputVertices; + + context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)maxOutputVertices); } else if (context.Config.Stage == ShaderStage.Fragment) { diff --git a/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/Ryujinx.Graphics.Shader/IGpuAccessor.cs index 87288b2e6..e6a62302a 100644 --- a/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -197,6 +197,15 @@ namespace Ryujinx.Graphics.Shader return false; } + /// + /// Queries host GPU geometry shader passthrough support. + /// + /// True if the GPU and driver supports geometry shader passthrough, false otherwise + bool QueryHostSupportsGeometryShaderPassthrough() + { + return true; + } + /// /// Queries host support for readable images without a explicit format declaration on the shader. /// diff --git a/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs b/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs index 8bf98963d..ffe9d9e75 100644 --- a/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs +++ b/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs @@ -91,7 +91,7 @@ namespace Ryujinx.Graphics.Shader.Translation AggregateType elemType; - if (!isOutAttr) + if (config.Stage == ShaderStage.Vertex && !isOutAttr) { elemType = config.GpuAccessor.QueryAttributeType(location) switch { diff --git a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs index dbf4eb125..38c503e67 100644 --- a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs +++ b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs @@ -2,6 +2,7 @@ using Ryujinx.Graphics.Shader.Decoders; using Ryujinx.Graphics.Shader.IntermediateRepresentation; using System.Collections.Generic; using System.Diagnostics; +using System.Numerics; using System.Runtime.CompilerServices; using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; @@ -232,6 +233,44 @@ namespace Ryujinx.Graphics.Shader.Translation { PrepareForVertexReturn(); } + else if (Config.Stage == ShaderStage.Geometry) + { + void WriteOutput(int index, int primIndex) + { + Operand x = this.LoadAttribute(Const(index), Const(0), Const(primIndex)); + Operand y = this.LoadAttribute(Const(index + 4), Const(0), Const(primIndex)); + Operand z = this.LoadAttribute(Const(index + 8), Const(0), Const(primIndex)); + Operand w = this.LoadAttribute(Const(index + 12), Const(0), Const(primIndex)); + + this.Copy(Attribute(index), x); + this.Copy(Attribute(index + 4), y); + this.Copy(Attribute(index + 8), z); + this.Copy(Attribute(index + 12), w); + } + + if (Config.GpPassthrough) + { + int inputVertices = Config.GpuAccessor.QueryPrimitiveTopology().ToInputVertices(); + + for (int primIndex = 0; primIndex < inputVertices; primIndex++) + { + WriteOutput(AttributeConsts.PositionX, primIndex); + + int passthroughAttributes = Config.PassthroughAttributes; + while (passthroughAttributes != 0) + { + int index = BitOperations.TrailingZeroCount(passthroughAttributes); + WriteOutput(AttributeConsts.UserAttributeBase + index * 16, primIndex); + Config.SetOutputUserAttribute(index, perPatch: false); + passthroughAttributes &= ~(1 << index); + } + + this.EmitVertex(); + } + + this.EndPrimitive(); + } + } else if (Config.Stage == ShaderStage.Fragment) { bool supportsBgra = Config.GpuAccessor.QueryHostSupportsBgraFormat(); diff --git a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs index e6e95240c..c5e138879 100644 --- a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs +++ b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs @@ -15,6 +15,7 @@ namespace Ryujinx.Graphics.Shader.Translation public ShaderStage Stage { get; } public bool GpPassthrough { get; } + public bool GpPassthroughWithHostSupport => GpPassthrough && GpuAccessor.QueryHostSupportsGeometryShaderPassthrough(); public bool LastInVertexPipeline { get; private set; } public int ThreadsPerInputPrimitive { get; } diff --git a/Ryujinx.Graphics.Vulkan/VulkanGraphicsDevice.cs b/Ryujinx.Graphics.Vulkan/VulkanGraphicsDevice.cs index 4153edcaf..177bb0824 100644 --- a/Ryujinx.Graphics.Vulkan/VulkanGraphicsDevice.cs +++ b/Ryujinx.Graphics.Vulkan/VulkanGraphicsDevice.cs @@ -38,6 +38,7 @@ namespace Ryujinx.Graphics.Vulkan internal bool SupportsCustomBorderColor { get; private set; } internal bool SupportsIndirectParameters { get; private set; } internal bool SupportsFragmentShaderInterlock { get; private set; } + internal bool SupportsGeometryShaderPassthrough { get; private set; } internal bool SupportsSubgroupSizeControl { get; private set; } internal uint QueueFamilyIndex { get; private set; } @@ -123,6 +124,7 @@ namespace Ryujinx.Graphics.Vulkan SupportsCustomBorderColor = supportedExtensions.Contains("VK_EXT_custom_border_color"); SupportsIndirectParameters = supportedExtensions.Contains(KhrDrawIndirectCount.ExtensionName); SupportsFragmentShaderInterlock = supportedExtensions.Contains("VK_EXT_fragment_shader_interlock"); + SupportsGeometryShaderPassthrough = supportedExtensions.Contains("VK_NV_geometry_shader_passthrough"); SupportsSubgroupSizeControl = supportedExtensions.Contains("VK_EXT_subgroup_size_control"); if (api.TryGetDeviceExtension(_instance, _device, out KhrSwapchain swapchainApi)) @@ -311,6 +313,7 @@ namespace Ryujinx.Graphics.Vulkan supportsR4G4Format: false, supportsFragmentShaderInterlock: SupportsFragmentShaderInterlock, supportsFragmentShaderOrderingIntel: false, + supportsGeometryShaderPassthrough: SupportsGeometryShaderPassthrough, supportsImageLoadFormatted: features.ShaderStorageImageReadWithoutFormat, supportsMismatchingViewFormat: true, supportsNonConstantTextureOffset: false,