@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.
419 lines • 18.7 kB
JavaScript
/* eslint-disable @typescript-eslint/no-unused-vars */
import { ShaderCodeNode } from "./shaderCodeNode.js";
import { ShaderCodeCursor } from "./shaderCodeCursor.js";
import { ShaderCodeConditionNode } from "./shaderCodeConditionNode.js";
import { ShaderCodeTestNode } from "./shaderCodeTestNode.js";
import { ShaderDefineIsDefinedOperator } from "./Expressions/Operators/shaderDefineIsDefinedOperator.js";
import { ShaderDefineOrOperator } from "./Expressions/Operators/shaderDefineOrOperator.js";
import { ShaderDefineAndOperator } from "./Expressions/Operators/shaderDefineAndOperator.js";
import { ShaderDefineExpression } from "./Expressions/shaderDefineExpression.js";
import { ShaderDefineArithmeticOperator } from "./Expressions/Operators/shaderDefineArithmeticOperator.js";
import { _WarnImport } from "../../Misc/devTools.js";
import { _getGlobalDefines } from "../abstractEngine.functions.js";
const regexSE = /defined\s*?\((.+?)\)/g;
const regexSERevert = /defined\s*?\[(.+?)\]/g;
const regexShaderInclude = /#include\s?<(.+)>(\((.*)\))*(\[(.*)\])*/g;
const regexShaderDecl = /__decl__/;
const regexLightX = /light\{X\}.(\w*)/g;
const regexX = /\{X\}/g;
const reusableMatches = [];
const _MoveCursorRegex = /(#ifdef)|(#else)|(#elif)|(#endif)|(#ifndef)|(#if)/;
/** @internal */
export function Initialize(options) {
if (options.processor && options.processor.initializeShaders) {
options.processor.initializeShaders(options.processingContext);
}
}
/** @internal */
export function Process(sourceCode, options, callback, engine) {
if (options.processor?.preProcessShaderCode) {
sourceCode = options.processor.preProcessShaderCode(sourceCode, options.isFragment);
}
_ProcessIncludes(sourceCode, options, (codeWithIncludes) => {
if (options.processCodeAfterIncludes) {
codeWithIncludes = options.processCodeAfterIncludes(options.isFragment ? "fragment" : "vertex", codeWithIncludes, options.defines);
}
const migratedCode = _ProcessShaderConversion(codeWithIncludes, options, engine);
callback(migratedCode, codeWithIncludes);
});
}
/** @internal */
export function PreProcess(sourceCode, options, callback, engine) {
if (options.processor?.preProcessShaderCode) {
sourceCode = options.processor.preProcessShaderCode(sourceCode, options.isFragment);
}
_ProcessIncludes(sourceCode, options, (codeWithIncludes) => {
if (options.processCodeAfterIncludes) {
codeWithIncludes = options.processCodeAfterIncludes(options.isFragment ? "fragment" : "vertex", codeWithIncludes, options.defines);
}
const migratedCode = _ApplyPreProcessing(codeWithIncludes, options, engine);
callback(migratedCode, codeWithIncludes);
});
}
/** @internal */
export function Finalize(vertexCode, fragmentCode, options) {
if (!options.processor || !options.processor.finalizeShaders) {
return { vertexCode, fragmentCode };
}
return options.processor.finalizeShaders(vertexCode, fragmentCode, options.processingContext);
}
function _ProcessPrecision(source, options) {
if (options.processor?.noPrecision) {
return source;
}
const shouldUseHighPrecisionShader = options.shouldUseHighPrecisionShader;
if (source.indexOf("precision highp float") === -1) {
if (!shouldUseHighPrecisionShader) {
source = "precision mediump float;\n" + source;
}
else {
source = "precision highp float;\n" + source;
}
}
else {
if (!shouldUseHighPrecisionShader) {
// Moving highp to mediump
source = source.replace("precision highp float", "precision mediump float");
}
}
return source;
}
function _ExtractOperation(expression) {
const regex = /defined\((.+)\)/;
const match = regex.exec(expression);
if (match && match.length) {
return new ShaderDefineIsDefinedOperator(match[1].trim(), expression[0] === "!");
}
const operators = ["==", "!=", ">=", "<=", "<", ">"];
let operator = "";
let indexOperator = 0;
for (operator of operators) {
indexOperator = expression.indexOf(operator);
if (indexOperator > -1) {
break;
}
}
if (indexOperator === -1) {
return new ShaderDefineIsDefinedOperator(expression);
}
const define = expression.substring(0, indexOperator).trim();
const value = expression.substring(indexOperator + operator.length).trim();
return new ShaderDefineArithmeticOperator(define, operator, value);
}
function _BuildSubExpression(expression) {
expression = expression.replace(regexSE, "defined[$1]");
const postfix = ShaderDefineExpression.infixToPostfix(expression);
const stack = [];
for (const c of postfix) {
if (c !== "||" && c !== "&&") {
stack.push(c);
}
else if (stack.length >= 2) {
let v1 = stack[stack.length - 1], v2 = stack[stack.length - 2];
stack.length -= 2;
const operator = c == "&&" ? new ShaderDefineAndOperator() : new ShaderDefineOrOperator();
if (typeof v1 === "string") {
v1 = v1.replace(regexSERevert, "defined($1)");
}
if (typeof v2 === "string") {
v2 = v2.replace(regexSERevert, "defined($1)");
}
operator.leftOperand = typeof v2 === "string" ? _ExtractOperation(v2) : v2;
operator.rightOperand = typeof v1 === "string" ? _ExtractOperation(v1) : v1;
stack.push(operator);
}
}
let result = stack[stack.length - 1];
if (typeof result === "string") {
result = result.replace(regexSERevert, "defined($1)");
}
// note: stack.length !== 1 if there was an error in the parsing
return typeof result === "string" ? _ExtractOperation(result) : result;
}
function _BuildExpression(line, start) {
const node = new ShaderCodeTestNode();
const command = line.substring(0, start);
let expression = line.substring(start);
expression = expression.substring(0, (expression.indexOf("//") + 1 || expression.length + 1) - 1).trim();
if (command === "#ifdef") {
node.testExpression = new ShaderDefineIsDefinedOperator(expression);
}
else if (command === "#ifndef") {
node.testExpression = new ShaderDefineIsDefinedOperator(expression, true);
}
else {
node.testExpression = _BuildSubExpression(expression);
}
return node;
}
function _MoveCursorWithinIf(cursor, rootNode, ifNode, preProcessorsFromCode) {
let line = cursor.currentLine;
while (_MoveCursor(cursor, ifNode, preProcessorsFromCode)) {
line = cursor.currentLine;
const first5 = line.substring(0, 5).toLowerCase();
if (first5 === "#else") {
const elseNode = new ShaderCodeNode();
rootNode.children.push(elseNode);
_MoveCursor(cursor, elseNode, preProcessorsFromCode);
return;
}
else if (first5 === "#elif") {
const elifNode = _BuildExpression(line, 5);
rootNode.children.push(elifNode);
ifNode = elifNode;
}
}
}
function _MoveCursor(cursor, rootNode, preProcessorsFromCode) {
while (cursor.canRead) {
cursor.lineIndex++;
const line = cursor.currentLine;
if (line.indexOf("#") >= 0) {
const matches = _MoveCursorRegex.exec(line);
if (matches && matches.length) {
const keyword = matches[0];
switch (keyword) {
case "#ifdef": {
const newRootNode = new ShaderCodeConditionNode();
rootNode.children.push(newRootNode);
const ifNode = _BuildExpression(line, 6);
newRootNode.children.push(ifNode);
_MoveCursorWithinIf(cursor, newRootNode, ifNode, preProcessorsFromCode);
break;
}
case "#else":
case "#elif":
return true;
case "#endif":
return false;
case "#ifndef": {
const newRootNode = new ShaderCodeConditionNode();
rootNode.children.push(newRootNode);
const ifNode = _BuildExpression(line, 7);
newRootNode.children.push(ifNode);
_MoveCursorWithinIf(cursor, newRootNode, ifNode, preProcessorsFromCode);
break;
}
case "#if": {
const newRootNode = new ShaderCodeConditionNode();
const ifNode = _BuildExpression(line, 3);
rootNode.children.push(newRootNode);
newRootNode.children.push(ifNode);
_MoveCursorWithinIf(cursor, newRootNode, ifNode, preProcessorsFromCode);
break;
}
}
continue;
}
}
const newNode = new ShaderCodeNode();
newNode.line = line;
rootNode.children.push(newNode);
// Detect additional defines
if (line[0] === "#" && line[1] === "d") {
const split = line.replace(";", "").split(" ");
newNode.additionalDefineKey = split[1];
if (split.length === 3) {
newNode.additionalDefineValue = split[2];
}
}
}
return false;
}
function _EvaluatePreProcessors(sourceCode, preprocessors, options, preProcessorsFromCode) {
const rootNode = new ShaderCodeNode();
const cursor = new ShaderCodeCursor();
cursor.lineIndex = -1;
cursor.lines = sourceCode.split("\n");
// Decompose (We keep it in 2 steps so it is easier to maintain and perf hit is insignificant)
_MoveCursor(cursor, rootNode, preProcessorsFromCode);
// Recompose
return rootNode.process(preprocessors, options, preProcessorsFromCode);
}
function _PreparePreProcessors(options, engine) {
const defines = options.defines;
const preprocessors = {};
for (const define of defines) {
const keyValue = define.replace("#define", "").replace(";", "").trim();
const split = keyValue.split(" ");
preprocessors[split[0]] = split.length > 1 ? split[1] : "";
}
if (options.processor?.shaderLanguage === 0 /* ShaderLanguage.GLSL */) {
preprocessors["GL_ES"] = "true";
}
preprocessors["__VERSION__"] = options.version;
preprocessors[options.platformName] = "true";
_getGlobalDefines(preprocessors, engine?.isNDCHalfZRange, engine?.useReverseDepthBuffer, engine?.useExactSrgbConversions);
return preprocessors;
}
function _ProcessShaderConversion(sourceCode, options, engine) {
let preparedSourceCode = _ProcessPrecision(sourceCode, options);
if (!options.processor) {
return preparedSourceCode;
}
// Already converted
if (options.processor.shaderLanguage === 0 /* ShaderLanguage.GLSL */ && preparedSourceCode.indexOf("#version 3") !== -1) {
preparedSourceCode = preparedSourceCode.replace("#version 300 es", "");
if (!options.processor.parseGLES3) {
return preparedSourceCode;
}
}
const defines = options.defines;
const preprocessors = _PreparePreProcessors(options, engine);
// General pre processing
if (options.processor.preProcessor) {
preparedSourceCode = options.processor.preProcessor(preparedSourceCode, defines, preprocessors, options.isFragment, options.processingContext);
}
const preProcessorsFromCode = {};
preparedSourceCode = _EvaluatePreProcessors(preparedSourceCode, preprocessors, options, preProcessorsFromCode);
// Post processing
if (options.processor.postProcessor) {
preparedSourceCode = options.processor.postProcessor(preparedSourceCode, defines, options.isFragment, options.processingContext, engine
? {
drawBuffersExtensionDisabled: engine.getCaps().drawBuffersExtension ? false : true,
}
: {}, preprocessors, preProcessorsFromCode);
}
// Inline functions tagged with #define inline
if (engine?._features.needShaderCodeInlining) {
preparedSourceCode = engine.inlineShaderCode(preparedSourceCode);
}
return preparedSourceCode;
}
function _ApplyPreProcessing(sourceCode, options, engine) {
let preparedSourceCode = sourceCode;
const defines = options.defines;
const preprocessors = _PreparePreProcessors(options, engine);
// General pre processing
if (options.processor?.preProcessor) {
preparedSourceCode = options.processor.preProcessor(preparedSourceCode, defines, preprocessors, options.isFragment, options.processingContext);
}
const preProcessorsFromCode = {};
preparedSourceCode = _EvaluatePreProcessors(preparedSourceCode, preprocessors, options, preProcessorsFromCode);
// Post processing
if (options.processor?.postProcessor) {
preparedSourceCode = options.processor.postProcessor(preparedSourceCode, defines, options.isFragment, options.processingContext, engine
? {
drawBuffersExtensionDisabled: engine.getCaps().drawBuffersExtension ? false : true,
}
: {}, preprocessors, preProcessorsFromCode);
}
// Inline functions tagged with #define inline
if (engine._features.needShaderCodeInlining) {
preparedSourceCode = engine.inlineShaderCode(preparedSourceCode);
}
return preparedSourceCode;
}
/** @internal */
export function _ProcessIncludes(sourceCode, options, callback) {
reusableMatches.length = 0;
let match;
// stay back-compat to the old matchAll syntax
while ((match = regexShaderInclude.exec(sourceCode)) !== null) {
reusableMatches.push(match);
}
let returnValue = String(sourceCode);
let parts = [sourceCode];
let keepProcessing = false;
for (const match of reusableMatches) {
let includeFile = match[1];
// Uniform declaration
if (includeFile.indexOf("__decl__") !== -1) {
includeFile = includeFile.replace(regexShaderDecl, "");
if (options.supportsUniformBuffers) {
includeFile = includeFile.replace("Vertex", "Ubo").replace("Fragment", "Ubo");
}
includeFile = includeFile + "Declaration";
}
if (options.includesShadersStore[includeFile]) {
// Substitution
let includeContent = options.includesShadersStore[includeFile];
if (match[2]) {
const splits = match[3].split(",");
for (let index = 0; index < splits.length; index += 2) {
const source = new RegExp(splits[index], "g");
const dest = splits[index + 1];
includeContent = includeContent.replace(source, dest);
}
}
if (match[4]) {
const indexString = match[5];
if (indexString.indexOf("..") !== -1) {
const indexSplits = indexString.split("..");
const minIndex = parseInt(indexSplits[0]);
let maxIndex = parseInt(indexSplits[1]);
let sourceIncludeContent = includeContent.slice(0);
includeContent = "";
if (isNaN(maxIndex)) {
maxIndex = options.indexParameters[indexSplits[1]];
}
for (let i = minIndex; i < maxIndex; i++) {
if (!options.supportsUniformBuffers) {
// Ubo replacement
sourceIncludeContent = sourceIncludeContent.replace(regexLightX, (str, p1) => {
return p1 + "{X}";
});
}
includeContent += sourceIncludeContent.replace(regexX, i.toString()) + "\n";
}
}
else {
if (!options.supportsUniformBuffers) {
// Ubo replacement
includeContent = includeContent.replace(regexLightX, (str, p1) => {
return p1 + "{X}";
});
}
includeContent = includeContent.replace(regexX, indexString);
}
}
// Replace
// Split all parts on match[0] and intersperse the parts with the include content
const newParts = [];
for (const part of parts) {
const splitPart = part.split(match[0]);
for (let i = 0; i < splitPart.length - 1; i++) {
newParts.push(splitPart[i]);
newParts.push(includeContent);
}
newParts.push(splitPart[splitPart.length - 1]);
}
parts = newParts;
keepProcessing = keepProcessing || includeContent.indexOf("#include<") >= 0 || includeContent.indexOf("#include <") >= 0;
}
else {
const includeShaderUrl = options.shadersRepository + "ShadersInclude/" + includeFile + ".fx";
_functionContainer.loadFile(includeShaderUrl, (fileContent) => {
options.includesShadersStore[includeFile] = fileContent;
_ProcessIncludes(parts.join(""), options, callback);
});
return;
}
}
reusableMatches.length = 0;
returnValue = parts.join("");
if (keepProcessing) {
_ProcessIncludes(returnValue.toString(), options, callback);
}
else {
callback(returnValue);
}
}
/** @internal */
export const _functionContainer = {
/**
* Loads a file from a url
* @param url url to load
* @param onSuccess callback called when the file successfully loads
* @param onProgress callback called while file is loading (if the server supports this mode)
* @param offlineProvider defines the offline provider for caching
* @param useArrayBuffer defines a boolean indicating that date must be returned as ArrayBuffer
* @param onError callback called when the file fails to load
* @returns a file request object
* @internal
*/
loadFile: (url, onSuccess, onProgress, offlineProvider, useArrayBuffer, onError) => {
throw _WarnImport("FileTools");
},
};
//# sourceMappingURL=shaderProcessor.js.map