Skip to content

Commit 61eae08

Browse files
Add texture-component-swizzle tests (#4427)
* Add texture-component-swizzle tests I removed the 3 stages and am only testing the fragment stage. As for speed, with `debug` false it runs fast enough as is I think. About 25 seconds for all tests on M1 Mac. Note: One issue I ran into is the code that created textures would potentially create them with RENDER_ATTACHMENT if they needed to be rendered to to fill. A texture created with RENDER_ATTACHMENT can't be swizzled. My solution was to change the code that makes the texture such that, if the texture had RENDER_ATTACHMENT added, and it can be copied texture to texture, then make a copy without RENDER_ATTACHMENT Another issue I ran into and I'm not sure what solution is better, is testing depth-stencil formats. The existing code is not designed for that. All the depth-stencil formats are "unencodable". My solution was createTextureWithRandomDataAndGetTexelsForEachAspect that returns 2 sets of TexelViews, one for the depth aspect, another for the stencil aspec. It's possible, maybe we should make those formats encodable as Texels? I didn't chose that becuase depth-stencil has 2 types, f32 for depth, u32 for stencil. This seems like it would cause issues all over the place where code expects just one type. Another issue is depth24plus-stencil, the depth part is undefined. In the code, createTextureWithRandomDataAndGetTexelsForEachAspect on a depth format returns a TexelView with float data, not depth24plus data. I'm not sure all the encoders etc would handle this. So, like I mentioned above, my solution was to return 2 sets of TexelViews, one for the depth aspect and one for the stenci aspect. The code then choose one based on which aspect it's testing. * Move texture swizzle operation tests under api/operations * skip non-matching swizzle operation tests in compat mode * Test that mismatching swizzles cause validation error in compat mode * Skip identity validation on browsers that have no yet implemented this * Fix textureLoad test for multisampled textures Multisampled textures are required to have RENDER_ATTACHMENT usage. This was working by accident as `createTextureWithRandopmDataAndGetTexels` as adding the RENDER_ATTACHMENT usage. That usage addition is removed in this PR so the bug that it was not added here surfaced. * More adjustment for texture-utils issues. Originally the texture-utils made textures filled with random data based on a texture descriptor. If the texture format was depth or stencil they needed to render to the texture to get the data in since some formats do not support writeTexture nor copyBufferToTexture. To do this they needed to add RENDER_ATTACHMENT. But, texture-component-swizzle requires that RENDER_ATTACHMENT not be set. So, a check was added, if the texture created had texture.usage different than was originally requested then, make a new texture without those added usages and copy the texture into this new texture. 🎉 But, there were a few tests, like textureLoad multisample and texture_utils that happened to be passing by accident. They were testing multisample. Multisampled texture require RENDER_ATTACHMENT. They were not requesting a texture with RENDER_ATTACHMENT but were instead getting one by luck on the old path when RENDER_ATTACHMENT was magically added when needed. With the fix to remove RENDER_ATTACHMENT when it was not requested, these tests started failing. On top of that, the texture-component-swizzle tests test both the depth and stencil aspect of depth-stencil textures. Note that the WGSL builtin texture tests do not (yet?) do this. Adding the path to upload data for both depth AND stencil data broke because the copy above happened in the wrong place. The old broken path was 1. make texture with RENDER_ATTACHMENT 2. use copy texel data via render to fill depth data 3. make copy of texture without RENDER_ATTACHMENT 4. use copy texel data via render to fill stencil data (ERROR!) You can't fill the stencil via render if RENDER_ATTACHMENT is removed. The fix was to refactor so it's now effectively 1. make texture with RENDER_ATTACHMENT 2. use copy texel data via render to fill depth data 3. use copy texel data via render to fill stencil data 4. make copy of texture without RENDER_ATTACHMENT * Add more compat limitation checks 1. `textureLoad` can not be uses with `texture_depth_2d` 2. `texture_depth_2d` can not be used with non-comparison samplers * Check the correct usages when deciding to make a copy. The code was checking COPY_SRC when it shouldn't have * Switching to view.usage to validate bad usages This was using texture.usage which was the wrong place to check. Switching to view.usage lets us test multisampled textures. * Test multisampled textures * Expect depth textures to return D,0,0,1 Note: this changes both the texture-component-swizzle tests and the general WGSL texture builtin tests so that if texture-component-swizzle is enabled, the WGSL texture builtin tests will expect D,0,0,1 instead of D,?,?,? * Don't Copy Textures to remove RENDER_ATTACHMENT usage This was added as RENDER_ATTACHMENT usage is incompatible with swizzle under the current spec. Unfortunately, copying a texture fails for depth textures on Mac Intel (because there is no test). It turns out the current spec only requires the texture view's usage not to have RENDER_ATTACHMENT, not the textures's, so this copy step is not needed. Removing it should let tests run on devices where copy is broken. * Switch to WGSL syntax * Add some swizzles that might mistakenly be accepted. * Add copy support for r8snorm, rg8snorm, rgba8snorm * remove invalid swizzle validation check * add a few more invalid swizzles * Refactor texture-component-swizzle validation tests. Validation changed from createView time to createBindGroup, beginRenderPass time. Co-authored-by: François Beaufort <[email protected]>
1 parent 4adb750 commit 61eae08

File tree

9 files changed

+1282
-61
lines changed

9 files changed

+1282
-61
lines changed

src/webgpu/api/operation/texture_view/texture_component_swizzle.spec.ts

Lines changed: 595 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
export const description = `
2+
Validation tests for the 'texture-component-swizzle' feature.
3+
4+
Test that:
5+
* when the feature is off, swizzling is not allowed, even the identity swizzle.
6+
* swizzling is not allowed on textures with usage STORAGE_BINDING nor RENDER_ATTACHMENT
7+
except the identity swizzle.
8+
`;
9+
10+
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
11+
import { UniqueFeaturesOrLimitsGPUTest } from '../../../../gpu_test.js';
12+
13+
import { isIdentitySwizzle, kSwizzleTests } from './texture_component_swizzle_utils.js';
14+
15+
export const g = makeTestGroup(UniqueFeaturesOrLimitsGPUTest);
16+
17+
g.test('invalid_swizzle')
18+
.desc(
19+
`
20+
Test that setting an invalid swizzle value on a texture view throws an exception.
21+
`
22+
)
23+
.params(u =>
24+
u.beginSubcases().combine('invalidSwizzle', [
25+
'rgbA', // swizzles are case-sensitive
26+
'RGBA', // swizzles are case-sensitive
27+
'rgb', // must have 4 components
28+
'rgba01',
29+
'ɲgba', // r with 0x200 added to each code point to make sure values are not truncated.
30+
'ɲɧɢɡ', // rgba with 0x200 added to each code point to make sure values are not truncated.
31+
'𝐫𝐠𝐛𝐚', // various unicode values that normalize to rgba
32+
'𝑟𝑔𝑏𝑎',
33+
'𝗋𝗀𝖻𝖺',
34+
'𝓇ℊ𝒷𝒶',
35+
'ⓡⓖⓑⓐ',
36+
'rgba',
37+
'ʳᵍᵇᵃ',
38+
'000',
39+
'00000',
40+
'111',
41+
'11111',
42+
0,
43+
1,
44+
1111, // passes because toString is '1111'
45+
1234,
46+
1111.1,
47+
0x72676261, // big endian rgba
48+
0x61626772, // little endian rgba
49+
0x30303030, // 0000
50+
0x31313131, // 1111
51+
true,
52+
false,
53+
null,
54+
])
55+
)
56+
.beforeAllSubcases(t => {
57+
// MAINTENANCE_TODO: Remove this cast once texture-component-swizzle is added to @webgpu/types
58+
t.selectDeviceOrSkipTestCase('texture-component-swizzle' as GPUFeatureName);
59+
})
60+
.fn(t => {
61+
const { invalidSwizzle } = t.params;
62+
const texture = t.createTextureTracked({
63+
format: 'rgba8unorm',
64+
size: [1],
65+
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
66+
});
67+
68+
const failure = typeof invalidSwizzle !== 'number' || invalidSwizzle !== 1111;
69+
t.shouldThrow(failure ? 'TypeError' : false, () => {
70+
texture.createView({ swizzle: invalidSwizzle as GPUTextureComponentSwizzle });
71+
});
72+
});
73+
74+
g.test('only_identity_swizzle')
75+
.desc(
76+
`
77+
Test that if texture-component-swizzle is not enabled, having a non-default swizzle property generates a validation error.
78+
`
79+
)
80+
.params(u => u.beginSubcases().combine('swizzle', kSwizzleTests))
81+
.fn(t => {
82+
// MAINTENANCE_TODO: Remove this check if the spec is updated to say that all implementations must validate this.
83+
t.skipIf(
84+
!t.adapter.features.has('texture-component-swizzle'),
85+
'skip on browsers that have not implemented texture-component-swizzle'
86+
);
87+
88+
const { swizzle } = t.params;
89+
const texture = t.createTextureTracked({
90+
format: 'rgba8unorm',
91+
size: [1],
92+
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
93+
});
94+
const shouldError = !isIdentitySwizzle(swizzle);
95+
t.expectValidationError(() => {
96+
texture.createView({ swizzle });
97+
}, shouldError);
98+
});
99+
100+
g.test('no_render_no_resolve_no_storage')
101+
.desc(
102+
`
103+
Test that setting a non-identity swizzle gets an error if used as a render attachment,
104+
a resolve target, or a storage binding.
105+
`
106+
)
107+
.params(u =>
108+
u
109+
.combine('useCase', [
110+
'texture-binding',
111+
'color-attachment',
112+
'depth-attachment',
113+
'stencil-attachment',
114+
'resolve-target',
115+
'storage-binding',
116+
] as const)
117+
.beginSubcases()
118+
.combine('swizzle', kSwizzleTests)
119+
)
120+
.beforeAllSubcases(t => {
121+
// MAINTENANCE_TODO: Remove this cast once texture-component-swizzle is added to @webgpu/types
122+
t.selectDeviceOrSkipTestCase('texture-component-swizzle' as GPUFeatureName);
123+
})
124+
.fn(t => {
125+
const { swizzle, useCase } = t.params;
126+
const texture = t.createTextureTracked({
127+
format:
128+
useCase === 'depth-attachment'
129+
? 'depth16unorm'
130+
: useCase === 'stencil-attachment'
131+
? 'stencil8'
132+
: 'rgba8unorm',
133+
size: [1],
134+
usage:
135+
GPUTextureUsage.COPY_SRC |
136+
GPUTextureUsage.COPY_DST |
137+
GPUTextureUsage.TEXTURE_BINDING |
138+
GPUTextureUsage.RENDER_ATTACHMENT |
139+
(useCase === 'storage-binding' ? GPUTextureUsage.STORAGE_BINDING : 0),
140+
});
141+
const view = texture.createView({ swizzle });
142+
const shouldError = useCase !== 'texture-binding' && !isIdentitySwizzle(swizzle);
143+
switch (useCase) {
144+
case 'texture-binding': {
145+
const bindGroupLayout = t.device.createBindGroupLayout({
146+
entries: [
147+
{
148+
binding: 0,
149+
visibility: GPUShaderStage.FRAGMENT,
150+
texture: {},
151+
},
152+
],
153+
});
154+
t.expectValidationError(() => {
155+
t.device.createBindGroup({
156+
layout: bindGroupLayout,
157+
entries: [{ binding: 0, resource: view }],
158+
});
159+
}, shouldError);
160+
break;
161+
}
162+
case 'color-attachment': {
163+
t.expectValidationError(() => {
164+
const encoder = t.device.createCommandEncoder();
165+
const pass = encoder.beginRenderPass({
166+
colorAttachments: [
167+
{
168+
view,
169+
loadOp: 'clear',
170+
storeOp: 'store',
171+
},
172+
],
173+
});
174+
pass.end();
175+
encoder.finish();
176+
}, shouldError);
177+
break;
178+
}
179+
case 'depth-attachment': {
180+
t.expectValidationError(() => {
181+
const encoder = t.device.createCommandEncoder();
182+
const pass = encoder.beginRenderPass({
183+
colorAttachments: [],
184+
depthStencilAttachment: {
185+
view,
186+
depthClearValue: 1,
187+
depthLoadOp: 'clear',
188+
depthStoreOp: 'store',
189+
},
190+
});
191+
pass.end();
192+
encoder.finish();
193+
}, shouldError);
194+
break;
195+
}
196+
case 'stencil-attachment': {
197+
t.expectValidationError(() => {
198+
const encoder = t.device.createCommandEncoder();
199+
const pass = encoder.beginRenderPass({
200+
colorAttachments: [],
201+
depthStencilAttachment: {
202+
view,
203+
stencilClearValue: 0,
204+
stencilLoadOp: 'clear',
205+
stencilStoreOp: 'store',
206+
},
207+
});
208+
pass.end();
209+
encoder.finish();
210+
}, shouldError);
211+
break;
212+
}
213+
case 'resolve-target': {
214+
t.expectValidationError(() => {
215+
const encoder = t.device.createCommandEncoder();
216+
const pass = encoder.beginRenderPass({
217+
colorAttachments: [
218+
{
219+
view: t.createTextureTracked({
220+
format: 'rgba8unorm',
221+
size: [1],
222+
usage: GPUTextureUsage.RENDER_ATTACHMENT,
223+
sampleCount: 4,
224+
}),
225+
resolveTarget: view,
226+
loadOp: 'clear',
227+
storeOp: 'store',
228+
},
229+
],
230+
});
231+
pass.end();
232+
encoder.finish();
233+
}, shouldError);
234+
break;
235+
}
236+
case 'storage-binding': {
237+
const bindGroupLayout = t.device.createBindGroupLayout({
238+
entries: [
239+
{
240+
binding: 0,
241+
visibility: GPUShaderStage.COMPUTE,
242+
storageTexture: {
243+
access: 'read-only',
244+
format: 'rgba8unorm',
245+
},
246+
},
247+
],
248+
});
249+
t.expectValidationError(() => {
250+
t.device.createBindGroup({
251+
layout: bindGroupLayout,
252+
entries: [{ binding: 0, resource: view }],
253+
});
254+
}, shouldError);
255+
break;
256+
}
257+
}
258+
});
259+
260+
g.test('compatibility_mode')
261+
.desc(
262+
`
263+
Test that in compatibility mode, swizzles must be equivalent.
264+
`
265+
)
266+
.beforeAllSubcases(t => {
267+
// MAINTENANCE_TODO: Remove this cast once texture-component-swizzle is added to @webgpu/types
268+
t.selectDeviceOrSkipTestCase('texture-component-swizzle' as GPUFeatureName);
269+
})
270+
.params(u =>
271+
u
272+
.beginSubcases()
273+
.combine('swizzle', kSwizzleTests)
274+
.combine('otherSwizzle', kSwizzleTests)
275+
.combine('pipelineType', ['render', 'compute'] as const)
276+
)
277+
.fn(t => {
278+
const { swizzle, otherSwizzle, pipelineType } = t.params;
279+
280+
const module = t.device.createShaderModule({
281+
code: `
282+
@group(0) @binding(0) var tex0: texture_2d<f32>;
283+
@group(1) @binding(0) var tex1: texture_2d<f32>;
284+
285+
@compute @workgroup_size(1) fn cs() {
286+
_ = tex0;
287+
_ = tex1;
288+
}
289+
290+
@vertex fn vs() -> @builtin(position) vec4f {
291+
return vec4f(0);
292+
}
293+
294+
@fragment fn fs() -> @location(0) vec4f {
295+
_ = tex0;
296+
_ = tex1;
297+
return vec4f(0);
298+
}
299+
`,
300+
});
301+
302+
const pipeline =
303+
pipelineType === 'compute'
304+
? t.device.createComputePipeline({
305+
layout: 'auto',
306+
compute: { module },
307+
})
308+
: t.device.createRenderPipeline({
309+
layout: 'auto',
310+
vertex: { module },
311+
fragment: { module, targets: [{ format: 'rgba8unorm' }] },
312+
});
313+
314+
const texture = t.createTextureTracked({
315+
size: [1],
316+
format: 'rgba8unorm',
317+
usage: GPUTextureUsage.TEXTURE_BINDING,
318+
});
319+
320+
const bindGroup0 = t.device.createBindGroup({
321+
layout: pipeline.getBindGroupLayout(0),
322+
entries: [
323+
{
324+
binding: 0,
325+
resource: texture.createView({ swizzle }),
326+
},
327+
],
328+
});
329+
330+
const bindGroup1 = t.device.createBindGroup({
331+
layout: pipeline.getBindGroupLayout(0),
332+
entries: [
333+
{
334+
binding: 0,
335+
resource: texture.createView({ swizzle: otherSwizzle }),
336+
},
337+
],
338+
});
339+
340+
const encoder = t.device.createCommandEncoder();
341+
switch (pipelineType) {
342+
case 'compute': {
343+
const pass = encoder.beginComputePass();
344+
pass.setPipeline(pipeline as GPUComputePipeline);
345+
pass.setBindGroup(0, bindGroup0);
346+
pass.setBindGroup(1, bindGroup1);
347+
pass.dispatchWorkgroups(1);
348+
pass.end();
349+
break;
350+
}
351+
case 'render': {
352+
const view = t.createTextureTracked({
353+
size: [1],
354+
format: 'rgba8unorm',
355+
usage: GPUTextureUsage.RENDER_ATTACHMENT,
356+
});
357+
const pass = encoder.beginRenderPass({
358+
colorAttachments: [{ view, loadOp: 'clear', storeOp: 'store' }],
359+
});
360+
pass.setPipeline(pipeline as GPURenderPipeline);
361+
pass.setBindGroup(0, bindGroup0);
362+
pass.setBindGroup(1, bindGroup1);
363+
pass.draw(3);
364+
pass.end();
365+
}
366+
}
367+
368+
const shouldError = t.isCompatibility && swizzle !== otherSwizzle;
369+
370+
t.expectValidationError(() => {
371+
encoder.finish();
372+
}, shouldError);
373+
});

0 commit comments

Comments
 (0)