[tint] Move vertex pulling structures to a separate file

This will allow these structures to be reused by the IR version of the
transform, and also by the backend options structure.

Bug: 380044409
Change-Id: If34718cce04626daa6fbaab259984b0821c2d622
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/222018
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/dawn/native/TintUtils.cpp b/src/dawn/native/TintUtils.cpp
index 048ed07..2bacd27 100644
--- a/src/dawn/native/TintUtils.cpp
+++ b/src/dawn/native/TintUtils.cpp
@@ -55,100 +55,100 @@
     return true;
 }
 
-tint::ast::transform::VertexFormat ToTintVertexFormat(wgpu::VertexFormat format) {
+tint::VertexFormat ToTintVertexFormat(wgpu::VertexFormat format) {
     switch (format) {
         case wgpu::VertexFormat::Uint8:
-            return tint::ast::transform::VertexFormat::kUint8;
+            return tint::VertexFormat::kUint8;
         case wgpu::VertexFormat::Uint8x2:
-            return tint::ast::transform::VertexFormat::kUint8x2;
+            return tint::VertexFormat::kUint8x2;
         case wgpu::VertexFormat::Uint8x4:
-            return tint::ast::transform::VertexFormat::kUint8x4;
+            return tint::VertexFormat::kUint8x4;
         case wgpu::VertexFormat::Sint8:
-            return tint::ast::transform::VertexFormat::kSint8;
+            return tint::VertexFormat::kSint8;
         case wgpu::VertexFormat::Sint8x2:
-            return tint::ast::transform::VertexFormat::kSint8x2;
+            return tint::VertexFormat::kSint8x2;
         case wgpu::VertexFormat::Sint8x4:
-            return tint::ast::transform::VertexFormat::kSint8x4;
+            return tint::VertexFormat::kSint8x4;
         case wgpu::VertexFormat::Unorm8:
-            return tint::ast::transform::VertexFormat::kUnorm8;
+            return tint::VertexFormat::kUnorm8;
         case wgpu::VertexFormat::Unorm8x2:
-            return tint::ast::transform::VertexFormat::kUnorm8x2;
+            return tint::VertexFormat::kUnorm8x2;
         case wgpu::VertexFormat::Unorm8x4:
-            return tint::ast::transform::VertexFormat::kUnorm8x4;
+            return tint::VertexFormat::kUnorm8x4;
         case wgpu::VertexFormat::Snorm8:
-            return tint::ast::transform::VertexFormat::kSnorm8;
+            return tint::VertexFormat::kSnorm8;
         case wgpu::VertexFormat::Snorm8x2:
-            return tint::ast::transform::VertexFormat::kSnorm8x2;
+            return tint::VertexFormat::kSnorm8x2;
         case wgpu::VertexFormat::Snorm8x4:
-            return tint::ast::transform::VertexFormat::kSnorm8x4;
+            return tint::VertexFormat::kSnorm8x4;
         case wgpu::VertexFormat::Uint16:
-            return tint::ast::transform::VertexFormat::kUint16;
+            return tint::VertexFormat::kUint16;
         case wgpu::VertexFormat::Uint16x2:
-            return tint::ast::transform::VertexFormat::kUint16x2;
+            return tint::VertexFormat::kUint16x2;
         case wgpu::VertexFormat::Uint16x4:
-            return tint::ast::transform::VertexFormat::kUint16x4;
+            return tint::VertexFormat::kUint16x4;
         case wgpu::VertexFormat::Sint16:
-            return tint::ast::transform::VertexFormat::kSint16;
+            return tint::VertexFormat::kSint16;
         case wgpu::VertexFormat::Sint16x2:
-            return tint::ast::transform::VertexFormat::kSint16x2;
+            return tint::VertexFormat::kSint16x2;
         case wgpu::VertexFormat::Sint16x4:
-            return tint::ast::transform::VertexFormat::kSint16x4;
+            return tint::VertexFormat::kSint16x4;
         case wgpu::VertexFormat::Unorm16:
-            return tint::ast::transform::VertexFormat::kUnorm16;
+            return tint::VertexFormat::kUnorm16;
         case wgpu::VertexFormat::Unorm16x2:
-            return tint::ast::transform::VertexFormat::kUnorm16x2;
+            return tint::VertexFormat::kUnorm16x2;
         case wgpu::VertexFormat::Unorm16x4:
-            return tint::ast::transform::VertexFormat::kUnorm16x4;
+            return tint::VertexFormat::kUnorm16x4;
         case wgpu::VertexFormat::Snorm16:
-            return tint::ast::transform::VertexFormat::kSnorm16;
+            return tint::VertexFormat::kSnorm16;
         case wgpu::VertexFormat::Snorm16x2:
-            return tint::ast::transform::VertexFormat::kSnorm16x2;
+            return tint::VertexFormat::kSnorm16x2;
         case wgpu::VertexFormat::Snorm16x4:
-            return tint::ast::transform::VertexFormat::kSnorm16x4;
+            return tint::VertexFormat::kSnorm16x4;
         case wgpu::VertexFormat::Float16:
-            return tint::ast::transform::VertexFormat::kFloat16;
+            return tint::VertexFormat::kFloat16;
         case wgpu::VertexFormat::Float16x2:
-            return tint::ast::transform::VertexFormat::kFloat16x2;
+            return tint::VertexFormat::kFloat16x2;
         case wgpu::VertexFormat::Float16x4:
-            return tint::ast::transform::VertexFormat::kFloat16x4;
+            return tint::VertexFormat::kFloat16x4;
         case wgpu::VertexFormat::Float32:
-            return tint::ast::transform::VertexFormat::kFloat32;
+            return tint::VertexFormat::kFloat32;
         case wgpu::VertexFormat::Float32x2:
-            return tint::ast::transform::VertexFormat::kFloat32x2;
+            return tint::VertexFormat::kFloat32x2;
         case wgpu::VertexFormat::Float32x3:
-            return tint::ast::transform::VertexFormat::kFloat32x3;
+            return tint::VertexFormat::kFloat32x3;
         case wgpu::VertexFormat::Float32x4:
-            return tint::ast::transform::VertexFormat::kFloat32x4;
+            return tint::VertexFormat::kFloat32x4;
         case wgpu::VertexFormat::Uint32:
-            return tint::ast::transform::VertexFormat::kUint32;
+            return tint::VertexFormat::kUint32;
         case wgpu::VertexFormat::Uint32x2:
-            return tint::ast::transform::VertexFormat::kUint32x2;
+            return tint::VertexFormat::kUint32x2;
         case wgpu::VertexFormat::Uint32x3:
-            return tint::ast::transform::VertexFormat::kUint32x3;
+            return tint::VertexFormat::kUint32x3;
         case wgpu::VertexFormat::Uint32x4:
-            return tint::ast::transform::VertexFormat::kUint32x4;
+            return tint::VertexFormat::kUint32x4;
         case wgpu::VertexFormat::Sint32:
-            return tint::ast::transform::VertexFormat::kSint32;
+            return tint::VertexFormat::kSint32;
         case wgpu::VertexFormat::Sint32x2:
-            return tint::ast::transform::VertexFormat::kSint32x2;
+            return tint::VertexFormat::kSint32x2;
         case wgpu::VertexFormat::Sint32x3:
-            return tint::ast::transform::VertexFormat::kSint32x3;
+            return tint::VertexFormat::kSint32x3;
         case wgpu::VertexFormat::Sint32x4:
-            return tint::ast::transform::VertexFormat::kSint32x4;
+            return tint::VertexFormat::kSint32x4;
         case wgpu::VertexFormat::Unorm10_10_10_2:
-            return tint::ast::transform::VertexFormat::kUnorm10_10_10_2;
+            return tint::VertexFormat::kUnorm10_10_10_2;
         case wgpu::VertexFormat::Unorm8x4BGRA:
-            return tint::ast::transform::VertexFormat::kUnorm8x4BGRA;
+            return tint::VertexFormat::kUnorm8x4BGRA;
     }
     DAWN_UNREACHABLE();
 }
 
-tint::ast::transform::VertexStepMode ToTintVertexStepMode(wgpu::VertexStepMode mode) {
+tint::VertexStepMode ToTintVertexStepMode(wgpu::VertexStepMode mode) {
     switch (mode) {
         case wgpu::VertexStepMode::Vertex:
-            return tint::ast::transform::VertexStepMode::kVertex;
+            return tint::VertexStepMode::kVertex;
         case wgpu::VertexStepMode::Instance:
-            return tint::ast::transform::VertexStepMode::kInstance;
+            return tint::VertexStepMode::kInstance;
         case wgpu::VertexStepMode::Undefined:
             break;
     }
@@ -173,16 +173,16 @@
     tlDevice = nullptr;
 }
 
-tint::ast::transform::VertexPulling::Config BuildVertexPullingTransformConfig(
+tint::VertexPullingConfig BuildVertexPullingTransformConfig(
     const RenderPipelineBase& renderPipeline,
     BindGroupIndex pullingBufferBindingSet) {
-    tint::ast::transform::VertexPulling::Config cfg;
+    tint::VertexPullingConfig cfg;
     cfg.pulling_group = static_cast<uint32_t>(pullingBufferBindingSet);
 
     cfg.vertex_state.resize(renderPipeline.GetVertexBufferCount());
     for (VertexBufferSlot slot : IterateBitSet(renderPipeline.GetVertexBuffersUsed())) {
         const VertexBufferInfo& dawnInfo = renderPipeline.GetVertexBuffer(slot);
-        tint::ast::transform::VertexBufferLayoutDescriptor* tintInfo =
+        tint::VertexBufferLayoutDescriptor* tintInfo =
             &cfg.vertex_state[static_cast<uint8_t>(slot)];
 
         tintInfo->array_stride = dawnInfo.arrayStride;
@@ -192,7 +192,7 @@
     for (VertexAttributeLocation location :
          IterateBitSet(renderPipeline.GetAttributeLocationsUsed())) {
         const VertexAttributeInfo& dawnInfo = renderPipeline.GetAttribute(location);
-        tint::ast::transform::VertexAttributeDescriptor tintInfo;
+        tint::VertexAttributeDescriptor tintInfo;
         tintInfo.format = ToTintVertexFormat(dawnInfo.format);
         tintInfo.offset = dawnInfo.offset;
         tintInfo.shader_location = static_cast<uint32_t>(static_cast<uint8_t>(location));
diff --git a/src/dawn/native/TintUtils.h b/src/dawn/native/TintUtils.h
index fc682b8..a9d8903 100644
--- a/src/dawn/native/TintUtils.h
+++ b/src/dawn/native/TintUtils.h
@@ -54,7 +54,7 @@
     ScopedTintICEHandler(ScopedTintICEHandler&&) = delete;
 };
 
-tint::ast::transform::VertexPulling::Config BuildVertexPullingTransformConfig(
+tint::VertexPullingConfig BuildVertexPullingTransformConfig(
     const RenderPipelineBase& renderPipeline,
     BindGroupIndex pullingBufferBindingSet);
 
diff --git a/src/dawn/native/metal/ShaderModuleMTL.mm b/src/dawn/native/metal/ShaderModuleMTL.mm
index 65975cc..d7aecb6 100644
--- a/src/dawn/native/metal/ShaderModuleMTL.mm
+++ b/src/dawn/native/metal/ShaderModuleMTL.mm
@@ -55,8 +55,7 @@
 // The name to use when remapping entry points.
 constexpr char kRemappedEntryPointName[] = "dawn_entry_point";
 
-using OptionalVertexPullingTransformConfig =
-    std::optional<tint::ast::transform::VertexPulling::Config>;
+using OptionalVertexPullingTransformConfig = std::optional<tint::VertexPullingConfig>;
 
 #define MSL_COMPILATION_REQUEST_MEMBERS(X)                                                       \
     X(SingleShaderStage, stage)                                                                  \
@@ -227,7 +226,7 @@
     tint::msl::writer::Bindings bindings =
         generateBindingInfo(stage, layout, moduleBindingInfo, arrayLengthFromUniform);
 
-    std::optional<tint::ast::transform::VertexPulling::Config> vertexPullingTransformConfig;
+    OptionalVertexPullingTransformConfig vertexPullingTransformConfig;
     if (stage == SingleShaderStage::Vertex &&
         device->IsToggleEnabled(Toggle::MetalEnableVertexPulling)) {
         vertexPullingTransformConfig =
@@ -329,9 +328,11 @@
             }
 
             if (r.vertexPullingTransformConfig) {
+                tint::ast::transform::VertexPulling::Config config;
+                config.pulling_group = r.vertexPullingTransformConfig->pulling_group;
+                config.vertex_state = r.vertexPullingTransformConfig->vertex_state;
                 transformManager.Add<tint::ast::transform::VertexPulling>();
-                transformInputs.Add<tint::ast::transform::VertexPulling::Config>(
-                    std::move(r.vertexPullingTransformConfig).value());
+                transformInputs.Add<tint::ast::transform::VertexPulling::Config>(std::move(config));
             }
 
             if (r.substituteOverrideConfig) {
diff --git a/src/tint/api/common/BUILD.bazel b/src/tint/api/common/BUILD.bazel
index f07b627..1f5c71c 100644
--- a/src/tint/api/common/BUILD.bazel
+++ b/src/tint/api/common/BUILD.bazel
@@ -40,10 +40,12 @@
   name = "common",
   srcs = [
     "common.cc",
+    "vertex_pulling_config.cc",
   ],
   hdrs = [
     "binding_point.h",
     "override_id.h",
+    "vertex_pulling_config.h",
   ],
   deps = [
     "//src/tint/utils",
@@ -67,6 +69,7 @@
   srcs = [
     "binding_point_test.cc",
     "override_id_test.cc",
+    "vertex_pulling_config_test.cc",
   ],
   deps = [
     "//src/tint/api/common",
diff --git a/src/tint/api/common/BUILD.cmake b/src/tint/api/common/BUILD.cmake
index a3f4e97..d220123 100644
--- a/src/tint/api/common/BUILD.cmake
+++ b/src/tint/api/common/BUILD.cmake
@@ -42,6 +42,8 @@
   api/common/binding_point.h
   api/common/common.cc
   api/common/override_id.h
+  api/common/vertex_pulling_config.cc
+  api/common/vertex_pulling_config.h
 )
 
 tint_target_add_dependencies(tint_api_common lib
@@ -68,6 +70,7 @@
 tint_add_target(tint_api_common_test test
   api/common/binding_point_test.cc
   api/common/override_id_test.cc
+  api/common/vertex_pulling_config_test.cc
 )
 
 tint_target_add_dependencies(tint_api_common_test test
diff --git a/src/tint/api/common/BUILD.gn b/src/tint/api/common/BUILD.gn
index 398938b..2e63d4a 100644
--- a/src/tint/api/common/BUILD.gn
+++ b/src/tint/api/common/BUILD.gn
@@ -48,6 +48,8 @@
     "binding_point.h",
     "common.cc",
     "override_id.h",
+    "vertex_pulling_config.cc",
+    "vertex_pulling_config.h",
   ]
   deps = [
     "${dawn_root}/src/utils:utils",
@@ -68,6 +70,7 @@
     sources = [
       "binding_point_test.cc",
       "override_id_test.cc",
+      "vertex_pulling_config_test.cc",
     ]
     deps = [
       "${dawn_root}/src/utils:utils",
diff --git a/src/tint/api/common/vertex_pulling_config.cc b/src/tint/api/common/vertex_pulling_config.cc
new file mode 100644
index 0000000..92afda5
--- /dev/null
+++ b/src/tint/api/common/vertex_pulling_config.cc
@@ -0,0 +1,44 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/api/common/vertex_pulling_config.h"
+
+#include <utility>
+
+namespace tint {
+
+VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor() = default;
+
+VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor(
+    uint32_t in_array_stride,
+    VertexStepMode in_step_mode,
+    std::vector<VertexAttributeDescriptor> in_attributes)
+    : array_stride(in_array_stride),
+      step_mode(in_step_mode),
+      attributes(std::move(in_attributes)) {}
+
+}  // namespace tint
diff --git a/src/tint/api/common/vertex_pulling_config.h b/src/tint/api/common/vertex_pulling_config.h
new file mode 100644
index 0000000..ed36b0d
--- /dev/null
+++ b/src/tint/api/common/vertex_pulling_config.h
@@ -0,0 +1,142 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_API_COMMON_VERTEX_PULLING_CONFIG_H_
+#define SRC_TINT_API_COMMON_VERTEX_PULLING_CONFIG_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "src/tint/utils/reflection.h"
+
+namespace tint {
+
+/// Describes the format of data in a vertex buffer.
+enum class VertexFormat : uint8_t {
+    kUint8,            // uint8
+    kUint8x2,          // uint8x2
+    kUint8x4,          // uint8x4
+    kSint8,            // sint8
+    kSint8x2,          // sint8x2
+    kSint8x4,          // sint8x4
+    kUnorm8,           // unorm8
+    kUnorm8x2,         // unorm8x2
+    kUnorm8x4,         // unorm8x4
+    kSnorm8,           // snorm8
+    kSnorm8x2,         // snorm8x2
+    kSnorm8x4,         // snorm8x4
+    kUint16,           // uint16
+    kUint16x2,         // uint16x2
+    kUint16x4,         // uint16x4
+    kSint16,           // sint16
+    kSint16x2,         // sint16x2
+    kSint16x4,         // sint16x4
+    kUnorm16,          // unorm16
+    kUnorm16x2,        // unorm16x2
+    kUnorm16x4,        // unorm16x4
+    kSnorm16,          // snorm16
+    kSnorm16x2,        // snorm16x2
+    kSnorm16x4,        // snorm16x4
+    kFloat16,          // float16
+    kFloat16x2,        // float16x2
+    kFloat16x4,        // float16x4
+    kFloat32,          // float32
+    kFloat32x2,        // float32x2
+    kFloat32x3,        // float32x3
+    kFloat32x4,        // float32x4
+    kUint32,           // uint32
+    kUint32x2,         // uint32x2
+    kUint32x3,         // uint32x3
+    kUint32x4,         // uint32x4
+    kSint32,           // sint32
+    kSint32x2,         // sint32x2
+    kSint32x3,         // sint32x3
+    kSint32x4,         // sint32x4
+    kUnorm10_10_10_2,  // unorm10-10-10-2
+    kUnorm8x4BGRA,     // unorm8x4-bgra
+};
+
+/// Describes if a vertex attribute increments with vertex index or instance index.
+enum class VertexStepMode : uint8_t { kVertex, kInstance };
+
+/// Describes a vertex attribute within a buffer
+struct VertexAttributeDescriptor {
+    /// The format of the attribute.
+    VertexFormat format;
+    /// The byte offset of the attribute in the buffer.
+    uint32_t offset;
+    /// The shader location used for the attribute.
+    uint32_t shader_location;
+
+    /// Reflect the fields of this class so that it can be used by tint::ForeachField()
+    TINT_REFLECT(VertexAttributeDescriptor, format, offset, shader_location);
+};
+
+/// Describes a buffer containing multiple vertex attributes
+struct VertexBufferLayoutDescriptor {
+    /// Constructor
+    VertexBufferLayoutDescriptor();
+    /// Constructor
+    /// @param in_array_stride the array stride in bytes of the in buffer
+    /// @param in_step_mode the step mode of the in buffer
+    /// @param in_attributes the in attributes
+    VertexBufferLayoutDescriptor(uint32_t in_array_stride,
+                                 VertexStepMode in_step_mode,
+                                 std::vector<VertexAttributeDescriptor> in_attributes);
+    /// The array stride in bytes used in the in buffer.
+    uint32_t array_stride = 0u;
+    /// The input step mode used.
+    VertexStepMode step_mode = VertexStepMode::kVertex;
+    /// The vertex attributes.
+    std::vector<VertexAttributeDescriptor> attributes;
+
+    /// Reflect the fields of this class so that it can be used by tint::ForeachField()
+    TINT_REFLECT(VertexBufferLayoutDescriptor, array_stride, step_mode, attributes);
+};
+
+/// Configuration options that control the vertex pulling transform.
+struct VertexPullingConfig {
+    /// The vertex state descriptor, containing info about attributes.
+    std::vector<VertexBufferLayoutDescriptor> vertex_state;
+
+    /// The "group" we will put all our vertex buffers into (as storage buffers).
+    /// Default to 4 as it is past the limits of user-accessible groups.
+    uint32_t pulling_group = 4u;
+
+    /// Reflect the fields of this class so that it can be used by tint::ForeachField()
+    TINT_REFLECT(VertexPullingConfig, vertex_state, pulling_group);
+};
+
+/// Reflection for VertexFormat.
+TINT_REFLECT_ENUM_RANGE(tint::VertexFormat, kUint8x2, kUnorm8x4BGRA);
+
+/// Reflection for VertexStepMode.
+TINT_REFLECT_ENUM_RANGE(tint::VertexStepMode, kVertex, kInstance);
+
+}  // namespace tint
+
+#endif  // SRC_TINT_API_COMMON_VERTEX_PULLING_CONFIG_H_
diff --git a/src/tint/api/common/vertex_pulling_config_test.cc b/src/tint/api/common/vertex_pulling_config_test.cc
new file mode 100644
index 0000000..55cf21d
--- /dev/null
+++ b/src/tint/api/common/vertex_pulling_config_test.cc
@@ -0,0 +1,43 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/api/common/vertex_pulling_config.h"
+
+#include <gtest/gtest.h>
+
+namespace tint {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, ApiCommonVertexPullingConfig) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(VertexPullingConfig);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(VertexAttributeDescriptor);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(VertexBufferLayoutDescriptor);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(VertexPullingConfig);
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc b/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
index 0a602ae..b4201b0 100644
--- a/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
+++ b/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
@@ -28,6 +28,7 @@
 #include "src/tint/lang/wgsl/ast/transform/vertex_pulling.h"
 
 #include <algorithm>
+#include <unordered_map>
 #include <utility>
 
 #include "src/tint/lang/core/builtin_value.h"
@@ -1055,22 +1056,4 @@
 VertexPulling::Config::~Config() = default;
 VertexPulling::Config& VertexPulling::Config::operator=(const Config&) = default;
 
-VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor() = default;
-
-VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor(
-    uint32_t in_array_stride,
-    VertexStepMode in_step_mode,
-    std::vector<VertexAttributeDescriptor> in_attributes)
-    : array_stride(in_array_stride),
-      step_mode(in_step_mode),
-      attributes(std::move(in_attributes)) {}
-
-VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor(
-    const VertexBufferLayoutDescriptor& other) = default;
-
-VertexBufferLayoutDescriptor& VertexBufferLayoutDescriptor::operator=(
-    const VertexBufferLayoutDescriptor& other) = default;
-
-VertexBufferLayoutDescriptor::~VertexBufferLayoutDescriptor() = default;
-
 }  // namespace tint::ast::transform
diff --git a/src/tint/lang/wgsl/ast/transform/vertex_pulling.h b/src/tint/lang/wgsl/ast/transform/vertex_pulling.h
index 69e473c..b7f2b48 100644
--- a/src/tint/lang/wgsl/ast/transform/vertex_pulling.h
+++ b/src/tint/lang/wgsl/ast/transform/vertex_pulling.h
@@ -28,115 +28,14 @@
 #ifndef SRC_TINT_LANG_WGSL_AST_TRANSFORM_VERTEX_PULLING_H_
 #define SRC_TINT_LANG_WGSL_AST_TRANSFORM_VERTEX_PULLING_H_
 
-#include <memory>
-#include <string>
-#include <unordered_map>
 #include <vector>
 
+#include "src/tint/api/common/vertex_pulling_config.h"
 #include "src/tint/lang/wgsl/ast/transform/transform.h"
 #include "src/tint/utils/reflection.h"
 
 namespace tint::ast::transform {
 
-/// Describes the format of data in a vertex buffer
-enum class VertexFormat {
-    kUint8,            // uint8
-    kUint8x2,          // uint8x2
-    kUint8x4,          // uint8x4
-    kSint8,            // sint8
-    kSint8x2,          // sint8x2
-    kSint8x4,          // sint8x4
-    kUnorm8,           // unorm8
-    kUnorm8x2,         // unorm8x2
-    kUnorm8x4,         // unorm8x4
-    kSnorm8,           // snorm8
-    kSnorm8x2,         // snorm8x2
-    kSnorm8x4,         // snorm8x4
-    kUint16,           // uint16
-    kUint16x2,         // uint16x2
-    kUint16x4,         // uint16x4
-    kSint16,           // sint16
-    kSint16x2,         // sint16x2
-    kSint16x4,         // sint16x4
-    kUnorm16,          // unorm16
-    kUnorm16x2,        // unorm16x2
-    kUnorm16x4,        // unorm16x4
-    kSnorm16,          // snorm16
-    kSnorm16x2,        // snorm16x2
-    kSnorm16x4,        // snorm16x4
-    kFloat16,          // float16
-    kFloat16x2,        // float16x2
-    kFloat16x4,        // float16x4
-    kFloat32,          // float32
-    kFloat32x2,        // float32x2
-    kFloat32x3,        // float32x3
-    kFloat32x4,        // float32x4
-    kUint32,           // uint32
-    kUint32x2,         // uint32x2
-    kUint32x3,         // uint32x3
-    kUint32x4,         // uint32x4
-    kSint32,           // sint32
-    kSint32x2,         // sint32x2
-    kSint32x3,         // sint32x3
-    kSint32x4,         // sint32x4
-    kUnorm10_10_10_2,  // unorm10-10-10-2
-    kUnorm8x4BGRA,     // unorm8x4-bgra
-};
-
-/// Describes if a vertex attributes increments with vertex index or instance
-/// index
-enum class VertexStepMode { kVertex, kInstance };
-
-/// Describes a vertex attribute within a buffer
-struct VertexAttributeDescriptor {
-    /// The format of the attribute
-    VertexFormat format;
-    /// The byte offset of the attribute in the buffer
-    uint32_t offset;
-    /// The shader location used for the attribute
-    uint32_t shader_location;
-
-    /// Reflect the fields of this class so that it can be used by tint::ForeachField()
-    TINT_REFLECT(VertexAttributeDescriptor, format, offset, shader_location);
-};
-
-/// Describes a buffer containing multiple vertex attributes
-struct VertexBufferLayoutDescriptor {
-    /// Constructor
-    VertexBufferLayoutDescriptor();
-    /// Constructor
-    /// @param in_array_stride the array stride of the in buffer
-    /// @param in_step_mode the step mode of the in buffer
-    /// @param in_attributes the in attributes
-    VertexBufferLayoutDescriptor(uint32_t in_array_stride,
-                                 VertexStepMode in_step_mode,
-                                 std::vector<VertexAttributeDescriptor> in_attributes);
-    /// Copy constructor
-    /// @param other the struct to copy
-    VertexBufferLayoutDescriptor(const VertexBufferLayoutDescriptor& other);
-
-    /// Assignment operator
-    /// @param other the struct to copy
-    /// @returns this struct
-    VertexBufferLayoutDescriptor& operator=(const VertexBufferLayoutDescriptor& other);
-
-    ~VertexBufferLayoutDescriptor();
-
-    /// The array stride used in the in buffer
-    uint32_t array_stride = 0u;
-    /// The input step mode used
-    VertexStepMode step_mode = VertexStepMode::kVertex;
-    /// The vertex attributes
-    std::vector<VertexAttributeDescriptor> attributes;
-
-    /// Reflect the fields of this class so that it can be used by tint::ForeachField()
-    TINT_REFLECT(VertexBufferLayoutDescriptor, array_stride, step_mode, attributes);
-};
-
-/// Describes vertex state, which consists of many buffers containing vertex
-/// attributes
-using VertexStateDescriptor = std::vector<VertexBufferLayoutDescriptor>;
-
 /// Converts a program to use vertex pulling
 ///
 /// Variables which accept vertex input are var<in> with a location attribute.
@@ -177,7 +76,7 @@
         Config& operator=(const Config&);
 
         /// The vertex state descriptor, containing info about attributes
-        VertexStateDescriptor vertex_state;
+        std::vector<VertexBufferLayoutDescriptor> vertex_state;
 
         /// The "group" we will put all our vertex buffers into (as storage buffers)
         /// Default to 4 as it is past the limits of user-accessible groups
@@ -206,14 +105,4 @@
 
 }  // namespace tint::ast::transform
 
-namespace tint {
-
-/// Reflection for VertexFormat
-TINT_REFLECT_ENUM_RANGE(tint::ast::transform::VertexFormat, kUint8x2, kUnorm10_10_10_2);
-
-/// Reflection for VertexStepMode
-TINT_REFLECT_ENUM_RANGE(tint::ast::transform::VertexStepMode, kVertex, kInstance);
-
-}  // namespace tint
-
 #endif  // SRC_TINT_LANG_WGSL_AST_TRANSFORM_VERTEX_PULLING_H_
diff --git a/src/tint/lang/wgsl/ast/transform/vertex_pulling_test.cc b/src/tint/lang/wgsl/ast/transform/vertex_pulling_test.cc
index 175a9f1..4d119b6 100644
--- a/src/tint/lang/wgsl/ast/transform/vertex_pulling_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/vertex_pulling_test.cc
@@ -34,12 +34,6 @@
 namespace tint::ast::transform {
 namespace {
 
-TEST(TintCheckAllFieldsReflected, WgslAstTransformVertexPullingTest) {
-    TINT_ASSERT_ALL_FIELDS_REFLECTED(VertexAttributeDescriptor);
-    TINT_ASSERT_ALL_FIELDS_REFLECTED(VertexBufferLayoutDescriptor);
-    TINT_ASSERT_ALL_FIELDS_REFLECTED(VertexPulling::Config);
-}
-
 using VertexPullingTest = TransformTest;
 
 TEST_F(VertexPullingTest, Error_NoEntryPoint) {