diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs index 3c49a7dc..f4b1d4d1 100644 --- a/src/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs @@ -21,6 +21,7 @@ namespace Ryujinx.Graphics.GAL public readonly bool SupportsBgraFormat; public readonly bool SupportsR4G4Format; public readonly bool SupportsR4G4B4A4Format; + public readonly bool SupportsScaledVertexFormats; public readonly bool SupportsSnormBufferTextureFormat; public readonly bool Supports5BitComponentFormat; public readonly bool SupportsBlendEquationAdvanced; @@ -71,6 +72,7 @@ namespace Ryujinx.Graphics.GAL bool supportsBgraFormat, bool supportsR4G4Format, bool supportsR4G4B4A4Format, + bool supportsScaledVertexFormats, bool supportsSnormBufferTextureFormat, bool supports5BitComponentFormat, bool supportsBlendEquationAdvanced, @@ -117,6 +119,7 @@ namespace Ryujinx.Graphics.GAL SupportsBgraFormat = supportsBgraFormat; SupportsR4G4Format = supportsR4G4Format; SupportsR4G4B4A4Format = supportsR4G4B4A4Format; + SupportsScaledVertexFormats = supportsScaledVertexFormats; SupportsSnormBufferTextureFormat = supportsSnormBufferTextureFormat; Supports5BitComponentFormat = supports5BitComponentFormat; SupportsBlendEquationAdvanced = supportsBlendEquationAdvanced; diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs index b2935a5b..e0607fbf 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs @@ -218,17 +218,34 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed { bool changed = false; ref Array32 attributeTypes = ref _graphics.AttributeTypes; + bool supportsScaledFormats = _context.Capabilities.SupportsScaledVertexFormats; for (int location = 0; location < state.Length; location++) { VertexAttribType type = state[location].UnpackType(); - AttributeType value = type switch + AttributeType value; + + if (supportsScaledFormats) { - VertexAttribType.Sint => AttributeType.Sint, - VertexAttribType.Uint => AttributeType.Uint, - _ => AttributeType.Float, - }; + value = type switch + { + VertexAttribType.Sint => AttributeType.Sint, + VertexAttribType.Uint => AttributeType.Uint, + _ => AttributeType.Float, + }; + } + else + { + value = type switch + { + VertexAttribType.Sint => AttributeType.Sint, + VertexAttribType.Uint => AttributeType.Uint, + VertexAttribType.Uscaled => AttributeType.Uscaled, + VertexAttribType.Sscaled => AttributeType.Sscaled, + _ => AttributeType.Float, + }; + } if (attributeTypes[location] != value) { diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index b08e7f26..1f919d9b 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -932,6 +932,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// private void UpdateVertexAttribState() { + bool supportsScaledFormats = _context.Capabilities.SupportsScaledVertexFormats; uint vbEnableMask = _vbEnableMask; Span vertexAttribs = stackalloc VertexAttribDescriptor[Constants.TotalVertexAttribs]; @@ -949,7 +950,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed continue; } - if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format)) + uint packedFormat = vertexAttrib.UnpackFormat(); + + if (!supportsScaledFormats) + { + packedFormat = vertexAttrib.UnpackType() switch + { + VertexAttribType.Uscaled => ((uint)VertexAttribType.Uint << 27) | (packedFormat & (0x3f << 21)), + VertexAttribType.Sscaled => ((uint)VertexAttribType.Sint << 27) | (packedFormat & (0x3f << 21)), + _ => packedFormat, + }; + } + + if (!FormatTable.TryGetAttribFormat(packedFormat, out Format format)) { Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}."); diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index 6d27f18d..e7a2d345 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -153,6 +153,8 @@ namespace Ryujinx.Graphics.Gpu.Shader public bool QueryHostSupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset; + public bool QueryHostSupportsScaledVertexFormats() => _context.Capabilities.SupportsScaledVertexFormats; + public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot; public bool QueryHostSupportsShaderBarrierDivergence() => _context.Capabilities.SupportsShaderBarrierDivergence; diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index 47b832f2..8a7ac855 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -159,6 +159,7 @@ namespace Ryujinx.Graphics.OpenGL supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat, supportsCubemapView: true, supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset, + supportsScaledVertexFormats: true, supportsShaderBallot: HwCapabilities.SupportsShaderBallot, supportsShaderBarrierDivergence: !(intelWindows || intelUnix), supportsShaderFloat64: true, diff --git a/src/Ryujinx.Graphics.Shader/AttributeType.cs b/src/Ryujinx.Graphics.Shader/AttributeType.cs index e6adb4b8..1d950773 100644 --- a/src/Ryujinx.Graphics.Shader/AttributeType.cs +++ b/src/Ryujinx.Graphics.Shader/AttributeType.cs @@ -9,6 +9,8 @@ namespace Ryujinx.Graphics.Shader Float, Sint, Uint, + Sscaled, + Uscaled, } static class AttributeTypeExtensions @@ -23,5 +25,18 @@ namespace Ryujinx.Graphics.Shader _ => throw new ArgumentException($"Invalid attribute type \"{type}\"."), }; } + + public static AggregateType ToAggregateType(this AttributeType type, bool supportsScaledFormats) + { + return type switch + { + AttributeType.Float => AggregateType.FP32, + AttributeType.Sint => AggregateType.S32, + AttributeType.Uint => AggregateType.U32, + AttributeType.Sscaled => supportsScaledFormats ? AggregateType.FP32 : AggregateType.S32, + AttributeType.Uscaled => supportsScaledFormats ? AggregateType.FP32 : AggregateType.U32, + _ => throw new ArgumentException($"Invalid attribute type \"{type}\"."), + }; + } } } diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs index 4c0adc3b..ee31f02d 100644 --- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -266,6 +266,15 @@ namespace Ryujinx.Graphics.Shader return true; } + /// + /// Queries host support scaled vertex formats, where a integer value is converted to floating-point. + /// + /// True if the host support scaled vertex formats, false otherwise + bool QueryHostSupportsScaledVertexFormats() + { + return true; + } + /// /// Queries host GPU shader ballot support. /// diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs index 542ec74a..53d774d6 100644 --- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs @@ -61,7 +61,31 @@ namespace Ryujinx.Graphics.Shader.Instructions } else { - context.Copy(Register(rd), AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, op.P)); + value = AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, op.P); + + if (!context.TranslatorContext.Definitions.SupportsScaledVertexFormats && + context.TranslatorContext.Stage == ShaderStage.Vertex && + !op.O && + offset >= 0x80 && + offset < 0x280) + { + // The host does not support scaled vertex formats, + // the emulator should use a integer format, and + // we compensate here inserting the conversion to float. + + AttributeType type = context.TranslatorContext.Definitions.GetAttributeType((offset - 0x80) >> 4); + + if (type == AttributeType.Sscaled) + { + value = context.IConvertS32ToFP32(value); + } + else if (type == AttributeType.Uscaled) + { + value = context.IConvertU32ToFP32(value); + } + } + + context.Copy(Register(rd), value); } } else diff --git a/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs b/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs index d278c42e..204f4278 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs @@ -53,6 +53,8 @@ namespace Ryujinx.Graphics.Shader.Translation public bool OmapSampleMask { get; } public bool OmapDepth { get; } + public bool SupportsScaledVertexFormats { get; } + public bool TransformFeedbackEnabled { get; } private readonly TransformFeedbackOutput[] _transformFeedbackOutputs; @@ -139,6 +141,7 @@ namespace Ryujinx.Graphics.Shader.Translation int omapTargets, bool omapSampleMask, bool omapDepth, + bool supportsScaledVertexFormats, bool transformFeedbackEnabled, ulong transformFeedbackVecMap, TransformFeedbackOutput[] transformFeedbackOutputs) @@ -154,6 +157,7 @@ namespace Ryujinx.Graphics.Shader.Translation OmapSampleMask = omapSampleMask; OmapDepth = omapDepth; LastInVertexPipeline = stage < ShaderStage.Fragment; + SupportsScaledVertexFormats = supportsScaledVertexFormats; TransformFeedbackEnabled = transformFeedbackEnabled; _transformFeedbackOutputs = transformFeedbackOutputs; _transformFeedbackDefinitions = new(); @@ -302,7 +306,7 @@ namespace Ryujinx.Graphics.Shader.Translation if (Stage == ShaderStage.Vertex && !isOutput) { - type |= _graphicsState.AttributeTypes[location].ToAggregateType(); + type |= _graphicsState.AttributeTypes[location].ToAggregateType(SupportsScaledVertexFormats); } else { @@ -311,5 +315,10 @@ namespace Ryujinx.Graphics.Shader.Translation return type; } + + public AttributeType GetAttributeType(int location) + { + return _graphicsState.AttributeTypes[location]; + } } } diff --git a/src/Ryujinx.Graphics.Shader/Translation/Translator.cs b/src/Ryujinx.Graphics.Shader/Translation/Translator.cs index b609ac07..93a70ace 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Translator.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Translator.cs @@ -116,6 +116,7 @@ namespace Ryujinx.Graphics.Shader.Translation header.OmapTargets, header.OmapSampleMask, header.OmapDepth, + gpuAccessor.QueryHostSupportsScaledVertexFormats(), transformFeedbackEnabled, transformFeedbackVecMap, transformFeedbackOutputs); diff --git a/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs b/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs index 5f7deeb6..7307a0ee 100644 --- a/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs +++ b/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs @@ -9,6 +9,48 @@ namespace Ryujinx.Graphics.Vulkan { class FormatCapabilities { + private static readonly GAL.Format[] _scaledFormats = { + GAL.Format.R8Uscaled, + GAL.Format.R8Sscaled, + GAL.Format.R16Uscaled, + GAL.Format.R16Sscaled, + GAL.Format.R8G8Uscaled, + GAL.Format.R8G8Sscaled, + GAL.Format.R16G16Uscaled, + GAL.Format.R16G16Sscaled, + GAL.Format.R8G8B8Uscaled, + GAL.Format.R8G8B8Sscaled, + GAL.Format.R16G16B16Uscaled, + GAL.Format.R16G16B16Sscaled, + GAL.Format.R8G8B8A8Uscaled, + GAL.Format.R8G8B8A8Sscaled, + GAL.Format.R16G16B16A16Uscaled, + GAL.Format.R16G16B16A16Sscaled, + GAL.Format.R10G10B10A2Uscaled, + GAL.Format.R10G10B10A2Sscaled, + }; + + private static readonly GAL.Format[] _intFormats = { + GAL.Format.R8Uint, + GAL.Format.R8Sint, + GAL.Format.R16Uint, + GAL.Format.R16Sint, + GAL.Format.R8G8Uint, + GAL.Format.R8G8Sint, + GAL.Format.R16G16Uint, + GAL.Format.R16G16Sint, + GAL.Format.R8G8B8Uint, + GAL.Format.R8G8B8Sint, + GAL.Format.R16G16B16Uint, + GAL.Format.R16G16B16Sint, + GAL.Format.R8G8B8A8Uint, + GAL.Format.R8G8B8A8Sint, + GAL.Format.R16G16B16A16Uint, + GAL.Format.R16G16B16A16Sint, + GAL.Format.R10G10B10A2Uint, + GAL.Format.R10G10B10A2Sint, + }; + private readonly FormatFeatureFlags[] _bufferTable; private readonly FormatFeatureFlags[] _optimalTable; @@ -66,6 +108,25 @@ namespace Ryujinx.Graphics.Vulkan return (formatFeatureFlags & flags) == flags; } + public bool SupportsScaledVertexFormats() + { + // We want to check is all scaled formats are supported, + // but if the integer variant is not supported either, + // then the format is likely not supported at all, + // we ignore formats that are entirely unsupported here. + + for (int i = 0; i < _scaledFormats.Length; i++) + { + if (!BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, _scaledFormats[i]) && + BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, _intFormats[i])) + { + return false; + } + } + + return true; + } + public bool BufferFormatSupports(FormatFeatureFlags flags, VkFormat format) { _api.GetPhysicalDeviceFormatProperties(_physicalDevice, format, out var fp); diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 20b32c70..3383d728 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -604,6 +604,7 @@ namespace Ryujinx.Graphics.Vulkan supportsMismatchingViewFormat: true, supportsCubemapView: !IsAmdGcn, supportsNonConstantTextureOffset: false, + supportsScaledVertexFormats: FormatCapabilities.SupportsScaledVertexFormats(), supportsShaderBallot: false, supportsShaderBarrierDivergence: Vendor != Vendor.Intel, supportsShaderFloat64: Capabilities.SupportsShaderFloat64,