import { range } from '../../../../../common/util/util.js';
import { kShaderStageCombinationsWithStage } from '../../../../capability_info.js';
import { GPUConst } from '../../../../constants.js';
import { kMaximumLimitBaseParams, LimitsRequest, makeLimitTestGroup } from './limit_utils.js';
const kExtraLimits: LimitsRequest = {
maxBindingsPerBindGroup: 'adapterLimit',
maxBindGroups: 'adapterLimit',
maxStorageBuffersPerShaderStage: 'adapterLimit',
maxStorageBuffersInFragmentStage: 'adapterLimit',
maxStorageBuffersInVertexStage: 'adapterLimit',
};
const limit = 'maxDynamicStorageBuffersPerPipelineLayout';
export const { g, description } = makeLimitTestGroup(limit);
g.test('createBindGroupLayout,at_over')
.desc(`Test using createBindGroupLayout at and over ${limit} limit`)
.params(
kMaximumLimitBaseParams
.combine('visibility', kShaderStageCombinationsWithStage)
.combine('type', ['storage', 'read-only-storage'] as GPUBufferBindingType[])
.filter(
({ visibility, type }) =>
(visibility & GPUConst.ShaderStage.VERTEX) === 0 || type !== 'storage'
)
)
.fn(async t => {
const { limitTest, testValueName, visibility, type } = t.params;
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
async ({ device, testValue, shouldError }) => {
t.skipIfNotEnoughStorageBuffersInStage(visibility, testValue);
shouldError ||= testValue > t.device.limits.maxStorageBuffersPerShaderStage;
await t.expectValidationError(() => {
device.createBindGroupLayout({
entries: range(testValue, i => ({
binding: i,
visibility,
buffer: {
type,
hasDynamicOffset: true,
},
})),
});
}, shouldError);
},
kExtraLimits
);
});
g.test('createPipelineLayout,at_over')
.desc(`Test using at and over ${limit} limit in createPipelineLayout`)
.params(
kMaximumLimitBaseParams.combine('type', [
'storage',
'read-only-storage',
] as GPUBufferBindingType[])
)
.fn(async t => {
const { limitTest, testValueName, type } = t.params;
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
async ({ device, testValue, shouldError, actualLimit }) => {
const maxComputeBindings = Math.min(
device.limits.maxStorageBuffersPerShaderStage,
actualLimit
);
const maxFragmentBindings = Math.min(
device.limits.maxStorageBuffersInFragmentStage ?? maxComputeBindings,
actualLimit
);
const maxVertexBindings =
type === 'storage'
? 0
: Math.min(
device.limits.maxStorageBuffersInVertexStage ?? maxComputeBindings,
actualLimit
);
const totalBindings = maxComputeBindings + maxFragmentBindings + maxVertexBindings;
t.skipIf(
totalBindings < testValue,
`total storage buffer bindings across stages (${totalBindings}) < testValue(${testValue})`
);
const maxBindingsPerStage = [maxVertexBindings, maxFragmentBindings, maxComputeBindings];
let numBindingsAvailable = testValue;
const bindGroupLayouts = maxBindingsPerStage.map((maxBindings, visibilityBit) => {
const numInGroup = Math.min(numBindingsAvailable, maxBindings);
numBindingsAvailable -= numInGroup;
t.debug(`group(${visibilityBit}) numBindings: ${numInGroup}`);
return device.createBindGroupLayout({
entries: range(numInGroup, i => ({
binding: i,
visibility: 1 << visibilityBit,
buffer: {
type,
hasDynamicOffset: true,
},
})),
});
});
await t.expectValidationError(
() => device.createPipelineLayout({ bindGroupLayouts }),
shouldError
);
},
kExtraLimits
);
});