From 9d7a142a48a5f804127fcae2265bb6ec5495d178 Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Mon, 16 Dec 2019 01:59:46 -0300
Subject: [PATCH] Support texture rectangle targets (non-normalized coords)

---
 Ryujinx.Graphics.Gpu/Engine/Compute.cs        |   2 +-
 Ryujinx.Graphics.Gpu/Engine/Methods.cs        |   6 +-
 .../Image/TextureBindingsManager.cs           |  15 ++
 .../Image/TextureDescriptor.cs                |   5 +
 Ryujinx.Graphics.Gpu/Image/TextureManager.cs  |   5 +
 Ryujinx.Graphics.Gpu/Image/TexturePool.cs     |  15 +-
 Ryujinx.Graphics.Gpu/Image/TextureTarget.cs   |   4 +-
 Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs    | 181 +++++++++------
 .../CodeGen/Glsl/Declarations.cs              |  42 +---
 Ryujinx.Graphics.Shader/DefineNames.cs        |   8 -
 Ryujinx.Graphics.Shader/InputTopology.cs      |  28 +++
 Ryujinx.Graphics.Shader/OutputTopology.cs     |  15 ++
 Ryujinx.Graphics.Shader/QueryInfoCallback.cs  |   4 +
 Ryujinx.Graphics.Shader/QueryInfoName.cs      |  17 ++
 Ryujinx.Graphics.Shader/ShaderCapabilities.cs |  27 ---
 Ryujinx.Graphics.Shader/ShaderConfig.cs       | 105 +++++++--
 Ryujinx.Graphics.Shader/ShaderHeader.cs       |  22 --
 .../Translation/EmitterContext.cs             |  32 +--
 .../Translation/Lowering.cs                   | 210 +++++++++++-------
 .../Optimizations/GlobalToStorage.cs          |   2 +-
 .../Translation/TranslationFlags.cs           |   6 +-
 .../Translation/Translator.cs                 |  76 ++-----
 Ryujinx.ShaderTools/Program.cs                |   2 +-
 23 files changed, 473 insertions(+), 356 deletions(-)
 create mode 100644 Ryujinx.Graphics.Shader/InputTopology.cs
 create mode 100644 Ryujinx.Graphics.Shader/QueryInfoCallback.cs
 create mode 100644 Ryujinx.Graphics.Shader/QueryInfoName.cs
 delete mode 100644 Ryujinx.Graphics.Shader/ShaderCapabilities.cs

diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute.cs b/Ryujinx.Graphics.Gpu/Engine/Compute.cs
index 46857b33ca..bbf1eeaae7 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Compute.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Compute.cs
@@ -21,7 +21,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
             ulong shaderGpuVa = shaderBaseAddress.Pack() + (uint)dispatchParams.ShaderOffset;
 
             // Note: A size of 0 is also invalid, the size must be at least 1.
-            int sharedMemorySize = Math.Clamp(dispatchParams.SharedMemorySize & 0xffff, 4, _context.Capabilities.MaximumComputeSharedMemorySize);
+            int sharedMemorySize = Math.Clamp(dispatchParams.SharedMemorySize & 0xffff, 1, _context.Capabilities.MaximumComputeSharedMemorySize);
 
             ComputeShader cs = _shaderCache.GetComputeShader(
                 shaderGpuVa,
diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
index 0fa3514f07..76b407cc2f 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
@@ -671,9 +671,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
                 addressesArray[index] = baseAddress + shader.Offset;
             }
 
-            bool viewportTransformEnable = GetViewportTransformEnable(state);
-
-            GraphicsShader gs = _shaderCache.GetGraphicsShader(addresses, !viewportTransformEnable);
+            GraphicsShader gs = _shaderCache.GetGraphicsShader(state, addresses);
 
             _vsUsesInstanceId = gs.Shader[0].Program.Info.UsesInstanceId;
 
@@ -734,7 +732,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
             _context.Renderer.Pipeline.BindProgram(gs.HostProgram);
         }
 
-        private bool GetViewportTransformEnable(GpuState state)
+        public bool GetViewportTransformEnable(GpuState state)
         {
             // FIXME: We should read ViewportTransformEnable, but it seems that some games writes 0 there?
             // return state.Get<Boolean32>(MethodOffset.ViewportTransformEnable) != 0;
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
index 63a42709ef..ce58e5c54a 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
@@ -199,6 +199,21 @@ namespace Ryujinx.Graphics.Gpu.Image
             }
         }
 
+        public TextureDescriptor GetTextureDescriptor(GpuState state, int stageIndex, int handle)
+        {
+            int packedId = ReadPackedId(stageIndex, handle);
+
+            int textureId = UnpackTextureId(packedId);
+
+            var poolState = state.Get<PoolState>(MethodOffset.TexturePoolState);
+
+            ulong poolAddress = _context.MemoryManager.Translate(poolState.Address.Pack());
+
+            TexturePool texturePool = _texturePoolCache.FindOrCreate(poolAddress, poolState.MaximumId);
+
+            return texturePool.GetDescriptor(textureId);
+        }
+
         private int ReadPackedId(int stage, int wordOffset)
         {
             ulong address;
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
index 79e4f55eaf..6d1f0fb18d 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
@@ -101,6 +101,11 @@ namespace Ryujinx.Graphics.Gpu.Image
             return (int)((Word5 >> 16) & 0x3fff) + 1;
         }
 
+        public bool UnpackTextureCoordNormalized()
+        {
+            return (Word5 & (1 << 31)) != 0;
+        }
+
         public int UnpackBaseLevel()
         {
             return (int)(Word7 & 0xf);
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index ce0cc249ff..73067249ae 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -129,6 +129,11 @@ namespace Ryujinx.Graphics.Gpu.Image
             UpdateRenderTargets();
         }
 
+        public TextureDescriptor GetGraphicsTextureDescriptor(GpuState state, int stageIndex, int handle)
+        {
+            return _gpBindingsManager.GetTextureDescriptor(state, stageIndex, handle);
+        }
+
         private void UpdateRenderTargets()
         {
             bool anyChanged = false;
diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
index 6014f07cd6..47fa01b6a3 100644
--- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -36,11 +36,7 @@ namespace Ryujinx.Graphics.Gpu.Image
 
             if (texture == null)
             {
-                ulong address = Address + (ulong)(uint)id * DescriptorSize;
-
-                Span<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);
-
-                TextureDescriptor descriptor = MemoryMarshal.Cast<byte, TextureDescriptor>(data)[0];
+                TextureDescriptor descriptor = GetDescriptor(id);
 
                 TextureInfo info = GetInfo(descriptor);
 
@@ -66,6 +62,15 @@ namespace Ryujinx.Graphics.Gpu.Image
             return texture;
         }
 
+        public TextureDescriptor GetDescriptor(int id)
+        {
+            ulong address = Address + (ulong)(uint)id * DescriptorSize;
+
+            Span<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);
+
+            return MemoryMarshal.Cast<byte, TextureDescriptor>(data)[0];
+        }
+
         protected override void InvalidateRangeImpl(ulong address, ulong size)
         {
             ulong endAddress = address + size;
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs b/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs
index 8f5139034f..d42d1e774e 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs
@@ -11,7 +11,7 @@ namespace Ryujinx.Graphics.Gpu.Image
         Texture1DArray,
         Texture2DArray,
         TextureBuffer,
-        Texture2DLinear,
+        Texture2DRect,
         CubemapArray
     }
 
@@ -33,7 +33,7 @@ namespace Ryujinx.Graphics.Gpu.Image
                 {
                     case TextureTarget.Texture1D:       return Target.Texture1D;
                     case TextureTarget.Texture2D:       return Target.Texture2D;
-                    case TextureTarget.Texture2DLinear: return Target.Texture2D;
+                    case TextureTarget.Texture2DRect:   return Target.Texture2D;
                     case TextureTarget.Texture3D:       return Target.Texture3D;
                     case TextureTarget.Texture1DArray:  return Target.Texture1DArray;
                     case TextureTarget.Texture2DArray:  return Target.Texture2DArray;
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index 648e073c77..422cc59727 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -1,10 +1,10 @@
 using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Image;
 using Ryujinx.Graphics.Gpu.State;
 using Ryujinx.Graphics.Shader;
 using Ryujinx.Graphics.Shader.Translation;
 using System;
 using System.Collections.Generic;
-using System.Globalization;
 using System.Runtime.InteropServices;
 
 namespace Ryujinx.Graphics.Gpu.Shader
@@ -13,6 +13,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
     {
         private const int MaxProgramSize = 0x100000;
 
+        private const TranslationFlags DefaultFlags = TranslationFlags.DebugMode;
+
         private GpuContext _context;
 
         private ShaderDumper _dumper;
@@ -69,7 +71,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
             return cpShader;
         }
 
-        public GraphicsShader GetGraphicsShader(ShaderAddresses addresses, bool dividePosXY)
+        public GraphicsShader GetGraphicsShader(GpuState state, ShaderAddresses addresses)
         {
             bool isCached = _gpPrograms.TryGetValue(addresses, out List<GraphicsShader> list);
 
@@ -86,28 +88,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
 
             GraphicsShader gpShaders = new GraphicsShader();
 
-            TranslationFlags flags =
-                TranslationFlags.DebugMode |
-                TranslationFlags.Unspecialized;
-
-            if (dividePosXY)
-            {
-                flags |= TranslationFlags.DividePosXY;
-            }
-
             if (addresses.VertexA != 0)
             {
-                gpShaders.Shader[0] = TranslateGraphicsShader(flags, addresses.Vertex, addresses.VertexA);
+                gpShaders.Shader[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex, addresses.VertexA);
             }
             else
             {
-                gpShaders.Shader[0] = TranslateGraphicsShader(flags, addresses.Vertex);
+                gpShaders.Shader[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex);
             }
 
-            gpShaders.Shader[1] = TranslateGraphicsShader(flags, addresses.TessControl);
-            gpShaders.Shader[2] = TranslateGraphicsShader(flags, addresses.TessEvaluation);
-            gpShaders.Shader[3] = TranslateGraphicsShader(flags, addresses.Geometry);
-            gpShaders.Shader[4] = TranslateGraphicsShader(flags, addresses.Fragment);
+            gpShaders.Shader[1] = TranslateGraphicsShader(state, ShaderStage.TessellationControl,    addresses.TessControl);
+            gpShaders.Shader[2] = TranslateGraphicsShader(state, ShaderStage.TessellationEvaluation, addresses.TessEvaluation);
+            gpShaders.Shader[3] = TranslateGraphicsShader(state, ShaderStage.Geometry,               addresses.Geometry);
+            gpShaders.Shader[4] = TranslateGraphicsShader(state, ShaderStage.Fragment,               addresses.Fragment);
 
             BackpropQualifiers(gpShaders);
 
@@ -199,25 +192,31 @@ namespace Ryujinx.Graphics.Gpu.Shader
                 return null;
             }
 
-            ShaderProgram program;
+            QueryInfoCallback queryInfo = (QueryInfoName info, int index) =>
+            {
+                switch (info)
+                {
+                    case QueryInfoName.ComputeLocalSizeX:
+                        return localSizeX;
+                    case QueryInfoName.ComputeLocalSizeY:
+                        return localSizeY;
+                    case QueryInfoName.ComputeLocalSizeZ:
+                        return localSizeZ;
+                    case QueryInfoName.ComputeSharedMemorySize:
+                        return sharedMemorySize;
+                }
 
-            const TranslationFlags flags =
-                TranslationFlags.Compute   |
-                TranslationFlags.DebugMode |
-                TranslationFlags.Unspecialized;
+                return QueryInfoCommon(info);
+            };
+
+            ShaderProgram program;
 
             Span<byte> code = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize);
 
-            program = Translator.Translate(code, GetShaderCapabilities(), flags);
+            program = Translator.Translate(code, queryInfo, DefaultFlags | TranslationFlags.Compute);
 
             int[] codeCached = MemoryMarshal.Cast<byte, int>(code.Slice(0, program.Size)).ToArray();
 
-            program.Replace(DefineNames.SharedMemorySize, (sharedMemorySize / 4).ToString(CultureInfo.InvariantCulture));
-
-            program.Replace(DefineNames.LocalSizeX, localSizeX.ToString(CultureInfo.InvariantCulture));
-            program.Replace(DefineNames.LocalSizeY, localSizeY.ToString(CultureInfo.InvariantCulture));
-            program.Replace(DefineNames.LocalSizeZ, localSizeZ.ToString(CultureInfo.InvariantCulture));
-
             _dumper.Dump(code, compute: true, out string fullPath, out string codePath);
 
             if (fullPath != null && codePath != null)
@@ -229,13 +228,30 @@ namespace Ryujinx.Graphics.Gpu.Shader
             return new CachedShader(program, codeCached);
         }
 
-        private CachedShader TranslateGraphicsShader(TranslationFlags flags, ulong gpuVa, ulong gpuVaA = 0)
+        private CachedShader TranslateGraphicsShader(GpuState state, ShaderStage stage, ulong gpuVa, ulong gpuVaA = 0)
         {
             if (gpuVa == 0)
             {
                 return new CachedShader(null, null);
             }
 
+            QueryInfoCallback queryInfo = (QueryInfoName info, int index) =>
+            {
+                switch (info)
+                {
+                    case QueryInfoName.IsTextureBuffer:
+                        return Convert.ToInt32(QueryIsTextureBuffer(state, (int)stage - 1, index));
+                    case QueryInfoName.IsTextureRectangle:
+                        return Convert.ToInt32(QueryIsTextureRectangle(state, (int)stage - 1, index));
+                    case QueryInfoName.PrimitiveTopology:
+                        return (int)GetPrimitiveTopology();
+                    case QueryInfoName.ViewportTransformEnable:
+                        return Convert.ToInt32(_context.Methods.GetViewportTransformEnable(state));
+                }
+
+                return QueryInfoCommon(info);
+            };
+
             ShaderProgram program;
 
             int[] codeCached = null;
@@ -245,9 +261,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
                 Span<byte> codeA = _context.MemoryAccessor.Read(gpuVaA, MaxProgramSize);
                 Span<byte> codeB = _context.MemoryAccessor.Read(gpuVa,  MaxProgramSize);
 
-                program = Translator.Translate(codeA, codeB, GetShaderCapabilities(), flags);
+                program = Translator.Translate(codeA, codeB, queryInfo, DefaultFlags);
 
-                // TODO: We should also check "codeA" into account.
+                // TODO: We should also take "codeA" into account.
                 codeCached = MemoryMarshal.Cast<byte, int>(codeB.Slice(0, program.Size)).ToArray();
 
                 _dumper.Dump(codeA, compute: false, out string fullPathA, out string codePathA);
@@ -265,7 +281,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
             {
                 Span<byte> code = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize);
 
-                program = Translator.Translate(code, GetShaderCapabilities(), flags);
+                program = Translator.Translate(code, queryInfo, DefaultFlags);
 
                 codeCached = MemoryMarshal.Cast<byte, int>(code.Slice(0, program.Size)).ToArray();
 
@@ -278,40 +294,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
                 }
             }
 
-            if (program.Stage == ShaderStage.Geometry)
-            {
-                PrimitiveType primitiveType = _context.Methods.PrimitiveType;
-
-                string inPrimitive = "points";
-
-                switch (primitiveType)
-                {
-                    case PrimitiveType.Points:
-                        inPrimitive = "points";
-                        break;
-                    case PrimitiveType.Lines:
-                    case PrimitiveType.LineLoop:
-                    case PrimitiveType.LineStrip:
-                        inPrimitive = "lines";
-                        break;
-                    case PrimitiveType.LinesAdjacency:
-                    case PrimitiveType.LineStripAdjacency:
-                        inPrimitive = "lines_adjacency";
-                        break;
-                    case PrimitiveType.Triangles:
-                    case PrimitiveType.TriangleStrip:
-                    case PrimitiveType.TriangleFan:
-                        inPrimitive = "triangles";
-                        break;
-                    case PrimitiveType.TrianglesAdjacency:
-                    case PrimitiveType.TriangleStripAdjacency:
-                        inPrimitive = "triangles_adjacency";
-                        break;
-                }
-
-                program.Replace(DefineNames.InputTopologyName, inPrimitive);
-            }
-
             ulong address = _context.MemoryManager.Translate(gpuVa);
 
             return new CachedShader(program, codeCached);
@@ -350,13 +332,66 @@ namespace Ryujinx.Graphics.Gpu.Shader
             }
         }
 
-        private ShaderCapabilities GetShaderCapabilities()
+        private InputTopology GetPrimitiveTopology()
         {
-            return new ShaderCapabilities(
-                _context.Capabilities.MaximumViewportDimensions,
-                _context.Capabilities.MaximumComputeSharedMemorySize,
-                _context.Capabilities.StorageBufferOffsetAlignment,
-                _context.Capabilities.SupportsNonConstantTextureOffset);
+            switch (_context.Methods.PrimitiveType)
+            {
+                case PrimitiveType.Points:
+                    return InputTopology.Points;
+                case PrimitiveType.Lines:
+                case PrimitiveType.LineLoop:
+                case PrimitiveType.LineStrip:
+                    return InputTopology.Lines;
+                case PrimitiveType.LinesAdjacency:
+                case PrimitiveType.LineStripAdjacency:
+                    return InputTopology.LinesAdjacency;
+                case PrimitiveType.Triangles:
+                case PrimitiveType.TriangleStrip:
+                case PrimitiveType.TriangleFan:
+                    return InputTopology.Triangles;
+                case PrimitiveType.TrianglesAdjacency:
+                case PrimitiveType.TriangleStripAdjacency:
+                    return InputTopology.TrianglesAdjacency;
+            }
+
+            return InputTopology.Points;
+        }
+
+        private bool QueryIsTextureBuffer(GpuState state, int stageIndex, int index)
+        {
+            return GetTextureDescriptor(state, stageIndex, index).UnpackTextureTarget() == TextureTarget.TextureBuffer;
+        }
+
+        private bool QueryIsTextureRectangle(GpuState state, int stageIndex, int index)
+        {
+            var descriptor = GetTextureDescriptor(state, stageIndex, index);
+
+            TextureTarget target = descriptor.UnpackTextureTarget();
+
+            bool is2DTexture = target == TextureTarget.Texture2D ||
+                               target == TextureTarget.Texture2DRect;
+
+            return !descriptor.UnpackTextureCoordNormalized() && is2DTexture;
+        }
+
+        private Image.TextureDescriptor GetTextureDescriptor(GpuState state, int stageIndex, int index)
+        {
+            return _context.Methods.TextureManager.GetGraphicsTextureDescriptor(state, stageIndex, index);
+        }
+
+        private int QueryInfoCommon(QueryInfoName info)
+        {
+            switch (info)
+            {
+                case QueryInfoName.MaximumViewportDimensions:
+                    return _context.Capabilities.MaximumViewportDimensions;
+                case QueryInfoName.StorageBufferOffsetAlignment:
+                    return _context.Capabilities.StorageBufferOffsetAlignment;
+                case QueryInfoName.SupportsNonConstantTextureOffset:
+                    return Convert.ToInt32(_context.Capabilities.SupportsNonConstantTextureOffset);
+            }
+
+            return 0;
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
index b96aa1aebd..bad5c00c7c 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
@@ -35,23 +35,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
 
             if (context.Config.Stage == ShaderStage.Geometry)
             {
-                string inPrimitive = "points";
-
-                if ((context.Config.Flags & TranslationFlags.Unspecialized) != 0)
-                {
-                    inPrimitive = DefineNames.InputTopologyName;
-                }
+                string inPrimitive = ((InputTopology)context.Config.QueryInfo(QueryInfoName.PrimitiveTopology)).ToGlslString();
 
                 context.AppendLine($"layout ({inPrimitive}) in;");
 
-                string outPrimitive = "triangle_strip";
-
-                switch (context.Config.OutputTopology)
-                {
-                    case OutputTopology.LineStrip:     outPrimitive = "line_strip";     break;
-                    case OutputTopology.PointList:     outPrimitive = "points";         break;
-                    case OutputTopology.TriangleStrip: outPrimitive = "triangle_strip"; break;
-                }
+                string outPrimitive = context.Config.OutputTopology.ToGlslString();
 
                 int maxOutputVertices = context.Config.MaxOutputVertices;
 
@@ -75,16 +63,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
 
             if (context.Config.Stage == ShaderStage.Compute)
             {
-                string size;
-
-                if ((context.Config.Flags & TranslationFlags.Unspecialized) != 0)
-                {
-                    size = DefineNames.SharedMemorySize;
-                }
-                else
-                {
-                    size = NumberFormatter.FormatInt(context.Config.Capabilities.MaximumComputeSharedMemorySize / 4);
-                }
+                string size = NumberFormatter.FormatInt(BitUtils.DivRoundUp(context.Config.QueryInfo(QueryInfoName.ComputeSharedMemorySize), 4));
 
                 context.AppendLine($"shared uint {DefaultNames.SharedMemoryName}[{size}];");
                 context.AppendLine();
@@ -136,19 +115,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             }
             else
             {
-                string localSizeX = "1";
-                string localSizeY = "1";
-                string localSizeZ = "1";
-
-                if ((context.Config.Flags & TranslationFlags.Unspecialized) != 0)
-                {
-                    localSizeX = DefineNames.LocalSizeX;
-                    localSizeY = DefineNames.LocalSizeY;
-                    localSizeZ = DefineNames.LocalSizeZ;
-                }
+                string localSizeX = NumberFormatter.FormatInt(context.Config.QueryInfo(QueryInfoName.ComputeLocalSizeX));
+                string localSizeY = NumberFormatter.FormatInt(context.Config.QueryInfo(QueryInfoName.ComputeLocalSizeY));
+                string localSizeZ = NumberFormatter.FormatInt(context.Config.QueryInfo(QueryInfoName.ComputeLocalSizeZ));
 
                 context.AppendLine(
-                    $"layout (" +
+                    "layout (" +
                     $"local_size_x = {localSizeX}, " +
                     $"local_size_y = {localSizeY}, " +
                     $"local_size_z = {localSizeZ}) in;");
diff --git a/Ryujinx.Graphics.Shader/DefineNames.cs b/Ryujinx.Graphics.Shader/DefineNames.cs
index 67e8e1ee3c..b043049919 100644
--- a/Ryujinx.Graphics.Shader/DefineNames.cs
+++ b/Ryujinx.Graphics.Shader/DefineNames.cs
@@ -2,14 +2,6 @@ namespace Ryujinx.Graphics.Shader
 {
     public static class DefineNames
     {
-        public const string InputTopologyName = "S_INPUT_TOPOLOGY";
-
         public const string OutQualifierPrefixName = "S_OUT_QUALIFIER";
-
-        public const string SharedMemorySize = "S_SHARED_MEMORY_SIZE";
-
-        public const string LocalSizeX = "S_LOCAL_SIZE_X";
-        public const string LocalSizeY = "S_LOCAL_SIZE_Y";
-        public const string LocalSizeZ = "S_LOCAL_SIZE_Z";
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/InputTopology.cs b/Ryujinx.Graphics.Shader/InputTopology.cs
new file mode 100644
index 0000000000..3b0dda45f1
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/InputTopology.cs
@@ -0,0 +1,28 @@
+namespace Ryujinx.Graphics.Shader
+{
+    public enum InputTopology
+    {
+        Points,
+        Lines,
+        LinesAdjacency,
+        Triangles,
+        TrianglesAdjacency
+    }
+
+    static class InputTopologyExtensions
+    {
+        public static string ToGlslString(this InputTopology topology)
+        {
+            switch (topology)
+            {
+                case InputTopology.Points:             return "points";
+                case InputTopology.Lines:              return "lines";
+                case InputTopology.LinesAdjacency:     return "lines_adjacency";
+                case InputTopology.Triangles:          return "triangles";
+                case InputTopology.TrianglesAdjacency: return "triangles_adjacency";
+            }
+
+            return "points";
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/OutputTopology.cs b/Ryujinx.Graphics.Shader/OutputTopology.cs
index e8336aa3f3..6f977becb4 100644
--- a/Ryujinx.Graphics.Shader/OutputTopology.cs
+++ b/Ryujinx.Graphics.Shader/OutputTopology.cs
@@ -6,4 +6,19 @@ namespace Ryujinx.Graphics.Shader
         LineStrip     = 6,
         TriangleStrip = 7
     }
+
+    static class OutputTopologyExtensions
+    {
+        public static string ToGlslString(this OutputTopology topology)
+        {
+            switch (topology)
+            {
+                case OutputTopology.LineStrip:     return "line_strip";
+                case OutputTopology.PointList:     return "points";
+                case OutputTopology.TriangleStrip: return "triangle_strip";
+            }
+
+            return "points";
+        }
+    }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/QueryInfoCallback.cs b/Ryujinx.Graphics.Shader/QueryInfoCallback.cs
new file mode 100644
index 0000000000..28261a77e2
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/QueryInfoCallback.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Graphics.Shader
+{
+    public delegate int QueryInfoCallback(QueryInfoName info, int index);
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/QueryInfoName.cs b/Ryujinx.Graphics.Shader/QueryInfoName.cs
new file mode 100644
index 0000000000..e976dcdbdb
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/QueryInfoName.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.Shader
+{
+    public enum QueryInfoName
+    {
+        ComputeLocalSizeX,
+        ComputeLocalSizeY,
+        ComputeLocalSizeZ,
+        ComputeSharedMemorySize,
+        IsTextureBuffer,
+        IsTextureRectangle,
+        MaximumViewportDimensions,
+        PrimitiveTopology,
+        StorageBufferOffsetAlignment,
+        SupportsNonConstantTextureOffset,
+        ViewportTransformEnable
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/ShaderCapabilities.cs b/Ryujinx.Graphics.Shader/ShaderCapabilities.cs
deleted file mode 100644
index 809481b50d..0000000000
--- a/Ryujinx.Graphics.Shader/ShaderCapabilities.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-namespace Ryujinx.Graphics.Shader
-{
-    public struct ShaderCapabilities
-    {
-        // Initialize with default values for Maxwell.
-        private static readonly ShaderCapabilities _default = new ShaderCapabilities(0x8000, 0xc000, 16, true);
-
-        public static ShaderCapabilities Default => _default;
-
-        public int  MaximumViewportDimensions        { get; }
-        public int  MaximumComputeSharedMemorySize   { get; }
-        public int  StorageBufferOffsetAlignment     { get; }
-        public bool SupportsNonConstantTextureOffset { get; }
-
-        public ShaderCapabilities(
-            int  maximumViewportDimensions,
-            int  maximumComputeSharedMemorySize,
-            int  storageBufferOffsetAlignment,
-            bool supportsNonConstantTextureOffset)
-        {
-            MaximumViewportDimensions        = maximumViewportDimensions;
-            MaximumComputeSharedMemorySize   = maximumComputeSharedMemorySize;
-            StorageBufferOffsetAlignment     = storageBufferOffsetAlignment;
-            SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
-        }
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/ShaderConfig.cs b/Ryujinx.Graphics.Shader/ShaderConfig.cs
index 3088cfbba6..6b3640dfcf 100644
--- a/Ryujinx.Graphics.Shader/ShaderConfig.cs
+++ b/Ryujinx.Graphics.Shader/ShaderConfig.cs
@@ -1,4 +1,5 @@
 using Ryujinx.Graphics.Shader.Translation;
+using System;
 
 namespace Ryujinx.Graphics.Shader
 {
@@ -6,26 +7,100 @@ namespace Ryujinx.Graphics.Shader
     {
         public ShaderStage Stage { get; }
 
-        public ShaderCapabilities Capabilities { get; }
-
-        public TranslationFlags Flags { get; }
+        public OutputTopology OutputTopology { get; }
 
         public int MaxOutputVertices { get; }
 
-        public OutputTopology OutputTopology { get; }
+        public OutputMapTarget[] OmapTargets    { get; }
+        public bool              OmapSampleMask { get; }
+        public bool              OmapDepth      { get; }
 
-        public ShaderConfig(
-            ShaderStage        stage,
-            ShaderCapabilities capabilities,
-            TranslationFlags   flags,
-            int                maxOutputVertices,
-            OutputTopology     outputTopology)
+        public TranslationFlags Flags { get; }
+
+        private QueryInfoCallback _queryInfoCallback;
+
+        public ShaderConfig(TranslationFlags flags, QueryInfoCallback queryInfoCallback)
         {
-            Stage             = stage;
-            Capabilities      = capabilities;
-            Flags             = flags;
-            MaxOutputVertices = maxOutputVertices;
-            OutputTopology    = outputTopology;
+            Stage              = ShaderStage.Compute;
+            OutputTopology     = OutputTopology.PointList;
+            MaxOutputVertices  = 0;
+            OmapTargets        = null;
+            OmapSampleMask     = false;
+            OmapDepth          = false;
+            Flags              = flags;
+            _queryInfoCallback = queryInfoCallback;
+        }
+
+        public ShaderConfig(ShaderHeader header, TranslationFlags flags, QueryInfoCallback queryInfoCallback)
+        {
+            Stage              = header.Stage;
+            OutputTopology     = header.OutputTopology;
+            MaxOutputVertices  = header.MaxOutputVertexCount;
+            OmapTargets        = header.OmapTargets;
+            OmapSampleMask     = header.OmapSampleMask;
+            OmapDepth          = header.OmapDepth;
+            Flags              = flags;
+            _queryInfoCallback = queryInfoCallback;
+        }
+
+        public int GetDepthRegister()
+        {
+            int count = 0;
+
+            for (int index = 0; index < OmapTargets.Length; index++)
+            {
+                for (int component = 0; component < 4; component++)
+                {
+                    if (OmapTargets[index].ComponentEnabled(component))
+                    {
+                        count++;
+                    }
+                }
+            }
+
+            // The depth register is always two registers after the last color output.
+            return count + 1;
+        }
+
+        public bool QueryInfoBool(QueryInfoName info, int index = 0)
+        {
+            return Convert.ToBoolean(QueryInfo(info, index));
+        }
+
+        public int QueryInfo(QueryInfoName info, int index = 0)
+        {
+            if (_queryInfoCallback != null)
+            {
+                return _queryInfoCallback(info, index);
+            }
+            else
+            {
+                switch (info)
+                {
+                    case QueryInfoName.ComputeLocalSizeX:
+                    case QueryInfoName.ComputeLocalSizeY:
+                    case QueryInfoName.ComputeLocalSizeZ:
+                        return 1;
+                    case QueryInfoName.ComputeSharedMemorySize:
+                        return 0xc000;
+                    case QueryInfoName.IsTextureBuffer:
+                        return Convert.ToInt32(false);
+                    case QueryInfoName.IsTextureRectangle:
+                        return Convert.ToInt32(false);
+                    case QueryInfoName.MaximumViewportDimensions:
+                        return 0x8000;
+                    case QueryInfoName.PrimitiveTopology:
+                        return (int)InputTopology.Points;
+                    case QueryInfoName.StorageBufferOffsetAlignment:
+                        return 16;
+                    case QueryInfoName.SupportsNonConstantTextureOffset:
+                        return Convert.ToInt32(true);
+                    case QueryInfoName.ViewportTransformEnable:
+                        return Convert.ToInt32(true);
+                }
+            }
+
+            return 0;
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/ShaderHeader.cs b/Ryujinx.Graphics.Shader/ShaderHeader.cs
index 94c5743595..a8d01086aa 100644
--- a/Ryujinx.Graphics.Shader/ShaderHeader.cs
+++ b/Ryujinx.Graphics.Shader/ShaderHeader.cs
@@ -76,28 +76,6 @@ namespace Ryujinx.Graphics.Shader
         public bool              OmapSampleMask { get; }
         public bool              OmapDepth      { get; }
 
-        public int DepthRegister
-        {
-            get
-            {
-                int count = 0;
-
-                for (int index = 0; index < OmapTargets.Length; index++)
-                {
-                    for (int component = 0; component < 4; component++)
-                    {
-                        if (OmapTargets[index].ComponentEnabled(component))
-                        {
-                            count++;
-                        }
-                    }
-                }
-
-                // Depth register is always two registers after the last color output.
-                return count + 1;
-            }
-        }
-
         public ShaderHeader(Span<byte> code)
         {
             Span<int> header = MemoryMarshal.Cast<byte, int>(code);
diff --git a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
index 7ba7b697bc..9620145aff 100644
--- a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
+++ b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
@@ -11,25 +11,15 @@ namespace Ryujinx.Graphics.Shader.Translation
         public Block  CurrBlock { get; set; }
         public OpCode CurrOp    { get; set; }
 
-        private ShaderStage        _stage;
-        private ShaderHeader       _header;
-        private ShaderCapabilities _capabilities;
-        private TranslationFlags   _flags;
+        private ShaderConfig _config;
 
         private List<Operation> _operations;
 
         private Dictionary<ulong, Operand> _labels;
 
-        public EmitterContext(
-            ShaderStage        stage,
-            ShaderHeader       header,
-            ShaderCapabilities capabilities,
-            TranslationFlags   flags)
+        public EmitterContext(ShaderConfig config)
         {
-            _stage        = stage;
-            _header       = header;
-            _capabilities = capabilities;
-            _flags        = flags;
+            _config = config;
 
             _operations = new List<Operation>();
 
@@ -69,24 +59,24 @@ namespace Ryujinx.Graphics.Shader.Translation
 
         public void PrepareForReturn()
         {
-            if (_stage == ShaderStage.Vertex)
+            if (_config.Stage == ShaderStage.Vertex)
             {
-                if ((_flags & TranslationFlags.DividePosXY) != 0)
+                if (!_config.QueryInfoBool(QueryInfoName.ViewportTransformEnable))
                 {
                     Operand posX = Attribute(AttributeConsts.PositionX);
                     Operand posY = Attribute(AttributeConsts.PositionY);
 
-                    this.Copy(posX, this.FPDivide(posX, ConstF(_capabilities.MaximumViewportDimensions / 2)));
-                    this.Copy(posY, this.FPDivide(posY, ConstF(_capabilities.MaximumViewportDimensions / 2)));
+                    this.Copy(posX, this.FPDivide(posX, ConstF(_config.QueryInfo(QueryInfoName.MaximumViewportDimensions) / 2)));
+                    this.Copy(posY, this.FPDivide(posY, ConstF(_config.QueryInfo(QueryInfoName.MaximumViewportDimensions) / 2)));
                 }
             }
-            else if (_stage == ShaderStage.Fragment)
+            else if (_config.Stage == ShaderStage.Fragment)
             {
-                if (_header.OmapDepth)
+                if (_config.OmapDepth)
                 {
                     Operand dest = Attribute(AttributeConsts.FragmentOutputDepth);
 
-                    Operand src = Register(_header.DepthRegister, RegisterType.Gpr);
+                    Operand src = Register(_config.GetDepthRegister(), RegisterType.Gpr);
 
                     this.Copy(dest, src);
                 }
@@ -95,7 +85,7 @@ namespace Ryujinx.Graphics.Shader.Translation
 
                 for (int attachment = 0; attachment < 8; attachment++)
                 {
-                    OutputMapTarget target = _header.OmapTargets[attachment];
+                    OutputMapTarget target = _config.OmapTargets[attachment];
 
                     for (int component = 0; component < 4; component++)
                     {
diff --git a/Ryujinx.Graphics.Shader/Translation/Lowering.cs b/Ryujinx.Graphics.Shader/Translation/Lowering.cs
index 46884bc983..1ee21e0a04 100644
--- a/Ryujinx.Graphics.Shader/Translation/Lowering.cs
+++ b/Ryujinx.Graphics.Shader/Translation/Lowering.cs
@@ -27,9 +27,9 @@ namespace Ryujinx.Graphics.Shader.Translation
                         node = RewriteGlobalAccess(node, config);
                     }
 
-                    if (!config.Capabilities.SupportsNonConstantTextureOffset && operation.Inst == Instruction.TextureSample)
+                    if (operation.Inst == Instruction.TextureSample)
                     {
-                        node = RewriteTextureSample(node);
+                        node = RewriteTextureSample(node, config);
                     }
                 }
             }
@@ -79,7 +79,7 @@ namespace Ryujinx.Graphics.Shader.Translation
                 sbSlot        = PrependOperation(Instruction.ConditionalSelect, inRange, Const(slot), sbSlot);
             }
 
-            Operand alignMask = Const(-config.Capabilities.StorageBufferOffsetAlignment);
+            Operand alignMask = Const(-config.QueryInfo(QueryInfoName.StorageBufferOffsetAlignment));
 
             Operand baseAddrTrunc = PrependOperation(Instruction.BitwiseAnd,    sbBaseAddrLow, Const(-64));
             Operand byteOffset    = PrependOperation(Instruction.Subtract,      addrLow, baseAddrTrunc);
@@ -124,23 +124,18 @@ namespace Ryujinx.Graphics.Shader.Translation
             return node;
         }
 
-        private static LinkedListNode<INode> RewriteTextureSample(LinkedListNode<INode> node)
+        private static LinkedListNode<INode> RewriteTextureSample(LinkedListNode<INode> node, ShaderConfig config)
         {
-            // Technically, non-constant texture offsets are not allowed (according to the spec),
-            // however some GPUs does support that.
-            // For GPUs where it is not supported, we can replace the instruction with the following:
-            // For texture*Offset, we replace it by texture*, and add the offset to the P coords.
-            // The offset can be calculated as offset / textureSize(lod), where lod = textureQueryLod(coords).
-            // For texelFetchOffset, we replace it by texelFetch and add the offset to the P coords directly.
-            // For textureGatherOffset, we take advantage of the fact that the operation is already broken down
-            // to read the 4 pixels separately, and just replace it with 4 textureGather with a different offset
-            // for each pixel.
             TextureOperation texOp = (TextureOperation)node.Value;
 
             bool hasOffset  = (texOp.Flags & TextureFlags.Offset)  != 0;
             bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
 
-            if (!(hasOffset || hasOffsets))
+            bool hasInvalidOffset = (hasOffset || hasOffsets) && !config.QueryInfoBool(QueryInfoName.SupportsNonConstantTextureOffset);
+
+            bool isRect = config.QueryInfoBool(QueryInfoName.IsTextureRectangle, texOp.Handle);
+
+            if (!(hasInvalidOffset || isRect))
             {
                 return node;
             }
@@ -159,14 +154,24 @@ namespace Ryujinx.Graphics.Shader.Translation
 
             int coordsCount = texOp.Type.GetDimensions();
 
-            int offsetsCount = coordsCount * (hasOffsets ? 4 : 1);
+            int offsetsCount;
+
+            if (hasOffsets)
+            {
+                offsetsCount = coordsCount * 4;
+            }
+            else if (hasOffset)
+            {
+                offsetsCount = coordsCount;
+            }
+            else
+            {
+                offsetsCount = 0;
+            }
 
             Operand[] offsets = new Operand[offsetsCount];
             Operand[] sources = new Operand[texOp.SourcesCount - offsetsCount];
 
-            int srcIndex = 0;
-            int dstIndex = 0;
-
             int copyCount = 0;
 
             if (isBindless || isIndexed)
@@ -207,6 +212,9 @@ namespace Ryujinx.Graphics.Shader.Translation
                 copyCount++;
             }
 
+            int srcIndex = 0;
+            int dstIndex = 0;
+
             for (int index = 0; index < copyCount; index++)
             {
                 sources[dstIndex++] = texOp.GetSource(srcIndex++);
@@ -223,7 +231,9 @@ namespace Ryujinx.Graphics.Shader.Translation
                 offsets[index] = offset;
             }
 
-            if (areAllOffsetsConstant)
+            hasInvalidOffset &= !areAllOffsetsConstant;
+
+            if (!(hasInvalidOffset || isRect))
             {
                 return node;
             }
@@ -240,50 +250,32 @@ namespace Ryujinx.Graphics.Shader.Translation
 
             int coordsIndex = isBindless || isIndexed ? 1 : 0;
 
-            if (intCoords)
+            int componentIndex = texOp.Index;
+
+            Operand Int(Operand value)
             {
-                for (int index = 0; index < coordsCount; index++)
-                {
-                    Operand source = sources[coordsIndex + index];
+                Operand res = Local();
 
-                    Operand coordPlusOffset = Local();
+                node.List.AddBefore(node, new Operation(Instruction.ConvertFPToS32, res, value));
 
-                    node.List.AddBefore(node, new Operation(Instruction.Add, coordPlusOffset, source, offsets[index]));
-
-                    sources[coordsIndex + index] = coordPlusOffset;
-                }
+                return res;
             }
-            else
+
+            Operand Float(Operand value)
             {
-                Operand lod = Local();
+                Operand res = Local();
 
-                node.List.AddBefore(node, new TextureOperation(
-                    Instruction.Lod,
-                    texOp.Type,
-                    texOp.Flags,
-                    texOp.Handle,
-                    1,
-                    lod,
-                    lodSources));
+                node.List.AddBefore(node, new Operation(Instruction.ConvertS32ToFP, res, value));
 
-                Operand Int(Operand value)
-                {
-                    Operand res = Local();
-
-                    node.List.AddBefore(node, new Operation(Instruction.ConvertFPToS32, res, value));
-
-                    return res;
-                }
-
-                Operand Float(Operand value)
-                {
-                    Operand res = Local();
-
-                    node.List.AddBefore(node, new Operation(Instruction.ConvertS32ToFP, res, value));
-
-                    return res;
-                }
+                return res;
+            }
 
+            // Emulate texture rectangle by normalizing the coordinates on the shader.
+            // When sampler*Rect is used, the coords are expected to the in the [0, W or H] range,
+            // and otherwise, it is expected to be in the [0, 1] range.
+            // We normalize by dividing the coords by the texture size.
+            if (isRect && !intCoords)
+            {
                 for (int index = 0; index < coordsCount; index++)
                 {
                     Operand coordSize = Local();
@@ -292,11 +284,11 @@ namespace Ryujinx.Graphics.Shader.Translation
 
                     if (isBindless || isIndexed)
                     {
-                        texSizeSources = new Operand[] { sources[0], Int(lod) };
+                        texSizeSources = new Operand[] { sources[0], Const(0) };
                     }
                     else
                     {
-                        texSizeSources = new Operand[] { Int(lod) };
+                        texSizeSources = new Operand[] { Const(0) };
                     }
 
                     node.List.AddBefore(node, new TextureOperation(
@@ -308,35 +300,101 @@ namespace Ryujinx.Graphics.Shader.Translation
                         coordSize,
                         texSizeSources));
 
-                    Operand offset = Local();
-
-                    Operand intOffset = offsets[index + (hasOffsets ? texOp.Index * coordsCount : 0)];
-
-                    node.List.AddBefore(node, new Operation(Instruction.FP | Instruction.Divide, offset, Float(intOffset), Float(coordSize)));
-
                     Operand source = sources[coordsIndex + index];
 
-                    Operand coordPlusOffset = Local();
+                    Operand coordNormalized = Local();
 
-                    node.List.AddBefore(node, new Operation(Instruction.FP | Instruction.Add, coordPlusOffset, source, offset));
+                    node.List.AddBefore(node, new Operation(Instruction.FP | Instruction.Divide, coordNormalized, source, Float(coordSize)));
 
-                    sources[coordsIndex + index] = coordPlusOffset;
+                    sources[coordsIndex + index] = coordNormalized;
                 }
             }
 
-            int componentIndex;
-
-            if (isGather && !isShadow)
+            // Technically, non-constant texture offsets are not allowed (according to the spec),
+            // however some GPUs does support that.
+            // For GPUs where it is not supported, we can replace the instruction with the following:
+            // For texture*Offset, we replace it by texture*, and add the offset to the P coords.
+            // The offset can be calculated as offset / textureSize(lod), where lod = textureQueryLod(coords).
+            // For texelFetchOffset, we replace it by texelFetch and add the offset to the P coords directly.
+            // For textureGatherOffset, we take advantage of the fact that the operation is already broken down
+            // to read the 4 pixels separately, and just replace it with 4 textureGather with a different offset
+            // for each pixel.
+            if (hasInvalidOffset)
             {
-                Operand gatherComponent = sources[dstIndex - 1];
+                if (intCoords)
+                {
+                    for (int index = 0; index < coordsCount; index++)
+                    {
+                        Operand source = sources[coordsIndex + index];
 
-                Debug.Assert(gatherComponent.Type == OperandType.Constant);
+                        Operand coordPlusOffset = Local();
 
-                componentIndex = gatherComponent.Value;
-            }
-            else
-            {
-                componentIndex = texOp.Index;
+                        node.List.AddBefore(node, new Operation(Instruction.Add, coordPlusOffset, source, offsets[index]));
+
+                        sources[coordsIndex + index] = coordPlusOffset;
+                    }
+                }
+                else
+                {
+                    Operand lod = Local();
+
+                    node.List.AddBefore(node, new TextureOperation(
+                        Instruction.Lod,
+                        texOp.Type,
+                        texOp.Flags,
+                        texOp.Handle,
+                        1,
+                        lod,
+                        lodSources));
+
+                    for (int index = 0; index < coordsCount; index++)
+                    {
+                        Operand coordSize = Local();
+
+                        Operand[] texSizeSources;
+
+                        if (isBindless || isIndexed)
+                        {
+                            texSizeSources = new Operand[] { sources[0], Int(lod) };
+                        }
+                        else
+                        {
+                            texSizeSources = new Operand[] { Int(lod) };
+                        }
+
+                        node.List.AddBefore(node, new TextureOperation(
+                            Instruction.TextureSize,
+                            texOp.Type,
+                            texOp.Flags,
+                            texOp.Handle,
+                            index,
+                            coordSize,
+                            texSizeSources));
+
+                        Operand offset = Local();
+
+                        Operand intOffset = offsets[index + (hasOffsets ? texOp.Index * coordsCount : 0)];
+
+                        node.List.AddBefore(node, new Operation(Instruction.FP | Instruction.Divide, offset, Float(intOffset), Float(coordSize)));
+
+                        Operand source = sources[coordsIndex + index];
+
+                        Operand coordPlusOffset = Local();
+
+                        node.List.AddBefore(node, new Operation(Instruction.FP | Instruction.Add, coordPlusOffset, source, offset));
+
+                        sources[coordsIndex + index] = coordPlusOffset;
+                    }
+                }
+
+                if (isGather && !isShadow)
+                {
+                    Operand gatherComponent = sources[dstIndex - 1];
+
+                    Debug.Assert(gatherComponent.Type == OperandType.Constant);
+
+                    componentIndex = gatherComponent.Value;
+                }
             }
 
             TextureOperation newTexOp = new TextureOperation(
diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs
index 639f9ba4b4..59261fbf06 100644
--- a/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs
+++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs
@@ -52,7 +52,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
 
                 Operand baseAddrTrunc = Local();
 
-                Operand alignMask = Const(-config.Capabilities.StorageBufferOffsetAlignment);
+                Operand alignMask = Const(-config.QueryInfo(QueryInfoName.StorageBufferOffsetAlignment));
 
                 Operation andOp = new Operation(Instruction.BitwiseAnd, baseAddrTrunc, baseAddrLow, alignMask);
 
diff --git a/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs b/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs
index 8faa43836b..1327abaed4 100644
--- a/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs
+++ b/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs
@@ -4,9 +4,7 @@ namespace Ryujinx.Graphics.Shader.Translation
     {
         None = 0,
 
-        Compute       = 1 << 0,
-        DebugMode     = 1 << 1,
-        Unspecialized = 1 << 2,
-        DividePosXY   = 1 << 3
+        Compute   = 1 << 0,
+        DebugMode = 1 << 1
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Translation/Translator.cs b/Ryujinx.Graphics.Shader/Translation/Translator.cs
index 69e63ae135..af209edf1d 100644
--- a/Ryujinx.Graphics.Shader/Translation/Translator.cs
+++ b/Ryujinx.Graphics.Shader/Translation/Translator.cs
@@ -16,14 +16,7 @@ namespace Ryujinx.Graphics.Shader.Translation
 
         public static Span<byte> ExtractCode(Span<byte> code, bool compute, out int headerSize)
         {
-            if (compute)
-            {
-                headerSize = 0;
-            }
-            else
-            {
-                headerSize = HeaderSize;
-            }
+            headerSize = compute ? 0 : HeaderSize;
 
             Block[] cfg = Decoder.Decode(code, (ulong)headerSize);
 
@@ -47,56 +40,21 @@ namespace Ryujinx.Graphics.Shader.Translation
             return code.Slice(0, headerSize + (int)endAddress);
         }
 
-        public static ShaderProgram Translate(Span<byte> code, ShaderCapabilities capabilities, TranslationFlags flags)
+        public static ShaderProgram Translate(Span<byte> code, QueryInfoCallback queryInfoCallback, TranslationFlags flags)
         {
             bool compute = (flags & TranslationFlags.Compute) != 0;
 
-            Operation[] ops = DecodeShader(code, capabilities, flags, out ShaderHeader header, out int size);
-
-            ShaderStage stage;
-
-            if (compute)
-            {
-                stage = ShaderStage.Compute;
-            }
-            else
-            {
-                stage = header.Stage;
-            }
-
-            int maxOutputVertexCount = 0;
-
-            OutputTopology outputTopology = OutputTopology.LineStrip;
-
-            if (!compute)
-            {
-                maxOutputVertexCount = header.MaxOutputVertexCount;
-                outputTopology       = header.OutputTopology;
-            }
-
-            ShaderConfig config = new ShaderConfig(
-                stage,
-                capabilities,
-                flags,
-                maxOutputVertexCount,
-                outputTopology);
+            Operation[] ops = DecodeShader(code, queryInfoCallback, flags, out ShaderConfig config, out int size);
 
             return Translate(ops, config, size);
         }
 
-        public static ShaderProgram Translate(Span<byte> vpACode, Span<byte> vpBCode, ShaderCapabilities capabilities, TranslationFlags flags)
+        public static ShaderProgram Translate(Span<byte> vpACode, Span<byte> vpBCode, QueryInfoCallback queryInfoCallback, TranslationFlags flags)
         {
             bool debugMode = (flags & TranslationFlags.DebugMode) != 0;
 
-            Operation[] vpAOps = DecodeShader(vpACode, capabilities, flags, out _, out _);
-            Operation[] vpBOps = DecodeShader(vpBCode, capabilities, flags, out ShaderHeader header, out int sizeB);
-
-            ShaderConfig config = new ShaderConfig(
-                header.Stage,
-                capabilities,
-                flags,
-                header.MaxOutputVertexCount,
-                header.OutputTopology);
+            Operation[] vpAOps = DecodeShader(vpACode, queryInfoCallback, flags, out _, out _);
+            Operation[] vpBOps = DecodeShader(vpBCode, queryInfoCallback, flags, out ShaderConfig config, out int sizeB);
 
             return Translate(Combine(vpAOps, vpBOps), config, sizeB);
         }
@@ -136,31 +94,25 @@ namespace Ryujinx.Graphics.Shader.Translation
         }
 
         private static Operation[] DecodeShader(
-            Span<byte>         code,
-            ShaderCapabilities capabilities,
-            TranslationFlags   flags,
-            out ShaderHeader   header,
-            out int            size)
+            Span<byte>        code,
+            QueryInfoCallback queryInfoCallback,
+            TranslationFlags  flags,
+            out ShaderConfig  config,
+            out int           size)
         {
             Block[] cfg;
 
-            EmitterContext context;
-
             if ((flags & TranslationFlags.Compute) != 0)
             {
-                header = null;
+                config = new ShaderConfig(flags, queryInfoCallback);
 
                 cfg = Decoder.Decode(code, 0);
-
-                context = new EmitterContext(ShaderStage.Compute, header, capabilities, flags);
             }
             else
             {
-                header = new ShaderHeader(code);
+                config = new ShaderConfig(new ShaderHeader(code), flags, queryInfoCallback);
 
                 cfg = Decoder.Decode(code, HeaderSize);
-
-                context = new EmitterContext(header.Stage, header, capabilities, flags);
             }
 
             if (cfg == null)
@@ -172,6 +124,8 @@ namespace Ryujinx.Graphics.Shader.Translation
                 return new Operation[0];
             }
 
+            EmitterContext context = new EmitterContext(config);
+
             ulong maxEndAddress = 0;
 
             for (int blkIndex = 0; blkIndex < cfg.Length; blkIndex++)
diff --git a/Ryujinx.ShaderTools/Program.cs b/Ryujinx.ShaderTools/Program.cs
index 275da794d3..445f6b46db 100644
--- a/Ryujinx.ShaderTools/Program.cs
+++ b/Ryujinx.ShaderTools/Program.cs
@@ -20,7 +20,7 @@ namespace Ryujinx.ShaderTools
 
                 byte[] data = File.ReadAllBytes(args[args.Length - 1]);
 
-                string code = Translator.Translate(data, ShaderCapabilities.Default, flags).Code;
+                string code = Translator.Translate(data, null, flags).Code;
 
                 Console.WriteLine(code);
             }