diff --git a/package-lock.json b/package-lock.json index b06437532f8a..c93268b9404c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@types/serve-index": "^1.9.3", "@typescript-eslint/eslint-plugin": "^6.9.1", "@typescript-eslint/parser": "^6.9.1", - "@webgpu/types": "^0.1.39", + "@webgpu/types": "^0.1.40", "ansi-colors": "4.1.3", "babel-plugin-add-header-comment": "^1.0.3", "babel-plugin-const-enum": "^1.2.0", @@ -1524,9 +1524,9 @@ "dev": true }, "node_modules/@webgpu/types": { - "version": "0.1.39", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.39.tgz", - "integrity": "sha512-sSVNnZpvHMnVX4KLAIx6RSQj2jTIoA4NCQTayarQskTqfrflWVXBZttEhIYWg4mJBW9No8q0g2PFCx2QEnIj/Q==", + "version": "0.1.40", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.40.tgz", + "integrity": "sha512-/BBkHLS6/eQjyWhY2H7Dx5DHcVrS2ICj9owvSRdgtQT6KcafLZA86tPze0xAOsd4FbsYKCUBUQyNi87q7gV7kw==", "dev": true }, "node_modules/abbrev": { @@ -11582,9 +11582,9 @@ "dev": true }, "@webgpu/types": { - "version": "0.1.39", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.39.tgz", - "integrity": "sha512-sSVNnZpvHMnVX4KLAIx6RSQj2jTIoA4NCQTayarQskTqfrflWVXBZttEhIYWg4mJBW9No8q0g2PFCx2QEnIj/Q==", + "version": "0.1.40", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.40.tgz", + "integrity": "sha512-/BBkHLS6/eQjyWhY2H7Dx5DHcVrS2ICj9owvSRdgtQT6KcafLZA86tPze0xAOsd4FbsYKCUBUQyNi87q7gV7kw==", "dev": true }, "abbrev": { diff --git a/package.json b/package.json index dfa253e86001..33a11e016fd8 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@types/serve-index": "^1.9.3", "@typescript-eslint/eslint-plugin": "^6.9.1", "@typescript-eslint/parser": "^6.9.1", - "@webgpu/types": "^0.1.39", + "@webgpu/types": "^0.1.40", "ansi-colors": "4.1.3", "babel-plugin-add-header-comment": "^1.0.3", "babel-plugin-const-enum": "^1.2.0", diff --git a/src/webgpu/api/validation/shader_module/entry_point.spec.ts b/src/webgpu/api/validation/shader_module/entry_point.spec.ts index 1a8da470a406..c956dc302144 100644 --- a/src/webgpu/api/validation/shader_module/entry_point.spec.ts +++ b/src/webgpu/api/validation/shader_module/entry_point.spec.ts @@ -3,6 +3,7 @@ This tests entry point validation of compute/render pipelines and their shader m The entryPoint in shader module include standard "main" and others. The entryPoint assigned in descriptor include: +- Undefined with matching entry point for stage - Matching case (control case) - Empty string - Mistyping @@ -10,7 +11,6 @@ The entryPoint assigned in descriptor include: - Unicode entrypoints and their ASCIIfied version TODO: -- Test unicode normalization (gpuweb/gpuweb#1160) - Fine-tune test cases to reduce number by removing trivially similar cases `; @@ -43,23 +43,52 @@ const kEntryPointTestCases = [ g.test('compute') .desc( ` -Tests calling createComputePipeline(Async) with valid vertex stage shader and different entryPoints, +Tests calling createComputePipeline(Async) with valid compute stage shader and different entryPoints, and check that the APIs only accept matching entryPoint. ` ) - .params(u => u.combine('isAsync', [true, false]).combineWithParams(kEntryPointTestCases)) + .params(u => + u + .combine('isAsync', [true, false]) + .combine('shaderModuleStage', ['compute', 'vertex', 'fragment'] as const) + .beginSubcases() + .combine('provideEntryPoint', [true, false]) + .combine('extraEntryPoint', [true, false]) + .combineWithParams(kEntryPointTestCases) + ) .fn(t => { - const { isAsync, shaderModuleEntryPoint, stageEntryPoint } = t.params; + const { + isAsync, + provideEntryPoint, + extraEntryPoint, + shaderModuleStage, + shaderModuleEntryPoint, + stageEntryPoint, + } = t.params; + const entryPoint = provideEntryPoint ? stageEntryPoint : undefined; + let code = getShaderWithEntryPoint(shaderModuleStage, shaderModuleEntryPoint); + if (extraEntryPoint) { + code += ` ${getShaderWithEntryPoint(shaderModuleStage, 'extra')}`; + } const descriptor: GPUComputePipelineDescriptor = { layout: 'auto', compute: { module: t.device.createShaderModule({ - code: getShaderWithEntryPoint('compute', shaderModuleEntryPoint), + code, }), - entryPoint: stageEntryPoint, + entryPoint, }, }; - const _success = shaderModuleEntryPoint === stageEntryPoint; + let _success = true; + if (shaderModuleStage !== 'compute') { + _success = false; + } + if (!provideEntryPoint && extraEntryPoint) { + _success = false; + } + if (shaderModuleEntryPoint !== stageEntryPoint && provideEntryPoint) { + _success = false; + } t.doCreateComputePipelineTest(isAsync, _success, descriptor); }); @@ -70,19 +99,46 @@ Tests calling createRenderPipeline(Async) with valid vertex stage shader and dif and check that the APIs only accept matching entryPoint. ` ) - .params(u => u.combine('isAsync', [true, false]).combineWithParams(kEntryPointTestCases)) + .params(u => + u + .combine('isAsync', [true, false]) + .combine('shaderModuleStage', ['compute', 'vertex', 'fragment'] as const) + .beginSubcases() + .combine('provideEntryPoint', [true, false]) + .combine('extraEntryPoint', [true, false]) + .combineWithParams(kEntryPointTestCases) + ) .fn(t => { - const { isAsync, shaderModuleEntryPoint, stageEntryPoint } = t.params; + const { + isAsync, + provideEntryPoint, + extraEntryPoint, + shaderModuleStage, + shaderModuleEntryPoint, + stageEntryPoint, + } = t.params; + const entryPoint = provideEntryPoint ? stageEntryPoint : undefined; + let code = getShaderWithEntryPoint(shaderModuleStage, shaderModuleEntryPoint); + if (extraEntryPoint) { + code += ` ${getShaderWithEntryPoint(shaderModuleStage, 'extra')}`; + } const descriptor: GPURenderPipelineDescriptor = { layout: 'auto', vertex: { - module: t.device.createShaderModule({ - code: getShaderWithEntryPoint('vertex', shaderModuleEntryPoint), - }), - entryPoint: stageEntryPoint, + module: t.device.createShaderModule({ code }), + entryPoint, }, }; - const _success = shaderModuleEntryPoint === stageEntryPoint; + let _success = true; + if (shaderModuleStage !== 'vertex') { + _success = false; + } + if (!provideEntryPoint && extraEntryPoint) { + _success = false; + } + if (shaderModuleEntryPoint !== stageEntryPoint && provideEntryPoint) { + _success = false; + } t.doCreateRenderPipelineTest(isAsync, _success, descriptor); }); @@ -93,25 +149,155 @@ Tests calling createRenderPipeline(Async) with valid fragment stage shader and d and check that the APIs only accept matching entryPoint. ` ) - .params(u => u.combine('isAsync', [true, false]).combineWithParams(kEntryPointTestCases)) + .params(u => + u + .combine('isAsync', [true, false]) + .combine('shaderModuleStage', ['compute', 'vertex', 'fragment'] as const) + .beginSubcases() + .combine('provideEntryPoint', [true, false]) + .combine('extraEntryPoint', [true, false]) + .combineWithParams(kEntryPointTestCases) + ) .fn(t => { - const { isAsync, shaderModuleEntryPoint, stageEntryPoint } = t.params; + const { + isAsync, + provideEntryPoint, + extraEntryPoint, + shaderModuleStage, + shaderModuleEntryPoint, + stageEntryPoint, + } = t.params; + const entryPoint = provideEntryPoint ? stageEntryPoint : undefined; + let code = getShaderWithEntryPoint(shaderModuleStage, shaderModuleEntryPoint); + if (extraEntryPoint) { + code += ` ${getShaderWithEntryPoint(shaderModuleStage, 'extra')}`; + } const descriptor: GPURenderPipelineDescriptor = { layout: 'auto', vertex: { module: t.device.createShaderModule({ code: kDefaultVertexShaderCode, }), - entryPoint: 'main', }, fragment: { module: t.device.createShaderModule({ - code: getShaderWithEntryPoint('fragment', shaderModuleEntryPoint), + code, }), - entryPoint: stageEntryPoint, + entryPoint, targets: [{ format: 'rgba8unorm' }], }, }; - const _success = shaderModuleEntryPoint === stageEntryPoint; + let _success = true; + if (shaderModuleStage !== 'fragment') { + _success = false; + } + if (!provideEntryPoint && extraEntryPoint) { + _success = false; + } + if (shaderModuleEntryPoint !== stageEntryPoint && provideEntryPoint) { + _success = false; + } t.doCreateRenderPipelineTest(isAsync, _success, descriptor); }); + +g.test('compute_undefined_entry_point_and_extra_stage') + .desc( + ` +Tests calling createComputePipeline(Async) with compute stage shader and +an undefined entryPoint is valid if there's an extra shader stage. +` + ) + .params(u => + u + .combine('isAsync', [true, false]) + .combine('extraShaderModuleStage', ['compute', 'vertex', 'fragment'] as const) + ) + .fn(t => { + const { isAsync, extraShaderModuleStage } = t.params; + const code = ` + ${getShaderWithEntryPoint('compute', 'main')} + ${getShaderWithEntryPoint(extraShaderModuleStage, 'extra')} + `; + const descriptor: GPUComputePipelineDescriptor = { + layout: 'auto', + compute: { + module: t.device.createShaderModule({ + code, + }), + entryPoint: undefined, + }, + }; + + const success = extraShaderModuleStage !== 'compute'; + t.doCreateComputePipelineTest(isAsync, success, descriptor); + }); + +g.test('vertex_undefined_entry_point_and_extra_stage') + .desc( + ` +Tests calling createRenderPipeline(Async) with vertex stage shader and +an undefined entryPoint is valid if there's an extra shader stage. +` + ) + .params(u => + u + .combine('isAsync', [true, false]) + .combine('extraShaderModuleStage', ['compute', 'vertex', 'fragment'] as const) + ) + .fn(t => { + const { isAsync, extraShaderModuleStage } = t.params; + const code = ` + ${getShaderWithEntryPoint('vertex', 'main')} + ${getShaderWithEntryPoint(extraShaderModuleStage, 'extra')} + `; + const descriptor: GPURenderPipelineDescriptor = { + layout: 'auto', + vertex: { + module: t.device.createShaderModule({ + code, + }), + entryPoint: undefined, + }, + }; + + const success = extraShaderModuleStage !== 'vertex'; + t.doCreateRenderPipelineTest(isAsync, success, descriptor); + }); + +g.test('fragment_undefined_entry_point_and_extra_stage') + .desc( + ` +Tests calling createRenderPipeline(Async) with fragment stage shader and +an undefined entryPoint is valid if there's an extra shader stage. +` + ) + .params(u => + u + .combine('isAsync', [true, false]) + .combine('extraShaderModuleStage', ['compute', 'vertex', 'fragment'] as const) + ) + .fn(t => { + const { isAsync, extraShaderModuleStage } = t.params; + const code = ` + ${getShaderWithEntryPoint('fragment', 'main')} + ${getShaderWithEntryPoint(extraShaderModuleStage, 'extra')} + `; + const descriptor: GPURenderPipelineDescriptor = { + layout: 'auto', + vertex: { + module: t.device.createShaderModule({ + code: kDefaultVertexShaderCode, + }), + }, + fragment: { + module: t.device.createShaderModule({ + code, + }), + entryPoint: undefined, + targets: [{ format: 'rgba8unorm' }], + }, + }; + + const success = extraShaderModuleStage !== 'fragment'; + t.doCreateRenderPipelineTest(isAsync, success, descriptor); + }); diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index c6d8415388d8..4671874feb95 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -775,8 +775,11 @@ "webgpu:api,validation,resource_usages,texture,in_render_misc:subresources,set_unused_bind_group:*": { "subcaseMS": 6.200 }, "webgpu:api,validation,resource_usages,texture,in_render_misc:subresources,texture_usages_in_copy_and_render_pass:*": { "subcaseMS": 4.763 }, "webgpu:api,validation,shader_module,entry_point:compute:*": { "subcaseMS": 4.439 }, + "webgpu:api,validation,shader_module,entry_point:compute_undefined_entry_point_and_extra_stage:*": { "subcaseMS": 17.075 }, "webgpu:api,validation,shader_module,entry_point:fragment:*": { "subcaseMS": 5.865 }, + "webgpu:api,validation,shader_module,entry_point:fragment_undefined_entry_point_and_extra_stage:*": { "subcaseMS": 16.050 }, "webgpu:api,validation,shader_module,entry_point:vertex:*": { "subcaseMS": 5.803 }, + "webgpu:api,validation,shader_module,entry_point:vertex_undefined_entry_point_and_extra_stage:*": { "subcaseMS": 15.851 }, "webgpu:api,validation,shader_module,overrides:id_conflict:*": { "subcaseMS": 36.700 }, "webgpu:api,validation,shader_module,overrides:name_conflict:*": { "subcaseMS": 1.500 }, "webgpu:api,validation,state,device_lost,destroy:command,clearBuffer:*": { "subcaseMS": 11.826 },