/*---------------------------------------------------------------------------------------------
* 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
 */
import { assert } from "@itwin/core-bentley";
import { addInstancedModelMatrixRTC } from "./glsl/Instancing";
import { volClassOpaqueColor } from "./glsl/PlanarClassification";
import { addPosition, earlyVertexDiscard, lateVertexDiscard, vertexDiscard } from "./glsl/Vertex";
import { ShaderProgram } from "./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:
                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:
                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:
                assert(false);
                return "undefined";
        }
    }
    Convert.precisionToString = precisionToString;
})(Convert || (Convert = {}));
/** Represents a variable within a fragment or vertex shader.
 * @internal
 */
export class ShaderVariable {
    constructor(name, type, scope, precision, isConst, addBinding, value, length = 0) {
        this.isConst = false; // for global variables only
        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(" ")};`;
    }
}
/**
 * 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
 */
export class ShaderVariables {
    constructor() {
        this._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) {
            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:
                    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:
                    assert(false, "Invalid varying variable type");
                    continue;
            }
            this.findSlot(variableSize, loopSize, registers);
        }
        const slotsUsed = registers.indexOf(0);
        return slotsUsed;
    }
}
/** Convenience API for assembling glsl source code.
 * @internal
 */
export class SourceBuilder {
    constructor() {
        this.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); }
}
/**
 * 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
 */
export class ShaderBuilder extends ShaderVariables {
    get usesInstancedGeometry() {
        return !!this._flags.instanced;
    }
    addInitializer(initializer) {
        if (-1 === this._initializers.indexOf(initializer))
            this._initializers.push(initializer);
    }
    constructor(maxComponents, flags) {
        super();
        this._components = new Array();
        this._functions = [];
        this._extensions = [];
        this._macros = [];
        this._fragOutputs = [];
        this._version = "";
        this.headerComment = "";
        this._initializers = new Array();
        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) {
        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) {
        assert(index < this._components.length);
        this._components[index] = undefined;
    }
    getComponent(index) {
        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;
        }
        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];
    }
}
/** Assembles the source code for a vertex shader from a set of modular components.
 * @internal
 */
export class VertexShaderBuilder extends ShaderBuilder {
    buildPrelude(attrMap) { return this.buildPreludeCommon(attrMap, true); }
    constructor(flags = {}) {
        super(14 /* VertexShaderComponent.COUNT */, flags);
        this._computedVarying = new Array();
        this.addDefine("MAT_NORM", "g_nmx");
        if (this.usesInstancedGeometry) {
            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");
        }
        addPosition(this, this.usesVertexTable);
    }
    get usesVertexTable() {
        return undefined !== this._flags.positionType;
    }
    get positionType() {
        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 */);
        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(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) {
            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(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(lateVertexDiscard);
        }
        prelude.addMain(main.source);
        return prelude.source;
    }
    copyFrom(src) {
        this.copyCommon(src);
        this._computedVarying = [...src._computedVarying];
    }
}
/** Assembles the source code for a fragment shader from a set of modular components.
 * @internal
 */
export class FragmentShaderBuilder extends ShaderBuilder {
    constructor(flags = {}) {
        super(24 /* FragmentShaderComponent.COUNT */, flags);
        this.requiresEarlyZWorkaround = false;
        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 */);
        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(23 /* FragmentShaderComponent.FinalizeNormal */);
        if (undefined !== finalizeNormal) {
            prelude.addFunction("vec3 finalizeNormal()", finalizeNormal);
            main.addline("  g_normal = finalizeNormal();");
        }
        main.addline("  vec4 baseColor = computeBaseColor();");
        const finalizeDepth = this.get(19 /* 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(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(14 /* 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(15 /* FragmentShaderComponent.ApplyWiremesh */);
        if (applyWiremesh) {
            prelude.addFunction("vec4 applyWiremesh(vec4 baseColor)", applyWiremesh);
            main.addline("  baseColor = applyWiremesh(baseColor);");
        }
        const applyAtmosphericScattering = this.get(22 /* FragmentShaderComponent.ApplyAtmosphericScattering */);
        if (applyAtmosphericScattering) {
            prelude.addFunction("vec4 applyAtmosphericScattering(vec4 baseColor)", applyAtmosphericScattering);
            main.addline("  baseColor = applyAtmosphericScattering(baseColor);");
        }
        const applyDebug = this.get(16 /* FragmentShaderComponent.ApplyDebugColor */);
        if (undefined !== applyDebug) {
            prelude.addFunction("vec4 applyDebugColor(vec4 baseColor)", applyDebug);
            main.addline("  baseColor = applyDebugColor(baseColor);");
        }
        const assignFragData = this.get(17 /* FragmentShaderComponent.AssignFragData */);
        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;
    }
}
/**
 * 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
 */
export class ProgramBuilder {
    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(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;
    }
}
//# sourceMappingURL=ShaderBuilder.js.map