import { AccessMode, AddressSpace, AddressSpaceInfo, kAccessModeInfo, kAddressSpaceInfo, } from '../../types.js'; /** An enumerator of shader stages */ export type ShaderStage = 'vertex' | 'fragment' | 'compute'; /** The list of all shader stages */ export const kShaderStages = ['vertex', 'fragment', 'compute'] as const; export function requiredLanguageFeatureHeader(addressSpace: AddressSpace): string { const feature = kAddressSpaceInfo[addressSpace].wgslLanguageFeature; return feature === undefined ? '' : `requires ${feature};\n`; } type AddressSpaceSupportTest = { skipIfLanguageFeatureNotSupported( langFeature: NonNullable ): void; }; export function skipIfImmediateDataNotSupported(t: AddressSpaceSupportTest): void { t.skipIfLanguageFeatureNotSupported('immediate_address_space'); } export function skipIfAddressSpaceNotSupported( t: AddressSpaceSupportTest, addressSpace: AddressSpace ): void { const feature = kAddressSpaceInfo[addressSpace].wgslLanguageFeature; if (feature !== undefined) { t.skipIfLanguageFeatureNotSupported(feature); } } /** * declareEntrypoint emits the WGSL to declare an entry point with the 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 arg - arg specifies the * optional entry point function name, the shader stage, and the body of the * function, excluding any automatically generated return statements. * @returns the WGSL string for the entry point */ export function declareEntryPoint(arg: { name?: string; stage: ShaderStage; body: string; }): string { if (arg.name === undefined) { arg.name = 'main'; } switch (arg.stage) { case 'vertex': return `@vertex fn ${arg.name}() -> @builtin(position) vec4f { ${arg.body} return vec4f(); }`; case 'fragment': return `@fragment fn ${arg.name}() { ${arg.body} }`; case 'compute': return `@compute @workgroup_size(1) fn ${arg.name}() { ${arg.body} }`; } } /** * @returns a WGSL var declaration with given parameters for variable 'x' and * store type i32. */ export function declareVarX(addressSpace: AddressSpace | '', accessMode: AccessMode | ''): string { const parts: string[] = []; if (addressSpace && kAddressSpaceInfo[addressSpace].binding) parts.push('@group(0) @binding(0) '); parts.push('var'); const template_parts: string[] = []; if (addressSpace) template_parts.push(addressSpace); if (accessMode) template_parts.push(accessMode); if (template_parts.length > 0) parts.push(`<${template_parts.join(',')}>`); parts.push(' x: i32;'); return parts.join(''); } /** * @returns a list of booleans indicating valid cases of specifying the address * space. */ export function explicitSpaceExpander(p: { addressSpace: AddressSpace }): readonly boolean[] { const info = kAddressSpaceInfo[p.addressSpace]; return info.spell === 'must' ? [true] : [true, false]; } /** * @returns a list of usable access modes under given experiment conditions, or undefined * if none are allowed. */ export function accessModeExpander(p: { addressSpace: AddressSpace; explicitAccess: boolean; // Whether the access mode will be emitted. }): readonly (AccessMode | '')[] { const info = kAddressSpaceInfo[p.addressSpace]; return p.explicitAccess && info.spellAccessMode !== 'never' ? info.accessModes : ['']; } /** * @returns a WGSL program with a single variable declaration, with the * given parameterization */ export function getVarDeclShader( p: { addressSpace: AddressSpace; // Address space for the variable. explicitSpace: boolean; // Should the address space be explicitly spelled? accessMode: AccessMode | ''; // What access mode to use. explicitAccess: boolean; // Should the access mode be explicitly spelled? stage: ShaderStage; // What shader stage to use. }, additionalBody?: string ): string { const info = kAddressSpaceInfo[p.addressSpace]; const decl = declareVarX( p.explicitSpace ? p.addressSpace : '', p.explicitAccess ? p.accessMode : '' ); const header = requiredLanguageFeatureHeader(p.addressSpace); additionalBody = additionalBody ?? ''; switch (info.scope) { case 'module': return header + decl + '\n' + declareEntryPoint({ stage: p.stage, body: additionalBody }); case 'function': return header + declareEntryPoint({ stage: p.stage, body: decl + '\n' + additionalBody }); } } /** * @returns the WGSL spelling of a pointer type corresponding to a variable * declared with the given parameters. */ export function pointerType(p: { addressSpace: AddressSpace; // Address space to use if p.explicitSpace explicitSpace: boolean; // If false, use 'function' address space accessMode: AccessMode | ''; // The access mode to use, if any ptrStoreType: string; // The store type. }): string { const space = p.explicitSpace ? p.addressSpace : 'function'; const modePart = p.accessMode ? ',' + p.accessMode : ''; return `ptr<${space},${p.ptrStoreType}${modePart}>`; } /** @returns the effective access mode for the given experiment. */ export function effectiveAccessMode( info: AddressSpaceInfo, accessMode: AccessMode | '' ): AccessMode { return accessMode || info.accessModes[0]; // default is first. } /** @returns whether the setup allows reads */ export function supportsRead(p: { addressSpace: AddressSpace; accessMode: AccessMode | ''; }): boolean { const info = kAddressSpaceInfo[p.addressSpace]; const mode = effectiveAccessMode(info, p.accessMode); return info.accessModes.includes(mode) && kAccessModeInfo[mode].read; } /** @returns whether the setup allows writes */ export function supportsWrite(p: { addressSpace: AddressSpace; accessMode: AccessMode | ''; }): boolean { const info = kAddressSpaceInfo[p.addressSpace]; const mode = effectiveAccessMode(info, p.accessMode); return info.accessModes.includes(mode) && kAccessModeInfo[mode].write; }