/** * Generate an entry point that uses an entry point IO variable. * * @param {Object} params * @param params.attribute The entry point IO attribute. * @param params.type The type to use for the entry point IO variable. * @param params.stage The shader stage. * @param params.io An "in|out" string specifying whether the entry point IO is an input or an output. * @param params.use_struct True to wrap the entry point IO in a struct. * @returns The generated shader code. */ export function generateShader({ attribute, type, stage, io, use_struct, enable, }: { attribute: string; type: string; stage: string; io: string; use_struct: boolean; enable?: string; }) { let code = ''; if (enable) { code += `enable ${enable};\n`; } if (use_struct) { // Generate a struct that wraps the entry point IO variable. code += 'struct S {\n'; code += ` ${attribute} value : ${type},\n`; if (stage === 'vertex' && io === 'out' && !attribute.includes('builtin(position)')) { // Add position builtin for vertex outputs. code += ` @builtin(position) position : vec4,\n`; } code += '};\n\n'; } if (stage !== '') { // Generate the entry point attributes. code += `@${stage}`; if (stage === 'compute') { code += ' @workgroup_size(1)'; } } // Generate the entry point parameter and return type. let param = ''; let retType = ''; let retVal = ''; if (io === 'in') { if (use_struct) { param = `in : S`; } else { param = `${attribute} value : ${type}`; } // Vertex shaders must always return `@builtin(position)`. if (stage === 'vertex') { retType = `-> @builtin(position) vec4`; retVal = `return vec4();`; } } else if (io === 'out') { if (use_struct) { retType = '-> S'; retVal = `return S();`; } else { retType = `-> ${attribute} ${type}`; retVal = `return ${type}();`; } } code += ` fn main(${param}) ${retType} { ${retVal} } `; return code; } /** * ResourceDeclarationEmitter is a function that emits the WGSL declaring a resource variable with * the given group, binding and name. */ export type ResourceDeclarationEmitter = (name: string, group?: number, binding?: number) => string; /** Helper function for emitting a resource declaration's group and binding attributes */ function groupAndBinding(group?: number, binding?: number): string { return ( `${group !== undefined ? `@group(${group})` : '/* no group */'} ` + `${binding !== undefined ? `@binding(${binding})` : '/* no binding */'}` ); } /** Helper function for emitting a resource declaration for the given type */ function basicEmitter(type: string): ResourceDeclarationEmitter { return (name: string, group?: number, binding?: number) => `${groupAndBinding(group, binding)} var ${name} : ${type};\n`; } /** Map of resource declaration name, to an emitter. */ export const kResourceEmitters = new Map([ ['texture_1d', basicEmitter('texture_1d')], ['texture_2d', basicEmitter('texture_2d')], ['texture_2d_array', basicEmitter('texture_2d_array')], ['texture_3d', basicEmitter('texture_3d')], ['texture_cube', basicEmitter('texture_cube')], ['texture_cube_array', basicEmitter('texture_cube_array')], ['texture_multisampled_2d', basicEmitter('texture_multisampled_2d')], ['texture_external', basicEmitter('texture_external')], ['texture_storage_1d', basicEmitter('texture_storage_1d')], ['texture_storage_2d', basicEmitter('texture_storage_2d')], ['texture_storage_2d_array', basicEmitter('texture_storage_2d_array')], ['texture_storage_3d', basicEmitter('texture_storage_3d')], ['texture_depth_2d', basicEmitter('texture_depth_2d')], ['texture_depth_2d_array', basicEmitter('texture_depth_2d_array')], ['texture_depth_cube', basicEmitter('texture_depth_cube')], ['texture_depth_cube_array', basicEmitter('texture_depth_cube_array')], ['texture_depth_multisampled_2d', basicEmitter('texture_depth_multisampled_2d')], ['sampler', basicEmitter('sampler')], ['sampler_comparison', basicEmitter('sampler_comparison')], [ 'uniform', (name: string, group?: number, binding?: number) => `${groupAndBinding(group, binding)} var ${name} : array, 16>;\n`, ], [ 'storage', (name: string, group?: number, binding?: number) => `${groupAndBinding(group, binding)} var ${name} : array, 16>;\n`, ], ]); /** All possible resource types for use as test permutations. */ export const kResourceKindsAll = [ 'texture_1d', 'texture_2d', 'texture_2d_array', 'texture_3d', 'texture_cube', 'texture_cube_array', 'texture_multisampled_2d', 'texture_external', 'texture_storage_1d', 'texture_storage_2d', 'texture_storage_2d_array', 'texture_storage_3d', 'texture_depth_2d', 'texture_depth_2d_array', 'texture_depth_cube', 'texture_depth_cube_array', 'texture_depth_multisampled_2d', 'sampler', 'sampler_comparison', 'uniform', 'storage', ]; /** A small selection of resource declaration names, which can be used in test permutations */ export const kResourceKindsA = ['storage', 'texture_2d', 'texture_external', 'uniform']; /** A small selection of resource declaration names, which can be used in test permutations */ export const kResourceKindsB = ['texture_3d', 'texture_storage_1d', 'uniform']; /** An enumerator of shader stages */ export type ShaderStage = 'vertex' | 'fragment' | 'compute'; /** * declareEntrypoint emits the WGSL to declare an entry point with the given name, stage and body. * The generated function will have an appropriate return type and return statement, so that `body` * does not have to change between stage. * @param name the entry point function name * @param stage the entry point stage * @param body the body of the function (excluding any automatically suffixed return statements) * @returns the WGSL string for the entry point */ export function declareEntrypoint(name: string, stage: ShaderStage, body: string): string { switch (stage) { case 'vertex': return `@vertex fn ${name}() -> @builtin(position) vec4f { ${body} return vec4f(); }`; case 'fragment': return `@fragment fn ${name}() { ${body} }`; case 'compute': return `@compute @workgroup_size(1) fn ${name}() { ${body} }`; } }