From 48d1d69e1e66fac327ce0c27ee370a2bf74310cd Mon Sep 17 00:00:00 2001 From: Gregg Tavares Date: Mon, 5 Feb 2024 15:27:39 -0800 Subject: [PATCH] Test maxBindGroupsPlusVertexBuffers limits --- .../capability_checks/limits/limit_utils.ts | 23 ++ .../maxBindGroupsPlusVertexBuffers.spec.ts | 274 ++++++++++++++++++ src/webgpu/listing_meta.json | 2 + 3 files changed, 299 insertions(+) create mode 100644 src/webgpu/api/validation/capability_checks/limits/maxBindGroupsPlusVertexBuffers.spec.ts diff --git a/src/webgpu/api/validation/capability_checks/limits/limit_utils.ts b/src/webgpu/api/validation/capability_checks/limits/limit_utils.ts index 8f37038ba737..36c70fef1a0d 100644 --- a/src/webgpu/api/validation/capability_checks/limits/limit_utils.ts +++ b/src/webgpu/api/validation/capability_checks/limits/limit_utils.ts @@ -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. */ diff --git a/src/webgpu/api/validation/capability_checks/limits/maxBindGroupsPlusVertexBuffers.spec.ts b/src/webgpu/api/validation/capability_checks/limits/maxBindGroupsPlusVertexBuffers.spec.ts new file mode 100644 index 000000000000..a2e5f5132b8d --- /dev/null +++ b/src/webgpu/api/validation/capability_checks/limits/maxBindGroupsPlusVertexBuffers.spec.ts @@ -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 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 + ); + }); diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index 4357795529c6..cf8a195fc372 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -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 },