|
19 | 19 | import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
20 | 20 | import { objectEquals } from '../../../../common/util/util.js';
|
21 | 21 | import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';
|
| 22 | +import * as vtu from '../validation_test_utils.js'; |
22 | 23 |
|
23 | 24 | class F extends AllFeaturesMaxLimitsGPUTest {
|
24 | 25 | beginRenderPass(commandEncoder: GPUCommandEncoder, view: GPUTextureView): GPURenderPassEncoder {
|
@@ -154,7 +155,7 @@ g.test('call_after_successful_finish')
|
154 | 155 | g.test('pass_end_none')
|
155 | 156 | .desc(
|
156 | 157 | `
|
157 |
| - Test that ending a {compute,render} pass without ending the passes generates a validation error. |
| 158 | + Test that finishing an encoder without ending a child {compute,render} pass generates a validation error. |
158 | 159 | `
|
159 | 160 | )
|
160 | 161 | .paramsSubcasesOnly(u => u.combine('passType', ['compute', 'render']).combine('endCount', [0, 1]))
|
@@ -247,3 +248,81 @@ g.test('pass_end_twice,render_pass_invalid')
|
247 | 248 | encoder.finish();
|
248 | 249 | });
|
249 | 250 | });
|
| 251 | + |
| 252 | +g.test('pass_begin_invalid_encoder') |
| 253 | + .desc( |
| 254 | + ` |
| 255 | + Test that {compute,render} passes can still be opened on an invalid encoder. |
| 256 | + ` |
| 257 | + ) |
| 258 | + .params(u => |
| 259 | + u |
| 260 | + .combine('pass0Type', ['compute', 'render']) |
| 261 | + .combine('pass1Type', ['compute', 'render']) |
| 262 | + .beginSubcases() |
| 263 | + .combine('firstPassInvalid', [false, true]) |
| 264 | + ) |
| 265 | + .beforeAllSubcases(t => t.usesMismatchedDevice()) |
| 266 | + .fn(t => { |
| 267 | + t.skipIfDeviceDoesNotSupportQueryType('timestamp'); |
| 268 | + |
| 269 | + const { pass0Type, pass1Type, firstPassInvalid } = t.params; |
| 270 | + |
| 271 | + const view = t.createAttachmentTextureView(); |
| 272 | + const mismatchedTexture = vtu.getDeviceMismatchedRenderTexture(t, 4) |
| 273 | + const mismatchedView = mismatchedTexture.createView(); |
| 274 | + |
| 275 | + const querySet = t.trackForCleanup( |
| 276 | + t.device.createQuerySet({ |
| 277 | + type: 'timestamp', |
| 278 | + count: 1, |
| 279 | + }) |
| 280 | + ); |
| 281 | + |
| 282 | + const timestampWrites = { |
| 283 | + querySet, |
| 284 | + beginningOfPassWriteIndex: 0, |
| 285 | + }; |
| 286 | + |
| 287 | + const descriptor = { |
| 288 | + timestampWrites, |
| 289 | + }; |
| 290 | + |
| 291 | + const mismatchedQuerySet = t.trackForCleanup( |
| 292 | + t.mismatchedDevice.createQuerySet({ |
| 293 | + type: 'timestamp', |
| 294 | + count: 1, |
| 295 | + }) |
| 296 | + ); |
| 297 | + |
| 298 | + const mismatchedTimestampWrites = { |
| 299 | + querySet: mismatchedQuerySet, |
| 300 | + beginningOfPassWriteIndex: 0, |
| 301 | + }; |
| 302 | + |
| 303 | + const mismatchedDescriptor = { |
| 304 | + timestampWrites: mismatchedTimestampWrites, |
| 305 | + }; |
| 306 | + |
| 307 | + const encoder = t.device.createCommandEncoder(); |
| 308 | + |
| 309 | + const firstPass = |
| 310 | + pass0Type === 'compute' ? |
| 311 | + (firstPassInvalid ? encoder.beginComputePass(mismatchedDescriptor) |
| 312 | + : encoder.beginComputePass(descriptor)) : |
| 313 | + (firstPassInvalid ? t.beginRenderPass(encoder, mismatchedView) |
| 314 | + : t.beginRenderPass(encoder, view)); |
| 315 | + |
| 316 | + // Ending an invalid pass invalidates the encoder |
| 317 | + firstPass.end(); |
| 318 | + |
| 319 | + // Passes can still be opened on an invalid encoder |
| 320 | + const secondPass = |
| 321 | + pass1Type === 'compute' ? encoder.beginComputePass() : t.beginRenderPass(encoder, view); |
| 322 | + |
| 323 | + secondPass.end(); |
| 324 | + |
| 325 | + t.expectValidationError(() => { |
| 326 | + encoder.finish(); |
| 327 | + }, firstPassInvalid); |
| 328 | + }); |
0 commit comments