Dawn: vertex buffer never OOB with zero stride count draw/Indexed

According to the spec, when call draw or drawIndexed, vertex step mode
vertex buffer never OOB if (vertexCount + firstVertex) = 0, and instance
step mode vertex buffer never OOB if (instanceCount + firstInstance) = 0.
Modify the validation implementation to be aligned with the spec, and
add corresponding unit tests.
This patch also add unit test case for (strideCount - 1) * arrayStride +
lastStride <= bound buffer size < strideCount * arrayStride.

Bug: dawn:1287
Change-Id: If444e400f5ac24f86ca12ff59fb886d8ef70e8c7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/90584
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Zhaoming Jiang <zhaoming.jiang@intel.com>
diff --git a/src/dawn/native/CommandBufferStateTracker.cpp b/src/dawn/native/CommandBufferStateTracker.cpp
index eb3affb..3a213fe 100644
--- a/src/dawn/native/CommandBufferStateTracker.cpp
+++ b/src/dawn/native/CommandBufferStateTracker.cpp
@@ -98,6 +98,13 @@
 
 MaybeError CommandBufferStateTracker::ValidateBufferInRangeForVertexBuffer(uint32_t vertexCount,
                                                                            uint32_t firstVertex) {
+    uint64_t strideCount = static_cast<uint64_t>(firstVertex) + vertexCount;
+
+    if (strideCount == 0) {
+        // All vertex step mode buffers are always in range if stride count is zero
+        return {};
+    }
+
     RenderPipelineBase* lastRenderPipeline = GetRenderPipeline();
 
     const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& vertexBufferSlotsUsedAsVertexBuffer =
@@ -115,23 +122,21 @@
                             bufferSize, static_cast<uint8_t>(usedSlotVertex),
                             vertexBuffer.usedBytesInStride);
         } else {
-            uint64_t strideCount = static_cast<uint64_t>(firstVertex) + vertexCount;
-            if (strideCount != 0u) {
-                uint64_t requiredSize = (strideCount - 1u) * arrayStride + vertexBuffer.lastStride;
-                // firstVertex and vertexCount are in uint32_t,
-                // arrayStride must not be larger than kMaxVertexBufferArrayStride, which is
-                // currently 2048, and vertexBuffer.lastStride = max(attribute.offset +
-                // sizeof(attribute.format)) with attribute.offset being no larger than
-                // kMaxVertexBufferArrayStride, so by doing checks in uint64_t we avoid
-                // overflows.
-                DAWN_INVALID_IF(
-                    requiredSize > bufferSize,
-                    "Vertex range (first: %u, count: %u) requires a larger buffer (%u) than "
-                    "the "
-                    "bound buffer size (%u) of the vertex buffer at slot %u with stride %u.",
-                    firstVertex, vertexCount, requiredSize, bufferSize,
-                    static_cast<uint8_t>(usedSlotVertex), arrayStride);
-            }
+            DAWN_ASSERT(strideCount != 0u);
+            uint64_t requiredSize = (strideCount - 1u) * arrayStride + vertexBuffer.lastStride;
+            // firstVertex and vertexCount are in uint32_t,
+            // arrayStride must not be larger than kMaxVertexBufferArrayStride, which is
+            // currently 2048, and vertexBuffer.lastStride = max(attribute.offset +
+            // sizeof(attribute.format)) with attribute.offset being no larger than
+            // kMaxVertexBufferArrayStride, so by doing checks in uint64_t we avoid
+            // overflows.
+            DAWN_INVALID_IF(
+                requiredSize > bufferSize,
+                "Vertex range (first: %u, count: %u) requires a larger buffer (%u) than "
+                "the "
+                "bound buffer size (%u) of the vertex buffer at slot %u with stride %u.",
+                firstVertex, vertexCount, requiredSize, bufferSize,
+                static_cast<uint8_t>(usedSlotVertex), arrayStride);
         }
     }
 
@@ -141,6 +146,13 @@
 MaybeError CommandBufferStateTracker::ValidateBufferInRangeForInstanceBuffer(
     uint32_t instanceCount,
     uint32_t firstInstance) {
+    uint64_t strideCount = static_cast<uint64_t>(firstInstance) + instanceCount;
+
+    if (strideCount == 0) {
+        // All instance step mode buffers are always in range if stride count is zero
+        return {};
+    }
+
     RenderPipelineBase* lastRenderPipeline = GetRenderPipeline();
 
     const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& vertexBufferSlotsUsedAsInstanceBuffer =
@@ -158,23 +170,21 @@
                             bufferSize, static_cast<uint8_t>(usedSlotInstance),
                             vertexBuffer.usedBytesInStride);
         } else {
-            uint64_t strideCount = static_cast<uint64_t>(firstInstance) + instanceCount;
-            if (strideCount != 0u) {
-                uint64_t requiredSize = (strideCount - 1u) * arrayStride + vertexBuffer.lastStride;
-                // firstInstance and instanceCount are in uint32_t,
-                // arrayStride must not be larger than kMaxVertexBufferArrayStride, which is
-                // currently 2048, and vertexBuffer.lastStride = max(attribute.offset +
-                // sizeof(attribute.format)) with attribute.offset being no larger than
-                // kMaxVertexBufferArrayStride, so by doing checks in uint64_t we avoid
-                // overflows.
-                DAWN_INVALID_IF(
-                    requiredSize > bufferSize,
-                    "Instance range (first: %u, count: %u) requires a larger buffer (%u) than "
-                    "the "
-                    "bound buffer size (%u) of the vertex buffer at slot %u with stride %u.",
-                    firstInstance, instanceCount, requiredSize, bufferSize,
-                    static_cast<uint8_t>(usedSlotInstance), arrayStride);
-            }
+            DAWN_ASSERT(strideCount != 0u);
+            uint64_t requiredSize = (strideCount - 1u) * arrayStride + vertexBuffer.lastStride;
+            // firstInstance and instanceCount are in uint32_t,
+            // arrayStride must not be larger than kMaxVertexBufferArrayStride, which is
+            // currently 2048, and vertexBuffer.lastStride = max(attribute.offset +
+            // sizeof(attribute.format)) with attribute.offset being no larger than
+            // kMaxVertexBufferArrayStride, so by doing checks in uint64_t we avoid
+            // overflows.
+            DAWN_INVALID_IF(
+                requiredSize > bufferSize,
+                "Instance range (first: %u, count: %u) requires a larger buffer (%u) than "
+                "the "
+                "bound buffer size (%u) of the vertex buffer at slot %u with stride %u.",
+                firstInstance, instanceCount, requiredSize, bufferSize,
+                static_cast<uint8_t>(usedSlotInstance), arrayStride);
         }
     }
 
diff --git a/src/dawn/native/RenderEncoderBase.cpp b/src/dawn/native/RenderEncoderBase.cpp
index 06e1458..242e47b 100644
--- a/src/dawn/native/RenderEncoderBase.cpp
+++ b/src/dawn/native/RenderEncoderBase.cpp
@@ -129,10 +129,7 @@
 
                 DAWN_TRY(mCommandBufferState.ValidateIndexBufferInRange(indexCount, firstIndex));
 
-                // Although we don't know actual vertex access range in CPU, we still call the
-                // ValidateBufferInRangeForVertexBuffer in order to deal with those vertex step
-                // mode vertex buffer with an array stride of zero.
-                DAWN_TRY(mCommandBufferState.ValidateBufferInRangeForVertexBuffer(0, 0));
+                // DrawIndexed only validate instance step mode vertex buffer
                 DAWN_TRY(mCommandBufferState.ValidateBufferInRangeForInstanceBuffer(instanceCount,
                                                                                     firstInstance));
             }
diff --git a/src/dawn/tests/unittests/validation/DrawVertexAndIndexBufferOOBValidationTests.cpp b/src/dawn/tests/unittests/validation/DrawVertexAndIndexBufferOOBValidationTests.cpp
index e2f1906..fa66bba 100644
--- a/src/dawn/tests/unittests/validation/DrawVertexAndIndexBufferOOBValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/DrawVertexAndIndexBufferOOBValidationTests.cpp
@@ -304,6 +304,12 @@
         // Non-zero offset and size
         {(2 * kFloat32x4Stride), 5 * (2 * kFloat32x4Stride), (2 * kFloat32x4Stride),
          3 * (2 * kFloat32x4Stride), 3},
+        // For (strideCount - 1) * arrayStride + lastStride <= bound buffer size < strideCount *
+        // arrayStride
+        {(kFloat32x4Stride + 4), 2 * (kFloat32x4Stride + 4) + kFloat32x4Stride, 0, wgpu::kWholeSize,
+         3},
+        {(kFloat32x4Stride + 4), 2 * (kFloat32x4Stride + 4) + kFloat32x4Stride - 1, 0,
+         wgpu::kWholeSize, 2},
     };
     // Parameters list for instance-step-mode buffer.
     const std::vector<VertexBufferParams> kInstanceParamsList = {
@@ -325,6 +331,12 @@
         // Non-zero offset and size
         {(3 * kFloat32x2Stride), 7 * (3 * kFloat32x2Stride), (3 * kFloat32x2Stride),
          5 * (3 * kFloat32x2Stride), 5},
+        // For (strideCount - 1) * arrayStride + lastStride <= bound buffer size < strideCount *
+        // arrayStride
+        {(kFloat32x2Stride + 4), 2 * (kFloat32x2Stride + 4) + kFloat32x2Stride, 0, wgpu::kWholeSize,
+         3},
+        {(kFloat32x2Stride + 4), 2 * (kFloat32x2Stride + 4) + kFloat32x2Stride - 1, 0,
+         wgpu::kWholeSize, 2},
     };
 
   private:
@@ -341,7 +353,9 @@
     {
         // Implicit size
         VertexBufferList vertexBufferList = {{0, vertexBuffer, 0, wgpu::kWholeSize}};
-        TestRenderPassDraw(pipeline, vertexBufferList, 3, 1, 0, 0, true);
+        TestRenderPassDraw(pipeline, vertexBufferList, /* vertexCount */ 3,
+                           /* instanceCount */ 1, /* firstVertex */ 0,
+                           /* firstInstance */ 0, /* isSuccess */ true);
     }
 
     {
@@ -362,9 +376,12 @@
         VertexBufferList vertexBufferList = {
             {0, vertexBuffer, params.bufferOffsetForEncoder, params.bufferSizeForEncoder}};
 
+        DAWN_ASSERT(params.maxValidAccessNumber > 0);
         uint32_t n = params.maxValidAccessNumber;
         // It is ok to draw n vertices with vertex buffer
-        TestRenderPassDraw(pipeline, vertexBufferList, n, 1, 0, 0, true);
+        TestRenderPassDraw(pipeline, vertexBufferList, /* vertexCount */ n,
+                           /* instanceCount */ 1, /* firstVertex */ 0,
+                           /* firstInstance */ 0, /* isSuccess */ true);
         // It is ok to draw n-1 vertices with offset 1
         TestRenderPassDraw(pipeline, vertexBufferList, n - 1, 1, 1, 0, true);
         // Drawing more vertices will cause OOB, even if not enough for another primitive
@@ -396,10 +413,14 @@
                  instanceParams.bufferSizeForEncoder},
             };
 
+            DAWN_ASSERT(vertexParams.maxValidAccessNumber > 0);
+            DAWN_ASSERT(instanceParams.maxValidAccessNumber > 0);
             uint32_t vert = vertexParams.maxValidAccessNumber;
             uint32_t inst = instanceParams.maxValidAccessNumber;
             // It is ok to draw vert vertices
-            TestRenderPassDraw(pipeline, vertexBufferList, vert, 1, 0, 0, true);
+            TestRenderPassDraw(pipeline, vertexBufferList, /* vertexCount */ vert,
+                               /* instanceCount */ 1, /* firstVertex */ 0,
+                               /* firstInstance */ 0, /* isSuccess */ true);
             TestRenderPassDraw(pipeline, vertexBufferList, vert - 1, 1, 1, 0, true);
             // It is ok to draw vert vertices and inst instences
             TestRenderPassDraw(pipeline, vertexBufferList, vert, inst, 0, 0, true);
@@ -432,7 +453,9 @@
 
     IndexBufferDesc indexBufferDesc = {indexBuffer, wgpu::IndexFormat::Uint32};
 
-    TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, 12, 1, 0, 0, 0, true);
+    TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, /* indexCount */ 12,
+                              /* instanceCount */ 1, /* firstIndex */ 0, /* baseVertex */ 0,
+                              /* firstInstance */ 0, /* isSuccess */ true);
 }
 
 // Verify index buffer OOB for DrawIndexed are caught in command encoder
@@ -454,10 +477,13 @@
                                            params.indexBufferOffsetForEncoder,
                                            params.indexBufferSizeForEncoder};
 
+        DAWN_ASSERT(params.maxValidIndexNumber > 0);
         uint32_t n = params.maxValidIndexNumber;
 
         // Control case
-        TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, n, 5, 0, 0, 0, true);
+        TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, /* indexCount */ n,
+                                  /* instanceCount */ 5, /* firstIndex */ 0, /* baseVertex */ 0,
+                                  /* firstInstance */ 0, /* isSuccess */ true);
         TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, n - 1, 5, 1, 0, 0,
                                   true);
         // Index buffer OOB, indexCount too large
@@ -487,8 +513,8 @@
             wgpu::RenderPipeline pipeline = CreateBasicRenderPipelineWithInstance(
                 vertexParams.bufferStride, instanceParams.bufferStride);
 
-            auto indexFormat = wgpu::IndexFormat::Uint32;
-            auto indexStride = sizeof(uint32_t);
+            constexpr wgpu::IndexFormat indexFormat = wgpu::IndexFormat::Uint32;
+            constexpr uint64_t indexStride = sizeof(uint32_t);
 
             // Build index buffer for 12 indexes
             wgpu::Buffer indexBuffer = CreateBuffer(12 * indexStride, wgpu::BufferUsage::Index);
@@ -505,10 +531,13 @@
 
             IndexBufferDesc indexBufferDesc = {indexBuffer, indexFormat};
 
+            DAWN_ASSERT(instanceParams.maxValidAccessNumber > 0);
             uint32_t inst = instanceParams.maxValidAccessNumber;
             // Control case
-            TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, 12, inst, 0, 0,
-                                      0, true);
+            TestRenderPassDrawIndexed(
+                pipeline, indexBufferDesc, vertexBufferList, /* indexCount */ 12,
+                /* instanceCount */ inst, /* firstIndex */ 0, /* baseVertex */ 0,
+                /* firstInstance */ 0, /* isSuccess */ true);
             // Vertex buffer (stepMode = instance) OOB, instanceCount too large
             TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, 12, inst + 1, 0,
                                       0, 0, false);
@@ -527,8 +556,10 @@
     }
 }
 
-// Verify instance mode vertex buffer OOB for DrawIndexed are caught in command encoder
-TEST_F(DrawVertexAndIndexBufferOOBValidationTests, ZeroArrayStrideVertexBufferOOB) {
+// Verify zero array stride vertex buffer OOB for Draw and DrawIndexed are caught in command encoder
+// This test only test cases that strideCount > 0. Cases of strideCount == 0 are tested in
+// ZeroStrideCountVertexBufferNeverOOB.
+TEST_F(DrawVertexAndIndexBufferOOBValidationTests, ZeroArrayStrideVertexBuffer) {
     // In this test, we use VertexBufferParams.maxValidAccessNumber > 0 to indicate that such
     // buffer parameter meet the requirement of pipeline, and maxValidAccessNumber == 0 to
     // indicate that such buffer parameter will cause OOB.
@@ -569,8 +600,8 @@
 
     for (VertexBufferParams vertexParams : kVertexParamsListForZeroStride) {
         for (VertexBufferParams instanceParams : kInstanceParamsListForZeroStride) {
-            auto indexFormat = wgpu::IndexFormat::Uint32;
-            auto indexStride = sizeof(uint32_t);
+            constexpr wgpu::IndexFormat indexFormat = wgpu::IndexFormat::Uint32;
+            constexpr uint64_t indexStride = sizeof(uint32_t);
 
             // Build index buffer for 12 indexes
             wgpu::Buffer indexBuffer = CreateBuffer(12 * indexStride, wgpu::BufferUsage::Index);
@@ -587,14 +618,137 @@
 
             IndexBufferDesc indexBufferDesc = {indexBuffer, indexFormat};
 
-            const bool isSuccess = (vertexParams.maxValidAccessNumber > 0) &&
-                                   (instanceParams.maxValidAccessNumber > 0);
+            const bool vertexModeBufferOOB = vertexParams.maxValidAccessNumber == 0;
+            const bool instanceModeBufferOOB = instanceParams.maxValidAccessNumber == 0;
+
+            // Draw validate both vertex and instance step mode buffer OOB.
             // vertexCount and instanceCount doesn't matter, as array stride is zero and all
-            // vertex/instance access the same space of buffer
-            TestRenderPassDraw(pipeline, vertexBufferList, 100, 100, 0, 0, isSuccess);
-            // indexCount doesn't matter as long as no index buffer OOB happened
-            TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, 12, 100, 0, 0, 0,
-                                      isSuccess);
+            // vertex/instance access the same space of buffer, as long as both (vertexCount +
+            // firstVertex) and (instanceCount + firstInstance) are larger than zero.
+            TestRenderPassDraw(pipeline, vertexBufferList, /* vertexCount */ 100,
+                               /* instanceCount */ 100, /* firstVertex */ 0,
+                               /* firstInstance */ 0,
+                               /* isSuccess */ !vertexModeBufferOOB && !instanceModeBufferOOB);
+            TestRenderPassDraw(pipeline, vertexBufferList, 100, 100, 100, 100,
+                               !vertexModeBufferOOB && !instanceModeBufferOOB);
+            TestRenderPassDraw(pipeline, vertexBufferList, 0, 0, 100, 100,
+                               !vertexModeBufferOOB && !instanceModeBufferOOB);
+
+            // DrawIndexed only validate instance step mode buffer OOB.
+            // indexCount doesn't matter as long as no index buffer OOB happened, and instanceCount
+            // doesn't matter as long as (instanceCount + firstInstance) are larger than zero.
+            TestRenderPassDrawIndexed(
+                pipeline, indexBufferDesc, vertexBufferList, /* indexCount */ 12,
+                /* instanceCount */ 100, /* firstIndex */ 0, /* baseVertex */ 0,
+                /* firstInstance */ 0, /* isSuccess */ !instanceModeBufferOOB);
+            TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, 12, 0, 0, 0, 100,
+                                      !instanceModeBufferOOB);
+        }
+    }
+}
+
+// Verify all vertex buffer never OOB for Draw and DrawIndexed with zero stride count
+TEST_F(DrawVertexAndIndexBufferOOBValidationTests, ZeroStrideCountVertexBufferNeverOOB) {
+    // In this test we use a render pipeline with non-zero array stride and a render pipeline with
+    // zero array stride, both use a vertex step mode vertex buffer with lastStride = 16 and a
+    // instance step mode vertex buffer with lastStride = 8. Binding a buffer with size less than
+    // lastStride to corresponding buffer slot will result in a OOB validation error is strideCount
+    // is larger than 0, but shall not result in validation error if strideCount == 0.
+
+    // Create the render pipeline with non-zero array stride (larger than lastStride)
+    std::vector<PipelineVertexBufferDesc> bufferDescListNonZeroArrayStride = {
+        {20u, wgpu::VertexStepMode::Vertex, {{0, wgpu::VertexFormat::Float32x4}}},
+        {12u, wgpu::VertexStepMode::Instance, {{3, wgpu::VertexFormat::Float32x2}}},
+    };
+    wgpu::RenderPipeline pipelineWithNonZeroArrayStride =
+        CreateRenderPipelineWithBufferDesc(bufferDescListNonZeroArrayStride);
+
+    // Create the render pipeline with zero array stride
+    std::vector<PipelineVertexBufferDesc> bufferDescListZeroArrayStride = {
+        {0u, wgpu::VertexStepMode::Vertex, {{0, wgpu::VertexFormat::Float32x4}}},
+        {0u, wgpu::VertexStepMode::Instance, {{3, wgpu::VertexFormat::Float32x2}}},
+    };
+    wgpu::RenderPipeline pipelineWithZeroArrayStride =
+        CreateRenderPipelineWithBufferDesc(bufferDescListZeroArrayStride);
+
+    const std::vector<VertexBufferParams> kVertexParamsListForZeroStride = {
+        // Size enough for 1 vertex
+        {kFloat32x4Stride, 16, 0, wgpu::kWholeSize, 1},
+        // No enough size for 1 vertex
+        {kFloat32x4Stride, 19, 4, wgpu::kWholeSize, 0},
+        {kFloat32x4Stride, 16, 16, wgpu::kWholeSize, 0},
+    };
+
+    const std::vector<VertexBufferParams> kInstanceParamsListForZeroStride = {
+        // Size enough for 1 instance
+        {kFloat32x2Stride, 8, 0, wgpu::kWholeSize, 1},
+        // No enough size for 1 instance
+        {kFloat32x2Stride, 11, 4, wgpu::kWholeSize, 0},
+        {kFloat32x2Stride, 8, 8, wgpu::kWholeSize, 0},
+    };
+
+    for (VertexBufferParams vertexParams : kVertexParamsListForZeroStride) {
+        for (VertexBufferParams instanceParams : kInstanceParamsListForZeroStride) {
+            for (wgpu::RenderPipeline pipeline :
+                 {pipelineWithNonZeroArrayStride, pipelineWithZeroArrayStride}) {
+                constexpr wgpu::IndexFormat indexFormat = wgpu::IndexFormat::Uint32;
+                constexpr uint64_t indexStride = sizeof(uint32_t);
+
+                // Build index buffer for 1 index
+                wgpu::Buffer indexBuffer = CreateBuffer(indexStride, wgpu::BufferUsage::Index);
+                // Build vertex buffer for vertices
+                wgpu::Buffer vertexBuffer = CreateBuffer(vertexParams.bufferSize);
+                // Build vertex buffer for instances
+                wgpu::Buffer instanceBuffer = CreateBuffer(instanceParams.bufferSize);
+
+                VertexBufferList vertexBufferList = {
+                    {0, vertexBuffer, vertexParams.bufferOffsetForEncoder,
+                     vertexParams.bufferSizeForEncoder},
+                    {1, instanceBuffer, instanceParams.bufferOffsetForEncoder,
+                     instanceParams.bufferSizeForEncoder}};
+
+                IndexBufferDesc indexBufferDesc = {indexBuffer, indexFormat};
+
+                const bool vertexModeBufferOOB = vertexParams.maxValidAccessNumber == 0;
+                const bool instanceModeBufferOOB = instanceParams.maxValidAccessNumber == 0;
+
+                // Draw validate both vertex and instance step mode buffer OOB.
+                // Control case, non-zero stride for both step mode buffer
+                TestRenderPassDraw(pipeline, vertexBufferList, /* vertexCount */ 1,
+                                   /* instanceCount */ 1, /* firstVertex */ 0,
+                                   /* firstInstance */ 0,
+                                   /* isSuccess */ !vertexModeBufferOOB && !instanceModeBufferOOB);
+                TestRenderPassDraw(pipeline, vertexBufferList, 1, 0, 0, 1,
+                                   !vertexModeBufferOOB && !instanceModeBufferOOB);
+                TestRenderPassDraw(pipeline, vertexBufferList, 0, 1, 1, 0,
+                                   !vertexModeBufferOOB && !instanceModeBufferOOB);
+                TestRenderPassDraw(pipeline, vertexBufferList, 0, 0, 1, 1,
+                                   !vertexModeBufferOOB && !instanceModeBufferOOB);
+                // Vertex step mode buffer will never OOB if (vertexCount + firstVertex) is zero,
+                // and only instance step mode buffer OOB will fail validation
+                TestRenderPassDraw(pipeline, vertexBufferList, 0, 1, 0, 0, !instanceModeBufferOOB);
+                TestRenderPassDraw(pipeline, vertexBufferList, 0, 0, 0, 1, !instanceModeBufferOOB);
+                // Instance step mode buffer will never OOB if (instanceCount + firstInstance) is
+                // zero, and only vertex step mode buffer OOB will fail validation
+                TestRenderPassDraw(pipeline, vertexBufferList, 1, 0, 0, 0, !vertexModeBufferOOB);
+                TestRenderPassDraw(pipeline, vertexBufferList, 0, 0, 1, 0, !vertexModeBufferOOB);
+                // If both (vertexCount + firstVertex) and (instanceCount + firstInstance) are zero,
+                // all vertex buffer will never OOB
+                TestRenderPassDraw(pipeline, vertexBufferList, 0, 0, 0, 0, true);
+
+                // DrawIndexed only validate instance step mode buffer OOB.
+                // Control case, non-zero stride for instance step mode buffer
+                TestRenderPassDrawIndexed(
+                    pipeline, indexBufferDesc, vertexBufferList, /* indexCount */ 1,
+                    /* instanceCount */ 1, /* firstIndex */ 0, /* baseVertex */ 0,
+                    /* firstInstance */ 0, /* isSuccess */ !instanceModeBufferOOB);
+                TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, 1, 0, 0, 0,
+                                          1, !instanceModeBufferOOB);
+                // Instance step mode buffer will never OOB if (instanceCount + firstInstance) is
+                // zero, validation shall always succeed
+                TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, 1, 0, 0, 0,
+                                          0, true);
+            }
         }
     }
 }