diff --git a/src/shader_recompiler/backend/glsl/emit_context.cpp b/src/shader_recompiler/backend/glsl/emit_context.cpp
index ed10eca8a..f0e9dffc2 100644
--- a/src/shader_recompiler/backend/glsl/emit_context.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_context.cpp
@@ -200,6 +200,27 @@ std::string_view OutputPrimitive(OutputTopology topology) {
     throw InvalidArgument("Invalid output topology {}", topology);
 }
 
+void SetupLegacyOutPerVertex(EmitContext& ctx, std::string& header) {
+    if (!ctx.info.stores_legacy_varyings) {
+        return;
+    }
+    if (ctx.info.stores_fixed_fnc_textures) {
+        header += "vec4 gl_TexCoord[8];";
+    }
+    if (ctx.info.stores_color_front_diffuse) {
+        header += "vec4 gl_FrontColor;";
+    }
+    if (ctx.info.stores_color_front_specular) {
+        header += "vec4 gl_FrontSecondaryColor;";
+    }
+    if (ctx.info.stores_color_back_diffuse) {
+        header += "vec4 gl_BackColor;";
+    }
+    if (ctx.info.stores_color_back_specular) {
+        header += "vec4 gl_BackSecondaryColor;";
+    }
+}
+
 void SetupOutPerVertex(EmitContext& ctx, std::string& header) {
     if (!StoresPerVertexAttributes(ctx.stage)) {
         return;
@@ -215,18 +236,34 @@ void SetupOutPerVertex(EmitContext& ctx, std::string& header) {
         ctx.stage != Stage::Geometry) {
         header += "int gl_ViewportIndex;";
     }
+    SetupLegacyOutPerVertex(ctx, header);
     header += "};";
     if (ctx.info.stores_viewport_index && ctx.stage == Stage::Geometry) {
         header += "out int gl_ViewportIndex;";
     }
 }
+
+void SetupLegacyInPerFragment(EmitContext& ctx, std::string& header) {
+    if (!ctx.info.loads_legacy_varyings) {
+        return;
+    }
+    header += "in gl_PerFragment{";
+    if (ctx.info.loads_fixed_fnc_textures) {
+        header += "vec4 gl_TexCoord[8];";
+    }
+    if (ctx.info.loads_color_front_diffuse) {
+        header += "vec4 gl_Color;";
+    }
+    header += "};";
+}
+
 } // Anonymous namespace
 
 EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile& profile_,
                          const RuntimeInfo& runtime_info_)
     : info{program.info}, profile{profile_}, runtime_info{runtime_info_} {
     header += "#pragma optionNV(fastmath off)\n";
-    SetupExtensions(header);
+    SetupExtensions();
     stage = program.stage;
     switch (program.stage) {
     case Stage::VertexA:
@@ -271,6 +308,8 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile
         break;
     }
     SetupOutPerVertex(*this, header);
+    SetupLegacyInPerFragment(*this, header);
+
     for (size_t index = 0; index < info.input_generics.size(); ++index) {
         const auto& generic{info.input_generics[index]};
         if (generic.used) {
@@ -306,7 +345,7 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile
     DefineConstants();
 }
 
-void EmitContext::SetupExtensions(std::string&) {
+void EmitContext::SetupExtensions() {
     if (profile.support_gl_texture_shadow_lod) {
         header += "#extension GL_EXT_texture_shadow_lod : enable\n";
     }
diff --git a/src/shader_recompiler/backend/glsl/emit_context.h b/src/shader_recompiler/backend/glsl/emit_context.h
index 685f56089..8fa87c02c 100644
--- a/src/shader_recompiler/backend/glsl/emit_context.h
+++ b/src/shader_recompiler/backend/glsl/emit_context.h
@@ -157,7 +157,7 @@ public:
     bool uses_cc_carry{};
 
 private:
-    void SetupExtensions(std::string& header);
+    void SetupExtensions();
     void DefineConstantBuffers(Bindings& bindings);
     void DefineStorageBuffers(Bindings& bindings);
     void DefineGenericOutput(size_t index, u32 invocations);
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl.cpp b/src/shader_recompiler/backend/glsl/emit_glsl.cpp
index 3e6add7cd..d76b63b2d 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl.cpp
@@ -166,7 +166,7 @@ void EmitCode(EmitContext& ctx, const IR::Program& program) {
 }
 
 std::string GlslVersionSpecifier(const EmitContext& ctx) {
-    if (ctx.uses_y_direction) {
+    if (ctx.uses_y_direction || ctx.info.stores_legacy_varyings) {
         return " compatibility";
     }
     return "";
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
index 96296ad28..3eeccfb3c 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
@@ -98,6 +98,10 @@ void GetCbuf16(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding, const
                 bit_offset);
     }
 }
+
+u32 TexCoordIndex(IR::Attribute attr) {
+    return (static_cast<u32>(attr) - static_cast<u32>(IR::Attribute::FixedFncTexture0S)) / 4;
+}
 } // Anonymous namespace
 
 void EmitGetCbufU8(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
@@ -178,6 +182,17 @@ void EmitGetAttribute(EmitContext& ctx, IR::Inst& inst, IR::Attribute attr,
         ctx.AddF32("{}=in_attr{}{}.{};", inst, index, InputVertexIndex(ctx, vertex), swizzle);
         return;
     }
+    // GLSL only exposes 8 legacy texcoords
+    if (attr >= IR::Attribute::FixedFncTexture8S && attr <= IR::Attribute::FixedFncTexture9Q) {
+        // LOG_WARNING(..., "GLSL does not allow access to gl_TexCoord[{}]", TexCoordIndex(attr));
+        ctx.AddF32("{}=0.f;", inst);
+        return;
+    }
+    if (attr >= IR::Attribute::FixedFncTexture0S && attr <= IR::Attribute::FixedFncTexture7Q) {
+        const u32 index{TexCoordIndex(attr)};
+        ctx.AddF32("{}=gl_TexCoord[{}].{};", inst, index, swizzle);
+        return;
+    }
     switch (attr) {
     case IR::Attribute::PrimitiveId:
         ctx.AddF32("{}=itof(gl_PrimitiveID);", inst);
@@ -227,19 +242,29 @@ void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, std::string_view val
                       [[maybe_unused]] std::string_view vertex) {
     if (IR::IsGeneric(attr)) {
         const u32 index{IR::GenericAttributeIndex(attr)};
-        const u32 element{IR::GenericAttributeElement(attr)};
-        const GenericElementInfo& info{ctx.output_generics.at(index).at(element)};
+        const u32 attr_element{IR::GenericAttributeElement(attr)};
+        const GenericElementInfo& info{ctx.output_generics.at(index).at(attr_element)};
         const auto output_decorator{OutputVertexIndex(ctx)};
         if (info.num_components == 1) {
             ctx.Add("{}{}={};", info.name, output_decorator, value);
         } else {
-            const u32 index_element{element - info.first_element};
+            const u32 index_element{attr_element - info.first_element};
             ctx.Add("{}{}.{}={};", info.name, output_decorator, "xyzw"[index_element], value);
         }
         return;
     }
     const u32 element{static_cast<u32>(attr) % 4};
     const char swizzle{"xyzw"[element]};
+    // GLSL only exposes 8 legacy texcoords
+    if (attr >= IR::Attribute::FixedFncTexture8S && attr <= IR::Attribute::FixedFncTexture9Q) {
+        // LOG_WARNING(..., "GLSL does not allow access to gl_TexCoord[{}]", TexCoordIndex(attr));
+        return;
+    }
+    if (attr >= IR::Attribute::FixedFncTexture0S && attr <= IR::Attribute::FixedFncTexture7Q) {
+        const u32 index{TexCoordIndex(attr)};
+        ctx.Add("gl_TexCoord[{}].{}={};", index, swizzle, value);
+        return;
+    }
     switch (attr) {
     case IR::Attribute::Layer:
         if (ctx.stage != Stage::Geometry &&
diff --git a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
index dc78cdefb..10d2822ae 100644
--- a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
+++ b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
@@ -35,6 +35,7 @@ void GetAttribute(Info& info, IR::Attribute attr) {
     }
     if (attr >= IR::Attribute::FixedFncTexture0S && attr <= IR::Attribute::FixedFncTexture9Q) {
         info.loads_fixed_fnc_textures = true;
+        info.loads_legacy_varyings = true;
         return;
     }
     switch (attr) {
@@ -52,6 +53,7 @@ void GetAttribute(Info& info, IR::Attribute attr) {
     case IR::Attribute::ColorFrontDiffuseB:
     case IR::Attribute::ColorFrontDiffuseA:
         info.loads_color_front_diffuse = true;
+        info.loads_legacy_varyings = true;
         break;
     case IR::Attribute::PointSpriteS:
     case IR::Attribute::PointSpriteT:
@@ -82,6 +84,7 @@ void SetAttribute(Info& info, IR::Attribute attr) {
     }
     if (attr >= IR::Attribute::FixedFncTexture0S && attr <= IR::Attribute::FixedFncTexture9Q) {
         info.stores_fixed_fnc_textures = true;
+        info.stores_legacy_varyings = true;
         return;
     }
     switch (attr) {
@@ -105,24 +108,28 @@ void SetAttribute(Info& info, IR::Attribute attr) {
     case IR::Attribute::ColorFrontDiffuseB:
     case IR::Attribute::ColorFrontDiffuseA:
         info.stores_color_front_diffuse = true;
+        info.stores_legacy_varyings = true;
         break;
     case IR::Attribute::ColorFrontSpecularR:
     case IR::Attribute::ColorFrontSpecularG:
     case IR::Attribute::ColorFrontSpecularB:
     case IR::Attribute::ColorFrontSpecularA:
         info.stores_color_front_specular = true;
+        info.stores_legacy_varyings = true;
         break;
     case IR::Attribute::ColorBackDiffuseR:
     case IR::Attribute::ColorBackDiffuseG:
     case IR::Attribute::ColorBackDiffuseB:
     case IR::Attribute::ColorBackDiffuseA:
         info.stores_color_back_diffuse = true;
+        info.stores_legacy_varyings = true;
         break;
     case IR::Attribute::ColorBackSpecularR:
     case IR::Attribute::ColorBackSpecularG:
     case IR::Attribute::ColorBackSpecularB:
     case IR::Attribute::ColorBackSpecularA:
-        info.stores_color_front_specular = true;
+        info.stores_color_back_specular = true;
+        info.stores_legacy_varyings = true;
         break;
     case IR::Attribute::ClipDistance0:
     case IR::Attribute::ClipDistance1:
diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h
index 9f7f0b42c..7536c9caf 100644
--- a/src/shader_recompiler/shader_info.h
+++ b/src/shader_recompiler/shader_info.h
@@ -128,6 +128,7 @@ struct Info {
     bool loads_instance_id{};
     bool loads_vertex_id{};
     bool loads_front_face{};
+    bool loads_legacy_varyings{};
 
     bool loads_tess_coord{};
 
@@ -150,6 +151,7 @@ struct Info {
     bool stores_clip_distance{};
     bool stores_fog_coordinate{};
     bool stores_viewport_mask{};
+    bool stores_legacy_varyings{};
 
     bool stores_tess_level_outer{};
     bool stores_tess_level_inner{};