Skip to content

Commit

Permalink
Test maxBindGroupsPlusVertexBuffers limits
Browse files Browse the repository at this point in the history
  • Loading branch information
greggman committed Feb 6, 2024
1 parent e05aad5 commit 48d1d69
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 0 deletions.
23 changes: 23 additions & 0 deletions src/webgpu/api/validation/capability_checks/limits/limit_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,29 @@ export class LimitTestsImpl extends GPUTestBase {
await this.expectValidationError(test, shouldError, msg);
}

/**
* Test a method on GPURenderCommandsMixin or GPUBindingCommandsMixin
* The function will be called with the mixin.
*/
async testGPURenderAndBindingCommandsMixin(
encoderType: RenderEncoderType,
fn: ({
mixin,
bindGroup,
}: {
mixin: GPURenderCommandsMixin & GPUBindingCommandsMixin;
bindGroup: GPUBindGroup;
}) => void,
shouldError: boolean,
msg = ''
) {
const { mixin, prep, test, bindGroup } = this._getGPURenderCommandsMixin(encoderType);
fn({ mixin, bindGroup });
prep();

await this.expectValidationError(test, shouldError, msg);
}

/**
* Creates GPUBindingCommandsMixin setup with some initial state.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
/* eslint-disable prettier/prettier */
import {
kRenderEncoderTypes,
kMaximumLimitBaseParams,
makeLimitTestGroup,
LimitsRequest,
} from './limit_utils.js';

const kVertexBufferBindGroupPreferences = ['vertexBuffers', 'bindGroups'] as const;
type VertexBufferBindGroupPreference = (typeof kVertexBufferBindGroupPreferences)[number];

const kLayoutTypes = ['auto', 'explicit'] as const;
type LayoutType = typeof kLayoutTypes[number];

/**
* Given testValue, choose more vertex buffers or more bind groups based on preference
*/
function getNumBindGroupsAndNumVertexBuffers(
device: GPUDevice,
preference: VertexBufferBindGroupPreference,
testValue: number
) {
switch (preference) {
case 'bindGroups': {
const numBindGroups = Math.min(testValue, device.limits.maxBindGroups);
const numVertexBuffers = Math.max(0, testValue - numBindGroups);
return { numVertexBuffers, numBindGroups };
}
case 'vertexBuffers': {
const numVertexBuffers = Math.min(testValue, device.limits.maxVertexBuffers);
const numBindGroups = Math.max(0, testValue - numVertexBuffers);
return { numVertexBuffers, numBindGroups };
}
}
}

function createLayout(device: GPUDevice, layoutType: LayoutType, numBindGroups: number) {
switch (layoutType) {
case 'auto':
return 'auto';
case 'explicit': {
const bindGroupLayouts = new Array(numBindGroups);
if (numBindGroups > 0) {
bindGroupLayouts.fill(device.createBindGroupLayout({ entries: [] }));
bindGroupLayouts[numBindGroups - 1] = device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.VERTEX,
buffer: {},
}
],
});
}
return device.createPipelineLayout({ bindGroupLayouts })
}
}
}

/**
* Generate a render pipeline that can be used to test maxBindGroupsPlusVertexBuffers
*/
function getPipelineDescriptor(device: GPUDevice, preference: VertexBufferBindGroupPreference, testValue: number, layoutType: LayoutType) {
// Get the numVertexBuffers and numBindGroups we could use given testValue as a total.
// We will only use 1 of each but we'll use the last index.
const { numVertexBuffers, numBindGroups } = getNumBindGroupsAndNumVertexBuffers(
device,
preference,
testValue
);

const layout = createLayout(device, layoutType, numBindGroups);

const [bindGroupDecl, bindGroupUsage] =
numBindGroups === 0
? ['', '']
: [`@group(${numBindGroups - 1}) @binding(0) var<uniform> u: f32;`, `_ = u;`];

const [attribDecl, attribUsage] =
numVertexBuffers === 0
? ['', '']
: ['@location(0) v: vec4f', `_ = v; // will use vertex buffer ${numVertexBuffers - 1}`];

const code = `
${bindGroupDecl}
@vertex fn vs(${attribDecl}) -> @builtin(position) vec4f {
${bindGroupUsage}
${attribUsage}
return vec4f(0);
}
`;

const module = device.createShaderModule({ code });
const buffers = new Array(numVertexBuffers);
if (numVertexBuffers > 0) {
buffers[numVertexBuffers - 1] = {
arrayStride: 16,
attributes: [{ shaderLocation: 0, offset: 0, format: 'float32' }],
};
}

return {
code,
descriptor: {
layout,
vertex: {
module,
buffers,
},
fragment: {
module,
targets: [{ format: 'rgba8unorm' }],
},
} as GPURenderPipelineDescriptor,
};
}

const kExtraLimits: LimitsRequest = {
maxBindGroups: 'adapterLimit',
maxVertexBuffers: 'adapterLimit',
};

const limit = 'maxBindGroupsPlusVertexBuffers';
export const { g, description } = makeLimitTestGroup(limit);

g.test('createRenderPipeline,at_over')
.desc(`Test using at and over ${limit} limit in createRenderPipeline(Async).`)
.params(
kMaximumLimitBaseParams
.combine('async', [false, true])
.beginSubcases()
.combine('preference', kVertexBufferBindGroupPreferences)
.combine('layoutType', kLayoutTypes)
)
.fn(async t => {
const { limitTest, testValueName, async, preference, layoutType } = t.params;
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
async ({ device, testValue, shouldError, actualLimit }) => {
const maxUsableBindGroupsPlusVertexBuffers =
device.limits.maxBindGroups + device.limits.maxVertexBuffers;
t.skipIf(
actualLimit > maxUsableBindGroupsPlusVertexBuffers,
`can not test because the max usable bindGroups + vertexBuffers (${maxUsableBindGroupsPlusVertexBuffers}) is < maxBindGroupsAndVertexBuffers(${actualLimit})`
);

const { code, descriptor } = getPipelineDescriptor(device, preference, testValue, layoutType);

await t.testCreateRenderPipeline(
descriptor,
async,
shouldError,
`testValue: ${testValue}, actualLimit: ${actualLimit}, shouldError: ${shouldError}\n${code}`
);
},
kExtraLimits
);
});

g.test('draw,at_over')
.desc(`Test using at and over ${limit} limit draw/drawIndexed/drawIndirect/drawIndexedIndirect`)
.params(
kMaximumLimitBaseParams
.combine('encoderType', kRenderEncoderTypes)
.beginSubcases()
.combine('preference', kVertexBufferBindGroupPreferences)
.combine('drawType', ['draw', 'drawIndexed', 'drawIndirect', 'drawIndexedIndirect'] as const)
)
.fn(async t => {
const { limitTest, testValueName, encoderType, drawType, preference } = t.params;
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
async ({ device, testValue, shouldError, actualLimit }) => {
const maxUsableVertexBuffers = Math.min(
device.limits.maxVertexBuffers,
device.limits.maxVertexAttributes
);
const maxUsableBindGroupsPlusVertexBuffers =
device.limits.maxBindGroups + maxUsableVertexBuffers;
t.skipIf(
actualLimit > maxUsableBindGroupsPlusVertexBuffers,
`can not test because the max usable bindGroups + vertexBuffers (${maxUsableBindGroupsPlusVertexBuffers}) is < the maxBindGroupsAndVertexBuffers(${actualLimit})`
);

// Get the numVertexBuffers and numBindGroups we could use given testValue as a total.
// We will only use 1 of each but we'll use the last index.
const { numVertexBuffers, numBindGroups } = getNumBindGroupsAndNumVertexBuffers(
device,
preference,
testValue
);

const module = device.createShaderModule({
code: `
@vertex fn vs() -> @builtin(position) vec4f {
return vec4f(0);
}
`,
});
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: { module },
});

const vertexBuffer = device.createBuffer({
size: 16,
usage: GPUBufferUsage.VERTEX,
});
t.trackForCleanup(vertexBuffer);

await t.testGPURenderAndBindingCommandsMixin(
encoderType,
({ bindGroup, mixin }) => {
// Set the last vertex buffer and clear it. This should have no effect
// unless there is a bug in bookkeeping in the implementation.
mixin.setVertexBuffer(device.limits.maxVertexBuffers - 1, vertexBuffer);
mixin.setVertexBuffer(device.limits.maxVertexBuffers - 1, null);

// Set the last bindGroup and clear it. This should have no effect
// unless there is a bug in bookkeeping in the implementation.
mixin.setBindGroup(device.limits.maxBindGroups - 1, bindGroup);
mixin.setBindGroup(device.limits.maxBindGroups - 1, null);

if (numVertexBuffers) {
mixin.setVertexBuffer(numVertexBuffers - 1, vertexBuffer);
}

if (numBindGroups) {
mixin.setBindGroup(numBindGroups - 1, bindGroup);
}

mixin.setPipeline(pipeline);

const indirectBuffer = device.createBuffer({
size: 4,
usage: GPUBufferUsage.INDIRECT,
});
t.trackForCleanup(indirectBuffer);

const indexBuffer = device.createBuffer({
size: 4,
usage: GPUBufferUsage.INDEX,
});
t.trackForCleanup(indexBuffer);

switch (drawType) {
case 'draw':
mixin.draw(0);
break;
case 'drawIndexed':
mixin.setIndexBuffer(indexBuffer, 'uint16');
mixin.drawIndexed(0);
break;
case 'drawIndirect':
mixin.drawIndirect(indirectBuffer, 0);
break;
case 'drawIndexedIndirect':
mixin.setIndexBuffer(indexBuffer, 'uint16');
mixin.drawIndexedIndirect(indirectBuffer, 0);
break;
}
},
shouldError,
`testValue: ${testValue}, actualLimit: ${actualLimit}, shouldError: ${shouldError}`
);

vertexBuffer.destroy();
},
kExtraLimits
);
});
2 changes: 2 additions & 0 deletions src/webgpu/listing_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,8 @@
"webgpu:api,validation,capability_checks,limits,maxBindGroups:createPipelineLayout,at_over:*": { "subcaseMS": 9.310 },
"webgpu:api,validation,capability_checks,limits,maxBindGroups:setBindGroup,at_over:*": { "subcaseMS": 9.984 },
"webgpu:api,validation,capability_checks,limits,maxBindGroups:validate,maxBindGroupsPlusVertexBuffers:*": { "subcaseMS": 11.200 },
"webgpu:api,validation,capability_checks,limits,maxBindGroupsPlusVertexBuffers:createRenderPipeline,at_over:*": { "subcaseMS": 11.200 },
"webgpu:api,validation,capability_checks,limits,maxBindGroupsPlusVertexBuffers:draw,at_over:*": { "subcaseMS": 11.200 },
"webgpu:api,validation,capability_checks,limits,maxBindingsPerBindGroup:createBindGroupLayout,at_over:*": { "subcaseMS": 12.441 },
"webgpu:api,validation,capability_checks,limits,maxBindingsPerBindGroup:createPipeline,at_over:*": { "subcaseMS": 11.179 },
"webgpu:api,validation,capability_checks,limits,maxBindingsPerBindGroup:validate:*": { "subcaseMS": 12.401 },
Expand Down

0 comments on commit 48d1d69

Please sign in to comment.