diff --git a/src/webgpu/api/validation/render_pipeline/resource_compatibility.spec.ts b/src/webgpu/api/validation/render_pipeline/resource_compatibility.spec.ts new file mode 100644 index 000000000000..5d6bc8d12547 --- /dev/null +++ b/src/webgpu/api/validation/render_pipeline/resource_compatibility.spec.ts @@ -0,0 +1,95 @@ +export const description = ` +Tests for resource compatibilty between pipeline layout and shader modules + `; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { keysOf } from '../../../../common/util/data_tables.js'; +import { + kAPIResources, + getWGSLShaderForResource, + getAPIBindGroupLayoutForResource, + doResourcesMatch, +} from '../utils.js'; + +import { CreateRenderPipelineValidationTest } from './common.js'; + +export const g = makeTestGroup(CreateRenderPipelineValidationTest); + +g.test('resource_compatibility') + .desc( + 'Tests validation of resource (bind group) compatibility between pipeline layout and WGSL shader' + ) + .params(u => + u // + .combine('stage', ['vertex', 'fragment'] as const) + .combine('apiResource', keysOf(kAPIResources)) + .filter(t => { + const res = kAPIResources[t.apiResource]; + if (t.stage === 'vertex') { + if (res.buffer && res.buffer.type === 'storage') { + return false; + } + if (res.storageTexture && res.storageTexture.access !== 'read-only') { + return false; + } + } + return true; + }) + .beginSubcases() + .combine('isAsync', [true, false] as const) + .combine('wgslResource', keysOf(kAPIResources)) + ) + .fn(t => { + const apiResource = kAPIResources[t.params.apiResource]; + const wgslResource = kAPIResources[t.params.wgslResource]; + t.skipIf( + wgslResource.storageTexture !== undefined && + wgslResource.storageTexture.access !== 'write-only' && + !t.hasLanguageFeature('readonly_and_readwrite_storage_textures'), + 'Storage textures require language feature' + ); + const emptyVS = ` +@vertex +fn main() -> @builtin(position) vec4f { + return vec4f(); +} +`; + const emptyFS = ` +@fragment +fn main() -> @location(0) vec4f { + return vec4f(); +} +`; + + const code = getWGSLShaderForResource(t.params.stage, wgslResource); + const vsCode = t.params.stage === 'vertex' ? code : emptyVS; + const fsCode = t.params.stage === 'fragment' ? code : emptyFS; + const gpuStage: GPUShaderStageFlags = + t.params.stage === 'vertex' ? GPUShaderStage.VERTEX : GPUShaderStage.FRAGMENT; + const layout = t.device.createPipelineLayout({ + bindGroupLayouts: [getAPIBindGroupLayoutForResource(t.device, gpuStage, apiResource)], + }); + + const descriptor = { + layout, + vertex: { + module: t.device.createShaderModule({ + code: vsCode, + }), + entryPoint: 'main', + }, + fragment: { + module: t.device.createShaderModule({ + code: fsCode, + }), + entryPoint: 'main', + targets: [{ format: 'rgba8unorm' }] as const, + }, + }; + + t.doCreateRenderPipelineTest( + t.params.isAsync, + doResourcesMatch(apiResource, wgslResource), + descriptor + ); + }); diff --git a/src/webgpu/api/validation/utils.ts b/src/webgpu/api/validation/utils.ts index 1c9b77ea0015..e6ee87f797d7 100644 --- a/src/webgpu/api/validation/utils.ts +++ b/src/webgpu/api/validation/utils.ts @@ -153,8 +153,15 @@ export function getWGSLShaderForResource(stage: string, resource: Resource): str code += `@workgroup_size(1)`; } - const retTy = stage === 'vertex' ? ' -> @builtin(position) vec4f' : ''; - const retVal = stage === 'vertex' ? 'return vecf();' : ''; + let retTy = ''; + let retVal = ''; + if (stage === 'vertex') { + retTy = ' -> @builtin(position) vec4f'; + retVal = 'return vec4f();'; + } else if (stage === 'fragment') { + retTy = ' -> @location(0) vec4f'; + retVal = 'return vec4f();'; + } code += ` fn main() ${retTy} { _ = ${resource.staticUse ?? 'res'}; diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index 72b9bfdb09e7..e5cbb483c0c8 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -747,6 +747,7 @@ "webgpu:api,validation,render_pipeline,overrides:value,validation_error,vertex:*": { "subcaseMS": 6.022 }, "webgpu:api,validation,render_pipeline,primitive_state:strip_index_format:*": { "subcaseMS": 5.267 }, "webgpu:api,validation,render_pipeline,primitive_state:unclipped_depth:*": { "subcaseMS": 1.025 }, + "webgpu:api,validation,render_pipeline,resource_compatibility:resource_compatibility:*": { "subcaseMS": 1.025 }, "webgpu:api,validation,render_pipeline,shader_module:device_mismatch:*": { "subcaseMS": 0.700 }, "webgpu:api,validation,render_pipeline,shader_module:invalid,fragment:*": { "subcaseMS": 5.800 }, "webgpu:api,validation,render_pipeline,shader_module:invalid,vertex:*": { "subcaseMS": 15.151 },