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:
parent
8d05400b40
commit
d8b3d99038
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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]);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"));
|
||||||
|
|
|
@ -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"}));
|
|
|
@ -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
|
Loading…
Reference in New Issue