diff --git a/Ryujinx.Graphics.GAL/Capabilities.cs b/Ryujinx.Graphics.GAL/Capabilities.cs index abacdcfa..7a1f44b6 100644 --- a/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/Ryujinx.Graphics.GAL/Capabilities.cs @@ -9,6 +9,8 @@ namespace Ryujinx.Graphics.GAL public readonly bool HasFrontFacingBug; public readonly bool HasVectorIndexingBug; + public readonly bool NeedsFragmentOutputSpecialization; + public readonly bool ReduceShaderPrecision; public readonly bool SupportsAstcCompression; public readonly bool SupportsBc123Compression; @@ -49,6 +51,8 @@ namespace Ryujinx.Graphics.GAL string vendorName, bool hasFrontFacingBug, bool hasVectorIndexingBug, + bool needsFragmentOutputSpecialization, + bool reduceShaderPrecision, bool supportsAstcCompression, bool supportsBc123Compression, bool supportsBc45Compression, @@ -85,6 +89,8 @@ namespace Ryujinx.Graphics.GAL VendorName = vendorName; HasFrontFacingBug = hasFrontFacingBug; HasVectorIndexingBug = hasVectorIndexingBug; + NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization; + ReduceShaderPrecision = reduceShaderPrecision; SupportsAstcCompression = supportsAstcCompression; SupportsBc123Compression = supportsBc123Compression; SupportsBc45Compression = supportsBc45Compression; diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs index 13b332f4..62df15e7 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs @@ -1,5 +1,6 @@ using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.Shader; @@ -10,6 +11,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// internal class SpecializationStateUpdater { + private readonly GpuContext _context; private GpuChannelGraphicsState _graphics; private GpuChannelPoolState _pool; @@ -18,6 +20,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed private bool _changed; + /// + /// Creates a new instance of the specialization state updater class. + /// + /// GPU context + public SpecializationStateUpdater(GpuContext context) + { + _context = context; + } + /// /// Signal that the specialization state has changed. /// @@ -232,6 +243,42 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed } } + /// + /// Updates the type of the outputs produced by the fragment shader based on the current render target state. + /// + /// The render target control register + /// The color attachment state + public void SetFragmentOutputTypes(RtControl rtControl, ref Array8 state) + { + bool changed = false; + int count = rtControl.UnpackCount(); + + for (int index = 0; index < Constants.TotalRenderTargets; index++) + { + int rtIndex = rtControl.UnpackPermutationIndex(index); + + var colorState = state[rtIndex]; + + if (index < count && StateUpdater.IsRtEnabled(colorState)) + { + Format format = colorState.Format.Convert().Format; + + AttributeType type = format.IsInteger() ? (format.IsSint() ? AttributeType.Sint : AttributeType.Uint) : AttributeType.Float; + + if (type != _graphics.FragmentOutputTypes[index]) + { + _graphics.FragmentOutputTypes[index] = type; + changed = true; + } + } + } + + if (changed && _context.Capabilities.NeedsFragmentOutputSpecialization) + { + Signal(); + } + } + /// /// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0. /// diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs index 3ed5607a..7c730967 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs @@ -138,6 +138,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed _dirtyMask = ulong.MaxValue >> ((sizeof(ulong) * 8) - _callbacks.Length); } + /// + /// Check if the given register group is dirty without clearing it. + /// + /// Index of the group to check + /// True if dirty, false otherwise + public bool IsDirty(int groupIndex) + { + return (_dirtyMask & (1UL << groupIndex)) != 0; + } + /// /// Check all the groups specified by for modification, and update if modified. /// diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index 64fa1735..9b59009c 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed public const int ScissorStateIndex = 16; public const int VertexBufferStateIndex = 0; public const int PrimitiveRestartStateIndex = 12; + public const int RenderTargetStateIndex = 27; private readonly GpuContext _context; private readonly GpuChannel _channel; @@ -264,6 +265,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed _prevTfEnable = false; } + if (_updateTracker.IsDirty(RenderTargetStateIndex)) + { + UpdateRenderTargetSpecialization(); + } + _updateTracker.Update(ulong.MaxValue); // If any state that the shader depends on changed, @@ -526,12 +532,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed } } + /// + /// Updates specialization state based on render target state. + /// + public void UpdateRenderTargetSpecialization() + { + _currentSpecState.SetFragmentOutputTypes(_state.State.RtControl, ref _state.State.RtColorState); + } + /// /// Checks if a render target color buffer is used. /// /// Color buffer information /// True if the specified buffer is enabled/used, false otherwise - private static bool IsRtEnabled(RtColorState colorState) + internal static bool IsRtEnabled(RtColorState colorState) { // Colors are disabled by writing 0 to the format. return colorState.Format != 0 && colorState.WidthOrStride != 0; @@ -893,7 +907,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed { Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}."); - format = Format.R32G32B32A32Float; + format = vertexAttrib.UnpackType() switch + { + VertexAttribType.Sint => Format.R32G32B32A32Sint, + VertexAttribType.Uint => Format.R32G32B32A32Uint, + _ => Format.R32G32B32A32Float + }; } vertexAttribs[index] = new VertexAttribDescriptor( diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs index a38c0987..19eb8b46 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs @@ -71,7 +71,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed _i2mClass = new InlineToMemoryClass(context, channel, initializeState: false); - var spec = new SpecializationStateUpdater(); + var spec = new SpecializationStateUpdater(context); var drawState = new DrawState(); _drawManager = new DrawManager(context, channel, _state, drawState, spec); diff --git a/Ryujinx.Graphics.Gpu/GpuChannel.cs b/Ryujinx.Graphics.Gpu/GpuChannel.cs index 57c632da..fe858762 100644 --- a/Ryujinx.Graphics.Gpu/GpuChannel.cs +++ b/Ryujinx.Graphics.Gpu/GpuChannel.cs @@ -1,4 +1,5 @@ -using Ryujinx.Graphics.Gpu.Engine.GPFifo; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.GPFifo; using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.Memory; using System; @@ -31,6 +32,11 @@ namespace Ryujinx.Graphics.Gpu /// internal MemoryManager MemoryManager => _memoryManager; + /// + /// Host hardware capabilities from the GPU context. + /// + internal ref Capabilities Capabilities => ref _context.Capabilities; + /// /// Creates a new instance of a GPU channel. /// diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs index c567c2c0..97173c96 100644 --- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs +++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs @@ -107,6 +107,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache return _oldSpecState.GraphicsState.AttributeTypes[location]; } + /// + public AttributeType QueryFragmentOutputType(int location) + { + return _oldSpecState.GraphicsState.FragmentOutputTypes[location]; + } + /// public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX; diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs index 28ea430c..05631a21 100644 --- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs +++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -113,6 +113,12 @@ namespace Ryujinx.Graphics.Gpu.Shader return _state.GraphicsState.AttributeTypes[location]; } + /// + public AttributeType QueryFragmentOutputType(int location) + { + return _state.GraphicsState.FragmentOutputTypes[location]; + } + /// public int QueryComputeLocalSizeX() => _state.ComputeState.LocalSizeX; diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index 33f06b6e..d36ffd70 100644 --- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -112,6 +112,8 @@ namespace Ryujinx.Graphics.Gpu.Shader }; } + public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision; + public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug; public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug; diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs index e5e48626..70ac5017 100644 --- a/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs +++ b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs @@ -87,6 +87,11 @@ namespace Ryujinx.Graphics.Gpu.Shader /// public bool HasUnalignedStorageBuffer; + /// + /// Type of the fragment shader outputs. + /// + public Array8 FragmentOutputTypes; + /// /// Creates a new GPU graphics state. /// @@ -105,6 +110,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Type of the vertex attributes consumed by the shader /// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0 /// Indicates that any storage buffer use is unaligned + /// Type of the fragment shader outputs public GpuChannelGraphicsState( bool earlyZForce, PrimitiveTopology topology, @@ -120,7 +126,8 @@ namespace Ryujinx.Graphics.Gpu.Shader float alphaTestReference, ref Array32 attributeTypes, bool hasConstantBufferDrawParameters, - bool hasUnalignedStorageBuffer) + bool hasUnalignedStorageBuffer, + ref Array8 fragmentOutputTypes) { EarlyZForce = earlyZForce; Topology = topology; @@ -137,6 +144,7 @@ namespace Ryujinx.Graphics.Gpu.Shader AttributeTypes = attributeTypes; HasConstantBufferDrawParameters = hasConstantBufferDrawParameters; HasUnalignedStorageBuffer = hasUnalignedStorageBuffer; + FragmentOutputTypes = fragmentOutputTypes; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs index b0d77d8a..a4bf8136 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -530,6 +530,11 @@ namespace Ryujinx.Graphics.Gpu.Shader return false; } + if (channel.Capabilities.NeedsFragmentOutputSpecialization && !graphicsState.FragmentOutputTypes.AsSpan().SequenceEqual(GraphicsState.FragmentOutputTypes.AsSpan())) + { + return false; + } + return Matches(channel, ref poolState, checkTextures, isCompute: false); } diff --git a/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index 1733c6f2..30ed942d 100644 --- a/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -106,6 +106,8 @@ namespace Ryujinx.Graphics.OpenGL vendorName: GpuVendor, hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows, hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows, + needsFragmentOutputSpecialization: false, + reduceShaderPrecision: false, supportsAstcCompression: HwCapabilities.SupportsAstcCompression, supportsBc123Compression: HwCapabilities.SupportsTextureCompressionS3tc, supportsBc45Compression: HwCapabilities.SupportsTextureCompressionRgtc, diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs index 4da21cb7..996d312b 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -346,12 +346,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { string name = context.OperandManager.DeclareLocal(decl); - context.AppendLine(GetVarTypeName(decl.VarType) + " " + name + ";"); + context.AppendLine(GetVarTypeName(context, decl.VarType) + " " + name + ";"); } } - public static string GetVarTypeName(AggregateType type, bool precise = true) + public static string GetVarTypeName(CodeGenContext context, AggregateType type, bool precise = true) { + if (context.Config.GpuAccessor.QueryHostReducedPrecision()) + { + precise = false; + } + return type switch { AggregateType.Void => "void", @@ -666,7 +671,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl } else { - context.AppendLine($"layout (location = {attr}) out vec4 {name};"); + string type = context.Config.Stage != ShaderStage.Fragment ? "vec4" : + context.Config.GpuAccessor.QueryFragmentOutputType(attr) switch + { + AttributeType.Sint => "ivec4", + AttributeType.Uint => "uvec4", + _ => "vec4" + }; + + if (context.Config.GpuAccessor.QueryHostReducedPrecision() && context.Config.Stage == ShaderStage.Vertex && attr == 0) + { + context.AppendLine($"layout (location = {attr}) invariant out {type} {name};"); + } + else + { + context.AppendLine($"layout (location = {attr}) out {type} {name};"); + } } } diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs index 90826a15..90727558 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { for (int i = 1; i < info.Functions.Count; i++) { - context.AppendLine($"{GetFunctionSignature(info.Functions[i])};"); + context.AppendLine($"{GetFunctionSignature(context, info.Functions[i])};"); } context.AppendLine(); @@ -44,7 +44,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { context.CurrentFunction = function; - context.AppendLine(GetFunctionSignature(function, funcName)); + context.AppendLine(GetFunctionSignature(context, function, funcName)); context.EnterScope(); Declarations.DeclareLocals(context, function); @@ -54,23 +54,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl context.LeaveScope(); } - private static string GetFunctionSignature(StructuredFunction function, string funcName = null) + private static string GetFunctionSignature(CodeGenContext context, StructuredFunction function, string funcName = null) { string[] args = new string[function.InArguments.Length + function.OutArguments.Length]; for (int i = 0; i < function.InArguments.Length; i++) { - args[i] = $"{Declarations.GetVarTypeName(function.InArguments[i])} {OperandManager.GetArgumentName(i)}"; + args[i] = $"{Declarations.GetVarTypeName(context, function.InArguments[i])} {OperandManager.GetArgumentName(i)}"; } for (int i = 0; i < function.OutArguments.Length; i++) { int j = i + function.InArguments.Length; - args[j] = $"out {Declarations.GetVarTypeName(function.OutArguments[i])} {OperandManager.GetArgumentName(j)}"; + args[j] = $"out {Declarations.GetVarTypeName(context, function.OutArguments[i])} {OperandManager.GetArgumentName(j)}"; } - return $"{Declarations.GetVarTypeName(function.ReturnType)} {funcName ?? function.Name}({string.Join(", ", args)})"; + return $"{Declarations.GetVarTypeName(context, function.ReturnType)} {funcName ?? function.Name}({string.Join(", ", args)})"; } private static void PrintBlock(CodeGenContext context, AstBlock block) diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs index f667d080..3dbd73b2 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs @@ -32,7 +32,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions if ((outputType & AggregateType.ElementCountMask) != 0) { - return $"{Declarations.GetVarTypeName(outputType, precise: false)}({imageConst})"; + return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({imageConst})"; } return imageConst; @@ -513,7 +513,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions if ((outputType & AggregateType.ElementCountMask) != 0) { - return $"{Declarations.GetVarTypeName(outputType, precise: false)}({scalarValue})"; + return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({scalarValue})"; } } diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs index 3da72b40..fab1667c 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs @@ -577,6 +577,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv context.Decorate(spvVar, Decoration.Patch); } + if (context.Config.GpuAccessor.QueryHostReducedPrecision() && attr == AttributeConsts.PositionX && context.Config.Stage != ShaderStage.Fragment) + { + context.Decorate(spvVar, Decoration.Invariant); + } + context.Decorate(spvVar, Decoration.BuiltIn, (LiteralInteger)GetBuiltIn(context, attrInfo.BaseValue)); if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline && isOutAttr) diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs index a02c4c22..61abf334 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs @@ -2194,13 +2194,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv if (operation.Inst.HasFlag(Instruction.FP64)) { var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2)); - context.Decorate(result, Decoration.NoContraction); + + if (!context.Config.GpuAccessor.QueryHostReducedPrecision()) + { + context.Decorate(result, Decoration.NoContraction); + } + return new OperationResult(AggregateType.FP64, result); } else if (operation.Inst.HasFlag(Instruction.FP32)) { var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2)); - context.Decorate(result, Decoration.NoContraction); + + if (!context.Config.GpuAccessor.QueryHostReducedPrecision()) + { + context.Decorate(result, Decoration.NoContraction); + } + return new OperationResult(AggregateType.FP32, result); } else @@ -2255,13 +2265,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv if (operation.Inst.HasFlag(Instruction.FP64)) { var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3)); - context.Decorate(result, Decoration.NoContraction); + + if (!context.Config.GpuAccessor.QueryHostReducedPrecision()) + { + context.Decorate(result, Decoration.NoContraction); + } + return new OperationResult(AggregateType.FP64, result); } else if (operation.Inst.HasFlag(Instruction.FP32)) { var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3)); - context.Decorate(result, Decoration.NoContraction); + + if (!context.Config.GpuAccessor.QueryHostReducedPrecision()) + { + context.Decorate(result, Decoration.NoContraction); + } + return new OperationResult(AggregateType.FP32, result); } else diff --git a/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/Ryujinx.Graphics.Shader/IGpuAccessor.cs index 337bd314..55df8dc3 100644 --- a/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -114,6 +114,16 @@ namespace Ryujinx.Graphics.Shader return index; } + /// + /// Queries output type for fragment shaders. + /// + /// Location of the framgent output + /// Output location + AttributeType QueryFragmentOutputType(int location) + { + return AttributeType.Float; + } + /// /// Queries Local Size X for compute shaders. /// @@ -186,6 +196,15 @@ namespace Ryujinx.Graphics.Shader return false; } + /// + /// Queries host about whether to reduce precision to improve performance. + /// + /// True if precision is limited to vertex position, false otherwise + bool QueryHostReducedPrecision() + { + return false; + } + /// /// Queries host about the presence of the FrontFacing built-in variable bug. /// diff --git a/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs b/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs index fa1ae17f..b671429a 100644 --- a/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs +++ b/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs @@ -128,7 +128,15 @@ namespace Ryujinx.Graphics.Shader.Translation } else if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd) { - return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | AggregateType.FP32, false); + int location = (value - AttributeConsts.FragmentOutputColorBase) / 16; + var elemType = config.GpuAccessor.QueryFragmentOutputType(location) switch + { + AttributeType.Sint => AggregateType.S32, + AttributeType.Uint => AggregateType.U32, + _ => AggregateType.FP32 + }; + + return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | elemType, false); } else if (value == AttributeConsts.SupportBlockViewInverseX || value == AttributeConsts.SupportBlockViewInverseY) { diff --git a/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/Ryujinx.Graphics.Vulkan/FramebufferParams.cs index 1b99a609..751ef5eb 100644 --- a/Ryujinx.Graphics.Vulkan/FramebufferParams.cs +++ b/Ryujinx.Graphics.Vulkan/FramebufferParams.cs @@ -140,6 +140,25 @@ namespace Ryujinx.Graphics.Vulkan return _attachments[index]; } + public ComponentType GetAttachmentComponentType(int index) + { + if (_colors != null && (uint)index < _colors.Length) + { + var format = _colors[index].Info.Format; + + if (format.IsSint()) + { + return ComponentType.SignedInteger; + } + else if (format.IsUint()) + { + return ComponentType.UnsignedInteger; + } + } + + return ComponentType.Float; + } + public bool IsValidColorAttachment(int bindIndex) { return (uint)bindIndex < Constants.MaxRenderTargets && (_validColorAttachments & (1u << bindIndex)) != 0; diff --git a/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs b/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs index 31acfc9b..0a4d365f 100644 --- a/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs +++ b/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs @@ -1,7 +1,20 @@ using Silk.NET.Vulkan; +using System; namespace Ryujinx.Graphics.Vulkan { + [Flags] + enum PortabilitySubsetFlags + { + None = 0, + + VertexBufferAlignment4B = 1, + NoTriangleFans = 1 << 1, + NoPointMode = 1 << 2, + No3DImageView = 1 << 3, + NoLodBias = 1 << 4 + } + readonly struct HardwareCapabilities { public readonly bool SupportsIndexTypeUint8; @@ -23,6 +36,7 @@ namespace Ryujinx.Graphics.Vulkan public readonly uint MaxSubgroupSize; public readonly ShaderStageFlags RequiredSubgroupSizeStages; public readonly SampleCountFlags SupportedSampleCounts; + public readonly PortabilitySubsetFlags PortabilitySubset; public HardwareCapabilities( bool supportsIndexTypeUint8, @@ -43,7 +57,8 @@ namespace Ryujinx.Graphics.Vulkan uint minSubgroupSize, uint maxSubgroupSize, ShaderStageFlags requiredSubgroupSizeStages, - SampleCountFlags supportedSampleCounts) + SampleCountFlags supportedSampleCounts, + PortabilitySubsetFlags portabilitySubset) { SupportsIndexTypeUint8 = supportsIndexTypeUint8; SupportsCustomBorderColor = supportsCustomBorderColor; @@ -64,6 +79,7 @@ namespace Ryujinx.Graphics.Vulkan MaxSubgroupSize = maxSubgroupSize; RequiredSubgroupSizeStages = requiredSubgroupSizeStages; SupportedSampleCounts = supportedSampleCounts; + PortabilitySubset = portabilitySubset; } } } diff --git a/Ryujinx.Graphics.Vulkan/HelperShader.cs b/Ryujinx.Graphics.Vulkan/HelperShader.cs index 14a63615..223bcc71 100644 --- a/Ryujinx.Graphics.Vulkan/HelperShader.cs +++ b/Ryujinx.Graphics.Vulkan/HelperShader.cs @@ -9,6 +9,13 @@ using VkFormat = Silk.NET.Vulkan.Format; namespace Ryujinx.Graphics.Vulkan { + enum ComponentType + { + Float, + SignedInteger, + UnsignedInteger + } + class HelperShader : IDisposable { private const int UniformBufferAlignment = 256; @@ -18,7 +25,9 @@ namespace Ryujinx.Graphics.Vulkan private readonly ISampler _samplerNearest; private readonly IProgram _programColorBlit; private readonly IProgram _programColorBlitClearAlpha; - private readonly IProgram _programColorClear; + private readonly IProgram _programColorClearF; + private readonly IProgram _programColorClearSI; + private readonly IProgram _programColorClearUI; private readonly IProgram _programStrideChange; private readonly IProgram _programConvertIndexBuffer; private readonly IProgram _programConvertIndirectData; @@ -63,10 +72,22 @@ namespace Ryujinx.Graphics.Vulkan Array.Empty(), Array.Empty()); - _programColorClear = gd.CreateProgramWithMinimalLayout(new[] + _programColorClearF = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), - new ShaderSource(ShaderBinaries.ColorClearFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), + new ShaderSource(ShaderBinaries.ColorClearFFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), + }); + + _programColorClearSI = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ShaderBinaries.ColorClearSIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), + }); + + _programColorClearUI = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ShaderBinaries.ColorClearUIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), }); var strideChangeBindings = new ShaderBindings( @@ -242,6 +263,7 @@ namespace Ryujinx.Graphics.Vulkan int dstWidth, int dstHeight, VkFormat dstFormat, + ComponentType type, Rectangle scissor) { const int ClearColorBufferSize = 16; @@ -273,7 +295,22 @@ namespace Ryujinx.Graphics.Vulkan scissors[0] = scissor; - _pipeline.SetProgram(_programColorClear); + IProgram program; + + if (type == ComponentType.SignedInteger) + { + program = _programColorClearSI; + } + else if (type == ComponentType.UnsignedInteger) + { + program = _programColorClearUI; + } + else + { + program = _programColorClearF; + } + + _pipeline.SetProgram(program); _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat); _pipeline.SetRenderTargetColorMasks(new uint[] { componentMask }); _pipeline.SetViewports(viewports, false); @@ -948,7 +985,9 @@ namespace Ryujinx.Graphics.Vulkan { _programColorBlitClearAlpha.Dispose(); _programColorBlit.Dispose(); - _programColorClear.Dispose(); + _programColorClearF.Dispose(); + _programColorClearSI.Dispose(); + _programColorClearUI.Dispose(); _programStrideChange.Dispose(); _programConvertIndexBuffer.Dispose(); _programConvertIndirectData.Dispose(); diff --git a/Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs b/Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs new file mode 100644 index 00000000..4fbae86e --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs @@ -0,0 +1,104 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan.MoltenVK +{ + enum MVKConfigLogLevel : int + { + None = 0, + Error = 1, + Warning = 2, + Info = 3, + Debug = 4 + } + + enum MVKConfigTraceVulkanCalls : int + { + None = 0, + Enter = 1, + EnterExit = 2, + Duration = 3 + } + + enum MVKConfigAutoGPUCaptureScope : int + { + None = 0, + Device = 1, + Frame = 2 + } + + [Flags] + enum MVKConfigAdvertiseExtensions : int + { + All = 0x00000001, + MoltenVK = 0x00000002, + WSI = 0x00000004, + Portability = 0x00000008 + } + + enum MVKVkSemaphoreSupportStyle : int + { + MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE = 0, + MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS_WHERE_SAFE = 1, + MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS = 2, + MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_CALLBACK = 3, + MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_MAX_ENUM = 0x7FFFFFFF + } + + readonly struct Bool32 + { + uint Value { get; } + + public Bool32(uint value) + { + Value = value; + } + + public Bool32(bool value) + { + Value = value ? 1u : 0u; + } + + public static implicit operator bool(Bool32 val) => val.Value == 1; + public static implicit operator Bool32(bool val) => new Bool32(val); + } + + [StructLayout(LayoutKind.Sequential)] + struct MVKConfiguration + { + public Bool32 DebugMode; + public Bool32 ShaderConversionFlipVertexY; + public Bool32 SynchronousQueueSubmits; + public Bool32 PrefillMetalCommandBuffers; + public uint MaxActiveMetalCommandBuffersPerQueue; + public Bool32 SupportLargeQueryPools; + public Bool32 PresentWithCommandBuffer; + public Bool32 SwapchainMagFilterUseNearest; + public ulong MetalCompileTimeout; + public Bool32 PerformanceTracking; + public uint PerformanceLoggingFrameCount; + public Bool32 DisplayWatermark; + public Bool32 SpecializedQueueFamilies; + public Bool32 SwitchSystemGPU; + public Bool32 FullImageViewSwizzle; + public uint DefaultGPUCaptureScopeQueueFamilyIndex; + public uint DefaultGPUCaptureScopeQueueIndex; + public Bool32 FastMathEnabled; + public MVKConfigLogLevel LogLevel; + public MVKConfigTraceVulkanCalls TraceVulkanCalls; + public Bool32 ForceLowPowerGPU; + public Bool32 SemaphoreUseMTLFence; + public MVKVkSemaphoreSupportStyle SemaphoreSupportStyle; + public MVKConfigAutoGPUCaptureScope AutoGPUCaptureScope; + public IntPtr AutoGPUCaptureOutputFilepath; + public Bool32 Texture1DAs2D; + public Bool32 PreallocateDescriptors; + public Bool32 UseCommandPooling; + public Bool32 UseMTLHeap; + public Bool32 LogActivityPerformanceInline; + public uint ApiVersionToAdvertise; + public MVKConfigAdvertiseExtensions AdvertiseExtensions; + public Bool32 ResumeLostDevice; + public Bool32 UseMetalArgumentBuffers; + } +} diff --git a/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs b/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs new file mode 100644 index 00000000..ca2fbfb9 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs @@ -0,0 +1,31 @@ +using Silk.NET.Vulkan; +using System; +using System.Runtime.Versioning; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan.MoltenVK +{ + [SupportedOSPlatform("macos")] + public static partial class MVKInitialization + { + [LibraryImport("libMoltenVK.dylib")] + private static partial Result vkGetMoltenVKConfigurationMVK(IntPtr unusedInstance, out MVKConfiguration config, in IntPtr configSize); + + [LibraryImport("libMoltenVK.dylib")] + private static partial Result vkSetMoltenVKConfigurationMVK(IntPtr unusedInstance, in MVKConfiguration config, in IntPtr configSize); + + public static void Initialize() + { + var configSize = (IntPtr)Marshal.SizeOf(); + + vkGetMoltenVKConfigurationMVK(IntPtr.Zero, out MVKConfiguration config, configSize); + + config.UseMetalArgumentBuffers = true; + + config.SemaphoreSupportStyle = MVKVkSemaphoreSupportStyle.MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE; + config.SynchronousQueueSubmits = false; + + vkSetMoltenVKConfigurationMVK(IntPtr.Zero, config, configSize); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/Ryujinx.Graphics.Vulkan/PipelineBase.cs index dfcb32d4..1c0c836b 100644 --- a/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -2,6 +2,7 @@ using Ryujinx.Graphics.Shader; using Silk.NET.Vulkan; using System; +using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -50,6 +51,11 @@ namespace Ryujinx.Graphics.Vulkan private Auto _renderPass; private int _writtenAttachmentCount; + private bool _framebufferUsingColorWriteMask; + + private ITexture[] _preMaskColors; + private ITexture _preMaskDepthStencil; + private readonly DescriptorSetUpdater _descriptorSetUpdater; private IndexBufferState _indexBuffer; @@ -905,22 +911,35 @@ namespace Ryujinx.Graphics.Vulkan } } - SignalStateChange(); - - if (writtenAttachments != _writtenAttachmentCount) + if (_framebufferUsingColorWriteMask) { - SignalAttachmentChange(); - _writtenAttachmentCount = writtenAttachments; + SetRenderTargetsInternal(_preMaskColors, _preMaskDepthStencil, true); } + else + { + SignalStateChange(); + + if (writtenAttachments != _writtenAttachmentCount) + { + SignalAttachmentChange(); + _writtenAttachmentCount = writtenAttachments; + } + } + } + + private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked) + { + FramebufferParams?.UpdateModifications(); + CreateFramebuffer(colors, depthStencil, filterWriteMasked); + CreateRenderPass(); + SignalStateChange(); + SignalAttachmentChange(); } public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) { - FramebufferParams?.UpdateModifications(); - CreateFramebuffer(colors, depthStencil); - CreateRenderPass(); - SignalStateChange(); - SignalAttachmentChange(); + _framebufferUsingColorWriteMask = false; + SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR); } public void SetRenderTargetScale(float scale) @@ -1102,7 +1121,7 @@ namespace Ryujinx.Graphics.Vulkan int vbSize = vertexBuffer.Buffer.Size; - if (Gd.Vendor == Vendor.Amd && vertexBuffer.Stride > 0) + if (Gd.Vendor == Vendor.Amd && !Gd.IsMoltenVk && vertexBuffer.Stride > 0) { // AMD has a bug where if offset + stride * count is greater than // the size, then the last attribute will have the wrong value. @@ -1119,7 +1138,8 @@ namespace Ryujinx.Graphics.Vulkan buffer.Dispose(); - if ((vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0) + if (!Gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.VertexBufferAlignment4B) && + (vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0) { buffer = new VertexBufferState( vb, @@ -1259,8 +1279,62 @@ namespace Ryujinx.Graphics.Vulkan _currentPipelineHandle = 0; } - private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil) + private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked) { + if (filterWriteMasked) + { + // TBDR GPUs don't work properly if the same attachment is bound to multiple targets, + // due to each attachment being a copy of the real attachment, rather than a direct write. + + // Just try to remove duplicate attachments. + // Save a copy of the array to rebind when mask changes. + + void maskOut() + { + if (!_framebufferUsingColorWriteMask) + { + _preMaskColors = colors.ToArray(); + _preMaskDepthStencil = depthStencil; + } + + // If true, then the framebuffer must be recreated when the mask changes. + _framebufferUsingColorWriteMask = true; + } + + // Look for textures that are masked out. + + for (int i = 0; i < colors.Length; i++) + { + if (colors[i] == null) + { + continue; + } + + ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[i]; + + for (int j = 0; j < i; j++) + { + // Check each binding for a duplicate binding before it. + + if (colors[i] == colors[j]) + { + // Prefer the binding with no write mask. + ref var vkBlend2 = ref _newState.Internal.ColorBlendAttachmentState[j]; + if (vkBlend.ColorWriteMask == 0) + { + colors[i] = null; + maskOut(); + } + else if (vkBlend2.ColorWriteMask == 0) + { + colors[j] = null; + maskOut(); + } + } + } + } + } + FramebufferParams = new FramebufferParams(Device, colors, depthStencil); UpdatePipelineAttachmentFormats(); } diff --git a/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/Ryujinx.Graphics.Vulkan/PipelineFull.cs index 2256c542..e4bf4fff 100644 --- a/Ryujinx.Graphics.Vulkan/PipelineFull.cs +++ b/Ryujinx.Graphics.Vulkan/PipelineFull.cs @@ -79,6 +79,7 @@ namespace Ryujinx.Graphics.Vulkan (int)FramebufferParams.Width, (int)FramebufferParams.Height, FramebufferParams.AttachmentFormats[index], + FramebufferParams.GetAttachmentComponentType(index), ClearScissor); } else diff --git a/Ryujinx.Graphics.Vulkan/Shaders/ColorClearFragmentShaderSource.frag b/Ryujinx.Graphics.Vulkan/Shaders/ColorClearFFragmentShaderSource.frag similarity index 100% rename from Ryujinx.Graphics.Vulkan/Shaders/ColorClearFragmentShaderSource.frag rename to Ryujinx.Graphics.Vulkan/Shaders/ColorClearFFragmentShaderSource.frag diff --git a/Ryujinx.Graphics.Vulkan/Shaders/ColorClearSIFragmentShaderSource.frag b/Ryujinx.Graphics.Vulkan/Shaders/ColorClearSIFragmentShaderSource.frag new file mode 100644 index 00000000..4254f4f8 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/Shaders/ColorClearSIFragmentShaderSource.frag @@ -0,0 +1,9 @@ +#version 450 core + +layout (location = 0) in vec4 clear_colour; +layout (location = 0) out ivec4 colour; + +void main() +{ + colour = floatBitsToInt(clear_colour); +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Vulkan/Shaders/ColorClearUIFragmentShaderSource.frag b/Ryujinx.Graphics.Vulkan/Shaders/ColorClearUIFragmentShaderSource.frag new file mode 100644 index 00000000..08a6b864 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/Shaders/ColorClearUIFragmentShaderSource.frag @@ -0,0 +1,9 @@ +#version 450 core + +layout (location = 0) in vec4 clear_colour; +layout (location = 0) out uvec4 colour; + +void main() +{ + colour = floatBitsToUint(clear_colour); +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs b/Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs index 992f1e69..667e5a8b 100644 --- a/Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs +++ b/Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs @@ -431,7 +431,7 @@ namespace Ryujinx.Graphics.Vulkan.Shaders 0x3C, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, }; - public static readonly byte[] ColorClearFragmentShaderSource = new byte[] + public static readonly byte[] ColorClearFFragmentShaderSource = new byte[] { 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00, @@ -459,6 +459,68 @@ namespace Ryujinx.Graphics.Vulkan.Shaders 0x0C, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, }; + public static readonly byte[] ColorClearSIFragmentShaderSource = new byte[] + { + 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0F, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, + 0xC2, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x6C, 0x6F, + 0x75, 0x72, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x63, 0x6C, 0x65, 0x61, + 0x72, 0x5F, 0x63, 0x6F, 0x6C, 0x6F, 0x75, 0x72, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x0D, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x3B, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x03, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, + 0x0B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x0C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, + 0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, + 0x0E, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x0F, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0F, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, + }; + + public static readonly byte[] ColorClearUIFragmentShaderSource = new byte[] + { + 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0F, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, + 0xC2, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x6C, 0x6F, + 0x75, 0x72, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x63, 0x6C, 0x65, 0x61, + 0x72, 0x5F, 0x63, 0x6F, 0x6C, 0x6F, 0x75, 0x72, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x0D, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x3B, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x03, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, + 0x0B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x0C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, + 0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, + 0x0E, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x0F, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0F, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, + }; + public static readonly byte[] ColorClearVertexShaderSource = new byte[] { 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x36, 0x00, 0x00, 0x00, diff --git a/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/Ryujinx.Graphics.Vulkan/TextureStorage.cs index f4feecbc..28fabb4f 100644 --- a/Ryujinx.Graphics.Vulkan/TextureStorage.cs +++ b/Ryujinx.Graphics.Vulkan/TextureStorage.cs @@ -106,7 +106,7 @@ namespace Ryujinx.Graphics.Vulkan flags |= ImageCreateFlags.CreateCubeCompatibleBit; } - if (type == ImageType.Type3D) + if (type == ImageType.Type3D && !gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.No3DImageView)) { flags |= ImageCreateFlags.Create2DArrayCompatibleBit; } diff --git a/Ryujinx.Graphics.Vulkan/TextureView.cs b/Ryujinx.Graphics.Vulkan/TextureView.cs index c58c9fc5..a9e1ed36 100644 --- a/Ryujinx.Graphics.Vulkan/TextureView.cs +++ b/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -94,8 +94,14 @@ namespace Ryujinx.Graphics.Vulkan var subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, layers); var subresourceRangeDepth = new ImageSubresourceRange(aspectFlagsDepth, (uint)firstLevel, levels, (uint)firstLayer, layers); - unsafe Auto CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType) + unsafe Auto CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType, ImageUsageFlags usageFlags = 0) { + var usage = new ImageViewUsageCreateInfo() + { + SType = StructureType.ImageViewUsageCreateInfo, + Usage = usageFlags + }; + var imageCreateInfo = new ImageViewCreateInfo() { SType = StructureType.ImageViewCreateInfo, @@ -103,7 +109,8 @@ namespace Ryujinx.Graphics.Vulkan ViewType = viewType, Format = format, Components = cm, - SubresourceRange = sr + SubresourceRange = sr, + PNext = usageFlags == 0 ? null : &usage }; gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError(); @@ -124,9 +131,21 @@ namespace Ryujinx.Graphics.Vulkan // Framebuffer attachments also require 3D textures to be bound as 2D array. if (info.Target == Target.Texture3D) { - subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth); + if (gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.No3DImageView)) + { + if (levels == 1 && (info.Format.IsRtColorCompatible() || info.Format.IsDepthOrStencil())) + { + subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, 1); - _imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray); + _imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2D, ImageUsageFlags.ColorAttachmentBit); + } + } + else + { + subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth); + + _imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray); + } } Valid = true; @@ -353,7 +372,7 @@ namespace Ryujinx.Graphics.Vulkan } if (VulkanConfiguration.UseSlowSafeBlitOnAmd && - _gd.Vendor == Vendor.Amd && + (_gd.Vendor == Vendor.Amd || _gd.IsMoltenVk) && src.Info.Target == Target.Texture2D && dst.Info.Target == Target.Texture2D && !dst.Info.Format.IsDepthOrStencil()) diff --git a/Ryujinx.Graphics.Vulkan/Vendor.cs b/Ryujinx.Graphics.Vulkan/Vendor.cs index 7a6e0e60..087d6e9d 100644 --- a/Ryujinx.Graphics.Vulkan/Vendor.cs +++ b/Ryujinx.Graphics.Vulkan/Vendor.cs @@ -5,9 +5,12 @@ namespace Ryujinx.Graphics.Vulkan enum Vendor { Amd, + ImgTec, Intel, Nvidia, + ARM, Qualcomm, + Apple, Unknown } @@ -21,7 +24,10 @@ namespace Ryujinx.Graphics.Vulkan return id switch { 0x1002 => Vendor.Amd, + 0x1010 => Vendor.ImgTec, + 0x106B => Vendor.Apple, 0x10DE => Vendor.Nvidia, + 0x13B5 => Vendor.ARM, 0x8086 => Vendor.Intel, 0x5143 => Vendor.Qualcomm, _ => Vendor.Unknown @@ -34,6 +40,7 @@ namespace Ryujinx.Graphics.Vulkan { 0x1002 => "AMD", 0x1010 => "ImgTec", + 0x106B => "Apple", 0x10DE => "NVIDIA", 0x13B5 => "ARM", 0x1AE0 => "Google", diff --git a/Ryujinx.Graphics.Vulkan/VertexBufferState.cs b/Ryujinx.Graphics.Vulkan/VertexBufferState.cs index 661bb774..7a022010 100644 --- a/Ryujinx.Graphics.Vulkan/VertexBufferState.cs +++ b/Ryujinx.Graphics.Vulkan/VertexBufferState.cs @@ -82,9 +82,9 @@ namespace Ryujinx.Graphics.Vulkan } _buffer = autoBuffer; - } - state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)_stride; + state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)stride; + } return; } diff --git a/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs index 68462825..fe9462aa 100644 --- a/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs +++ b/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs @@ -389,6 +389,18 @@ namespace Ryujinx.Graphics.Vulkan features2.PNext = &featuresCustomBorderColorSupported; } + PhysicalDeviceRobustness2FeaturesEXT supportedFeaturesRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT() + { + SType = StructureType.PhysicalDeviceRobustness2FeaturesExt + }; + + if (supportedExtensions.Contains("VK_EXT_robustness2")) + { + supportedFeaturesRobustness2.PNext = features2.PNext; + + features2.PNext = &supportedFeaturesRobustness2; + } + api.GetPhysicalDeviceFeatures2(physicalDevice, &features2); var supportedFeatures = features2.Features; @@ -428,14 +440,17 @@ namespace Ryujinx.Graphics.Vulkan pExtendedFeatures = &featuresTransformFeedback; - var featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT() + if (supportedExtensions.Contains("VK_EXT_robustness2")) { - SType = StructureType.PhysicalDeviceRobustness2FeaturesExt, - PNext = pExtendedFeatures, - NullDescriptor = true - }; + var featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT() + { + SType = StructureType.PhysicalDeviceRobustness2FeaturesExt, + PNext = pExtendedFeatures, + NullDescriptor = supportedFeaturesRobustness2.NullDescriptor + }; - pExtendedFeatures = &featuresRobustness2; + pExtendedFeatures = &featuresRobustness2; + } var featuresExtendedDynamicState = new PhysicalDeviceExtendedDynamicStateFeaturesEXT() { diff --git a/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 5c77cb00..f1922efe 100644 --- a/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -3,6 +3,7 @@ using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader.Translation; +using Ryujinx.Graphics.Vulkan.MoltenVK; using Ryujinx.Graphics.Vulkan.Queries; using Silk.NET.Vulkan; using Silk.NET.Vulkan.Extensions.EXT; @@ -77,6 +78,8 @@ namespace Ryujinx.Graphics.Vulkan internal bool IsAmdWindows { get; private set; } internal bool IsIntelWindows { get; private set; } internal bool IsAmdGcn { get; private set; } + internal bool IsMoltenVk { get; private set; } + internal bool IsTBDR { get; private set; } public string GpuVendor { get; private set; } public string GpuRenderer { get; private set; } public string GpuVersion { get; private set; } @@ -93,6 +96,14 @@ namespace Ryujinx.Graphics.Vulkan Shaders = new HashSet(); Textures = new HashSet(); Samplers = new HashSet(); + + if (OperatingSystem.IsMacOS()) + { + MVKInitialization.Initialize(); + + // Any device running on MacOS is using MoltenVK, even Intel and AMD vendors. + IsMoltenVk = true; + } } private unsafe void LoadFeatures(string[] supportedExtensions, uint maxQueueCount, uint queueFamilyIndex) @@ -161,7 +172,10 @@ namespace Ryujinx.Graphics.Vulkan properties2.PNext = &propertiesTransformFeedback; } - Api.GetPhysicalDeviceProperties2(_physicalDevice, &properties2); + PhysicalDevicePortabilitySubsetPropertiesKHR propertiesPortabilitySubset = new PhysicalDevicePortabilitySubsetPropertiesKHR() + { + SType = StructureType.PhysicalDevicePortabilitySubsetPropertiesKhr + }; PhysicalDeviceFeatures2 features2 = new PhysicalDeviceFeatures2() { @@ -183,6 +197,11 @@ namespace Ryujinx.Graphics.Vulkan SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt }; + PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new PhysicalDevicePortabilitySubsetFeaturesKHR() + { + SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr + }; + if (supportedExtensions.Contains("VK_EXT_robustness2")) { features2.PNext = &featuresRobustness2; @@ -200,8 +219,31 @@ namespace Ryujinx.Graphics.Vulkan features2.PNext = &featuresCustomBorderColor; } + bool usePortability = supportedExtensions.Contains("VK_KHR_portability_subset"); + + if (usePortability) + { + propertiesPortabilitySubset.PNext = properties2.PNext; + properties2.PNext = &propertiesPortabilitySubset; + + featuresPortabilitySubset.PNext = features2.PNext; + features2.PNext = &featuresPortabilitySubset; + } + + Api.GetPhysicalDeviceProperties2(_physicalDevice, &properties2); Api.GetPhysicalDeviceFeatures2(_physicalDevice, &features2); + var portabilityFlags = PortabilitySubsetFlags.None; + + if (usePortability) + { + portabilityFlags |= propertiesPortabilitySubset.MinVertexInputBindingStrideAlignment > 1 ? PortabilitySubsetFlags.VertexBufferAlignment4B : 0; + portabilityFlags |= featuresPortabilitySubset.TriangleFans ? 0 : PortabilitySubsetFlags.NoTriangleFans; + portabilityFlags |= featuresPortabilitySubset.PointPolygons ? 0 : PortabilitySubsetFlags.NoPointMode; + portabilityFlags |= featuresPortabilitySubset.ImageView2DOn3DImage ? 0 : PortabilitySubsetFlags.No3DImageView; + portabilityFlags |= featuresPortabilitySubset.SamplerMipLodBias ? 0 : PortabilitySubsetFlags.NoLodBias; + } + bool customBorderColorSupported = supportedExtensions.Contains("VK_EXT_custom_border_color") && featuresCustomBorderColor.CustomBorderColors && featuresCustomBorderColor.CustomBorderColorWithoutFormat; @@ -224,7 +266,7 @@ namespace Ryujinx.Graphics.Vulkan supportedExtensions.Contains(ExtConditionalRendering.ExtensionName), supportedExtensions.Contains(ExtExtendedDynamicState.ExtensionName), features2.Features.MultiViewport, - featuresRobustness2.NullDescriptor, + featuresRobustness2.NullDescriptor || IsMoltenVk, supportedExtensions.Contains(KhrPushDescriptor.ExtensionName), supportsTransformFeedback, propertiesTransformFeedback.TransformFeedbackQueries, @@ -232,7 +274,8 @@ namespace Ryujinx.Graphics.Vulkan propertiesSubgroupSizeControl.MinSubgroupSize, propertiesSubgroupSizeControl.MaxSubgroupSize, propertiesSubgroupSizeControl.RequiredSubgroupSizeStages, - supportedSampleCounts); + supportedSampleCounts, + portabilityFlags); MemoryAllocator = new MemoryAllocator(Api, _device, properties.Limits.MaxMemoryAllocationCount); @@ -413,6 +456,36 @@ namespace Ryujinx.Graphics.Vulkan bool supportsR4G4B4A4Format = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags, GAL.Format.R4G4B4A4Unorm); + bool supportsAstcFormats = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags, + GAL.Format.Astc4x4Unorm, + GAL.Format.Astc5x4Unorm, + GAL.Format.Astc5x5Unorm, + GAL.Format.Astc6x5Unorm, + GAL.Format.Astc6x6Unorm, + GAL.Format.Astc8x5Unorm, + GAL.Format.Astc8x6Unorm, + GAL.Format.Astc8x8Unorm, + GAL.Format.Astc10x5Unorm, + GAL.Format.Astc10x6Unorm, + GAL.Format.Astc10x8Unorm, + GAL.Format.Astc10x10Unorm, + GAL.Format.Astc12x10Unorm, + GAL.Format.Astc12x12Unorm, + GAL.Format.Astc4x4Srgb, + GAL.Format.Astc5x4Srgb, + GAL.Format.Astc5x5Srgb, + GAL.Format.Astc6x5Srgb, + GAL.Format.Astc6x6Srgb, + GAL.Format.Astc8x5Srgb, + GAL.Format.Astc8x6Srgb, + GAL.Format.Astc8x8Srgb, + GAL.Format.Astc10x5Srgb, + GAL.Format.Astc10x6Srgb, + GAL.Format.Astc10x8Srgb, + GAL.Format.Astc10x10Srgb, + GAL.Format.Astc12x10Srgb, + GAL.Format.Astc12x12Srgb); + PhysicalDeviceVulkan12Features featuresVk12 = new PhysicalDeviceVulkan12Features() { SType = StructureType.PhysicalDeviceVulkan12Features @@ -434,7 +507,9 @@ namespace Ryujinx.Graphics.Vulkan GpuVendor, hasFrontFacingBug: IsIntelWindows, hasVectorIndexingBug: Vendor == Vendor.Qualcomm, - supportsAstcCompression: features2.Features.TextureCompressionAstcLdr, + needsFragmentOutputSpecialization: IsMoltenVk, + reduceShaderPrecision: IsMoltenVk, + supportsAstcCompression: features2.Features.TextureCompressionAstcLdr && supportsAstcFormats, supportsBc123Compression: supportsBc123CompressionFormat, supportsBc45Compression: supportsBc45CompressionFormat, supportsBc67Compression: supportsBc67CompressionFormat, @@ -515,12 +590,13 @@ namespace Ryujinx.Graphics.Vulkan IsAmdWindows = Vendor == Vendor.Amd && RuntimeInformation.IsOSPlatform(OSPlatform.Windows); IsIntelWindows = Vendor == Vendor.Intel && RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + IsTBDR = IsMoltenVk || Vendor == Vendor.Qualcomm || Vendor == Vendor.ARM || Vendor == Vendor.ImgTec; GpuVendor = vendorName; GpuRenderer = Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName); GpuVersion = $"Vulkan v{ParseStandardVulkanVersion(properties.ApiVersion)}, Driver v{ParseDriverVersion(ref properties)}"; - IsAmdGcn = Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer); + IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer); Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})"); } @@ -531,6 +607,7 @@ namespace Ryujinx.Graphics.Vulkan { GAL.PrimitiveTopology.Quads => GAL.PrimitiveTopology.Triangles, GAL.PrimitiveTopology.QuadStrip => GAL.PrimitiveTopology.TriangleStrip, + GAL.PrimitiveTopology.TriangleFan => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans) ? GAL.PrimitiveTopology.Triangles : topology, _ => topology }; } @@ -540,6 +617,7 @@ namespace Ryujinx.Graphics.Vulkan return topology switch { GAL.PrimitiveTopology.Quads => true, + GAL.PrimitiveTopology.TriangleFan => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans), _ => false }; } @@ -553,7 +631,13 @@ namespace Ryujinx.Graphics.Vulkan public bool NeedsVertexBufferAlignment(int attrScalarAlignment, out int alignment) { - if (Vendor != Vendor.Nvidia) + if (Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.VertexBufferAlignment4B)) + { + alignment = 4; + + return true; + } + else if (Vendor != Vendor.Nvidia) { // Vulkan requires that vertex attributes are globally aligned by their component size, // so buffer strides that don't divide by the largest scalar element are invalid.