diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp
index cfdb92807..ce3445512 100644
--- a/src/video_core/shader/decode/memory.cpp
+++ b/src/video_core/shader/decode/memory.cpp
@@ -204,7 +204,7 @@ u32 ShaderIR::DecodeMemory(BasicBlock& bb, u32 pc) {
         break;
     }
     case OpCode::Id::TEXS: {
-        Tegra::Shader::TextureType texture_type{instr.texs.GetTextureType()};
+        const TextureType texture_type{instr.texs.GetTextureType()};
         const bool is_array{instr.texs.IsArrayTexture()};
         const bool depth_compare = instr.texs.UsesMiscMode(TextureMiscMode::DC);
         const auto process_mode = instr.texs.GetTextureProcessMode();
@@ -373,6 +373,22 @@ u32 ShaderIR::DecodeMemory(BasicBlock& bb, u32 pc) {
                                GetRegister(RZ), GetRegister(RZ)));
         break;
     }
+    case OpCode::Id::TLDS: {
+        const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()};
+        const bool is_array{instr.tlds.IsArrayTexture()};
+
+        UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(TextureMiscMode::AOFFI),
+                             "AOFFI is not implemented");
+        UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(TextureMiscMode::MZ), "MZ is not implemented");
+
+        if (instr.tlds.UsesMiscMode(TextureMiscMode::NODEP)) {
+            LOG_WARNING(HW_GPU, "TMML.NODEP implementation is incomplete");
+        }
+
+        const Node texture = GetTldsCode(instr, texture_type, is_array);
+        WriteTexsInstructionFloat(bb, instr, texture);
+        break;
+    }
     default:
         UNIMPLEMENTED_MSG("Unhandled memory instruction: {}", opcode->get().GetName());
     }
@@ -576,22 +592,59 @@ Node ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool dep
     for (size_t i = 0; i < coord_count; ++i) {
         params.push_back(GetRegister(coord_register + i));
     }
-    std::size_t array_offset{};
+    std::optional<u32> array_offset;
     if (is_array) {
-        array_offset = params.size();
+        array_offset = static_cast<u32>(params.size());
         params.push_back(GetRegister(array_register));
     }
 
     const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, depth_compare);
-
-    std::optional<u32> array_offset_value;
-    if (is_array)
-        array_offset_value = static_cast<u32>(array_offset);
-    MetaTexture meta{sampler, static_cast<u32>(params.size()), array_offset_value};
+    MetaTexture meta{sampler, static_cast<u32>(params.size()), array_offset};
 
     return Operation(OperationCode::F4TextureGather, std::move(meta), std::move(params));
 }
 
+Node ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is_array) {
+    const std::size_t type_coord_count = GetCoordCount(texture_type);
+    const std::size_t total_coord_count = type_coord_count + (is_array ? 1 : 0);
+    const bool lod_enabled = instr.tlds.GetTextureProcessMode() == TextureProcessMode::LL;
+
+    // If enabled arrays index is always stored in the gpr8 field
+    const u64 array_register = instr.gpr8.Value();
+    // if is array gpr20 is used
+    const u64 coord_register = is_array ? instr.gpr20.Value() : instr.gpr8.Value();
+
+    const u64 last_coord_register =
+        ((type_coord_count > 2) || (type_coord_count == 2 && !lod_enabled)) && !is_array
+            ? static_cast<u64>(instr.gpr20.Value())
+            : coord_register + 1;
+
+    std::vector<Node> params;
+
+    for (std::size_t i = 0; i < type_coord_count; ++i) {
+        const bool last = (i == (type_coord_count - 1)) && (type_coord_count > 1);
+        params.push_back(GetRegister(last ? last_coord_register : coord_register + i));
+    }
+    std::optional<u32> array_offset;
+    if (is_array) {
+        array_offset = static_cast<u32>(params.size());
+        params.push_back(GetRegister(array_register));
+    }
+    const auto coords_count = static_cast<u32>(params.size());
+
+    if (lod_enabled) {
+        // When lod is used always is in grp20
+        params.push_back(GetRegister(instr.gpr20));
+    } else {
+        params.push_back(Immediate(0));
+    }
+
+    const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false);
+    MetaTexture meta{sampler, coords_count, array_offset};
+
+    return Operation(OperationCode::F4TexelFetch, std::move(meta), std::move(params));
+}
+
 std::tuple<std::size_t, std::size_t> ShaderIR::ValidateAndGetCoordinateElement(
     TextureType texture_type, bool depth_compare, bool is_array, bool lod_bias_enabled,
     std::size_t max_coords, std::size_t max_inputs) {
diff --git a/src/video_core/shader/glsl_decompiler.cpp b/src/video_core/shader/glsl_decompiler.cpp
index a513c0c4b..b93ea9ec6 100644
--- a/src/video_core/shader/glsl_decompiler.cpp
+++ b/src/video_core/shader/glsl_decompiler.cpp
@@ -635,8 +635,6 @@ private:
                                                          result_type));
     }
 
-#pragma optimize("", off)
-
     std::string GenerateTexture(Operation operation, const std::string& func,
                                 std::string extra_cast(std::string) = nullptr) {
         constexpr std::array<const char*, 4> coord_constructors = {"float", "vec2", "vec3", "vec4"};
@@ -1100,6 +1098,32 @@ private:
         return "vec4(itof(int(" + tmp + ".y)), utof(uint(" + tmp + ".x)), 0, 0)";
     }
 
+    std::string F4TexelFetch(Operation operation) {
+        constexpr std::array<const char*, 4> constructors = {"int", "ivec2", "ivec3", "ivec4"};
+        const auto& meta = std::get<MetaTexture>(operation.GetMeta());
+        const auto count = static_cast<u32>(operation.GetOperandsCount());
+
+        std::string expr = "texelFetch(";
+        expr += GetSampler(meta.sampler);
+        expr += ", ";
+
+        expr += constructors[meta.coords_count - 1];
+        expr += '(';
+        for (u32 i = 0; i < count; ++i) {
+            expr += VisitOperand(operation, i, Type::Int);
+            expr += ", ";
+
+            if (i + 1 == meta.coords_count) {
+                expr += ')';
+            }
+            if (i + 1 < count) {
+                expr += ", ";
+            }
+        }
+        expr += ')';
+        return expr;
+    }
+
     std::string Ipa(Operation operation) {
         const auto& attribute = operation[0];
         // TODO(Rodrigo): Special IPA attribute interactions
@@ -1314,6 +1338,7 @@ private:
         &F4TextureGather,
         &F4TextureQueryDimensions,
         &F4TextureQueryLod,
+        &F4TexelFetch,
 
         &Ipa,
 
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index 691bd6d72..231f58f6a 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -154,6 +154,7 @@ enum class OperationCode {
     F4TextureGather,          /// (MetaTexture, float[N] coords, float[M] params) -> float4
     F4TextureQueryDimensions, /// (MetaTexture, float a) -> float4
     F4TextureQueryLod,        /// (MetaTexture, float[N] coords) -> float4
+    F4TexelFetch,             /// (MetaTexture, int[N], int) -> float4
 
     Ipa, /// (abuf src) -> float
 
@@ -694,6 +695,9 @@ private:
     Node GetTld4Code(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
                      bool depth_compare, bool is_array);
 
+    Node GetTldsCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
+                     bool is_array);
+
     std::tuple<std::size_t, std::size_t> ValidateAndGetCoordinateElement(
         Tegra::Shader::TextureType texture_type, bool depth_compare, bool is_array,
         bool lod_bias_enabled, std::size_t max_coords, std::size_t max_inputs);