diff --git a/Ryujinx.Graphics.GAL/Capabilities.cs b/Ryujinx.Graphics.GAL/Capabilities.cs
index e388f0e55a..d0c63bf257 100644
--- a/Ryujinx.Graphics.GAL/Capabilities.cs
+++ b/Ryujinx.Graphics.GAL/Capabilities.cs
@@ -4,8 +4,9 @@ namespace Ryujinx.Graphics.GAL
     {
         public bool SupportsAstcCompression          { get; }
         public bool SupportsImageLoadFormatted       { get; }
-        public bool SupportsNonConstantTextureOffset { get; }
         public bool SupportsMismatchingViewFormat    { get; }
+        public bool SupportsNonConstantTextureOffset { get; }
+        public bool SupportsTextureShadowLod         { get; }
         public bool SupportsViewportSwizzle          { get; }
 
         public int   MaximumComputeSharedMemorySize { get; }
@@ -15,8 +16,9 @@ namespace Ryujinx.Graphics.GAL
         public Capabilities(
             bool  supportsAstcCompression,
             bool  supportsImageLoadFormatted,
-            bool  supportsNonConstantTextureOffset,
             bool  supportsMismatchingViewFormat,
+            bool  supportsNonConstantTextureOffset,
+            bool  supportsTextureShadowLod,
             bool  supportsViewportSwizzle,
             int   maximumComputeSharedMemorySize,
             float maximumSupportedAnisotropy,
@@ -24,8 +26,9 @@ namespace Ryujinx.Graphics.GAL
         {
             SupportsAstcCompression          = supportsAstcCompression;
             SupportsImageLoadFormatted       = supportsImageLoadFormatted;
-            SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
             SupportsMismatchingViewFormat    = supportsMismatchingViewFormat;
+            SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
+            SupportsTextureShadowLod         = supportsTextureShadowLod;
             SupportsViewportSwizzle          = supportsViewportSwizzle;
             MaximumComputeSharedMemorySize   = maximumComputeSharedMemorySize;
             MaximumSupportedAnisotropy       = maximumSupportedAnisotropy;
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
index 75ff037e15..4381301b15 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
@@ -180,6 +180,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <returns>True if the GPU and driver supports non-constant texture offsets, false otherwise</returns>
         public bool QuerySupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset;
 
+        /// <summary>
+        /// Queries host GPU texture shadow LOD support.
+        /// </summary>
+        /// <returns>True if the GPU and driver supports texture shadow LOD, false otherwise</returns>
+        public bool QuerySupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
+
         /// <summary>
         /// Gets the texture descriptor for a given texture on the pool.
         /// </summary>
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index f9ad0ad28e..6813a44026 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -36,7 +36,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 = 2397;
+        private const ulong ShaderCodeGenVersion = 2404;
 
         // Progress reporting helpers
         private volatile int _shaderCount;
diff --git a/Ryujinx.Graphics.OpenGL/HwCapabilities.cs b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs
index 97574aea92..a971d5fcc9 100644
--- a/Ryujinx.Graphics.OpenGL/HwCapabilities.cs
+++ b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs
@@ -7,11 +7,12 @@ namespace Ryujinx.Graphics.OpenGL
     {
         private static readonly Lazy<bool> _supportsAstcCompression           = new Lazy<bool>(() => HasExtension("GL_KHR_texture_compression_astc_ldr"));
         private static readonly Lazy<bool> _supportsImageLoadFormatted        = new Lazy<bool>(() => HasExtension("GL_EXT_shader_image_load_formatted"));
-        private static readonly Lazy<bool> _supportsPolygonOffsetClamp        = new Lazy<bool>(() => HasExtension("GL_EXT_polygon_offset_clamp"));
-        private static readonly Lazy<bool> _supportsViewportSwizzle           = new Lazy<bool>(() => HasExtension("GL_NV_viewport_swizzle"));
-        private static readonly Lazy<bool> _supportsSeamlessCubemapPerTexture = new Lazy<bool>(() => HasExtension("GL_ARB_seamless_cubemap_per_texture"));
         private static readonly Lazy<bool> _supportsParallelShaderCompile     = new Lazy<bool>(() => HasExtension("GL_ARB_parallel_shader_compile"));
+        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> _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<int> _maximumComputeSharedMemorySize = new Lazy<int>(() => GetLimit(All.MaxComputeSharedMemorySize));
         private static readonly Lazy<int> _storageBufferOffsetAlignment   = new Lazy<int>(() => GetLimit(All.ShaderStorageBufferOffsetAlignment));
@@ -33,14 +34,16 @@ namespace Ryujinx.Graphics.OpenGL
 
         public static bool SupportsAstcCompression           => _supportsAstcCompression.Value;
         public static bool SupportsImageLoadFormatted        => _supportsImageLoadFormatted.Value;
-        public static bool SupportsPolygonOffsetClamp        => _supportsPolygonOffsetClamp.Value;
-        public static bool SupportsViewportSwizzle           => _supportsViewportSwizzle.Value;
-        public static bool SupportsSeamlessCubemapPerTexture => _supportsSeamlessCubemapPerTexture.Value;
         public static bool SupportsParallelShaderCompile     => _supportsParallelShaderCompile.Value;
+        public static bool SupportsPolygonOffsetClamp        => _supportsPolygonOffsetClamp.Value;
         public static bool SupportsQuads                     => _supportsQuads.Value;
-        public static bool SupportsNonConstantTextureOffset  => _gpuVendor.Value == GpuVendor.Nvidia;
-        public static bool RequiresSyncFlush                 => _gpuVendor.Value == GpuVendor.Amd || _gpuVendor.Value == GpuVendor.IntelWindows || _gpuVendor.Value == GpuVendor.IntelUnix;
-        public static bool SupportsMismatchingViewFormat     => _gpuVendor.Value != GpuVendor.Amd && _gpuVendor.Value != GpuVendor.IntelWindows;
+        public static bool SupportsSeamlessCubemapPerTexture => _supportsSeamlessCubemapPerTexture.Value;
+        public static bool SupportsTextureShadowLod          => _supportsTextureShadowLod.Value;
+        public static bool SupportsViewportSwizzle           => _supportsViewportSwizzle.Value;
+
+        public static bool SupportsMismatchingViewFormat    => _gpuVendor.Value != GpuVendor.Amd && _gpuVendor.Value != GpuVendor.IntelWindows;
+        public static bool SupportsNonConstantTextureOffset => _gpuVendor.Value == GpuVendor.Nvidia;
+        public static bool RequiresSyncFlush                => _gpuVendor.Value == GpuVendor.Amd || _gpuVendor.Value == GpuVendor.IntelWindows || _gpuVendor.Value == GpuVendor.IntelUnix;
 
         public static int MaximumComputeSharedMemorySize => _maximumComputeSharedMemorySize.Value;
         public static int StorageBufferOffsetAlignment   => _storageBufferOffsetAlignment.Value;
@@ -78,7 +81,7 @@ namespace Ryujinx.Graphics.OpenGL
             else if (vendor == "intel")
             {
                 string renderer = GL.GetString(StringName.Renderer).ToLower();
-                
+
                 return renderer.Contains("mesa") ? GpuVendor.IntelUnix : GpuVendor.IntelWindows;
             }
             else if (vendor == "ati technologies inc." || vendor == "advanced micro devices, inc.")
diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs
index 7416cdd7ad..a2be43730b 100644
--- a/Ryujinx.Graphics.OpenGL/Renderer.cs
+++ b/Ryujinx.Graphics.OpenGL/Renderer.cs
@@ -1,4 +1,4 @@
-using OpenTK.Graphics;
+using OpenTK.Graphics;
 using OpenTK.Graphics.OpenGL;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
@@ -96,8 +96,9 @@ namespace Ryujinx.Graphics.OpenGL
             return new Capabilities(
                 HwCapabilities.SupportsAstcCompression,
                 HwCapabilities.SupportsImageLoadFormatted,
-                HwCapabilities.SupportsNonConstantTextureOffset,
                 HwCapabilities.SupportsMismatchingViewFormat,
+                HwCapabilities.SupportsNonConstantTextureOffset,
+                HwCapabilities.SupportsTextureShadowLod,
                 HwCapabilities.SupportsViewportSwizzle,
                 HwCapabilities.MaximumComputeSharedMemorySize,
                 HwCapabilities.MaximumSupportedAnisotropy,
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
index 2e993ef0ba..4471fa3223 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
@@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             context.AppendLine("#extension GL_ARB_shader_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");
 
             if (context.Config.Stage == ShaderStage.Compute)
             {
@@ -32,7 +33,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             }
 
             context.AppendLine("#pragma optionNV(fastmath off)");
-
             context.AppendLine();
 
             context.AppendLine($"const int {DefaultNames.UndefinedName} = 0;");
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
index 911c7b0566..cb99bdcc43 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
@@ -309,20 +309,32 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
             bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
             bool isShadow      = (texOp.Type & SamplerType.Shadow)      != 0;
 
+            SamplerType type = texOp.Type & SamplerType.Mask;
+
+            bool is2D   = type == SamplerType.Texture2D;
+            bool isCube = type == SamplerType.TextureCube;
+
+            // 2D Array and Cube shadow samplers with LOD level or bias requires an extension.
+            // If the extension is not supported, just remove the LOD parameter.
+            if (isArray && isShadow && (is2D || isCube) && !context.Config.GpuAccessor.QuerySupportsTextureShadowLod())
+            {
+                hasLodBias = false;
+                hasLodLevel = false;
+            }
+
+            // Cube shadow samplers with LOD level requires an extension.
+            // If the extension is not supported, just remove the LOD level parameter.
+            if (isShadow && isCube && !context.Config.GpuAccessor.QuerySupportsTextureShadowLod())
+            {
+                hasLodLevel = false;
+            }
+
             // TODO: Bindless texture support. For now we just return 0.
             if (isBindless)
             {
                 return NumberFormatter.FormatFloat(0);
             }
 
-            // This combination is valid, but not available on GLSL.
-            // For now, ignore the LOD level and do a normal sample.
-            // TODO: How to implement it properly?
-            if (hasLodLevel && isArray && isShadow)
-            {
-                hasLodLevel = false;
-            }
-
             string texCall = intCoords ? "texelFetch" : "texture";
 
             if (isGather)
diff --git a/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/Ryujinx.Graphics.Shader/IGpuAccessor.cs
index 6b584e533e..26a8cafdb0 100644
--- a/Ryujinx.Graphics.Shader/IGpuAccessor.cs
+++ b/Ryujinx.Graphics.Shader/IGpuAccessor.cs
@@ -74,6 +74,11 @@
             return true;
         }
 
+        bool QuerySupportsTextureShadowLod()
+        {
+            return true;
+        }
+
         TextureFormat QueryTextureFormat(int handle, int cbufSlot = -1)
         {
             return TextureFormat.R8G8B8A8Unorm;