From effd546331371928bc38bc8a48b0c26c7c59f3e9 Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Wed, 16 Aug 2023 08:30:33 -0300
Subject: Implement scaled vertex format emulation (#5564)

* Implement scaled vertex format emulation

* Auto-format (whitespace)

* Delete ToVec4Type
---
 src/Ryujinx.Graphics.GAL/Capabilities.cs           |  3 ++
 .../Engine/Threed/SpecializationStateUpdater.cs    | 27 ++++++++--
 .../Engine/Threed/StateUpdater.cs                  | 15 +++++-
 src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs |  2 +
 src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs      |  1 +
 src/Ryujinx.Graphics.Shader/AttributeType.cs       | 15 ++++++
 src/Ryujinx.Graphics.Shader/IGpuAccessor.cs        |  9 ++++
 .../Instructions/InstEmitAttribute.cs              | 26 ++++++++-
 .../Translation/ShaderDefinitions.cs               | 11 +++-
 .../Translation/Translator.cs                      |  1 +
 src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs  | 61 ++++++++++++++++++++++
 src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs      |  1 +
 12 files changed, 164 insertions(+), 8 deletions(-)

(limited to 'src')

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<AttributeType> 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)
+                {
+                    value = type switch
+                    {
+                        VertexAttribType.Sint => AttributeType.Sint,
+                        VertexAttribType.Uint => AttributeType.Uint,
+                        _ => AttributeType.Float,
+                    };
+                }
+                else
                 {
-                    VertexAttribType.Sint => AttributeType.Sint,
-                    VertexAttribType.Uint => AttributeType.Uint,
-                    _ => AttributeType.Float,
-                };
+                    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
         /// </summary>
         private void UpdateVertexAttribState()
         {
+            bool supportsScaledFormats = _context.Capabilities.SupportsScaledVertexFormats;
             uint vbEnableMask = _vbEnableMask;
 
             Span<VertexAttribDescriptor> 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;
         }
 
+        /// <summary>
+        /// Queries host support scaled vertex formats, where a integer value is converted to floating-point.
+        /// </summary>
+        /// <returns>True if the host support scaled vertex formats, false otherwise</returns>
+        bool QueryHostSupportsScaledVertexFormats()
+        {
+            return true;
+        }
+
         /// <summary>
         /// Queries host GPU shader ballot support.
         /// </summary>
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,
-- 
cgit v1.2.3-70-g09d2