1 // Copyright 2019 The Dawn Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <initializer_list>
16 #include <limits>
17 #include "tests/unittests/validation/ValidationTest.h"
18 #include "utils/ComboRenderPipelineDescriptor.h"
19 #include "utils/WGPUHelpers.h"
20
21 class DrawIndirectValidationTest : public ValidationTest {
22 protected:
SetUp()23 void SetUp() override {
24 ValidationTest::SetUp();
25
26 wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"(
27 [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> {
28 return vec4<f32>(0.0, 0.0, 0.0, 0.0);
29 })");
30
31 wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"(
32 [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32>{
33 return vec4<f32>(0.0, 0.0, 0.0, 0.0);
34 })");
35
36 // Set up render pipeline
37 wgpu::PipelineLayout pipelineLayout = utils::MakeBasicPipelineLayout(device, nullptr);
38
39 utils::ComboRenderPipelineDescriptor descriptor;
40 descriptor.layout = pipelineLayout;
41 descriptor.vertex.module = vsModule;
42 descriptor.cFragment.module = fsModule;
43
44 pipeline = device.CreateRenderPipeline(&descriptor);
45 }
46
ValidateExpectation(wgpu::CommandEncoder encoder,utils::Expectation expectation)47 void ValidateExpectation(wgpu::CommandEncoder encoder, utils::Expectation expectation) {
48 if (expectation == utils::Expectation::Success) {
49 encoder.Finish();
50 } else {
51 ASSERT_DEVICE_ERROR(encoder.Finish());
52 }
53 }
54
TestIndirectOffsetDrawIndexed(utils::Expectation expectation,std::initializer_list<uint32_t> bufferList,uint64_t indirectOffset)55 void TestIndirectOffsetDrawIndexed(utils::Expectation expectation,
56 std::initializer_list<uint32_t> bufferList,
57 uint64_t indirectOffset) {
58 TestIndirectOffset(expectation, bufferList, indirectOffset, true);
59 }
60
TestIndirectOffsetDraw(utils::Expectation expectation,std::initializer_list<uint32_t> bufferList,uint64_t indirectOffset)61 void TestIndirectOffsetDraw(utils::Expectation expectation,
62 std::initializer_list<uint32_t> bufferList,
63 uint64_t indirectOffset) {
64 TestIndirectOffset(expectation, bufferList, indirectOffset, false);
65 }
66
TestIndirectOffset(utils::Expectation expectation,std::initializer_list<uint32_t> bufferList,uint64_t indirectOffset,bool indexed,wgpu::BufferUsage usage=wgpu::BufferUsage::Indirect)67 void TestIndirectOffset(utils::Expectation expectation,
68 std::initializer_list<uint32_t> bufferList,
69 uint64_t indirectOffset,
70 bool indexed,
71 wgpu::BufferUsage usage = wgpu::BufferUsage::Indirect) {
72 wgpu::Buffer indirectBuffer =
73 utils::CreateBufferFromData<uint32_t>(device, usage, bufferList);
74
75 DummyRenderPass renderPass(device);
76 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
77 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
78 pass.SetPipeline(pipeline);
79 if (indexed) {
80 uint32_t zeros[100] = {};
81 wgpu::Buffer indexBuffer =
82 utils::CreateBufferFromData(device, zeros, sizeof(zeros), wgpu::BufferUsage::Index);
83 pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
84 pass.DrawIndexedIndirect(indirectBuffer, indirectOffset);
85 } else {
86 pass.DrawIndirect(indirectBuffer, indirectOffset);
87 }
88 pass.EndPass();
89
90 ValidateExpectation(encoder, expectation);
91 }
92
93 wgpu::RenderPipeline pipeline;
94 };
95
96 // Verify out of bounds indirect draw calls are caught early
TEST_F(DrawIndirectValidationTest,DrawIndirectOffsetBounds)97 TEST_F(DrawIndirectValidationTest, DrawIndirectOffsetBounds) {
98 // In bounds
99 TestIndirectOffsetDraw(utils::Expectation::Success, {1, 2, 3, 4}, 0);
100 // In bounds, bigger buffer
101 TestIndirectOffsetDraw(utils::Expectation::Success, {1, 2, 3, 4, 5, 6, 7}, 0);
102 // In bounds, bigger buffer, positive offset
103 TestIndirectOffsetDraw(utils::Expectation::Success, {1, 2, 3, 4, 5, 6, 7, 8},
104 4 * sizeof(uint32_t));
105
106 // In bounds, non-multiple of 4 offsets
107 TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3, 4, 5}, 1);
108 TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3, 4, 5}, 2);
109
110 // Out of bounds, buffer too small
111 TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3}, 0);
112 // Out of bounds, index too big
113 TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3, 4}, 1 * sizeof(uint32_t));
114 // Out of bounds, index past buffer
115 TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3, 4}, 5 * sizeof(uint32_t));
116 // Out of bounds, index + size of command overflows
117 uint64_t offset = std::numeric_limits<uint64_t>::max();
118 TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3, 4, 5, 6, 7}, offset);
119 }
120
121 // Verify out of bounds indirect draw indexed calls are caught early
TEST_F(DrawIndirectValidationTest,DrawIndexedIndirectOffsetBounds)122 TEST_F(DrawIndirectValidationTest, DrawIndexedIndirectOffsetBounds) {
123 // In bounds
124 TestIndirectOffsetDrawIndexed(utils::Expectation::Success, {1, 2, 3, 4, 5}, 0);
125 // In bounds, bigger buffer
126 TestIndirectOffsetDrawIndexed(utils::Expectation::Success, {1, 2, 3, 4, 5, 6, 7, 8, 9}, 0);
127 // In bounds, bigger buffer, positive offset
128 TestIndirectOffsetDrawIndexed(utils::Expectation::Success, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
129 5 * sizeof(uint32_t));
130
131 // In bounds, non-multiple of 4 offsets
132 TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4, 5, 6}, 1);
133 TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4, 5, 6}, 2);
134
135 // Out of bounds, buffer too small
136 TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4}, 0);
137 // Out of bounds, index too big
138 TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4, 5},
139 1 * sizeof(uint32_t));
140 // Out of bounds, index past buffer
141 TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4, 5},
142 5 * sizeof(uint32_t));
143 // Out of bounds, index + size of command overflows
144 uint64_t offset = std::numeric_limits<uint64_t>::max();
145 TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
146 offset);
147 }
148
149 // Check that the buffer must have the indirect usage
TEST_F(DrawIndirectValidationTest,IndirectUsage)150 TEST_F(DrawIndirectValidationTest, IndirectUsage) {
151 // Control cases: using a buffer with the indirect usage is valid.
152 TestIndirectOffset(utils::Expectation::Success, {1, 2, 3, 4}, 0, false,
153 wgpu::BufferUsage::Indirect);
154 TestIndirectOffset(utils::Expectation::Success, {1, 2, 3, 4, 5}, 0, true,
155 wgpu::BufferUsage::Indirect);
156
157 // Error cases: using a buffer with the vertex usage is an error.
158 TestIndirectOffset(utils::Expectation::Failure, {1, 2, 3, 4}, 0, false,
159 wgpu::BufferUsage::Vertex);
160 TestIndirectOffset(utils::Expectation::Failure, {1, 2, 3, 4, 5}, 0, true,
161 wgpu::BufferUsage::Vertex);
162 }
163