playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
643 lines (637 loc) • 24 kB
JavaScript
import {
BINDGROUP_MESH,
semanticToLocation,
SHADERSTAGE_VERTEX,
SHADERSTAGE_FRAGMENT,
SAMPLETYPE_FLOAT,
TEXTUREDIMENSION_2D,
TEXTUREDIMENSION_2D_ARRAY,
TEXTUREDIMENSION_CUBE,
TEXTUREDIMENSION_3D,
TEXTUREDIMENSION_1D,
TEXTUREDIMENSION_CUBE_ARRAY,
SAMPLETYPE_INT,
SAMPLETYPE_UINT,
SAMPLETYPE_DEPTH,
SAMPLETYPE_UNFILTERABLE_FLOAT,
BINDGROUP_MESH_UB,
uniformTypeToNameWGSL,
uniformTypeToNameMapWGSL,
bindGroupNames,
UNIFORMTYPE_FLOAT,
UNUSED_UNIFORM_NAME,
TYPE_FLOAT32,
TYPE_FLOAT16,
TYPE_INT8,
TYPE_INT16,
TYPE_INT32
} from "../constants.js";
import { UniformFormat, UniformBufferFormat } from "../uniform-buffer-format.js";
import { BindGroupFormat, BindStorageBufferFormat, BindTextureFormat } from "../bind-group-format.js";
const KEYWORD = /^[ \t]*(attribute|varying|uniform)[\t ]+/gm;
const KEYWORD_LINE = /^[ \t]*(attribute|varying|uniform)[ \t]*([^;]+)(;+)/gm;
const KEYWORD_RESOURCE = /^[ \t]*var\s*(?:(<storage,[^>]*>)\s*([\w\d_]+)\s*:\s*(.*?)\s*;|(<(?!storage,)[^>]*>)?\s*([\w\d_]+)\s*:\s*(texture_.*|storage_texture_.*|storage\w.*|external_texture|sampler(?:_comparison)?)\s*;)\s*$/gm;
const VARYING = /(?:@interpolate\([^)]*\)\s*)?([\w]+)\s*:\s*([\w<>]+)/;
const MARKER = "@@@";
const ENTRY_FUNCTION = /(@vertex|@fragment)\s*fn\s+\w+\s*\(\s*(\w+)\s*:[\s\S]*?\{/;
const FRAGMENT_BUILTINS = [
{ wgslName: "position", wgslType: "vec4f", wgslBuiltin: "position", pcName: "pcPosition", isFallback: true },
{ wgslName: "frontFacing", wgslType: "bool", wgslBuiltin: "front_facing", pcName: "pcFrontFacing" },
{ wgslName: "sampleIndex", wgslType: "u32", wgslBuiltin: "sample_index", pcName: "pcSampleIndex" },
{ wgslName: "primitiveIndex", wgslType: "u32", wgslBuiltin: "primitive_index", pcName: "pcPrimitiveIndex", requiresFeature: "supportsPrimitiveIndex" }
];
const VERTEX_BUILTINS = [
{ wgslName: "vertexIndex", wgslType: "u32", wgslBuiltin: "vertex_index", pcName: "pcVertexIndex", isFallback: true },
{ wgslName: "instanceIndex", wgslType: "u32", wgslBuiltin: "instance_index", pcName: "pcInstanceIndex" }
];
const detectUsedBuiltins = (builtins, source, entryInputName, device) => {
return builtins.filter((b) => {
if (b.requiresFeature && !device[b.requiresFeature]) return false;
return new RegExp(`\\b(?:${b.pcName}|${entryInputName}\\.${b.wgslName})\\b`).test(source);
});
};
const ensureNonEmptyStruct = (used, builtins, device, otherFieldsPresent) => {
if (used.length === 0 && !otherFieldsPresent) {
const fallback = builtins.find((b) => b.isFallback && (!b.requiresFeature || device[b.requiresFeature]));
if (fallback) return [fallback];
}
return used;
};
const renderBuiltinStructFields = (used) => used.map((b) => ` @builtin(${b.wgslBuiltin}) ${b.wgslName} : ${b.wgslType},
`).join("");
const renderBuiltinPrivates = (used) => used.map((b) => ` var<private> ${b.pcName}: ${b.wgslType};
`).join("");
const renderBuiltinCopies = (used) => used.map((b) => ` ${b.pcName} = input.${b.wgslName};
`).join("");
const textureBaseInfo = {
"texture_1d": { viewDimension: TEXTUREDIMENSION_1D, baseSampleType: SAMPLETYPE_FLOAT },
"texture_2d": { viewDimension: TEXTUREDIMENSION_2D, baseSampleType: SAMPLETYPE_FLOAT },
"texture_2d_array": { viewDimension: TEXTUREDIMENSION_2D_ARRAY, baseSampleType: SAMPLETYPE_FLOAT },
"texture_3d": { viewDimension: TEXTUREDIMENSION_3D, baseSampleType: SAMPLETYPE_FLOAT },
"texture_cube": { viewDimension: TEXTUREDIMENSION_CUBE, baseSampleType: SAMPLETYPE_FLOAT },
"texture_cube_array": { viewDimension: TEXTUREDIMENSION_CUBE_ARRAY, baseSampleType: SAMPLETYPE_FLOAT },
"texture_multisampled_2d": { viewDimension: TEXTUREDIMENSION_2D, baseSampleType: SAMPLETYPE_FLOAT },
"texture_depth_2d": { viewDimension: TEXTUREDIMENSION_2D, baseSampleType: SAMPLETYPE_DEPTH },
"texture_depth_2d_array": { viewDimension: TEXTUREDIMENSION_2D_ARRAY, baseSampleType: SAMPLETYPE_DEPTH },
"texture_depth_cube": { viewDimension: TEXTUREDIMENSION_CUBE, baseSampleType: SAMPLETYPE_DEPTH },
"texture_depth_cube_array": { viewDimension: TEXTUREDIMENSION_CUBE_ARRAY, baseSampleType: SAMPLETYPE_DEPTH },
"texture_external": { viewDimension: TEXTUREDIMENSION_2D, baseSampleType: SAMPLETYPE_UNFILTERABLE_FLOAT }
};
const getTextureInfo = (baseType, componentType) => {
const baseInfo = textureBaseInfo[baseType];
let finalSampleType = baseInfo.baseSampleType;
if (baseInfo.baseSampleType === SAMPLETYPE_FLOAT && baseType !== "texture_multisampled_2d") {
switch (componentType) {
case "u32":
finalSampleType = SAMPLETYPE_UINT;
break;
case "i32":
finalSampleType = SAMPLETYPE_INT;
break;
case "f32":
finalSampleType = SAMPLETYPE_FLOAT;
break;
// custom 'uff' type for unfilterable float, allowing us to create correct bind, which is automatically generated based on the shader
case "uff":
finalSampleType = SAMPLETYPE_UNFILTERABLE_FLOAT;
break;
}
}
return {
viewDimension: baseInfo.viewDimension,
sampleType: finalSampleType
};
};
const getTextureDeclarationType = (viewDimension, sampleType) => {
if (sampleType === SAMPLETYPE_DEPTH) {
switch (viewDimension) {
case TEXTUREDIMENSION_2D:
return "texture_depth_2d";
case TEXTUREDIMENSION_2D_ARRAY:
return "texture_depth_2d_array";
case TEXTUREDIMENSION_CUBE:
return "texture_depth_cube";
case TEXTUREDIMENSION_CUBE_ARRAY:
return "texture_depth_cube_array";
default:
}
}
let baseTypeString;
switch (viewDimension) {
case TEXTUREDIMENSION_1D:
baseTypeString = "texture_1d";
break;
case TEXTUREDIMENSION_2D:
baseTypeString = "texture_2d";
break;
case TEXTUREDIMENSION_2D_ARRAY:
baseTypeString = "texture_2d_array";
break;
case TEXTUREDIMENSION_3D:
baseTypeString = "texture_3d";
break;
case TEXTUREDIMENSION_CUBE:
baseTypeString = "texture_cube";
break;
case TEXTUREDIMENSION_CUBE_ARRAY:
baseTypeString = "texture_cube_array";
break;
default:
}
let coreFormatString;
switch (sampleType) {
case SAMPLETYPE_FLOAT:
case SAMPLETYPE_UNFILTERABLE_FLOAT:
coreFormatString = "f32";
break;
case SAMPLETYPE_UINT:
coreFormatString = "u32";
break;
case SAMPLETYPE_INT:
coreFormatString = "i32";
break;
default:
}
return `${baseTypeString}<${coreFormatString}>`;
};
const wrappedArrayTypes = {
"f32": "WrappedF32",
"i32": "WrappedI32",
"u32": "WrappedU32",
"vec2f": "WrappedVec2F",
"vec2i": "WrappedVec2I",
"vec2u": "WrappedVec2U"
};
const splitToWords = (line) => {
line = line.replace(/\s+/g, " ").trim();
return line.split(/[\s:]+/);
};
const UNIFORM_ARRAY_REGEX = /array<([^,]+),\s*([^>]+)>/;
class UniformLine {
ubName = null;
arraySize = 0;
constructor(line, shader) {
this.line = line;
const parts = splitToWords(line);
if (parts.length < 2) {
shader.failed = true;
return;
}
this.name = parts[0];
this.type = parts.slice(1).join(" ");
if (this.type.includes("array<")) {
const match = UNIFORM_ARRAY_REGEX.exec(this.type);
this.type = match[1].trim();
this.arraySize = Number(match[2]);
if (isNaN(this.arraySize)) {
shader.failed = true;
}
}
}
}
const TEXTURE_REGEX = /^\s*var\s+(\w+)\s*:\s*(texture_\w+)(?:<(\w+)>)?;\s*$/;
const STORAGE_TEXTURE_REGEX = /^\s*var\s+([\w\d_]+)\s*:\s*(texture_storage_2d|texture_storage_2d_array)<([\w\d_]+),\s*(\w+)>\s*;\s*$/;
const STORAGE_BUFFER_REGEX = /^\s*var\s*<storage,\s*(read|write)?>\s*([\w\d_]+)\s*:\s*(.*)\s*;\s*$/;
const EXTERNAL_TEXTURE_REGEX = /^\s*var\s+([\w\d_]+)\s*:\s*texture_external;\s*$/;
const SAMPLER_REGEX = /^\s*var\s+([\w\d_]+)\s*:\s*(sampler|sampler_comparison)\s*;\s*$/;
class ResourceLine {
constructor(line, shader) {
this.originalLine = line;
this.line = line;
this.isTexture = false;
this.isSampler = false;
this.isStorageTexture = false;
this.isStorageBuffer = false;
this.isExternalTexture = false;
this.type = "";
this.matchedElements = [];
const textureMatch = this.line.match(TEXTURE_REGEX);
if (textureMatch) {
this.name = textureMatch[1];
this.type = textureMatch[2];
this.textureFormat = textureMatch[3];
this.isTexture = true;
this.matchedElements.push(...textureMatch);
const info = getTextureInfo(this.type, this.textureFormat);
this.textureDimension = info.viewDimension;
this.sampleType = info.sampleType;
}
const storageTextureMatch = this.line.match(STORAGE_TEXTURE_REGEX);
if (storageTextureMatch) {
this.isStorageTexture = true;
this.name = storageTextureMatch[1];
this.textureType = storageTextureMatch[2];
this.format = storageTextureMatch[3];
this.access = storageTextureMatch[4];
this.matchedElements.push(...storageTextureMatch);
}
const storageBufferMatch = this.line.match(STORAGE_BUFFER_REGEX);
if (storageBufferMatch) {
this.isStorageBuffer = true;
this.accessMode = storageBufferMatch[1] || "none";
this.name = storageBufferMatch[2];
this.type = storageBufferMatch[3];
this.matchedElements.push(...storageBufferMatch);
}
const externalTextureMatch = this.line.match(EXTERNAL_TEXTURE_REGEX);
if (externalTextureMatch) {
this.name = externalTextureMatch[1];
this.isExternalTexture = true;
this.matchedElements.push(...storageBufferMatch);
}
const samplerMatch = this.line.match(SAMPLER_REGEX);
if (samplerMatch) {
this.name = samplerMatch[1];
this.samplerType = samplerMatch[2];
this.isSampler = true;
this.matchedElements.push(...samplerMatch);
}
if (this.matchedElements.length === 0) {
shader.failed = true;
}
}
equals(other) {
if (this.name !== other.name) return false;
if (this.type !== other.type) return false;
if (this.isTexture !== other.isTexture) return false;
if (this.isSampler !== other.isSampler) return false;
if (this.isStorageTexture !== other.isStorageTexture) return false;
if (this.isStorageBuffer !== other.isStorageBuffer) return false;
if (this.isExternalTexture !== other.isExternalTexture) return false;
if (this.textureFormat !== other.textureFormat) return false;
if (this.textureDimension !== other.textureDimension) return false;
if (this.sampleType !== other.sampleType) return false;
if (this.textureType !== other.textureType) return false;
if (this.format !== other.format) return false;
if (this.access !== other.access) return false;
if (this.accessMode !== other.accessMode) return false;
if (this.samplerType !== other.samplerType) return false;
return true;
}
}
class WebgpuShaderProcessorWGSL {
static run(device, shaderDefinition, shader) {
const varyingMap = /* @__PURE__ */ new Map();
const vertexExtracted = WebgpuShaderProcessorWGSL.extract(shaderDefinition.vshader);
const fragmentExtracted = WebgpuShaderProcessorWGSL.extract(shaderDefinition.fshader);
const vertexInputName = vertexExtracted.src.match(ENTRY_FUNCTION)?.[2] ?? "";
const fragmentInputName = fragmentExtracted.src.match(ENTRY_FUNCTION)?.[2] ?? "";
const attributesMap = /* @__PURE__ */ new Map();
const attributesBlock = WebgpuShaderProcessorWGSL.processAttributes(vertexExtracted.attributes, shaderDefinition.attributes, attributesMap, shaderDefinition.processingOptions, shader, device, vertexExtracted.src, vertexInputName);
const vertexVaryingsBlock = WebgpuShaderProcessorWGSL.processVaryings(vertexExtracted.varyings, varyingMap, true, device);
const fragmentVaryingsBlock = WebgpuShaderProcessorWGSL.processVaryings(fragmentExtracted.varyings, varyingMap, false, device, fragmentExtracted.src, fragmentInputName);
const concatUniforms = vertexExtracted.uniforms.concat(fragmentExtracted.uniforms);
const uniforms = Array.from(new Set(concatUniforms));
const parsedUniforms = uniforms.map((line) => new UniformLine(line, shader));
const uniformsData = WebgpuShaderProcessorWGSL.processUniforms(device, parsedUniforms, shaderDefinition.processingOptions, shader);
vertexExtracted.src = WebgpuShaderProcessorWGSL.renameUniformAccess(vertexExtracted.src, parsedUniforms);
fragmentExtracted.src = WebgpuShaderProcessorWGSL.renameUniformAccess(fragmentExtracted.src, parsedUniforms);
const parsedResources = WebgpuShaderProcessorWGSL.mergeResources(vertexExtracted.resources, fragmentExtracted.resources, shader);
const resourcesData = WebgpuShaderProcessorWGSL.processResources(device, parsedResources, shaderDefinition.processingOptions, shader);
const fOutput = WebgpuShaderProcessorWGSL.generateFragmentOutputStruct(fragmentExtracted.src, device.maxColorAttachments);
vertexExtracted.src = WebgpuShaderProcessorWGSL.copyInputs(vertexExtracted.src, shader);
fragmentExtracted.src = WebgpuShaderProcessorWGSL.copyInputs(fragmentExtracted.src, shader);
const vBlock = `${attributesBlock}
${vertexVaryingsBlock}
${uniformsData.code}
${resourcesData.code}
`;
const vshader = vertexExtracted.src.replace(MARKER, vBlock);
const fBlock = `${fragmentVaryingsBlock}
${fOutput}
${uniformsData.code}
${resourcesData.code}
`;
const fshader = fragmentExtracted.src.replace(MARKER, fBlock);
return {
vshader,
fshader,
attributes: attributesMap,
meshUniformBufferFormat: uniformsData.meshUniformBufferFormat,
meshBindGroupFormat: resourcesData.meshBindGroupFormat
};
}
// Extract required information from the shader source code.
static extract(src) {
const attributes = [];
const varyings = [];
const uniforms = [];
const resources = [];
let replacement = `${MARKER}
`;
let match;
while ((match = KEYWORD.exec(src)) !== null) {
const keyword = match[1];
KEYWORD_LINE.lastIndex = match.index;
const lineMatch = KEYWORD_LINE.exec(src);
if (keyword === "attribute") {
attributes.push(lineMatch[2]);
} else if (keyword === "varying") {
varyings.push(lineMatch[2]);
} else if (keyword === "uniform") {
uniforms.push(lineMatch[2]);
}
src = WebgpuShaderProcessorWGSL.cutOut(src, match.index, KEYWORD_LINE.lastIndex, replacement);
KEYWORD.lastIndex = match.index + replacement.length;
replacement = "";
}
while ((match = KEYWORD_RESOURCE.exec(src)) !== null) {
resources.push(match[0]);
src = WebgpuShaderProcessorWGSL.cutOut(src, match.index, KEYWORD_RESOURCE.lastIndex, replacement);
KEYWORD_RESOURCE.lastIndex = match.index + replacement.length;
replacement = "";
}
return {
src,
attributes,
varyings,
uniforms,
resources
};
}
static processUniforms(device, uniforms, processingOptions, shader) {
const meshUniforms = [];
uniforms.forEach((uniform) => {
if (!processingOptions.hasUniform(uniform.name)) {
uniform.ubName = "ub_mesh_ub";
const uniformType = uniformTypeToNameMapWGSL.get(uniform.type);
const uniformFormat = new UniformFormat(uniform.name, uniformType, uniform.arraySize);
meshUniforms.push(uniformFormat);
} else {
uniform.ubName = "ub_view";
}
});
if (meshUniforms.length === 0) {
meshUniforms.push(new UniformFormat(UNUSED_UNIFORM_NAME, UNIFORMTYPE_FLOAT));
}
const meshUniformBufferFormat = new UniformBufferFormat(device, meshUniforms);
let code = "";
processingOptions.uniformFormats.forEach((format, bindGroupIndex) => {
if (format) {
code += WebgpuShaderProcessorWGSL.getUniformShaderDeclaration(format, bindGroupIndex, 0);
}
});
if (meshUniformBufferFormat) {
code += WebgpuShaderProcessorWGSL.getUniformShaderDeclaration(meshUniformBufferFormat, BINDGROUP_MESH_UB, 0);
}
return {
code,
meshUniformBufferFormat
};
}
static renameUniformAccess(source, uniforms) {
uniforms.forEach((uniform) => {
const srcName = `uniform.${uniform.name}`;
const dstName = `${uniform.ubName}.${uniform.name}`;
const regex = new RegExp(`\\b${srcName}\\b`, "g");
source = source.replace(regex, dstName);
});
return source;
}
static mergeResources(vertex, fragment, shader) {
const resources = vertex.map((line) => new ResourceLine(line, shader));
const fragmentResources = fragment.map((line) => new ResourceLine(line, shader));
fragmentResources.forEach((fragmentResource) => {
const existing = resources.find((resource) => resource.name === fragmentResource.name);
if (existing) {
if (!existing.equals(fragmentResource)) {
shader.failed = true;
}
} else {
resources.push(fragmentResource);
}
});
return resources;
}
static processResources(device, resources, processingOptions, shader) {
const textureFormats = [];
for (let i = 0; i < resources.length; i++) {
const resource = resources[i];
if (resource.isTexture) {
const sampler = resources[i + 1];
const hasSampler = sampler?.isSampler;
const sampleType = resource.sampleType;
const dimension = resource.textureDimension;
textureFormats.push(new BindTextureFormat(resource.name, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT, dimension, sampleType, hasSampler, hasSampler ? sampler.name : null));
if (hasSampler) i++;
}
if (resource.isStorageBuffer) {
const readOnly = resource.accessMode !== "read_write";
const bufferFormat = new BindStorageBufferFormat(resource.name, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT, readOnly);
bufferFormat.format = resource.type;
textureFormats.push(bufferFormat);
}
}
const meshBindGroupFormat = new BindGroupFormat(device, textureFormats);
let code = "";
processingOptions.bindGroupFormats.forEach((format, bindGroupIndex) => {
if (format) {
code += WebgpuShaderProcessorWGSL.getTextureShaderDeclaration(format, bindGroupIndex);
}
});
code += WebgpuShaderProcessorWGSL.getTextureShaderDeclaration(meshBindGroupFormat, BINDGROUP_MESH);
return {
code,
meshBindGroupFormat
};
}
static getUniformShaderDeclaration(ubFormat, bindGroup, bindIndex) {
const name = bindGroupNames[bindGroup];
const structName = `struct_ub_${name}`;
let code = `struct ${structName} {
`;
ubFormat.uniforms.forEach((uniform) => {
let typeString = uniformTypeToNameWGSL[uniform.type][0];
if (uniform.count > 0) {
if (wrappedArrayTypes.hasOwnProperty(typeString)) {
typeString = wrappedArrayTypes[typeString];
}
code += ` ${uniform.shortName}: array<${typeString}, ${uniform.count}>,
`;
} else {
code += ` ${uniform.shortName}: ${typeString},
`;
}
});
code += "};\n";
code += `@group(${bindGroup}) @binding(${bindIndex}) var<uniform> ub_${name} : ${structName};
`;
return code;
}
static getTextureShaderDeclaration(format, bindGroup) {
let code = "";
format.textureFormats.forEach((format2) => {
const textureTypeName = getTextureDeclarationType(format2.textureDimension, format2.sampleType);
code += `@group(${bindGroup}) @binding(${format2.slot}) var ${format2.name}: ${textureTypeName};
`;
if (format2.hasSampler) {
const samplerName = format2.sampleType === SAMPLETYPE_DEPTH ? "sampler_comparison" : "sampler";
code += `@group(${bindGroup}) @binding(${format2.slot + 1}) var ${format2.samplerName}: ${samplerName};
`;
}
});
format.storageBufferFormats.forEach((format2) => {
const access = format2.readOnly ? "read" : "read_write";
code += `@group(${bindGroup}) @binding(${format2.slot}) var<storage, ${access}> ${format2.name} : ${format2.format};
`;
});
return code;
}
static processVaryings(varyingLines, varyingMap, isVertex, device, source = "", entryInputName = "") {
let block = "";
let blockPrivates = "";
let blockCopy = "";
varyingLines.forEach((line, index) => {
const match = line.match(VARYING);
if (match) {
const name = match[1];
const type = match[2];
if (isVertex) {
varyingMap.set(name, index);
} else {
index = varyingMap.get(name);
}
block += ` @location(${index}) ${line},
`;
if (!isVertex) {
blockPrivates += ` var<private> ${name}: ${type};
`;
blockCopy += ` ${name} = input.${name};
`;
}
}
});
if (isVertex) {
block += " @builtin(position) position : vec4f,\n";
return `
struct VertexOutput {
${block}
};
`;
}
const usedBuiltins = ensureNonEmptyStruct(
detectUsedBuiltins(FRAGMENT_BUILTINS, source, entryInputName, device),
FRAGMENT_BUILTINS,
device,
block.length > 0
);
block += renderBuiltinStructFields(usedBuiltins);
return `
struct FragmentInput {
${block}
};
${renderBuiltinPrivates(usedBuiltins)}
${blockPrivates}
// function to copy inputs (varyings) to private global variables
fn _pcCopyInputs(input: FragmentInput) {
${blockCopy}
${renderBuiltinCopies(usedBuiltins)}
}
`;
}
static generateFragmentOutputStruct(src, numRenderTargets) {
let structCode = "struct FragmentOutput {\n";
const colorName = (i) => `color${i > 0 ? i : ""}`;
for (let i = 0; i < numRenderTargets; i++) {
const name = colorName(i);
if (src.search(new RegExp(`\\.${name}\\s*=`)) !== -1) {
structCode += ` @location(${i}) ${name} : pcOutType${i},
`;
}
}
const needsFragDepth = src.search(/\.fragDepth\s*=/) !== -1;
if (needsFragDepth) {
structCode += " @builtin(frag_depth) fragDepth : f32\n";
}
return `${structCode}};
`;
}
// convert a float attribute type to matching signed or unsigned int type
// for example: vec4f -> vec4u, f32 -> u32
static floatAttributeToInt(type, signed) {
const longToShortMap = {
"f32": "f32",
"vec2<f32>": "vec2f",
"vec3<f32>": "vec3f",
"vec4<f32>": "vec4f"
};
const shortType = longToShortMap[type] || type;
const floatToIntShort = {
"f32": signed ? "i32" : "u32",
"vec2f": signed ? "vec2i" : "vec2u",
"vec3f": signed ? "vec3i" : "vec3u",
"vec4f": signed ? "vec4i" : "vec4u"
};
return floatToIntShort[shortType] || null;
}
static processAttributes(attributeLines, shaderDefinitionAttributes = {}, attributesMap, processingOptions, shader, device, source = "", entryInputName = "") {
let blockAttributes = "";
let blockPrivates = "";
let blockCopy = "";
const usedLocations = {};
attributeLines.forEach((line) => {
const words = splitToWords(line);
const name = words[0];
let type = words[1];
const originalType = type;
if (shaderDefinitionAttributes.hasOwnProperty(name)) {
const semantic = shaderDefinitionAttributes[name];
const location = semanticToLocation[semantic];
usedLocations[location] = semantic;
attributesMap.set(location, name);
const element = processingOptions.getVertexElement(semantic);
if (element) {
const dataType = element.dataType;
if (dataType !== TYPE_FLOAT32 && dataType !== TYPE_FLOAT16 && !element.normalize && !element.asInt) {
const isSignedType = dataType === TYPE_INT8 || dataType === TYPE_INT16 || dataType === TYPE_INT32;
type = WebgpuShaderProcessorWGSL.floatAttributeToInt(type, isSignedType);
}
}
blockAttributes += ` @location(${location}) ${name}: ${type},
`;
blockPrivates += ` var<private> ${line};
`;
blockCopy += ` ${name} = ${originalType}(input.${name});
`;
} else {
}
});
const usedBuiltins = ensureNonEmptyStruct(
detectUsedBuiltins(VERTEX_BUILTINS, source, entryInputName, device),
VERTEX_BUILTINS,
device,
blockAttributes.length > 0
);
return `
struct VertexInput {
${blockAttributes}
${renderBuiltinStructFields(usedBuiltins)}
};
${blockPrivates}
${renderBuiltinPrivates(usedBuiltins)}
fn _pcCopyInputs(input: VertexInput) {
${blockCopy}
${renderBuiltinCopies(usedBuiltins)}
}
`;
}
static copyInputs(src, shader) {
const match = src.match(ENTRY_FUNCTION);
if (!match || !match[2]) {
return src;
}
const inputName = match[2];
const braceIndex = match.index + match[0].length - 1;
const beginning = src.slice(0, braceIndex + 1);
const end = src.slice(braceIndex + 1);
const lineToInject = `
_pcCopyInputs(${inputName});`;
return beginning + lineToInject + end;
}
static cutOut(src, start, end, replacement) {
return src.substring(0, start) + replacement + src.substring(end);
}
}
export {
WebgpuShaderProcessorWGSL
};