blob: aad0e3a97f82f9fbfe0a3568a308f7679d7074b0 [file] [log] [blame]
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;
}