diff --git a/Ryujinx.Graphics.GAL/Capabilities.cs b/Ryujinx.Graphics.GAL/Capabilities.cs
index 78a9955426..6e5329b2ae 100644
--- a/Ryujinx.Graphics.GAL/Capabilities.cs
+++ b/Ryujinx.Graphics.GAL/Capabilities.cs
@@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.GAL
         public bool SupportsImageLoadFormatted { get; }
         public bool SupportsMismatchingViewFormat { get; }
         public bool SupportsNonConstantTextureOffset { get; }
+        public bool SupportsShaderBallot { get; }
         public bool SupportsTextureShadowLod { get; }
         public bool SupportsViewportSwizzle { get; }
         public bool SupportsIndirectParameters { get; }
@@ -24,6 +25,7 @@ namespace Ryujinx.Graphics.GAL
             bool supportsImageLoadFormatted,
             bool supportsMismatchingViewFormat,
             bool supportsNonConstantTextureOffset,
+            bool supportsShaderBallot,
             bool supportsTextureShadowLod,
             bool supportsViewportSwizzle,
             bool supportsIndirectParameters,
@@ -37,6 +39,7 @@ namespace Ryujinx.Graphics.GAL
             SupportsImageLoadFormatted = supportsImageLoadFormatted;
             SupportsMismatchingViewFormat = supportsMismatchingViewFormat;
             SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
+            SupportsShaderBallot = supportsShaderBallot;
             SupportsTextureShadowLod = supportsTextureShadowLod;
             SupportsViewportSwizzle = supportsViewportSwizzle;
             SupportsIndirectParameters = supportsIndirectParameters;
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index cda3ecb32c..926a673aa8 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 = 2613;
+        private const ulong ShaderCodeGenVersion = 2627;
 
         // Progress reporting helpers
         private volatile int _shaderCount;
diff --git a/Ryujinx.Graphics.Gpu/Shader/TextureDescriptorCapableGpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/TextureDescriptorCapableGpuAccessor.cs
index 7c4eea02f8..dfb67f358a 100644
--- a/Ryujinx.Graphics.Gpu/Shader/TextureDescriptorCapableGpuAccessor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/TextureDescriptorCapableGpuAccessor.cs
@@ -47,6 +47,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <returns>True if the GPU and driver supports non-constant texture offsets, false otherwise</returns>
         public bool QueryHostSupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset;
 
+        /// <summary>
+        /// Queries host GPU shader ballot support.
+        /// </summary>
+        /// <returns>True if the GPU and driver supports shader ballot, false otherwise</returns>
+        public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot;
+
         /// <summary>
         /// Queries host GPU texture shadow LOD support.
         /// </summary>
diff --git a/Ryujinx.Graphics.OpenGL/HwCapabilities.cs b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs
index dd917b7b27..9a62bb2ec4 100644
--- a/Ryujinx.Graphics.OpenGL/HwCapabilities.cs
+++ b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs
@@ -11,6 +11,7 @@ namespace Ryujinx.Graphics.OpenGL
         private static readonly Lazy<bool> _supportsPolygonOffsetClamp        = new Lazy<bool>(() => HasExtension("GL_EXT_polygon_offset_clamp"));
         private static readonly Lazy<bool> _supportsQuads                     = new Lazy<bool>(SupportsQuadsCheck);
         private static readonly Lazy<bool> _supportsSeamlessCubemapPerTexture = new Lazy<bool>(() => HasExtension("GL_ARB_seamless_cubemap_per_texture"));
+        private static readonly Lazy<bool> _supportsShaderBallot              = new Lazy<bool>(() => HasExtension("GL_ARB_shader_ballot"));
         private static readonly Lazy<bool> _supportsTextureShadowLod          = new Lazy<bool>(() => HasExtension("GL_EXT_texture_shadow_lod"));
         private static readonly Lazy<bool> _supportsViewportSwizzle           = new Lazy<bool>(() => HasExtension("GL_NV_viewport_swizzle"));
         private static readonly Lazy<bool> _supportsIndirectParameters        = new Lazy<bool>(() => HasExtension("GL_ARB_indirect_parameters"));
@@ -45,6 +46,7 @@ namespace Ryujinx.Graphics.OpenGL
         public static bool SupportsPolygonOffsetClamp        => _supportsPolygonOffsetClamp.Value;
         public static bool SupportsQuads                     => _supportsQuads.Value;
         public static bool SupportsSeamlessCubemapPerTexture => _supportsSeamlessCubemapPerTexture.Value;
+        public static bool SupportsShaderBallot              => _supportsShaderBallot.Value;
         public static bool SupportsTextureShadowLod          => _supportsTextureShadowLod.Value;
         public static bool SupportsViewportSwizzle           => _supportsViewportSwizzle.Value;
         public static bool SupportsIndirectParameters        => _supportsIndirectParameters.Value;
diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs
index 92f650835d..768c7450cb 100644
--- a/Ryujinx.Graphics.OpenGL/Renderer.cs
+++ b/Ryujinx.Graphics.OpenGL/Renderer.cs
@@ -107,6 +107,7 @@ namespace Ryujinx.Graphics.OpenGL
                 HwCapabilities.SupportsImageLoadFormatted,
                 HwCapabilities.SupportsMismatchingViewFormat,
                 HwCapabilities.SupportsNonConstantTextureOffset,
+                HwCapabilities.SupportsShaderBallot,
                 HwCapabilities.SupportsTextureShadowLod,
                 HwCapabilities.SupportsViewportSwizzle,
                 HwCapabilities.SupportsIndirectParameters,
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
index 7b26801a55..e29ff486c8 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
@@ -13,7 +13,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
         {
             context.AppendLine("#version 450 core");
             context.AppendLine("#extension GL_ARB_gpu_shader_int64 : enable");
-            context.AppendLine("#extension GL_ARB_shader_ballot : enable");
+
+            if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
+            {
+                context.AppendLine("#extension GL_ARB_shader_ballot : enable");
+            }
+            else
+            {
+                context.AppendLine("#extension GL_KHR_shader_subgroup_basic : enable");
+                context.AppendLine("#extension GL_KHR_shader_subgroup_ballot : enable");
+            }
+
             context.AppendLine("#extension GL_ARB_shader_group_vote : enable");
             context.AppendLine("#extension GL_EXT_shader_image_load_formatted : enable");
             context.AppendLine("#extension GL_EXT_texture_shadow_lod : enable");
@@ -531,6 +541,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             code = code.Replace("$SHARED_MEM$", DefaultNames.SharedMemoryName);
             code = code.Replace("$STORAGE_MEM$", OperandManager.GetShaderStagePrefix(context.Config.Stage) + "_" + DefaultNames.StorageNamePrefix);
 
+            if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
+            {
+                code = code.Replace("$SUBGROUP_INVOCATION$", "gl_SubGroupInvocationARB");
+                code = code.Replace("$SUBGROUP_BROADCAST$", "readInvocationARB");
+            }
+            else
+            {
+                code = code.Replace("$SUBGROUP_INVOCATION$", "gl_SubgroupInvocationID");
+                code = code.Replace("$SUBGROUP_BROADCAST$", "subgroupBroadcast");
+            }
+
             context.AppendLine(code);
             context.AppendLine();
         }
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl
index cb7c8d4355..7cb4764dd4 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl
@@ -2,10 +2,10 @@ float Helper_Shuffle(float x, uint index, uint mask, out bool valid)
 {
     uint clamp = mask & 0x1fu;
     uint segMask = (mask >> 8) & 0x1fu;
-    uint minThreadId = gl_SubGroupInvocationARB & segMask;
+    uint minThreadId = $SUBGROUP_INVOCATION$ & segMask;
     uint maxThreadId = minThreadId | (clamp & ~segMask);
     uint srcThreadId = (index & ~segMask) | minThreadId;
     valid = srcThreadId <= maxThreadId;
-    float v = readInvocationARB(x, srcThreadId);
+    float v = $SUBGROUP_BROADCAST$(x, srcThreadId);
     return valid ? v : x;
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl
index 450125501e..71d901d5d2 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl
@@ -2,10 +2,10 @@ float Helper_ShuffleDown(float x, uint index, uint mask, out bool valid)
 {
     uint clamp = mask & 0x1fu;
     uint segMask = (mask >> 8) & 0x1fu;
-    uint minThreadId = gl_SubGroupInvocationARB & segMask;
+    uint minThreadId = $SUBGROUP_INVOCATION$ & segMask;
     uint maxThreadId = minThreadId | (clamp & ~segMask);
-    uint srcThreadId = gl_SubGroupInvocationARB + index;
+    uint srcThreadId = $SUBGROUP_INVOCATION$ + index;
     valid = srcThreadId <= maxThreadId;
-    float v = readInvocationARB(x, srcThreadId);
+    float v = $SUBGROUP_BROADCAST$(x, srcThreadId);
     return valid ? v : x;
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl
index 0781678a9e..ae264d8704 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl
@@ -1,9 +1,9 @@
 float Helper_ShuffleUp(float x, uint index, uint mask, out bool valid)
 {
     uint segMask = (mask >> 8) & 0x1fu;
-    uint minThreadId = gl_SubGroupInvocationARB & segMask;
-    uint srcThreadId = gl_SubGroupInvocationARB - index;
+    uint minThreadId = $SUBGROUP_INVOCATION$ & segMask;
+    uint srcThreadId = $SUBGROUP_INVOCATION$ - index;
     valid = int(srcThreadId) >= int(minThreadId);
-    float v = readInvocationARB(x, srcThreadId);
+    float v = $SUBGROUP_BROADCAST$(x, srcThreadId);
     return valid ? v : x;
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl
index 59db544415..789089d69c 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl
@@ -2,10 +2,10 @@ float Helper_ShuffleXor(float x, uint index, uint mask, out bool valid)
 {
     uint clamp = mask & 0x1fu;
     uint segMask = (mask >> 8) & 0x1fu;
-    uint minThreadId = gl_SubGroupInvocationARB & segMask;
+    uint minThreadId = $SUBGROUP_INVOCATION$ & segMask;
     uint maxThreadId = minThreadId | (clamp & ~segMask);
-    uint srcThreadId = gl_SubGroupInvocationARB ^ index;
+    uint srcThreadId = $SUBGROUP_INVOCATION$ ^ index;
     valid = srcThreadId <= maxThreadId;
-    float v = readInvocationARB(x, srcThreadId);
+    float v = $SUBGROUP_BROADCAST$(x, srcThreadId);
     return valid ? v : x;
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl
index 7df3e57fd0..ed00dfec91 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl
@@ -2,6 +2,6 @@ float Helper_SwizzleAdd(float x, float y, int mask)
 {
     vec4 xLut = vec4(1.0, -1.0, 1.0, 0.0);
     vec4 yLut = vec4(1.0, 1.0, -1.0, 1.0);
-    int lutIdx = mask >> int(gl_SubGroupInvocationARB & 3u) * 2;
+    int lutIdx = mask >> int($SUBGROUP_INVOCATION$ & 3u) * 2;
     return x * xLut[lutIdx] + y * yLut[lutIdx];
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs
index 24d4cabd46..3fa13eb5cd 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs
@@ -2,6 +2,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
 using Ryujinx.Graphics.Shader.StructuredIr;
 using System;
 
+using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenBallot;
 using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenCall;
 using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper;
 using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenMemory;
@@ -75,14 +76,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
                     }
                 }
 
-                if (inst == Instruction.Ballot)
-                {
-                    return $"unpackUint2x32({info.OpName}({args})).x";
-                }
-                else
-                {
-                    return info.OpName + "(" + args + ")";
-                }
+                return info.OpName + '(' + args + ')';
             }
             else if ((info.Type & InstType.Op) != 0)
             {
@@ -128,6 +122,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
             {
                 switch (inst)
                 {
+                    case Instruction.Ballot:
+                        return Ballot(context, operation);
+
                     case Instruction.Call:
                         return Call(context, operation);
 
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs
new file mode 100644
index 0000000000..51e7bd2122
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs
@@ -0,0 +1,26 @@
+using Ryujinx.Graphics.Shader.StructuredIr;
+
+using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper;
+using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
+{
+    static class InstGenBallot
+    {
+        public static string Ballot(CodeGenContext context, AstOperation operation)
+        {
+            VariableType dstType = GetSrcVarType(operation.Inst, 0);
+
+            string arg = GetSoureExpr(context, operation.GetSource(0), dstType);
+
+            if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
+            {
+                return $"unpackUint2x32(ballotARB({arg})).x";
+            }
+            else
+            {
+                return $"subgroupBallot({arg}).x";
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs
index 6f4f0c4e60..424a1c4f0b 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs
@@ -25,7 +25,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
             Add(Instruction.AtomicXor,                InstType.AtomicBinary,   "atomicXor");
             Add(Instruction.Absolute,                 InstType.CallUnary,      "abs");
             Add(Instruction.Add,                      InstType.OpBinaryCom,    "+",               2);
-            Add(Instruction.Ballot,                   InstType.CallUnary,      "ballotARB");
+            Add(Instruction.Ballot,                   InstType.Special);
             Add(Instruction.Barrier,                  InstType.CallNullary,    "barrier");
             Add(Instruction.BitCount,                 InstType.CallUnary,      "bitCount");
             Add(Instruction.BitfieldExtractS32,       InstType.CallTernary,    "bitfieldExtract");
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
index deea6c7217..d35525f90c 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
@@ -52,20 +52,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             { AttributeConsts.FrontFacing,         new BuiltInAttribute("gl_FrontFacing",     VariableType.Bool) },
 
             // Special.
-            { AttributeConsts.FragmentOutputDepth, new BuiltInAttribute("gl_FragDepth",                           VariableType.F32)  },
-            { AttributeConsts.ThreadKill,          new BuiltInAttribute("gl_HelperInvocation",                    VariableType.Bool) },
-            { AttributeConsts.ThreadIdX,           new BuiltInAttribute("gl_LocalInvocationID.x",                 VariableType.U32)  },
-            { AttributeConsts.ThreadIdY,           new BuiltInAttribute("gl_LocalInvocationID.y",                 VariableType.U32)  },
-            { AttributeConsts.ThreadIdZ,           new BuiltInAttribute("gl_LocalInvocationID.z",                 VariableType.U32)  },
-            { AttributeConsts.CtaIdX,              new BuiltInAttribute("gl_WorkGroupID.x",                       VariableType.U32)  },
-            { AttributeConsts.CtaIdY,              new BuiltInAttribute("gl_WorkGroupID.y",                       VariableType.U32)  },
-            { AttributeConsts.CtaIdZ,              new BuiltInAttribute("gl_WorkGroupID.z",                       VariableType.U32)  },
-            { AttributeConsts.LaneId,              new BuiltInAttribute("gl_SubGroupInvocationARB",               VariableType.U32)  },
-            { AttributeConsts.EqMask,              new BuiltInAttribute("unpackUint2x32(gl_SubGroupEqMaskARB).x", VariableType.U32)  },
-            { AttributeConsts.GeMask,              new BuiltInAttribute("unpackUint2x32(gl_SubGroupGeMaskARB).x", VariableType.U32)  },
-            { AttributeConsts.GtMask,              new BuiltInAttribute("unpackUint2x32(gl_SubGroupGtMaskARB).x", VariableType.U32)  },
-            { AttributeConsts.LeMask,              new BuiltInAttribute("unpackUint2x32(gl_SubGroupLeMaskARB).x", VariableType.U32)  },
-            { AttributeConsts.LtMask,              new BuiltInAttribute("unpackUint2x32(gl_SubGroupLtMaskARB).x", VariableType.U32)  },
+            { AttributeConsts.FragmentOutputDepth, new BuiltInAttribute("gl_FragDepth",           VariableType.F32)  },
+            { AttributeConsts.ThreadKill,          new BuiltInAttribute("gl_HelperInvocation",    VariableType.Bool) },
+            { AttributeConsts.ThreadIdX,           new BuiltInAttribute("gl_LocalInvocationID.x", VariableType.U32)  },
+            { AttributeConsts.ThreadIdY,           new BuiltInAttribute("gl_LocalInvocationID.y", VariableType.U32)  },
+            { AttributeConsts.ThreadIdZ,           new BuiltInAttribute("gl_LocalInvocationID.z", VariableType.U32)  },
+            { AttributeConsts.CtaIdX,              new BuiltInAttribute("gl_WorkGroupID.x",       VariableType.U32)  },
+            { AttributeConsts.CtaIdY,              new BuiltInAttribute("gl_WorkGroupID.y",       VariableType.U32)  },
+            { AttributeConsts.CtaIdZ,              new BuiltInAttribute("gl_WorkGroupID.z",       VariableType.U32)  },
+            { AttributeConsts.LaneId,              new BuiltInAttribute(null,                     VariableType.U32)  },
+            { AttributeConsts.EqMask,              new BuiltInAttribute(null,                     VariableType.U32)  },
+            { AttributeConsts.GeMask,              new BuiltInAttribute(null,                     VariableType.U32)  },
+            { AttributeConsts.GtMask,              new BuiltInAttribute(null,                     VariableType.U32)  },
+            { AttributeConsts.LeMask,              new BuiltInAttribute(null,                     VariableType.U32)  },
+            { AttributeConsts.LtMask,              new BuiltInAttribute(null,                     VariableType.U32)  },
 
             // Support uniforms.
             { AttributeConsts.FragmentOutputIsBgraBase + 0,  new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[0]",  VariableType.Bool) },
@@ -149,6 +149,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
 
         public static string GetAttributeName(int value, ShaderConfig config, bool isOutAttr = false, string indexExpr = "0")
         {
+            value &= ~3;
             char swzMask = GetSwizzleMask((value >> 2) & 3);
 
             if (value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd)
@@ -201,12 +202,35 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
 
                     return $"{DefaultNames.OAttributePrefix}{(value >> 4)}.{swzMask}";
                 }
-                else if (_builtInAttributes.TryGetValue(value & ~3, out BuiltInAttribute builtInAttr))
+                else if (_builtInAttributes.TryGetValue(value, out BuiltInAttribute builtInAttr))
                 {
+                    string subgroupMask = value switch
+                    {
+                        AttributeConsts.EqMask => "Eq",
+                        AttributeConsts.GeMask => "Ge",
+                        AttributeConsts.GtMask => "Gt",
+                        AttributeConsts.LeMask => "Le",
+                        AttributeConsts.LtMask => "Lt",
+                        _ => null
+                    };
+
+                    if (subgroupMask != null)
+                    {
+                        return config.GpuAccessor.QueryHostSupportsShaderBallot()
+                            ? $"unpackUint2x32(gl_SubGroup{subgroupMask}MaskARB).x"
+                            : $"gl_Subgroup{subgroupMask}Mask.x";
+                    }
+                    else if (value == AttributeConsts.LaneId)
+                    {
+                        return config.GpuAccessor.QueryHostSupportsShaderBallot()
+                            ? "gl_SubGroupInvocationARB"
+                            : "gl_SubgroupInvocationID";
+                    }
+
                     // TODO: There must be a better way to handle this...
                     if (config.Stage == ShaderStage.Fragment)
                     {
-                        switch (value & ~3)
+                        switch (value)
                         {
                             case AttributeConsts.PositionX: return $"(gl_FragCoord.x / {DefaultNames.SupportBlockRenderScaleName}[0])";
                             case AttributeConsts.PositionY: return $"(gl_FragCoord.y / {DefaultNames.SupportBlockRenderScaleName}[0])";
diff --git a/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/Ryujinx.Graphics.Shader/IGpuAccessor.cs
index 9ae399f796..a74782c8aa 100644
--- a/Ryujinx.Graphics.Shader/IGpuAccessor.cs
+++ b/Ryujinx.Graphics.Shader/IGpuAccessor.cs
@@ -1,6 +1,4 @@
-using Ryujinx.Graphics.Shader.Decoders;
-
-namespace Ryujinx.Graphics.Shader
+namespace Ryujinx.Graphics.Shader
 {
     public interface IGpuAccessor
     {
@@ -76,6 +74,11 @@ namespace Ryujinx.Graphics.Shader
             return true;
         }
 
+        bool QueryHostSupportsShaderBallot()
+        {
+            return true;
+        }
+
         bool QueryHostSupportsTextureShadowLod()
         {
             return true;