diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index 4e1e130cb8..353c5dfedd 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 = 2826;
+        private const ulong ShaderCodeGenVersion = 2816;
 
         // Progress reporting helpers
         private volatile int _shaderCount;
diff --git a/Ryujinx.Graphics.Shader/Decoders/Decoder.cs b/Ryujinx.Graphics.Shader/Decoders/Decoder.cs
index 714aaad342..80d2cb4af1 100644
--- a/Ryujinx.Graphics.Shader/Decoders/Decoder.cs
+++ b/Ryujinx.Graphics.Shader/Decoders/Decoder.cs
@@ -319,6 +319,13 @@ namespace Ryujinx.Graphics.Shader.Decoders
                             config.SetInputUserAttribute(index, perPatch);
                         }
                     }
+
+                    if (!isStore &&
+                        ((attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0) ||
+                        (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)))
+                    {
+                        config.SetUsedFeature(FeatureFlags.FixedFuncAttr);
+                    }
                 }
             }
         }
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs
index f82b835cab..e2131602a0 100644
--- a/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs
@@ -42,7 +42,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
                 }
                 else if (op.SrcB == RegisterConsts.RegisterZeroIndex || op.P)
                 {
-                    int offset = op.Imm11 + index * 4;
+                    int offset = FixedFuncToUserAttribute(context.Config, op.Imm11 + index * 4, op.O);
 
                     context.FlagAttributeRead(offset);
 
@@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
                 }
                 else
                 {
-                    int offset = op.Imm11 + index * 4;
+                    int offset = FixedFuncToUserAttribute(context.Config, op.Imm11 + index * 4, op.O);
 
                     context.FlagAttributeRead(offset);
 
@@ -101,6 +101,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
                     int offset = op.Imm11 + index * 4;
 
+                    if (!context.Config.IsUsedOutputAttribute(offset))
+                    {
+                        return;
+                    }
+
+                    offset = FixedFuncToUserAttribute(context.Config, offset, isOutput: true);
+
                     context.FlagAttributeWritten(offset);
 
                     Operand dest = op.P ? AttributePerPatch(offset) : Attribute(offset);
@@ -118,6 +125,8 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
             Operand res;
 
+            bool isFixedFunc = false;
+
             if (op.Idx)
             {
                 Operand userAttrOffset = context.ISubtract(GetSrcReg(context, op.SrcA), Const(AttributeConsts.UserAttributeBase));
@@ -130,7 +139,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
             }
             else
             {
-                res = Attribute(op.Imm10);
+                isFixedFunc = TryFixedFuncToUserAttributeIpa(context, op.Imm10, out res);
 
                 if (op.Imm10 >= AttributeConsts.UserAttributeBase && op.Imm10 < AttributeConsts.UserAttributeEnd)
                 {
@@ -143,7 +152,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
                 }
             }
 
-            if (op.IpaOp == IpaOp.Multiply)
+            if (op.IpaOp == IpaOp.Multiply && !isFixedFunc)
             {
                 Operand srcB = GetSrcReg(context, op.SrcB);
 
@@ -204,5 +213,72 @@ namespace Ryujinx.Graphics.Shader.Instructions
                 context.EndPrimitive();
             }
         }
+
+        private static bool TryFixedFuncToUserAttributeIpa(EmitterContext context, int attr, out Operand selectedAttr)
+        {
+            if (attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.BackColorDiffuseR)
+            {
+                // TODO: If two sided rendering is enabled, then this should return
+                // FrontColor if the fragment is front facing, and back color otherwise.
+                int index = (attr - AttributeConsts.FrontColorDiffuseR) >> 4;
+                int userAttrIndex = context.Config.GetFreeUserAttribute(isOutput: false, index);
+                Operand frontAttr = Attribute(AttributeConsts.UserAttributeBase + userAttrIndex * 16 + (attr & 0xf));
+
+                context.Config.SetInputUserAttributeFixedFunc(userAttrIndex);
+
+                selectedAttr = frontAttr;
+                return true;
+            }
+            else if (attr >= AttributeConsts.BackColorDiffuseR && attr < AttributeConsts.ClipDistance0)
+            {
+                selectedAttr = ConstF(((attr >> 2) & 3) == 3 ? 1f : 0f);
+                return true;
+            }
+            else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)
+            {
+                selectedAttr = Attribute(FixedFuncToUserAttribute(context.Config, attr, AttributeConsts.TexCoordBase, 4, isOutput: false));
+                return true;
+            }
+
+            selectedAttr = Attribute(attr);
+            return false;
+        }
+
+        private static int FixedFuncToUserAttribute(ShaderConfig config, int attr, bool isOutput)
+        {
+            if (attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0)
+            {
+                attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.FrontColorDiffuseR, 0, isOutput);
+            }
+            else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)
+            {
+                attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.TexCoordBase, 4, isOutput);
+            }
+
+            return attr;
+        }
+
+        private static int FixedFuncToUserAttribute(ShaderConfig config, int attr, int baseAttr, int baseIndex, bool isOutput)
+        {
+            int index = (attr - baseAttr) >> 4;
+            int userAttrIndex = config.GetFreeUserAttribute(isOutput, index);
+
+            if ((uint)userAttrIndex < Constants.MaxAttributes)
+            {
+                userAttrIndex += baseIndex;
+                attr = AttributeConsts.UserAttributeBase + userAttrIndex * 16 + (attr & 0xf);
+
+                if (isOutput)
+                {
+                    config.SetOutputUserAttributeFixedFunc(userAttrIndex);
+                }
+                else
+                {
+                    config.SetInputUserAttributeFixedFunc(userAttrIndex);
+                }
+            }
+
+            return attr;
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs
index 128013d8c8..370af00973 100644
--- a/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs
+++ b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs
@@ -2,33 +2,52 @@ namespace Ryujinx.Graphics.Shader.Translation
 {
     static class AttributeConsts
     {
-        public const int TessLevelOuter0 = 0x000;
-        public const int TessLevelOuter1 = 0x004;
-        public const int TessLevelOuter2 = 0x008;
-        public const int TessLevelOuter3 = 0x00c;
-        public const int TessLevelInner0 = 0x010;
-        public const int TessLevelInner1 = 0x014;
-        public const int Layer           = 0x064;
-        public const int PointSize       = 0x06c;
-        public const int PositionX       = 0x070;
-        public const int PositionY       = 0x074;
-        public const int PositionZ       = 0x078;
-        public const int PositionW       = 0x07c;
-        public const int ClipDistance0   = 0x2c0;
-        public const int ClipDistance1   = 0x2c4;
-        public const int ClipDistance2   = 0x2c8;
-        public const int ClipDistance3   = 0x2cc;
-        public const int ClipDistance4   = 0x2d0;
-        public const int ClipDistance5   = 0x2d4;
-        public const int ClipDistance6   = 0x2d8;
-        public const int ClipDistance7   = 0x2dc;
-        public const int PointCoordX     = 0x2e0;
-        public const int PointCoordY     = 0x2e4;
-        public const int TessCoordX      = 0x2f0;
-        public const int TessCoordY      = 0x2f4;
-        public const int InstanceId      = 0x2f8;
-        public const int VertexId        = 0x2fc;
-        public const int FrontFacing     = 0x3fc;
+        public const int TessLevelOuter0     = 0x000;
+        public const int TessLevelOuter1     = 0x004;
+        public const int TessLevelOuter2     = 0x008;
+        public const int TessLevelOuter3     = 0x00c;
+        public const int TessLevelInner0     = 0x010;
+        public const int TessLevelInner1     = 0x014;
+        public const int Layer               = 0x064;
+        public const int PointSize           = 0x06c;
+        public const int PositionX           = 0x070;
+        public const int PositionY           = 0x074;
+        public const int PositionZ           = 0x078;
+        public const int PositionW           = 0x07c;
+        public const int FrontColorDiffuseR  = 0x280;
+        public const int FrontColorDiffuseG  = 0x284;
+        public const int FrontColorDiffuseB  = 0x288;
+        public const int FrontColorDiffuseA  = 0x28c;
+        public const int FrontColorSpecularR = 0x290;
+        public const int FrontColorSpecularG = 0x294;
+        public const int FrontColorSpecularB = 0x298;
+        public const int FrontColorSpecularA = 0x29c;
+        public const int BackColorDiffuseR   = 0x2a0;
+        public const int BackColorDiffuseG   = 0x2a4;
+        public const int BackColorDiffuseB   = 0x2a8;
+        public const int BackColorDiffuseA   = 0x2ac;
+        public const int BackColorSpecularR  = 0x2b0;
+        public const int BackColorSpecularG  = 0x2b4;
+        public const int BackColorSpecularB  = 0x2b8;
+        public const int BackColorSpecularA  = 0x2bc;
+        public const int ClipDistance0       = 0x2c0;
+        public const int ClipDistance1       = 0x2c4;
+        public const int ClipDistance2       = 0x2c8;
+        public const int ClipDistance3       = 0x2cc;
+        public const int ClipDistance4       = 0x2d0;
+        public const int ClipDistance5       = 0x2d4;
+        public const int ClipDistance6       = 0x2d8;
+        public const int ClipDistance7       = 0x2dc;
+        public const int PointCoordX         = 0x2e0;
+        public const int PointCoordY         = 0x2e4;
+        public const int TessCoordX          = 0x2f0;
+        public const int TessCoordY          = 0x2f4;
+        public const int InstanceId          = 0x2f8;
+        public const int VertexId            = 0x2fc;
+        public const int TexCoordCount       = 10;
+        public const int TexCoordBase        = 0x300;
+        public const int TexCoordEnd         = TexCoordBase + TexCoordCount * 16;
+        public const int FrontFacing         = 0x3fc;
 
         public const int UserAttributesCount = 32;
         public const int UserAttributeBase   = 0x80;
diff --git a/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs b/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs
index f602ea64dd..a2363fcbb4 100644
--- a/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs
+++ b/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs
@@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.Shader.Translation
         RtLayer = 1 << 4,
         CbIndexing = 1 << 5,
         IaIndexing = 1 << 6,
-        OaIndexing = 1 << 7
+        OaIndexing = 1 << 7,
+        FixedFuncAttr = 1 << 8
     }
 }
diff --git a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
index 2314016e2f..df14a5edca 100644
--- a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
+++ b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
@@ -43,11 +43,14 @@ namespace Ryujinx.Graphics.Shader.Translation
 
         private readonly TranslationCounts _counts;
 
+        public bool NextUsesFixedFuncAttributes { get; private set; }
         public int UsedInputAttributes { get; private set; }
-        public int UsedInputAttributesPerPatch { get; private set; }
         public int UsedOutputAttributes { get; private set; }
+        public int UsedInputAttributesPerPatch { get; private set; }
         public int UsedOutputAttributesPerPatch { get; private set; }
         public int PassthroughAttributes { get; private set; }
+        private int _nextUsedInputAttributes;
+        private int _thisUsedInputAttributes;
 
         private int _usedConstantBuffers;
         private int _usedStorageBuffers;
@@ -224,6 +227,16 @@ namespace Ryujinx.Graphics.Shader.Translation
             }
         }
 
+        public void SetInputUserAttributeFixedFunc(int index)
+        {
+            UsedInputAttributes |= 1 << index;
+        }
+
+        public void SetOutputUserAttributeFixedFunc(int index)
+        {
+            UsedOutputAttributes |= 1 << index;
+        }
+
         public void SetInputUserAttribute(int index, bool perPatch)
         {
             if (perPatch)
@@ -232,7 +245,10 @@ namespace Ryujinx.Graphics.Shader.Translation
             }
             else
             {
-                UsedInputAttributes |= 1 << index;
+                int mask = 1 << index;
+
+                UsedInputAttributes |= mask;
+                _thisUsedInputAttributes |= mask;
             }
         }
 
@@ -248,8 +264,16 @@ namespace Ryujinx.Graphics.Shader.Translation
             }
         }
 
+        public void MergeFromtNextStage(ShaderConfig config)
+        {
+            NextUsesFixedFuncAttributes = config.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr);
+            MergeOutputUserAttributes(config.UsedInputAttributes, config.UsedInputAttributesPerPatch);
+        }
+
         public void MergeOutputUserAttributes(int mask, int maskPerPatch)
         {
+            _nextUsedInputAttributes = mask;
+
             if (GpPassthrough)
             {
                 PassthroughAttributes = mask & ~UsedOutputAttributes;
@@ -261,6 +285,47 @@ namespace Ryujinx.Graphics.Shader.Translation
             }
         }
 
+        public bool IsUsedOutputAttribute(int attr)
+        {
+            // The check for fixed function attributes on the next stage is conservative,
+            // returning false if the output is just not used by the next stage is also valid.
+            if (NextUsesFixedFuncAttributes &&
+                attr >= AttributeConsts.UserAttributeBase &&
+                attr < AttributeConsts.UserAttributeEnd)
+            {
+                int index = (attr - AttributeConsts.UserAttributeBase) >> 4;
+                return (_nextUsedInputAttributes & (1 << index)) != 0;
+            }
+
+            return true;
+        }
+
+        public int GetFreeUserAttribute(bool isOutput, int index)
+        {
+            int useMask = isOutput ? _nextUsedInputAttributes : _thisUsedInputAttributes;
+            int bit = -1;
+
+            while (useMask != -1)
+            {
+                bit = BitOperations.TrailingZeroCount(~useMask);
+
+                if (bit == 32)
+                {
+                    bit = -1;
+                    break;
+                }
+                else if (index < 1)
+                {
+                    break;
+                }
+
+                useMask |= 1 << bit;
+                index--;
+            }
+
+            return bit;
+        }
+
         public void SetAllInputUserAttributes()
         {
             UsedInputAttributes |= Constants.AllAttributesMask;
diff --git a/Ryujinx.Graphics.Shader/Translation/Translator.cs b/Ryujinx.Graphics.Shader/Translation/Translator.cs
index a658697b7f..cef25350a3 100644
--- a/Ryujinx.Graphics.Shader/Translation/Translator.cs
+++ b/Ryujinx.Graphics.Shader/Translation/Translator.cs
@@ -232,6 +232,22 @@ namespace Ryujinx.Graphics.Shader.Translation
 
                 usedAttributesPerPatch &= ~(1 << index);
             }
+
+            if (config.NextUsesFixedFuncAttributes)
+            {
+                for (int i = 0; i < 4 + AttributeConsts.TexCoordCount; i++)
+                {
+                    int index = config.GetFreeUserAttribute(isOutput: true, i);
+                    if (index < 0)
+                    {
+                        break;
+                    }
+
+                    InitializeOutput(context, AttributeConsts.UserAttributeBase + index * 16, perPatch: false);
+
+                    config.SetOutputUserAttributeFixedFunc(index);
+                }
+            }
         }
 
         private static void InitializeOutput(EmitterContext context, int baseAttr, bool perPatch)
diff --git a/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
index b19e39af76..b4e61cb63c 100644
--- a/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
+++ b/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
@@ -136,9 +136,7 @@ namespace Ryujinx.Graphics.Shader.Translation
         {
             if (nextStage != null)
             {
-                _config.MergeOutputUserAttributes(
-                    nextStage._config.UsedInputAttributes,
-                    nextStage._config.UsedInputAttributesPerPatch);
+                _config.MergeFromtNextStage(nextStage._config);
             }
 
             FunctionCode[] code = EmitShader(_program, _config, initializeOutputs: other == null, out _);