From 43b4b34376cdea486906f8bb4058dda3be7e1bd8 Mon Sep 17 00:00:00 2001
From: riperiperi <rhy3756547@hotmail.com>
Date: Thu, 12 May 2022 14:47:13 +0100
Subject: Implement Viewport Transform Disable (#3328)

* Initial implementation (no specialization)

* Use specialization

* Fix render scale, increase code gen version

* Revert accidental change

* Address Feedback
---
 Ryujinx.Graphics.GAL/IPipeline.cs                  |  2 +-
 .../Multithreading/Commands/SetViewportsCommand.cs |  6 ++-
 .../Multithreading/ThreadedPipeline.cs             |  4 +-
 Ryujinx.Graphics.GAL/SupportBufferUpdater.cs       |  7 ++++
 Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs | 23 ++++++++--
 Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs     |  3 +-
 .../Shader/DiskCache/DiskCacheGpuAccessor.cs       |  6 +++
 .../Shader/DiskCache/DiskCacheHostStorage.cs       |  2 +-
 Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs         |  6 +++
 .../Shader/GpuChannelGraphicsState.cs              |  9 +++-
 Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs         |  8 ++--
 .../Shader/ShaderCacheHashTable.cs                 |  4 +-
 .../Shader/ShaderSpecializationList.cs             |  9 +++-
 .../Shader/ShaderSpecializationState.cs            |  8 +++-
 Ryujinx.Graphics.OpenGL/Pipeline.cs                | 15 ++++++-
 .../CodeGen/Glsl/Declarations.cs                   |  9 ++--
 .../CodeGen/Glsl/DefaultNames.cs                   |  1 +
 .../CodeGen/Glsl/OperandManager.cs                 |  5 ++-
 Ryujinx.Graphics.Shader/IGpuAccessor.cs            |  9 ++++
 .../Instructions/InstEmitAttribute.cs              | 28 ++++++++++++-
 Ryujinx.Graphics.Shader/SupportBuffer.cs           |  3 ++
 .../Translation/AttributeConsts.cs                 |  3 ++
 .../Translation/EmitterContext.cs                  | 49 +++++++++++++++++++++-
 .../Translation/ShaderConfig.cs                    |  7 ++++
 24 files changed, 200 insertions(+), 26 deletions(-)

diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs
index 75c3077e..aec096e7 100644
--- a/Ryujinx.Graphics.GAL/IPipeline.cs
+++ b/Ryujinx.Graphics.GAL/IPipeline.cs
@@ -94,7 +94,7 @@ namespace Ryujinx.Graphics.GAL
         void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs);
         void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers);
 
-        void SetViewports(int first, ReadOnlySpan<Viewport> viewports);
+        void SetViewports(int first, ReadOnlySpan<Viewport> viewports, bool disableTransform);
 
         void TextureBarrier();
         void TextureBarrierTiled();
diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs
index e11b00e8..b208d9fe 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs
@@ -9,17 +9,19 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
         public CommandType CommandType => CommandType.SetViewports;
         private int _first;
         private SpanRef<Viewport> _viewports;
+        private bool _disableTransform;
 
-        public void Set(int first, SpanRef<Viewport> viewports)
+        public void Set(int first, SpanRef<Viewport> viewports, bool disableTransform)
         {
             _first = first;
             _viewports = viewports;
+            _disableTransform = disableTransform;
         }
 
         public static void Run(ref SetViewportsCommand command, ThreadedRenderer threaded, IRenderer renderer)
         {
             ReadOnlySpan<Viewport> viewports = command._viewports.Get(threaded);
-            renderer.Pipeline.SetViewports(command._first, viewports);
+            renderer.Pipeline.SetViewports(command._first, viewports, command._disableTransform);
             command._viewports.Dispose(threaded);
         }
     }
diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
index b6acfaa8..010ee7e6 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
@@ -304,9 +304,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
             _renderer.QueueCommand();
         }
 
-        public void SetViewports(int first, ReadOnlySpan<Viewport> viewports)
+        public void SetViewports(int first, ReadOnlySpan<Viewport> viewports, bool disableTransform)
         {
-            _renderer.New<SetViewportsCommand>().Set(first, _renderer.CopySpan(viewports));
+            _renderer.New<SetViewportsCommand>().Set(first, _renderer.CopySpan(viewports), disableTransform);
             _renderer.QueueCommand();
         }
 
diff --git a/Ryujinx.Graphics.GAL/SupportBufferUpdater.cs b/Ryujinx.Graphics.GAL/SupportBufferUpdater.cs
index cb24bdd7..da7a2461 100644
--- a/Ryujinx.Graphics.GAL/SupportBufferUpdater.cs
+++ b/Ryujinx.Graphics.GAL/SupportBufferUpdater.cs
@@ -72,6 +72,13 @@ namespace Ryujinx.Graphics.GAL
             UpdateGenericField(SupportBuffer.FragmentIsBgraOffset, data, Data.FragmentIsBgra.ToSpan(), offset, count);
         }
 
+        public void UpdateViewportInverse(Vector4<float> data)
+        {
+            Data.ViewportInverse = data;
+
+            MarkDirty(SupportBuffer.ViewportInverseOffset, SupportBuffer.FieldSize);
+        }
+
         public void Commit()
         {
             if (_startOffset != -1)
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
index 3bc15a31..d0c3bc5a 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
@@ -113,7 +113,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                     nameof(ThreedClassState.DepthMode),
                     nameof(ThreedClassState.ViewportTransform),
                     nameof(ThreedClassState.ViewportExtents),
-                    nameof(ThreedClassState.YControl)),
+                    nameof(ThreedClassState.YControl),
+                    nameof(ThreedClassState.ViewportTransformEnable)),
 
                 new StateUpdateCallbackEntry(UpdatePolygonMode,
                     nameof(ThreedClassState.PolygonModeFront),
@@ -200,7 +201,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
             // of the shader for the new state.
             if (_shaderSpecState != null)
             {
-                if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState()))
+                if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState()))
                 {
                     ForceShaderUpdate();
                 }
@@ -568,6 +569,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
             var yControl = _state.State.YControl;
             var face = _state.State.FaceState;
 
+            bool disableTransform = _state.State.ViewportTransformEnable == 0;
+
             UpdateFrontFace(yControl, face.FrontFace);
             UpdateDepthMode();
 
@@ -577,6 +580,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
 
             for (int index = 0; index < Constants.TotalViewports; index++)
             {
+                if (disableTransform)
+                {
+                    ref var scissor = ref _state.State.ScreenScissorState;
+
+                    float rScale = _channel.TextureManager.RenderTargetScale;
+                    var scissorRect = new RectangleF(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale);
+
+                    viewports[index] = new Viewport(scissorRect, ViewportSwizzle.PositiveX, ViewportSwizzle.PositiveY, ViewportSwizzle.PositiveZ, ViewportSwizzle.PositiveW, 0, 1);
+                    continue;
+                }
+
                 ref var transform = ref _state.State.ViewportTransform[index];
                 ref var extents = ref _state.State.ViewportExtents[index];
 
@@ -628,7 +642,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                 viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar);
             }
 
-            _context.Renderer.Pipeline.SetViewports(0, viewports);
+            _context.Renderer.Pipeline.SetViewports(0, viewports, disableTransform);
         }
 
         /// <summary>
@@ -1194,7 +1208,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
             return new GpuChannelGraphicsState(
                 _state.State.EarlyZForce,
                 _drawState.Topology,
-                _state.State.TessMode);
+                _state.State.TessMode,
+                _state.State.ViewportTransformEnable == 0);
         }
 
         /// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs
index 27fac8f3..4de6eff9 100644
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs
@@ -166,7 +166,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
                         GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState(
                             accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce),
                             topology,
-                            tessMode);
+                            tessMode,
+                            false);
 
                         TransformFeedbackDescriptor[] tfdNew = null;
 
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
index b1c04eac..bc63f714 100644
--- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
@@ -185,6 +185,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
             return _oldSpecState.GraphicsState.EarlyZForce;
         }
 
+        /// <inheritdoc/>
+        public bool QueryViewportTransformDisable()
+        {
+            return _oldSpecState.GraphicsState.ViewportTransformDisable;
+        }
+
         /// <inheritdoc/>
         public void RegisterTexture(int handle, int cbufSlot)
         {
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
index 0028e879..5d99957f 100644
--- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
@@ -21,7 +21,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
         private const ushort FileFormatVersionMajor = 1;
         private const ushort FileFormatVersionMinor = 1;
         private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
-        private const uint CodeGenVersion = 0;
+        private const uint CodeGenVersion = 1;
 
         private const string SharedTocFileName = "shared.toc";
         private const string SharedDataFileName = "shared.data";
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
index 192467b7..5cd966af 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
@@ -217,6 +217,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
             return _state.GraphicsState.EarlyZForce;
         }
 
+        /// <inheritdoc/>
+        public bool QueryViewportTransformDisable()
+        {
+            return _state.GraphicsState.ViewportTransformDisable;
+        }
+
         /// <inheritdoc/>
         public void RegisterTexture(int handle, int cbufSlot)
         {
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
index 5eb31db6..92ec117f 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
@@ -25,17 +25,24 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// </summary>
         public readonly TessMode TessellationMode;
 
+        /// <summary>
+        /// Indicates whenever the viewport transform is disabled.
+        /// </summary>
+        public readonly bool ViewportTransformDisable;
+
         /// <summary>
         /// Creates a new GPU graphics state.
         /// </summary>
         /// <param name="earlyZForce">Early Z force enable</param>
         /// <param name="topology">Primitive topology</param>
         /// <param name="tessellationMode">Tessellation mode</param>
-        public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode)
+        /// <param name="viewportTransformDisable">Indicates whenever the viewport transform is disabled</param>
+        public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode, bool viewportTransformDisable)
         {
             EarlyZForce = earlyZForce;
             Topology = topology;
             TessellationMode = tessellationMode;
+            ViewportTransformDisable = viewportTransformDisable;
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index 03d5ecad..df4b9d12 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -249,12 +249,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
             GpuChannelGraphicsState graphicsState,
             ShaderAddresses addresses)
         {
-            if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, poolState, gpShaders, addresses))
+            if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, poolState, graphicsState, gpShaders, addresses))
             {
                 return gpShaders;
             }
 
-            if (_graphicsShaderCache.TryFind(channel, poolState, addresses, out gpShaders, out var cachedGuestCode))
+            if (_graphicsShaderCache.TryFind(channel, poolState, graphicsState, addresses, out gpShaders, out var cachedGuestCode))
             {
                 _gpPrograms[addresses] = gpShaders;
                 return gpShaders;
@@ -429,12 +429,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// </summary>
         /// <param name="channel">GPU channel using the shader</param>
         /// <param name="poolState">GPU channel state to verify shader compatibility</param>
+        /// <param name="graphicsState">GPU channel graphics state to verify shader compatibility</param>
         /// <param name="gpShaders">Cached graphics shaders</param>
         /// <param name="addresses">GPU virtual addresses of all enabled shader stages</param>
         /// <returns>True if the code is different, false otherwise</returns>
         private static bool IsShaderEqual(
             GpuChannel channel,
             GpuChannelPoolState poolState,
+            GpuChannelGraphicsState graphicsState,
             CachedShaderProgram gpShaders,
             ShaderAddresses addresses)
         {
@@ -452,7 +454,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
                 }
             }
 
-            return gpShaders.SpecializationState.MatchesGraphics(channel, poolState);
+            return gpShaders.SpecializationState.MatchesGraphics(channel, poolState, graphicsState);
         }
 
         /// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs
index 065f9ba9..3d74e53a 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs
@@ -208,6 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// </remarks>
         /// <param name="channel">GPU channel</param>
         /// <param name="poolState">Texture pool state</param>
+        /// <param name="graphicsState">Graphics state</param>
         /// <param name="addresses">Guest addresses of the shaders to find</param>
         /// <param name="program">Cached host program for the given state, if found</param>
         /// <param name="guestCode">Cached guest code, if any found</param>
@@ -215,6 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         public bool TryFind(
             GpuChannel channel,
             GpuChannelPoolState poolState,
+            GpuChannelGraphicsState graphicsState,
             ShaderAddresses addresses,
             out CachedShaderProgram program,
             out CachedGraphicsGuestCode guestCode)
@@ -234,7 +236,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
 
             if (found && _shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList))
             {
-                return specList.TryFindForGraphics(channel, poolState, out program);
+                return specList.TryFindForGraphics(channel, poolState, graphicsState, out program);
             }
 
             return false;
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs
index 87e08754..e3e57d74 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs
@@ -24,13 +24,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// </summary>
         /// <param name="channel">GPU channel</param>
         /// <param name="poolState">Texture pool state</param>
+        /// <param name="graphicsState">Graphics state</param>
         /// <param name="program">Cached program, if found</param>
         /// <returns>True if a compatible program is found, false otherwise</returns>
-        public bool TryFindForGraphics(GpuChannel channel, GpuChannelPoolState poolState, out CachedShaderProgram program)
+        public bool TryFindForGraphics(
+            GpuChannel channel,
+            GpuChannelPoolState poolState,
+            GpuChannelGraphicsState graphicsState,
+            out CachedShaderProgram program)
         {
             foreach (var entry in _entries)
             {
-                if (entry.SpecializationState.MatchesGraphics(channel, poolState))
+                if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState))
                 {
                     program = entry;
                     return true;
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
index 2bbc3d2c..418c7b1a 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
@@ -395,9 +395,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// </summary>
         /// <param name="channel">GPU channel</param>
         /// <param name="poolState">Texture pool state</param>
+        /// <param name="graphicsState">Graphics state</param>
         /// <returns>True if the state matches, false otherwise</returns>
-        public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState)
+        public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState)
         {
+            if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable)
+            {
+                return false;
+            }
+
             return Matches(channel, poolState, isCompute: false);
         }
 
diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs
index 114fa685..0326f980 100644
--- a/Ryujinx.Graphics.OpenGL/Pipeline.cs
+++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -1266,7 +1266,7 @@ namespace Ryujinx.Graphics.OpenGL
             _vertexArray.SetVertexBuffers(vertexBuffers);
         }
 
-        public void SetViewports(int first, ReadOnlySpan<Viewport> viewports)
+        public void SetViewports(int first, ReadOnlySpan<Viewport> viewports, bool disableTransform)
         {
             Array.Resize(ref _viewportArray, viewports.Length * 4);
             Array.Resize(ref _depthRangeArray, viewports.Length * 2);
@@ -1305,6 +1305,19 @@ namespace Ryujinx.Graphics.OpenGL
 
             GL.ViewportArray(first, viewports.Length, viewportArray);
             GL.DepthRangeArray(first, viewports.Length, depthRangeArray);
+
+            float disableTransformF = disableTransform ? 1.0f : 0.0f;
+            if (_supportBuffer.Data.ViewportInverse.W != disableTransformF || disableTransform)
+            {
+                float scale = _renderScale[0].X;
+                _supportBuffer.UpdateViewportInverse(new Vector4<float>
+                {
+                    X = scale * 2f / viewports[first].Region.Width,
+                    Y = scale * 2f / viewports[first].Region.Height,
+                    Z = 1,
+                    W = disableTransformF
+                });
+            }
         }
 
         public void TextureBarrier()
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
index c955a616..59a7ccdc 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
@@ -249,7 +249,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
                         context.AppendLine();
                     }
                 }
-                else if (isFragment)
+                else if (isFragment || context.Config.Stage == ShaderStage.Vertex)
                 {
                     DeclareSupportUniformBlock(context, context.Config.Stage, 0);
                 }
@@ -615,8 +615,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
 
         private static void DeclareSupportUniformBlock(CodeGenContext context, ShaderStage stage, int scaleElements)
         {
-            bool isFragment = stage == ShaderStage.Fragment;
-            if (!isFragment && scaleElements == 0)
+            bool needsSupportBlock = stage == ShaderStage.Fragment || 
+                (context.Config.LastInVertexPipeline && context.Config.GpuAccessor.QueryViewportTransformDisable());
+
+            if (!needsSupportBlock && scaleElements == 0)
             {
                 return;
             }
@@ -630,6 +632,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
                 case ShaderStage.Vertex:
                     context.AppendLine($"uint {DefaultNames.SupportBlockAlphaTestName};");
                     context.AppendLine($"bool {DefaultNames.SupportBlockIsBgraName}[{SupportBuffer.FragmentIsBgraCount}];");
+                    context.AppendLine($"vec4 {DefaultNames.SupportBlockViewportInverse};");
                     context.AppendLine($"int {DefaultNames.SupportBlockFragmentScaleCount};");
                     break;
                 case ShaderStage.Compute:
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs
index 76203522..3ab4814c 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs
@@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
         public const string SupportBlockName = "support_block";
         public const string SupportBlockAlphaTestName = "s_alpha_test";
         public const string SupportBlockIsBgraName = "s_is_bgra";
+        public const string SupportBlockViewportInverse = "s_viewport_inverse";
         public const string SupportBlockFragmentScaleCount = "s_frag_scale_count";
         public const string SupportBlockRenderScaleName = "s_render_scale";
 
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
index 2d6607ad..334c744d 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
@@ -84,7 +84,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             { AttributeConsts.FragmentOutputIsBgraBase + 16, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[4]",  VariableType.Bool) },
             { AttributeConsts.FragmentOutputIsBgraBase + 20, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[5]",  VariableType.Bool) },
             { AttributeConsts.FragmentOutputIsBgraBase + 24, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[6]",  VariableType.Bool) },
-            { AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[7]",  VariableType.Bool) }
+            { AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[7]",  VariableType.Bool) },
+
+            { AttributeConsts.SupportBlockViewInverseX,  new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.x",  VariableType.F32) },
+            { AttributeConsts.SupportBlockViewInverseY,  new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.y",  VariableType.F32) }
         };
 
         private Dictionary<AstOperand, string> _locals;
diff --git a/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/Ryujinx.Graphics.Shader/IGpuAccessor.cs
index 9c624d90..180fc187 100644
--- a/Ryujinx.Graphics.Shader/IGpuAccessor.cs
+++ b/Ryujinx.Graphics.Shader/IGpuAccessor.cs
@@ -329,6 +329,15 @@ namespace Ryujinx.Graphics.Shader
             return false;
         }
 
+        /// <summary>
+        /// Queries if host state disables the viewport transform.
+        /// </summary>
+        /// <returns>True if the viewport transform is disabled</returns>
+        bool QueryViewportTransformDisable()
+        {
+            return false;
+        }
+
         /// <summary>
         /// Registers a texture used by the shader.
         /// </summary>
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs
index 1cdb3842..6ce2e537 100644
--- a/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs
@@ -206,7 +206,33 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
             if (emit)
             {
-                context.EmitVertex();
+                if (context.Config.LastInVertexPipeline)
+                {
+                    context.PrepareForVertexReturn(out var tempXLocal, out var tempYLocal, out var tempZLocal);
+
+                    context.EmitVertex();
+
+                    // Restore output position value before transformation.
+
+                    if (tempXLocal != null)
+                    {
+                        context.Copy(Attribute(AttributeConsts.PositionX), tempXLocal);
+                    }
+
+                    if (tempYLocal != null)
+                    {
+                        context.Copy(Attribute(AttributeConsts.PositionY), tempYLocal);
+                    }
+
+                    if (tempZLocal != null)
+                    {
+                        context.Copy(Attribute(AttributeConsts.PositionZ), tempZLocal);
+                    }
+                }
+                else
+                {
+                    context.EmitVertex();
+                }
             }
 
             if (cut)
diff --git a/Ryujinx.Graphics.Shader/SupportBuffer.cs b/Ryujinx.Graphics.Shader/SupportBuffer.cs
index 47a47ea6..28a48c2a 100644
--- a/Ryujinx.Graphics.Shader/SupportBuffer.cs
+++ b/Ryujinx.Graphics.Shader/SupportBuffer.cs
@@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader
 
         public static int FragmentAlphaTestOffset;
         public static int FragmentIsBgraOffset;
+        public static int ViewportInverseOffset;
         public static int FragmentRenderScaleCountOffset;
         public static int GraphicsRenderScaleOffset;
         public static int ComputeRenderScaleOffset;
@@ -40,6 +41,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);
             FragmentRenderScaleCountOffset = OffsetOf(ref instance, ref instance.FragmentRenderScaleCount);
             GraphicsRenderScaleOffset = OffsetOf(ref instance, ref instance.RenderScale);
             ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize;
@@ -47,6 +49,7 @@ namespace Ryujinx.Graphics.Shader
 
         public Vector4<int> FragmentAlphaTest;
         public Array8<Vector4<int>> FragmentIsBgra;
+        public Vector4<float> ViewportInverse;
         public Vector4<int> FragmentRenderScaleCount;
 
         // Render scale max count: 1 + 32 + 8. First scale is fragment output scale, others are textures/image inputs.
diff --git a/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs
index 370af009..ada60ab9 100644
--- a/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs
+++ b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs
@@ -67,6 +67,9 @@ namespace Ryujinx.Graphics.Shader.Translation
         public const int FragmentOutputIsBgraBase = 0x1000100;
         public const int FragmentOutputIsBgraEnd  = FragmentOutputIsBgraBase + 8 * 4;
 
+        public const int SupportBlockViewInverseX = 0x1000200;
+        public const int SupportBlockViewInverseY = 0x1000204;
+
         public const int ThreadIdX = 0x2000000;
         public const int ThreadIdY = 0x2000004;
         public const int ThreadIdZ = 0x2000008;
diff --git a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
index 775f1217..ba3b551d 100644
--- a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
+++ b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
@@ -154,9 +154,56 @@ namespace Ryujinx.Graphics.Shader.Translation
             return label;
         }
 
+        public void PrepareForVertexReturn()
+        {
+            if (Config.GpuAccessor.QueryViewportTransformDisable())
+            {
+                Operand x = Attribute(AttributeConsts.PositionX | AttributeConsts.LoadOutputMask);
+                Operand y = Attribute(AttributeConsts.PositionY | AttributeConsts.LoadOutputMask);
+                Operand xScale = Attribute(AttributeConsts.SupportBlockViewInverseX);
+                Operand yScale = Attribute(AttributeConsts.SupportBlockViewInverseY);
+                Operand negativeOne = ConstF(-1.0f);
+
+                this.Copy(Attribute(AttributeConsts.PositionX), this.FPFusedMultiplyAdd(x, xScale, negativeOne));
+                this.Copy(Attribute(AttributeConsts.PositionY), this.FPFusedMultiplyAdd(y, yScale, negativeOne));
+            }
+        }
+
+        public void PrepareForVertexReturn(out Operand oldXLocal, out Operand oldYLocal, out Operand oldZLocal)
+        {
+            if (Config.GpuAccessor.QueryViewportTransformDisable())
+            {
+                oldXLocal = Local();
+                this.Copy(oldXLocal, Attribute(AttributeConsts.PositionX | AttributeConsts.LoadOutputMask));
+                oldYLocal = Local();
+                this.Copy(oldYLocal, Attribute(AttributeConsts.PositionY | AttributeConsts.LoadOutputMask));
+            }
+            else
+            {
+                oldXLocal = null;
+                oldYLocal = null;
+            }
+
+            // Will be used by Vulkan backend for depth mode emulation.
+            oldZLocal = null;
+
+            PrepareForVertexReturn();
+        }
+
         public void PrepareForReturn()
         {
-            if (!IsNonMain && Config.Stage == ShaderStage.Fragment)
+            if (IsNonMain)
+            {
+                return;
+            }
+
+            if (Config.LastInVertexPipeline &&
+                (Config.Stage == ShaderStage.Vertex || Config.Stage == ShaderStage.TessellationEvaluation) &&
+                (Config.Options.Flags & TranslationFlags.VertexA) == 0)
+            {
+                PrepareForVertexReturn();
+            }
+            else if (Config.Stage == ShaderStage.Fragment)
             {
                 if (Config.OmapDepth)
                 {
diff --git a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
index 23b8b951..27d72cd5 100644
--- a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
+++ b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
@@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Shader.Translation
         public ShaderStage Stage { get; }
 
         public bool GpPassthrough { get; }
+        public bool LastInVertexPipeline { get; private set; }
 
         public int ThreadsPerInputPrimitive { get; }
 
@@ -135,6 +136,7 @@ namespace Ryujinx.Graphics.Shader.Translation
             OmapSampleMask           = header.OmapSampleMask;
             OmapDepth                = header.OmapDepth;
             TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled();
+            LastInVertexPipeline     = header.Stage < ShaderStage.Fragment;
         }
 
         public int GetDepthRegister()
@@ -274,6 +276,11 @@ namespace Ryujinx.Graphics.Shader.Translation
             NextInputAttributesPerPatchComponents = config.ThisInputAttributesPerPatchComponents;
             NextUsesFixedFuncAttributes = config.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr);
             MergeOutputUserAttributes(config.UsedInputAttributes, config.UsedInputAttributesPerPatch);
+
+            if (config.Stage != ShaderStage.Fragment)
+            {
+                LastInVertexPipeline = false;
+            }
         }
 
         public void MergeOutputUserAttributes(int mask, int maskPerPatch)
-- 
cgit v1.2.3-70-g09d2