@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
335 lines (334 loc) • 18.6 kB
JavaScript
import { WebGPUShaderProcessingContext } from "./webgpuShaderProcessingContext.js";
import { Logger } from "../../Misc/logger.js";
import { WebGPUShaderProcessor } from "./webgpuShaderProcessor.js";
import { InjectStartingAndEndingCode } from "../../Misc/codeStringParsingTools.js";
/** @internal */
export class WebGPUShaderProcessorGLSL extends WebGPUShaderProcessor {
constructor() {
super(...arguments);
this._missingVaryings = [];
this._textureArrayProcessing = [];
this._vertexIsGLES3 = false;
this._fragmentIsGLES3 = false;
this.shaderLanguage = 0 /* ShaderLanguage.GLSL */;
this.parseGLES3 = true;
}
_getArraySize(name, type, preProcessors) {
let length = 0;
const startArray = name.indexOf("[");
const endArray = name.indexOf("]");
if (startArray > 0 && endArray > 0) {
const lengthInString = name.substring(startArray + 1, endArray);
length = +lengthInString;
if (isNaN(length)) {
length = +preProcessors[lengthInString.trim()];
}
name = name.substring(0, startArray);
}
return [name, type, length];
}
initializeShaders(processingContext) {
this._webgpuProcessingContext = processingContext;
this._missingVaryings.length = 0;
this._textureArrayProcessing.length = 0;
this.attributeKeywordName = undefined;
this.varyingVertexKeywordName = undefined;
this.varyingFragmentKeywordName = undefined;
}
preProcessShaderCode(code, isFragment) {
const ubDeclaration = `// Internals UBO\nuniform ${WebGPUShaderProcessor.InternalsUBOName} {\nfloat yFactor_;\nfloat textureOutputHeight_;\n};\n`;
const alreadyInjected = code.indexOf("// Internals UBO") !== -1;
if (isFragment) {
this._fragmentIsGLES3 = code.indexOf("#version 3") !== -1;
if (this._fragmentIsGLES3) {
this.varyingFragmentKeywordName = "in";
}
return alreadyInjected ? code : ubDeclaration + "##INJECTCODE##\n" + code;
}
this._vertexIsGLES3 = code.indexOf("#version 3") !== -1;
if (this._vertexIsGLES3) {
this.attributeKeywordName = "in";
this.varyingVertexKeywordName = "out";
}
return alreadyInjected ? code : ubDeclaration + code;
}
varyingCheck(varying, isFragment) {
const outRegex = /(flat\s)?\s*\bout\b/;
const inRegex = /(flat\s)?\s*\bin\b/;
const varyingRegex = /(flat\s)?\s*\bvarying\b/;
const regex = isFragment && this._fragmentIsGLES3 ? inRegex : !isFragment && this._vertexIsGLES3 ? outRegex : varyingRegex;
return regex.test(varying);
}
varyingProcessor(varying, isFragment, preProcessors) {
this._preProcessors = preProcessors;
const outRegex = /\s*(flat)?\s*out\s+(?:(?:highp)?|(?:lowp)?)\s*(\S+)\s+(\S+)\s*;/gm;
const inRegex = /\s*(flat)?\s*in\s+(?:(?:highp)?|(?:lowp)?)\s*(\S+)\s+(\S+)\s*;/gm;
const varyingRegex = /\s*(flat)?\s*varying\s+(?:(?:highp)?|(?:lowp)?)\s*(\S+)\s+(\S+)\s*;/gm;
const regex = isFragment && this._fragmentIsGLES3 ? inRegex : !isFragment && this._vertexIsGLES3 ? outRegex : varyingRegex;
const match = regex.exec(varying);
if (match !== null) {
const interpolationQualifier = match[1] ?? "";
const varyingType = match[2];
const name = match[3];
let location;
if (isFragment) {
location = this._webgpuProcessingContext.availableVaryings[name];
this._missingVaryings[location] = "";
if (location === undefined) {
Logger.Warn(`Invalid fragment shader: The varying named "${name}" is not declared in the vertex shader! This declaration will be ignored.`);
}
}
else {
location = this._webgpuProcessingContext.getVaryingNextLocation(varyingType, this._getArraySize(name, varyingType, preProcessors)[2]);
this._webgpuProcessingContext.availableVaryings[name] = location;
this._missingVaryings[location] = `layout(location = ${location}) ${interpolationQualifier} in ${varyingType} ${name};`;
}
varying = varying.replace(match[0], location === undefined ? "" : `layout(location = ${location}) ${interpolationQualifier} ${isFragment ? "in" : "out"} ${varyingType} ${name};`);
}
return varying;
}
attributeProcessor(attribute, preProcessors) {
this._preProcessors = preProcessors;
const inRegex = /\s*in\s+(\S+)\s+(\S+)\s*;/gm;
const attribRegex = /\s*attribute\s+(\S+)\s+(\S+)\s*;/gm;
const regex = this._vertexIsGLES3 ? inRegex : attribRegex;
const match = regex.exec(attribute);
if (match !== null) {
const attributeType = match[1];
const name = match[2];
const location = this._webgpuProcessingContext.getAttributeNextLocation(attributeType, this._getArraySize(name, attributeType, preProcessors)[2]);
this._webgpuProcessingContext.availableAttributes[name] = location;
this._webgpuProcessingContext.orderedAttributes[location] = name;
const numComponents = this._webgpuProcessingContext.vertexBufferKindToNumberOfComponents[name];
if (numComponents !== undefined) {
// Special case for an int/ivecX vertex buffer that is used as a float/vecX attribute in the shader.
const newType = numComponents < 0 ? (numComponents === -1 ? "int" : "ivec" + -numComponents) : numComponents === 1 ? "uint" : "uvec" + numComponents;
const newName = `_int_${name}_`;
attribute = attribute.replace(match[0], `layout(location = ${location}) in ${newType} ${newName}; ${attributeType} ${name} = ${attributeType}(${newName});`);
}
else {
attribute = attribute.replace(match[0], `layout(location = ${location}) in ${attributeType} ${name};`);
}
}
return attribute;
}
uniformProcessor(uniform, isFragment, preProcessors) {
this._preProcessors = preProcessors;
const uniformRegex = /\s*uniform\s+(?:(?:highp)?|(?:lowp)?)\s*(\S+)\s+(\S+)\s*;/gm;
const match = uniformRegex.exec(uniform);
if (match !== null) {
let uniformType = match[1];
let name = match[2];
if (uniformType.indexOf("sampler") === 0 || uniformType.indexOf("sampler") === 1) {
let arraySize = 0; // 0 means the texture is not declared as an array
[name, uniformType, arraySize] = this._getArraySize(name, uniformType, preProcessors);
let textureInfo = this._webgpuProcessingContext.availableTextures[name];
if (!textureInfo) {
textureInfo = {
autoBindSampler: true,
isTextureArray: arraySize > 0,
isStorageTexture: false,
textures: [],
sampleType: "float" /* WebGPUConstants.TextureSampleType.Float */,
};
for (let i = 0; i < (arraySize || 1); ++i) {
textureInfo.textures.push(this._webgpuProcessingContext.getNextFreeUBOBinding());
}
}
const samplerType = WebGPUShaderProcessor._SamplerTypeByWebGLSamplerType[uniformType] ?? "sampler";
const isComparisonSampler = !!WebGPUShaderProcessor._IsComparisonSamplerByWebGPUSamplerType[samplerType];
const samplerBindingType = isComparisonSampler ? "comparison" /* WebGPUConstants.SamplerBindingType.Comparison */ : "filtering" /* WebGPUConstants.SamplerBindingType.Filtering */;
const samplerName = name + `Sampler`;
let samplerInfo = this._webgpuProcessingContext.availableSamplers[samplerName];
if (!samplerInfo) {
samplerInfo = {
binding: this._webgpuProcessingContext.getNextFreeUBOBinding(),
type: samplerBindingType,
};
}
const componentType = uniformType.charAt(0) === "u" ? "u" : uniformType.charAt(0) === "i" ? "i" : "";
if (componentType) {
uniformType = uniformType.substring(1);
}
const sampleType = isComparisonSampler
? "depth" /* WebGPUConstants.TextureSampleType.Depth */
: componentType === "u"
? "uint" /* WebGPUConstants.TextureSampleType.Uint */
: componentType === "i"
? "sint" /* WebGPUConstants.TextureSampleType.Sint */
: "float" /* WebGPUConstants.TextureSampleType.Float */;
textureInfo.sampleType = sampleType;
const isTextureArray = arraySize > 0;
const samplerGroupIndex = samplerInfo.binding.groupIndex;
const samplerBindingIndex = samplerInfo.binding.bindingIndex;
const samplerFunction = WebGPUShaderProcessor._SamplerFunctionByWebGLSamplerType[uniformType];
const textureType = WebGPUShaderProcessor._TextureTypeByWebGLSamplerType[uniformType];
const textureDimension = WebGPUShaderProcessor._GpuTextureViewDimensionByWebGPUTextureType[textureType];
// Manage textures and samplers.
if (!isTextureArray) {
arraySize = 1;
uniform = `layout(set = ${samplerGroupIndex}, binding = ${samplerBindingIndex}) uniform ${samplerType} ${samplerName};
layout(set = ${textureInfo.textures[0].groupIndex}, binding = ${textureInfo.textures[0].bindingIndex}) uniform ${componentType}${textureType} ${name}Texture;
#define ${name} ${componentType}${samplerFunction}(${name}Texture, ${samplerName})`;
}
else {
const layouts = [];
layouts.push(`layout(set = ${samplerGroupIndex}, binding = ${samplerBindingIndex}) uniform ${componentType}${samplerType} ${samplerName};`);
uniform = `\n`;
for (let i = 0; i < arraySize; ++i) {
const textureSetIndex = textureInfo.textures[i].groupIndex;
const textureBindingIndex = textureInfo.textures[i].bindingIndex;
layouts.push(`layout(set = ${textureSetIndex}, binding = ${textureBindingIndex}) uniform ${textureType} ${name}Texture${i};`);
uniform += `${i > 0 ? "\n" : ""}#define ${name}${i} ${componentType}${samplerFunction}(${name}Texture${i}, ${samplerName})`;
}
uniform = layouts.join("\n") + uniform;
this._textureArrayProcessing.push(name);
}
this._webgpuProcessingContext.availableTextures[name] = textureInfo;
this._webgpuProcessingContext.availableSamplers[samplerName] = samplerInfo;
this._addSamplerBindingDescription(samplerName, samplerInfo, !isFragment);
for (let i = 0; i < arraySize; ++i) {
this._addTextureBindingDescription(name, textureInfo, i, textureDimension, null, !isFragment);
}
}
else {
this._addUniformToLeftOverUBO(name, uniformType, preProcessors);
uniform = "";
}
}
return uniform;
}
uniformBufferProcessor(uniformBuffer, isFragment) {
const uboRegex = /uniform\s+(\w+)/gm;
const match = uboRegex.exec(uniformBuffer);
if (match !== null) {
const name = match[1];
let uniformBufferInfo = this._webgpuProcessingContext.availableBuffers[name];
if (!uniformBufferInfo) {
const knownUBO = WebGPUShaderProcessingContext.KnownUBOs[name];
let binding;
if (knownUBO && knownUBO.binding.groupIndex !== -1) {
binding = knownUBO.binding;
}
else {
binding = this._webgpuProcessingContext.getNextFreeUBOBinding();
}
uniformBufferInfo = { binding };
this._webgpuProcessingContext.availableBuffers[name] = uniformBufferInfo;
}
this._addBufferBindingDescription(name, uniformBufferInfo, "uniform" /* WebGPUConstants.BufferBindingType.Uniform */, !isFragment);
uniformBuffer = uniformBuffer.replace("uniform", `layout(set = ${uniformBufferInfo.binding.groupIndex}, binding = ${uniformBufferInfo.binding.bindingIndex}) uniform`);
}
return uniformBuffer;
}
postProcessor(code, defines, isFragment, _processingContext, _parameters) {
const hasDrawBuffersExtension = code.search(/#extension.+GL_EXT_draw_buffers.+require/) !== -1;
// Remove extensions
const regex = /#extension.+(GL_OVR_multiview2|GL_OES_standard_derivatives|GL_EXT_shader_texture_lod|GL_EXT_frag_depth|GL_EXT_draw_buffers).+(enable|require)/g;
code = code.replace(regex, "");
// Replace instructions
code = code.replace(/texture2D\s*\(/g, "texture(");
if (isFragment) {
const hasFragCoord = code.indexOf("gl_FragCoord") >= 0;
const fragCoordCode = `
glFragCoord_ = gl_FragCoord;
if (yFactor_ == 1.) {
glFragCoord_.y = textureOutputHeight_ - glFragCoord_.y;
}
`;
const injectCode = hasFragCoord ? "vec4 glFragCoord_;\n" : "";
const hasOutput = code.search(/layout *\(location *= *0\) *out/g) !== -1;
code = code.replace(/texture2DLodEXT\s*\(/g, "textureLod(");
code = code.replace(/textureCubeLodEXT\s*\(/g, "textureLod(");
code = code.replace(/textureCube\s*\(/g, "texture(");
code = code.replace(/gl_FragDepthEXT/g, "gl_FragDepth");
code = code.replace(/gl_FragColor/g, "glFragColor");
code = code.replace(/gl_FragData/g, "glFragData");
code = code.replace(/gl_FragCoord/g, "glFragCoord_");
if (!this._fragmentIsGLES3) {
code = code.replace(/void\s+?main\s*\(/g, (hasDrawBuffersExtension || hasOutput ? "" : "layout(location = 0) out vec4 glFragColor;\n") + "void main(");
}
else {
const match = /^\s*out\s+\S+\s+\S+\s*;/gm.exec(code);
if (match !== null) {
code = code.substring(0, match.index) + "layout(location = 0) " + code.substring(match.index);
}
}
code = code.replace(/dFdy/g, "(-yFactor_)*dFdy"); // will also handle dFdyCoarse and dFdyFine
code = code.replace("##INJECTCODE##", injectCode);
if (hasFragCoord) {
code = InjectStartingAndEndingCode(code, "void main", fragCoordCode);
}
}
else {
code = code.replace(/gl_InstanceID/g, "gl_InstanceIndex");
code = code.replace(/gl_VertexID/g, "gl_VertexIndex");
const hasMultiviewExtension = defines.indexOf("#define MULTIVIEW") !== -1;
if (hasMultiviewExtension) {
return "#extension GL_OVR_multiview2 : require\nlayout (num_views = 2) in;\n" + code;
}
}
// Flip Y + convert z range from [-1,1] to [0,1]
if (!isFragment) {
const lastClosingCurly = code.lastIndexOf("}");
code = code.substring(0, lastClosingCurly);
code += "gl_Position.y *= yFactor_;\n";
// isNDCHalfZRange is always true in WebGPU
code += "}";
}
return code;
}
_applyTextureArrayProcessing(code, name) {
// Replaces the occurrences of name[XX] by nameXX
const regex = new RegExp(name + "\\s*\\[(.+)?\\]", "gm");
let match = regex.exec(code);
while (match !== null) {
const index = match[1];
let iindex = +index;
if (this._preProcessors && isNaN(iindex)) {
iindex = +this._preProcessors[index.trim()];
}
code = code.replace(match[0], name + iindex);
match = regex.exec(code);
}
return code;
}
_generateLeftOverUBOCode(name, uniformBufferDescription) {
let ubo = `layout(set = ${uniformBufferDescription.binding.groupIndex}, binding = ${uniformBufferDescription.binding.bindingIndex}) uniform ${name} {\n `;
for (const leftOverUniform of this._webgpuProcessingContext.leftOverUniforms) {
if (leftOverUniform.length > 0) {
ubo += ` ${leftOverUniform.type} ${leftOverUniform.name}[${leftOverUniform.length}];\n`;
}
else {
ubo += ` ${leftOverUniform.type} ${leftOverUniform.name};\n`;
}
}
ubo += "};\n\n";
return ubo;
}
finalizeShaders(vertexCode, fragmentCode) {
// make replacements for texture names in the texture array case
for (let i = 0; i < this._textureArrayProcessing.length; ++i) {
const name = this._textureArrayProcessing[i];
vertexCode = this._applyTextureArrayProcessing(vertexCode, name);
fragmentCode = this._applyTextureArrayProcessing(fragmentCode, name);
}
// inject the missing varying in the fragment shader
for (let i = 0; i < this._missingVaryings.length; ++i) {
const decl = this._missingVaryings[i];
if (decl && decl.length > 0) {
fragmentCode = decl + "\n" + fragmentCode;
}
}
// Builds the leftover UBOs.
const leftOverUBO = this._buildLeftOverUBO();
vertexCode = leftOverUBO + vertexCode;
fragmentCode = leftOverUBO + fragmentCode;
this._collectBindingNames();
this._preCreateBindGroupEntries();
this._preProcessors = null;
this._webgpuProcessingContext.vertexBufferKindToNumberOfComponents = {};
return { vertexCode, fragmentCode };
}
}
//# sourceMappingURL=webgpuShaderProcessorsGLSL.js.map