From ed840583eb79d60815f492ae73fac898cf530b1a Mon Sep 17 00:00:00 2001 From: shrekshao Date: Fri, 11 Mar 2022 01:09:02 +0000 Subject: [PATCH] Relax vertex stride requirement Relax validation of last stride of vertex buffer according to the updated spec. Bug: dawn:1287 Change-Id: I7a58401933b48c5cb121ba73c592575ada3e7151 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/83203 Auto-Submit: Shrek Shao Reviewed-by: Austin Eng Commit-Queue: Austin Eng --- src/dawn/native/CommandBufferStateTracker.cpp | 58 ++- src/dawn/native/RenderPipeline.cpp | 9 +- src/dawn/native/RenderPipeline.h | 3 + .../VertexBufferValidationTests.cpp | 490 ++++++++++++++++++ .../validation/VertexStateValidationTests.cpp | 1 - 5 files changed, 536 insertions(+), 25 deletions(-) diff --git a/src/dawn/native/CommandBufferStateTracker.cpp b/src/dawn/native/CommandBufferStateTracker.cpp index 370139ef81..ee164c7b29 100644 --- a/src/dawn/native/CommandBufferStateTracker.cpp +++ b/src/dawn/native/CommandBufferStateTracker.cpp @@ -104,17 +104,24 @@ namespace dawn::native { bufferSize, static_cast(usedSlotVertex), vertexBuffer.usedBytesInStride); } else { - uint64_t requiredSize = - (static_cast(firstVertex) + vertexCount) * arrayStride; - // firstVertex and vertexCount are in uint32_t, and arrayStride must not - // be larger than kMaxVertexBufferArrayStride, which is currently 2048. 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(usedSlotVertex), arrayStride); + uint64_t strideCount = static_cast(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(usedSlotVertex), arrayStride); + } } } @@ -142,17 +149,24 @@ namespace dawn::native { bufferSize, static_cast(usedSlotInstance), vertexBuffer.usedBytesInStride); } else { - uint64_t requiredSize = - (static_cast(firstInstance) + instanceCount) * arrayStride; - // firstInstance and instanceCount are in uint32_t, and arrayStride must - // not be larger than kMaxVertexBufferArrayStride, which is currently 2048. - // 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(usedSlotInstance), arrayStride); + uint64_t strideCount = static_cast(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(usedSlotInstance), arrayStride); + } } } diff --git a/src/dawn/native/RenderPipeline.cpp b/src/dawn/native/RenderPipeline.cpp index b61b192eb1..47d3d20a2d 100644 --- a/src/dawn/native/RenderPipeline.cpp +++ b/src/dawn/native/RenderPipeline.cpp @@ -610,6 +610,7 @@ namespace dawn::native { mVertexBufferInfos[typedSlot].arrayStride = buffers[slot].arrayStride; mVertexBufferInfos[typedSlot].stepMode = buffers[slot].stepMode; mVertexBufferInfos[typedSlot].usedBytesInStride = 0; + mVertexBufferInfos[typedSlot].lastStride = 0; switch (buffers[slot].stepMode) { case wgpu::VertexStepMode::Vertex: mVertexBufferSlotsUsedAsVertexBuffer.set(typedSlot); @@ -634,12 +635,16 @@ namespace dawn::native { // 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(GetVertexFormatInfo(buffers[slot].attributes[i].format).byteSize); + 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, + mAttributeInfos[location].offset + formatByteSize); } } diff --git a/src/dawn/native/RenderPipeline.h b/src/dawn/native/RenderPipeline.h index da6cdd2380..429f2a9ce4 100644 --- a/src/dawn/native/RenderPipeline.h +++ b/src/dawn/native/RenderPipeline.h @@ -54,6 +54,9 @@ namespace dawn::native { uint64_t arrayStride; wgpu::VertexStepMode stepMode; uint16_t usedBytesInStride; + // As indicated in the spec, the lastStride is max(attribute.offset + + // sizeof(attribute.format)) for each attribute in the buffer[slot] + uint64_t lastStride; }; class RenderPipelineBase : public PipelineBase { diff --git a/src/dawn/tests/unittests/validation/VertexBufferValidationTests.cpp b/src/dawn/tests/unittests/validation/VertexBufferValidationTests.cpp index 35e7889e11..d26b5d573a 100644 --- a/src/dawn/tests/unittests/validation/VertexBufferValidationTests.cpp +++ b/src/dawn/tests/unittests/validation/VertexBufferValidationTests.cpp @@ -25,6 +25,11 @@ class VertexBufferValidationTest : public ValidationTest { void SetUp() override { ValidationTest::SetUp(); + // dummy vertex shader module + vsModule = utils::CreateShaderModule(device, R"( + @stage(vertex) fn main() -> @builtin(position) vec4 { + return vec4(0.0, 0.0, 0.0, 0.0); + })"); fsModule = utils::CreateShaderModule(device, R"( @stage(fragment) fn main() -> @location(0) vec4 { return vec4(0.0, 1.0, 0.0, 1.0); @@ -66,6 +71,18 @@ class VertexBufferValidationTest : public ValidationTest { return utils::CreateShaderModule(device, vs.str().c_str()); } + wgpu::RenderPipeline MakeRenderPipeline(const wgpu::ShaderModule& vsModule, + const utils::ComboVertexState& state) { + utils::ComboRenderPipelineDescriptor descriptor; + descriptor.vertex.module = vsModule; + descriptor.cFragment.module = fsModule; + + descriptor.vertex.bufferCount = state.vertexBufferCount; + descriptor.vertex.buffers = &state.cVertexBuffers[0]; + + return device.CreateRenderPipeline(&descriptor); + } + wgpu::RenderPipeline MakeRenderPipeline(const wgpu::ShaderModule& vsModule, unsigned int bufferCount) { utils::ComboRenderPipelineDescriptor descriptor; @@ -83,6 +100,7 @@ class VertexBufferValidationTest : public ValidationTest { return device.CreateRenderPipeline(&descriptor); } + wgpu::ShaderModule vsModule; wgpu::ShaderModule fsModule; }; @@ -354,3 +372,475 @@ TEST_F(VertexBufferValidationTest, OffsetAlignment) { ASSERT_DEVICE_ERROR(encoder.Finish()); } } + +// Check vertex buffer stride requirements for draw command. +TEST_F(VertexBufferValidationTest, DrawStrideLimitsVertex) { + DummyRenderPass renderPass(device); + + // Create a buffer of size 28, containing 4 float32 elements, array stride size = 8 + // The last element doesn't have the full stride size + wgpu::BufferDescriptor descriptor; + descriptor.size = 28; + descriptor.usage = wgpu::BufferUsage::Vertex; + wgpu::Buffer vertexBuffer = device.CreateBuffer(&descriptor); + + // Vertex attribute offset is 0 + wgpu::RenderPipeline pipeline1; + { + utils::ComboVertexState state; + state.vertexBufferCount = 1; + state.cVertexBuffers[0].arrayStride = 8; + state.cVertexBuffers[0].stepMode = wgpu::VertexStepMode::Vertex; + state.cVertexBuffers[0].attributeCount = 1; + state.cAttributes[0].offset = 0; + + pipeline1 = MakeRenderPipeline(vsModule, state); + } + + // Vertex attribute offset is 4 + wgpu::RenderPipeline pipeline2; + { + utils::ComboVertexState state; + state.vertexBufferCount = 1; + state.cVertexBuffers[0].arrayStride = 8; + state.cVertexBuffers[0].stepMode = wgpu::VertexStepMode::Vertex; + state.cVertexBuffers[0].attributeCount = 1; + state.cAttributes[0].offset = 4; + + pipeline2 = MakeRenderPipeline(vsModule, state); + } + + // Control case: draw 3 elements, 3 * 8 = 24 <= 28, is valid anyway + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline1); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(3); + pass.End(); + } + encoder.Finish(); + + // Valid: draw 3 elements with firstVertex == 1, (2 + 1) * 8 + 4 = 28 <= 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline1); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(3, 0, 1, 0); + pass.End(); + } + encoder.Finish(); + + // Valid: draw 3 elements with offset == 4, 4 + 3 * 8 = 24 <= 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline2); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(3); + pass.End(); + } + encoder.Finish(); + + // Valid: draw 4 elements, 4 * 8 = 32 > 28 + // But the last element does not require to have the full stride size + // So 3 * 8 + 4 = 28 <= 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline1); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(4); + pass.End(); + } + encoder.Finish(); + + // Invalid: draw 4 elements with firstVertex == 1 + // It requires a buffer with size of (3 + 1) * 8 + 4 = 36 > 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline1); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(4, 0, 1, 0); + pass.End(); + } + ASSERT_DEVICE_ERROR(encoder.Finish()); + + // Invalid: draw 4 elements with offset == 4 + // It requires a buffer with size of 4 + 3 * 8 + 4 = 32 > 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline2); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(4); + pass.End(); + } + ASSERT_DEVICE_ERROR(encoder.Finish()); + + // Valid: stride count == 0 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline2); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(0); + pass.End(); + } + encoder.Finish(); + + // Invalid: stride count == 4 + // It requires a buffer with size of 4 + 3 * 8 + 4 = 32 > 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline2); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(0, 0, 4); + pass.End(); + } + ASSERT_DEVICE_ERROR(encoder.Finish()); +} + +// Check instance buffer stride requirements with instanced attributes for draw command. +TEST_F(VertexBufferValidationTest, DrawStrideLimitsInstance) { + DummyRenderPass renderPass(device); + + // Create a buffer of size 28, containing 4 float32 elements, array stride size = 8 + // The last element doesn't have the full stride size + wgpu::BufferDescriptor descriptor; + descriptor.size = 28; + descriptor.usage = wgpu::BufferUsage::Vertex; + wgpu::Buffer vertexBuffer = device.CreateBuffer(&descriptor); + + // Vertex attribute offset is 0 + wgpu::RenderPipeline pipeline1; + { + utils::ComboVertexState state; + state.vertexBufferCount = 1; + state.cVertexBuffers[0].arrayStride = 8; + state.cVertexBuffers[0].stepMode = wgpu::VertexStepMode::Instance; + state.cVertexBuffers[0].attributeCount = 1; + state.cAttributes[0].offset = 0; + + pipeline1 = MakeRenderPipeline(vsModule, state); + } + + // Vertex attribute offset is 4 + wgpu::RenderPipeline pipeline2; + { + utils::ComboVertexState state; + state.vertexBufferCount = 1; + state.cVertexBuffers[0].arrayStride = 8; + state.cVertexBuffers[0].stepMode = wgpu::VertexStepMode::Instance; + state.cVertexBuffers[0].attributeCount = 1; + state.cAttributes[0].offset = 4; + + pipeline2 = MakeRenderPipeline(vsModule, state); + } + + // Control case: draw 3 instances, 3 * 8 = 24 <= 28, is valid anyway + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline1); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(1, 3); + pass.End(); + } + encoder.Finish(); + + // Valid: draw 3 instances with firstInstance == 1, (2 + 1) * 8 + 4 = 28 <= 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline1); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(1, 3, 0, 1); + pass.End(); + } + encoder.Finish(); + + // Valid: draw 3 instances with offset == 4, 4 + 3 * 8 = 24 <= 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline2); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(1, 3); + pass.End(); + } + encoder.Finish(); + + // Valid: draw 4 instances, 4 * 8 = 32 > 28 + // But the last element does not require to have the full stride size + // So 3 * 8 + 4 = 28 <= 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline1); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(1, 4); + pass.End(); + } + encoder.Finish(); + + // Invalid: draw 4 instances with firstInstance == 1 + // It requires a buffer with size of (3 + 1) * 8 + 4 = 36 > 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline1); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(1, 4, 0, 1); + pass.End(); + } + ASSERT_DEVICE_ERROR(encoder.Finish()); + + // Invalid: draw 4 instances with offset == 4 + // It requires a buffer with size of 4 + 3 * 8 + 4 = 32 > 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline2); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(1, 4); + pass.End(); + } + ASSERT_DEVICE_ERROR(encoder.Finish()); + + // Valid: stride count == 0 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline2); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(1, 0); + pass.End(); + } + encoder.Finish(); + + // Invalid, stride count == 4 + // It requires a buffer with size of 4 + 3 * 8 + 4 = 32 > 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline2); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(1, 0, 0, 4); + pass.End(); + } + ASSERT_DEVICE_ERROR(encoder.Finish()); +} + +// Check vertex buffer stride requirements with instanced attributes for draw indexed command. +TEST_F(VertexBufferValidationTest, DrawIndexedStrideLimitsInstance) { + DummyRenderPass renderPass(device); + + // Create a buffer of size 28, containing 4 float32 elements, array stride size = 8 + // The last element doesn't have the full stride size + wgpu::BufferDescriptor descriptor; + descriptor.size = 28; + descriptor.usage = wgpu::BufferUsage::Vertex; + wgpu::Buffer vertexBuffer = device.CreateBuffer(&descriptor); + + wgpu::Buffer indexBuffer = + utils::CreateBufferFromData(device, wgpu::BufferUsage::Index, {0, 1, 2}); + + // Vertex attribute offset is 0 + wgpu::RenderPipeline pipeline1; + { + utils::ComboVertexState state; + state.vertexBufferCount = 1; + state.cVertexBuffers[0].arrayStride = 8; + state.cVertexBuffers[0].stepMode = wgpu::VertexStepMode::Instance; + state.cVertexBuffers[0].attributeCount = 1; + state.cAttributes[0].offset = 0; + + pipeline1 = MakeRenderPipeline(vsModule, state); + } + + // Vertex attribute offset is 4 + wgpu::RenderPipeline pipeline2; + { + utils::ComboVertexState state; + state.vertexBufferCount = 1; + state.cVertexBuffers[0].arrayStride = 8; + state.cVertexBuffers[0].stepMode = wgpu::VertexStepMode::Instance; + state.cVertexBuffers[0].attributeCount = 1; + state.cAttributes[0].offset = 4; + + pipeline2 = MakeRenderPipeline(vsModule, state); + } + + // Control case: draw 3 instances, 3 * 8 = 24 <= 28, is valid anyway + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline1); + pass.SetVertexBuffer(0, vertexBuffer); + pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32); + pass.DrawIndexed(3, 3); + pass.End(); + } + encoder.Finish(); + + // Valid: draw 3 instances with firstInstance == 1, (2 + 1) * 8 + 4 = 28 <= 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline1); + pass.SetVertexBuffer(0, vertexBuffer); + pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32); + pass.Draw(3, 3, 0, 1); + pass.End(); + } + encoder.Finish(); + + // Valid: draw 3 instances with offset == 4, 4 + 3 * 8 = 24 <= 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline2); + pass.SetVertexBuffer(0, vertexBuffer); + pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32); + pass.Draw(3, 3); + pass.End(); + } + encoder.Finish(); + + // Valid: draw 4 instances, 4 * 8 = 32 > 28 + // But the last element does not require to have the full stride size + // So 3 * 8 + 4 = 28 <= 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline1); + pass.SetVertexBuffer(0, vertexBuffer); + pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32); + pass.Draw(3, 4); + pass.End(); + } + encoder.Finish(); + + // Invalid: draw 4 instances with firstInstance == 1 + // It requires a buffer with size of (3 + 1) * 8 + 4 = 36 > 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline1); + pass.SetVertexBuffer(0, vertexBuffer); + pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32); + pass.Draw(3, 4, 0, 1); + pass.End(); + } + ASSERT_DEVICE_ERROR(encoder.Finish()); + + // Invalid: draw 4 instances with offset == 4 + // It requires a buffer with size of 4 + 3 * 8 + 4 = 32 > 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline2); + pass.SetVertexBuffer(0, vertexBuffer); + pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32); + pass.Draw(3, 4); + pass.End(); + } + ASSERT_DEVICE_ERROR(encoder.Finish()); + + // Valid: stride count == 0 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline2); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(3, 0); + pass.End(); + } + encoder.Finish(); + + // Invalid, stride count == 4 + // It requires a buffer with size of 4 + 3 * 8 + 4 = 32 > 28 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline2); + pass.SetVertexBuffer(0, vertexBuffer); + pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32); + pass.Draw(3, 0, 0, 4); + pass.End(); + } + ASSERT_DEVICE_ERROR(encoder.Finish()); +} + +// Check last stride is computed correctly for vertex buffer with multiple attributes. +TEST_F(VertexBufferValidationTest, DrawStrideLimitsVertexMultipleAttributes) { + DummyRenderPass renderPass(device); + + // Create a buffer of size 44, array stride size = 12 + wgpu::BufferDescriptor descriptor; + descriptor.size = 44; + descriptor.usage = wgpu::BufferUsage::Vertex; + wgpu::Buffer vertexBuffer = device.CreateBuffer(&descriptor); + + // lastStride = attribute[1].offset + sizeof(attribute[1].format) = 8 + wgpu::RenderPipeline pipeline1; + { + utils::ComboVertexState state; + state.vertexBufferCount = 1; + state.cVertexBuffers[0].arrayStride = 12; + state.cVertexBuffers[0].stepMode = wgpu::VertexStepMode::Vertex; + state.cVertexBuffers[0].attributeCount = 2; + state.cAttributes[0].format = wgpu::VertexFormat::Float32; + state.cAttributes[0].offset = 0; + state.cAttributes[0].shaderLocation = 0; + state.cAttributes[1].format = wgpu::VertexFormat::Float32; + state.cAttributes[1].offset = 4; + state.cAttributes[1].shaderLocation = 1; + + pipeline1 = MakeRenderPipeline(vsModule, state); + } + + // lastStride = attribute[1].offset + sizeof(attribute[1].format) = 12 + wgpu::RenderPipeline pipeline2; + { + utils::ComboVertexState state; + state.vertexBufferCount = 1; + state.cVertexBuffers[0].arrayStride = 12; + state.cVertexBuffers[0].stepMode = wgpu::VertexStepMode::Vertex; + state.cVertexBuffers[0].attributeCount = 2; + state.cAttributes[0].format = wgpu::VertexFormat::Float32; + state.cAttributes[0].offset = 0; + state.cAttributes[0].shaderLocation = 0; + state.cAttributes[1].format = wgpu::VertexFormat::Float32x2; + state.cAttributes[1].offset = 4; + state.cAttributes[1].shaderLocation = 1; + + pipeline2 = MakeRenderPipeline(vsModule, state); + } + + // Valid: draw 4 elements, last stride is 8, 3 * 12 + 8 = 44 <= 44 + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline1); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(4); + pass.End(); + } + encoder.Finish(); + + // Invalid: draw 4 elements, last stride is 12, 3 * 12 + 12 = 48 > 44 + encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline2); + pass.SetVertexBuffer(0, vertexBuffer); + pass.Draw(4); + pass.End(); + } + ASSERT_DEVICE_ERROR(encoder.Finish()); +} diff --git a/src/dawn/tests/unittests/validation/VertexStateValidationTests.cpp b/src/dawn/tests/unittests/validation/VertexStateValidationTests.cpp index eeb2f6913a..e522cb34c1 100644 --- a/src/dawn/tests/unittests/validation/VertexStateValidationTests.cpp +++ b/src/dawn/tests/unittests/validation/VertexStateValidationTests.cpp @@ -84,7 +84,6 @@ TEST_F(VertexStateTest, NullBufferIsOk) { CreatePipeline(true, state, kDummyVertexShader); } -// Check validation that pipeline vertex buffers are backed by attributes in the vertex input // Check validation that pipeline vertex buffers are backed by attributes in the vertex input TEST_F(VertexStateTest, PipelineCompatibility) { utils::ComboVertexState state;