layers: Add min/maxTexelOffset check
diff --git a/layers/core_validation.h b/layers/core_validation.h
index 3fa418a..e052452 100644
--- a/layers/core_validation.h
+++ b/layers/core_validation.h
@@ -535,7 +535,7 @@
                                       VkShaderStageFlagBits stage) const;
     bool ValidatePrimitiveRateShaderState(const PIPELINE_STATE* pipeline, SHADER_MODULE_STATE const* src,
                                           spirv_inst_iter entrypoint, VkShaderStageFlagBits stage) const;
-    bool ValidateTexelGatherOffset(SHADER_MODULE_STATE const* src, spirv_inst_iter& insn) const;
+    bool ValidateTexelOffsetLimits(SHADER_MODULE_STATE const* src, spirv_inst_iter& insn) const;
     bool ValidateShaderCapabilitiesAndExtensions(SHADER_MODULE_STATE const* src, spirv_inst_iter& insn) const;
     bool ValidateShaderStageWritableOrAtomicDescriptor(VkShaderStageFlagBits stage, bool has_writable_descriptor,
                                                        bool has_atomic_descriptor) const;
diff --git a/layers/shader_validation.cpp b/layers/shader_validation.cpp
index 1fec5bb..b9f6c2a 100644
--- a/layers/shader_validation.cpp
+++ b/layers/shader_validation.cpp
@@ -2141,18 +2141,21 @@
     return skip;
 }
 
-bool CoreChecks::ValidateTexelGatherOffset(SHADER_MODULE_STATE const *src, spirv_inst_iter &insn) const {
+// Checks for both TexelOffset and TexelGatherOffset limits
+bool CoreChecks::ValidateTexelOffsetLimits(SHADER_MODULE_STATE const *src, spirv_inst_iter &insn) const {
     bool skip = false;
 
     const uint32_t opcode = insn.opcode();
-    if (ImageGatherOperation(opcode)) {
+    if (ImageGatherOperation(opcode) || ImageSampleOperation(opcode) || ImageFetchOperation(opcode)) {
         uint32_t image_operand_position = ImageOperandsParam(opcode);
-        // Image operands are optional
+        // Image operands can be optional
         if (image_operand_position != 0 && insn.len() > image_operand_position) {
             auto image_operand = insn.word(image_operand_position);
-            // Bits we are validating
+            // Bits we are validating (sample/fetch only check ConstOffset)
             uint32_t offset_bits =
-                spv::ImageOperandsOffsetMask | spv::ImageOperandsConstOffsetMask | spv::ImageOperandsConstOffsetsMask;
+                ImageGatherOperation(opcode)
+                    ? (spv::ImageOperandsOffsetMask | spv::ImageOperandsConstOffsetMask | spv::ImageOperandsConstOffsetsMask)
+                    : (spv::ImageOperandsConstOffsetMask);
             if (image_operand & (offset_bits)) {
                 // Operand values follow
                 uint32_t index = image_operand_position + 1;
@@ -2171,25 +2174,46 @@
                                     const auto &comp_type = src->get_def(comp.word(1));
                                     // Get operand value
                                     const uint32_t offset = comp.word(3);
+                                    // spec requires minTexelGatherOffset/minTexelOffset to be -8 or less so never can compare if
+                                    // unsigned spec requires maxTexelGatherOffset/maxTexelOffset to be 7 or greater so never can
+                                    // compare if signed is less then zero
                                     const int32_t signed_offset = static_cast<int32_t>(offset);
                                     const bool use_signed = (comp_type.opcode() == spv::OpTypeInt && comp_type.word(3) != 0);
 
-                                    // spec requires minTexelGatherOffset to be -8 or less so never can compare if unsigned
-                                    // spec requires maxTexelGatherOffset to be 7 or greater so never can compare if signed is less
-                                    // then zero
-                                    if (use_signed && (signed_offset < phys_dev_props.limits.minTexelGatherOffset)) {
-                                        skip |= LogError(device, "VUID-RuntimeSpirv-OpImage-06376",
+                                    // There are 2 sets of VU being covered where the only main difference is the opcode
+                                    if (ImageGatherOperation(opcode)) {
+                                        // min/maxTexelGatherOffset
+                                        if (use_signed && (signed_offset < phys_dev_props.limits.minTexelGatherOffset)) {
+                                            skip |=
+                                                LogError(device, "VUID-RuntimeSpirv-OpImage-06376",
                                                          "vkCreateShaderModule(): Shader uses %s with offset (%" PRIi32
                                                          ") less than VkPhysicalDeviceLimits::minTexelGatherOffset (%" PRIi32 ").",
                                                          string_SpvOpcode(opcode), signed_offset,
                                                          phys_dev_props.limits.minTexelGatherOffset);
-                                    } else if ((offset > phys_dev_props.limits.maxTexelGatherOffset) &&
-                                               (!use_signed || (use_signed && signed_offset > 0))) {
-                                        skip |=
-                                            LogError(device, "VUID-RuntimeSpirv-OpImage-06377",
-                                                     "vkCreateShaderModule(): Shader uses %s with offset (%" PRIu32
-                                                     ") greater than VkPhysicalDeviceLimits::maxTexelGatherOffset (%" PRIu32 ").",
-                                                     string_SpvOpcode(opcode), offset, phys_dev_props.limits.maxTexelGatherOffset);
+                                        } else if ((offset > phys_dev_props.limits.maxTexelGatherOffset) &&
+                                                   (!use_signed || (use_signed && signed_offset > 0))) {
+                                            skip |= LogError(
+                                                device, "VUID-RuntimeSpirv-OpImage-06377",
+                                                "vkCreateShaderModule(): Shader uses %s with offset (%" PRIu32
+                                                ") greater than VkPhysicalDeviceLimits::maxTexelGatherOffset (%" PRIu32 ").",
+                                                string_SpvOpcode(opcode), offset, phys_dev_props.limits.maxTexelGatherOffset);
+                                        }
+                                    } else {
+                                        // min/maxTexelOffset
+                                        if (use_signed && (signed_offset < phys_dev_props.limits.minTexelOffset)) {
+                                            skip |= LogError(device, "VUID-RuntimeSpirv-OpImageSample-06435",
+                                                             "vkCreateShaderModule(): Shader uses %s with offset (%" PRIi32
+                                                             ") less than VkPhysicalDeviceLimits::minTexelOffset (%" PRIi32 ").",
+                                                             string_SpvOpcode(opcode), signed_offset,
+                                                             phys_dev_props.limits.minTexelOffset);
+                                        } else if ((offset > phys_dev_props.limits.maxTexelOffset) &&
+                                                   (!use_signed || (use_signed && signed_offset > 0))) {
+                                            skip |=
+                                                LogError(device, "VUID-RuntimeSpirv-OpImageSample-06436",
+                                                         "vkCreateShaderModule(): Shader uses %s with offset (%" PRIu32
+                                                         ") greater than VkPhysicalDeviceLimits::maxTexelOffset (%" PRIu32 ").",
+                                                         string_SpvOpcode(opcode), offset, phys_dev_props.limits.maxTexelOffset);
+                                        }
                                     }
                                 }
                             }
@@ -2354,7 +2378,7 @@
     // and mainly only checking the instruction in detail for a single operation
     uint32_t total_shared_size = 0;
     for (auto insn : *module) {
-        skip |= ValidateTexelGatherOffset(module, insn);
+        skip |= ValidateTexelOffsetLimits(module, insn);
         skip |= ValidateShaderCapabilitiesAndExtensions(module, insn);
         skip |= ValidateShaderClock(module, insn);
         skip |= ValidateShaderStageGroupNonUniform(module, pStage->stage, insn);
diff --git a/tests/vklayertests_pipeline_shader.cpp b/tests/vklayertests_pipeline_shader.cpp
index 8aaa96d..0e9a08a 100644
--- a/tests/vklayertests_pipeline_shader.cpp
+++ b/tests/vklayertests_pipeline_shader.cpp
@@ -14018,8 +14018,8 @@
   %uint_n100 = OpConstant %uint 4294967196
     %int_100 = OpConstant %int 100
       %int_0 = OpConstant %int 0
-         %22 = OpConstantComposite %v2int %int_n100 %int_100
-         %23 = OpConstantComposite %v2int %int_0 %uint_n100
+ %offset_100 = OpConstantComposite %v2int %int_n100 %int_100
+%offset_n100 = OpConstantComposite %v2int %int_0 %uint_n100
 
                ; Function main
        %main = OpFunction %void None %3
@@ -14027,9 +14027,9 @@
       %color = OpVariable %_ptr_Function_v4float Function
          %14 = OpLoad %11 %samp
                ; Should trigger min and max
-         %24 = OpImageGather %v4float %14 %17 %int_0 ConstOffset %22
+         %24 = OpImageGather %v4float %14 %17 %int_0 ConstOffset %offset_100
                ; Should only trigger max since uint
-         %25 = OpImageGather %v4float %14 %17 %int_0 ConstOffset %23
+         %25 = OpImageGather %v4float %14 %17 %int_0 ConstOffset %offset_n100
                OpStore %color %24
                OpReturn
                OpFunctionEnd
@@ -14057,6 +14057,91 @@
     m_errorMonitor->VerifyFound();
 }
 
+TEST_F(VkLayerTest, TestMinAndMaxTexelOffset) {
+    TEST_DESCRIPTION("Test shader with offset less than minTexelOffset and greather than maxTexelOffset");
+
+    ASSERT_NO_FATAL_FAILURE(Init());
+    ASSERT_NO_FATAL_FAILURE(InitRenderTarget());
+
+    if (m_device->phy().properties().limits.minTexelOffset <= -100 || m_device->phy().properties().limits.maxTexelOffset >= 100) {
+        printf("%s test needs minTexelOffset greater than -100 and maxTexelOffset less than 100. Skipping.\n", kSkipPrefix);
+        return;
+    }
+
+    const std::string spv_source = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main"
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %textureSampler DescriptorSet 0
+               OpDecorate %textureSampler Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+         %10 = OpTypeImage %float 2D 0 0 0 1 Unknown
+         %11 = OpTypeSampledImage %10
+%_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11
+%textureSampler = OpVariable %_ptr_UniformConstant_11 UniformConstant
+    %v2float = OpTypeVector %float 2
+    %float_0 = OpConstant %float 0
+         %17 = OpConstantComposite %v2float %float_0 %float_0
+              ; set up composite to be validated
+       %uint = OpTypeInt 32 0
+        %int = OpTypeInt 32 1
+      %v2int = OpTypeVector %int 2
+      %int_0 = OpConstant %int 0
+   %int_n100 = OpConstant %int -100
+  %uint_n100 = OpConstant %uint 4294967196
+    %int_100 = OpConstant %int 100
+ %offset_100 = OpConstantComposite %v2int %int_n100 %int_100
+%offset_n100 = OpConstantComposite %v2int %int_0 %uint_n100
+         %24 = OpConstantComposite %v2int %int_0 %int_0
+
+       %main = OpFunction %void None %3
+      %label = OpLabel
+         %14 = OpLoad %11 %textureSampler
+         %26 = OpImage %10 %14
+               ; Should trigger min and max
+    %result0 = OpImageSampleImplicitLod %v4float %14 %17 ConstOffset %offset_100
+    %result1 = OpImageFetch %v4float %26 %24 ConstOffset %offset_100
+               ; Should only trigger max since uint
+    %result2 = OpImageSampleImplicitLod %v4float %14 %17 ConstOffset %offset_n100
+    %result3 = OpImageFetch %v4float %26 %24 ConstOffset %offset_n100
+               OpReturn
+               OpFunctionEnd
+        )";
+
+    OneOffDescriptorSet descriptor_set(m_device,
+                                       {
+                                           {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
+                                       });
+
+    VkShaderObj const fs(m_device, spv_source, VK_SHADER_STAGE_FRAGMENT_BIT, this);
+
+    CreatePipelineHelper pipe(*this);
+    pipe.InitInfo();
+    pipe.shader_stages_ = {pipe.vs_->GetStageCreateInfo(), fs.GetStageCreateInfo()};
+    pipe.InitState();
+    pipe.pipeline_layout_ = VkPipelineLayoutObj(m_device, {&descriptor_set.layout_});
+    // as commented in SPIR-V should trigger the limits as following
+    //
+    // OpImageSampleImplicitLod
+    m_errorMonitor->SetDesiredFailureMsg(kErrorBit, "VUID-RuntimeSpirv-OpImageSample-06435");
+    m_errorMonitor->SetDesiredFailureMsg(kErrorBit, "VUID-RuntimeSpirv-OpImageSample-06436");
+    m_errorMonitor->SetDesiredFailureMsg(kErrorBit, "VUID-RuntimeSpirv-OpImageSample-06436");
+    // // OpImageFetch
+    m_errorMonitor->SetDesiredFailureMsg(kErrorBit, "VUID-RuntimeSpirv-OpImageSample-06435");
+    m_errorMonitor->SetDesiredFailureMsg(kErrorBit, "VUID-RuntimeSpirv-OpImageSample-06436");
+    m_errorMonitor->SetDesiredFailureMsg(kErrorBit, "VUID-RuntimeSpirv-OpImageSample-06436");
+    pipe.CreateGraphicsPipeline();
+
+    m_errorMonitor->VerifyFound();
+}
+
 TEST_F(VkLayerTest, RayTracingLibraryFlags) {
     TEST_DESCRIPTION("Validate ray tracing pipeline flags match library flags.");