blob: 2d5068259b3575176832f571bf719243c48d476b [file] [log] [blame]
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
createBindGroupLayout validation tests.
TODO: review existing tests, write descriptions, and make sure tests are complete.
`;
import { pbool, poptions, params } from '../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import {
kBindingTypeInfo,
kBindingTypes,
kBufferBindingTypeInfo,
kMaxBindingsPerBindGroup,
kShaderStages,
kShaderStageCombinations,
kTextureBindingTypeInfo,
kTextureComponentTypes,
kTextureViewDimensions,
kTextureViewDimensionInfo,
kAllTextureFormats,
kAllTextureFormatInfo,
} from '../../capability_info.js';
import { ValidationTest } from './validation_test.js';
function clone(descriptor) {
return JSON.parse(JSON.stringify(descriptor));
}
export const g = makeTestGroup(ValidationTest);
g.test('some_binding_index_was_specified_more_than_once').fn(async t => {
const goodDescriptor = {
entries: [
{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' },
{ binding: 1, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' },
],
};
// Control case
t.device.createBindGroupLayout(goodDescriptor);
const badDescriptor = clone(goodDescriptor);
badDescriptor.entries[1].binding = 0;
// Binding index 0 can't be specified twice.
t.expectValidationError(() => {
t.device.createBindGroupLayout(badDescriptor);
});
});
g.test('visibility')
.params(
params()
.combine(poptions('type', kBindingTypes))
.combine(poptions('visibility', kShaderStageCombinations))
)
.fn(async t => {
const { type, visibility } = t.params;
const info = kBindingTypeInfo[type];
const storageTextureFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined;
const success = (visibility & ~kBindingTypeInfo[type].validStages) === 0;
t.expectValidationError(() => {
t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility, type, storageTextureFormat }],
});
}, !success);
});
g.test('bindingTypeSpecific_optional_members')
.params(
params()
.combine(poptions('type', kBindingTypes))
.combine([
// Case with every member set to `undefined`.
{
// Workaround for TS inferring the type of [ {}, ...x ] overly conservatively, as {}[].
_: 0,
},
// Cases with one member set.
...pbool('hasDynamicOffset'),
...poptions('minBufferBindingSize', [0, 4]),
...poptions('textureComponentType', kTextureComponentTypes),
...poptions('viewDimension', kTextureViewDimensions),
...poptions('storageTextureFormat', kAllTextureFormats),
])
)
.fn(t => {
const {
type,
hasDynamicOffset,
minBufferBindingSize,
textureComponentType,
viewDimension,
storageTextureFormat,
} = t.params;
let success = true;
if (!(type in kBufferBindingTypeInfo)) {
success && (success = hasDynamicOffset === undefined);
success && (success = minBufferBindingSize === undefined);
}
if (!(type in kTextureBindingTypeInfo)) {
success && (success = viewDimension === undefined);
}
if (kBindingTypeInfo[type].resource !== 'sampledTex') {
success && (success = textureComponentType === undefined);
}
if (kBindingTypeInfo[type].resource !== 'storageTex') {
success && (success = storageTextureFormat === undefined);
} else {
success &&
(success = viewDimension === undefined || kTextureViewDimensionInfo[viewDimension].storage);
success &&
(success =
storageTextureFormat === undefined ||
kAllTextureFormatInfo[storageTextureFormat].storage);
}
t.expectValidationError(() => {
t.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.COMPUTE,
type,
hasDynamicOffset,
minBufferBindingSize,
textureComponentType,
viewDimension,
storageTextureFormat,
},
],
});
}, !success);
});
g.test('multisample_requires_2d_view_dimension')
.params(params().combine(poptions('viewDimension', [undefined, ...kTextureViewDimensions])))
.fn(async t => {
const { viewDimension } = t.params;
const success = viewDimension === '2d' || viewDimension === undefined;
t.expectValidationError(() => {
t.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.COMPUTE,
type: 'multisampled-texture',
viewDimension,
},
],
});
}, !success);
});
g.test('number_of_dynamic_buffers_exceeds_the_maximum_value')
.desc(
`TODO: describe
TODO(#230): Update to enforce per-stage and per-pipeline-layout limits on BGLs as well.`
)
.params([
{ type: 'storage-buffer', maxDynamicBufferCount: 4 },
{ type: 'uniform-buffer', maxDynamicBufferCount: 8 },
])
.fn(async t => {
const { type, maxDynamicBufferCount } = t.params;
const maxDynamicBufferBindings = [];
for (let i = 0; i < maxDynamicBufferCount; i++) {
maxDynamicBufferBindings.push({
binding: i,
visibility: GPUShaderStage.COMPUTE,
type,
hasDynamicOffset: true,
});
}
const goodDescriptor = {
entries: [
...maxDynamicBufferBindings,
{
binding: maxDynamicBufferBindings.length,
visibility: GPUShaderStage.COMPUTE,
type,
hasDynamicOffset: false,
},
],
};
// Control case
t.device.createBindGroupLayout(goodDescriptor);
// Dynamic buffers exceed maximum in a bind group layout.
const badDescriptor = clone(goodDescriptor);
badDescriptor.entries[maxDynamicBufferCount].hasDynamicOffset = true;
t.expectValidationError(() => {
t.device.createBindGroupLayout(badDescriptor);
});
});
// One bind group layout will be filled with kPerStageBindingLimit[...] of the type |type|.
// For each item in the array returned here, a case will be generated which tests a pipeline
// layout with one extra bind group layout with one extra binding. That extra binding will have:
//
// - If extraTypeSame, any of the binding types which counts toward the same limit as |type|.
// (i.e. 'storage-buffer' <-> 'readonly-storage-buffer').
// - Otherwise, an arbitrary other type.
function* pickExtraBindingTypes(bindingType, extraTypeSame) {
const info = kBindingTypeInfo[bindingType];
if (extraTypeSame) {
for (const extraBindingType of kBindingTypes) {
if (
info.perStageLimitClass.class ===
kBindingTypeInfo[extraBindingType].perStageLimitClass.class
) {
yield extraBindingType;
}
}
} else {
yield info.perStageLimitClass.class === 'sampler' ? 'sampled-texture' : 'sampler';
}
}
const kCasesForMaxResourcesPerStageTests = params()
.combine(poptions('maxedType', kBindingTypes))
.combine(poptions('maxedVisibility', kShaderStages))
.filter(p => (kBindingTypeInfo[p.maxedType].validStages & p.maxedVisibility) !== 0)
.expand(function* (p) {
for (const extraTypeSame of [true, false]) {
yield* poptions('extraType', pickExtraBindingTypes(p.maxedType, extraTypeSame));
}
})
.combine(poptions('extraVisibility', kShaderStages))
.filter(p => (kBindingTypeInfo[p.extraType].validStages & p.extraVisibility) !== 0);
// Should never fail unless kMaxBindingsPerBindGroup is exceeded, because the validation for
// resources-of-type-per-stage is in pipeline layout creation.
g.test('max_resources_per_stage,in_bind_group_layout')
.desc(
`TODO: describe
TODO(#230): Update to enforce per-stage and per-pipeline-layout limits on BGLs as well.`
)
.params(kCasesForMaxResourcesPerStageTests)
.fn(async t => {
const { maxedType, extraType, maxedVisibility, extraVisibility } = t.params;
const maxedTypeInfo = kBindingTypeInfo[maxedType];
const maxedCount = maxedTypeInfo.perStageLimitClass.max;
const extraTypeInfo = kBindingTypeInfo[extraType];
const maxResourceBindings = [];
for (let i = 0; i < maxedCount; i++) {
maxResourceBindings.push({
binding: i,
visibility: maxedVisibility,
type: maxedType,
storageTextureFormat: maxedTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined,
});
}
const goodDescriptor = { entries: maxResourceBindings };
// Control
t.device.createBindGroupLayout(goodDescriptor);
const newDescriptor = clone(goodDescriptor);
newDescriptor.entries.push({
binding: maxedCount,
visibility: extraVisibility,
type: extraType,
storageTextureFormat: extraTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined,
});
const shouldError = maxedCount >= kMaxBindingsPerBindGroup;
t.expectValidationError(() => {
t.device.createBindGroupLayout(newDescriptor);
}, shouldError);
});
// One pipeline layout can have a maximum number of each type of binding *per stage* (which is
// different for each type). Test that the max works, then add one more binding of same-or-different
// type and same-or-different visibility.
g.test('max_resources_per_stage,in_pipeline_layout')
.params(kCasesForMaxResourcesPerStageTests)
.fn(async t => {
const { maxedType, extraType, maxedVisibility, extraVisibility } = t.params;
const maxedTypeInfo = kBindingTypeInfo[maxedType];
const maxedCount = maxedTypeInfo.perStageLimitClass.max;
const extraTypeInfo = kBindingTypeInfo[extraType];
const maxResourceBindings = [];
for (let i = 0; i < maxedCount; i++) {
maxResourceBindings.push({
binding: i,
visibility: maxedVisibility,
type: maxedType,
storageTextureFormat: maxedTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined,
});
}
const goodLayout = t.device.createBindGroupLayout({ entries: maxResourceBindings });
// Control
t.device.createPipelineLayout({ bindGroupLayouts: [goodLayout] });
const extraLayout = t.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: extraVisibility,
type: extraType,
storageTextureFormat: extraTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined,
},
],
});
// Some binding types use the same limit, e.g. 'storage-buffer' and 'readonly-storage-buffer'.
const newBindingCountsTowardSamePerStageLimit =
(maxedVisibility & extraVisibility) !== 0 &&
kBindingTypeInfo[maxedType].perStageLimitClass.class ===
kBindingTypeInfo[extraType].perStageLimitClass.class;
const layoutExceedsPerStageLimit = newBindingCountsTowardSamePerStageLimit;
t.expectValidationError(() => {
t.device.createPipelineLayout({ bindGroupLayouts: [goodLayout, extraLayout] });
}, layoutExceedsPerStageLimit);
});