1export const description = `
2createBindGroup validation tests.
3`;
4
5import { poptions, params } from '../../../common/framework/params_builder.js';
6import { makeTestGroup } from '../../../common/framework/test_group.js';
7import { unreachable } from '../../../common/framework/util/util.js';
8import {
9  kBindingTypes,
10  kBindingTypeInfo,
11  kBindableResources,
12  kTextureUsages,
13  kTextureBindingTypes,
14  kTextureBindingTypeInfo,
15} from '../../capability_info.js';
16
17import { ValidationTest } from './validation_test.js';
18
19function clone<T extends GPUTextureDescriptor>(descriptor: T): T {
20  return JSON.parse(JSON.stringify(descriptor));
21}
22
23export const g = makeTestGroup(ValidationTest);
24
25g.test('binding_count_mismatch').fn(async t => {
26  const bindGroupLayout = t.device.createBindGroupLayout({
27    entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' }],
28  });
29
30  const goodDescriptor = {
31    entries: [{ binding: 0, resource: { buffer: t.getStorageBuffer() } }],
32    layout: bindGroupLayout,
33  };
34
35  // Control case
36  t.device.createBindGroup(goodDescriptor);
37
38  // Another binding is not expected.
39  const badDescriptor = {
40    entries: [
41      { binding: 0, resource: { buffer: t.getStorageBuffer() } },
42      // Another binding is added.
43      { binding: 1, resource: { buffer: t.getStorageBuffer() } },
44    ],
45    layout: bindGroupLayout,
46  };
47
48  t.expectValidationError(() => {
49    t.device.createBindGroup(badDescriptor);
50  });
51});
52
53g.test('binding_must_be_present_in_layout').fn(async t => {
54  const bindGroupLayout = t.device.createBindGroupLayout({
55    entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' }],
56  });
57
58  const goodDescriptor = {
59    entries: [{ binding: 0, resource: { buffer: t.getStorageBuffer() } }],
60    layout: bindGroupLayout,
61  };
62
63  // Control case
64  t.device.createBindGroup(goodDescriptor);
65
66  // Binding index 0 must be present.
67  const badDescriptor = {
68    entries: [{ binding: 1, resource: { buffer: t.getStorageBuffer() } }],
69    layout: bindGroupLayout,
70  };
71
72  t.expectValidationError(() => {
73    t.device.createBindGroup(badDescriptor);
74  });
75});
76
77g.test('buffer_binding_must_contain_exactly_one_buffer_of_its_type')
78  .params(
79    params()
80      .combine(poptions('bindingType', kBindingTypes))
81      .combine(poptions('resourceType', kBindableResources))
82  )
83  .fn(t => {
84    const { bindingType, resourceType } = t.params;
85    const info = kBindingTypeInfo[bindingType];
86
87    const storageTextureFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined;
88    const layout = t.device.createBindGroupLayout({
89      entries: [
90        { binding: 0, visibility: GPUShaderStage.COMPUTE, type: bindingType, storageTextureFormat },
91      ],
92    });
93
94    const resource = t.getBindingResource(resourceType);
95
96    const resourceBindingMatches = info.resource === resourceType;
97    t.expectValidationError(() => {
98      t.device.createBindGroup({ layout, entries: [{ binding: 0, resource }] });
99    }, !resourceBindingMatches);
100  });
101
102g.test('texture_binding_must_have_correct_usage')
103  .params(
104    params()
105      .combine(poptions('type', kTextureBindingTypes))
106      .combine(poptions('usage', kTextureUsages))
107  )
108  .fn(async t => {
109    const { type, usage } = t.params;
110    const info = kTextureBindingTypeInfo[type];
111
112    const storageTextureFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined;
113    const bindGroupLayout = t.device.createBindGroupLayout({
114      entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, type, storageTextureFormat }],
115    });
116
117    const descriptor = {
118      size: { width: 16, height: 16, depth: 1 },
119      format: 'rgba8unorm' as const,
120      usage,
121    };
122
123    const shouldError = usage !== info.usage;
124    t.expectValidationError(() => {
125      t.device.createBindGroup({
126        entries: [{ binding: 0, resource: t.device.createTexture(descriptor).createView() }],
127        layout: bindGroupLayout,
128      });
129    }, shouldError);
130  });
131
132g.test('texture_must_have_correct_component_type')
133  .params(poptions('textureComponentType', ['float', 'sint', 'uint'] as const))
134  .fn(async t => {
135    const { textureComponentType } = t.params;
136
137    const bindGroupLayout = t.device.createBindGroupLayout({
138      entries: [
139        {
140          binding: 0,
141          visibility: GPUShaderStage.FRAGMENT,
142          type: 'sampled-texture',
143          textureComponentType,
144        },
145      ],
146    });
147
148    // TODO: Test more texture component types.
149    let format: GPUTextureFormat;
150    if (textureComponentType === 'float') {
151      format = 'r8unorm';
152    } else if (textureComponentType === 'sint') {
153      format = 'r8sint';
154    } else if (textureComponentType === 'uint') {
155      format = 'r8uint';
156    } else {
157      unreachable('Unexpected texture component type');
158    }
159
160    const goodDescriptor = {
161      size: { width: 16, height: 16, depth: 1 },
162      format,
163      usage: GPUTextureUsage.SAMPLED,
164    };
165
166    // Control case
167    t.device.createBindGroup({
168      entries: [
169        {
170          binding: 0,
171          resource: t.device.createTexture(goodDescriptor).createView(),
172        },
173      ],
174      layout: bindGroupLayout,
175    });
176
177    function* mismatchedTextureFormats(): Iterable<GPUTextureFormat> {
178      if (textureComponentType !== 'float') {
179        yield 'r8unorm';
180      }
181      if (textureComponentType !== 'sint') {
182        yield 'r8sint';
183      }
184      if (textureComponentType !== 'uint') {
185        yield 'r8uint';
186      }
187    }
188
189    // Mismatched texture binding formats are not valid.
190    for (const mismatchedTextureFormat of mismatchedTextureFormats()) {
191      const badDescriptor: GPUTextureDescriptor = clone(goodDescriptor);
192      badDescriptor.format = mismatchedTextureFormat;
193
194      t.expectValidationError(() => {
195        t.device.createBindGroup({
196          entries: [{ binding: 0, resource: t.device.createTexture(badDescriptor).createView() }],
197          layout: bindGroupLayout,
198        });
199      });
200    }
201  });
202
203// TODO: Write test for all dimensions.
204g.test('texture_must_have_correct_dimension').fn(async t => {
205  const bindGroupLayout = t.device.createBindGroupLayout({
206    entries: [
207      {
208        binding: 0,
209        visibility: GPUShaderStage.FRAGMENT,
210        type: 'sampled-texture',
211        viewDimension: '2d',
212      },
213    ],
214  });
215
216  const goodDescriptor = {
217    size: { width: 16, height: 16, depth: 1 },
218    format: 'rgba8unorm' as const,
219    usage: GPUTextureUsage.SAMPLED,
220  };
221
222  // Control case
223  t.device.createBindGroup({
224    entries: [{ binding: 0, resource: t.device.createTexture(goodDescriptor).createView() }],
225    layout: bindGroupLayout,
226  });
227
228  // Mismatched texture binding formats are not valid.
229  const badDescriptor = clone(goodDescriptor);
230  badDescriptor.size.depth = 2;
231
232  t.expectValidationError(() => {
233    t.device.createBindGroup({
234      entries: [{ binding: 0, resource: t.device.createTexture(badDescriptor).createView() }],
235      layout: bindGroupLayout,
236    });
237  });
238});
239
240g.test('buffer_offset_and_size_for_bind_groups_match')
241  .params([
242    { offset: 0, size: 512, _success: true }, // offset 0 is valid
243    { offset: 256, size: 256, _success: true }, // offset 256 (aligned) is valid
244
245    // Touching the end of the buffer
246    { offset: 0, size: 1024, _success: true },
247    { offset: 0, size: undefined, _success: true },
248    { offset: 256 * 3, size: 256, _success: true },
249    { offset: 256 * 3, size: undefined, _success: true },
250
251    // Zero-sized bindings
252    { offset: 0, size: 0, _success: true },
253    { offset: 256, size: 0, _success: true },
254    { offset: 1024, size: 0, _success: true },
255    { offset: 1024, size: undefined, _success: true },
256
257    // Unaligned buffer offset is invalid
258    { offset: 1, size: 256, _success: false },
259    { offset: 1, size: undefined, _success: false },
260    { offset: 128, size: 256, _success: false },
261    { offset: 255, size: 256, _success: false },
262
263    // Out-of-bounds
264    { offset: 256 * 5, size: 0, _success: false }, // offset is OOB
265    { offset: 0, size: 256 * 5, _success: false }, // size is OOB
266    { offset: 1024, size: 1, _success: false }, // offset+size is OOB
267  ])
268  .fn(async t => {
269    const { offset, size, _success } = t.params;
270
271    const bindGroupLayout = t.device.createBindGroupLayout({
272      entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' }],
273    });
274
275    const buffer = t.device.createBuffer({
276      size: 1024,
277      usage: GPUBufferUsage.STORAGE,
278    });
279
280    const descriptor = {
281      entries: [
282        {
283          binding: 0,
284          resource: { buffer, offset, size },
285        },
286      ],
287      layout: bindGroupLayout,
288    };
289
290    if (_success) {
291      // Control case
292      t.device.createBindGroup(descriptor);
293    } else {
294      // Buffer offset and/or size don't match in bind groups.
295      t.expectValidationError(() => {
296        t.device.createBindGroup(descriptor);
297      });
298    }
299  });
300