Skip to content

Commit e99550b

Browse files
authored
Compat: Add tests for maxStorage(Buffer/Texture)sIn(Vertex/Fragment)Stage (#4118)
Note: I couldn't really think of a test on the core side. Originally I was going to try to request `maxXXXInYYYStage` less than `maxXXXPerShaderStage` which is what the tests do in compat but in core I believe it will be a no-op and that InStage limits will always match their PerStage counterparts. I added a `RequiredLimitsTestMixin` to try to make it easier to write a test that needs specific limits. The issue is the key. I thought about making the key just be the source of `getRequiredLimits` but I can imagine someone writing some more generic requester that takes a list of of limits in which case the code would be generic and so using the source of the funciton would not be a valid key. I also though about adding a MaxOfSpecificLimitsMixin where you could pass in a list of maxes but I think most code that needs maxes can use MaxLimitsTestMixin
1 parent d3fec1f commit e99550b

File tree

5 files changed

+403
-15
lines changed

5 files changed

+403
-15
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
export const description = `
2+
Tests that, in compat mode, you can not create a bind group layout with with
3+
more than the max in stage limit even if the per stage limit is higher.
4+
`;
5+
6+
import { makeTestGroup } from '../../../../common/framework/test_group.js';
7+
import { range } from '../../../../common/util/util.js';
8+
import { RequiredLimitsTestMixin } from '../../../gpu_test.js';
9+
import { CompatibilityTest } from '../../compatibility_test.js';
10+
11+
export const g = makeTestGroup(
12+
RequiredLimitsTestMixin(CompatibilityTest, {
13+
getRequiredLimits(adapter: GPUAdapter) {
14+
return {
15+
maxStorageBuffersInFragmentStage: adapter.limits.maxStorageBuffersInFragmentStage! / 2,
16+
maxStorageBuffersInVertexStage: adapter.limits.maxStorageBuffersInVertexStage! / 2,
17+
maxStorageBuffersPerShaderStage: adapter.limits.maxStorageBuffersPerShaderStage,
18+
maxStorageTexturesInFragmentStage: adapter.limits.maxStorageTexturesInFragmentStage! / 2,
19+
maxStorageTexturesInVertexStage: adapter.limits.maxStorageTexturesInVertexStage! / 2,
20+
maxStorageTexturesPerShaderStage: adapter.limits.maxStorageTexturesPerShaderStage,
21+
};
22+
},
23+
key() {
24+
return `
25+
maxStorageBuffersInFragmentStage/2,
26+
maxStorageBuffersInVertexStage/2,
27+
maxStorageTexturesInFragmentStage/2,
28+
maxStorageTexturesInVertexStage/2,
29+
maxStorageBuffersPerShaderStage
30+
maxStorageTexturesPerShaderStage
31+
`;
32+
},
33+
})
34+
);
35+
36+
g.test('maxStorageBuffersTexturesInVertexFragmentStage')
37+
.desc(
38+
`
39+
Tests that you can't use more than maxStorage(Buffers/Textures)In(Fragment/Vertex)Stage when
40+
the limit is less than maxStorage(Buffers/Textures)PerShaderStage
41+
`
42+
)
43+
.params(u =>
44+
u
45+
.combine('limit', [
46+
'maxStorageBuffersInFragmentStage',
47+
'maxStorageBuffersInVertexStage',
48+
'maxStorageTexturesInFragmentStage',
49+
'maxStorageTexturesInVertexStage',
50+
] as const)
51+
.beginSubcases()
52+
.combine('extra', [0, 1] as const)
53+
)
54+
.fn(t => {
55+
const { limit, extra } = t.params;
56+
const { device } = t;
57+
58+
const isBuffer = limit.includes('Buffers');
59+
const inStageLimit = device.limits[limit]!;
60+
const perStageLimitName = isBuffer
61+
? 'maxStorageBuffersPerShaderStage'
62+
: 'maxStorageTexturesPerShaderStage';
63+
const perStageLimit = device.limits[perStageLimitName];
64+
65+
t.debug(`${limit}(${inStageLimit}), ${perStageLimitName}(${perStageLimit})`);
66+
67+
t.skipIf(inStageLimit === 0, `${limit} is 0`);
68+
t.skipIf(
69+
!(inStageLimit < perStageLimit),
70+
`${limit}(${inStageLimit}) is not less than ${perStageLimitName}(${perStageLimit})`
71+
);
72+
73+
const visibility = limit.includes('Fragment') ? GPUShaderStage.FRAGMENT : GPUShaderStage.VERTEX;
74+
75+
const expectFailure = extra > 0;
76+
t.expectValidationError(() => {
77+
device.createBindGroupLayout({
78+
entries: range(inStageLimit + extra, i => ({
79+
binding: i,
80+
visibility,
81+
...(isBuffer
82+
? { buffer: { type: 'read-only-storage' } }
83+
: { storageTexture: { format: 'r32float', access: 'read-only' } }),
84+
})),
85+
});
86+
}, expectFailure);
87+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
export const description = `
2+
Tests that, in compat mode, you can not create a pipeline layout with with
3+
more than the max in stage limit even if the per stage limit is higher.
4+
`;
5+
6+
import { makeTestGroup } from '../../../../common/framework/test_group.js';
7+
import { range } from '../../../../common/util/util.js';
8+
import { RequiredLimitsTestMixin } from '../../../gpu_test.js';
9+
import { CompatibilityTest } from '../../compatibility_test.js';
10+
11+
export const g = makeTestGroup(
12+
RequiredLimitsTestMixin(CompatibilityTest, {
13+
getRequiredLimits(adapter: GPUAdapter) {
14+
return {
15+
maxStorageBuffersInFragmentStage: adapter.limits.maxStorageBuffersInFragmentStage! / 2,
16+
maxStorageBuffersInVertexStage: adapter.limits.maxStorageBuffersInVertexStage! / 2,
17+
maxStorageBuffersPerShaderStage: adapter.limits.maxStorageBuffersPerShaderStage,
18+
maxStorageTexturesInFragmentStage: adapter.limits.maxStorageTexturesInFragmentStage! / 2,
19+
maxStorageTexturesInVertexStage: adapter.limits.maxStorageTexturesInVertexStage! / 2,
20+
maxStorageTexturesPerShaderStage: adapter.limits.maxStorageTexturesPerShaderStage,
21+
};
22+
},
23+
key() {
24+
return `
25+
maxStorageBuffersInFragmentStage/2,
26+
maxStorageBuffersInVertexStage/2,
27+
maxStorageTexturesInFragmentStage/2,
28+
maxStorageTexturesInVertexStage/2,
29+
maxStorageBuffersPerShaderStage
30+
maxStorageTexturesPerShaderStage
31+
`;
32+
},
33+
})
34+
);
35+
36+
g.test('maxStorageBuffersTexturesInVertexFragmentStage')
37+
.desc(
38+
`
39+
Tests that you can't use more than maxStorage(Buffers/Textures)In(Fragment/Vertex)Stage when
40+
the limit is less than maxStorage(Buffers/Textures)PerShaderStage
41+
`
42+
)
43+
.params(u =>
44+
u
45+
.combine('limit', [
46+
'maxStorageBuffersInFragmentStage',
47+
'maxStorageBuffersInVertexStage',
48+
'maxStorageTexturesInFragmentStage',
49+
'maxStorageTexturesInVertexStage',
50+
] as const)
51+
.beginSubcases()
52+
.combine('extra', [0, 1] as const)
53+
)
54+
.fn(t => {
55+
const { limit, extra } = t.params;
56+
const { device } = t;
57+
58+
const isBuffer = limit.includes('Buffers');
59+
const inStageLimit = device.limits[limit]!;
60+
const perStageLimitName = isBuffer
61+
? 'maxStorageBuffersPerShaderStage'
62+
: 'maxStorageTexturesPerShaderStage';
63+
const perStageLimit = device.limits[perStageLimitName];
64+
65+
t.debug(`{${limit}(${inStageLimit}), ${perStageLimitName}(${perStageLimit}})`);
66+
67+
t.skipIf(inStageLimit === 0, `${limit} is 0`);
68+
t.skipIf(
69+
!(inStageLimit < perStageLimit),
70+
`{${limit}(${inStageLimit}) is not less than ${perStageLimitName}(${perStageLimit}})`
71+
);
72+
73+
const visibility = limit.includes('Fragment') ? GPUShaderStage.FRAGMENT : GPUShaderStage.VERTEX;
74+
75+
const bindGroupLayouts = [inStageLimit, extra].map(count =>
76+
device.createBindGroupLayout({
77+
entries: range(count, i => ({
78+
binding: i,
79+
visibility,
80+
...(isBuffer
81+
? { buffer: { type: 'read-only-storage' } }
82+
: { storageTexture: { format: 'r32float', access: 'read-only' } }),
83+
})),
84+
})
85+
);
86+
87+
const expectFailure = extra > 0;
88+
t.expectValidationError(() => {
89+
device.createPipelineLayout({ bindGroupLayouts });
90+
}, expectFailure);
91+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
export const description = `
2+
Tests that, in compat mode, you can not create a pipeline layout with with
3+
more than the max in stage limit even if the per stage limit is higher.
4+
`;
5+
6+
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
7+
import { range } from '../../../../../common/util/util.js';
8+
import { RequiredLimitsTestMixin } from '../../../../gpu_test.js';
9+
import { CompatibilityTest } from '../../../compatibility_test.js';
10+
11+
export const g = makeTestGroup(
12+
RequiredLimitsTestMixin(CompatibilityTest, {
13+
getRequiredLimits(adapter: GPUAdapter) {
14+
return {
15+
maxStorageBuffersInFragmentStage: adapter.limits.maxStorageBuffersInFragmentStage! / 2,
16+
maxStorageBuffersInVertexStage: adapter.limits.maxStorageBuffersInVertexStage! / 2,
17+
maxStorageBuffersPerShaderStage: adapter.limits.maxStorageBuffersPerShaderStage,
18+
maxStorageTexturesInFragmentStage: adapter.limits.maxStorageTexturesInFragmentStage! / 2,
19+
maxStorageTexturesInVertexStage: adapter.limits.maxStorageTexturesInVertexStage! / 2,
20+
maxStorageTexturesPerShaderStage: adapter.limits.maxStorageTexturesPerShaderStage,
21+
};
22+
},
23+
key() {
24+
return `
25+
maxStorageBuffersInFragmentStage/2,
26+
maxStorageBuffersInVertexStage/2,
27+
maxStorageTexturesInFragmentStage/2,
28+
maxStorageTexturesInVertexStage/2,
29+
maxStorageBuffersPerShaderStage
30+
maxStorageTexturesPerShaderStage
31+
`;
32+
},
33+
})
34+
);
35+
36+
g.test('maxStorageBuffersTexturesInVertexFragmentStage')
37+
.desc(
38+
`
39+
Tests that you can't use more than maxStorage(Buffers/Textures)In(Fragment/Vertex)Stage when
40+
the limit is less than maxStorage(Buffers/Textures)PerShaderStage
41+
`
42+
)
43+
.params(u =>
44+
u
45+
.combine('limit', [
46+
'maxStorageBuffersInFragmentStage',
47+
'maxStorageBuffersInVertexStage',
48+
'maxStorageTexturesInFragmentStage',
49+
'maxStorageTexturesInVertexStage',
50+
] as const)
51+
.beginSubcases()
52+
.combine('async', [false, true] as const)
53+
.combine('extra', [0, 1] as const)
54+
)
55+
.fn(t => {
56+
const { limit, extra, async } = t.params;
57+
const { device } = t;
58+
59+
const isBuffer = limit.includes('Buffers');
60+
const inStageLimit = device.limits[limit]!;
61+
const perStageLimitName = isBuffer
62+
? 'maxStorageBuffersPerShaderStage'
63+
: 'maxStorageTexturesPerShaderStage';
64+
const perStageLimit = device.limits[perStageLimitName];
65+
66+
t.debug(`${limit}(${inStageLimit}), ${perStageLimitName}(${perStageLimit})`);
67+
68+
t.skipIf(inStageLimit === 0, `${limit} is 0`);
69+
t.skipIf(
70+
!(inStageLimit < perStageLimit),
71+
`${limit}(${inStageLimit}) is not less than ${perStageLimitName}(${perStageLimit})`
72+
);
73+
74+
const typeWGSLFn = isBuffer
75+
? (i: number) => `var<storage, read> v${i}: f32;`
76+
: (i: number) => `var v${i}: texture_storage_2d<r32float, read>;`;
77+
78+
const count = inStageLimit + extra;
79+
const code = `
80+
${range(count, i => `@group(0) @binding(${i}) ${typeWGSLFn(i)}`).join('\n')}
81+
82+
fn useResources() {
83+
${range(count, i => `_ = v${i};`).join('\n')}
84+
}
85+
86+
@vertex fn vsNoUse() -> @builtin(position) vec4f {
87+
return vec4f(0);
88+
}
89+
90+
@vertex fn vsUse() -> @builtin(position) vec4f {
91+
useResources();
92+
return vec4f(0);
93+
}
94+
95+
@fragment fn fsNoUse() -> @location(0) vec4f {
96+
return vec4f(0);
97+
}
98+
99+
@fragment fn fsUse() -> @location(0) vec4f {
100+
useResources();
101+
return vec4f(0);
102+
}
103+
`;
104+
105+
const module = device.createShaderModule({ code });
106+
107+
const isFragment = limit.includes('Fragment');
108+
const pipelineDescriptor: GPURenderPipelineDescriptor = {
109+
layout: 'auto',
110+
vertex: {
111+
module,
112+
entryPoint: isFragment ? 'vsNoUse' : 'vsUse',
113+
},
114+
fragment: {
115+
module,
116+
entryPoint: isFragment ? 'fsUse' : 'fsNoUse',
117+
targets: [{ format: 'rgba8unorm' }],
118+
},
119+
};
120+
121+
const success = extra === 0;
122+
t.doCreateRenderPipelineTest(async, success, pipelineDescriptor);
123+
});

0 commit comments

Comments
 (0)