From f95b7c58779f01d9077996da67953d8d9acd058c Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Sat, 29 Jul 2023 18:47:03 -0300
Subject: Fix incorrect fragment origin when YNegate is enabled (#4673)

* Fix incorrect fragment origin when YNegate is enabled

* Shader cache version bump

* Do not update support buffer if shader does not read gl_FragCoord

* Pass unscaled viewport size to the support buffer
---
 .../Engine/Threed/SpecializationStateUpdater.cs    | 14 +++++++
 .../Engine/Threed/StateUpdater.cs                  | 47 ++++++++++++++++++++--
 .../Memory/SupportBufferUpdater.cs                 | 30 ++++++++++++++
 .../Shader/DiskCache/DiskCacheGpuAccessor.cs       |  6 +++
 .../Shader/DiskCache/DiskCacheHostStorage.cs       |  9 ++++-
 src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs     | 16 +++++---
 .../Shader/GpuChannelGraphicsState.cs              | 12 +++++-
 .../Shader/ShaderSpecializationState.cs            |  5 +++
 .../CodeGen/Glsl/Declarations.cs                   | 15 +++++--
 .../CodeGen/Spirv/SpirvGenerator.cs                |  2 +-
 src/Ryujinx.Graphics.Shader/IGpuAccessor.cs        | 21 +++++++---
 .../Instructions/InstEmitAttribute.cs              | 12 ++++++
 src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs   |  3 ++
 .../StructuredIr/ShaderProperties.cs               |  7 ++++
 src/Ryujinx.Graphics.Shader/SupportBuffer.cs       |  5 +++
 .../Translation/EmitterContextInsts.cs             |  5 +++
 .../Translation/ShaderConfig.cs                    | 20 +++++++--
 17 files changed, 205 insertions(+), 24 deletions(-)

(limited to 'src')

diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs
index cbf1573c..b2935a5b 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs
@@ -342,5 +342,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                 Signal();
             }
         }
+
+        /// <summary>
+        /// Sets the Y negate enabled state.
+        /// </summary>
+        /// <param name="enabled">True if Y negate of the fragment coordinates is enabled</param>
+        public void SetYNegateEnabled(bool enabled)
+        {
+            if (enabled != _graphics.YNegateEnabled)
+            {
+                _graphics.YNegateEnabled = enabled;
+
+                Signal();
+            }
+        }
     }
 }
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
index b4f56245..c0c2d5b3 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
@@ -37,6 +37,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
 
         private ProgramPipelineState _pipeline;
 
+        private bool _fsReadsFragCoord;
         private bool _vsUsesDrawParameters;
         private bool _vtgWritesRtLayer;
         private byte _vsClipDistancesWritten;
@@ -692,12 +693,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
             var face = _state.State.FaceState;
 
             bool disableTransform = _state.State.ViewportTransformEnable == 0;
+            bool yNegate = yControl.HasFlag(YControl.NegateY);
 
             UpdateFrontFace(yControl, face.FrontFace);
             UpdateDepthMode();
 
-            bool flipY = yControl.HasFlag(YControl.NegateY);
-
             Span<Viewport> viewports = stackalloc Viewport[Constants.TotalViewports];
 
             for (int index = 0; index < Constants.TotalViewports; index++)
@@ -719,7 +719,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                 float scaleX = MathF.Abs(transform.ScaleX);
                 float scaleY = transform.ScaleY;
 
-                if (flipY)
+                if (yNegate)
                 {
                     scaleY = -scaleY;
                 }
@@ -771,8 +771,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                 _channel.TextureManager.RenderTargetScale,
                 disableTransform);
 
+            // Viewport size is only used on the shader when YNegate is enabled,
+            // and if the fragment shader accesses gl_FragCoord,
+            // so there's no need to update it in other cases.
+            if (yNegate && _fsReadsFragCoord)
+            {
+                UpdateSupportBufferViewportSize();
+            }
+
             _currentSpecState.SetViewportTransformDisable(disableTransform);
             _currentSpecState.SetDepthMode(GetDepthMode() == DepthMode.MinusOneToOne);
+            _currentSpecState.SetYNegateEnabled(yNegate);
         }
 
         /// <summary>
@@ -1415,9 +1424,41 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                 _currentProgramInfo[stageIndex] = info;
             }
 
+            if (gs.Shaders[5]?.Info.UsesFragCoord == true)
+            {
+                // Make sure we update the viewport size on the support buffer if it will be consumed on the new shader.
+
+                if (!_fsReadsFragCoord && _state.State.YControl.HasFlag(YControl.NegateY))
+                {
+                    UpdateSupportBufferViewportSize();
+                }
+
+                _fsReadsFragCoord = true;
+            }
+            else
+            {
+                _fsReadsFragCoord = false;
+            }
+
             _context.Renderer.Pipeline.SetProgram(gs.HostProgram);
         }
 
+        /// <summary>
+        /// Updates the viewport size on the support buffer for fragment shader access.
+        /// </summary>
+        private void UpdateSupportBufferViewportSize()
+        {
+            ref var transform = ref _state.State.ViewportTransform[0];
+
+            float scaleX = MathF.Abs(transform.ScaleX);
+            float scaleY = transform.ScaleY;
+
+            float width = scaleX * 2;
+            float height = scaleY * 2;
+
+            _context.SupportBufferUpdater.SetViewportSize(width, MathF.Abs(height));
+        }
+
         /// <summary>
         /// Updates bindings consumed by the shader on the texture and buffer managers.
         /// </summary>
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs b/src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs
index 50c042fb..b236476e 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs
@@ -112,6 +112,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
             MarkDirty(SupportBuffer.ViewportInverseOffset, SupportBuffer.FieldSize);
         }
 
+        /// <summary>
+        /// Updates the viewport size vector.
+        /// </summary>
+        /// <param name="data">Viewport size vector</param>
+        private void UpdateViewportSize(Vector4<float> data)
+        {
+            _data.ViewportSize = data;
+
+            MarkDirty(SupportBuffer.ViewportSizeOffset, SupportBuffer.FieldSize);
+        }
+
         /// <summary>
         /// Sets the scale of all output render targets (they should all have the same scale).
         /// </summary>
@@ -192,6 +203,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
             }
         }
 
+        /// <summary>
+        /// Sets the viewport size, used to invert the fragment coordinates Y value.
+        /// </summary>
+        /// <param name="viewportWidth">Value used as viewport width</param>
+        /// <param name="viewportHeight">Value used as viewport height</param>
+        public void SetViewportSize(float viewportWidth, float viewportHeight)
+        {
+            if (_data.ViewportSize.X != viewportWidth || _data.ViewportSize.Y != viewportHeight)
+            {
+                UpdateViewportSize(new Vector4<float>
+                {
+                    X = viewportWidth,
+                    Y = viewportHeight,
+                    Z = 1,
+                    W = 0
+                });
+            }
+        }
+
         /// <summary>
         /// Submits all pending buffer updates to the GPU.
         /// </summary>
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
index 7f01aca6..b5f9395e 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
@@ -247,6 +247,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
             return _oldSpecState.GraphicsState.ViewportTransformDisable;
         }
 
+        /// <inheritdoc/>
+        public bool QueryYNegateEnabled()
+        {
+            return _oldSpecState.GraphicsState.YNegateEnabled;
+        }
+
         /// <inheritdoc/>
         public void RegisterTexture(int handle, int cbufSlot)
         {
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
index 672b3b8d..4bab165d 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
         private const ushort FileFormatVersionMajor = 1;
         private const ushort FileFormatVersionMinor = 2;
         private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
-        private const uint CodeGenVersion = 5266;
+        private const uint CodeGenVersion = 4675;
 
         private const string SharedTocFileName = "shared.toc";
         private const string SharedDataFileName = "shared.data";
@@ -140,6 +140,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
             /// </summary>
             public ShaderStage Stage;
 
+            /// <summary>
+            /// Indicates if the fragment shader accesses the fragment coordinate built-in variable.
+            /// </summary>
+            public bool UsesFragCoord;
+
             /// <summary>
             /// Indicates if the shader accesses the Instance ID built-in variable.
             /// </summary>
@@ -781,6 +786,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
                 ShaderIdentification.None,
                 0,
                 dataInfo.Stage,
+                dataInfo.UsesFragCoord,
                 dataInfo.UsesInstanceId,
                 dataInfo.UsesDrawParameters,
                 dataInfo.UsesRtLayer,
@@ -807,6 +813,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
                 TexturesCount = (ushort)info.Textures.Count,
                 ImagesCount = (ushort)info.Images.Count,
                 Stage = info.Stage,
+                UsesFragCoord = info.UsesFragCoord,
                 UsesInstanceId = info.UsesInstanceId,
                 UsesDrawParameters = info.UsesDrawParameters,
                 UsesRtLayer = info.UsesRtLayer,
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
index ca9c883e..1fcc93c5 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
@@ -113,6 +113,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
             return _state.GraphicsState.AttributeTypes[location];
         }
 
+        /// <inheritdoc/>
+        public bool QueryEarlyZForce()
+        {
+            _state.SpecializationState?.RecordEarlyZForce();
+            return _state.GraphicsState.EarlyZForce;
+        }
+
         /// <inheritdoc/>
         public AttributeType QueryFragmentOutputType(int location)
         {
@@ -276,16 +283,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
         }
 
         /// <inheritdoc/>
-        public bool QueryEarlyZForce()
+        public bool QueryViewportTransformDisable()
         {
-            _state.SpecializationState?.RecordEarlyZForce();
-            return _state.GraphicsState.EarlyZForce;
+            return _state.GraphicsState.ViewportTransformDisable;
         }
 
         /// <inheritdoc/>
-        public bool QueryViewportTransformDisable()
+        public bool QueryYNegateEnabled()
         {
-            return _state.GraphicsState.ViewportTransformDisable;
+            return _state.GraphicsState.YNegateEnabled;
         }
 
         /// <inheritdoc/>
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
index 544e689a..f392491c 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
@@ -97,6 +97,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// </summary>
         public bool DualSourceBlendEnable;
 
+        /// <summary>
+        /// Indicates whether Y negate of the fragment coordinates is enabled.
+        /// </summary>
+        public bool YNegateEnabled;
+
         /// <summary>
         /// Creates a new GPU graphics state.
         /// </summary>
@@ -116,7 +121,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
         /// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
         /// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param>
-        /// <param name="dualSourceBlendEnable">Type of the vertex attributes consumed by the shader</param>
+        /// <param name="dualSourceBlendEnable">Indicates whether dual source blend is enabled</param>
+        /// <param name="yNegateEnabled">Indicates whether Y negate of the fragment coordinates is enabled</param>
         public GpuChannelGraphicsState(
             bool earlyZForce,
             PrimitiveTopology topology,
@@ -134,7 +140,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
             bool hasConstantBufferDrawParameters,
             bool hasUnalignedStorageBuffer,
             ref Array8<AttributeType> fragmentOutputTypes,
-            bool dualSourceBlendEnable)
+            bool dualSourceBlendEnable,
+            bool yNegateEnabled)
         {
             EarlyZForce = earlyZForce;
             Topology = topology;
@@ -153,6 +160,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
             HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
             FragmentOutputTypes = fragmentOutputTypes;
             DualSourceBlendEnable = dualSourceBlendEnable;
+            YNegateEnabled = yNegateEnabled;
         }
     }
 }
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
index 775bfb2a..b33f96c5 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
@@ -540,6 +540,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
                 return false;
             }
 
+            if (graphicsState.YNegateEnabled != GraphicsState.YNegateEnabled)
+            {
+                return false;
+            }
+
             return Matches(channel, ref poolState, checkTextures, isCompute: false);
         }
 
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
index 2370b49f..2a45e23d 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
@@ -188,10 +188,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
                 context.AppendLine();
             }
 
-            if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryEarlyZForce())
+            if (context.Config.Stage == ShaderStage.Fragment)
             {
-                context.AppendLine("layout(early_fragment_tests) in;");
-                context.AppendLine();
+                if (context.Config.GpuAccessor.QueryEarlyZForce())
+                {
+                    context.AppendLine("layout (early_fragment_tests) in;");
+                    context.AppendLine();
+                }
+
+                if (context.Config.Properties.OriginUpperLeft)
+                {
+                    context.AppendLine("layout (origin_upper_left) in vec4 gl_FragCoord;");
+                    context.AppendLine();
+                }
             }
 
             if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighS32) != 0)
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
index c8fcd75a..21797975 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
@@ -251,7 +251,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
                 }
                 else if (context.Config.Stage == ShaderStage.Fragment)
                 {
-                    context.AddExecutionMode(spvFunc, context.Config.Options.TargetApi == TargetApi.Vulkan
+                    context.AddExecutionMode(spvFunc, context.Config.Properties.OriginUpperLeft
                         ? ExecutionMode.OriginUpperLeft
                         : ExecutionMode.OriginLowerLeft);
 
diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs
index 1c2b2809..a47791d3 100644
--- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs
+++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs
@@ -178,6 +178,15 @@ namespace Ryujinx.Graphics.Shader
             return 0;
         }
 
+        /// <summary>
+        /// Queries if host state forces early depth testing.
+        /// </summary>
+        /// <returns>True if early depth testing is forced</returns>
+        bool QueryEarlyZForce()
+        {
+            return false;
+        }
+
         /// <summary>
         /// Queries whenever the current draw has written the base vertex and base instance into Constant Buffer 0.
         /// </summary>
@@ -534,19 +543,19 @@ namespace Ryujinx.Graphics.Shader
         }
 
         /// <summary>
-        /// Queries if host state forces early depth testing.
+        /// Queries if host state disables the viewport transform.
         /// </summary>
-        /// <returns>True if early depth testing is forced</returns>
-        bool QueryEarlyZForce()
+        /// <returns>True if the viewport transform is disabled</returns>
+        bool QueryViewportTransformDisable()
         {
             return false;
         }
 
         /// <summary>
-        /// Queries if host state disables the viewport transform.
+        /// Queries Y negate enable state.
         /// </summary>
-        /// <returns>True if the viewport transform is disabled</returns>
-        bool QueryViewportTransformDisable()
+        /// <returns>True if Y negate of the fragment coordinates is enabled, false otherwise</returns>
+        bool QueryYNegateEnabled()
         {
             return false;
         }
diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs
index 1876847c..c7bd0fd6 100644
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs
@@ -161,6 +161,18 @@ namespace Ryujinx.Graphics.Shader.Instructions
                     // FragCoord X/Y must be divided by the render target scale, if resolution scaling is active,
                     // because the shader code is not expecting scaled values.
                     res = context.FPDivide(res, context.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.RenderScale), Const(0)));
+
+                    if (op.Imm10 == AttributeConsts.PositionY && context.Config.Options.TargetApi != TargetApi.OpenGL)
+                    {
+                        // If YNegate is enabled, we need to flip the fragment coordinates vertically, unless
+                        // the API supports changing the origin (only OpenGL does).
+                        if (context.Config.GpuAccessor.QueryYNegateEnabled())
+                        {
+                            Operand viewportHeight = context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.ViewportSize), Const(1));
+
+                            res = context.FPSubtract(viewportHeight, res);
+                        }
+                    }
                 }
                 else if (op.Imm10 == AttributeConsts.FrontFacing && context.Config.GpuAccessor.QueryHostHasFrontFacingBug())
                 {
diff --git a/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs b/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs
index e87769bb..f9776afc 100644
--- a/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs
+++ b/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs
@@ -13,6 +13,7 @@ namespace Ryujinx.Graphics.Shader
         public ShaderIdentification Identification { get; }
         public int GpLayerInputAttribute { get; }
         public ShaderStage Stage { get; }
+        public bool UsesFragCoord { get; }
         public bool UsesInstanceId { get; }
         public bool UsesDrawParameters { get; }
         public bool UsesRtLayer { get; }
@@ -27,6 +28,7 @@ namespace Ryujinx.Graphics.Shader
             ShaderIdentification identification,
             int gpLayerInputAttribute,
             ShaderStage stage,
+            bool usesFragCoord,
             bool usesInstanceId,
             bool usesDrawParameters,
             bool usesRtLayer,
@@ -41,6 +43,7 @@ namespace Ryujinx.Graphics.Shader
             Identification = identification;
             GpLayerInputAttribute = gpLayerInputAttribute;
             Stage = stage;
+            UsesFragCoord = usesFragCoord;
             UsesInstanceId = usesInstanceId;
             UsesDrawParameters = usesDrawParameters;
             UsesRtLayer = usesRtLayer;
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs
index 048a260a..b7e379c6 100644
--- a/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs
@@ -18,6 +18,8 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
         public IReadOnlyDictionary<int, MemoryDefinition> LocalMemories => _localMemories;
         public IReadOnlyDictionary<int, MemoryDefinition> SharedMemories => _sharedMemories;
 
+        public readonly bool OriginUpperLeft;
+
         public ShaderProperties()
         {
             _constantBuffers = new Dictionary<int, BufferDefinition>();
@@ -28,6 +30,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
             _sharedMemories = new Dictionary<int, MemoryDefinition>();
         }
 
+        public ShaderProperties(bool originUpperLeft) : this()
+        {
+            OriginUpperLeft = originUpperLeft;
+        }
+
         public void AddOrUpdateConstantBuffer(int binding, BufferDefinition definition)
         {
             _constantBuffers[binding] = definition;
diff --git a/src/Ryujinx.Graphics.Shader/SupportBuffer.cs b/src/Ryujinx.Graphics.Shader/SupportBuffer.cs
index 24a99345..0b7a2edd 100644
--- a/src/Ryujinx.Graphics.Shader/SupportBuffer.cs
+++ b/src/Ryujinx.Graphics.Shader/SupportBuffer.cs
@@ -19,6 +19,7 @@ namespace Ryujinx.Graphics.Shader
         FragmentAlphaTest,
         FragmentIsBgra,
         ViewportInverse,
+        ViewportSize,
         FragmentRenderScaleCount,
         RenderScale,
     }
@@ -33,6 +34,7 @@ namespace Ryujinx.Graphics.Shader
         public static readonly int FragmentAlphaTestOffset;
         public static readonly int FragmentIsBgraOffset;
         public static readonly int ViewportInverseOffset;
+        public static readonly int ViewportSizeOffset;
         public static readonly int FragmentRenderScaleCountOffset;
         public static readonly int GraphicsRenderScaleOffset;
         public static readonly int ComputeRenderScaleOffset;
@@ -56,6 +58,7 @@ namespace Ryujinx.Graphics.Shader
             FragmentAlphaTestOffset = OffsetOf(ref instance, ref instance.FragmentAlphaTest);
             FragmentIsBgraOffset = OffsetOf(ref instance, ref instance.FragmentIsBgra);
             ViewportInverseOffset = OffsetOf(ref instance, ref instance.ViewportInverse);
+            ViewportSizeOffset = OffsetOf(ref instance, ref instance.ViewportSize);
             FragmentRenderScaleCountOffset = OffsetOf(ref instance, ref instance.FragmentRenderScaleCount);
             GraphicsRenderScaleOffset = OffsetOf(ref instance, ref instance.RenderScale);
             ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize;
@@ -68,6 +71,7 @@ namespace Ryujinx.Graphics.Shader
                 new StructureField(AggregateType.U32, "s_alpha_test"),
                 new StructureField(AggregateType.Array | AggregateType.U32, "s_is_bgra", FragmentIsBgraCount),
                 new StructureField(AggregateType.Vector4 | AggregateType.FP32, "s_viewport_inverse"),
+                new StructureField(AggregateType.Vector4 | AggregateType.FP32, "s_viewport_size"),
                 new StructureField(AggregateType.S32, "s_frag_scale_count"),
                 new StructureField(AggregateType.Array | AggregateType.FP32, "s_render_scale", RenderScaleMaxCount),
             });
@@ -76,6 +80,7 @@ namespace Ryujinx.Graphics.Shader
         public Vector4<int> FragmentAlphaTest;
         public Array8<Vector4<int>> FragmentIsBgra;
         public Vector4<float> ViewportInverse;
+        public Vector4<float> ViewportSize;
         public Vector4<int> FragmentRenderScaleCount;
 
         // Render scale max count: 1 + 64 + 8. First scale is fragment output scale, others are textures/image inputs.
diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs
index c92d0583..6cb57238 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs
@@ -429,6 +429,11 @@ namespace Ryujinx.Graphics.Shader.Translation
             return context.Add(Instruction.FP32 | Instruction.SquareRoot, Local(), a);
         }
 
+        public static Operand FPSubtract(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32)
+        {
+            return context.Add(fpType | Instruction.Subtract, Local(), a, b);
+        }
+
         public static Operand FPTruncate(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32)
         {
             return context.Add(fpType | Instruction.Truncate, Local(), a);
diff --git a/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
index 5741d028..27b46867 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
@@ -123,7 +123,20 @@ namespace Ryujinx.Graphics.Shader.Translation
             UsedInputAttributesPerPatch = new HashSet<int>();
             UsedOutputAttributesPerPatch = new HashSet<int>();
 
-            ResourceManager = new ResourceManager(stage, gpuAccessor, new ShaderProperties());
+            ShaderProperties properties;
+
+            switch (stage)
+            {
+                case ShaderStage.Fragment:
+                    bool originUpperLeft = options.TargetApi == TargetApi.Vulkan || gpuAccessor.QueryYNegateEnabled();
+                    properties = new ShaderProperties(originUpperLeft);
+                    break;
+                default:
+                    properties = new ShaderProperties();
+                    break;
+            }
+
+            ResourceManager = new ResourceManager(stage, gpuAccessor, properties);
 
             if (!gpuAccessor.QueryHostSupportsTransformFeedback() && gpuAccessor.QueryTransformFeedbackEnabled())
             {
@@ -135,7 +148,7 @@ namespace Ryujinx.Graphics.Shader.Translation
 
                 BufferDefinition tfeInfoBuffer = new(BufferLayout.Std430, 1, Constants.TfeInfoBinding, "tfe_info", tfeInfoStruct);
 
-                Properties.AddOrUpdateStorageBuffer(Constants.TfeInfoBinding, tfeInfoBuffer);
+                properties.AddOrUpdateStorageBuffer(Constants.TfeInfoBinding, tfeInfoBuffer);
 
                 StructureType tfeDataStruct = new(new StructureField[]
                 {
@@ -146,7 +159,7 @@ namespace Ryujinx.Graphics.Shader.Translation
                 {
                     int binding = Constants.TfeBufferBaseBinding + i;
                     BufferDefinition tfeDataBuffer = new(BufferLayout.Std430, 1, binding, $"tfe_data{i}", tfeDataStruct);
-                    Properties.AddOrUpdateStorageBuffer(binding, tfeDataBuffer);
+                    properties.AddOrUpdateStorageBuffer(binding, tfeDataBuffer);
                 }
             }
         }
@@ -615,6 +628,7 @@ namespace Ryujinx.Graphics.Shader.Translation
                 identification,
                 GpLayerInputAttribute,
                 Stage,
+                UsedFeatures.HasFlag(FeatureFlags.FragCoordXY),
                 UsedFeatures.HasFlag(FeatureFlags.InstanceId),
                 UsedFeatures.HasFlag(FeatureFlags.DrawParameters),
                 UsedFeatures.HasFlag(FeatureFlags.RtLayer),
-- 
cgit v1.2.3-70-g09d2