Add OOB validation in RenderPassEncoder for Draw and DrawIndexed

1. Validate the buffer range for both vertex step mode and instance step mode vertex buffers in Draw,
2. Validate the buffer range for instance step mode vertex buffers and the range of index buffer in DrawIndexed, and
3. Add related validation unit tests DrawVertexAndIndexBufferOOBValidationTests, and remove out-of-date vertex buffer robustness tests.

Bug: dawn:808
Change-Id: Ic27a95138cb1e21b72a1da392b7828368bfe2010
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/56361
Commit-Queue: Zhaoming Jiang <zhaoming.jiang@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
Zhaoming Jiang 2021-07-16 03:22:58 +00:00 committed by Dawn LUCI CQ
parent 8d05400b40
commit d8b3d99038
9 changed files with 766 additions and 277 deletions

View File

@ -76,6 +76,66 @@ namespace dawn_native {
return ValidateOperation(kDrawIndexedAspects); return ValidateOperation(kDrawIndexedAspects);
} }
MaybeError CommandBufferStateTracker::ValidateBufferInRangeForVertexBuffer(
uint32_t vertexCount,
uint32_t firstVertex) {
const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>&
vertexBufferSlotsUsedAsVertexBuffer =
mLastRenderPipeline->GetVertexBufferSlotsUsedAsVertexBuffer();
for (auto usedSlotVertex : IterateBitSet(vertexBufferSlotsUsedAsVertexBuffer)) {
const VertexBufferInfo& vertexBuffer =
mLastRenderPipeline->GetVertexBuffer(usedSlotVertex);
uint64_t arrayStride = vertexBuffer.arrayStride;
uint64_t bufferSize = mVertexBufferSizes[usedSlotVertex];
// 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.
if ((static_cast<uint64_t>(firstVertex) + vertexCount) * arrayStride > bufferSize) {
return DAWN_VALIDATION_ERROR("Vertex buffer out of bound");
}
}
return {};
}
MaybeError CommandBufferStateTracker::ValidateBufferInRangeForInstanceBuffer(
uint32_t instanceCount,
uint32_t firstInstance) {
const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>&
vertexBufferSlotsUsedAsInstanceBuffer =
mLastRenderPipeline->GetVertexBufferSlotsUsedAsInstanceBuffer();
for (auto usedSlotInstance : IterateBitSet(vertexBufferSlotsUsedAsInstanceBuffer)) {
const VertexBufferInfo& vertexBuffer =
mLastRenderPipeline->GetVertexBuffer(usedSlotInstance);
uint64_t arrayStride = vertexBuffer.arrayStride;
uint64_t bufferSize = mVertexBufferSizes[usedSlotInstance];
// 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.
if ((static_cast<uint64_t>(firstInstance) + instanceCount) * arrayStride > bufferSize) {
return DAWN_VALIDATION_ERROR("Vertex buffer out of bound");
}
}
return {};
}
MaybeError CommandBufferStateTracker::ValidateIndexBufferInRange(uint32_t indexCount,
uint32_t firstIndex) {
// Validate the range of index buffer
// firstIndex and indexCount are in uint32_t, while IndexFormatSize is 2 (for
// wgpu::IndexFormat::Uint16) or 4 (for wgpu::IndexFormat::Uint32), so by doing checks in
// uint64_t we avoid overflows.
if ((static_cast<uint64_t>(firstIndex) + indexCount) * IndexFormatSize(mIndexFormat) >
mIndexBufferSize) {
// Index range is out of bounds
return DAWN_VALIDATION_ERROR("Index buffer out of bound");
}
return {};
}
MaybeError CommandBufferStateTracker::ValidateOperation(ValidationAspects requiredAspects) { MaybeError CommandBufferStateTracker::ValidateOperation(ValidationAspects requiredAspects) {
// Fast return-true path if everything is good // Fast return-true path if everything is good
ValidationAspects missingAspects = requiredAspects & ~mAspects; ValidationAspects missingAspects = requiredAspects & ~mAspects;
@ -213,8 +273,9 @@ namespace dawn_native {
mIndexBufferSize = size; mIndexBufferSize = size;
} }
void CommandBufferStateTracker::SetVertexBuffer(VertexBufferSlot slot) { void CommandBufferStateTracker::SetVertexBuffer(VertexBufferSlot slot, uint64_t size) {
mVertexBufferSlotsUsed.set(slot); mVertexBufferSlotsUsed.set(slot);
mVertexBufferSizes[slot] = size;
} }
void CommandBufferStateTracker::SetPipelineCommon(PipelineBase* pipeline) { void CommandBufferStateTracker::SetPipelineCommon(PipelineBase* pipeline) {
@ -234,5 +295,4 @@ namespace dawn_native {
PipelineLayoutBase* CommandBufferStateTracker::GetPipelineLayout() const { PipelineLayoutBase* CommandBufferStateTracker::GetPipelineLayout() const {
return mLastPipelineLayout; return mLastPipelineLayout;
} }
} // namespace dawn_native } // namespace dawn_native

View File

@ -22,9 +22,6 @@
#include "dawn_native/Error.h" #include "dawn_native/Error.h"
#include "dawn_native/Forward.h" #include "dawn_native/Forward.h"
#include <map>
#include <set>
namespace dawn_native { namespace dawn_native {
class CommandBufferStateTracker { class CommandBufferStateTracker {
@ -33,23 +30,21 @@ namespace dawn_native {
MaybeError ValidateCanDispatch(); MaybeError ValidateCanDispatch();
MaybeError ValidateCanDraw(); MaybeError ValidateCanDraw();
MaybeError ValidateCanDrawIndexed(); MaybeError ValidateCanDrawIndexed();
MaybeError ValidateBufferInRangeForVertexBuffer(uint32_t vertexCount, uint32_t firstVertex);
MaybeError ValidateBufferInRangeForInstanceBuffer(uint32_t instanceCount,
uint32_t firstInstance);
MaybeError ValidateIndexBufferInRange(uint32_t indexCount, uint32_t firstIndex);
// State-modifying methods // State-modifying methods
void SetComputePipeline(ComputePipelineBase* pipeline); void SetComputePipeline(ComputePipelineBase* pipeline);
void SetRenderPipeline(RenderPipelineBase* pipeline); void SetRenderPipeline(RenderPipelineBase* pipeline);
void SetBindGroup(BindGroupIndex index, BindGroupBase* bindgroup); void SetBindGroup(BindGroupIndex index, BindGroupBase* bindgroup);
void SetIndexBuffer(wgpu::IndexFormat format, uint64_t size); void SetIndexBuffer(wgpu::IndexFormat format, uint64_t size);
void SetVertexBuffer(VertexBufferSlot slot); void SetVertexBuffer(VertexBufferSlot slot, uint64_t size);
static constexpr size_t kNumAspects = 4; static constexpr size_t kNumAspects = 4;
using ValidationAspects = std::bitset<kNumAspects>; using ValidationAspects = std::bitset<kNumAspects>;
uint64_t GetIndexBufferSize() {
return mIndexBufferSize;
}
wgpu::IndexFormat GetIndexFormat() {
return mIndexFormat;
}
BindGroupBase* GetBindGroup(BindGroupIndex index) const; BindGroupBase* GetBindGroup(BindGroupIndex index) const;
PipelineLayoutBase* GetPipelineLayout() const; PipelineLayoutBase* GetPipelineLayout() const;
@ -68,6 +63,8 @@ namespace dawn_native {
wgpu::IndexFormat mIndexFormat; wgpu::IndexFormat mIndexFormat;
uint64_t mIndexBufferSize = 0; uint64_t mIndexBufferSize = 0;
ityp::array<VertexBufferSlot, uint64_t, kMaxVertexBuffers> mVertexBufferSizes = {};
PipelineLayoutBase* mLastPipelineLayout = nullptr; PipelineLayoutBase* mLastPipelineLayout = nullptr;
RenderPipelineBase* mLastRenderPipeline = nullptr; RenderPipelineBase* mLastRenderPipeline = nullptr;

View File

@ -67,6 +67,11 @@ namespace dawn_native {
if (mDisableBaseInstance && firstInstance != 0) { if (mDisableBaseInstance && firstInstance != 0) {
return DAWN_VALIDATION_ERROR("Non-zero first instance not supported"); return DAWN_VALIDATION_ERROR("Non-zero first instance not supported");
} }
DAWN_TRY(mCommandBufferState.ValidateBufferInRangeForVertexBuffer(vertexCount,
firstVertex));
DAWN_TRY(mCommandBufferState.ValidateBufferInRangeForInstanceBuffer(instanceCount,
firstInstance));
} }
DrawCmd* draw = allocator->Allocate<DrawCmd>(Command::Draw); DrawCmd* draw = allocator->Allocate<DrawCmd>(Command::Draw);
@ -94,16 +99,13 @@ namespace dawn_native {
if (mDisableBaseVertex && baseVertex != 0) { if (mDisableBaseVertex && baseVertex != 0) {
return DAWN_VALIDATION_ERROR("Non-zero base vertex not supported"); return DAWN_VALIDATION_ERROR("Non-zero base vertex not supported");
} }
DAWN_TRY(mCommandBufferState.ValidateIndexBufferInRange(indexCount, firstIndex));
DAWN_TRY(mCommandBufferState.ValidateBufferInRangeForInstanceBuffer(instanceCount,
firstInstance));
} }
if (static_cast<uint64_t>(firstIndex) + indexCount >
mCommandBufferState.GetIndexBufferSize() /
IndexFormatSize(mCommandBufferState.GetIndexFormat())) {
// Index range is out of bounds
// Treat as no-op and skip issuing draw call
dawn::WarningLog() << "Index range is out of bounds";
return {};
}
DrawIndexedCmd* draw = allocator->Allocate<DrawIndexedCmd>(Command::DrawIndexed); DrawIndexedCmd* draw = allocator->Allocate<DrawIndexedCmd>(Command::DrawIndexed);
draw->indexCount = indexCount; draw->indexCount = indexCount;
draw->instanceCount = instanceCount; draw->instanceCount = instanceCount;
@ -292,7 +294,7 @@ namespace dawn_native {
} }
} }
mCommandBufferState.SetVertexBuffer(VertexBufferSlot(uint8_t(slot))); mCommandBufferState.SetVertexBuffer(VertexBufferSlot(uint8_t(slot)), size);
SetVertexBufferCmd* cmd = SetVertexBufferCmd* cmd =
allocator->Allocate<SetVertexBufferCmd>(Command::SetVertexBuffer); allocator->Allocate<SetVertexBufferCmd>(Command::SetVertexBuffer);

View File

@ -395,6 +395,16 @@ namespace dawn_native {
mVertexBufferSlotsUsed.set(typedSlot); mVertexBufferSlotsUsed.set(typedSlot);
mVertexBufferInfos[typedSlot].arrayStride = buffers[slot].arrayStride; mVertexBufferInfos[typedSlot].arrayStride = buffers[slot].arrayStride;
mVertexBufferInfos[typedSlot].stepMode = buffers[slot].stepMode; mVertexBufferInfos[typedSlot].stepMode = buffers[slot].stepMode;
switch (buffers[slot].stepMode) {
case wgpu::InputStepMode::Vertex:
mVertexBufferSlotsUsedAsVertexBuffer.set(typedSlot);
break;
case wgpu::InputStepMode::Instance:
mVertexBufferSlotsUsedAsInstanceBuffer.set(typedSlot);
break;
default:
DAWN_UNREACHABLE();
}
for (uint32_t i = 0; i < buffers[slot].attributeCount; ++i) { for (uint32_t i = 0; i < buffers[slot].attributeCount; ++i) {
VertexAttributeLocation location = VertexAttributeLocation( VertexAttributeLocation location = VertexAttributeLocation(
@ -494,6 +504,18 @@ namespace dawn_native {
return mVertexBufferSlotsUsed; return mVertexBufferSlotsUsed;
} }
const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>&
RenderPipelineBase::GetVertexBufferSlotsUsedAsVertexBuffer() const {
ASSERT(!IsError());
return mVertexBufferSlotsUsedAsVertexBuffer;
}
const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>&
RenderPipelineBase::GetVertexBufferSlotsUsedAsInstanceBuffer() const {
ASSERT(!IsError());
return mVertexBufferSlotsUsedAsInstanceBuffer;
}
const VertexBufferInfo& RenderPipelineBase::GetVertexBuffer(VertexBufferSlot slot) const { const VertexBufferInfo& RenderPipelineBase::GetVertexBuffer(VertexBufferSlot slot) const {
ASSERT(!IsError()); ASSERT(!IsError());
ASSERT(mVertexBufferSlotsUsed[slot]); ASSERT(mVertexBufferSlotsUsed[slot]);

View File

@ -63,6 +63,10 @@ namespace dawn_native {
GetAttributeLocationsUsed() const; GetAttributeLocationsUsed() const;
const VertexAttributeInfo& GetAttribute(VertexAttributeLocation location) const; const VertexAttributeInfo& GetAttribute(VertexAttributeLocation location) const;
const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& GetVertexBufferSlotsUsed() const; const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& GetVertexBufferSlotsUsed() const;
const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>&
GetVertexBufferSlotsUsedAsVertexBuffer() const;
const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>&
GetVertexBufferSlotsUsedAsInstanceBuffer() const;
const VertexBufferInfo& GetVertexBuffer(VertexBufferSlot slot) const; const VertexBufferInfo& GetVertexBuffer(VertexBufferSlot slot) const;
uint32_t GetVertexBufferCount() const; uint32_t GetVertexBufferCount() const;
@ -104,6 +108,8 @@ namespace dawn_native {
ityp::array<VertexAttributeLocation, VertexAttributeInfo, kMaxVertexAttributes> ityp::array<VertexAttributeLocation, VertexAttributeInfo, kMaxVertexAttributes>
mAttributeInfos; mAttributeInfos;
ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> mVertexBufferSlotsUsed; ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> mVertexBufferSlotsUsed;
ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> mVertexBufferSlotsUsedAsVertexBuffer;
ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> mVertexBufferSlotsUsedAsInstanceBuffer;
ityp::array<VertexBufferSlot, VertexBufferInfo, kMaxVertexBuffers> mVertexBufferInfos; ityp::array<VertexBufferSlot, VertexBufferInfo, kMaxVertexBuffers> mVertexBufferInfos;
// Attachments // Attachments

View File

@ -196,6 +196,7 @@ test("dawn_unittests") {
"unittests/validation/CopyTextureForBrowserTests.cpp", "unittests/validation/CopyTextureForBrowserTests.cpp",
"unittests/validation/DebugMarkerValidationTests.cpp", "unittests/validation/DebugMarkerValidationTests.cpp",
"unittests/validation/DrawIndirectValidationTests.cpp", "unittests/validation/DrawIndirectValidationTests.cpp",
"unittests/validation/DrawVertexAndIndexBufferOOBValidationTests.cpp",
"unittests/validation/DynamicStateCommandValidationTests.cpp", "unittests/validation/DynamicStateCommandValidationTests.cpp",
"unittests/validation/ErrorScopeValidationTests.cpp", "unittests/validation/ErrorScopeValidationTests.cpp",
"unittests/validation/ExternalTextureTests.cpp", "unittests/validation/ExternalTextureTests.cpp",
@ -353,7 +354,6 @@ source_set("dawn_end2end_tests_sources") {
"end2end/TextureSubresourceTests.cpp", "end2end/TextureSubresourceTests.cpp",
"end2end/TextureViewTests.cpp", "end2end/TextureViewTests.cpp",
"end2end/TextureZeroInitTests.cpp", "end2end/TextureZeroInitTests.cpp",
"end2end/VertexBufferRobustnessTests.cpp",
"end2end/VertexFormatTests.cpp", "end2end/VertexFormatTests.cpp",
"end2end/VertexStateTests.cpp", "end2end/VertexStateTests.cpp",
"end2end/ViewportOrientationTests.cpp", "end2end/ViewportOrientationTests.cpp",

View File

@ -137,45 +137,6 @@ TEST_P(DrawIndexedTest, Uint32) {
Test(6, 1, 0, 0, 0, 0, filled, filled); Test(6, 1, 0, 0, 0, 0, filled, filled);
} }
// Out of bounds drawIndexed are treated as no-ops instead of invalid operations
// Some agreements: https://github.com/gpuweb/gpuweb/issues/955
TEST_P(DrawIndexedTest, OutOfBounds) {
RGBA8 filled(0, 255, 0, 255);
RGBA8 notFilled(0, 0, 0, 0);
// a valid draw.
Test(6, 1, 0, 0, 0, 0, filled, filled);
// indexCount is 0 but firstIndex out of bound
Test(0, 1, 20, 0, 0, 0, notFilled, notFilled);
// indexCount + firstIndex out of bound
Test(6, 1, 7, 0, 0, 0, notFilled, notFilled);
// only firstIndex out of bound
Test(6, 1, 20, 0, 0, 0, notFilled, notFilled);
// firstIndex much larger than the bound
Test(6, 1, 10000, 0, 0, 0, notFilled, notFilled);
// only indexCount out of bound
Test(20, 1, 0, 0, 0, 0, notFilled, notFilled);
// indexCount much larger than the bound
Test(10000, 1, 0, 0, 0, 0, notFilled, notFilled);
// max uint32_t indexCount and firstIndex
Test(std::numeric_limits<uint32_t>::max(), 1, std::numeric_limits<uint32_t>::max(), 0, 0, 0,
notFilled, notFilled);
// max uint32_t indexCount and small firstIndex
Test(std::numeric_limits<uint32_t>::max(), 1, 2, 0, 0, 0, notFilled, notFilled);
// small indexCount and max uint32_t firstIndex
Test(2, 1, std::numeric_limits<uint32_t>::max(), 0, 0, 0, notFilled, notFilled);
}
TEST_P(DrawIndexedTest, ZeroSizedIndexBuffer) {
RGBA8 notFilled(0, 0, 0, 0);
// IndexBuffer size is zero, so index access is always out of bounds
TestZeroSizedIndexBufferDraw(3, 1, notFilled, notFilled);
TestZeroSizedIndexBufferDraw(0, 1, notFilled, notFilled);
TestZeroSizedIndexBufferDraw(3, 0, notFilled, notFilled);
TestZeroSizedIndexBufferDraw(0, 0, notFilled, notFilled);
}
// Test the parameter 'baseVertex' of DrawIndexed() works. // Test the parameter 'baseVertex' of DrawIndexed() works.
TEST_P(DrawIndexedTest, BaseVertex) { TEST_P(DrawIndexedTest, BaseVertex) {
DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("disable_base_vertex")); DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("disable_base_vertex"));

View File

@ -1,216 +0,0 @@
// Copyright 2020 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "common/Assert.h"
#include "common/Constants.h"
#include "common/Math.h"
#include "tests/DawnTest.h"
#include "utils/ComboRenderPipelineDescriptor.h"
#include "utils/WGPUHelpers.h"
// Vertex buffer robustness tests that clamping is applied on vertex attributes. This would happen
// on backends where vertex pulling is enabled, such as Metal.
class VertexBufferRobustnessTest : public DawnTest {
protected:
void SetUp() override {
DawnTest::SetUp();
}
// Creates a vertex module that tests an expression with given attributes. If successful, the
// point drawn would be moved out of the viewport. On failure, the point is kept inside the
// viewport.
wgpu::ShaderModule CreateVertexModule(const std::string& attribute,
const std::string& successExpression) {
return utils::CreateShaderModule(device, (R"(
[[stage(vertex)]] fn main(
)" + attribute + R"(
) -> [[builtin(position)]] vec4<f32> {
if ()" + successExpression + R"() {
// Success case, move the vertex out of the viewport
return vec4<f32>(-10.0, 0.0, 0.0, 1.0);
}
// Failure case, move the vertex inside the viewport
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
}
)")
.c_str());
}
// Runs the test, a true |expectation| meaning success
void DoTest(const std::string& attributes,
const std::string& successExpression,
const utils::ComboVertexStateDescriptor& vertexState,
wgpu::Buffer vertexBuffer,
uint64_t bufferOffset,
bool expectation) {
wgpu::ShaderModule vsModule = CreateVertexModule(attributes, successExpression);
wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"(
[[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> {
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
}
)");
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
utils::ComboRenderPipelineDescriptor descriptor;
descriptor.vertex.module = vsModule;
descriptor.cFragment.module = fsModule;
descriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
descriptor.vertex.bufferCount = vertexState.vertexBufferCount;
descriptor.vertex.buffers = &vertexState.cVertexBuffers[0];
descriptor.cTargets[0].format = renderPass.colorFormat;
renderPass.renderPassInfo.cColorAttachments[0].clearColor = {0, 0, 0, 1};
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor);
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
pass.SetPipeline(pipeline);
pass.SetVertexBuffer(0, vertexBuffer, bufferOffset);
pass.Draw(1000);
pass.EndPass();
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
RGBA8 noOutput(0, 0, 0, 255);
RGBA8 someOutput(255, 255, 255, 255);
EXPECT_PIXEL_RGBA8_EQ(expectation ? noOutput : someOutput, renderPass.color, 0, 0);
}
};
TEST_P(VertexBufferRobustnessTest, DetectInvalidValues) {
utils::ComboVertexStateDescriptor vertexState;
vertexState.vertexBufferCount = 1;
vertexState.cVertexBuffers[0].arrayStride = sizeof(float);
vertexState.cVertexBuffers[0].attributeCount = 1;
vertexState.cAttributes[0].format = wgpu::VertexFormat::Float32;
vertexState.cAttributes[0].offset = 0;
vertexState.cAttributes[0].shaderLocation = 0;
// Bind at an offset of 0, so we see 111.0, leading to failure
float kVertices[] = {111.0, 473.0, 473.0};
wgpu::Buffer vertexBuffer = utils::CreateBufferFromData(device, kVertices, sizeof(kVertices),
wgpu::BufferUsage::Vertex);
DoTest("[[location(0)]] a : f32", "a == 473.0", vertexState, vertexBuffer, 0, false);
}
TEST_P(VertexBufferRobustnessTest, FloatClamp) {
utils::ComboVertexStateDescriptor vertexState;
vertexState.vertexBufferCount = 1;
vertexState.cVertexBuffers[0].arrayStride = sizeof(float);
vertexState.cVertexBuffers[0].attributeCount = 1;
vertexState.cAttributes[0].format = wgpu::VertexFormat::Float32;
vertexState.cAttributes[0].offset = 0;
vertexState.cAttributes[0].shaderLocation = 0;
// Bind at an offset of 4, so we clamp to only values containing 473.0
float kVertices[] = {111.0, 473.0, 473.0};
wgpu::Buffer vertexBuffer = utils::CreateBufferFromData(device, kVertices, sizeof(kVertices),
wgpu::BufferUsage::Vertex);
DoTest("[[location(0)]] a : f32", "a == 473.0", vertexState, vertexBuffer, 4, true);
}
TEST_P(VertexBufferRobustnessTest, IntClamp) {
utils::ComboVertexStateDescriptor vertexState;
vertexState.vertexBufferCount = 1;
vertexState.cVertexBuffers[0].arrayStride = sizeof(int32_t);
vertexState.cVertexBuffers[0].attributeCount = 1;
vertexState.cAttributes[0].format = wgpu::VertexFormat::Sint32;
vertexState.cAttributes[0].offset = 0;
vertexState.cAttributes[0].shaderLocation = 0;
// Bind at an offset of 4, so we clamp to only values containing 473
int32_t kVertices[] = {111, 473, 473};
wgpu::Buffer vertexBuffer = utils::CreateBufferFromData(device, kVertices, sizeof(kVertices),
wgpu::BufferUsage::Vertex);
DoTest("[[location(0)]] a : i32", "a == 473", vertexState, vertexBuffer, 4, true);
}
TEST_P(VertexBufferRobustnessTest, UIntClamp) {
utils::ComboVertexStateDescriptor vertexState;
vertexState.vertexBufferCount = 1;
vertexState.cVertexBuffers[0].arrayStride = sizeof(uint32_t);
vertexState.cVertexBuffers[0].attributeCount = 1;
vertexState.cAttributes[0].format = wgpu::VertexFormat::Uint32;
vertexState.cAttributes[0].offset = 0;
vertexState.cAttributes[0].shaderLocation = 0;
// Bind at an offset of 4, so we clamp to only values containing 473
uint32_t kVertices[] = {111, 473, 473};
wgpu::Buffer vertexBuffer = utils::CreateBufferFromData(device, kVertices, sizeof(kVertices),
wgpu::BufferUsage::Vertex);
DoTest("[[location(0)]] a : u32", "a == 473u", vertexState, vertexBuffer, 4, true);
}
TEST_P(VertexBufferRobustnessTest, Float2Clamp) {
utils::ComboVertexStateDescriptor vertexState;
vertexState.vertexBufferCount = 1;
vertexState.cVertexBuffers[0].arrayStride = sizeof(float) * 2;
vertexState.cVertexBuffers[0].attributeCount = 1;
vertexState.cAttributes[0].format = wgpu::VertexFormat::Float32x2;
vertexState.cAttributes[0].offset = 0;
vertexState.cAttributes[0].shaderLocation = 0;
// Bind at an offset of 8, so we clamp to only values containing 473.0
float kVertices[] = {111.0, 111.0, 473.0, 473.0};
wgpu::Buffer vertexBuffer = utils::CreateBufferFromData(device, kVertices, sizeof(kVertices),
wgpu::BufferUsage::Vertex);
DoTest("[[location(0)]] a : vec2<f32>", "a[0] == 473.0 && a[1] == 473.0",
std::move(vertexState), vertexBuffer, 8, true);
}
TEST_P(VertexBufferRobustnessTest, Float3Clamp) {
utils::ComboVertexStateDescriptor vertexState;
vertexState.vertexBufferCount = 1;
vertexState.cVertexBuffers[0].arrayStride = sizeof(float) * 3;
vertexState.cVertexBuffers[0].attributeCount = 1;
vertexState.cAttributes[0].format = wgpu::VertexFormat::Float32x3;
vertexState.cAttributes[0].offset = 0;
vertexState.cAttributes[0].shaderLocation = 0;
// Bind at an offset of 12, so we clamp to only values containing 473.0
float kVertices[] = {111.0, 111.0, 111.0, 473.0, 473.0, 473.0};
wgpu::Buffer vertexBuffer = utils::CreateBufferFromData(device, kVertices, sizeof(kVertices),
wgpu::BufferUsage::Vertex);
DoTest("[[location(0)]] a : vec3<f32>",
"a[0] == 473.0 && a[1] == 473.0 && a[2] == 473.0", vertexState, vertexBuffer, 12, true);
}
TEST_P(VertexBufferRobustnessTest, Float4Clamp) {
utils::ComboVertexStateDescriptor vertexState;
vertexState.vertexBufferCount = 1;
vertexState.cVertexBuffers[0].arrayStride = sizeof(float) * 4;
vertexState.cVertexBuffers[0].attributeCount = 1;
vertexState.cAttributes[0].format = wgpu::VertexFormat::Float32x4;
vertexState.cAttributes[0].offset = 0;
vertexState.cAttributes[0].shaderLocation = 0;
// Bind at an offset of 16, so we clamp to only values containing 473.0
float kVertices[] = {111.0, 111.0, 111.0, 111.0, 473.0, 473.0, 473.0, 473.0};
wgpu::Buffer vertexBuffer = utils::CreateBufferFromData(device, kVertices, sizeof(kVertices),
wgpu::BufferUsage::Vertex);
DoTest("[[location(0)]] a : vec4<f32>",
"a[0] == 473.0 && a[1] == 473.0 && a[2] == 473.0 && a[3] == 473.0", vertexState,
vertexBuffer, 16, true);
}
DAWN_INSTANTIATE_TEST(VertexBufferRobustnessTest, MetalBackend({"metal_enable_vertex_pulling"}));

View File

@ -0,0 +1,657 @@
// Copyright 2021 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "tests/unittests/validation/ValidationTest.h"
#include "common/Constants.h"
#include "common/VertexFormatUtils.h"
#include "utils/ComboRenderPipelineDescriptor.h"
#include "utils/WGPUHelpers.h"
namespace {
constexpr uint32_t kRTSize = 4;
constexpr uint32_t kFloat32x2Stride = 2 * sizeof(float);
constexpr uint32_t kFloat32x4Stride = 4 * sizeof(float);
class DrawVertexAndIndexBufferOOBValidationTests : public ValidationTest {
public:
// Parameters for testing index buffer
struct IndexBufferParams {
wgpu::IndexFormat indexFormat;
uint32_t indexBufferSize; // Size for creating index buffer
uint32_t indexBufferOffsetForEncoder; // Offset for SetIndexBuffer in encoder
uint32_t indexBufferSizeForEncoder; // Size for SetIndexBuffer in encoder
uint32_t maxValidIndexNumber; // max number of {indexCount + firstIndex} for this set
// of parameters
};
// Parameters for testing vertex-step-mode and instance-step-mode vertex buffer
struct VertexBufferParams {
uint32_t bufferStride;
uint32_t bufferSize; // Size for creating vertex buffer
uint32_t bufferOffsetForEncoder; // Offset for SetVertexBuffer in encoder
uint32_t bufferSizeForEncoder; // Size for SetVertexBuffer in encoder
uint32_t maxValidAccessNumber; // max number of valid access time for this set of
// parameters, i.e. {vertexCount + firstVertex} for
// vertex-step-mode, and {instanceCount + firstInstance}
// for instance-step-mode
};
// Parameters for setIndexBuffer
struct IndexBufferDesc {
const wgpu::Buffer buffer;
wgpu::IndexFormat indexFormat;
uint64_t offset = 0;
uint64_t size = 0;
};
// Parameters for setVertexBuffer
struct VertexBufferSpec {
uint32_t slot;
const wgpu::Buffer buffer;
uint64_t offset = 0;
uint64_t size = 0;
};
using VertexBufferList = std::vector<VertexBufferSpec>;
// Buffer layout parameters for creating pipeline
struct PipelineVertexBufferAttributeDesc {
uint32_t shaderLocation;
wgpu::VertexFormat format;
uint64_t offset = 0;
};
struct PipelineVertexBufferDesc {
uint64_t arrayStride;
wgpu::InputStepMode stepMode;
std::vector<PipelineVertexBufferAttributeDesc> attributes = {};
};
void SetUp() override {
ValidationTest::SetUp();
renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize);
fsModule = utils::CreateShaderModule(device, R"(
[[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> {
return vec4<f32>(0.0, 1.0, 0.0, 1.0);
})");
}
const wgpu::RenderPassDescriptor* GetBasicRenderPassDescriptor() const {
return &renderPass.renderPassInfo;
}
wgpu::Buffer CreateBuffer(uint64_t size,
wgpu::BufferUsage usage = wgpu::BufferUsage::Vertex) {
wgpu::BufferDescriptor descriptor;
descriptor.size = size;
descriptor.usage = usage;
return device.CreateBuffer(&descriptor);
}
wgpu::ShaderModule CreateVertexShaderModuleWithBuffer(
std::vector<PipelineVertexBufferDesc> bufferDescList) {
uint32_t attributeCount = 0;
std::stringstream inputStringStream;
for (auto buffer : bufferDescList) {
for (auto attr : buffer.attributes) {
// [[location({shaderLocation})]] var_{id} : {typeString},
inputStringStream << "[[location(" << attr.shaderLocation << ")]] var_"
<< attributeCount << " : "
<< dawn::GetWGSLVertexFormatType(attr.format) << ", ";
attributeCount++;
}
}
std::stringstream shaderStringStream;
shaderStringStream << R"(
[[stage(vertex)]]
fn main()" << inputStringStream.str()
<< R"() -> [[builtin(position)]] vec4<f32> {
return vec4<f32>(0.0, 1.0, 0.0, 1.0);
})";
return utils::CreateShaderModule(device, shaderStringStream.str().c_str());
}
// Create a render pipeline with given buffer layout description, using a vertex shader
// module automatically generated from the buffer description.
wgpu::RenderPipeline CreateRenderPipelineWithBufferDesc(
std::vector<PipelineVertexBufferDesc> bufferDescList) {
utils::ComboRenderPipelineDescriptor descriptor;
descriptor.vertex.module = CreateVertexShaderModuleWithBuffer(bufferDescList);
descriptor.cFragment.module = fsModule;
descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
descriptor.vertex.bufferCount = bufferDescList.size();
size_t attributeCount = 0;
for (size_t bufferCount = 0; bufferCount < bufferDescList.size(); bufferCount++) {
auto bufferDesc = bufferDescList[bufferCount];
descriptor.cBuffers[bufferCount].arrayStride = bufferDesc.arrayStride;
descriptor.cBuffers[bufferCount].stepMode = bufferDesc.stepMode;
if (bufferDesc.attributes.size() > 0) {
descriptor.cBuffers[bufferCount].attributeCount = bufferDesc.attributes.size();
descriptor.cBuffers[bufferCount].attributes =
&descriptor.cAttributes[attributeCount];
for (auto attribute : bufferDesc.attributes) {
descriptor.cAttributes[attributeCount].shaderLocation =
attribute.shaderLocation;
descriptor.cAttributes[attributeCount].format = attribute.format;
descriptor.cAttributes[attributeCount].offset = attribute.offset;
attributeCount++;
}
} else {
descriptor.cBuffers[bufferCount].attributeCount = 0;
descriptor.cBuffers[bufferCount].attributes = nullptr;
}
}
descriptor.cTargets[0].format = renderPass.colorFormat;
return device.CreateRenderPipeline(&descriptor);
}
// Create a render pipeline using only one vertex-step-mode Float32x4 buffer
wgpu::RenderPipeline CreateBasicRenderPipeline(uint32_t bufferStride = kFloat32x4Stride) {
DAWN_ASSERT(bufferStride >=
dawn::VertexFormatNumComponents(wgpu::VertexFormat::Float32x4) *
dawn::VertexFormatComponentSize(wgpu::VertexFormat::Float32x4));
std::vector<PipelineVertexBufferDesc> bufferDescList = {
{bufferStride, wgpu::InputStepMode::Vertex, {{0, wgpu::VertexFormat::Float32x4}}},
};
return CreateRenderPipelineWithBufferDesc(bufferDescList);
}
// Create a render pipeline using one vertex-step-mode Float32x4 buffer and one
// instance-step-mode Float32x2 buffer
wgpu::RenderPipeline CreateBasicRenderPipelineWithInstance(
uint32_t bufferStride1 = kFloat32x4Stride,
uint32_t bufferStride2 = kFloat32x2Stride) {
DAWN_ASSERT(bufferStride1 >= kFloat32x4Stride);
DAWN_ASSERT(bufferStride2 >= kFloat32x2Stride);
std::vector<PipelineVertexBufferDesc> bufferDescList = {
{bufferStride1, wgpu::InputStepMode::Vertex, {{0, wgpu::VertexFormat::Float32x4}}},
{bufferStride2,
wgpu::InputStepMode::Instance,
{{3, wgpu::VertexFormat::Float32x2}}},
};
return CreateRenderPipelineWithBufferDesc(bufferDescList);
}
void TestRenderPassDraw(const wgpu::RenderPipeline& pipeline,
VertexBufferList vertexBufferList,
uint32_t vertexCount,
uint32_t instanceCount,
uint32_t firstVertex,
uint32_t firstInstance,
bool isSuccess) {
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder renderPassEncoder =
encoder.BeginRenderPass(GetBasicRenderPassDescriptor());
renderPassEncoder.SetPipeline(pipeline);
for (auto vertexBufferParam : vertexBufferList) {
renderPassEncoder.SetVertexBuffer(vertexBufferParam.slot, vertexBufferParam.buffer,
vertexBufferParam.offset, vertexBufferParam.size);
}
renderPassEncoder.Draw(vertexCount, instanceCount, firstVertex, firstInstance);
renderPassEncoder.EndPass();
if (isSuccess) {
encoder.Finish();
} else {
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
void TestRenderPassDrawIndexed(const wgpu::RenderPipeline& pipeline,
IndexBufferDesc indexBuffer,
VertexBufferList vertexBufferList,
uint32_t indexCount,
uint32_t instanceCount,
uint32_t firstIndex,
int32_t baseVertex,
uint32_t firstInstance,
bool isSuccess) {
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder renderPassEncoder =
encoder.BeginRenderPass(GetBasicRenderPassDescriptor());
renderPassEncoder.SetPipeline(pipeline);
renderPassEncoder.SetIndexBuffer(indexBuffer.buffer, indexBuffer.indexFormat,
indexBuffer.offset, indexBuffer.size);
for (auto vertexBufferParam : vertexBufferList) {
renderPassEncoder.SetVertexBuffer(vertexBufferParam.slot, vertexBufferParam.buffer,
vertexBufferParam.offset, vertexBufferParam.size);
}
renderPassEncoder.DrawIndexed(indexCount, instanceCount, firstIndex, baseVertex,
firstInstance);
renderPassEncoder.EndPass();
if (isSuccess) {
encoder.Finish();
} else {
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
// Parameters list for index buffer. Should cover all IndexFormat, and the zero/non-zero
// offset and size case in SetIndexBuffer
const std::vector<IndexBufferParams> kIndexParamsList = {
{wgpu::IndexFormat::Uint32, 12 * sizeof(uint32_t), 0, 0, 12},
{wgpu::IndexFormat::Uint32, 13 * sizeof(uint32_t), sizeof(uint32_t), 0, 12},
{wgpu::IndexFormat::Uint32, 13 * sizeof(uint32_t), 0, 12 * sizeof(uint32_t), 12},
{wgpu::IndexFormat::Uint32, 14 * sizeof(uint32_t), sizeof(uint32_t),
12 * sizeof(uint32_t), 12},
{wgpu::IndexFormat::Uint16, 12 * sizeof(uint16_t), 0, 0, 12},
{wgpu::IndexFormat::Uint16, 13 * sizeof(uint16_t), sizeof(uint16_t), 0, 12},
{wgpu::IndexFormat::Uint16, 13 * sizeof(uint16_t), 0, 12 * sizeof(uint16_t), 12},
{wgpu::IndexFormat::Uint16, 14 * sizeof(uint16_t), sizeof(uint16_t),
12 * sizeof(uint16_t), 12},
};
// Parameters list for vertex-step-mode buffer. These parameters should cover different
// stride, buffer size, SetVertexBuffer size and offset.
const std::vector<VertexBufferParams> kVertexParamsList = {
// For stride = kFloat32x4Stride
{kFloat32x4Stride, 3 * kFloat32x4Stride, 0, 0, 3},
// Non-zero offset
{kFloat32x4Stride, 4 * kFloat32x4Stride, kFloat32x4Stride, 0, 3},
// Non-zero size
{kFloat32x4Stride, 4 * kFloat32x4Stride, 0, 3 * kFloat32x4Stride, 3},
// Non-zero offset and size
{kFloat32x4Stride, 5 * kFloat32x4Stride, kFloat32x4Stride, 3 * kFloat32x4Stride, 3},
// For stride = 2 * kFloat32x4Stride
{(2 * kFloat32x4Stride), 3 * (2 * kFloat32x4Stride), 0, 0, 3},
// Non-zero offset
{(2 * kFloat32x4Stride), 4 * (2 * kFloat32x4Stride), (2 * kFloat32x4Stride), 0, 3},
// Non-zero size
{(2 * kFloat32x4Stride), 4 * (2 * kFloat32x4Stride), 0, 3 * (2 * kFloat32x4Stride), 3},
// Non-zero offset and size
{(2 * kFloat32x4Stride), 5 * (2 * kFloat32x4Stride), (2 * kFloat32x4Stride),
3 * (2 * kFloat32x4Stride), 3},
};
// Parameters list for instance-step-mode buffer.
const std::vector<VertexBufferParams> kInstanceParamsList = {
// For stride = kFloat32x2Stride
{kFloat32x2Stride, 5 * kFloat32x2Stride, 0, 0, 5},
// Non-zero offset
{kFloat32x2Stride, 6 * kFloat32x2Stride, kFloat32x2Stride, 0, 5},
// Non-zero size
{kFloat32x2Stride, 6 * kFloat32x2Stride, 0, 5 * kFloat32x2Stride, 5},
// Non-zero offset and size
{kFloat32x2Stride, 7 * kFloat32x2Stride, kFloat32x2Stride, 5 * kFloat32x2Stride, 5},
// For stride = 3 * kFloat32x2Stride
{(3 * kFloat32x2Stride), 5 * (3 * kFloat32x2Stride), 0, 0, 5},
// Non-zero offset
{(3 * kFloat32x2Stride), 6 * (3 * kFloat32x2Stride), (3 * kFloat32x2Stride), 0, 5},
// Non-zero size
{(3 * kFloat32x2Stride), 6 * (3 * kFloat32x2Stride), 0, 5 * (3 * kFloat32x2Stride), 5},
// Non-zero offset and size
{(3 * kFloat32x2Stride), 7 * (3 * kFloat32x2Stride), (3 * kFloat32x2Stride),
5 * (3 * kFloat32x2Stride), 5},
};
private:
wgpu::ShaderModule fsModule;
utils::BasicRenderPass renderPass;
};
// Control case for Draw
TEST_F(DrawVertexAndIndexBufferOOBValidationTests, DrawBasic) {
wgpu::RenderPipeline pipeline = CreateBasicRenderPipeline();
wgpu::Buffer vertexBuffer = CreateBuffer(3 * kFloat32x4Stride);
VertexBufferList vertexBufferList = {{0, vertexBuffer, 0, 0}};
TestRenderPassDraw(pipeline, vertexBufferList, 3, 1, 0, 0, true);
}
// Verify vertex buffer OOB for non-instanced Draw are caught in command encoder
TEST_F(DrawVertexAndIndexBufferOOBValidationTests, DrawVertexBufferOutOfBoundWithoutInstance) {
for (VertexBufferParams params : kVertexParamsList) {
// Create a render pipeline without instance step mode buffer
wgpu::RenderPipeline pipeline = CreateBasicRenderPipeline(params.bufferStride);
// Build vertex buffer for 3 vertices
wgpu::Buffer vertexBuffer = CreateBuffer(params.bufferSize);
VertexBufferList vertexBufferList = {
{0, vertexBuffer, params.bufferOffsetForEncoder, params.bufferSizeForEncoder}};
uint32_t n = params.maxValidAccessNumber;
// It is ok to draw n vertices with vertex buffer
TestRenderPassDraw(pipeline, vertexBufferList, n, 1, 0, 0, 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
TestRenderPassDraw(pipeline, vertexBufferList, n + 1, 1, 0, 0, false);
// Drawing n vertices will non-zero offset will cause OOB
TestRenderPassDraw(pipeline, vertexBufferList, n, 1, 1, 0, false);
// It is ok to draw any number of instances, as we have no instance-mode buffer
TestRenderPassDraw(pipeline, vertexBufferList, n, 5, 0, 0, true);
TestRenderPassDraw(pipeline, vertexBufferList, n, 5, 0, 5, true);
}
}
// Verify vertex buffer OOB for instanced Draw are caught in command encoder
TEST_F(DrawVertexAndIndexBufferOOBValidationTests, DrawVertexBufferOutOfBoundWithInstance) {
for (VertexBufferParams vertexParams : kVertexParamsList) {
for (VertexBufferParams instanceParams : kInstanceParamsList) {
// Create pipeline with given buffer stride
wgpu::RenderPipeline pipeline = CreateBasicRenderPipelineWithInstance(
vertexParams.bufferStride, instanceParams.bufferStride);
// Build vertex buffer
wgpu::Buffer vertexBuffer = CreateBuffer(vertexParams.bufferSize);
wgpu::Buffer instanceBuffer = CreateBuffer(instanceParams.bufferSize);
VertexBufferList vertexBufferList = {
{0, vertexBuffer, vertexParams.bufferOffsetForEncoder,
vertexParams.bufferSizeForEncoder},
{1, instanceBuffer, instanceParams.bufferOffsetForEncoder,
instanceParams.bufferSizeForEncoder},
};
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, vert - 1, 1, 1, 0, true);
// It is ok to draw vert vertices and inst instences
TestRenderPassDraw(pipeline, vertexBufferList, vert, inst, 0, 0, true);
TestRenderPassDraw(pipeline, vertexBufferList, vert, inst - 1, 0, 1, true);
// more vertices causing OOB
TestRenderPassDraw(pipeline, vertexBufferList, vert + 1, 1, 0, 0, false);
TestRenderPassDraw(pipeline, vertexBufferList, vert, 1, 1, 0, false);
TestRenderPassDraw(pipeline, vertexBufferList, vert + 1, inst, 0, 0, false);
TestRenderPassDraw(pipeline, vertexBufferList, vert, inst, 1, 0, false);
// more instances causing OOB
TestRenderPassDraw(pipeline, vertexBufferList, vert, inst + 1, 0, 0, false);
TestRenderPassDraw(pipeline, vertexBufferList, vert, inst, 0, 1, false);
// Both OOB
TestRenderPassDraw(pipeline, vertexBufferList, vert, inst + 1, 0, 0, false);
TestRenderPassDraw(pipeline, vertexBufferList, vert, inst, 1, 1, false);
}
}
}
// Control case for DrawIndexed
TEST_F(DrawVertexAndIndexBufferOOBValidationTests, DrawIndexedBasic) {
wgpu::RenderPipeline pipeline = CreateBasicRenderPipeline();
// Build index buffer for 12 indexes
wgpu::Buffer indexBuffer = CreateBuffer(12 * sizeof(uint32_t), wgpu::BufferUsage::Index);
// Build vertex buffer for 3 vertices
wgpu::Buffer vertexBuffer = CreateBuffer(3 * kFloat32x4Stride);
VertexBufferList vertexBufferList = {{0, vertexBuffer, 0, 0}};
IndexBufferDesc indexBufferDesc = {indexBuffer, wgpu::IndexFormat::Uint32};
TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, 12, 1, 0, 0, 0,
true);
}
// Verify index buffer OOB for DrawIndexed are caught in command encoder
TEST_F(DrawVertexAndIndexBufferOOBValidationTests, DrawIndexedIndexBufferOOB) {
wgpu::RenderPipeline pipeline = CreateBasicRenderPipelineWithInstance();
for (IndexBufferParams params : kIndexParamsList) {
// Build index buffer use given params
wgpu::Buffer indexBuffer =
CreateBuffer(params.indexBufferSize, wgpu::BufferUsage::Index);
// Build vertex buffer for 3 vertices
wgpu::Buffer vertexBuffer = CreateBuffer(3 * kFloat32x4Stride);
// Build vertex buffer for 5 instances
wgpu::Buffer instanceBuffer = CreateBuffer(5 * kFloat32x2Stride);
VertexBufferList vertexBufferList = {{0, vertexBuffer, 0, 0},
{1, instanceBuffer, 0, 0}};
IndexBufferDesc indexBufferDesc = {indexBuffer, params.indexFormat,
params.indexBufferOffsetForEncoder,
params.indexBufferSizeForEncoder};
uint32_t n = params.maxValidIndexNumber;
// Control case
TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, n, 5, 0, 0, 0,
true);
TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, n - 1, 5, 1, 0,
0, true);
// Index buffer OOB, indexCount too large
TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, n + 1, 5, 0, 0,
0, false);
// Index buffer OOB, indexCount + firstIndex too large
TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, n, 5, 1, 0, 0,
false);
if (!HasToggleEnabled("disable_base_vertex")) {
// baseVertex is not considered in CPU validation and has no effect on validation
// Although baseVertex is too large, it will still pass
TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, n, 5, 0, 100,
0, true);
// Index buffer OOB, indexCount too large
TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, n + 1, 5, 0,
100, 0, false);
}
}
}
// Verify instance mode vertex buffer OOB for DrawIndexed are caught in command encoder
TEST_F(DrawVertexAndIndexBufferOOBValidationTests, DrawIndexedVertexBufferOOB) {
for (VertexBufferParams vertexParams : kVertexParamsList) {
for (VertexBufferParams instanceParams : kInstanceParamsList) {
// Create pipeline with given buffer stride
wgpu::RenderPipeline pipeline = CreateBasicRenderPipelineWithInstance(
vertexParams.bufferStride, instanceParams.bufferStride);
auto indexFormat = wgpu::IndexFormat::Uint32;
auto indexStride = sizeof(uint32_t);
// Build index buffer for 12 indexes
wgpu::Buffer indexBuffer = CreateBuffer(12 * 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};
uint32_t vert = vertexParams.maxValidAccessNumber;
uint32_t inst = instanceParams.maxValidAccessNumber;
// Control case
TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, vert, inst,
0, 0, 0, true);
// Vertex buffer (stepMode = instance) OOB, instanceCount too large
TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, vert,
inst + 1, 0, 0, 0, false);
if (!HasToggleEnabled("disable_base_instance")) {
// firstInstance is considered in CPU validation
// Vertex buffer (stepMode = instance) in bound
TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, vert,
inst - 1, 0, 0, 1, true);
// Vertex buffer (stepMode = instance) OOB, instanceCount + firstInstance too
// large
TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, vert,
inst, 0, 0, 1, false);
}
// OOB of vertex buffer that stepMode=vertex can not be validated in CPU.
TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, vert + 1,
inst, 0, 0, 0, true);
}
}
}
// Verify that if setVertexBuffer and/or setIndexBuffer for multiple times, only the last one is
// taken into account
TEST_F(DrawVertexAndIndexBufferOOBValidationTests, SetBufferMultipleTime) {
wgpu::IndexFormat indexFormat = wgpu::IndexFormat::Uint32;
uint32_t indexStride = sizeof(uint32_t);
// Build index buffer for 11 indexes
wgpu::Buffer indexBuffer11 = CreateBuffer(11 * indexStride, wgpu::BufferUsage::Index);
// Build index buffer for 12 indexes
wgpu::Buffer indexBuffer12 = CreateBuffer(12 * indexStride, wgpu::BufferUsage::Index);
// Build vertex buffer for 2 vertices
wgpu::Buffer vertexBuffer2 = CreateBuffer(2 * kFloat32x4Stride);
// Build vertex buffer for 3 vertices
wgpu::Buffer vertexBuffer3 = CreateBuffer(3 * kFloat32x4Stride);
// Build vertex buffer for 4 instances
wgpu::Buffer instanceBuffer4 = CreateBuffer(4 * kFloat32x2Stride);
// Build vertex buffer for 5 instances
wgpu::Buffer instanceBuffer5 = CreateBuffer(5 * kFloat32x2Stride);
// Test for setting vertex buffer for multiple times
{
wgpu::RenderPipeline pipeline = CreateBasicRenderPipelineWithInstance();
// Set to vertexBuffer3 and instanceBuffer5 at last
VertexBufferList vertexBufferList = {{0, vertexBuffer2, 0, 0},
{1, instanceBuffer4, 0, 0},
{1, instanceBuffer5, 0, 0},
{0, vertexBuffer3, 0, 0}};
// For Draw, the max vertexCount is 3 and the max instanceCount is 5
TestRenderPassDraw(pipeline, vertexBufferList, 3, 5, 0, 0, true);
TestRenderPassDraw(pipeline, vertexBufferList, 4, 5, 0, 0, false);
TestRenderPassDraw(pipeline, vertexBufferList, 3, 6, 0, 0, false);
// For DrawIndex, the max instanceCount is 5
TestRenderPassDrawIndexed(pipeline, {indexBuffer12, indexFormat}, vertexBufferList, 12,
5, 0, 0, 0, true);
TestRenderPassDrawIndexed(pipeline, {indexBuffer12, indexFormat}, vertexBufferList, 12,
6, 0, 0, 0, false);
// Set to vertexBuffer2 and instanceBuffer4 at last
vertexBufferList = VertexBufferList{{0, vertexBuffer3, 0, 0},
{1, instanceBuffer5, 0, 0},
{0, vertexBuffer2, 0, 0},
{1, instanceBuffer4, 0, 0}};
// For Draw, the max vertexCount is 2 and the max instanceCount is 4
TestRenderPassDraw(pipeline, vertexBufferList, 2, 4, 0, 0, true);
TestRenderPassDraw(pipeline, vertexBufferList, 3, 4, 0, 0, false);
TestRenderPassDraw(pipeline, vertexBufferList, 2, 5, 0, 0, false);
// For DrawIndex, the max instanceCount is 4
TestRenderPassDrawIndexed(pipeline, {indexBuffer12, indexFormat}, vertexBufferList, 12,
4, 0, 0, 0, true);
TestRenderPassDrawIndexed(pipeline, {indexBuffer12, indexFormat}, vertexBufferList, 12,
5, 0, 0, 0, false);
}
// Test for setIndexBuffer multiple times
{
wgpu::RenderPipeline pipeline = CreateBasicRenderPipeline();
VertexBufferList vertexBufferList = {{0, vertexBuffer3, 0, 0}};
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder renderPassEncoder =
encoder.BeginRenderPass(GetBasicRenderPassDescriptor());
renderPassEncoder.SetPipeline(pipeline);
// Index buffer is set to indexBuffer12 at last
renderPassEncoder.SetIndexBuffer(indexBuffer11, indexFormat);
renderPassEncoder.SetIndexBuffer(indexBuffer12, indexFormat);
renderPassEncoder.SetVertexBuffer(0, vertexBuffer3, 0, 0);
// It should be ok to draw 12 index
renderPassEncoder.DrawIndexed(12, 1, 0, 0, 0);
renderPassEncoder.EndPass();
// Expect success
encoder.Finish();
}
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder renderPassEncoder =
encoder.BeginRenderPass(GetBasicRenderPassDescriptor());
renderPassEncoder.SetPipeline(pipeline);
// Index buffer is set to indexBuffer12 at last
renderPassEncoder.SetIndexBuffer(indexBuffer11, indexFormat);
renderPassEncoder.SetIndexBuffer(indexBuffer12, indexFormat);
renderPassEncoder.SetVertexBuffer(0, vertexBuffer3, 0, 0);
// It should be index buffer OOB to draw 13 index
renderPassEncoder.DrawIndexed(13, 1, 0, 0, 0);
renderPassEncoder.EndPass();
// Expect failure
ASSERT_DEVICE_ERROR(encoder.Finish());
}
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder renderPassEncoder =
encoder.BeginRenderPass(GetBasicRenderPassDescriptor());
renderPassEncoder.SetPipeline(pipeline);
// Index buffer is set to indexBuffer11 at last
renderPassEncoder.SetIndexBuffer(indexBuffer12, indexFormat);
renderPassEncoder.SetIndexBuffer(indexBuffer11, indexFormat);
renderPassEncoder.SetVertexBuffer(0, vertexBuffer3, 0, 0);
// It should be ok to draw 11 index
renderPassEncoder.DrawIndexed(11, 1, 0, 0, 0);
renderPassEncoder.EndPass();
// Expect success
encoder.Finish();
}
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder renderPassEncoder =
encoder.BeginRenderPass(GetBasicRenderPassDescriptor());
renderPassEncoder.SetPipeline(pipeline);
// Index buffer is set to indexBuffer11 at last
renderPassEncoder.SetIndexBuffer(indexBuffer12, indexFormat);
renderPassEncoder.SetIndexBuffer(indexBuffer11, indexFormat);
renderPassEncoder.SetVertexBuffer(0, vertexBuffer3, 0, 0);
// It should be index buffer OOB to draw 12 index
renderPassEncoder.DrawIndexed(12, 1, 0, 0, 0);
renderPassEncoder.EndPass();
// Expect failure
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
}
} // anonymous namespace