diff --git a/Ryujinx.Graphics.GAL/BlendFactor.cs b/Ryujinx.Graphics.GAL/BlendFactor.cs
index 135873e992..4149ad514d 100644
--- a/Ryujinx.Graphics.GAL/BlendFactor.cs
+++ b/Ryujinx.Graphics.GAL/BlendFactor.cs
@@ -38,4 +38,25 @@ namespace Ryujinx.Graphics.GAL
         Src1AlphaGl             = 0xc902,
         OneMinusSrc1AlphaGl     = 0xc903
     }
+
+    public static class BlendFactorExtensions
+    {
+        public static bool IsDualSource(this BlendFactor factor)
+        {
+            switch (factor)
+            {
+                case BlendFactor.Src1Color:
+                case BlendFactor.Src1ColorGl:
+                case BlendFactor.Src1Alpha:
+                case BlendFactor.Src1AlphaGl:
+                case BlendFactor.OneMinusSrc1Color:
+                case BlendFactor.OneMinusSrc1ColorGl:
+                case BlendFactor.OneMinusSrc1Alpha:
+                case BlendFactor.OneMinusSrc1AlphaGl:
+                    return true;
+            }
+
+            return false;
+        }
+    }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs
index 62df15e793..a8af549701 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs
@@ -328,5 +328,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                 Signal();
             }
         }
+
+        /// <summary>
+        /// Sets the dual-source blend enabled state.
+        /// </summary>
+        /// <param name="enabled">True if blending is enabled and using dual-source blend</param>
+        public void SetDualSourceBlendEnabled(bool enabled)
+        {
+            if (enabled != _graphics.DualSourceBlendEnable)
+            {
+                _graphics.DualSourceBlendEnable = enabled;
+
+                Signal();
+            }
+        }
     }
 }
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
index d7d197adb4..00e09a3102 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
@@ -1183,6 +1183,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
             bool blendIndependent = _state.State.BlendIndependent;
             ColorF blendConstant = _state.State.BlendConstant;
 
+            bool dualSourceBlendEnabled = false;
+
             if (blendIndependent)
             {
                 for (int index = 0; index < Constants.TotalRenderTargets; index++)
@@ -1200,6 +1202,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                         FilterBlendFactor(blend.AlphaSrcFactor, index),
                         FilterBlendFactor(blend.AlphaDstFactor, index));
 
+                    if (enable &&
+                        (blend.ColorSrcFactor.IsDualSource() ||
+                        blend.ColorDstFactor.IsDualSource() ||
+                        blend.AlphaSrcFactor.IsDualSource() ||
+                        blend.AlphaDstFactor.IsDualSource()))
+                    {
+                        dualSourceBlendEnabled = true;
+                    }
+
                     _pipeline.BlendDescriptors[index] = descriptor;
                     _context.Renderer.Pipeline.SetBlendState(index, descriptor);
                 }
@@ -1219,12 +1230,23 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                     FilterBlendFactor(blend.AlphaSrcFactor, 0),
                     FilterBlendFactor(blend.AlphaDstFactor, 0));
 
+                if (enable &&
+                    (blend.ColorSrcFactor.IsDualSource() ||
+                    blend.ColorDstFactor.IsDualSource() ||
+                    blend.AlphaSrcFactor.IsDualSource() ||
+                    blend.AlphaDstFactor.IsDualSource()))
+                {
+                    dualSourceBlendEnabled = true;
+                }
+
                 for (int index = 0; index < Constants.TotalRenderTargets; index++)
                 {
                     _pipeline.BlendDescriptors[index] = descriptor;
                     _context.Renderer.Pipeline.SetBlendState(index, descriptor);
                 }
             }
+
+            _currentSpecState.SetDualSourceBlendEnabled(dualSourceBlendEnabled);
         }
 
         /// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
index 97173c964b..17639ca17d 100644
--- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
@@ -141,6 +141,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
             return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters;
         }
 
+        /// <inheritdoc/>
+        public bool QueryDualSourceBlendEnable()
+        {
+            return _oldSpecState.GraphicsState.DualSourceBlendEnable;
+        }
+
         /// <inheritdoc/>
         public InputTopology QueryPrimitiveTopology()
         {
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
index edc5a8a085..0b87cc9101 100644
--- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
         private const ushort FileFormatVersionMajor = 1;
         private const ushort FileFormatVersionMinor = 2;
         private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
-        private const uint CodeGenVersion = 4368;
+        private const uint CodeGenVersion = 4404;
 
         private const string SharedTocFileName = "shared.toc";
         private const string SharedDataFileName = "shared.data";
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
index 05631a2107..3e8167331f 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
@@ -157,6 +157,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
             return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer;
         }
 
+        /// <inheritdoc/>
+        public bool QueryDualSourceBlendEnable()
+        {
+            return _state.GraphicsState.DualSourceBlendEnable;
+        }
+
         /// <inheritdoc/>
         public InputTopology QueryPrimitiveTopology()
         {
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
index 70ac501703..5247a096fa 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
@@ -92,6 +92,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// </summary>
         public Array8<AttributeType> FragmentOutputTypes;
 
+        /// <summary>
+        /// Indicates whether dual source blend is enabled.
+        /// </summary>
+        public bool DualSourceBlendEnable;
+
         /// <summary>
         /// Creates a new GPU graphics state.
         /// </summary>
@@ -111,6 +116,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
         /// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
         /// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param>
+        /// <param name="dualSourceBlendEnable">Type of the vertex attributes consumed by the shader</param>
         public GpuChannelGraphicsState(
             bool earlyZForce,
             PrimitiveTopology topology,
@@ -127,7 +133,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
             ref Array32<AttributeType> attributeTypes,
             bool hasConstantBufferDrawParameters,
             bool hasUnalignedStorageBuffer,
-            ref Array8<AttributeType> fragmentOutputTypes)
+            ref Array8<AttributeType> fragmentOutputTypes,
+            bool dualSourceBlendEnable)
         {
             EarlyZForce = earlyZForce;
             Topology = topology;
@@ -145,6 +152,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
             HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
             HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
             FragmentOutputTypes = fragmentOutputTypes;
+            DualSourceBlendEnable = dualSourceBlendEnable;
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
index 856507cd74..b2c4fccdb9 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
@@ -535,6 +535,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
                 return false;
             }
 
+            if (graphicsState.DualSourceBlendEnable != GraphicsState.DualSourceBlendEnable)
+            {
+                return false;
+            }
+
             return Matches(channel, ref poolState, checkTextures, isCompute: false);
         }
 
diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs
index 62996bd0a1..6b6d0289c1 100644
--- a/Ryujinx.Graphics.OpenGL/Pipeline.cs
+++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -833,31 +833,13 @@ namespace Ryujinx.Graphics.OpenGL
                 (BlendingFactorSrc)blend.AlphaSrcFactor.Convert(),
                 (BlendingFactorDest)blend.AlphaDstFactor.Convert());
 
-            static bool IsDualSource(BlendFactor factor)
-            {
-                switch (factor)
-                {
-                    case BlendFactor.Src1Color:
-                    case BlendFactor.Src1ColorGl:
-                    case BlendFactor.Src1Alpha:
-                    case BlendFactor.Src1AlphaGl:
-                    case BlendFactor.OneMinusSrc1Color:
-                    case BlendFactor.OneMinusSrc1ColorGl:
-                    case BlendFactor.OneMinusSrc1Alpha:
-                    case BlendFactor.OneMinusSrc1AlphaGl:
-                        return true;
-                }
-
-                return false;
-            }
-
             EnsureFramebuffer();
 
             _framebuffer.SetDualSourceBlend(
-                IsDualSource(blend.ColorSrcFactor) ||
-                IsDualSource(blend.ColorDstFactor) ||
-                IsDualSource(blend.AlphaSrcFactor) ||
-                IsDualSource(blend.AlphaDstFactor));
+                blend.ColorSrcFactor.IsDualSource() ||
+                blend.ColorDstFactor.IsDualSource() ||
+                blend.AlphaSrcFactor.IsDualSource() ||
+                blend.AlphaDstFactor.IsDualSource());
 
             if (_blendConstant != blend.BlendConstant)
             {
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
index 996d312b7a..9032ca59ec 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
@@ -612,6 +612,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             else
             {
                 int usedAttributes = context.Config.UsedOutputAttributes;
+
+                if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryDualSourceBlendEnable())
+                {
+                    int firstOutput = BitOperations.TrailingZeroCount(usedAttributes);
+                    int mask = 3 << firstOutput;
+
+                    if ((usedAttributes & mask) == mask)
+                    {
+                        usedAttributes &= ~mask;
+                        DeclareOutputDualSourceBlendAttribute(context, firstOutput);
+                    }
+                }
+
                 while (usedAttributes != 0)
                 {
                     int index = BitOperations.TrailingZeroCount(usedAttributes);
@@ -690,6 +703,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             }
         }
 
+        private static void DeclareOutputDualSourceBlendAttribute(CodeGenContext context, int attr)
+        {
+            string name = $"{DefaultNames.OAttributePrefix}{attr}";
+            string name2 = $"{DefaultNames.OAttributePrefix}{(attr + 1)}";
+
+            context.AppendLine($"layout (location = {attr}, index = 0) out vec4 {name};");
+            context.AppendLine($"layout (location = {attr}, index = 1) out vec4 {name2};");
+        }
+
         private static void DeclareUsedOutputAttributesPerPatch(CodeGenContext context, HashSet<int> attrs)
         {
             foreach (int attr in attrs.Order())
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
index 5108d8619b..df42a13cb7 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
@@ -6,6 +6,7 @@ using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Linq;
+using System.Numerics;
 using static Spv.Specification;
 
 namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
@@ -622,7 +623,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
             else if (attr >= AttributeConsts.FragmentOutputColorBase && attr < AttributeConsts.FragmentOutputColorEnd)
             {
                 int location = (attr - AttributeConsts.FragmentOutputColorBase) / 16;
-                context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
+
+                if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryDualSourceBlendEnable())
+                {
+                    int firstLocation = BitOperations.TrailingZeroCount(context.Config.UsedOutputAttributes);
+                    int index = location - firstLocation;
+                    int mask = 3 << firstLocation;
+
+                    if ((uint)index < 2 && (context.Config.UsedOutputAttributes & mask) == mask)
+                    {
+                        context.Decorate(spvVar, Decoration.Location, (LiteralInteger)firstLocation);
+                        context.Decorate(spvVar, Decoration.Index, (LiteralInteger)index);
+                    }
+                    else
+                    {
+                        context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
+                    }
+                }
+                else
+                {
+                    context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
+                }
             }
 
             if (!isOutAttr)
diff --git a/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/Ryujinx.Graphics.Shader/IGpuAccessor.cs
index f364437c73..ba5f2a92fe 100644
--- a/Ryujinx.Graphics.Shader/IGpuAccessor.cs
+++ b/Ryujinx.Graphics.Shader/IGpuAccessor.cs
@@ -205,6 +205,15 @@ namespace Ryujinx.Graphics.Shader
             return false;
         }
 
+        /// <summary>
+        /// Queries dual source blend state.
+        /// </summary>
+        /// <returns>True if blending is enabled with a dual source blend equation, false otherwise</returns>
+        bool QueryDualSourceBlendEnable()
+        {
+            return false;
+        }
+
         /// <summary>
         /// Queries host about the presence of the FrontFacing built-in variable bug.
         /// </summary>