Skip to content

Commit

Permalink
Fix maxBindGroups test so it can attempt more resources
Browse files Browse the repository at this point in the history
Before it was just binding uniform buffers but if maxBindGroups is
more than maxUniformBuffersPerShaderStage the test would fail.

Now it uses all possible resources types. One per bind group.
  • Loading branch information
greggman committed Jan 31, 2024
1 parent 379339d commit d5b0dae
Showing 1 changed file with 137 additions and 15 deletions.
152 changes: 137 additions & 15 deletions src/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { range } from '../../../../../common/util/util.js';
import { assert } from '../../../../../common/util/util.js';

import {
kCreatePipelineTypes,
Expand All @@ -10,30 +10,152 @@ import {
const limit = 'maxBindGroups';
export const { g, description } = makeLimitTestGroup(limit);

type BindingLayout = {
buffer?: GPUBufferBindingLayout;
sampler?: GPUSamplerBindingLayout;
texture?: GPUTextureBindingLayout;
storageTexture?: GPUStorageTextureBindingLayout;
externalTexture?: GPUExternalTextureBindingLayout;
};

type LimitToBindingLayout = {
name: keyof GPUSupportedLimits;
entry: BindingLayout;
};

const kLimitToBindingLayout: LimitToBindingLayout[] = [
{
name: 'maxSampledTexturesPerShaderStage',
entry: {
texture: {},
},
},
{
name: 'maxSamplersPerShaderStage',
entry: {
sampler: {},
},
},
{
name: 'maxUniformBuffersPerShaderStage',
entry: {
buffer: {},
},
},
{
name: 'maxStorageBuffersPerShaderStage',
entry: {
buffer: {
type: 'read-only-storage',
},
},
},
{
name: 'maxStorageTexturesPerShaderStage',
entry: {
storageTexture: {
access: 'write-only',
format: 'rgba8unorm',
viewDimension: '2d',
},
},
},
];

/**
* Yields all possible binding layout entries for a stage.
*/
function* getBindingLayoutEntriesForStage(device: GPUDevice) {
for (const { name, entry } of kLimitToBindingLayout) {
const limit = device.limits[name] as number;
for (let i = 0; i < limit; ++i) {
yield entry;
}
}
}

/**
* Yields all of the possible BindingLayoutEntryAndVisibility entries for a render pipeline
*/
function* getBindingLayoutEntriesForRenderPipeline(
device: GPUDevice
): Generator<GPUBindGroupLayoutEntry> {
const visibilities = [GPUShaderStage.VERTEX, GPUShaderStage.FRAGMENT];
for (const visibility of visibilities) {
for (const bindEntryResourceType of getBindingLayoutEntriesForStage(device)) {
const entry: GPUBindGroupLayoutEntry = {
binding: 0,
visibility,
...bindEntryResourceType,
};
yield entry;
}
}
}

/**
* Returns the total possible bindings per render pipeline
*/
function getTotalPossibleBindingsPerRenderPipeline(device: GPUDevice) {
const totalPossibleBindingsPerStage =
device.limits.maxSampledTexturesPerShaderStage +
device.limits.maxSamplersPerShaderStage +
device.limits.maxUniformBuffersPerShaderStage +
device.limits.maxStorageBuffersPerShaderStage +
device.limits.maxStorageTexturesPerShaderStage;
return totalPossibleBindingsPerStage * 2;
}

/**
* Yields count GPUBindGroupLayoutEntries
*/
function* getBindingLayoutEntries(
device: GPUDevice,
count: number
): Generator<GPUBindGroupLayoutEntry> {
assert(count < getTotalPossibleBindingsPerRenderPipeline(device));
const iter = getBindingLayoutEntriesForRenderPipeline(device);
for (; count > 0; --count) {
yield iter.next().value;
}
}

g.test('createPipelineLayout,at_over')
.desc(`Test using createPipelineLayout at and over ${limit} limit`)
.params(kMaximumLimitBaseParams)
.fn(async t => {
const { limitTest, testValueName } = t.params;

await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
async ({ device, testValue, shouldError }) => {
const bindGroupLayouts = range(testValue, _i =>
device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.VERTEX,
buffer: {},
},
],
})
async ({ device, testValue, shouldError, actualLimit }) => {
const totalPossibleBindingsPerPipeline = getTotalPossibleBindingsPerRenderPipeline(device);
// Not sure what to do if we ever hit this but I think it's better to assert than silently skip.
assert(
testValue < totalPossibleBindingsPerPipeline,
`not enough possible bindings(${totalPossibleBindingsPerPipeline}) to test ${testValue} bindGroups`
);

await t.expectValidationError(() => {
device.createPipelineLayout({ bindGroupLayouts });
}, shouldError);
const bindingDescriptions: string[] = [];
const bindGroupLayouts = [...getBindingLayoutEntries(device, testValue)].map(entry => {
bindingDescriptions.push(
`${JSON.stringify(entry)} // group(${bindingDescriptions.length})`
);
return device.createBindGroupLayout({
entries: [entry],
});
});

await t.expectValidationError(
() => {
device.createPipelineLayout({ bindGroupLayouts });
},
shouldError,
`testing ${testValue} bindGroups on maxBindGroups = ${actualLimit} with \n${bindingDescriptions.join(
'\n'
)}`
);
}
);
});
Expand Down

0 comments on commit d5b0dae

Please sign in to comment.