@itwin/core-frontend
Version:
iTwin.js frontend components
922 lines • 43.4 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module WebGL
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProgramBuilder = exports.FragmentShaderBuilder = exports.VertexShaderBuilder = exports.ShaderBuilder = exports.SourceBuilder = exports.ShaderVariables = exports.ShaderVariable = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const Instancing_1 = require("./glsl/Instancing");
const PlanarClassification_1 = require("./glsl/PlanarClassification");
const Vertex_1 = require("./glsl/Vertex");
const ShaderProgram_1 = require("./ShaderProgram");
/** @internal */
var Convert;
(function (Convert) {
function typeToString(type) {
switch (type) {
case 0 /* VariableType.Boolean */: return "bool";
case 1 /* VariableType.Int */: return "int";
case 2 /* VariableType.Float */: return "float";
case 3 /* VariableType.Vec2 */: return "vec2";
case 4 /* VariableType.Vec3 */: return "vec3";
case 5 /* VariableType.Vec4 */: return "vec4";
case 6 /* VariableType.Mat3 */: return "mat3";
case 7 /* VariableType.Mat4 */: return "mat4";
case 8 /* VariableType.Sampler2D */: return "sampler2D";
case 9 /* VariableType.SamplerCube */: return "samplerCube";
case 10 /* VariableType.Uint */: return "uint";
case 11 /* VariableType.BVec2 */: return "bvec2";
default:
(0, core_bentley_1.assert)(false);
return "undefined";
}
}
Convert.typeToString = typeToString;
function scopeToString(scope, isVertexShader) {
switch (scope) {
case 0 /* VariableScope.Global */: return "";
case 1 /* VariableScope.Varying */: return (isVertexShader ? "out" : "in");
case 2 /* VariableScope.Uniform */: return "uniform";
default:
(0, core_bentley_1.assert)(false);
return "undefined";
}
}
Convert.scopeToString = scopeToString;
function precisionToString(precision) {
switch (precision) {
case 0 /* VariablePrecision.Default */: return "";
case 1 /* VariablePrecision.Low */: return "lowp";
case 2 /* VariablePrecision.Medium */: return "mediump";
case 3 /* VariablePrecision.High */: return "highp";
default:
(0, core_bentley_1.assert)(false);
return "undefined";
}
}
Convert.precisionToString = precisionToString;
})(Convert || (Convert = {}));
/** Represents a variable within a fragment or vertex shader.
* @internal
*/
class ShaderVariable {
_addBinding;
name;
value; // for global variables only
type;
scope;
precision;
isConst = false; // for global variables only
length; // for uniform arrays only
constructor(name, type, scope, precision, isConst, addBinding, value, length = 0) {
this._addBinding = addBinding;
this.name = name;
this.value = value;
this.type = type;
this.scope = scope;
this.precision = precision;
this.isConst = isConst;
this.length = length;
}
static create(name, type, scope, addBinding, precision = 0 /* VariablePrecision.Default */) {
return new ShaderVariable(name, type, scope, precision, false, addBinding, undefined);
}
static createArray(name, type, length, scope, addBinding, precision = 0 /* VariablePrecision.Default */) {
return new ShaderVariable(name, type, scope, precision, false, addBinding, undefined, length);
}
static createGlobal(name, type, value, isConst = false) {
return new ShaderVariable(name, type, 0 /* VariableScope.Global */, 0 /* VariablePrecision.Default */, isConst, undefined, value);
}
get hasBinding() { return undefined !== this._addBinding; }
addBinding(prog) {
if (undefined !== this._addBinding)
this._addBinding(prog);
}
get typeName() { return Convert.typeToString(this.type); }
getScopeName(isVertexShader) { return Convert.scopeToString(this.scope, isVertexShader); }
get precisionName() { return Convert.precisionToString(this.precision); }
/** Constructs the single-line declaration of this variable */
buildDeclaration(isVertexShader) {
const parts = new Array();
if (this.isConst)
parts.push("const");
const scopeName = this.getScopeName(isVertexShader);
if (0 < scopeName.length)
parts.push(scopeName);
const precisionName = this.precisionName;
if (0 < precisionName.length)
parts.push(precisionName);
parts.push(this.typeName);
if (this.length > 0)
parts.push(`${this.name}[${this.length.toFixed(0)}]`);
else
parts.push(this.name);
if (undefined !== this.value && 0 < this.value.length) {
parts.push("=");
parts.push(this.value);
}
return `${parts.join(" ")};`;
}
}
exports.ShaderVariable = ShaderVariable;
/**
* Represents the set of variables defined and used within a fragment or vertex shader.
* If the same variable is used in both the fragment and vertex shader (e.g., a varying variable), it should be defined in both ShaderBuilders' ShaderVariables object.
* @internal
*/
class ShaderVariables {
_list = new Array();
/** Find an existing variable with the specified name */
find(name) { return this._list.find((v) => v.name === name); }
/** Add a new variable, if a variable with the same name does not already exist. return true if added */
addVariable(v) {
const found = this.find(v.name);
if (undefined !== found) {
(0, core_bentley_1.assert)(found.type === v.type);
// assume same binding etc...
return false;
}
else {
this._list.push(v);
return true;
}
}
addUniform(name, type, binding, precision = 0 /* VariablePrecision.Default */) {
this.addVariable(ShaderVariable.create(name, type, 2 /* VariableScope.Uniform */, binding, precision));
}
addUniformArray(name, type, length, binding) {
this.addVariable(ShaderVariable.createArray(name, type, length, 2 /* VariableScope.Uniform */, binding));
}
addVarying(name, type) {
return this.addVariable(ShaderVariable.create(name, type, 1 /* VariableScope.Varying */));
}
addGlobal(name, type, value, isConst = false) {
this.addVariable(ShaderVariable.createGlobal(name, type, value, isConst));
}
addConstant(name, type, value) {
this.addGlobal(name, type, value, true);
}
addBitFlagConstant(name, value) {
this.addGlobal(name, 10 /* VariableType.Uint */, `${(2 ** value).toFixed(0)}u`, true);
}
/** Constructs the lines of glsl code declaring the variables of a certain scope. */
buildScopeDeclarations(isVertexShader, scope, constants) {
let decls = "";
for (const v of this._list) {
if (scope === v.scope && (undefined === constants ? true : v.isConst === constants))
decls += `${v.buildDeclaration(isVertexShader)}\n`;
}
return decls;
}
/** Constructs the lines of glsl code declaring all of the variables. */
buildDeclarations(isVertexShader) {
let decls = "";
if (isVertexShader) {
decls += this.buildScopeDeclarations(isVertexShader, 2 /* VariableScope.Uniform */);
decls += this.buildScopeDeclarations(isVertexShader, 0 /* VariableScope.Global */, true);
decls += this.buildScopeDeclarations(isVertexShader, 0 /* VariableScope.Global */, false);
decls += this.buildScopeDeclarations(isVertexShader, 1 /* VariableScope.Varying */);
}
else {
decls += this.buildScopeDeclarations(isVertexShader, 1 /* VariableScope.Varying */);
decls += this.buildScopeDeclarations(isVertexShader, 2 /* VariableScope.Uniform */);
decls += this.buildScopeDeclarations(isVertexShader, 0 /* VariableScope.Global */, true);
decls += this.buildScopeDeclarations(isVertexShader, 0 /* VariableScope.Global */, false);
}
return decls;
}
/**
* For every uniform and attribute variable not contained in the optional 'predefined' list, invokes the associated binding function
* to add the corresponding Uniform or Attribute object to the ShaderProgram.
*/
addBindings(prog, predefined) {
for (const v of this._list) {
// Some variables exist in both frag and vert shaders - only add them to the program once.
if (v.hasBinding && (undefined === predefined || undefined === predefined.find(v.name))) {
v.addBinding(prog);
}
}
}
get length() { return this._list.length; }
findSlot(variableSize, loopSize, registers) {
// Find the first available slot into which to insert this variable
for (let i = 0; i < loopSize; i++) {
const newSize = registers[i] + variableSize;
if (newSize <= 4) {
registers[i] = newSize;
return i;
}
}
return -1;
}
// Return string of varying types with their theoretical slot numbers
checkMaxVaryingVectors(fragSource) {
// Varyings go into a matrix of 4 columns and GL_MAX_VARYING_VECTORS rows of floats.
// The packing rules are defined by the standard. Specifically each row can contain one of:
// vec4
// vec3 (+ float)
// vec2 (+ vec2)
// vec2 (+ float (+ float))
// float (+ float (+ float (+ float)))
// Varyings are packed in order of size from largest to smallest
const loopSize = 64;
const registers = Array(loopSize + 1).fill(0);
let outStr = "";
let slot = 0;
const varyings = this._list.filter((variable) => 1 /* VariableScope.Varying */ === variable.scope);
// Add in any built in vars that count as varyings if they are used
if (fragSource.includes("gl_FragCoord")) {
varyings.push(ShaderVariable.create("gl_FragCoord", 5 /* VariableType.Vec4 */, 1 /* VariableScope.Varying */));
}
if (fragSource.includes("gl_PointCoord")) {
varyings.push(ShaderVariable.create("gl_PointCoord", 3 /* VariableType.Vec2 */, 1 /* VariableScope.Varying */));
}
if (fragSource.includes("gl_FrontFacing")) {
varyings.push(ShaderVariable.create("gl_FrontFacing", 0 /* VariableType.Boolean */, 1 /* VariableScope.Varying */));
}
// Need to process in size order (largest first)
varyings.sort((a, b) => b.type - a.type); // this is good enough to sort by
for (const variable of varyings) {
let variableSize = 0;
switch (variable.type) {
case 0 /* VariableType.Boolean */:
case 1 /* VariableType.Int */:
case 2 /* VariableType.Float */:
variableSize = 1;
break;
case 3 /* VariableType.Vec2 */:
case 11 /* VariableType.BVec2 */:
variableSize = 2;
break;
case 4 /* VariableType.Vec3 */:
variableSize = 3;
break;
case 5 /* VariableType.Vec4 */:
variableSize = 4;
break;
default:
(0, core_bentley_1.assert)(false, "Invalid varying variable type");
continue;
}
slot = this.findSlot(variableSize, loopSize, registers);
outStr += `// varying slot ${slot} ${Convert.typeToString(variable.type)} ${variable.name}\n`;
}
const slotsUsed = registers.indexOf(0);
registers.length = slotsUsed;
outStr += `// Slots used: ${slotsUsed} [${registers.toString()}]\n`;
// debug output modes
const outputAll = true; // false just outputs varyings that use more than 8
if (outputAll) {
return outStr;
}
else {
if (slotsUsed > 8)
return outStr;
else
return "";
}
}
// Return the number of varying vectors used by the shader.
// Varyings go into a matrix of 4 columns and GL_MAX_VARYING_VECTORS rows of floats.
// The packing rules are defined by the standard. Specifically each row can contain one of:
// vec4
// vec3 (+ float)
// vec2 (+ vec2)
// vec2 (+ float (+ float))
// float (+ float (+ float (+ float)))
// Varyings are packed in order of size from largest to smallest
computeNumVaryingVectors(fragSource) {
const loopSize = 64;
const registers = Array(loopSize + 1).fill(0);
const varyings = this._list.filter((variable) => 1 /* VariableScope.Varying */ === variable.scope);
// Add in any built in vars that count as varyings if they are used
if (fragSource.includes("gl_FragCoord")) {
varyings.push(ShaderVariable.create("gl_FragCoord", 5 /* VariableType.Vec4 */, 1 /* VariableScope.Varying */));
}
if (fragSource.includes("gl_PointCoord")) {
varyings.push(ShaderVariable.create("gl_PointCoord", 3 /* VariableType.Vec2 */, 1 /* VariableScope.Varying */));
}
if (fragSource.includes("gl_FrontFacing")) {
varyings.push(ShaderVariable.create("gl_FrontFacing", 0 /* VariableType.Boolean */, 1 /* VariableScope.Varying */));
}
// Need to process in size order (largest first)
varyings.sort((a, b) => b.type - a.type); // this is good enough to sort by
for (const variable of varyings) {
if (1 /* VariableScope.Varying */ !== variable.scope)
continue;
let variableSize = 0;
switch (variable.type) {
case 0 /* VariableType.Boolean */:
case 1 /* VariableType.Int */:
case 2 /* VariableType.Float */:
variableSize = 1;
break;
case 3 /* VariableType.Vec2 */:
case 11 /* VariableType.BVec2 */:
variableSize = 2;
break;
case 4 /* VariableType.Vec3 */:
variableSize = 3;
break;
case 5 /* VariableType.Vec4 */:
variableSize = 4;
break;
default:
(0, core_bentley_1.assert)(false, "Invalid varying variable type");
continue;
}
this.findSlot(variableSize, loopSize, registers);
}
const slotsUsed = registers.indexOf(0);
return slotsUsed;
}
}
exports.ShaderVariables = ShaderVariables;
/** Convenience API for assembling glsl source code.
* @internal
*/
class SourceBuilder {
source = "";
/* Append the specified string to the glsl source */
add(what) { this.source += what; }
/* Append a new-line to the glsl source */
newline() { this.add("\n"); }
/* Append the specified string to the glsl source, followed by a new-line */
addline(what) {
this.add(what);
this.newline();
}
/**
* Construct a function definition given the function signature and body. For example:
* buildFunctionDefinition("float average(float a, float b)", "\n return (a + b) / 2.0;\n");
* will produce:
* "float average(float a, float b) {
* return (a + b) / 2.0;
* }"
* For an inline function:
* buildFunctionDefinition("float average(float a, float b)", "return (a + b) / 2.0;");
* will produce:
* "float average(float a, float b) { return (a + b) / 2.0; }"
*/
static buildFunctionDefinition(declaration, implementation) {
// If implementation does not start with a newline then assume it is an inline function & add spaces between braces.
if ("\n" === implementation.charAt(0))
return `${declaration} {${implementation}}\n\n`;
else
return `${declaration} { ${implementation} }\n\n`;
}
/** Constructs a function definition as described by buildFunctionDefinition() and appends it to the glsl source. */
addFunction(declaration, implementation) { this.add(SourceBuilder.buildFunctionDefinition(declaration, implementation)); }
/** Constructs the definition of the main() function using the supplied function body and appends it to the glsl source. */
addMain(implementation) { this.addFunction("void main()", implementation); }
}
exports.SourceBuilder = SourceBuilder;
/**
* Represents a fragment or vertex shader under construction. The shader consists of a set of defined variables,
* plus a set of code snippets which can be concatenated together to form the shader source.
* @internal
*/
class ShaderBuilder extends ShaderVariables {
_components = new Array();
_functions = [];
_extensions = [];
_macros = [];
_fragOutputs = [];
_version = "";
headerComment = "";
_flags;
_initializers = new Array();
get usesInstancedGeometry() {
return !!this._flags.instanced;
}
addInitializer(initializer) {
if (-1 === this._initializers.indexOf(initializer))
this._initializers.push(initializer);
}
constructor(maxComponents, flags) {
super();
this._components.length = maxComponents;
this._flags = flags;
this._version = "#version 300 es";
this.addDefine("TEXTURE", "texture");
this.addDefine("TEXTURE_CUBE", "texture");
this.addDefine("TEXTURE_PROJ", "textureProj");
}
addComponent(index, component) {
(0, core_bentley_1.assert)(index < this._components.length);
// assume if caller is replacing an existing component, they know what they're doing...
this._components[index] = component;
}
removeComponent(index) {
(0, core_bentley_1.assert)(index < this._components.length);
this._components[index] = undefined;
}
getComponent(index) {
(0, core_bentley_1.assert)(index < this._components.length);
return this._components[index];
}
addFunction(declarationOrFull, implementation) {
let def = declarationOrFull;
if (undefined !== implementation)
def = SourceBuilder.buildFunctionDefinition(`\n${declarationOrFull}`, implementation);
if (undefined === this.findFunction(def))
this._functions.push(def);
}
replaceFunction(existing, replacement) {
const index = this._functions.indexOf(existing);
if (-1 !== index) {
this._functions[index] = replacement;
}
(0, core_bentley_1.assert)(-1 !== index);
return -1 !== index;
}
findFunction(func) {
return this._functions.find((f) => f === func);
}
addExtension(extName) {
if (-1 === this._extensions.indexOf(extName))
this._extensions.push(extName);
}
addMacro(macro) {
if (-1 === this._macros.indexOf(macro))
this._macros.push(macro);
}
addDefine(name, value) {
const defineName = `#define ${name} `;
const macro = defineName + value;
const index = this._macros.findIndex((x) => x.startsWith(defineName));
if (-1 !== index)
this._macros[index] = macro;
else
this._macros.push(defineName + value);
}
clearFragOutput() {
while (this._fragOutputs.length > 0)
this._fragOutputs.pop();
}
addFragOutput(name, value) {
if (-1 === value) {
this._fragOutputs.push("out vec4 FragColor;");
return;
}
this._fragOutputs.push(`layout(location = ${value}) out vec4 ${name};`);
}
buildPreludeCommon(attrMap, isVertexShader) {
const src = new SourceBuilder();
// Version number (must be first thing for GLSL 300)
src.addline(this._version);
// Header comment
src.newline();
if ("" !== this.headerComment) {
src.addline(this.headerComment);
src.newline();
}
// Macros
for (const macro of this._macros)
src.addline(macro);
// Extensions
for (const ext of this._extensions)
src.addline(`#extension ${ext} : enable`);
// Default precisions
src.addline("precision highp float;");
src.addline("precision highp int;");
src.newline();
// Attribute declarations
if (attrMap !== undefined) {
attrMap.forEach((attr, key) => {
src.addline(`in ${Convert.typeToString(attr.type)} ${key};`);
});
}
// Variable declarations
src.add(this.buildDeclarations(isVertexShader));
// Layouts
for (const layout of this._fragOutputs)
src.addline(layout);
// Functions
for (const func of this._functions) {
src.add(func);
}
if (!src.source.endsWith("\n\n"))
src.newline();
return src;
}
copyCommon(src) {
this._version = src._version;
this.headerComment = src.headerComment;
this._initializers = [...src._initializers];
this._components = [...src._components];
this._functions = [...src._functions];
this._extensions = [...src._extensions];
this._list = [...src._list];
this._macros = [...src._macros];
this._fragOutputs = [...src._fragOutputs];
}
}
exports.ShaderBuilder = ShaderBuilder;
/** Assembles the source code for a vertex shader from a set of modular components.
* @internal
*/
class VertexShaderBuilder extends ShaderBuilder {
_computedVarying = new Array();
buildPrelude(attrMap) { return this.buildPreludeCommon(attrMap, true); }
constructor(flags = {}) {
super(14 /* VertexShaderComponent.COUNT */, flags);
this.addDefine("MAT_NORM", "g_nmx");
if (this.usesInstancedGeometry) {
(0, Instancing_1.addInstancedModelMatrixRTC)(this);
this.addDefine("MAT_MV", "g_mv");
this.addDefine("MAT_MVP", "g_mvp");
}
else {
this.addDefine("MAT_MV", "u_mv");
this.addDefine("MAT_MVP", "u_mvp");
}
(0, Vertex_1.addPosition)(this, this.usesVertexTable);
}
get usesVertexTable() {
return undefined !== this._flags.positionType;
}
get positionType() {
(0, core_bentley_1.assert)(undefined !== this._flags.positionType);
return this._flags.positionType;
}
get(id) { return this.getComponent(id); }
set(id, component) { this.addComponent(id, component); }
unset(id) { this.removeComponent(id); }
addComputedVarying(name, type, computation) {
this.addVarying(name, type);
this._computedVarying.push(computation);
}
buildSource(attrMap) {
const prelude = this.buildPrelude(attrMap);
const main = new SourceBuilder();
main.newline();
const computePosition = this.get(10 /* VertexShaderComponent.ComputePosition */);
(0, core_bentley_1.assert)(undefined !== computePosition);
if (undefined !== computePosition) {
prelude.addFunction("vec4 computePosition(vec4 rawPos)", computePosition);
}
const computeQPos = this.get(0 /* VertexShaderComponent.ComputeQuantizedPosition */) ?? "return a_pos;";
prelude.addFunction("vec3 computeQuantizedPosition()", computeQPos);
main.addline(" vec3 qpos = computeQuantizedPosition();");
// Initialization logic that should occur at start of main() - primarily global variables whose values
// are too complex to compute inline or which depend on uniforms and/or other globals.
for (const init of this._initializers) {
if ("\n" === init.charAt(0))
main.addline(` {${init} }\n`);
else
main.addline(` { ${init} }\n`);
}
main.addline(" vec4 rawPosition = computeVertexPosition(qpos);");
const adjustRawPosition = this.get(1 /* VertexShaderComponent.AdjustRawPosition */);
if (undefined !== adjustRawPosition) {
prelude.addFunction("vec4 adjustRawPosition(vec4 rawPos)", adjustRawPosition);
main.addline(" rawPosition = adjustRawPosition(rawPosition);");
}
const checkForEarlyDiscard = this.get(2 /* VertexShaderComponent.CheckForEarlyDiscard */);
if (undefined !== checkForEarlyDiscard) {
prelude.addFunction("bool checkForEarlyDiscard(vec4 rawPos)", checkForEarlyDiscard);
main.add(Vertex_1.earlyVertexDiscard);
}
const computeFeatureOverrides = this.get(3 /* VertexShaderComponent.ComputeFeatureOverrides */);
if (undefined !== computeFeatureOverrides) {
prelude.addFunction("void computeFeatureOverrides()", computeFeatureOverrides);
main.addline(" computeFeatureOverrides();");
}
const computeMaterial = this.get(4 /* VertexShaderComponent.ComputeMaterial */);
if (undefined !== computeMaterial) {
prelude.addFunction("void computeMaterial()", computeMaterial);
main.addline(" computeMaterial();");
}
const computeBaseColor = this.get(5 /* VertexShaderComponent.ComputeBaseColor */);
if (undefined !== computeBaseColor) {
(0, core_bentley_1.assert)(undefined !== this.find("v_color"));
prelude.addFunction("vec4 computeBaseColor()", computeBaseColor);
main.addline(" vec4 baseColor = computeBaseColor();");
const applyMaterialColor = this.get(6 /* VertexShaderComponent.ApplyMaterialColor */);
if (undefined !== applyMaterialColor) {
prelude.addFunction("vec4 applyMaterialColor(vec4 baseColor)", applyMaterialColor);
main.addline(" baseColor = applyMaterialColor(baseColor);");
}
const applyFeatureColor = this.get(7 /* VertexShaderComponent.ApplyFeatureColor */);
if (undefined !== applyFeatureColor) {
prelude.addFunction("vec4 applyFeatureColor(vec4 baseColor)", applyFeatureColor);
main.addline(" baseColor = applyFeatureColor(baseColor);");
}
const adjustContrast = this.get(8 /* VertexShaderComponent.AdjustContrast */);
if (adjustContrast) {
prelude.addFunction("vec4 adjustContrast(vec4 baseColor)", adjustContrast);
main.addline(" baseColor = adjustContrast(baseColor);");
}
main.addline(" v_color = baseColor;");
}
const checkForDiscard = this.get(9 /* VertexShaderComponent.CheckForDiscard */);
if (undefined !== checkForDiscard) {
prelude.addFunction("bool checkForDiscard()", checkForDiscard);
main.add(Vertex_1.vertexDiscard);
}
main.addline(" gl_Position = computePosition(rawPosition);");
const finalizePos = this.get(13 /* VertexShaderComponent.FinalizePosition */);
if (undefined !== finalizePos) {
prelude.addFunction("vec4 finalizePosition(vec4 pos)", finalizePos);
main.addline(" gl_Position = finalizePosition(gl_Position);");
}
for (const comp of this._computedVarying) {
main.addline(` ${comp}`);
}
const computeAtmosphericScatteringVaryings = this.get(11 /* VertexShaderComponent.ComputeAtmosphericScatteringVaryings */);
if (undefined !== computeAtmosphericScatteringVaryings) {
prelude.addFunction("void computeAtmosphericScatteringVaryings()", computeAtmosphericScatteringVaryings);
main.addline(" computeAtmosphericScatteringVaryings();");
}
const checkForLateDiscard = this.get(12 /* VertexShaderComponent.CheckForLateDiscard */);
if (undefined !== checkForLateDiscard) {
prelude.addFunction("bool checkForLateDiscard()", checkForLateDiscard);
main.addline(Vertex_1.lateVertexDiscard);
}
prelude.addMain(main.source);
return prelude.source;
}
copyFrom(src) {
this.copyCommon(src);
this._computedVarying = [...src._computedVarying];
}
}
exports.VertexShaderBuilder = VertexShaderBuilder;
/** Assembles the source code for a fragment shader from a set of modular components.
* @internal
*/
class FragmentShaderBuilder extends ShaderBuilder {
requiresEarlyZWorkaround = false;
constructor(flags = {}) {
super(26 /* FragmentShaderComponent.COUNT */, flags);
this.addFragOutput("FragColor", -1);
}
get(id) { return this.getComponent(id); }
set(id, component) { this.addComponent(id, component); }
unset(id) { this.removeComponent(id); }
addDrawBuffersExtension(n) {
this.clearFragOutput();
for (let i = 0; i < n; i++)
this.addFragOutput(`FragColor${i}`, i);
}
buildSource() {
const applyLighting = this.get(8 /* FragmentShaderComponent.ApplyLighting */);
const prelude = this.buildPrelude(undefined);
const computeBaseColor = this.get(1 /* FragmentShaderComponent.ComputeBaseColor */);
(0, core_bentley_1.assert)(undefined !== computeBaseColor);
if (undefined !== computeBaseColor) {
prelude.addFunction("vec4 computeBaseColor()", computeBaseColor);
}
const main = new SourceBuilder();
main.newline();
// Initialization logic that should occur at start of main() - primarily global variables whose values
// are too complex to compute inline or which depend on uniforms and/or other globals.
for (const init of this._initializers) {
if ("\n" === init.charAt(0))
main.addline(` {${init} }\n`);
else
main.addline(` { ${init} }\n`);
}
const checkForEarlyDiscard = this.get(0 /* FragmentShaderComponent.CheckForEarlyDiscard */);
if (undefined !== checkForEarlyDiscard) {
prelude.addFunction("bool checkForEarlyDiscard()", checkForEarlyDiscard);
main.addline(" if (checkForEarlyDiscard()) { discard; return; }");
}
const finalizeNormal = this.get(25 /* FragmentShaderComponent.FinalizeNormal */);
if (undefined !== finalizeNormal) {
prelude.addFunction("vec3 finalizeNormal()", finalizeNormal);
main.addline(" g_normal = finalizeNormal();");
}
main.addline(" vec4 baseColor = computeBaseColor();");
const finalizeDepth = this.get(20 /* FragmentShaderComponent.FinalizeDepth */);
if (undefined !== finalizeDepth) {
prelude.addFunction("float finalizeDepth()", finalizeDepth);
main.addline(" float finalDepth = finalizeDepth();");
main.addline(" gl_FragDepth = finalDepth;");
}
let clipIndent = "";
const applyClipping = this.get(10 /* FragmentShaderComponent.ApplyClipping */);
if (undefined !== applyClipping) {
prelude.addline("vec3 g_clipColor;\n");
prelude.addFunction("bvec2 applyClipping(vec4 baseColor)", applyClipping);
main.addline(" g_hasClipColor = applyClipping(baseColor);");
main.addline(" if (g_hasClipColor.x) { baseColor.rgb = g_clipColor; } else {");
clipIndent = " ";
}
const applyMaterialOverrides = this.get(2 /* FragmentShaderComponent.ApplyMaterialOverrides */);
if (undefined !== applyMaterialOverrides) {
prelude.addFunction("vec4 applyMaterialOverrides(vec4 baseColor)", applyMaterialOverrides);
main.addline(`${clipIndent} baseColor = applyMaterialOverrides(baseColor);`);
}
const applyThematicDisplay = this.get(7 /* FragmentShaderComponent.ApplyThematicDisplay */);
if (undefined !== applyThematicDisplay) {
prelude.addFunction("vec4 applyThematicDisplay(vec4 baseColor)", applyThematicDisplay);
main.addline(`${clipIndent} if (u_renderPass != kRenderPass_PlanarClassification)`);
main.addline(`${clipIndent} baseColor = applyThematicDisplay(baseColor);`);
}
const applyPlanarClassifier = this.get(13 /* FragmentShaderComponent.ApplyPlanarClassifier */);
if (undefined !== applyPlanarClassifier) {
if (undefined === finalizeDepth) {
if (this.findFunction(PlanarClassification_1.volClassOpaqueColor))
main.addline(`${clipIndent} float finalDepth = gl_FragCoord.z;`);
else
main.addline(`${clipIndent} float finalDepth = 1.0;`);
}
prelude.addFunction("vec4 applyPlanarClassifications(vec4 baseColor, float depth)", applyPlanarClassifier);
main.addline(`${clipIndent} baseColor = applyPlanarClassifications(baseColor, finalDepth);`);
}
const applySolarShadowMap = this.get(15 /* FragmentShaderComponent.ApplySolarShadowMap */);
if (undefined !== applySolarShadowMap) {
prelude.addFunction("vec4 applySolarShadowMap(vec4 baseColor)", applySolarShadowMap);
main.addline(`${clipIndent} baseColor = applySolarShadowMap(baseColor);`);
}
const finalize = this.get(3 /* FragmentShaderComponent.FinalizeBaseColor */);
if (undefined !== finalize) {
prelude.addFunction("vec4 finalizeBaseColor(vec4 baseColor)", finalize);
main.addline(`${clipIndent} baseColor = finalizeBaseColor(baseColor);`);
}
const checkForDiscard = this.get(4 /* FragmentShaderComponent.CheckForDiscard */);
if (undefined !== checkForDiscard) {
prelude.addFunction("bool checkForDiscard(vec4 baseColor)", checkForDiscard);
main.addline(`${clipIndent} if (checkForDiscard(baseColor)) { discard; return; }`);
}
const discardByAlpha = this.get(5 /* FragmentShaderComponent.DiscardByAlpha */);
if (undefined !== discardByAlpha) {
prelude.addFunction("bool discardByAlpha(float alpha)", discardByAlpha);
main.addline(`${clipIndent} if (discardByAlpha(baseColor.a)) { discard; return; }`);
}
if (undefined !== applyClipping)
main.addline(" }");
const applyMonochrome = this.get(6 /* FragmentShaderComponent.ApplyMonochrome */);
if (undefined !== applyMonochrome) {
prelude.addFunction("vec4 applyMonochrome(vec4 baseColor)", applyMonochrome);
main.addline(" baseColor = applyMonochrome(baseColor);");
}
if (undefined !== applyLighting) {
prelude.addFunction("vec4 applyLighting(vec4 baseColor)", applyLighting);
main.addline(" baseColor = applyLighting(baseColor);");
}
if (undefined !== applyClipping) {
main.addline(" if (g_hasClipColor.y) { baseColor.rgba = vec4(g_clipColor, 1.0); } ");
}
const reverseWoW = this.get(9 /* FragmentShaderComponent.ReverseWhiteOnWhite */);
if (undefined !== reverseWoW) {
prelude.addFunction("vec4 reverseWhiteOnWhite(vec4 baseColor)", reverseWoW);
main.addline(" baseColor = reverseWhiteOnWhite(baseColor);");
}
const applyContours = this.get(11 /* FragmentShaderComponent.ApplyContours */);
if (undefined !== applyContours) {
prelude.addFunction("vec4 applyContours(vec4 baseColor)", applyContours);
main.addline(" baseColor = applyContours(baseColor);");
}
const applyFlash = this.get(12 /* FragmentShaderComponent.ApplyFlash */);
if (undefined !== applyFlash) {
prelude.addFunction("vec4 applyFlash(vec4 baseColor)", applyFlash);
main.addline(" baseColor = applyFlash(baseColor);");
}
const applyWiremesh = this.get(16 /* FragmentShaderComponent.ApplyWiremesh */);
if (applyWiremesh) {
prelude.addFunction("vec4 applyWiremesh(vec4 baseColor)", applyWiremesh);
main.addline(" baseColor = applyWiremesh(baseColor);");
}
const applyAtmosphericScattering = this.get(24 /* FragmentShaderComponent.ApplyAtmosphericScattering */);
if (applyAtmosphericScattering) {
prelude.addFunction("vec4 applyAtmosphericScattering(vec4 baseColor)", applyAtmosphericScattering);
main.addline(" baseColor = applyAtmosphericScattering(baseColor);");
}
const applyDraping = this.get(14 /* FragmentShaderComponent.ApplyDraping */);
if (undefined !== applyDraping) {
prelude.addFunction("vec4 applyDraping(vec4 baseColor)", applyDraping);
main.addline(`${clipIndent} baseColor = applyDraping(baseColor);`);
}
const applyDebug = this.get(17 /* FragmentShaderComponent.ApplyDebugColor */);
if (undefined !== applyDebug) {
prelude.addFunction("vec4 applyDebugColor(vec4 baseColor)", applyDebug);
main.addline(" baseColor = applyDebugColor(baseColor);");
}
const assignFragData = this.get(18 /* FragmentShaderComponent.AssignFragData */);
(0, core_bentley_1.assert)(undefined !== assignFragData);
if (undefined !== assignFragData) {
prelude.addFunction("void assignFragData(vec4 baseColor)", assignFragData);
main.addline(" assignFragData(baseColor);");
}
if (this.requiresEarlyZWorkaround) {
// Add a conditional discard that never executes to force buggy Intel driver to skip early Z despite our fragment shader writing depth.
main.addline("if (v_eyeSpace.z == 9999999.0) discard;");
}
prelude.addMain(main.source);
return prelude.source;
}
buildPrelude(attrMap) {
return this.buildPreludeCommon(attrMap, false);
}
copyFrom(src) {
this.copyCommon(src);
this.requiresEarlyZWorkaround = src.requiresEarlyZWorkaround;
}
}
exports.FragmentShaderBuilder = FragmentShaderBuilder;
/**
* Assembles vertex and fragment shaders from a set of modular components to produce a compiled ShaderProgram.
* Be very careful with components which use samplers to ensure that no conflicts exist with texture units used by other components (see TextureUnit enum).
* @internal
*/
class ProgramBuilder {
vert;
frag;
_flags;
_attrMap;
constructor(attrMap, flags = {}) {
this._attrMap = attrMap;
this.vert = new VertexShaderBuilder(flags);
this.frag = new FragmentShaderBuilder(flags);
this._flags = flags; // only needed for clone - though could look up from vert or frag shader.
}
addVariable(v, which) {
if (which & 1 /* ShaderType.Fragment */) {
this.frag.addVariable(v);
}
if (which & 2 /* ShaderType.Vertex */) {
this.vert.addVariable(v);
}
}
addUniform(name, type, binding, which = 3 /* ShaderType.Both */) {
this.addVariable(ShaderVariable.create(name, type, 2 /* VariableScope.Uniform */, binding), which);
}
addUniformArray(name, type, length, binding, which = 3 /* ShaderType.Both */) {
this.addVariable(ShaderVariable.createArray(name, type, length, 2 /* VariableScope.Uniform */, binding), which);
}
addVarying(name, type) {
this.addVariable(ShaderVariable.create(name, type, 1 /* VariableScope.Varying */), 3 /* ShaderType.Both */);
}
addGlobal(name, type, which = 3 /* ShaderType.Both */, value, isConst = false) {
this.addVariable(ShaderVariable.createGlobal(name, type, value, isConst), which);
}
addInlineComputedVarying(name, type, inlineComputation) {
if (this.frag.addVarying(name, type))
this.vert.addComputedVarying(name, type, inlineComputation);
}
addFunctionComputedVarying(name, type, funcName, funcBody) {
let funcDecl = `\n${Convert.typeToString(type)} ${funcName}()`;
funcDecl = SourceBuilder.buildFunctionDefinition(funcDecl, funcBody);
const funcCall = `${funcName}()`;
this.addFunctionComputedVaryingWithArgs(name, type, funcCall, funcDecl);
}
addFunctionComputedVaryingWithArgs(name, type, funcCall, funcDef) {
this.vert.addFunction(funcDef);
const computation = `${name} = ${funcCall};`;
this.addInlineComputedVarying(name, type, computation);
}
/** Assembles the vertex and fragment shader code and returns a ready-to-compile shader program */
buildProgram(gl) {
const vertSource = this.vert.buildSource(this._attrMap);
const fragSource = this.frag.buildSource(); // NB: frag has no need to specify attributes, only vertex does.
// Debug output
const debugVaryings = false;
if (debugVaryings) {
const dbgLog = (x) => console.log(x); // eslint-disable-line no-console
const outSrc = false; // true for source out, false for just varying info
if (this.frag.headerComment) {
let tStr = "";
if (!outSrc) {
tStr = `${this.frag.headerComment}\n`;
}
const tStr2 = this.vert.checkMaxVaryingVectors(fragSource);
if (tStr2) {
if (outSrc) {
dbgLog("//===============================================================================================================");
dbgLog(vertSource);
dbgLog("//========= Varying Info =========");
dbgLog(tStr2);
dbgLog(fragSource);
}
else {
dbgLog(tStr + tStr2);
}
}
}
}
const prog = new ShaderProgram_1.ShaderProgram(gl, vertSource, fragSource, this._attrMap, this.vert.headerComment, this.frag.headerComment);
this.vert.addBindings(prog);
this.frag.addBindings(prog, this.vert);
return prog;
}
setDebugDescription(description) {
this.vert.headerComment = (`//!V! ${description}`);
this.frag.headerComment = (`//!F! ${description}`);
}
/** Returns a deep copy of this program builder. */
clone() {
const clone = new ProgramBuilder(this._attrMap, this._flags);
clone.vert.copyFrom(this.vert);
clone.frag.copyFrom(this.frag);
return clone;
}
}
exports.ProgramBuilder = ProgramBuilder;
//# sourceMappingURL=ShaderBuilder.js.map