dawn-cmake/src/tests/unittests/validation/DrawIndirectValidationTests.cpp
Corentin Wallez 310d86f4a0 dawn_native: Move pass validation of buffer usages into the encoder.
In a future CL the PassResourceUsage structure will become a
SyncScopeResourceUsage and will be used to validate at each
synchronization scope. For separation of concerns, the validation that
resource have the correct usage shouldn't be done at the sync scope
level but at each entrypoint that uses the resource.

The validation tests had no coverage of usage validation for pass usage
so validation tests are added for Indirct/Index/Vertex usages. (Uniform
and Storage are validated at bindgroup creation and already had
validation tests)

Bug: dawn:635
Change-Id: I5058ad30eb041809f0f60d9403f3cc2d5d7e7c96
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/38380
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Auto-Submit: Corentin Wallez <cwallez@chromium.org>
2021-01-22 09:57:38 +00:00

165 lines
7.3 KiB
C++

// Copyright 2019 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 <initializer_list>
#include <limits>
#include "tests/unittests/validation/ValidationTest.h"
#include "utils/ComboRenderPipelineDescriptor.h"
#include "utils/WGPUHelpers.h"
class DrawIndirectValidationTest : public ValidationTest {
protected:
void SetUp() override {
ValidationTest::SetUp();
wgpu::ShaderModule vsModule = utils::CreateShaderModuleFromWGSL(device, R"(
[[builtin(position)]] var<out> Position : vec4<f32>;
[[stage(vertex)]] fn main() -> void {
Position = vec4<f32>(0.0, 0.0, 0.0, 0.0);
})");
wgpu::ShaderModule fsModule = utils::CreateShaderModuleFromWGSL(device, R"(
[[location(0)]] var<out> fragColor : vec4<f32>;
[[stage(fragment)]] fn main() -> void {
fragColor = vec4<f32>(0.0, 0.0, 0.0, 0.0);
})");
// Set up render pipeline
wgpu::PipelineLayout pipelineLayout = utils::MakeBasicPipelineLayout(device, nullptr);
utils::ComboRenderPipelineDescriptor descriptor(device);
descriptor.layout = pipelineLayout;
descriptor.vertexStage.module = vsModule;
descriptor.cFragmentStage.module = fsModule;
pipeline = device.CreateRenderPipeline(&descriptor);
}
void ValidateExpectation(wgpu::CommandEncoder encoder, utils::Expectation expectation) {
if (expectation == utils::Expectation::Success) {
encoder.Finish();
} else {
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
void TestIndirectOffsetDrawIndexed(utils::Expectation expectation,
std::initializer_list<uint32_t> bufferList,
uint64_t indirectOffset) {
TestIndirectOffset(expectation, bufferList, indirectOffset, true);
}
void TestIndirectOffsetDraw(utils::Expectation expectation,
std::initializer_list<uint32_t> bufferList,
uint64_t indirectOffset) {
TestIndirectOffset(expectation, bufferList, indirectOffset, false);
}
void TestIndirectOffset(utils::Expectation expectation,
std::initializer_list<uint32_t> bufferList,
uint64_t indirectOffset,
bool indexed,
wgpu::BufferUsage usage = wgpu::BufferUsage::Indirect) {
wgpu::Buffer indirectBuffer =
utils::CreateBufferFromData<uint32_t>(device, usage, bufferList);
DummyRenderPass renderPass(device);
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
pass.SetPipeline(pipeline);
if (indexed) {
uint32_t zeros[100] = {};
wgpu::Buffer indexBuffer =
utils::CreateBufferFromData(device, zeros, sizeof(zeros), wgpu::BufferUsage::Index);
pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
pass.DrawIndexedIndirect(indirectBuffer, indirectOffset);
} else {
pass.DrawIndirect(indirectBuffer, indirectOffset);
}
pass.EndPass();
ValidateExpectation(encoder, expectation);
}
wgpu::RenderPipeline pipeline;
};
// Verify out of bounds indirect draw calls are caught early
TEST_F(DrawIndirectValidationTest, DrawIndirectOffsetBounds) {
// In bounds
TestIndirectOffsetDraw(utils::Expectation::Success, {1, 2, 3, 4}, 0);
// In bounds, bigger buffer
TestIndirectOffsetDraw(utils::Expectation::Success, {1, 2, 3, 4, 5, 6, 7}, 0);
// In bounds, bigger buffer, positive offset
TestIndirectOffsetDraw(utils::Expectation::Success, {1, 2, 3, 4, 5, 6, 7, 8},
4 * sizeof(uint32_t));
// In bounds, non-multiple of 4 offsets
TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3, 4, 5}, 1);
TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3, 4, 5}, 2);
// Out of bounds, buffer too small
TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3}, 0);
// Out of bounds, index too big
TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3, 4}, 1 * sizeof(uint32_t));
// Out of bounds, index past buffer
TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3, 4}, 5 * sizeof(uint32_t));
// Out of bounds, index + size of command overflows
uint64_t offset = std::numeric_limits<uint64_t>::max();
TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3, 4, 5, 6, 7}, offset);
}
// Verify out of bounds indirect draw indexed calls are caught early
TEST_F(DrawIndirectValidationTest, DrawIndexedIndirectOffsetBounds) {
// In bounds
TestIndirectOffsetDrawIndexed(utils::Expectation::Success, {1, 2, 3, 4, 5}, 0);
// In bounds, bigger buffer
TestIndirectOffsetDrawIndexed(utils::Expectation::Success, {1, 2, 3, 4, 5, 6, 7, 8, 9}, 0);
// In bounds, bigger buffer, positive offset
TestIndirectOffsetDrawIndexed(utils::Expectation::Success, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
5 * sizeof(uint32_t));
// In bounds, non-multiple of 4 offsets
TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4, 5, 6}, 1);
TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4, 5, 6}, 2);
// Out of bounds, buffer too small
TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4}, 0);
// Out of bounds, index too big
TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4, 5},
1 * sizeof(uint32_t));
// Out of bounds, index past buffer
TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4, 5},
5 * sizeof(uint32_t));
// Out of bounds, index + size of command overflows
uint64_t offset = std::numeric_limits<uint64_t>::max();
TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
offset);
}
// Check that the buffer must have the indirect usage
TEST_F(DrawIndirectValidationTest, IndirectUsage) {
// Control cases: using a buffer with the indirect usage is valid.
TestIndirectOffset(utils::Expectation::Success, {1, 2, 3, 4}, 0, false,
wgpu::BufferUsage::Indirect);
TestIndirectOffset(utils::Expectation::Success, {1, 2, 3, 4, 5}, 0, true,
wgpu::BufferUsage::Indirect);
// Error cases: using a buffer with the vertex usage is an error.
TestIndirectOffset(utils::Expectation::Failure, {1, 2, 3, 4}, 0, false,
wgpu::BufferUsage::Vertex);
TestIndirectOffset(utils::Expectation::Failure, {1, 2, 3, 4, 5}, 0, true,
wgpu::BufferUsage::Vertex);
}