spirv-val: Allow ArrayStrideIdEXT on structs with descriptors (#6705)

Per `SPV_EXT_descriptor_heap`, `ArrayStrideIdEXT` must be allowed on
arrays containing descriptor types. This includes composites that
contain descriptors, not only arrays whose element type is directly a
descriptor.

**Root Cause**:
The validator only checked whether the array element type itself was a
descriptor type.
For
[RuntimeArray<Material>](https://github.com/KhronosGroup/glslang/pull/4272/changes#diff-3142250076641bab8379f6637d480636190a14c9f31e0f7915cb4e76fc1d840e),
the direct element type is `OpTypeStruct`, so validation rejected it
even though `Material` contains `OpTypeBufferEXT`.

**Solution**:
Recursively inspect the array element type when validating
`ArrayStrideIdEXT`, so structs and other composites containing
descriptor types are accepted.

**Test**:
Added a validator regression test covering a runtime array of a struct
containing `OpTypeBufferEXT`.

Co-authored-by: guangxu2 <guangxu2@amd.com>
diff --git a/source/val/validate_annotation.cpp b/source/val/validate_annotation.cpp
index 037bbfd..be5cc38 100644
--- a/source/val/validate_annotation.cpp
+++ b/source/val/validate_annotation.cpp
@@ -403,10 +403,15 @@
         }
       }
 
-      // Strip array and should be the descriptor type
+      const auto is_descriptor_type = [&_](const Instruction* type_inst) {
+        return _.IsDescriptorType(type_inst->opcode());
+      };
+
+      // Strip the array. The element may be a descriptor type directly, or a
+      // composite containing a descriptor type.
       const uint32_t element_type =
           _.FindDef(target_id)->GetOperandAs<uint32_t>(1);
-      if (!_.IsDescriptorType(element_type)) {
+      if (!_.ContainsType(element_type, is_descriptor_type, true)) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << "ArrayStrideIdEXT decoration must only be applied to"
                << " array type containing a Descriptor type.";
diff --git a/test/val/val_extension_spv_ext_descriptor_heap.cpp b/test/val/val_extension_spv_ext_descriptor_heap.cpp
index ce6c1fe..55bda50 100644
--- a/test/val/val_extension_spv_ext_descriptor_heap.cpp
+++ b/test/val/val_extension_spv_ext_descriptor_heap.cpp
@@ -1560,6 +1560,33 @@
                         "array type containing a Descriptor type."));
 }
 
+TEST_F(ValidateSpvEXTDescriptorHeap, ArrayStrideStructContainingDescriptor) {
+  const std::string str = R"(
+              OpCapability Shader
+              OpCapability DescriptorHeapEXT
+              OpExtension "SPV_EXT_descriptor_heap"
+              OpMemoryModel Logical GLSL450
+              OpEntryPoint GLCompute %main "main"
+              OpExecutionMode %main LocalSize 1 1 1
+              OpDecorateId %_runtimearr_Material ArrayStrideIdEXT %uint_16
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+   %uint_16 = OpConstant %uint 16
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+     %buffer = OpTypeBufferEXT StorageBuffer
+   %Material = OpTypeStruct %v4float %buffer
+%_runtimearr_Material = OpTypeRuntimeArray %Material
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+              OpReturn
+              OpFunctionEnd
+  )";
+  CompileSuccessfully(str.c_str(), SPV_ENV_VULKAN_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+}
+
 TEST_F(ValidateSpvEXTDescriptorHeap, MemberDecorateIdArrayStrideIdEXT) {
   const std::string str = R"(
                OpCapability Shader