1 // Copyright 2017 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 <gmock/gmock.h>
16
17 #include "dawn_native/CommandEncoder.h"
18
19 #include "tests/unittests/validation/ValidationTest.h"
20
21 #include "utils/WGPUHelpers.h"
22
23 using ::testing::HasSubstr;
24
25 class CommandBufferValidationTest : public ValidationTest {};
26
27 // Test for an empty command buffer
TEST_F(CommandBufferValidationTest,Empty)28 TEST_F(CommandBufferValidationTest, Empty) {
29 device.CreateCommandEncoder().Finish();
30 }
31
32 // Test that a command buffer cannot be ended mid render pass
TEST_F(CommandBufferValidationTest,EndedMidRenderPass)33 TEST_F(CommandBufferValidationTest, EndedMidRenderPass) {
34 DummyRenderPass dummyRenderPass(device);
35
36 // Control case, command buffer ended after the pass is ended.
37 {
38 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
39 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
40 pass.EndPass();
41 encoder.Finish();
42 }
43
44 // Error case, command buffer ended mid-pass.
45 {
46 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
47 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
48 ASSERT_DEVICE_ERROR(
49 encoder.Finish(),
50 HasSubstr("Command buffer recording ended before [RenderPassEncoder] was ended."));
51 }
52
53 // Error case, command buffer ended mid-pass. Trying to use encoders after Finish
54 // should fail too.
55 {
56 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
57 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
58 ASSERT_DEVICE_ERROR(
59 encoder.Finish(),
60 HasSubstr("Command buffer recording ended before [RenderPassEncoder] was ended."));
61 ASSERT_DEVICE_ERROR(
62 pass.EndPass(),
63 HasSubstr("Recording in an error or already ended [RenderPassEncoder]."));
64 }
65 }
66
67 // Test that a command buffer cannot be ended mid compute pass
TEST_F(CommandBufferValidationTest,EndedMidComputePass)68 TEST_F(CommandBufferValidationTest, EndedMidComputePass) {
69 // Control case, command buffer ended after the pass is ended.
70 {
71 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
72 wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
73 pass.EndPass();
74 encoder.Finish();
75 }
76
77 // Error case, command buffer ended mid-pass.
78 {
79 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
80 wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
81 ASSERT_DEVICE_ERROR(
82 encoder.Finish(),
83 HasSubstr("Command buffer recording ended before [ComputePassEncoder] was ended."));
84 }
85
86 // Error case, command buffer ended mid-pass. Trying to use encoders after Finish
87 // should fail too.
88 {
89 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
90 wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
91 ASSERT_DEVICE_ERROR(
92 encoder.Finish(),
93 HasSubstr("Command buffer recording ended before [ComputePassEncoder] was ended."));
94 ASSERT_DEVICE_ERROR(
95 pass.EndPass(),
96 HasSubstr("Recording in an error or already ended [ComputePassEncoder]."));
97 }
98 }
99
100 // Test that a render pass cannot be ended twice
TEST_F(CommandBufferValidationTest,RenderPassEndedTwice)101 TEST_F(CommandBufferValidationTest, RenderPassEndedTwice) {
102 DummyRenderPass dummyRenderPass(device);
103
104 // Control case, pass is ended once
105 {
106 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
107 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
108 pass.EndPass();
109 encoder.Finish();
110 }
111
112 // Error case, pass ended twice
113 {
114 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
115 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
116 pass.EndPass();
117 pass.EndPass();
118 ASSERT_DEVICE_ERROR(
119 encoder.Finish(),
120 HasSubstr("Recording in an error or already ended [RenderPassEncoder]."));
121 }
122 }
123
124 // Test that a compute pass cannot be ended twice
TEST_F(CommandBufferValidationTest,ComputePassEndedTwice)125 TEST_F(CommandBufferValidationTest, ComputePassEndedTwice) {
126 // Control case, pass is ended once.
127 {
128 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
129 wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
130 pass.EndPass();
131 encoder.Finish();
132 }
133
134 // Error case, pass ended twice
135 {
136 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
137 wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
138 pass.EndPass();
139 pass.EndPass();
140 ASSERT_DEVICE_ERROR(
141 encoder.Finish(),
142 HasSubstr("Recording in an error or already ended [ComputePassEncoder]."));
143 }
144 }
145
146 // Test that beginning a compute pass before ending the previous pass causes an error.
TEST_F(CommandBufferValidationTest,BeginComputePassBeforeEndPreviousPass)147 TEST_F(CommandBufferValidationTest, BeginComputePassBeforeEndPreviousPass) {
148 DummyRenderPass dummyRenderPass(device);
149
150 // Beginning a compute pass before ending a render pass causes an error.
151 {
152 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
153 wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&dummyRenderPass);
154 wgpu::ComputePassEncoder computePass = encoder.BeginComputePass();
155 computePass.EndPass();
156 renderPass.EndPass();
157 ASSERT_DEVICE_ERROR(encoder.Finish());
158 }
159
160 // Beginning a compute pass before ending a compute pass causes an error.
161 {
162 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
163 wgpu::ComputePassEncoder computePass1 = encoder.BeginComputePass();
164 wgpu::ComputePassEncoder computePass2 = encoder.BeginComputePass();
165 computePass2.EndPass();
166 computePass1.EndPass();
167 ASSERT_DEVICE_ERROR(encoder.Finish());
168 }
169 }
170
171 // Test that beginning a render pass before ending the previous pass causes an error.
TEST_F(CommandBufferValidationTest,BeginRenderPassBeforeEndPreviousPass)172 TEST_F(CommandBufferValidationTest, BeginRenderPassBeforeEndPreviousPass) {
173 DummyRenderPass dummyRenderPass(device);
174
175 // Beginning a render pass before ending the render pass causes an error.
176 {
177 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
178 wgpu::RenderPassEncoder renderPass1 = encoder.BeginRenderPass(&dummyRenderPass);
179 wgpu::RenderPassEncoder renderPass2 = encoder.BeginRenderPass(&dummyRenderPass);
180 renderPass2.EndPass();
181 renderPass1.EndPass();
182 ASSERT_DEVICE_ERROR(encoder.Finish());
183 }
184
185 // Beginning a compute pass before ending a compute pass causes an error.
186 {
187 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
188 wgpu::ComputePassEncoder computePass = encoder.BeginComputePass();
189 wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&dummyRenderPass);
190 renderPass.EndPass();
191 computePass.EndPass();
192 ASSERT_DEVICE_ERROR(encoder.Finish());
193 }
194 }
195
196 // Test that encoding command after a successful finish produces an error
TEST_F(CommandBufferValidationTest,CallsAfterASuccessfulFinish)197 TEST_F(CommandBufferValidationTest, CallsAfterASuccessfulFinish) {
198 // A buffer that can be used in CopyBufferToBuffer
199 wgpu::BufferDescriptor copyBufferDesc;
200 copyBufferDesc.size = 16;
201 copyBufferDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
202 wgpu::Buffer copyBuffer = device.CreateBuffer(©BufferDesc);
203
204 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
205 encoder.Finish();
206
207 ASSERT_DEVICE_ERROR(encoder.CopyBufferToBuffer(copyBuffer, 0, copyBuffer, 0, 0));
208 }
209
210 // Test that encoding command after a failed finish produces an error
TEST_F(CommandBufferValidationTest,CallsAfterAFailedFinish)211 TEST_F(CommandBufferValidationTest, CallsAfterAFailedFinish) {
212 // A buffer that can be used in CopyBufferToBuffer
213 wgpu::BufferDescriptor copyBufferDesc;
214 copyBufferDesc.size = 16;
215 copyBufferDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
216 wgpu::Buffer copyBuffer = device.CreateBuffer(©BufferDesc);
217
218 // A buffer that can't be used in CopyBufferToBuffer
219 wgpu::BufferDescriptor bufferDesc;
220 bufferDesc.size = 16;
221 bufferDesc.usage = wgpu::BufferUsage::Uniform;
222 wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc);
223
224 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
225 encoder.CopyBufferToBuffer(buffer, 0, buffer, 0, 0);
226 ASSERT_DEVICE_ERROR(encoder.Finish());
227
228 ASSERT_DEVICE_ERROR(encoder.CopyBufferToBuffer(copyBuffer, 0, copyBuffer, 0, 0));
229 }
230
231 // Test that passes which are de-referenced prior to ending still allow the correct errors to be
232 // produced.
TEST_F(CommandBufferValidationTest,PassDereferenced)233 TEST_F(CommandBufferValidationTest, PassDereferenced) {
234 DummyRenderPass dummyRenderPass(device);
235
236 // Control case, command buffer ended after the pass is ended.
237 {
238 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
239 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
240 pass.EndPass();
241 encoder.Finish();
242 }
243
244 // Error case, no reference is kept to a render pass.
245 {
246 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
247 encoder.BeginRenderPass(&dummyRenderPass);
248 ASSERT_DEVICE_ERROR(
249 encoder.Finish(),
250 HasSubstr("Command buffer recording ended before [RenderPassEncoder] was ended."));
251 }
252
253 // Error case, no reference is kept to a compute pass.
254 {
255 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
256 encoder.BeginComputePass();
257 ASSERT_DEVICE_ERROR(
258 encoder.Finish(),
259 HasSubstr("Command buffer recording ended before [ComputePassEncoder] was ended."));
260 }
261
262 // Error case, beginning a new pass after failing to end a de-referenced pass.
263 {
264 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
265 encoder.BeginRenderPass(&dummyRenderPass);
266 wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
267 pass.EndPass();
268 ASSERT_DEVICE_ERROR(
269 encoder.Finish(),
270 HasSubstr("Command buffer recording ended before [RenderPassEncoder] was ended."));
271 }
272
273 // Error case, deleting the pass after finishing the command encoder shouldn't generate an
274 // uncaptured error.
275 {
276 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
277 wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
278 ASSERT_DEVICE_ERROR(
279 encoder.Finish(),
280 HasSubstr("Command buffer recording ended before [ComputePassEncoder] was ended."));
281
282 pass = nullptr;
283 }
284
285 // Valid case, command encoder is never finished so the de-referenced pass shouldn't
286 // generate an uncaptured error.
287 {
288 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
289 encoder.BeginComputePass();
290 }
291 }
292
293 // Test that calling inject validation error produces an error.
TEST_F(CommandBufferValidationTest,InjectValidationError)294 TEST_F(CommandBufferValidationTest, InjectValidationError) {
295 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
296 encoder.InjectValidationError("my error");
297 ASSERT_DEVICE_ERROR(encoder.Finish(), HasSubstr("my error"));
298 }
299
TEST_F(CommandBufferValidationTest,DestroyEncoder)300 TEST_F(CommandBufferValidationTest, DestroyEncoder) {
301 // Skip these tests if we are using wire because the destroy functionality is not exposed
302 // and needs to use a cast to call manually. We cannot test this in the wire case since the
303 // only way to trigger the destroy call is by losing all references which means we cannot
304 // call finish.
305 DAWN_SKIP_TEST_IF(UsesWire());
306 DummyRenderPass dummyRenderPass(device);
307
308 // Control case, command buffer ended after the pass is ended.
309 {
310 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
311 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
312 pass.EndPass();
313 encoder.Finish();
314 }
315
316 // Destroyed encoder with encoded commands should emit error on finish.
317 {
318 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
319 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
320 pass.EndPass();
321 dawn_native::FromAPI(encoder.Get())->Destroy();
322 ASSERT_DEVICE_ERROR(encoder.Finish(), HasSubstr("Destroyed encoder cannot be finished."));
323 }
324
325 // Destroyed encoder with encoded commands shouldn't emit an error if never finished.
326 {
327 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
328 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
329 pass.EndPass();
330 dawn_native::FromAPI(encoder.Get())->Destroy();
331 }
332
333 // Destroyed encoder should allow encoding, and emit error on finish.
334 {
335 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
336 dawn_native::FromAPI(encoder.Get())->Destroy();
337 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
338 pass.EndPass();
339 ASSERT_DEVICE_ERROR(encoder.Finish(), HasSubstr("Destroyed encoder cannot be finished."));
340 }
341
342 // Destroyed encoder should allow encoding and shouldn't emit an error if never finished.
343 {
344 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
345 dawn_native::FromAPI(encoder.Get())->Destroy();
346 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
347 pass.EndPass();
348 }
349
350 // Destroying a finished encoder should not emit any errors.
351 {
352 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
353 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
354 pass.EndPass();
355 encoder.Finish();
356 dawn_native::FromAPI(encoder.Get())->Destroy();
357 }
358
359 // Destroying an encoder twice should not emit any errors.
360 {
361 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
362 dawn_native::FromAPI(encoder.Get())->Destroy();
363 dawn_native::FromAPI(encoder.Get())->Destroy();
364 }
365
366 // Destroying an encoder twice and then calling finish should fail.
367 {
368 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
369 dawn_native::FromAPI(encoder.Get())->Destroy();
370 dawn_native::FromAPI(encoder.Get())->Destroy();
371 ASSERT_DEVICE_ERROR(encoder.Finish(), HasSubstr("Destroyed encoder cannot be finished."));
372 }
373 }
374