Use more ityp helpers for VertexBufferSlot.

Bug: dawn:2222
Change-Id: I255f7780d20592559c2b6d20ee92878aee7e804e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/164320
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Loko Kung <lokokung@google.com>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn/native/CommandBufferStateTracker.cpp b/src/dawn/native/CommandBufferStateTracker.cpp
index 4946180..309da4d 100644
--- a/src/dawn/native/CommandBufferStateTracker.cpp
+++ b/src/dawn/native/CommandBufferStateTracker.cpp
@@ -397,10 +397,10 @@
 
     RenderPipelineBase* lastRenderPipeline = GetRenderPipeline();
 
-    const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& vertexBufferSlotsUsedAsVertexBuffer =
-        lastRenderPipeline->GetVertexBufferSlotsUsedAsVertexBuffer();
+    const auto& vertexBuffersUsedAsVertexBuffer =
+        lastRenderPipeline->GetVertexBuffersUsedAsVertexBuffer();
 
-    for (auto usedSlotVertex : IterateBitSet(vertexBufferSlotsUsedAsVertexBuffer)) {
+    for (auto usedSlotVertex : IterateBitSet(vertexBuffersUsedAsVertexBuffer)) {
         const VertexBufferInfo& vertexBuffer = lastRenderPipeline->GetVertexBuffer(usedSlotVertex);
         uint64_t arrayStride = vertexBuffer.arrayStride;
         uint64_t bufferSize = mVertexBufferSizes[usedSlotVertex];
@@ -443,10 +443,10 @@
 
     RenderPipelineBase* lastRenderPipeline = GetRenderPipeline();
 
-    const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& vertexBufferSlotsUsedAsInstanceBuffer =
-        lastRenderPipeline->GetVertexBufferSlotsUsedAsInstanceBuffer();
+    const auto& vertexBuffersUsedAsInstanceBuffer =
+        lastRenderPipeline->GetVertexBuffersUsedAsInstanceBuffer();
 
-    for (auto usedSlotInstance : IterateBitSet(vertexBufferSlotsUsedAsInstanceBuffer)) {
+    for (auto usedSlotInstance : IterateBitSet(vertexBuffersUsedAsInstanceBuffer)) {
         const VertexBufferInfo& vertexBuffer =
             lastRenderPipeline->GetVertexBuffer(usedSlotInstance);
         uint64_t arrayStride = vertexBuffer.arrayStride;
@@ -548,9 +548,8 @@
     if (aspects[VALIDATION_ASPECT_VERTEX_BUFFERS]) {
         RenderPipelineBase* lastRenderPipeline = GetRenderPipeline();
 
-        const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& requiredVertexBuffers =
-            lastRenderPipeline->GetVertexBufferSlotsUsed();
-        if (IsSubset(requiredVertexBuffers, mVertexBufferSlotsUsed)) {
+        const auto& requiredVertexBuffers = lastRenderPipeline->GetVertexBuffersUsed();
+        if (IsSubset(requiredVertexBuffers, mVertexBuffersUsed)) {
             mAspects.set(VALIDATION_ASPECT_VERTEX_BUFFERS);
         }
     }
@@ -601,8 +600,8 @@
 
     if (aspects[VALIDATION_ASPECT_VERTEX_BUFFERS]) {
         // Try to be helpful by finding one missing vertex buffer to surface in the error message.
-        const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> missingVertexBuffers =
-            GetRenderPipeline()->GetVertexBufferSlotsUsed() & ~mVertexBufferSlotsUsed;
+        const auto missingVertexBuffers =
+            GetRenderPipeline()->GetVertexBuffersUsed() & ~mVertexBuffersUsed;
         DAWN_ASSERT(missingVertexBuffers.any());
 
         VertexBufferSlot firstMissing = ityp::Sub(GetHighestBitIndexPlusOne(missingVertexBuffers),
@@ -758,13 +757,13 @@
 }
 
 void CommandBufferStateTracker::UnsetVertexBuffer(VertexBufferSlot slot) {
-    mVertexBufferSlotsUsed.set(slot, false);
+    mVertexBuffersUsed.set(slot, false);
     mVertexBufferSizes[slot] = 0;
     mAspects.reset(VALIDATION_ASPECT_VERTEX_BUFFERS);
 }
 
 void CommandBufferStateTracker::SetVertexBuffer(VertexBufferSlot slot, uint64_t size) {
-    mVertexBufferSlotsUsed.set(slot);
+    mVertexBuffersUsed.set(slot);
     mVertexBufferSizes[slot] = size;
 }
 
diff --git a/src/dawn/native/CommandBufferStateTracker.h b/src/dawn/native/CommandBufferStateTracker.h
index 64ea127..a7c8dc4 100644
--- a/src/dawn/native/CommandBufferStateTracker.h
+++ b/src/dawn/native/CommandBufferStateTracker.h
@@ -94,13 +94,14 @@
 
     ityp::array<BindGroupIndex, BindGroupBase*, kMaxBindGroups> mBindgroups = {};
     ityp::array<BindGroupIndex, std::vector<uint32_t>, kMaxBindGroups> mDynamicOffsets = {};
-    ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> mVertexBufferSlotsUsed;
+
+    VertexBufferMask mVertexBuffersUsed;
+    PerVertexBuffer<uint64_t> mVertexBufferSizes = {};
+
     bool mIndexBufferSet = false;
     wgpu::IndexFormat mIndexFormat;
     uint64_t mIndexBufferSize = 0;
 
-    ityp::array<VertexBufferSlot, uint64_t, kMaxVertexBuffers> mVertexBufferSizes = {};
-
     PipelineLayoutBase* mLastPipelineLayout = nullptr;
     PipelineBase* mLastPipeline = nullptr;
 
diff --git a/src/dawn/native/IntegerTypes.h b/src/dawn/native/IntegerTypes.h
index 913aba7..890cf12 100644
--- a/src/dawn/native/IntegerTypes.h
+++ b/src/dawn/native/IntegerTypes.h
@@ -33,6 +33,22 @@
 #include "dawn/common/Constants.h"
 #include "dawn/common/TypedInteger.h"
 
+namespace dawn::ityp {
+template <typename Index, typename Value, size_t Size>
+class array;
+
+template <typename Index, size_t N>
+class bitset;
+}  // namespace dawn::ityp
+
+// This files creates a number of integer types using ityp to represent the zoo of indices used all
+// over the codebase, so that the semantic of numbers is clear from the types, but also so it is
+// harder to use the wrong type to index into array, bitsets and other containers.
+//
+// In addition various container type aliases are declared so that they have a consistent name
+// everywhere and don't need explicit sizing with the kMaxStuff constants. Respective ityp::
+// headers still need to be #included in the files using them though.
+
 namespace dawn::native {
 
 // Binding numbers in the shader and BindGroup/BindGroupLayoutDescriptors
@@ -49,10 +65,16 @@
 constexpr ColorAttachmentIndex kMaxColorAttachmentsTyped =
     ColorAttachmentIndex(kMaxColorAttachments);
 
+// Vertex buffer slots represent the `slot` passed in calls to SetVertexBuffer or the index in the
+// wgpu::VertexState::vertexBuffers
 using VertexBufferSlot = TypedInteger<struct VertexBufferSlotT, uint8_t>;
-using VertexAttributeLocation = TypedInteger<struct VertexAttributeLocationT, uint8_t>;
-
 constexpr VertexBufferSlot kMaxVertexBuffersTyped = VertexBufferSlot(kMaxVertexBuffers);
+
+using VertexBufferMask = ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>;
+template <typename Value>
+using PerVertexBuffer = ityp::array<VertexBufferSlot, Value, kMaxVertexBuffers>;
+
+using VertexAttributeLocation = TypedInteger<struct VertexAttributeLocationT, uint8_t>;
 constexpr VertexAttributeLocation kMaxVertexAttributesTyped =
     VertexAttributeLocation(kMaxVertexAttributes);
 
diff --git a/src/dawn/native/RenderPipeline.cpp b/src/dawn/native/RenderPipeline.cpp
index 4e5f572..a97e479 100644
--- a/src/dawn/native/RenderPipeline.cpp
+++ b/src/dawn/native/RenderPipeline.cpp
@@ -835,53 +835,53 @@
                    GetRenderStagesAndSetPlaceholderShader(device, descriptor)),
       mAttachmentState(device->GetOrCreateAttachmentState(descriptor, GetLayout())) {
     mVertexBufferCount = descriptor->vertex.bufferCount;
-    const VertexBufferLayout* buffers = descriptor->vertex.buffers;
-    for (uint8_t slot = 0; slot < mVertexBufferCount; ++slot) {
+
+    auto buffers =
+        ityp::SpanFromUntyped<VertexBufferSlot>(descriptor->vertex.buffers, mVertexBufferCount);
+    for (auto [slot, buffer] : Enumerate(buffers)) {
         // Skip unused slots
-        if (buffers[slot].stepMode == wgpu::VertexStepMode::VertexBufferNotUsed) {
+        if (buffer.stepMode == wgpu::VertexStepMode::VertexBufferNotUsed) {
             continue;
         }
 
-        VertexBufferSlot typedSlot(slot);
-
-        mVertexBufferSlotsUsed.set(typedSlot);
-        mVertexBufferInfos[typedSlot].arrayStride = buffers[slot].arrayStride;
-        mVertexBufferInfos[typedSlot].stepMode = buffers[slot].stepMode;
-        mVertexBufferInfos[typedSlot].usedBytesInStride = 0;
-        mVertexBufferInfos[typedSlot].lastStride = 0;
-        switch (buffers[slot].stepMode) {
+        mVertexBuffersUsed.set(slot);
+        mVertexBufferInfos[slot].arrayStride = buffer.arrayStride;
+        mVertexBufferInfos[slot].stepMode = buffer.stepMode;
+        mVertexBufferInfos[slot].usedBytesInStride = 0;
+        mVertexBufferInfos[slot].lastStride = 0;
+        switch (buffer.stepMode) {
             case wgpu::VertexStepMode::Vertex:
-                mVertexBufferSlotsUsedAsVertexBuffer.set(typedSlot);
+                mVertexBuffersUsedAsVertexBuffer.set(slot);
                 break;
             case wgpu::VertexStepMode::Instance:
-                mVertexBufferSlotsUsedAsInstanceBuffer.set(typedSlot);
+                mVertexBuffersUsedAsInstanceBuffer.set(slot);
                 break;
             default:
                 DAWN_UNREACHABLE();
         }
 
-        for (uint32_t i = 0; i < buffers[slot].attributeCount; ++i) {
-            VertexAttributeLocation location = VertexAttributeLocation(
-                static_cast<uint8_t>(buffers[slot].attributes[i].shaderLocation));
+        auto attributes = ityp::SpanFromUntyped<size_t>(buffer.attributes, buffer.attributeCount);
+        for (auto [i, attribute] : Enumerate(attributes)) {
+            VertexAttributeLocation location =
+                VertexAttributeLocation(static_cast<uint8_t>(attribute.shaderLocation));
+
             mAttributeLocationsUsed.set(location);
             mAttributeInfos[location].shaderLocation = location;
-            mAttributeInfos[location].vertexBufferSlot = typedSlot;
-            mAttributeInfos[location].offset = buffers[slot].attributes[i].offset;
-            mAttributeInfos[location].format = buffers[slot].attributes[i].format;
+            mAttributeInfos[location].vertexBufferSlot = slot;
+            mAttributeInfos[location].offset = attribute.offset;
+            mAttributeInfos[location].format = attribute.format;
             // Compute the access boundary of this attribute by adding attribute format size to
             // attribute offset. Although offset is in uint64_t, such sum must be no larger than
             // maxVertexBufferArrayStride (2048), which is promised by the GPUVertexBufferLayout
             // validation of creating render pipeline. Therefore, calculating in uint16_t will
             // cause no overflow.
-            uint32_t formatByteSize =
-                GetVertexFormatInfo(buffers[slot].attributes[i].format).byteSize;
-            DAWN_ASSERT(buffers[slot].attributes[i].offset <= 2048);
-            uint16_t accessBoundary =
-                uint16_t(buffers[slot].attributes[i].offset) + uint16_t(formatByteSize);
-            mVertexBufferInfos[typedSlot].usedBytesInStride =
-                std::max(mVertexBufferInfos[typedSlot].usedBytesInStride, accessBoundary);
-            mVertexBufferInfos[typedSlot].lastStride =
-                std::max(mVertexBufferInfos[typedSlot].lastStride,
+            uint32_t formatByteSize = GetVertexFormatInfo(attribute.format).byteSize;
+            DAWN_ASSERT(attribute.offset <= 2048);
+            uint16_t accessBoundary = uint16_t(attribute.offset) + uint16_t(formatByteSize);
+            mVertexBufferInfos[slot].usedBytesInStride =
+                std::max(mVertexBufferInfos[slot].usedBytesInStride, accessBoundary);
+            mVertexBufferInfos[slot].lastStride =
+                std::max(mVertexBufferInfos[slot].lastStride,
                          mAttributeInfos[location].offset + formatByteSize);
         }
     }
@@ -1017,27 +1017,24 @@
     return mAttributeInfos[location];
 }
 
-const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>&
-RenderPipelineBase::GetVertexBufferSlotsUsed() const {
+const VertexBufferMask& RenderPipelineBase::GetVertexBuffersUsed() const {
     DAWN_ASSERT(!IsError());
-    return mVertexBufferSlotsUsed;
+    return mVertexBuffersUsed;
 }
 
-const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>&
-RenderPipelineBase::GetVertexBufferSlotsUsedAsVertexBuffer() const {
+const VertexBufferMask& RenderPipelineBase::GetVertexBuffersUsedAsVertexBuffer() const {
     DAWN_ASSERT(!IsError());
-    return mVertexBufferSlotsUsedAsVertexBuffer;
+    return mVertexBuffersUsedAsVertexBuffer;
 }
 
-const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>&
-RenderPipelineBase::GetVertexBufferSlotsUsedAsInstanceBuffer() const {
+const VertexBufferMask& RenderPipelineBase::GetVertexBuffersUsedAsInstanceBuffer() const {
     DAWN_ASSERT(!IsError());
-    return mVertexBufferSlotsUsedAsInstanceBuffer;
+    return mVertexBuffersUsedAsInstanceBuffer;
 }
 
 const VertexBufferInfo& RenderPipelineBase::GetVertexBuffer(VertexBufferSlot slot) const {
     DAWN_ASSERT(!IsError());
-    DAWN_ASSERT(mVertexBufferSlotsUsed[slot]);
+    DAWN_ASSERT(mVertexBuffersUsed[slot]);
     return mVertexBufferInfos[slot];
 }
 
@@ -1201,8 +1198,8 @@
         recorder.Record(desc.shaderLocation, desc.vertexBufferSlot, desc.offset, desc.format);
     }
 
-    recorder.Record(mVertexBufferSlotsUsed);
-    for (VertexBufferSlot slot : IterateBitSet(mVertexBufferSlotsUsed)) {
+    recorder.Record(mVertexBuffersUsed);
+    for (VertexBufferSlot slot : IterateBitSet(mVertexBuffersUsed)) {
         const VertexBufferInfo& desc = GetVertexBuffer(slot);
         recorder.Record(desc.arrayStride, desc.stepMode);
     }
@@ -1306,11 +1303,11 @@
         }
     }
 
-    if (a->mVertexBufferSlotsUsed != b->mVertexBufferSlotsUsed) {
+    if (a->mVertexBuffersUsed != b->mVertexBuffersUsed) {
         return false;
     }
 
-    for (VertexBufferSlot slot : IterateBitSet(a->mVertexBufferSlotsUsed)) {
+    for (VertexBufferSlot slot : IterateBitSet(a->mVertexBuffersUsed)) {
         const VertexBufferInfo& descA = a->GetVertexBuffer(slot);
         const VertexBufferInfo& descB = b->GetVertexBuffer(slot);
         if (descA.arrayStride != descB.arrayStride || descA.stepMode != descB.stepMode) {
diff --git a/src/dawn/native/RenderPipeline.h b/src/dawn/native/RenderPipeline.h
index 9ba6f0c..359249a 100644
--- a/src/dawn/native/RenderPipeline.h
+++ b/src/dawn/native/RenderPipeline.h
@@ -33,7 +33,6 @@
 #include <vector>
 
 #include "dawn/common/ContentLessObjectCacheable.h"
-#include "dawn/common/TypedInteger.h"
 #include "dawn/native/AttachmentState.h"
 #include "dawn/native/Forward.h"
 #include "dawn/native/IntegerTypes.h"
@@ -102,11 +101,9 @@
     const ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes>& GetAttributeLocationsUsed()
         const;
     const VertexAttributeInfo& GetAttribute(VertexAttributeLocation location) const;
-    const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& GetVertexBufferSlotsUsed() const;
-    const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>&
-    GetVertexBufferSlotsUsedAsVertexBuffer() const;
-    const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>&
-    GetVertexBufferSlotsUsedAsInstanceBuffer() const;
+    const VertexBufferMask& GetVertexBuffersUsed() const;
+    const VertexBufferMask& GetVertexBuffersUsedAsVertexBuffer() const;
+    const VertexBufferMask& GetVertexBuffersUsedAsInstanceBuffer() const;
     const VertexBufferInfo& GetVertexBuffer(VertexBufferSlot slot) const;
     uint32_t GetVertexBufferCount() const;
 
@@ -154,10 +151,10 @@
     uint32_t mVertexBufferCount;
     ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes> mAttributeLocationsUsed;
     ityp::array<VertexAttributeLocation, VertexAttributeInfo, kMaxVertexAttributes> mAttributeInfos;
-    ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> mVertexBufferSlotsUsed;
-    ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> mVertexBufferSlotsUsedAsVertexBuffer;
-    ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> mVertexBufferSlotsUsedAsInstanceBuffer;
-    ityp::array<VertexBufferSlot, VertexBufferInfo, kMaxVertexBuffers> mVertexBufferInfos;
+    VertexBufferMask mVertexBuffersUsed;
+    VertexBufferMask mVertexBuffersUsedAsVertexBuffer;
+    VertexBufferMask mVertexBuffersUsedAsInstanceBuffer;
+    PerVertexBuffer<VertexBufferInfo> mVertexBufferInfos;
 
     // Attachments
     Ref<AttachmentState> mAttachmentState;
diff --git a/src/dawn/native/TintUtils.cpp b/src/dawn/native/TintUtils.cpp
index 0736045..534e82f 100644
--- a/src/dawn/native/TintUtils.cpp
+++ b/src/dawn/native/TintUtils.cpp
@@ -178,7 +178,7 @@
     cfg.pulling_group = static_cast<uint32_t>(pullingBufferBindingSet);
 
     cfg.vertex_state.resize(renderPipeline.GetVertexBufferCount());
-    for (VertexBufferSlot slot : IterateBitSet(renderPipeline.GetVertexBufferSlotsUsed())) {
+    for (VertexBufferSlot slot : IterateBitSet(renderPipeline.GetVertexBuffersUsed())) {
         const VertexBufferInfo& dawnInfo = renderPipeline.GetVertexBuffer(slot);
         tint::ast::transform::VertexBufferLayoutDescriptor* tintInfo =
             &cfg.vertex_state[static_cast<uint8_t>(slot)];
diff --git a/src/dawn/native/d3d11/CommandBufferD3D11.cpp b/src/dawn/native/d3d11/CommandBufferD3D11.cpp
index fe550bf..49cd6e1 100644
--- a/src/dawn/native/d3d11/CommandBufferD3D11.cpp
+++ b/src/dawn/native/d3d11/CommandBufferD3D11.cpp
@@ -91,8 +91,7 @@
         // If the vertex state has changed, we need to update the strides.
         if (mLastAppliedRenderPipeline != renderPipeline) {
             mLastAppliedRenderPipeline = renderPipeline;
-            for (VertexBufferSlot slot :
-                 IterateBitSet(renderPipeline->GetVertexBufferSlotsUsed())) {
+            for (VertexBufferSlot slot : IterateBitSet(renderPipeline->GetVertexBuffersUsed())) {
                 mStrides[slot] = renderPipeline->GetVertexBuffer(slot).arrayStride;
             }
         }
@@ -104,9 +103,9 @@
   private:
     const ScopedSwapStateCommandRecordingContext* mCommandContext;
     const RenderPipeline* mLastAppliedRenderPipeline = nullptr;
-    ityp::array<VertexBufferSlot, ID3D11Buffer*, kMaxVertexBuffers> mD3D11Buffers = {};
-    ityp::array<VertexBufferSlot, UINT, kMaxVertexBuffers> mStrides = {};
-    ityp::array<VertexBufferSlot, UINT, kMaxVertexBuffers> mOffsets = {};
+    PerVertexBuffer<ID3D11Buffer*> mD3D11Buffers = {};
+    PerVertexBuffer<UINT> mStrides = {};
+    PerVertexBuffer<UINT> mOffsets = {};
 };
 
 MaybeError SynchronizeTextureBeforeUse(
diff --git a/src/dawn/native/d3d12/CommandBufferD3D12.cpp b/src/dawn/native/d3d12/CommandBufferD3D12.cpp
index 6a86747..ba6727b 100644
--- a/src/dawn/native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn/native/d3d12/CommandBufferD3D12.cpp
@@ -664,7 +664,7 @@
   public:
     void OnSetVertexBuffer(VertexBufferSlot slot, Buffer* buffer, uint64_t offset, uint64_t size) {
         mStartSlot = std::min(mStartSlot, slot);
-        mEndSlot = std::max(mEndSlot, ityp::Add(slot, VertexBufferSlot(uint8_t(1))));
+        mEndSlot = std::max(mEndSlot, ityp::PlusOne(slot));
 
         auto* d3d12BufferView = &mD3D12BufferViews[slot];
         d3d12BufferView->BufferLocation = buffer->GetVA() + offset;
@@ -684,10 +684,9 @@
         if (mLastAppliedRenderPipeline != renderPipeline) {
             mLastAppliedRenderPipeline = renderPipeline;
 
-            for (VertexBufferSlot slot :
-                 IterateBitSet(renderPipeline->GetVertexBufferSlotsUsed())) {
+            for (VertexBufferSlot slot : IterateBitSet(renderPipeline->GetVertexBuffersUsed())) {
                 startSlot = std::min(startSlot, slot);
-                endSlot = std::max(endSlot, ityp::Add(slot, VertexBufferSlot(uint8_t(1))));
+                endSlot = std::max(endSlot, ityp::PlusOne(slot));
                 mD3D12BufferViews[slot].StrideInBytes =
                     renderPipeline->GetVertexBuffer(slot).arrayStride;
             }
@@ -705,8 +704,8 @@
                                         static_cast<uint8_t>(ityp::Sub(endSlot, startSlot)),
                                         &mD3D12BufferViews[startSlot]);
 
-        mStartSlot = VertexBufferSlot(kMaxVertexBuffers);
-        mEndSlot = VertexBufferSlot(uint8_t(0));
+        mStartSlot = kMaxVertexBuffersTyped;
+        mEndSlot = {};
     }
 
   private:
@@ -716,9 +715,8 @@
     // data in the middle of the range).
     const RenderPipeline* mLastAppliedRenderPipeline = nullptr;
     VertexBufferSlot mStartSlot{kMaxVertexBuffers};
-    VertexBufferSlot mEndSlot{uint8_t(0)};
-    ityp::array<VertexBufferSlot, D3D12_VERTEX_BUFFER_VIEW, kMaxVertexBuffers> mD3D12BufferViews =
-        {};
+    VertexBufferSlot mEndSlot{};
+    PerVertexBuffer<D3D12_VERTEX_BUFFER_VIEW> mD3D12BufferViews = {};
 };
 
 void ResolveMultisampledRenderPass(CommandRecordingContext* commandContext,
diff --git a/src/dawn/native/metal/CommandBufferMTL.mm b/src/dawn/native/metal/CommandBufferMTL.mm
index 22f0ce5..c3d37f1 100644
--- a/src/dawn/native/metal/CommandBufferMTL.mm
+++ b/src/dawn/native/metal/CommandBufferMTL.mm
@@ -683,14 +683,13 @@
         // When a new pipeline is bound we must set all the vertex buffers again because
         // they might have been offset by the pipeline layout, and they might be packed
         // differently from the previous pipeline.
-        mDirtyVertexBuffers |= pipeline->GetVertexBufferSlotsUsed();
+        mDirtyVertexBuffers |= pipeline->GetVertexBuffersUsed();
     }
 
     void Apply(id<MTLRenderCommandEncoder> encoder,
                RenderPipeline* pipeline,
                bool enableVertexPulling) {
-        const auto& vertexBuffersToApply =
-            mDirtyVertexBuffers & pipeline->GetVertexBufferSlotsUsed();
+        const auto& vertexBuffersToApply = mDirtyVertexBuffers & pipeline->GetVertexBuffersUsed();
 
         for (VertexBufferSlot slot : IterateBitSet(vertexBuffersToApply)) {
             uint32_t metalIndex = pipeline->GetMtlVertexBufferIndex(slot);
@@ -712,10 +711,10 @@
 
   private:
     // All the indices in these arrays are Dawn vertex buffer indices
-    ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> mDirtyVertexBuffers;
-    ityp::array<VertexBufferSlot, id<MTLBuffer>, kMaxVertexBuffers> mVertexBuffers;
-    ityp::array<VertexBufferSlot, NSUInteger, kMaxVertexBuffers> mVertexBufferOffsets;
-    ityp::array<VertexBufferSlot, uint32_t, kMaxVertexBuffers> mVertexBufferBindingSizes;
+    VertexBufferMask mDirtyVertexBuffers;
+    PerVertexBuffer<id<MTLBuffer>> mVertexBuffers;
+    PerVertexBuffer<NSUInteger> mVertexBufferOffsets;
+    PerVertexBuffer<uint32_t> mVertexBufferBindingSizes;
 
     StorageBufferLengthTracker* mLengthTracker;
 };
diff --git a/src/dawn/native/metal/RenderPipelineMTL.h b/src/dawn/native/metal/RenderPipelineMTL.h
index 0033dd6..51d2a3c 100644
--- a/src/dawn/native/metal/RenderPipelineMTL.h
+++ b/src/dawn/native/metal/RenderPipelineMTL.h
@@ -75,7 +75,7 @@
     MTLCullMode mMtlCullMode;
     NSPRef<id<MTLRenderPipelineState>> mMtlRenderPipelineState;
     NSPRef<id<MTLDepthStencilState>> mMtlDepthStencilState;
-    ityp::array<VertexBufferSlot, uint32_t, kMaxVertexBuffers> mMtlVertexBufferIndices;
+    PerVertexBuffer<uint32_t> mMtlVertexBufferIndices;
 
     wgpu::ShaderStage mStagesRequiringStorageBufferLength = wgpu::ShaderStage::None;
 };
diff --git a/src/dawn/native/metal/RenderPipelineMTL.mm b/src/dawn/native/metal/RenderPipelineMTL.mm
index 7c65a35..3ea2a23 100644
--- a/src/dawn/native/metal/RenderPipelineMTL.mm
+++ b/src/dawn/native/metal/RenderPipelineMTL.mm
@@ -353,7 +353,7 @@
         uint32_t mtlVertexBufferIndex =
             ToBackend(GetLayout())->GetBufferBindingCount(SingleShaderStage::Vertex);
 
-        for (VertexBufferSlot slot : IterateBitSet(GetVertexBufferSlotsUsed())) {
+        for (VertexBufferSlot slot : IterateBitSet(GetVertexBuffersUsed())) {
             mMtlVertexBufferIndices[slot] = mtlVertexBufferIndex;
             mtlVertexBufferIndex++;
         }
@@ -511,7 +511,7 @@
 NSRef<MTLVertexDescriptor> RenderPipeline::MakeVertexDesc() const {
     MTLVertexDescriptor* mtlVertexDescriptor = [MTLVertexDescriptor new];
 
-    for (VertexBufferSlot slot : IterateBitSet(GetVertexBufferSlotsUsed())) {
+    for (VertexBufferSlot slot : IterateBitSet(GetVertexBuffersUsed())) {
         const VertexBufferInfo& info = GetVertexBuffer(slot);
 
         MTLVertexBufferLayoutDescriptor* layoutDesc = [MTLVertexBufferLayoutDescriptor new];
diff --git a/src/dawn/native/metal/ShaderModuleMTL.mm b/src/dawn/native/metal/ShaderModuleMTL.mm
index ab996f0..f983c20 100644
--- a/src/dawn/native/metal/ShaderModuleMTL.mm
+++ b/src/dawn/native/metal/ShaderModuleMTL.mm
@@ -213,7 +213,7 @@
         vertexPullingTransformConfig =
             BuildVertexPullingTransformConfig(*renderPipeline, kPullingBufferBindingSet);
 
-        for (VertexBufferSlot slot : IterateBitSet(renderPipeline->GetVertexBufferSlotsUsed())) {
+        for (VertexBufferSlot slot : IterateBitSet(renderPipeline->GetVertexBuffersUsed())) {
             uint32_t metalIndex = renderPipeline->GetMtlVertexBufferIndex(slot);
 
             // Tell Tint to map (kPullingBufferBindingSet, slot) to this MSL buffer index.
diff --git a/src/dawn/native/opengl/CommandBufferGL.cpp b/src/dawn/native/opengl/CommandBufferGL.cpp
index ad8824d..75b10e5 100644
--- a/src/dawn/native/opengl/CommandBufferGL.cpp
+++ b/src/dawn/native/opengl/CommandBufferGL.cpp
@@ -180,7 +180,7 @@
         }
 
         mIndexBufferDirty = true;
-        mDirtyVertexBuffers |= pipeline->GetVertexBufferSlotsUsed();
+        mDirtyVertexBuffers |= pipeline->GetVertexBuffersUsed();
 
         mLastPipeline = pipeline;
     }
@@ -192,7 +192,7 @@
         }
 
         for (VertexBufferSlot slot :
-             IterateBitSet(mDirtyVertexBuffers & mLastPipeline->GetVertexBufferSlotsUsed())) {
+             IterateBitSet(mDirtyVertexBuffers & mLastPipeline->GetVertexBuffersUsed())) {
             for (VertexAttributeLocation location :
                  IterateBitSet(ToBackend(mLastPipeline)->GetAttributesUsingVertexBuffer(slot))) {
                 const VertexAttributeInfo& attribute = mLastPipeline->GetAttribute(location);
@@ -226,9 +226,9 @@
     bool mIndexBufferDirty = false;
     Buffer* mIndexBuffer = nullptr;
 
-    ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> mDirtyVertexBuffers;
-    ityp::array<VertexBufferSlot, Buffer*, kMaxVertexBuffers> mVertexBuffers;
-    ityp::array<VertexBufferSlot, uint64_t, kMaxVertexBuffers> mVertexBufferOffsets;
+    VertexBufferMask mDirtyVertexBuffers;
+    PerVertexBuffer<Buffer*> mVertexBuffers;
+    PerVertexBuffer<uint64_t> mVertexBufferOffsets;
 
     RenderPipelineBase* mLastPipeline = nullptr;
 };
diff --git a/src/dawn/native/opengl/RenderPipelineGL.h b/src/dawn/native/opengl/RenderPipelineGL.h
index 08995e3..5028873 100644
--- a/src/dawn/native/opengl/RenderPipelineGL.h
+++ b/src/dawn/native/opengl/RenderPipelineGL.h
@@ -64,9 +64,7 @@
     GLuint mVertexArrayObject;
     GLenum mGlPrimitiveTopology;
 
-    ityp::array<VertexBufferSlot,
-                ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes>,
-                kMaxVertexBuffers>
+    PerVertexBuffer<ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes>>
         mAttributesUsingVertexBuffer;
 };
 
diff --git a/src/dawn/native/vulkan/RenderPipelineVk.cpp b/src/dawn/native/vulkan/RenderPipelineVk.cpp
index 86dd907c..6b4af38 100644
--- a/src/dawn/native/vulkan/RenderPipelineVk.cpp
+++ b/src/dawn/native/vulkan/RenderPipelineVk.cpp
@@ -611,7 +611,7 @@
     PipelineVertexInputStateCreateInfoTemporaryAllocations* tempAllocations) {
     // Fill in the "binding info" that will be chained in the create info
     uint32_t bindingCount = 0;
-    for (VertexBufferSlot slot : IterateBitSet(GetVertexBufferSlotsUsed())) {
+    for (VertexBufferSlot slot : IterateBitSet(GetVertexBuffersUsed())) {
         const VertexBufferInfo& bindingInfo = GetVertexBuffer(slot);
 
         VkVertexInputBindingDescription* bindingDesc = &tempAllocations->bindings[bindingCount];