UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

337 lines (336 loc) 11.7 kB
import { BINDGROUP_MESH, uniformTypeToName, semanticToLocation, SHADERSTAGE_VERTEX, SHADERSTAGE_FRAGMENT, SAMPLETYPE_FLOAT, SAMPLETYPE_DEPTH, SAMPLETYPE_UNFILTERABLE_FLOAT, TEXTUREDIMENSION_2D, TEXTUREDIMENSION_2D_ARRAY, TEXTUREDIMENSION_CUBE, TEXTUREDIMENSION_3D, TYPE_FLOAT32, TYPE_INT8, TYPE_INT16, TYPE_INT32, TYPE_FLOAT16, SAMPLETYPE_INT, SAMPLETYPE_UINT, BINDGROUP_MESH_UB, UNUSED_UNIFORM_NAME, UNIFORMTYPE_FLOAT, bindGroupNames } from "./constants.js"; import { UniformFormat, UniformBufferFormat } from "./uniform-buffer-format.js"; import { BindGroupFormat, BindTextureFormat } from "./bind-group-format.js"; const KEYWORD = /[ \t]*(\battribute\b|\bvarying\b|\buniform\b)/g; const KEYWORD_LINE = /(\battribute\b|\bvarying\b|\bout\b|\buniform\b)[ \t]*([^;]+)(;+)/g; const MARKER = "@@@"; const ARRAY_IDENTIFIER = /([\w-]+)\[(.*?)\]/; const precisionQualifiers = /* @__PURE__ */ new Set(["highp", "mediump", "lowp"]); const shadowSamplers = /* @__PURE__ */ new Set(["sampler2DShadow", "samplerCubeShadow", "sampler2DArrayShadow"]); const textureDimensions = { sampler2D: TEXTUREDIMENSION_2D, sampler3D: TEXTUREDIMENSION_3D, samplerCube: TEXTUREDIMENSION_CUBE, samplerCubeShadow: TEXTUREDIMENSION_CUBE, sampler2DShadow: TEXTUREDIMENSION_2D, sampler2DArray: TEXTUREDIMENSION_2D_ARRAY, sampler2DArrayShadow: TEXTUREDIMENSION_2D_ARRAY, isampler2D: TEXTUREDIMENSION_2D, usampler2D: TEXTUREDIMENSION_2D, isampler3D: TEXTUREDIMENSION_3D, usampler3D: TEXTUREDIMENSION_3D, isamplerCube: TEXTUREDIMENSION_CUBE, usamplerCube: TEXTUREDIMENSION_CUBE, isampler2DArray: TEXTUREDIMENSION_2D_ARRAY, usampler2DArray: TEXTUREDIMENSION_2D_ARRAY }; const textureDimensionInfo = { [TEXTUREDIMENSION_2D]: "texture2D", [TEXTUREDIMENSION_CUBE]: "textureCube", [TEXTUREDIMENSION_3D]: "texture3D", [TEXTUREDIMENSION_2D_ARRAY]: "texture2DArray" }; class UniformLine { constructor(line, shader) { this.line = line; const words = line.trim().split(/\s+/); if (precisionQualifiers.has(words[0])) { this.precision = words.shift(); } this.type = words.shift(); if (line.includes(",")) { } if (line.includes("[")) { const rest = words.join(" "); const match = ARRAY_IDENTIFIER.exec(rest); this.name = match[1]; this.arraySize = Number(match[2]); if (isNaN(this.arraySize)) { shader.failed = true; } } else { this.name = words.shift(); this.arraySize = 0; } this.isSampler = this.type.indexOf("sampler") !== -1; this.isSignedInt = this.type.indexOf("isampler") !== -1; this.isUnsignedInt = this.type.indexOf("usampler") !== -1; } } class ShaderProcessorGLSL { static run(device, shaderDefinition, shader) { const varyingMap = /* @__PURE__ */ new Map(); const vertexExtracted = ShaderProcessorGLSL.extract(shaderDefinition.vshader); const fragmentExtracted = ShaderProcessorGLSL.extract(shaderDefinition.fshader); const attributesMap = /* @__PURE__ */ new Map(); const attributesBlock = ShaderProcessorGLSL.processAttributes(vertexExtracted.attributes, shaderDefinition.attributes, attributesMap, shaderDefinition.processingOptions); const vertexVaryingsBlock = ShaderProcessorGLSL.processVaryings(vertexExtracted.varyings, varyingMap, true); const fragmentVaryingsBlock = ShaderProcessorGLSL.processVaryings(fragmentExtracted.varyings, varyingMap, false); const outBlock = ShaderProcessorGLSL.processOuts(fragmentExtracted.outs); 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 = ShaderProcessorGLSL.processUniforms(device, parsedUniforms, shaderDefinition.processingOptions, shader); const vBlock = `${attributesBlock} ${vertexVaryingsBlock} ${uniformsData.code}`; const vshader = vertexExtracted.src.replace(MARKER, vBlock); const fBlock = `${fragmentVaryingsBlock} ${outBlock} ${uniformsData.code}`; const fshader = fragmentExtracted.src.replace(MARKER, fBlock); return { vshader, fshader, attributes: attributesMap, meshUniformBufferFormat: uniformsData.meshUniformBufferFormat, meshBindGroupFormat: uniformsData.meshBindGroupFormat }; } // Extract required information from the shader source code. static extract(src) { const attributes = []; const varyings = []; const outs = []; const uniforms = []; let replacement = `${MARKER} `; let match; while ((match = KEYWORD.exec(src)) !== null) { const keyword = match[1]; switch (keyword) { case "attribute": case "varying": case "uniform": case "out": { 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 === "out") { outs.push(lineMatch[2]); } else if (keyword === "uniform") { uniforms.push(lineMatch[2]); } src = ShaderProcessorGLSL.cutOut(src, match.index, KEYWORD_LINE.lastIndex, replacement); KEYWORD.lastIndex = match.index + replacement.length; replacement = ""; break; } } } return { src, attributes, varyings, outs, uniforms }; } static processUniforms(device, uniforms, processingOptions, shader) { const uniformLinesSamplers = []; const uniformLinesNonSamplers = []; uniforms.forEach((uniform) => { if (uniform.isSampler) { uniformLinesSamplers.push(uniform); } else { uniformLinesNonSamplers.push(uniform); } }); const meshUniforms = []; uniformLinesNonSamplers.forEach((uniform) => { if (!processingOptions.hasUniform(uniform.name)) { const uniformType = uniformTypeToName.indexOf(uniform.type); const uniformFormat = new UniformFormat(uniform.name, uniformType, uniform.arraySize); meshUniforms.push(uniformFormat); } }); if (meshUniforms.length === 0) { meshUniforms.push(new UniformFormat(UNUSED_UNIFORM_NAME, UNIFORMTYPE_FLOAT)); } const meshUniformBufferFormat = meshUniforms.length ? new UniformBufferFormat(device, meshUniforms) : null; const textureFormats = []; uniformLinesSamplers.forEach((uniform) => { if (!processingOptions.hasTexture(uniform.name)) { let sampleType = SAMPLETYPE_FLOAT; if (uniform.isSignedInt) { sampleType = SAMPLETYPE_INT; } else if (uniform.isUnsignedInt) { sampleType = SAMPLETYPE_UINT; } else { if (uniform.precision === "highp") { sampleType = SAMPLETYPE_UNFILTERABLE_FLOAT; } if (shadowSamplers.has(uniform.type)) { sampleType = SAMPLETYPE_DEPTH; } } const dimension = textureDimensions[uniform.type]; textureFormats.push(new BindTextureFormat(uniform.name, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT, dimension, sampleType)); } }); const meshBindGroupFormat = new BindGroupFormat(device, textureFormats); let code = ""; processingOptions.uniformFormats.forEach((format, bindGroupIndex) => { if (format) { code += ShaderProcessorGLSL.getUniformShaderDeclaration(format, bindGroupIndex, 0); } }); if (meshUniformBufferFormat) { code += ShaderProcessorGLSL.getUniformShaderDeclaration(meshUniformBufferFormat, BINDGROUP_MESH_UB, 0); } processingOptions.bindGroupFormats.forEach((format, bindGroupIndex) => { if (format) { code += ShaderProcessorGLSL.getTexturesShaderDeclaration(format, bindGroupIndex); } }); code += ShaderProcessorGLSL.getTexturesShaderDeclaration(meshBindGroupFormat, BINDGROUP_MESH); return { code, meshUniformBufferFormat, meshBindGroupFormat }; } static processVaryings(varyingLines, varyingMap, isVertex) { let block = ""; const op = isVertex ? "out" : "in"; varyingLines.forEach((line, index) => { const words = ShaderProcessorGLSL.splitToWords(line); const type = words.slice(0, -1).join(" "); const name = words[words.length - 1]; if (isVertex) { varyingMap.set(name, index); } else { index = varyingMap.get(name); } block += `layout(location = ${index}) ${op} ${type} ${name}; `; }); return block; } static processOuts(outsLines) { let block = ""; outsLines.forEach((line, index) => { block += `layout(location = ${index}) out ${line}; `; }); return block; } // extract count from type ('vec3' => 3, 'float' => 1) static getTypeCount(type) { const lastChar = type.substring(type.length - 1); const num = parseInt(lastChar, 10); return isNaN(num) ? 1 : num; } static processAttributes(attributeLines, shaderDefinitionAttributes, attributesMap, processingOptions) { let block = ""; const usedLocations = {}; attributeLines.forEach((line) => { const words = ShaderProcessorGLSL.splitToWords(line); let type = words[0]; let name = words[1]; if (shaderDefinitionAttributes.hasOwnProperty(name)) { const semantic = shaderDefinitionAttributes[name]; const location = semanticToLocation[semantic]; usedLocations[location] = semantic; attributesMap.set(location, name); let copyCode; const element = processingOptions.getVertexElement(semantic); if (element) { const dataType = element.dataType; if (dataType !== TYPE_FLOAT32 && dataType !== TYPE_FLOAT16 && !element.normalize && !element.asInt) { const attribNumElements = ShaderProcessorGLSL.getTypeCount(type); const newName = `_private_${name}`; copyCode = `vec${attribNumElements} ${name} = vec${attribNumElements}(${newName}); `; name = newName; const isSignedType = dataType === TYPE_INT8 || dataType === TYPE_INT16 || dataType === TYPE_INT32; if (attribNumElements === 1) { type = isSignedType ? "int" : "uint"; } else { type = isSignedType ? `ivec${attribNumElements}` : `uvec${attribNumElements}`; } } } block += `layout(location = ${location}) in ${type} ${name}; `; if (copyCode) { block += copyCode; } } }); return block; } static splitToWords(line) { line = line.replace(/\s+/g, " ").trim(); return line.split(" "); } static cutOut(src, start, end, replacement) { return src.substring(0, start) + replacement + src.substring(end); } static getUniformShaderDeclaration(format, bindGroup, bindIndex) { const name = bindGroupNames[bindGroup]; let code = `layout(set = ${bindGroup}, binding = ${bindIndex}, std140) uniform ub_${name} { `; format.uniforms.forEach((uniform) => { const typeString = uniformTypeToName[uniform.type]; code += ` ${typeString} ${uniform.shortName}${uniform.count ? `[${uniform.count}]` : ""}; `; }); return `${code}}; `; } static getTexturesShaderDeclaration(bindGroupFormat, bindGroup) { let code = ""; bindGroupFormat.textureFormats.forEach((format) => { let textureType = textureDimensionInfo[format.textureDimension]; const isArray = textureType === "texture2DArray"; const sampleTypePrefix = format.sampleType === SAMPLETYPE_UINT ? "u" : format.sampleType === SAMPLETYPE_INT ? "i" : ""; textureType = `${sampleTypePrefix}${textureType}`; let namePostfix = ""; let extraCode = ""; if (isArray) { namePostfix = "_texture"; extraCode = `#define ${format.name} ${sampleTypePrefix}sampler2DArray(${format.name}${namePostfix}, ${format.name}_sampler) `; } code += `layout(set = ${bindGroup}, binding = ${format.slot}) uniform ${textureType} ${format.name}${namePostfix}; `; if (format.hasSampler) { code += `layout(set = ${bindGroup}, binding = ${format.slot + 1}) uniform sampler ${format.name}_sampler; `; } code += extraCode; }); return code; } } export { ShaderProcessorGLSL };