diff --git a/src/common/framework/preprocessor.ts b/src/common/framework/preprocessor.ts index 9d4f75ca3e1b..5dd683684f51 100644 --- a/src/common/framework/preprocessor.ts +++ b/src/common/framework/preprocessor.ts @@ -23,7 +23,7 @@ abstract class Directive { protected checkDepth(stack: StateStack): void { assert( stack.length === this.depth, - `Number of "$"s must match nesting depth, currently ${stack.length} (e.g. $if $$if $$endif $endif)` + `Number of "_"s must match nesting depth, currently ${stack.length} (e.g. _if __if __endif _endif)` ); } @@ -87,6 +87,18 @@ class EndIf extends Directive { } } +class Join { + private lines: string[]; + + constructor(lines: string[]) { + this.lines = lines; + } + + join(indentation: number) { + return this.lines.join('\n' + ' '.repeat(indentation)); + } +} + /** * A simple template-based, non-line-based preprocessor implementing if/elif/else/endif. * @@ -104,10 +116,13 @@ class EndIf extends Directive { * * @param strings - The array of constant string chunks of the template string. * @param ...values - The array of interpolated ${} values within the template string. + * If a value is a Directive, it affects the preprocessor state and injects no string. + * If a value is a string[], the array is joined together with newlines, preserving vertical + * alignment by indenting with spaces. */ export function pp( strings: TemplateStringsArray, - ...values: ReadonlyArray + ...values: ReadonlyArray ): string { let result = ''; const stateStack: StateStack = [{ allowsFollowingElse: false, state: State.Passing }]; @@ -121,6 +136,11 @@ export function pp( const value = values[i]; if (value instanceof Directive) { value.applyTo(stateStack); + } else if (value instanceof Join) { + const indexOfLastNewline = result.lastIndexOf('\n'); + // Vertically align by indenting with spaces. Works even if indexOfLastNewline === -1. + const indentation = result.length - indexOfLastNewline - 1; + result += value.join(indentation); } else { if (passing) { result += value; @@ -132,6 +152,7 @@ export function pp( return result; } +pp.join = (args: string[]) => new Join(args); pp._if = (predicate: boolean) => new If(1, predicate); pp._elif = (predicate: boolean) => new ElseIf(1, predicate); pp._else = new Else(1); diff --git a/src/unittests/preprocessor.spec.ts b/src/unittests/preprocessor.spec.ts index 2f5f19d27c1f..74d756d03e96 100644 --- a/src/unittests/preprocessor.spec.ts +++ b/src/unittests/preprocessor.spec.ts @@ -58,6 +58,35 @@ b`; t.test(act, exp); }); +g.test('join,0').fn(t => { + const act = pp`a ${pp.join([])} b`; + const exp = 'a b'; + t.test(act, exp); +}); + +g.test('join,1').fn(t => { + const act = pp`a ${pp.join(['3'])} b`; + const exp = 'a 3 b'; + t.test(act, exp); +}); + +g.test('join,2').fn(t => { + const act = pp`a ${pp.join(['3', '4'])} b`; + const exp = `\ +a 3 + 4 b`; + t.test(act, exp); +}); + +g.test('join,22').fn(t => { + const act = pp`a ${pp.join(['33333', '4'])} b ${pp.join(['5', '66'])} d`; + const exp = `\ +a 33333 + 4 b 5 + 66 d`; + t.test(act, exp); +}); + g.test('if,true').fn(t => { const act = pp` a diff --git a/src/webgpu/api/validation/resource_usages/textureUsageInPassEncoder.spec.ts b/src/webgpu/api/validation/resource_usages/textureUsageInPassEncoder.spec.ts index ea8f13689fff..1ab225cf2083 100644 --- a/src/webgpu/api/validation/resource_usages/textureUsageInPassEncoder.spec.ts +++ b/src/webgpu/api/validation/resource_usages/textureUsageInPassEncoder.spec.ts @@ -39,6 +39,7 @@ Test Coverage: `; import { pbool, poptions, params } from '../../../../common/framework/params_builder.js'; +import { pp } from '../../../../common/framework/preprocessor.js'; import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { assert } from '../../../../common/framework/util/util.js'; import { @@ -925,9 +926,9 @@ g.test('unused_bindings_in_pipeline') entry_point vertex = main; `; // TODO: revisit the shader code once 'image' can be supported in wgsl. - const wgslFragment = ` - ${useBindGroup0 ? '[[set 0, binding 0]] var image0;' : ''} - ${useBindGroup1 ? '[[set 1, binding 0]] var image1;' : ''} + const wgslFragment = pp` + ${pp._if(useBindGroup0)}[[set 0, binding 0]] var image0;${pp._endif} + ${pp._if(useBindGroup1)}[[set 1, binding 0]] var image1;${pp._endif} fn main() -> void { return; } @@ -937,8 +938,8 @@ g.test('unused_bindings_in_pipeline') // TODO: revisit the shader code once 'image' can be supported in wgsl. const wgslCompute = ` - ${useBindGroup0 ? '[[set 0, binding 0]] var image0;' : ''} - ${useBindGroup1 ? '[[set 1, binding 0]] var image1;' : ''} + ${pp._if(useBindGroup0)}[[set 0, binding 0]] var image0;${pp._endif} + ${pp._if(useBindGroup1)}[[set 1, binding 0]] var image1;${pp._endif} fn main() -> void { return; } diff --git a/src/webgpu/util/texture/texelData.spec.ts b/src/webgpu/util/texture/texelData.spec.ts index 8c4149a79cab..f79eb4ce0209 100644 --- a/src/webgpu/util/texture/texelData.spec.ts +++ b/src/webgpu/util/texture/texelData.spec.ts @@ -1,6 +1,7 @@ export const description = 'Test helpers for texel data produce the expected data in the shader'; import { params, poptions } from '../../../common/framework/params_builder.js'; +import { pp } from '../../../common/framework/preprocessor.js'; import { makeTestGroup } from '../../../common/framework/test_group.js'; import { unreachable, assert } from '../../../common/framework/util/util.js'; import { @@ -68,20 +69,18 @@ function doTest( unreachable(); } - const shader = ` + const shader = pp` [[set(0), binding(0)]] var tex : texture_2d<${shaderType}>; [[block]] struct Output { - ${rep.componentOrder - .map((C, i) => `[[offset(${i * 4})]] result${C} : ${shaderType};`) - .join('\n')} + ${pp.join(rep.componentOrder.map((C, i) => `[[offset(${i * 4})]] result${C} : ${shaderType};`))} }; [[set(0), binding(1)]] var output : Output; [[stage(compute)]] fn main() -> void { var texel : vec4<${shaderType}> = textureLoad(tex, vec2(0, 0), 0); - ${rep.componentOrder.map(C => `output.result${C} = texel.${C.toLowerCase()};`).join('\n')} + ${pp.join(rep.componentOrder.map(C => `output.result${C} = texel.${C.toLowerCase()};`))} return; }`;