From 7e967d796cf572377f21af3817a22755c5b01cb1 Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Fri, 21 Jan 2022 12:35:21 -0300
Subject: [PATCH] Stop using glTransformFeedbackVaryings and use explicit
 layout on the shader (#3012)

* Stop using glTransformFeedbackVarying and use explicit layout on the shader

* This is no longer needed

* Shader cache version bump

* Fix gl_PerVertex output for tessellation control shaders
---
 Ryujinx.Graphics.GAL/IRenderer.cs             |  2 +-
 .../Programs/SourceProgramRequest.cs          |  6 +-
 .../Multithreading/ThreadedRenderer.cs        |  4 +-
 .../Shader/CachedGpuAccessor.cs               | 35 +++++++++-
 Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs    | 29 ++++++++
 .../Shader/GpuAccessorState.cs                |  6 ++
 Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs    | 46 +++++--------
 .../Shader}/TransformFeedbackDescriptor.cs    |  4 +-
 Ryujinx.Graphics.OpenGL/Program.cs            | 50 +-------------
 Ryujinx.Graphics.OpenGL/Renderer.cs           |  4 +-
 .../CodeGen/Glsl/CodeGenContext.cs            | 12 ++++
 .../CodeGen/Glsl/Declarations.cs              | 28 +++++++-
 .../CodeGen/Glsl/OperandManager.cs            |  2 +-
 .../CodeGen/Glsl/Varying.cs                   | 69 -------------------
 Ryujinx.Graphics.Shader/IGpuAccessor.cs       | 15 ++++
 .../StructuredIr/StructuredProgram.cs         | 18 +++++
 .../StructuredIr/StructuredProgramInfo.cs     | 20 ++++++
 .../Translation/ShaderConfig.cs               |  3 +
 .../Translation/TranslationFlags.cs           |  3 +-
 19 files changed, 192 insertions(+), 164 deletions(-)
 rename {Ryujinx.Graphics.GAL => Ryujinx.Graphics.Gpu/Shader}/TransformFeedbackDescriptor.cs (85%)
 delete mode 100644 Ryujinx.Graphics.Shader/CodeGen/Glsl/Varying.cs

diff --git a/Ryujinx.Graphics.GAL/IRenderer.cs b/Ryujinx.Graphics.GAL/IRenderer.cs
index 7c0cb394b..a11932082 100644
--- a/Ryujinx.Graphics.GAL/IRenderer.cs
+++ b/Ryujinx.Graphics.GAL/IRenderer.cs
@@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.GAL
 
         BufferHandle CreateBuffer(int size);
 
-        IProgram CreateProgram(IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors);
+        IProgram CreateProgram(IShader[] shaders);
 
         ISampler CreateSampler(SamplerCreateInfo info);
         ITexture CreateTexture(TextureCreateInfo info, float scale);
diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs
index d40ce6a46..8e4cd1d49 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs
@@ -7,14 +7,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs
         public ThreadedProgram Threaded { get; set; }
 
         private IShader[] _shaders;
-        private TransformFeedbackDescriptor[] _transformFeedbackDescriptors;
 
-        public SourceProgramRequest(ThreadedProgram program, IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors)
+        public SourceProgramRequest(ThreadedProgram program, IShader[] shaders)
         {
             Threaded = program;
 
             _shaders = shaders;
-            _transformFeedbackDescriptors = transformFeedbackDescriptors;
         }
 
         public IProgram Create(IRenderer renderer)
@@ -26,7 +24,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs
                 return threaded?.Base;
             }).ToArray();
 
-            return renderer.CreateProgram(shaders, _transformFeedbackDescriptors);
+            return renderer.CreateProgram(shaders);
         }
     }
 }
diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
index 52197688d..df46b4289 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
@@ -268,10 +268,10 @@ namespace Ryujinx.Graphics.GAL.Multithreading
             return handle;
         }
 
-        public IProgram CreateProgram(IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors)
+        public IProgram CreateProgram(IShader[] shaders)
         {
             var program = new ThreadedProgram(this);
-            SourceProgramRequest request = new SourceProgramRequest(program, shaders, transformFeedbackDescriptors);
+            SourceProgramRequest request = new SourceProgramRequest(program, shaders);
             Programs.Add(request);
 
             New<CreateProgramCommand>().Set(Ref((IProgramRequest)request));
diff --git a/Ryujinx.Graphics.Gpu/Shader/CachedGpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/CachedGpuAccessor.cs
index 21d08823e..d65349a52 100644
--- a/Ryujinx.Graphics.Gpu/Shader/CachedGpuAccessor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/CachedGpuAccessor.cs
@@ -13,6 +13,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         private readonly ReadOnlyMemory<byte> _cb1Data;
         private readonly GuestGpuAccessorHeader _header;
         private readonly Dictionary<int, GuestTextureDescriptor> _textureDescriptors;
+        private readonly TransformFeedbackDescriptor[] _tfd;
 
         /// <summary>
         /// Creates a new instance of the cached GPU state accessor for shader translation.
@@ -27,7 +28,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
             ReadOnlyMemory<byte> data,
             ReadOnlyMemory<byte> cb1Data,
             GuestGpuAccessorHeader header,
-            IReadOnlyDictionary<int, GuestTextureDescriptor> guestTextureDescriptors) : base(context)
+            IReadOnlyDictionary<int, GuestTextureDescriptor> guestTextureDescriptors,
+            TransformFeedbackDescriptor[] tfd) : base(context)
         {
             _data = data;
             _cb1Data = cb1Data;
@@ -38,6 +40,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
             {
                 _textureDescriptors.Add(guestTextureDescriptor.Key, guestTextureDescriptor.Value);
             }
+
+            _tfd = tfd;
         }
 
         /// <summary>
@@ -177,6 +181,35 @@ namespace Ryujinx.Graphics.Gpu.Shader
             return textureDescriptor;
         }
 
+        /// <summary>
+        /// Queries transform feedback enable state.
+        /// </summary>
+        /// <returns>True if the shader uses transform feedback, false otherwise</returns>
+        public bool QueryTransformFeedbackEnabled()
+        {
+            return _tfd != null;
+        }
+
+        /// <summary>
+        /// Queries the varying locations that should be written to the transform feedback buffer.
+        /// </summary>
+        /// <param name="bufferIndex">Index of the transform feedback buffer</param>
+        /// <returns>Varying locations for the specified buffer</returns>
+        public ReadOnlySpan<byte> QueryTransformFeedbackVaryingLocations(int bufferIndex)
+        {
+            return _tfd[bufferIndex].VaryingLocations;
+        }
+
+        /// <summary>
+        /// Queries the stride (in bytes) of the per vertex data written into the transform feedback buffer.
+        /// </summary>
+        /// <param name="bufferIndex">Index of the transform feedback buffer</param>
+        /// <returns>Stride for the specified buffer</returns>
+        public int QueryTransformFeedbackStride(int bufferIndex)
+        {
+            return _tfd[bufferIndex].Stride;
+        }
+
         /// <summary>
         /// Queries if host state forces early depth testing.
         /// </summary>
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
index 64604a99c..a5c7575f2 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
@@ -222,6 +222,35 @@ namespace Ryujinx.Graphics.Gpu.Shader
             }
         }
 
+        /// <summary>
+        /// Queries transform feedback enable state.
+        /// </summary>
+        /// <returns>True if the shader uses transform feedback, false otherwise</returns>
+        public bool QueryTransformFeedbackEnabled()
+        {
+            return _state.TransformFeedbackDescriptors != null;
+        }
+
+        /// <summary>
+        /// Queries the varying locations that should be written to the transform feedback buffer.
+        /// </summary>
+        /// <param name="bufferIndex">Index of the transform feedback buffer</param>
+        /// <returns>Varying locations for the specified buffer</returns>
+        public ReadOnlySpan<byte> QueryTransformFeedbackVaryingLocations(int bufferIndex)
+        {
+            return _state.TransformFeedbackDescriptors[bufferIndex].VaryingLocations;
+        }
+
+        /// <summary>
+        /// Queries the stride (in bytes) of the per vertex data written into the transform feedback buffer.
+        /// </summary>
+        /// <param name="bufferIndex">Index of the transform feedback buffer</param>
+        /// <returns>Stride for the specified buffer</returns>
+        public int QueryTransformFeedbackStride(int bufferIndex)
+        {
+            return _state.TransformFeedbackDescriptors[bufferIndex].Stride;
+        }
+
         /// <summary>
         /// Queries if host state forces early depth testing.
         /// </summary>
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs
index ebbf3b696..6818072b4 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs
@@ -38,6 +38,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// </summary>
         public TessMode TessellationMode { get; }
 
+        /// <summary>
+        /// Transform feedback information, if the shader uses transform feedback. Otherwise, should be null.
+        /// </summary>
+        public TransformFeedbackDescriptor[] TransformFeedbackDescriptors { get; set; }
+
         /// <summary>
         /// Creates a new instance of the GPU accessor state.
         /// </summary>
@@ -61,6 +66,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
             EarlyZForce = earlyZForce;
             Topology = topology;
             TessellationMode = tessellationMode;
+            TransformFeedbackDescriptors = null;
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index 4c6224e3f..110115e76 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <summary>
         /// Version of the codegen (to be changed when codegen or guest format change).
         /// </summary>
-        private const ulong ShaderCodeGenVersion = 2972;
+        private const ulong ShaderCodeGenVersion = 3012;
 
         // Progress reporting helpers
         private volatile int _shaderCount;
@@ -227,7 +227,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
                                             binaryCode,
                                             binaryCode.Slice(binaryCode.Length - entry.Header.Cb1DataSize),
                                             entry.Header.GpuAccessorHeader,
-                                            entry.TextureDescriptors);
+                                            entry.TextureDescriptors,
+                                            null);
 
                                         var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.Compute);
                                         program = Translator.CreateContext(0, gpuAccessor, options).Translate(out shaderProgramInfo);
@@ -251,7 +252,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
 
                                         // Compile shader and create program as the shader program binary got invalidated.
                                         shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, program.Code);
-                                        hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null);
+                                        hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader });
 
                                         task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) =>
                                         {
@@ -293,13 +294,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
 
                             TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader);
 
-                            TranslationFlags flags = DefaultFlags;
-
-                            if (tfd != null)
-                            {
-                                flags |= TranslationFlags.Feedback;
-                            }
-
                             TranslationCounts counts = new TranslationCounts();
 
                             HostShaderCacheEntry[] hostShaderEntries = null;
@@ -343,15 +337,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
                                                 binaryCode,
                                                 binaryCode.Slice(binaryCode.Length - entry.Header.Cb1DataSize),
                                                 entry.Header.GpuAccessorHeader,
-                                                entry.TextureDescriptors);
+                                                entry.TextureDescriptors,
+                                                tfd);
 
-                                            var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags);
+                                            var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags);
 
                                             shaderContexts[i + 1] = Translator.CreateContext(0, gpuAccessor, options, counts);
 
                                             if (entry.Header.SizeA != 0)
                                             {
-                                                var options2 = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags | TranslationFlags.VertexA);
+                                                var options2 = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.VertexA);
 
                                                 shaderContexts[0] = Translator.CreateContext((ulong)entry.Header.Size, gpuAccessor, options2, counts);
                                             }
@@ -431,7 +426,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
                                             hostShaders.Add(hostShader);
                                         }
 
-                                        hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd);
+                                        hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray());
 
                                         task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) =>
                                         {
@@ -622,7 +617,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
 
                 shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code);
 
-                IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null);
+                IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader });
 
                 cpShader = new ShaderBundle(hostProgram, shader);
 
@@ -684,25 +679,20 @@ namespace Ryujinx.Graphics.Gpu.Shader
 
             TransformFeedbackDescriptor[] tfd = GetTransformFeedbackDescriptors(ref state);
 
-            TranslationFlags flags = DefaultFlags;
-
-            if (tfd != null)
-            {
-                flags |= TranslationFlags.Feedback;
-            }
+            gas.TransformFeedbackDescriptors = tfd;
 
             TranslationCounts counts = new TranslationCounts();
 
             if (addresses.VertexA != 0)
             {
-                shaderContexts[0] = DecodeGraphicsShader(channel, gas, counts, flags | TranslationFlags.VertexA, ShaderStage.Vertex, addresses.VertexA);
+                shaderContexts[0] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags | TranslationFlags.VertexA, ShaderStage.Vertex, addresses.VertexA);
             }
 
-            shaderContexts[1] = DecodeGraphicsShader(channel, gas, counts, flags, ShaderStage.Vertex, addresses.Vertex);
-            shaderContexts[2] = DecodeGraphicsShader(channel, gas, counts, flags, ShaderStage.TessellationControl, addresses.TessControl);
-            shaderContexts[3] = DecodeGraphicsShader(channel, gas, counts, flags, ShaderStage.TessellationEvaluation, addresses.TessEvaluation);
-            shaderContexts[4] = DecodeGraphicsShader(channel, gas, counts, flags, ShaderStage.Geometry, addresses.Geometry);
-            shaderContexts[5] = DecodeGraphicsShader(channel, gas, counts, flags, ShaderStage.Fragment, addresses.Fragment);
+            shaderContexts[1] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.Vertex, addresses.Vertex);
+            shaderContexts[2] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.TessellationControl, addresses.TessControl);
+            shaderContexts[3] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.TessellationEvaluation, addresses.TessEvaluation);
+            shaderContexts[4] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.Geometry, addresses.Geometry);
+            shaderContexts[5] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.Fragment, addresses.Fragment);
 
             bool isShaderCacheEnabled = _cacheManager != null;
             bool isShaderCacheReadOnly = false;
@@ -765,7 +755,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
                     hostShaders.Add(hostShader);
                 }
 
-                IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd);
+                IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray());
 
                 gpShaders = new ShaderBundle(hostProgram, shaders);
 
diff --git a/Ryujinx.Graphics.GAL/TransformFeedbackDescriptor.cs b/Ryujinx.Graphics.Gpu/Shader/TransformFeedbackDescriptor.cs
similarity index 85%
rename from Ryujinx.Graphics.GAL/TransformFeedbackDescriptor.cs
rename to Ryujinx.Graphics.Gpu/Shader/TransformFeedbackDescriptor.cs
index 690b9108e..eaa889cc5 100644
--- a/Ryujinx.Graphics.GAL/TransformFeedbackDescriptor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/TransformFeedbackDescriptor.cs
@@ -1,8 +1,8 @@
 using System;
 
-namespace Ryujinx.Graphics.GAL
+namespace Ryujinx.Graphics.Gpu.Shader
 {
-    public struct TransformFeedbackDescriptor
+    struct TransformFeedbackDescriptor
     {
         public int BufferIndex { get; }
         public int Stride      { get; }
diff --git a/Ryujinx.Graphics.OpenGL/Program.cs b/Ryujinx.Graphics.OpenGL/Program.cs
index 785f2f005..959029903 100644
--- a/Ryujinx.Graphics.OpenGL/Program.cs
+++ b/Ryujinx.Graphics.OpenGL/Program.cs
@@ -29,7 +29,7 @@ namespace Ryujinx.Graphics.OpenGL
         private ProgramLinkStatus _status = ProgramLinkStatus.Incomplete;
         private IShader[] _shaders;
 
-        public Program(IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors)
+        public Program(IShader[] shaders)
         {
             Handle = GL.CreateProgram();
 
@@ -42,54 +42,6 @@ namespace Ryujinx.Graphics.OpenGL
                 GL.AttachShader(Handle, shaderHandle);
             }
 
-            if (transformFeedbackDescriptors != null)
-            {
-                List<string> varyings = new List<string>();
-
-                int cbi = 0;
-
-                foreach (var tfd in transformFeedbackDescriptors.OrderBy(x => x.BufferIndex))
-                {
-                    if (tfd.VaryingLocations.Length == 0)
-                    {
-                        continue;
-                    }
-
-                    while (cbi < tfd.BufferIndex)
-                    {
-                        varyings.Add("gl_NextBuffer");
-
-                        cbi++;
-                    }
-
-                    int stride = Math.Min(128 * 4, (tfd.Stride + 3) & ~3);
-
-                    int j = 0;
-
-                    for (; j < tfd.VaryingLocations.Length && j * 4 < stride; j++)
-                    {
-                        byte location = tfd.VaryingLocations[j];
-
-                        varyings.Add(Varying.GetName(location) ?? "gl_SkipComponents1");
-
-                        j += Varying.GetSize(location) - 1;
-                    }
-
-                    int feedbackBytes = j * 4;
-
-                    while (feedbackBytes < stride)
-                    {
-                        int bytes = Math.Min(16, stride - feedbackBytes);
-
-                        varyings.Add($"gl_SkipComponents{(bytes / 4)}");
-
-                        feedbackBytes += bytes;
-                    }
-                }
-
-                GL.TransformFeedbackVaryings(Handle, varyings.Count, varyings.ToArray(), TransformFeedbackMode.InterleavedAttribs);
-            }
-
             GL.LinkProgram(Handle);
 
             _shaders = shaders;
diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs
index d725eb7c1..ceacbf294 100644
--- a/Ryujinx.Graphics.OpenGL/Renderer.cs
+++ b/Ryujinx.Graphics.OpenGL/Renderer.cs
@@ -66,9 +66,9 @@ namespace Ryujinx.Graphics.OpenGL
             return Buffer.Create(size);
         }
 
-        public IProgram CreateProgram(IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors)
+        public IProgram CreateProgram(IShader[] shaders)
         {
-            return new Program(shaders, transformFeedbackDescriptors);
+            return new Program(shaders);
         }
 
         public ISampler CreateSampler(SamplerCreateInfo info)
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs
index f0f8ea351..b48230199 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs
@@ -103,6 +103,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             return _info.Functions[id];
         }
 
+        public TransformFeedbackOutput GetTransformFeedbackOutput(int location, int component)
+        {
+            int index = (AttributeConsts.UserAttributeBase / 4) + location * 4 + component;
+            return _info.TransformFeedbackOutputs[index];
+        }
+
+        public TransformFeedbackOutput GetTransformFeedbackOutput(int location)
+        {
+            int index = location / 4;
+            return _info.TransformFeedbackOutputs[index];
+        }
+
         private void UpdateIndentation()
         {
             _indentation = GetIndentation(_level);
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
index d89565672..55d5551ce 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
@@ -191,6 +191,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
 
                     context.AppendLine();
                 }
+
+                if (context.Config.Stage != ShaderStage.Compute &&
+                    context.Config.Stage != ShaderStage.Fragment &&
+                    context.Config.TransformFeedbackEnabled)
+                {
+                    var tfOutput = context.GetTransformFeedbackOutput(AttributeConsts.PositionX);
+                    if (tfOutput.Valid)
+                    {
+                        context.AppendLine($"layout (xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}) out gl_PerVertex");
+                        context.EnterScope();
+                        context.AppendLine("vec4 gl_Position;");
+                        context.LeaveScope(context.Config.Stage == ShaderStage.TessellationControl ? " gl_out[];" : ";");
+                    }
+                }
             }
             else
             {
@@ -514,7 +528,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             string pass = (context.Config.PassthroughAttributes & (1 << attr)) != 0 ? "passthrough, " : string.Empty;
             string name = $"{DefaultNames.IAttributePrefix}{attr}";
 
-            if ((context.Config.Options.Flags & TranslationFlags.Feedback) != 0)
+            if (context.Config.TransformFeedbackEnabled && context.Config.Stage != ShaderStage.Vertex)
             {
                 for (int c = 0; c < 4; c++)
                 {
@@ -559,13 +573,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             string suffix = OperandManager.IsArrayAttribute(context.Config.Stage, isOutAttr: true) ? "[]" : string.Empty;
             string name = $"{DefaultNames.OAttributePrefix}{attr}{suffix}";
 
-            if ((context.Config.Options.Flags & TranslationFlags.Feedback) != 0)
+            if (context.Config.TransformFeedbackEnabled && context.Config.Stage != ShaderStage.Fragment)
             {
                 for (int c = 0; c < 4; c++)
                 {
                     char swzMask = "xyzw"[c];
 
-                    context.AppendLine($"layout (location = {attr}, component = {c}) out float {name}_{swzMask};");
+                    string xfb = string.Empty;
+
+                    var tfOutput = context.GetTransformFeedbackOutput(attr, c);
+                    if (tfOutput.Valid)
+                    {
+                        xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
+                    }
+
+                    context.AppendLine($"layout (location = {attr}, component = {c}{xfb}) out float {name}_{swzMask};");
                 }
             }
             else
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
index 9680df270..b1bd81880 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
@@ -194,7 +194,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
 
                     return name + $"[{(value >> 4)}]." + swzMask;
                 }
-                else if (config.Options.Flags.HasFlag(TranslationFlags.Feedback))
+                else if (config.TransformFeedbackEnabled && (config.Stage != ShaderStage.Vertex || isOutAttr))
                 {
                     string name = $"{prefix}{(value >> 4)}_{swzMask}";
 
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Varying.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Varying.cs
deleted file mode 100644
index b9b2afb47..000000000
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Varying.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using Ryujinx.Graphics.Shader.Translation;
-
-namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
-{
-    public static class Varying
-    {
-        public static string GetName(int offset)
-        {
-            offset <<= 2;
-
-            if (offset >= AttributeConsts.UserAttributeBase &&
-                offset <  AttributeConsts.UserAttributeEnd)
-            {
-                offset -= AttributeConsts.UserAttributeBase;
-
-                string name = $"{ DefaultNames.OAttributePrefix}{(offset >> 4)}";
-
-                name += "_" + "xyzw"[(offset >> 2) & 3];
-
-                return name;
-            }
-
-            switch (offset)
-            {
-                case AttributeConsts.PositionX:
-                case AttributeConsts.PositionY:
-                case AttributeConsts.PositionZ:
-                case AttributeConsts.PositionW:
-                    return "gl_Position";
-                case AttributeConsts.PointSize:
-                    return "gl_PointSize";
-                case AttributeConsts.ClipDistance0:
-                    return "gl_ClipDistance[0]";
-                case AttributeConsts.ClipDistance1:
-                    return "gl_ClipDistance[1]";
-                case AttributeConsts.ClipDistance2:
-                    return "gl_ClipDistance[2]";
-                case AttributeConsts.ClipDistance3:
-                    return "gl_ClipDistance[3]";
-                case AttributeConsts.ClipDistance4:
-                    return "gl_ClipDistance[4]";
-                case AttributeConsts.ClipDistance5:
-                    return "gl_ClipDistance[5]";
-                case AttributeConsts.ClipDistance6:
-                    return "gl_ClipDistance[6]";
-                case AttributeConsts.ClipDistance7:
-                    return "gl_ClipDistance[7]";
-                case AttributeConsts.VertexId:
-                    return "gl_VertexID";
-            }
-
-            return null;
-        }
-
-        public static int GetSize(int offset)
-        {
-            switch (offset << 2)
-            {
-                case AttributeConsts.PositionX:
-                case AttributeConsts.PositionY:
-                case AttributeConsts.PositionZ:
-                case AttributeConsts.PositionW:
-                    return 4;
-            }
-
-            return 1;
-        }
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/Ryujinx.Graphics.Shader/IGpuAccessor.cs
index 27f6f53b0..b2512868e 100644
--- a/Ryujinx.Graphics.Shader/IGpuAccessor.cs
+++ b/Ryujinx.Graphics.Shader/IGpuAccessor.cs
@@ -131,6 +131,21 @@ namespace Ryujinx.Graphics.Shader
             return TextureFormat.R8G8B8A8Unorm;
         }
 
+        bool QueryTransformFeedbackEnabled()
+        {
+            return false;
+        }
+
+        ReadOnlySpan<byte> QueryTransformFeedbackVaryingLocations(int bufferIndex)
+        {
+            return ReadOnlySpan<byte>.Empty;
+        }
+
+        int QueryTransformFeedbackStride(int bufferIndex)
+        {
+            return 0;
+        }
+
         bool QueryEarlyZForce()
         {
             return false;
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
index 61cc167a7..31c71f20f 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
@@ -64,6 +64,24 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
                 context.LeaveFunction();
             }
 
+            if (config.TransformFeedbackEnabled)
+            {
+                for (int tfbIndex = 0; tfbIndex < 4; tfbIndex++)
+                {
+                    var locations = config.GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
+                    var stride = config.GpuAccessor.QueryTransformFeedbackStride(tfbIndex);
+
+                    for (int j = 0; j < locations.Length; j++)
+                    {
+                        byte location = locations[j];
+                        if (location < 0x80)
+                        {
+                            context.Info.TransformFeedbackOutputs[location] = new TransformFeedbackOutput(tfbIndex, j * 4, stride);
+                        }
+                    }
+                }
+            }
+
             return context.Info;
         }
 
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
index aeaa03ec6..933f265f9 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
@@ -2,15 +2,35 @@ using System.Collections.Generic;
 
 namespace Ryujinx.Graphics.Shader.StructuredIr
 {
+    struct TransformFeedbackOutput
+    {
+        public readonly bool Valid;
+        public readonly int Buffer;
+        public readonly int Offset;
+        public readonly int Stride;
+
+        public TransformFeedbackOutput(int buffer, int offset, int stride)
+        {
+            Valid = true;
+            Buffer = buffer;
+            Offset = offset;
+            Stride = stride;
+        }
+    }
+
     class StructuredProgramInfo
     {
         public List<StructuredFunction> Functions { get; }
 
         public HelperFunctionsMask HelperFunctionsMask { get; set; }
 
+        public TransformFeedbackOutput[] TransformFeedbackOutputs { get; }
+
         public StructuredProgramInfo()
         {
             Functions = new List<StructuredFunction>();
+
+            TransformFeedbackOutputs = new TransformFeedbackOutput[0x80];
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
index 9d5a40706..21f170414 100644
--- a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
+++ b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
@@ -33,6 +33,8 @@ namespace Ryujinx.Graphics.Shader.Translation
 
         public TranslationOptions Options { get; }
 
+        public bool TransformFeedbackEnabled { get; }
+
         public int Size { get; private set; }
 
         public byte ClipDistancesWritten { get; private set; }
@@ -128,6 +130,7 @@ namespace Ryujinx.Graphics.Shader.Translation
             OmapTargets              = header.OmapTargets;
             OmapSampleMask           = header.OmapSampleMask;
             OmapDepth                = header.OmapDepth;
+            TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled();
         }
 
         public int GetDepthRegister()
diff --git a/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs b/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs
index 9af95389a..1874dec3b 100644
--- a/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs
+++ b/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs
@@ -9,7 +9,6 @@ namespace Ryujinx.Graphics.Shader.Translation
 
         VertexA   = 1 << 0,
         Compute   = 1 << 1,
-        Feedback  = 1 << 2,
-        DebugMode = 1 << 3
+        DebugMode = 1 << 2
     }
 }
\ No newline at end of file