cesium
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
243 lines (211 loc) • 7.16 kB
JavaScript
import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
/**
* A function to port GLSL shaders from GLSL ES 1.00 to GLSL ES 3.00
*
* This function is nowhere near comprehensive or complete. It just
* handles some common cases.
*
* Note that this function requires the presence of the
* "#define OUTPUT_DECLARATION" line that is appended
* by ShaderSource.
*
* @private
*/
function modernizeShader(source, isFragmentShader) {
const outputDeclarationRegex = /#define OUTPUT_DECLARATION/;
const splitSource = source.split("\n");
if (/#version 300 es/g.test(source)) {
return source;
}
let outputDeclarationLine = -1;
let i, line;
for (i = 0; i < splitSource.length; ++i) {
line = splitSource[i];
if (outputDeclarationRegex.test(line)) {
outputDeclarationLine = i;
break;
}
}
if (outputDeclarationLine === -1) {
throw new DeveloperError("Could not find a #define OUTPUT_DECLARATION!");
}
const outputVariables = [];
for (i = 0; i < 10; i++) {
const fragDataString = `gl_FragData\\[${i}\\]`;
const newOutput = `czm_out${i}`;
const regex = new RegExp(fragDataString, "g");
if (regex.test(source)) {
setAdd(newOutput, outputVariables);
replaceInSourceString(fragDataString, newOutput, splitSource);
splitSource.splice(
outputDeclarationLine,
0,
`layout(location = ${i}) out vec4 ${newOutput};`
);
outputDeclarationLine += 1;
}
}
const czmFragColor = "czm_fragColor";
if (findInSource("gl_FragColor", splitSource)) {
setAdd(czmFragColor, outputVariables);
replaceInSourceString("gl_FragColor", czmFragColor, splitSource);
splitSource.splice(
outputDeclarationLine,
0,
"layout(location = 0) out vec4 czm_fragColor;"
);
outputDeclarationLine += 1;
}
const variableMap = getVariablePreprocessorBranch(
outputVariables,
splitSource
);
const lineAdds = {};
for (i = 0; i < splitSource.length; i++) {
line = splitSource[i];
for (const variable in variableMap) {
if (variableMap.hasOwnProperty(variable)) {
const matchVar = new RegExp(
`(layout)[^]+(out)[^]+(${variable})[^]+`,
"g"
);
if (matchVar.test(line)) {
lineAdds[line] = variable;
}
}
}
}
for (const layoutDeclaration in lineAdds) {
if (lineAdds.hasOwnProperty(layoutDeclaration)) {
const variableName = lineAdds[layoutDeclaration];
let lineNumber = splitSource.indexOf(layoutDeclaration);
const entry = variableMap[variableName];
const depth = entry.length;
for (let d = 0; d < depth; d++) {
splitSource.splice(lineNumber, 0, entry[d]);
}
lineNumber += depth + 1;
for (let d = depth - 1; d >= 0; d--) {
splitSource.splice(lineNumber, 0, `#endif //${entry[d]}`);
}
}
}
const webgl2UniqueID = "WEBGL_2";
const webgl2DefineMacro = `#define ${webgl2UniqueID}`;
const versionThree = "#version 300 es";
let foundVersion = false;
for (i = 0; i < splitSource.length; i++) {
if (/#version/.test(splitSource[i])) {
splitSource[i] = versionThree;
foundVersion = true;
break;
}
}
if (!foundVersion) {
splitSource.splice(0, 0, versionThree);
}
splitSource.splice(1, 0, webgl2DefineMacro);
removeExtension("EXT_draw_buffers", webgl2UniqueID, splitSource);
removeExtension("EXT_frag_depth", webgl2UniqueID, splitSource);
removeExtension("OES_standard_derivatives", webgl2UniqueID, splitSource);
replaceInSourceString("texture2D", "texture", splitSource);
replaceInSourceString("texture3D", "texture", splitSource);
replaceInSourceString("textureCube", "texture", splitSource);
replaceInSourceString("gl_FragDepthEXT", "gl_FragDepth", splitSource);
if (isFragmentShader) {
replaceInSourceString("varying", "in", splitSource);
} else {
replaceInSourceString("attribute", "in", splitSource);
replaceInSourceString("varying", "out", splitSource);
}
return compileSource(splitSource);
}
// Note that this fails if your string looks like
// searchString[singleCharacter]searchString
function replaceInSourceString(str, replacement, splitSource) {
const regexStr = `(^|[^\\w])(${str})($|[^\\w])`;
const regex = new RegExp(regexStr, "g");
const splitSourceLength = splitSource.length;
for (let i = 0; i < splitSourceLength; ++i) {
const line = splitSource[i];
splitSource[i] = line.replace(regex, `$1${replacement}$3`);
}
}
function replaceInSourceRegex(regex, replacement, splitSource) {
const splitSourceLength = splitSource.length;
for (let i = 0; i < splitSourceLength; ++i) {
const line = splitSource[i];
splitSource[i] = line.replace(regex, replacement);
}
}
function findInSource(str, splitSource) {
const regexStr = `(^|[^\\w])(${str})($|[^\\w])`;
const regex = new RegExp(regexStr, "g");
const splitSourceLength = splitSource.length;
for (let i = 0; i < splitSourceLength; ++i) {
const line = splitSource[i];
if (regex.test(line)) {
return true;
}
}
return false;
}
function compileSource(splitSource) {
let wholeSource = "";
const splitSourceLength = splitSource.length;
for (let i = 0; i < splitSourceLength; ++i) {
wholeSource += `${splitSource[i]}\n`;
}
return wholeSource;
}
function setAdd(variable, set) {
if (set.indexOf(variable) === -1) {
set.push(variable);
}
}
function getVariablePreprocessorBranch(layoutVariables, splitSource) {
const variableMap = {};
const numLayoutVariables = layoutVariables.length;
const stack = [];
for (let i = 0; i < splitSource.length; ++i) {
const line = splitSource[i];
const hasIF = /(#ifdef|#if)/g.test(line);
const hasELSE = /#else/g.test(line);
const hasENDIF = /#endif/g.test(line);
if (hasIF) {
stack.push(line);
} else if (hasELSE) {
const top = stack[stack.length - 1];
let op = top.replace("ifdef", "ifndef");
if (/if/g.test(op)) {
op = op.replace(/(#if\s+)(\S*)([^]*)/, "$1!($2)$3");
}
stack.pop();
stack.push(op);
} else if (hasENDIF) {
stack.pop();
} else if (!/layout/g.test(line)) {
for (let varIndex = 0; varIndex < numLayoutVariables; ++varIndex) {
const varName = layoutVariables[varIndex];
if (line.indexOf(varName) !== -1) {
if (!defined(variableMap[varName])) {
variableMap[varName] = stack.slice();
} else {
variableMap[varName] = variableMap[varName].filter(function (x) {
return stack.indexOf(x) >= 0;
});
}
}
}
}
}
return variableMap;
}
function removeExtension(name, webgl2UniqueID, splitSource) {
const regex = `#extension\\s+GL_${name}\\s+:\\s+[a-zA-Z0-9]+\\s*$`;
replaceInSourceRegex(new RegExp(regex, "g"), "", splitSource);
// replace any possible directive #ifdef (GL_EXT_extension) with WEBGL_2 unique directive
replaceInSourceString(`GL_${name}`, webgl2UniqueID, splitSource);
}
export default modernizeShader;