spirv-val: Add VK_KHR_maintenance8 support (#5951)

* spirv-val: Add VK_KHR_maintenance8 support

* spirv-val: Remove old 04663 VUID

* spirv-val: Add new VUID for 10213
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index 981ea02..ecf763f 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -732,6 +732,11 @@
 SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetAllowLocalSizeId(
     spv_validator_options options, bool val);
 
+// Allow Offset (in addition to ConstOffset) for texture operations.
+// Was added for VK_KHR_maintenance8
+SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetAllowOffsetTextureOperand(
+    spv_validator_options options, bool val);
+
 // Whether friendly names should be used in validation error messages.
 SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetFriendlyNames(
     spv_validator_options options, bool val);
diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp
index 6a64e93..1a75868 100644
--- a/include/spirv-tools/libspirv.hpp
+++ b/include/spirv-tools/libspirv.hpp
@@ -126,6 +126,12 @@
     spvValidatorOptionsSetAllowLocalSizeId(options_, val);
   }
 
+  // Allow Offset (in addition to ConstOffset) for texture
+  // operations. Was added for VK_KHR_maintenance8
+  void SetAllowOffsetTextureOperand(bool val) {
+    spvValidatorOptionsSetAllowOffsetTextureOperand(options_, val);
+  }
+
   // Records whether or not the validator should relax the rules on pointer
   // usage in logical addressing mode.
   //
diff --git a/source/spirv_validator_options.cpp b/source/spirv_validator_options.cpp
index b72a644..fb09d5c 100644
--- a/source/spirv_validator_options.cpp
+++ b/source/spirv_validator_options.cpp
@@ -126,6 +126,11 @@
   options->allow_localsizeid = val;
 }
 
+void spvValidatorOptionsSetAllowOffsetTextureOperand(
+    spv_validator_options options, bool val) {
+  options->allow_offset_texture_operand = val;
+}
+
 void spvValidatorOptionsSetFriendlyNames(spv_validator_options options,
                                          bool val) {
   options->use_friendly_names = val;
diff --git a/source/spirv_validator_options.h b/source/spirv_validator_options.h
index 0145048..b794981 100644
--- a/source/spirv_validator_options.h
+++ b/source/spirv_validator_options.h
@@ -48,6 +48,7 @@
         workgroup_scalar_block_layout(false),
         skip_block_layout(false),
         allow_localsizeid(false),
+        allow_offset_texture_operand(false),
         before_hlsl_legalization(false),
         use_friendly_names(true) {}
 
@@ -60,6 +61,7 @@
   bool workgroup_scalar_block_layout;
   bool skip_block_layout;
   bool allow_localsizeid;
+  bool allow_offset_texture_operand;
   bool before_hlsl_legalization;
   bool use_friendly_names;
 };
diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp
index cb987d8..a5f91f7 100644
--- a/source/val/validate_image.cpp
+++ b/source/val/validate_image.cpp
@@ -455,13 +455,14 @@
     }
 
     if (!_.options()->before_hlsl_legalization &&
-        spvIsVulkanEnv(_.context()->target_env)) {
+        spvIsVulkanEnv(_.context()->target_env) &&
+        !_.options()->allow_offset_texture_operand) {
       if (opcode != spv::Op::OpImageGather &&
           opcode != spv::Op::OpImageDrefGather &&
           opcode != spv::Op::OpImageSparseGather &&
           opcode != spv::Op::OpImageSparseDrefGather) {
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << _.VkErrorID(4663)
+               << _.VkErrorID(10213)
                << "Image Operand Offset can only be used with "
                   "OpImage*Gather operations";
       }
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index c8bf1ee..350a24c 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -2264,8 +2264,6 @@
       return VUID_WRAP(VUID-StandaloneSpirv-OpImageTexelPointer-04658);
     case 4659:
       return VUID_WRAP(VUID-StandaloneSpirv-OpImageQuerySizeLod-04659);
-    case 4663:
-      return VUID_WRAP(VUID-StandaloneSpirv-Offset-04663);
     case 4664:
       return VUID_WRAP(VUID-StandaloneSpirv-OpImageGather-04664);
     case 4667:
@@ -2466,6 +2464,9 @@
       return VUID_WRAP(VUID-StandaloneSpirv-OpEntryPoint-09658);
     case 9659:
       return VUID_WRAP(VUID-StandaloneSpirv-OpEntryPoint-09659);
+    case 10213:
+      // This use to be a standalone, but maintenance8 will set allow_offset_texture_operand now
+      return VUID_WRAP(VUID-RuntimeSpirv-Offset-10213);
     default:
       return "";  // unknown id
   }
diff --git a/test/val/val_image_test.cpp b/test/val/val_image_test.cpp
index 93fab04..2a86c91 100644
--- a/test/val/val_image_test.cpp
+++ b/test/val/val_image_test.cpp
@@ -20,6 +20,7 @@
 #include <string>
 
 #include "gmock/gmock.h"
+#include "spirv-tools/libspirv.h"
 #include "test/unit_spirv.h"
 #include "test/val/val_fixtures.h"
 
@@ -2144,13 +2145,26 @@
   CompileSuccessfully(
       GenerateShaderCode(body, "", "Fragment", "", SPV_ENV_VULKAN_1_0).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(getDiagnosticString(),
-              AnyVUID("VUID-StandaloneSpirv-Offset-04663"));
+  EXPECT_THAT(getDiagnosticString(), AnyVUID("VUID-RuntimeSpirv-Offset-10213"));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Image Operand Offset can only be used with "
                         "OpImage*Gather operations"));
 }
 
+TEST_F(ValidateImage, SampleImplicitLodVulkanOffsetMaintenance8) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res4 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 Offset %s32vec2_01
+)";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, "", "Fragment", "", SPV_ENV_VULKAN_1_0).c_str());
+  spvValidatorOptionsSetAllowOffsetTextureOperand(getValidatorOptions(), true);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
 TEST_F(ValidateImage, SampleImplicitLodVulkanOffsetWrongBeforeLegalization) {
   const std::string body = R"(
 %img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
diff --git a/tools/val/val.cpp b/tools/val/val.cpp
index ae88c9e..3dbc7f4 100644
--- a/tools/val/val.cpp
+++ b/tools/val/val.cpp
@@ -66,6 +66,8 @@
                                    members.
   --allow-localsizeid              Allow use of the LocalSizeId decoration where it would otherwise not
                                    be allowed by the target environment.
+  --allow-offset-texture-operand   Allow use of the Offset texture operands where it would otherwise not
+                                   be allowed by the target environment.
   --before-hlsl-legalization       Allows code patterns that are intended to be
                                    fixed by spirv-opt's legalization passes.
   --version                        Display validator version information.
@@ -161,6 +163,8 @@
         options.SetSkipBlockLayout(true);
       } else if (0 == strcmp(cur_arg, "--allow-localsizeid")) {
         options.SetAllowLocalSizeId(true);
+      } else if (0 == strcmp(cur_arg, "--allow-offset-texture-operand")) {
+        options.SetAllowOffsetTextureOperand(true);
       } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
         options.SetRelaxStructStore(true);
       } else if (0 == cur_arg[1]) {