import { assert, unreachable } from '../../common/util/util.js'; export const kDefaultVertexShaderCode = ` @vertex fn main() -> @builtin(position) vec4 { return vec4(0.0, 0.0, 0.0, 1.0); } `; export const kDefaultFragmentShaderCode = ` @fragment fn main() -> @location(0) vec4 { return vec4(1.0, 1.0, 1.0, 1.0); }`; // MAINTENANCE_TODO(#3344): deduplicate fullscreen quad shader code. export const kFullscreenQuadVertexShaderCode = ` struct VertexOutput { @builtin(position) Position : vec4 }; @vertex fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput { var pos = array, 6>( vec2( 1.0, 1.0), vec2( 1.0, -1.0), vec2(-1.0, -1.0), vec2( 1.0, 1.0), vec2(-1.0, -1.0), vec2(-1.0, 1.0)); var output : VertexOutput; output.Position = vec4(pos[VertexIndex], 0.0, 1.0); return output; } `; const kPlainTypeInfo = { i32: { suffix: '', fractionDigits: 0, }, u32: { suffix: 'u', fractionDigits: 0, }, f32: { suffix: '', fractionDigits: 4, }, }; /** * * @param sampleType sampleType of texture format * @returns plain type compatible of the sampleType */ export function getPlainTypeInfo(sampleType: GPUTextureSampleType): keyof typeof kPlainTypeInfo { switch (sampleType) { case 'sint': return 'i32'; case 'uint': return 'u32'; case 'float': case 'unfilterable-float': case 'depth': return 'f32'; default: unreachable(); } } /** * Build a fragment shader based on output value and types * e.g. write to color target 0 a `vec4(1.0, 0.0, 1.0, 1.0)` and color target 2 a `vec2(1, 2)` * ``` * outputs: [ * { * values: [1, 0, 1, 1],, * plainType: 'f32', * componentCount: 4, * }, * null, * { * values: [1, 2], * plainType: 'u32', * componentCount: 2, * }, * ] * ``` * * return: * ``` * struct Outputs { * @location(0) o1 : vec4, * @location(2) o3 : vec2, * } * @fragment fn main() -> Outputs { * return Outputs(vec4(1.0, 0.0, 1.0, 1.0), vec4(1, 2)); * } * ``` * * If fragDepth is given there will be an extra @builtin(frag_depth) output with the specified value assigned. * * @param outputs the shader outputs for each location attribute * @param fragDepth the shader outputs frag_depth value (optional) * @returns the fragment shader string */ export function getFragmentShaderCodeWithOutput( outputs: ({ values: readonly number[]; plainType: 'i32' | 'u32' | 'f32'; componentCount: number; } | null)[], fragDepth: { value: number } | null = null, dualSourceBlending: boolean = false ): string { if (outputs.length === 0) { if (fragDepth) { return ` @fragment fn main() -> @builtin(frag_depth) f32 { return ${fragDepth.value.toFixed(kPlainTypeInfo['f32'].fractionDigits)}; }`; } return ` @fragment fn main() { }`; } const resultStrings = [] as string[]; let outputStructString = ''; if (fragDepth) { resultStrings.push(`${fragDepth.value.toFixed(kPlainTypeInfo['f32'].fractionDigits)}`); outputStructString += `@builtin(frag_depth) depth_out: f32,\n`; } for (let i = 0; i < outputs.length; i++) { const o = outputs[i]; if (o === null) { continue; } const plainType = o.plainType; const { suffix, fractionDigits } = kPlainTypeInfo[plainType]; let outputType; const v = o.values.map(n => n.toFixed(fractionDigits)); switch (o.componentCount) { case 1: outputType = plainType; resultStrings.push(`${v[0]}${suffix}`); break; case 2: outputType = `vec2<${plainType}>`; resultStrings.push(`${outputType}(${v[0]}${suffix}, ${v[1]}${suffix})`); break; case 3: outputType = `vec3<${plainType}>`; resultStrings.push(`${outputType}(${v[0]}${suffix}, ${v[1]}${suffix}, ${v[2]}${suffix})`); break; case 4: outputType = `vec4<${plainType}>`; resultStrings.push( `${outputType}(${v[0]}${suffix}, ${v[1]}${suffix}, ${v[2]}${suffix}, ${v[3]}${suffix})` ); break; default: unreachable(); } if (dualSourceBlending) { assert(i === 0 && outputs.length === 1); outputStructString += ` @location(0) @blend_src(0) o0 : ${outputType}, @location(0) @blend_src(1) o0_blend : ${outputType}, `; resultStrings.push(resultStrings[0]); break; } else { outputStructString += `@location(${i}) o${i} : ${outputType},\n`; } } return ` ${dualSourceBlending ? 'enable dual_source_blending;' : ''} struct Outputs { ${outputStructString} } @fragment fn main() -> Outputs { return Outputs(${resultStrings.join(',')}); }`; } export const kValidShaderStages = ['compute', 'vertex', 'fragment'] as const; export type TValidShaderStage = (typeof kValidShaderStages)[number]; export type TShaderStage = TValidShaderStage | 'empty'; /** * Return a foo shader of the given stage with the given entry point * @param shaderStage * @param entryPoint * @returns the shader string */ export function getShaderWithEntryPoint(shaderStage: TShaderStage, entryPoint: string): string { let code; switch (shaderStage) { case 'compute': { code = `@compute @workgroup_size(1) fn ${entryPoint}() {}`; break; } case 'vertex': { code = ` @vertex fn ${entryPoint}() -> @builtin(position) vec4 { return vec4(0.0, 0.0, 0.0, 1.0); }`; break; } case 'fragment': { code = ` @fragment fn ${entryPoint}() -> @location(0) vec4 { return vec4(0.0, 1.0, 0.0, 1.0); }`; break; } case 'empty': default: { code = ''; break; } } return code; }