| import { assert, unreachable } from '../../common/util/util.js'; |
| |
| export const kDefaultVertexShaderCode = ` |
| @vertex fn main() -> @builtin(position) vec4<f32> { |
| return vec4<f32>(0.0, 0.0, 0.0, 1.0); |
| } |
| `; |
| |
| export const kDefaultFragmentShaderCode = ` |
| @fragment fn main() -> @location(0) vec4<f32> { |
| return vec4<f32>(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<f32> |
| }; |
| |
| @vertex fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput { |
| var pos = array<vec2<f32>, 6>( |
| vec2<f32>( 1.0, 1.0), |
| vec2<f32>( 1.0, -1.0), |
| vec2<f32>(-1.0, -1.0), |
| vec2<f32>( 1.0, 1.0), |
| vec2<f32>(-1.0, -1.0), |
| vec2<f32>(-1.0, 1.0)); |
| |
| var output : VertexOutput; |
| output.Position = vec4<f32>(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<f32>(1.0, 0.0, 1.0, 1.0)` and color target 2 a `vec2<u32>(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<f32>, |
| * @location(2) o3 : vec2<u32>, |
| * } |
| * @fragment fn main() -> Outputs { |
| * return Outputs(vec4<f32>(1.0, 0.0, 1.0, 1.0), vec4<u32>(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<f32> { |
| return vec4<f32>(0.0, 0.0, 0.0, 1.0); |
| }`; |
| break; |
| } |
| case 'fragment': { |
| code = ` |
| @fragment fn ${entryPoint}() -> @location(0) vec4<f32> { |
| return vec4<f32>(0.0, 1.0, 0.0, 1.0); |
| }`; |
| break; |
| } |
| case 'empty': |
| default: { |
| code = ''; |
| break; |
| } |
| } |
| return code; |
| } |