summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts1129
1 files changed, 1129 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts
new file mode 100644
index 0000000000..63c5fd20a4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts
@@ -0,0 +1,1129 @@
+export const description = `
+render pass descriptor validation tests.
+
+TODO: review for completeness
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { range } from '../../../../common/util/util.js';
+import {
+ kDepthStencilFormats,
+ kMaxColorAttachments,
+ kQueryTypes,
+ kRenderableColorTextureFormats,
+ kTextureFormatInfo,
+} from '../../../capability_info.js';
+import { GPUConst } from '../../../constants.js';
+import { ValidationTest } from '../validation_test.js';
+
+class F extends ValidationTest {
+ createTexture(
+ options: {
+ format?: GPUTextureFormat;
+ width?: number;
+ height?: number;
+ arrayLayerCount?: number;
+ mipLevelCount?: number;
+ sampleCount?: number;
+ usage?: GPUTextureUsageFlags;
+ } = {}
+ ): GPUTexture {
+ const {
+ format = 'rgba8unorm',
+ width = 16,
+ height = 16,
+ arrayLayerCount = 1,
+ mipLevelCount = 1,
+ sampleCount = 1,
+ usage = GPUTextureUsage.RENDER_ATTACHMENT,
+ } = options;
+
+ return this.device.createTexture({
+ size: { width, height, depthOrArrayLayers: arrayLayerCount },
+ format,
+ mipLevelCount,
+ sampleCount,
+ usage,
+ });
+ }
+
+ getColorAttachment(
+ texture: GPUTexture,
+ textureViewDescriptor?: GPUTextureViewDescriptor
+ ): GPURenderPassColorAttachment {
+ const view = texture.createView(textureViewDescriptor);
+
+ return {
+ view,
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ };
+ }
+
+ getDepthStencilAttachment(
+ texture: GPUTexture,
+ textureViewDescriptor?: GPUTextureViewDescriptor
+ ): GPURenderPassDepthStencilAttachment {
+ const view = texture.createView(textureViewDescriptor);
+
+ return {
+ view,
+ depthClearValue: 1.0,
+ depthLoadOp: 'clear',
+ depthStoreOp: 'store',
+ stencilClearValue: 0,
+ stencilLoadOp: 'clear',
+ stencilStoreOp: 'store',
+ };
+ }
+
+ tryRenderPass(success: boolean, descriptor: GPURenderPassDescriptor): void {
+ const commandEncoder = this.device.createCommandEncoder();
+ const renderPass = commandEncoder.beginRenderPass(descriptor);
+ renderPass.end();
+
+ this.expectValidationError(() => {
+ commandEncoder.finish();
+ }, !success);
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('attachments,one_color_attachment')
+ .desc(`Test that a render pass works with only one color attachment.`)
+ .fn(t => {
+ const colorTexture = t.createTexture({ format: 'rgba8unorm' });
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture)],
+ };
+
+ t.tryRenderPass(true, descriptor);
+ });
+
+g.test('attachments,one_depth_stencil_attachment')
+ .desc(`Test that a render pass works with only one depthStencil attachment.`)
+ .fn(t => {
+ const depthStencilTexture = t.createTexture({ format: 'depth24plus-stencil8' });
+ const descriptor = {
+ colorAttachments: [],
+ depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture),
+ };
+
+ t.tryRenderPass(true, descriptor);
+ });
+
+g.test('color_attachments,empty')
+ .desc(
+ `
+ Test that when colorAttachments has all values be 'undefined' or the sequence is empty, the
+ depthStencilAttachment must not be 'undefined'.
+ `
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('colorAttachments', [
+ [],
+ [undefined],
+ [undefined, undefined],
+ new Array(8).fill(undefined),
+ [{ format: 'rgba8unorm' }],
+ ])
+ .combine('hasDepthStencilAttachment', [false, true])
+ )
+ .fn(async t => {
+ const { colorAttachments, hasDepthStencilAttachment } = t.params;
+
+ let isEmptyColorTargets = true;
+ for (let i = 0; i < colorAttachments.length; i++) {
+ if (colorAttachments[i] !== undefined) {
+ isEmptyColorTargets = false;
+ const colorTexture = t.createTexture();
+ colorAttachments[i] = t.getColorAttachment(colorTexture);
+ }
+ }
+
+ const _success = !isEmptyColorTargets || hasDepthStencilAttachment;
+ t.tryRenderPass(_success, {
+ colorAttachments,
+ depthStencilAttachment: hasDepthStencilAttachment
+ ? t.getDepthStencilAttachment(t.createTexture({ format: 'depth24plus-stencil8' }))
+ : undefined,
+ });
+ });
+
+g.test('color_attachments,limits,maxColorAttachments')
+ .desc(
+ `
+ Test that the out of bound of color attachment indexes are handled.
+ - a validation error is generated when color attachments exceed the maximum limit(8).
+ `
+ )
+ .paramsSimple([
+ { colorAttachmentsCount: 8, _success: true }, // Control case
+ { colorAttachmentsCount: 9, _success: false }, // Out of bounds
+ ])
+ .fn(async t => {
+ const { colorAttachmentsCount, _success } = t.params;
+
+ const colorAttachments = [];
+ for (let i = 0; i < colorAttachmentsCount; i++) {
+ const colorTexture = t.createTexture();
+ colorAttachments.push(t.getColorAttachment(colorTexture));
+ }
+
+ t.tryRenderPass(_success, { colorAttachments });
+ });
+
+g.test('color_attachments,limits,maxColorAttachmentBytesPerSample,aligned')
+ .desc(
+ `
+ Test that the total bytes per sample of the formats of the color attachments must be no greater
+ than maxColorAttachmentBytesPerSample when the components are aligned (same format).
+ `
+ )
+ .params(u =>
+ u
+ .combine('format', kRenderableColorTextureFormats)
+ .beginSubcases()
+ .combine(
+ 'attachmentCount',
+ range(kMaxColorAttachments, i => i + 1)
+ )
+ )
+ .fn(async t => {
+ const { format, attachmentCount } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const colorAttachments = [];
+ for (let i = 0; i < attachmentCount; i++) {
+ const colorTexture = t.createTexture();
+ colorAttachments.push(t.getColorAttachment(colorTexture));
+ }
+ const shouldError =
+ info.renderTargetPixelByteCost === undefined ||
+ info.renderTargetPixelByteCost * attachmentCount >
+ t.device.limits.maxColorAttachmentBytesPerSample;
+
+ t.tryRenderPass(!shouldError, { colorAttachments });
+ });
+
+g.test('color_attachments,limits,maxColorAttachmentBytesPerSample,unaligned')
+ .desc(
+ `
+ Test that the total bytes per sample of the formats of the color attachments must be no greater
+ than maxColorAttachmentBytesPerSample when the components are (potentially) unaligned.
+ `
+ )
+ .params(u =>
+ u.combineWithParams([
+ // Alignment causes the first 1 byte R8Unorm to become 4 bytes. So even though
+ // 1+4+8+16+1 < 32, the 4 byte alignment requirement of R32Float makes the first R8Unorm
+ // become 4 and 4+4+8+16+1 > 32. Re-ordering this so the R8Unorm's are at the end, however
+ // is allowed: 4+8+16+1+1 < 32.
+ {
+ formats: [
+ 'r8unorm',
+ 'r32float',
+ 'rgba8unorm',
+ 'rgba32float',
+ 'r8unorm',
+ ] as GPUTextureFormat[],
+ _success: true,
+ },
+ {
+ formats: [
+ 'r32float',
+ 'rgba8unorm',
+ 'rgba32float',
+ 'r8unorm',
+ 'r8unorm',
+ ] as GPUTextureFormat[],
+ _success: false,
+ },
+ ])
+ )
+ .fn(async t => {
+ const { formats, _success } = t.params;
+
+ const colorAttachments = [];
+ for (const format of formats) {
+ const colorTexture = t.createTexture({ format });
+ colorAttachments.push(t.getColorAttachment(colorTexture));
+ }
+ t.tryRenderPass(_success, { colorAttachments });
+ });
+
+g.test('attachments,same_size')
+ .desc(
+ `
+ Test that attachments have the same size. Otherwise, a validation error should be generated.
+ - Succeed if all attachments have the same size.
+ - Fail if one of the color attachments has a different size.
+ - Fail if the depth stencil attachment has a different size.
+ `
+ )
+ .fn(async t => {
+ const colorTexture1x1A = t.createTexture({ width: 1, height: 1, format: 'rgba8unorm' });
+ const colorTexture1x1B = t.createTexture({ width: 1, height: 1, format: 'rgba8unorm' });
+ const colorTexture2x2 = t.createTexture({ width: 2, height: 2, format: 'rgba8unorm' });
+ const depthStencilTexture1x1 = t.createTexture({
+ width: 1,
+ height: 1,
+ format: 'depth24plus-stencil8',
+ });
+ const depthStencilTexture2x2 = t.createTexture({
+ width: 2,
+ height: 2,
+ format: 'depth24plus-stencil8',
+ });
+
+ {
+ // Control case: all the same size (1x1)
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [
+ t.getColorAttachment(colorTexture1x1A),
+ t.getColorAttachment(colorTexture1x1B),
+ ],
+ depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture1x1),
+ };
+
+ t.tryRenderPass(true, descriptor);
+ }
+ {
+ // One of the color attachments has a different size
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [
+ t.getColorAttachment(colorTexture1x1A),
+ t.getColorAttachment(colorTexture2x2),
+ ],
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+ {
+ // The depth stencil attachment has a different size
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [
+ t.getColorAttachment(colorTexture1x1A),
+ t.getColorAttachment(colorTexture1x1B),
+ ],
+ depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture2x2),
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+ });
+
+g.test('attachments,color_depth_mismatch')
+ .desc(`Test that attachments match whether they are used for color or depth stencil.`)
+ .fn(async t => {
+ const colorTexture = t.createTexture({ format: 'rgba8unorm' });
+ const depthStencilTexture = t.createTexture({ format: 'depth24plus-stencil8' });
+
+ {
+ // Using depth-stencil for color
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [t.getColorAttachment(depthStencilTexture)],
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+ {
+ // Using color for depth-stencil
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [],
+ depthStencilAttachment: t.getDepthStencilAttachment(colorTexture),
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+ });
+
+g.test('attachments,layer_count')
+ .desc(
+ `
+ Test the layer counts for color or depth stencil.
+ - Fail if using 2D array texture view with arrayLayerCount > 1.
+ - Succeed if using 2D array texture view that covers the first layer of the texture.
+ - Succeed if using 2D array texture view that covers the last layer for depth stencil.
+ `
+ )
+ .paramsSimple([
+ { arrayLayerCount: 5, baseArrayLayer: 0, _success: false },
+ { arrayLayerCount: 1, baseArrayLayer: 0, _success: true },
+ { arrayLayerCount: 1, baseArrayLayer: 9, _success: true },
+ ])
+ .fn(async t => {
+ const { arrayLayerCount, baseArrayLayer, _success } = t.params;
+
+ const ARRAY_LAYER_COUNT = 10;
+ const MIP_LEVEL_COUNT = 1;
+ const COLOR_FORMAT = 'rgba8unorm';
+ const DEPTH_STENCIL_FORMAT = 'depth24plus-stencil8';
+
+ const colorTexture = t.createTexture({
+ format: COLOR_FORMAT,
+ width: 32,
+ height: 32,
+ mipLevelCount: MIP_LEVEL_COUNT,
+ arrayLayerCount: ARRAY_LAYER_COUNT,
+ });
+ const depthStencilTexture = t.createTexture({
+ format: DEPTH_STENCIL_FORMAT,
+ width: 32,
+ height: 32,
+ mipLevelCount: MIP_LEVEL_COUNT,
+ arrayLayerCount: ARRAY_LAYER_COUNT,
+ });
+
+ const baseTextureViewDescriptor: GPUTextureViewDescriptor = {
+ dimension: '2d-array',
+ baseArrayLayer,
+ arrayLayerCount,
+ baseMipLevel: 0,
+ mipLevelCount: MIP_LEVEL_COUNT,
+ };
+
+ {
+ // Check 2D array texture view for color
+ const textureViewDescriptor: GPUTextureViewDescriptor = {
+ ...baseTextureViewDescriptor,
+ format: COLOR_FORMAT,
+ };
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture, textureViewDescriptor)],
+ };
+
+ t.tryRenderPass(_success, descriptor);
+ }
+ {
+ // Check 2D array texture view for depth stencil
+ const textureViewDescriptor: GPUTextureViewDescriptor = {
+ ...baseTextureViewDescriptor,
+ format: DEPTH_STENCIL_FORMAT,
+ };
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [],
+ depthStencilAttachment: t.getDepthStencilAttachment(
+ depthStencilTexture,
+ textureViewDescriptor
+ ),
+ };
+
+ t.tryRenderPass(_success, descriptor);
+ }
+ });
+
+g.test('attachments,mip_level_count')
+ .desc(
+ `
+ Test the mip level count for color or depth stencil.
+ - Fail if using 2D texture view with mipLevelCount > 1.
+ - Succeed if using 2D texture view that covers the first level of the texture.
+ - Succeed if using 2D texture view that covers the last level of the texture.
+ `
+ )
+ .paramsSimple([
+ { mipLevelCount: 2, baseMipLevel: 0, _success: false },
+ { mipLevelCount: 1, baseMipLevel: 0, _success: true },
+ { mipLevelCount: 1, baseMipLevel: 3, _success: true },
+ ])
+ .fn(async t => {
+ const { mipLevelCount, baseMipLevel, _success } = t.params;
+
+ const ARRAY_LAYER_COUNT = 1;
+ const MIP_LEVEL_COUNT = 4;
+ const COLOR_FORMAT = 'rgba8unorm';
+ const DEPTH_STENCIL_FORMAT = 'depth24plus-stencil8';
+
+ const colorTexture = t.createTexture({
+ format: COLOR_FORMAT,
+ width: 32,
+ height: 32,
+ mipLevelCount: MIP_LEVEL_COUNT,
+ arrayLayerCount: ARRAY_LAYER_COUNT,
+ });
+ const depthStencilTexture = t.createTexture({
+ format: DEPTH_STENCIL_FORMAT,
+ width: 32,
+ height: 32,
+ mipLevelCount: MIP_LEVEL_COUNT,
+ arrayLayerCount: ARRAY_LAYER_COUNT,
+ });
+
+ const baseTextureViewDescriptor: GPUTextureViewDescriptor = {
+ dimension: '2d',
+ baseArrayLayer: 0,
+ arrayLayerCount: ARRAY_LAYER_COUNT,
+ baseMipLevel,
+ mipLevelCount,
+ };
+
+ {
+ // Check 2D texture view for color
+ const textureViewDescriptor: GPUTextureViewDescriptor = {
+ ...baseTextureViewDescriptor,
+ format: COLOR_FORMAT,
+ };
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture, textureViewDescriptor)],
+ };
+
+ t.tryRenderPass(_success, descriptor);
+ }
+ {
+ // Check 2D texture view for depth stencil
+ const textureViewDescriptor: GPUTextureViewDescriptor = {
+ ...baseTextureViewDescriptor,
+ format: DEPTH_STENCIL_FORMAT,
+ };
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [],
+ depthStencilAttachment: t.getDepthStencilAttachment(
+ depthStencilTexture,
+ textureViewDescriptor
+ ),
+ };
+
+ t.tryRenderPass(_success, descriptor);
+ }
+ });
+
+g.test('color_attachments,non_multisampled')
+ .desc(
+ `
+ Test that setting a resolve target is invalid if the color attachments is non multisampled.
+ `
+ )
+ .fn(async t => {
+ const colorTexture = t.createTexture({ sampleCount: 1 });
+ const resolveTargetTexture = t.createTexture({ sampleCount: 1 });
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [
+ {
+ view: colorTexture.createView(),
+ resolveTarget: resolveTargetTexture.createView(),
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ };
+
+ t.tryRenderPass(false, descriptor);
+ });
+
+g.test('color_attachments,sample_count')
+ .desc(
+ `
+ Test the usages of multisampled textures for color attachments.
+ - Succeed if using a multisampled color attachment without setting a resolve target.
+ - Fail if using multiple color attachments with different sample counts.
+ `
+ )
+ .fn(async t => {
+ const colorTexture = t.createTexture({ sampleCount: 1 });
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+
+ {
+ // It is allowed to use a multisampled color attachment without setting resolve target
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [t.getColorAttachment(multisampledColorTexture)],
+ };
+ t.tryRenderPass(true, descriptor);
+ }
+ {
+ // It is not allowed to use multiple color attachments with different sample counts
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [
+ t.getColorAttachment(colorTexture),
+ t.getColorAttachment(multisampledColorTexture),
+ ],
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+ });
+
+g.test('resolveTarget,sample_count')
+ .desc(
+ `
+ Test that using multisampled resolve target is invalid for color attachments.
+ `
+ )
+ .fn(async t => {
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const multisampledResolveTargetTexture = t.createTexture({ sampleCount: 4 });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = multisampledResolveTargetTexture.createView();
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [colorAttachment],
+ };
+
+ t.tryRenderPass(false, descriptor);
+ });
+
+g.test('resolveTarget,array_layer_count')
+ .desc(
+ `
+ Test that using a resolve target with array layer count is greater than 1 is invalid for color
+ attachments.
+ `
+ )
+ .fn(async t => {
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const resolveTargetTexture = t.createTexture({ arrayLayerCount: 2 });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTargetTexture.createView({ dimension: '2d-array' });
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [colorAttachment],
+ };
+
+ t.tryRenderPass(false, descriptor);
+ });
+
+g.test('resolveTarget,mipmap_level_count')
+ .desc(
+ `
+ Test that using a resolve target with that mipmap level count is greater than 1 is invalid for
+ color attachments.
+ `
+ )
+ .fn(async t => {
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const resolveTargetTexture = t.createTexture({ mipLevelCount: 2 });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTargetTexture.createView();
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [colorAttachment],
+ };
+
+ t.tryRenderPass(false, descriptor);
+ });
+
+g.test('resolveTarget,usage')
+ .desc(
+ `
+ Test that using a resolve target whose usage is not RENDER_ATTACHMENT is invalid for color
+ attachments.
+ `
+ )
+ .paramsSimple([
+ { usage: GPUConst.TextureUsage.COPY_SRC | GPUConst.TextureUsage.COPY_DST },
+ { usage: GPUConst.TextureUsage.STORAGE_BINDING | GPUConst.TextureUsage.TEXTURE_BINDING },
+ { usage: GPUConst.TextureUsage.STORAGE_BINDING | GPUConst.TextureUsage.STORAGE },
+ { usage: GPUConst.TextureUsage.RENDER_ATTACHMENT | GPUConst.TextureUsage.TEXTURE_BINDING },
+ ])
+ .fn(async t => {
+ const { usage } = t.params;
+
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const resolveTargetTexture = t.createTexture({ usage });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTargetTexture.createView();
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [colorAttachment],
+ };
+
+ const isValid = usage & GPUConst.TextureUsage.RENDER_ATTACHMENT ? true : false;
+ t.tryRenderPass(isValid, descriptor);
+ });
+
+g.test('resolveTarget,error_state')
+ .desc(`Test that a resolve target that has a error is invalid for color attachments.`)
+ .fn(async t => {
+ const ARRAY_LAYER_COUNT = 1;
+
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const resolveTargetTexture = t.createTexture({ arrayLayerCount: ARRAY_LAYER_COUNT });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ t.expectValidationError(() => {
+ colorAttachment.resolveTarget = resolveTargetTexture.createView({
+ dimension: '2d',
+ format: 'rgba8unorm',
+ baseArrayLayer: ARRAY_LAYER_COUNT + 1,
+ });
+ });
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [colorAttachment],
+ };
+
+ t.tryRenderPass(false, descriptor);
+ });
+
+g.test('resolveTarget,single_sample_count')
+ .desc(
+ `
+ Test that a resolve target that has multi sample color attachment and a single resolve target is
+ valid.
+ `
+ )
+ .fn(async t => {
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const resolveTargetTexture = t.createTexture({ sampleCount: 1 });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTargetTexture.createView();
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [colorAttachment],
+ };
+
+ t.tryRenderPass(true, descriptor);
+ });
+
+g.test('resolveTarget,different_format')
+ .desc(`Test that a resolve target that has a different format is invalid.`)
+ .fn(async t => {
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const resolveTargetTexture = t.createTexture({ format: 'bgra8unorm' });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTargetTexture.createView();
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [colorAttachment],
+ };
+
+ t.tryRenderPass(false, descriptor);
+ });
+
+g.test('resolveTarget,different_size')
+ .desc(
+ `
+ Test that a resolve target that has a different size with the color attachment is invalid.
+ `
+ )
+ .fn(async t => {
+ const size = 16;
+ const multisampledColorTexture = t.createTexture({ width: size, height: size, sampleCount: 4 });
+ const resolveTargetTexture = t.createTexture({
+ width: size * 2,
+ height: size * 2,
+ mipLevelCount: 2,
+ });
+
+ {
+ const resolveTargetTextureView = resolveTargetTexture.createView({
+ baseMipLevel: 0,
+ mipLevelCount: 1,
+ });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTargetTextureView;
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [colorAttachment],
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+ {
+ const resolveTargetTextureView = resolveTargetTexture.createView({ baseMipLevel: 1 });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTargetTextureView;
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [colorAttachment],
+ };
+
+ t.tryRenderPass(true, descriptor);
+ }
+ });
+
+g.test('depth_stencil_attachment,sample_counts_mismatch')
+ .desc(
+ `
+ Test that the depth stencil attachment that has different number of samples with the color
+ attachment is invalid.
+ `
+ )
+ .fn(async t => {
+ const multisampledDepthStencilTexture = t.createTexture({
+ sampleCount: 4,
+ format: 'depth24plus-stencil8',
+ });
+
+ {
+ // It is not allowed to use a depth stencil attachment whose sample count is different from
+ // the one of the color attachment.
+ const depthStencilTexture = t.createTexture({
+ sampleCount: 1,
+ format: 'depth24plus-stencil8',
+ });
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [t.getColorAttachment(multisampledColorTexture)],
+ depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture),
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+ {
+ const colorTexture = t.createTexture({ sampleCount: 1 });
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture)],
+ depthStencilAttachment: t.getDepthStencilAttachment(multisampledDepthStencilTexture),
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+ {
+ // It is allowed to use a multisampled depth stencil attachment whose sample count is equal to
+ // the one of the color attachment.
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [t.getColorAttachment(multisampledColorTexture)],
+ depthStencilAttachment: t.getDepthStencilAttachment(multisampledDepthStencilTexture),
+ };
+
+ t.tryRenderPass(true, descriptor);
+ }
+ {
+ // It is allowed to use a multisampled depth stencil attachment with no color attachment.
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [],
+ depthStencilAttachment: t.getDepthStencilAttachment(multisampledDepthStencilTexture),
+ };
+
+ t.tryRenderPass(true, descriptor);
+ }
+ });
+
+g.test('depth_stencil_attachment')
+ .desc(
+ `
+ Test GPURenderPassDepthStencilAttachment Usage:
+ - depthReadOnly and stencilReadOnly must match if the format is a combined depth-stencil format.
+ - depthLoadOp and depthStoreOp must be provided iff the format has a depth aspect and
+ depthReadOnly is not true.
+ - stencilLoadOp and stencilStoreOp must be provided iff the format has a stencil aspect and
+ stencilReadOnly is not true.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('format', kDepthStencilFormats)
+ .beginSubcases()
+ .combine('depthReadOnly', [false, true])
+ .combine('stencilReadOnly', [false, true])
+ .combine('setDepthLoadStoreOp', [false, true])
+ .combine('setStencilLoadStoreOp', [false, true])
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+ })
+ .fn(async t => {
+ const {
+ format,
+ depthReadOnly,
+ stencilReadOnly,
+ setDepthLoadStoreOp,
+ setStencilLoadStoreOp,
+ } = t.params;
+
+ let isValid = true;
+ const info = kTextureFormatInfo[format];
+ if (info.depth && info.stencil) {
+ isValid &&= depthReadOnly === stencilReadOnly;
+ }
+
+ if (info.depth && !depthReadOnly) {
+ isValid &&= setDepthLoadStoreOp;
+ } else {
+ isValid &&= !setDepthLoadStoreOp;
+ }
+
+ if (info.stencil && !stencilReadOnly) {
+ isValid &&= setStencilLoadStoreOp;
+ } else {
+ isValid &&= !setStencilLoadStoreOp;
+ }
+
+ const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
+ view: t.createTexture({ format }).createView(),
+ depthReadOnly,
+ stencilReadOnly,
+ };
+
+ if (setDepthLoadStoreOp) {
+ depthStencilAttachment.depthLoadOp = 'clear';
+ depthStencilAttachment.depthStoreOp = 'store';
+ }
+ if (setStencilLoadStoreOp) {
+ depthStencilAttachment.stencilLoadOp = 'clear';
+ depthStencilAttachment.stencilStoreOp = 'store';
+ }
+
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(t.createTexture())],
+ depthStencilAttachment,
+ };
+
+ t.tryRenderPass(isValid, descriptor);
+ });
+
+g.test('depth_stencil_attachment,depth_clear_value')
+ .desc(
+ `
+ Test that depthClearValue is invalid if the value is out of the range(0.0 and 1.0) only when
+ depthLoadOp is 'clear'.
+ `
+ )
+ .params(u =>
+ u
+ .combine('depthLoadOp', ['load', 'clear', undefined] as const)
+ .combineWithParams([
+ { depthClearValue: -1.0 },
+ { depthClearValue: 0.0 },
+ { depthClearValue: 0.5 },
+ { depthClearValue: 1.0 },
+ { depthClearValue: 1.5 },
+ ])
+ )
+ .fn(t => {
+ const { depthLoadOp, depthClearValue } = t.params;
+
+ const depthStencilTexture = t.createTexture({
+ format: depthLoadOp === undefined ? 'stencil8' : 'depth24plus-stencil8',
+ });
+ const depthStencilAttachment = t.getDepthStencilAttachment(depthStencilTexture);
+ depthStencilAttachment.depthClearValue = depthClearValue;
+ depthStencilAttachment.depthLoadOp = depthLoadOp;
+ if (depthLoadOp === undefined) {
+ depthStencilAttachment.depthStoreOp = undefined;
+ }
+
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(t.createTexture())],
+ depthStencilAttachment,
+ };
+
+ const isValid = !(depthLoadOp === 'clear' && (depthClearValue < 0.0 || depthClearValue > 1.0));
+
+ t.tryRenderPass(isValid, descriptor);
+ });
+
+g.test('resolveTarget,format_supports_resolve')
+ .desc(
+ `
+ For all formats that support 'multisample', test that they can be used as a resolveTarget
+ if and only if they support 'resolve'.
+ `
+ )
+ .params(u =>
+ u
+ .combine('format', kRenderableColorTextureFormats)
+ .filter(t => kTextureFormatInfo[t.format].multisample)
+ )
+ .fn(async t => {
+ const { format } = t.params;
+ const multisampledColorTexture = t.createTexture({ format, sampleCount: 4 });
+ const resolveTarget = t.createTexture({ format });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTarget.createView();
+
+ t.tryRenderPass(kTextureFormatInfo[format].resolve, {
+ colorAttachments: [colorAttachment],
+ });
+ });
+
+g.test('timestampWrites,query_set_type')
+ .desc(
+ `
+ Test that all entries of the timestampWrites must have type 'timestamp'. If all query types are
+ not 'timestamp', a validation error should be generated.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('queryTypeA', kQueryTypes)
+ .combine('queryTypeB', kQueryTypes)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase(['timestamp-query']);
+ })
+ .fn(async t => {
+ const { queryTypeA, queryTypeB } = t.params;
+
+ const timestampWriteA = {
+ querySet: t.device.createQuerySet({ type: queryTypeA, count: 1 }),
+ queryIndex: 0,
+ location: 'beginning' as const,
+ };
+
+ const timestampWriteB = {
+ querySet: t.device.createQuerySet({ type: queryTypeB, count: 1 }),
+ queryIndex: 0,
+ location: 'end' as const,
+ };
+
+ const isValid = queryTypeA === 'timestamp' && queryTypeB === 'timestamp';
+
+ const colorTexture = t.createTexture();
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture)],
+ timestampWrites: [timestampWriteA, timestampWriteB],
+ };
+
+ t.tryRenderPass(isValid, descriptor);
+ });
+
+g.test('timestamp_writes_location')
+ .desc('Test that entries in timestampWrites do not have the same location.')
+ .params(u =>
+ u //
+ .combine('locationA', ['beginning', 'end'] as const)
+ .combine('locationB', ['beginning', 'end'] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase(['timestamp-query']);
+ })
+ .fn(async t => {
+ const { locationA, locationB } = t.params;
+
+ const querySet = t.device.createQuerySet({
+ type: 'timestamp',
+ count: 2,
+ });
+
+ const timestampWriteA = {
+ querySet,
+ queryIndex: 0,
+ location: locationA,
+ };
+
+ const timestampWriteB = {
+ querySet,
+ queryIndex: 1,
+ location: locationB,
+ };
+
+ const isValid = locationA !== locationB;
+
+ const colorTexture = t.createTexture({ format: 'rgba8unorm' });
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture)],
+ timestampWrites: [timestampWriteA, timestampWriteB],
+ };
+
+ t.tryRenderPass(isValid, descriptor);
+ });
+
+g.test('timestampWrite,query_index')
+ .desc(`Test that querySet.count should be greater than timestampWrite.queryIndex.`)
+ .params(u => u.combine('queryIndex', [0, 1, 2, 3]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase(['timestamp-query']);
+ })
+ .fn(async t => {
+ const { queryIndex } = t.params;
+
+ const querySetCount = 2;
+
+ const timestampWrite = {
+ querySet: t.device.createQuerySet({ type: 'timestamp', count: querySetCount }),
+ queryIndex,
+ location: 'beginning' as const,
+ };
+
+ const isValid = queryIndex < querySetCount;
+
+ const colorTexture = t.createTexture();
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture)],
+ timestampWrites: [timestampWrite],
+ };
+
+ t.tryRenderPass(isValid, descriptor);
+ });
+
+g.test('timestampWrite,same_query_index')
+ .desc(
+ `
+ Test that timestampWrites is invalid if each entry has the same queryIndex in the same querySet.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('queryIndexA', [0, 1])
+ .combine('queryIndexB', [0, 1])
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase(['timestamp-query']);
+ })
+ .fn(async t => {
+ const { queryIndexA, queryIndexB } = t.params;
+
+ const querySet = t.device.createQuerySet({
+ type: 'timestamp',
+ count: 2,
+ });
+
+ const timestampWriteA = {
+ querySet,
+ queryIndex: queryIndexA,
+ location: 'beginning' as const,
+ };
+
+ const timestampWriteB = {
+ querySet,
+ queryIndex: queryIndexB,
+ location: 'end' as const,
+ };
+
+ const isValid = queryIndexA !== queryIndexB;
+
+ const colorTexture = t.createTexture();
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture)],
+ timestampWrites: [timestampWriteA, timestampWriteB],
+ };
+
+ t.tryRenderPass(isValid, descriptor);
+ });
+
+g.test('occlusionQuerySet,query_set_type')
+ .desc(`Test that occlusionQuerySet must have type 'occlusion'.`)
+ .params(u => u.combine('queryType', kQueryTypes))
+ .beforeAllSubcases(t => {
+ if (t.params.queryType === 'timestamp') {
+ t.selectDeviceOrSkipTestCase(['timestamp-query']);
+ }
+ })
+ .fn(async t => {
+ const { queryType } = t.params;
+
+ const querySet = t.device.createQuerySet({
+ type: queryType,
+ count: 1,
+ });
+
+ const colorTexture = t.createTexture();
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture)],
+ occlusionQuerySet: querySet,
+ };
+
+ const isValid = queryType === 'occlusion';
+ t.tryRenderPass(isValid, descriptor);
+ });