Home Reference Source

cables_dev/cables/src/libs/cgl/shadermodifier/cgl_shadermodifier.js

class ShaderModifier
{
    constructor(cgl, name, options)
    {
        this._cgl = cgl;
        this._name = name;
        this._origShaders = {};
        this._uniforms = [];
        this._structUniforms = [];
        this._definesToggled = {};
        this._defines = {};
        this._mods = [];
        this._textures = [];
        this._boundShader = null;
        this._changedDefines = true;
        this._changedUniforms = true;
        this._modulesChanged = false;
        this.needsTexturePush = false;
        this._lastShader = null;
        this._attributes = [];
        if (options && options.opId) this.opId = options.opId;

        if (this._cgl.glVersion == 1)
        {
            this._cgl.enableExtension("OES_texture_float");
            this._cgl.enableExtension("OES_texture_float_linear");
            this._cgl.enableExtension("OES_texture_half_float");
            this._cgl.enableExtension("OES_texture_half_float_linear");
        }
    }

    bind(curShader, pushShader)
    {
        const shader = curShader || this._cgl.getShader();
        if (!shader) return;

        this._boundShader = this._origShaders[shader.id];
        let missingMod = false;

        if (this._boundShader && this._lastShader != this._boundShader.shader) // shader changed since last bind
        {
            if (!this._boundShader.shader.hasModule(this._mods[0].id)) missingMod = true;
        }

        if (missingMod || !this._boundShader || shader.lastCompile != this._boundShader.lastCompile || this._modulesChanged || shader._needsRecompile)
        {
            if (this._boundShader) this._boundShader.shader.dispose();
            if (shader._needsRecompile) shader.compile();
            this.needsTexturePush = true;

            this._boundShader = this._origShaders[shader.id] =
            {
                "lastCompile": shader.lastCompile,
                "orig": shader,
                "shader": shader.copy()
            };

            this._addModulesToShader(this._boundShader.shader);
            this._updateDefinesShader(this._boundShader.shader);
            this._updateUniformsShader(this._boundShader.shader);
        }

        this._boundShader.wireframe = shader.wireframe;
        if (this._changedDefines) this._updateDefines();
        if (this._changedUniforms) this._updateUniforms();

        if (pushShader !== false) this._cgl.pushShader(this._boundShader.shader);

        this._boundShader.shader.copyUniformValues(this._boundShader.orig);

        if (this.needsTexturePush)
        {
            for (let j = 0; j < this._textures.length; j++)
            {
                const uniformName = this._textures[j][0];
                const tex = this._textures[j][1];
                const texType = this._textures[j][2];

                if (this._getUniform(uniformName))
                {
                    const name = this.getPrefixedName(uniformName);
                    const uni = this._boundShader.shader.getUniform(name);

                    if (uni) this._boundShader.shader.pushTexture(uni, tex, texType);
                }
            }

            this.needsTexturePush = false;
            this._textures.length = 0;
        }

        this._modulesChanged = false;

        this._boundShader.shader.fromMod = this;

        if (this.onBind) this.onBind(this._boundShader.shader);

        return this._boundShader.shader;
    }

    unbind(popShader)
    {
        if (this._boundShader)
        {
            if (popShader !== false) this._cgl.popShader();
            // this._boundShader = null;
            // return true;
        }
        this._boundShader = null;
    }

    _addModulesToShader(shader)
    {
        let firstMod;

        if (this._mods.length > 1) firstMod = this._mods[0];

        for (let i = 0; i < this._mods.length; i++) shader.addModule(this._mods[i], firstMod);
    }

    _removeModulesFromShader(mod)
    {
        for (const j in this._origShaders) this._origShaders[j].shader.removeModule(mod);
    }

    addModule(mod)
    {
        this._mods.push(mod);
        this._modulesChanged = true;
    }

    removeModule(title)
    {
        const indicesToRemove = [];

        let found = false;
        for (let i = 0; i < this._mods.length; i++)
        {
            if (this._mods[i].title == title)
            {
                found = true;
                this._removeModulesFromShader(this._mods[i]);
                indicesToRemove.push(i);
            }
        }

        // * go in reverse order so the indices of the mods stay the same
        for (let j = indicesToRemove.length - 1; j >= 0; j -= 1)
            this._mods.splice(indicesToRemove[j], 1);

        this._modulesChanged = true;
    }

    _updateUniformsShader(shader)
    {
        for (let i = 0; i < this._uniforms.length; i++)
        {
            const uni = this._uniforms[i];
            const name = this.getPrefixedName(uni.name);

            if (!shader.hasUniform(name) && !uni.structName)
            {
                let un = null;
                if (uni.shaderType === "both")
                {
                    un = shader.addUniformBoth(uni.type, name, uni.v1, uni.v2, uni.v3, uni.v4);
                    un.comment = "mod: " + this._name;
                }
                else if (uni.shaderType === "frag")
                {
                    un = shader.addUniformFrag(uni.type, name, uni.v1, uni.v2, uni.v3, uni.v4);
                    un.comment = "mod: " + this._name;
                }
                else if (uni.shaderType === "vert")
                {
                    un = shader.addUniformVert(uni.type, name, uni.v1, uni.v2, uni.v3, uni.v4);
                    un.comment = "mod: " + this._name;
                }
            }
        }

        for (let j = 0; j < this._structUniforms.length; j += 1)
        {
            const structUniform = this._structUniforms[j];
            let structUniformName = structUniform.uniformName;
            let structName = structUniform.structName;

            const members = structUniform.members;

            structUniformName = this.getPrefixedName(structUniform.uniformName);
            structName = this.getPrefixedName(structUniform.structName);

            if (structUniform.shaderType === "frag")
            {
                shader.addUniformStructFrag(structName, structUniformName, members);
            }
            if (structUniform.shaderType === "vert")
            {
                shader.addUniformStructVert(structName, structUniformName, members);
            }
            if (structUniform.shaderType === "both")
            {
                shader.addUniformStructBoth(structName, structUniformName, members);
            }
        }
    }

    _updateUniforms()
    {
        for (const j in this._origShaders)
            this._updateUniformsShader(this._origShaders[j].shader);

        this._changedUniforms = false;
    }

    _setUniformValue(shader, uniformName, value)
    {
        const uniform = shader.getUniform(uniformName);

        if (uniform) uniform.setValue(value);
    }

    setUniformValue(name, value)
    {
        const uni = this._getUniform(name);
        if (!uni) return;

        const defineName = this.getPrefixedName(name);

        for (const j in this._origShaders)
        {
            this._setUniformValue(this._origShaders[j].shader, defineName, value);
        }
    }

    hasUniform(name)
    {
        return this._getUniform(name);
    }

    _getUniform(name)
    {
        for (let i = 0; i < this._uniforms.length; i++)
        {
            if (this._uniforms[i].name == name) return this._uniforms[i];
            if (this._uniforms[i].structName)
            {
                if (this._uniforms[i].propertyName == name) return this._uniforms[i];
            }
        }
        return false;
    }

    _getStructUniform(uniName)
    {
        for (let i = 0; i < this._structUniforms.length; i += 1)
            if (this._structUniforms[i].uniformName === uniName) return this._structUniforms[i];

        return null;
    }

    _isStructUniform(name)
    {
        for (let i = 0; i < this._uniforms.length; i++)
        {
            if (this._uniforms[i].name == name) return false;
            if (this._uniforms[i].structName)
            {
                if (this._uniforms[i].propertyName == name) return true;
            }
        }
        return false;
    }


    addUniform(type, name, valOrPort, v2, v3, v4, structUniformName, structName, propertyName, shaderType)
    {
        if (!this._getUniform(name))
        {
            let _shaderType = "both";
            if (shaderType) _shaderType = shaderType;

            this._uniforms.push(
                {
                    "type": type,
                    "name": name,
                    "v1": valOrPort,
                    "v2": v2,
                    "v3": v3,
                    "v4": v4,
                    "structUniformName": structUniformName,
                    "structName": structName,
                    "propertyName": propertyName,
                    "shaderType": _shaderType,
                });
            this._changedUniforms = true;
        }
    }

    addUniformFrag(type, name, valOrPort, v2, v3, v4)
    {
        this.addUniform(type, name, valOrPort, v2, v3, v4, null, null, null, "frag");
        this._changedUniforms = true;
    }

    addUniformVert(type, name, valOrPort, v2, v3, v4)
    {
        this.addUniform(type, name, valOrPort, v2, v3, v4, null, null, null, "vert");
        this._changedUniforms = true;
    }

    addUniformBoth(type, name, valOrPort, v2, v3, v4)
    {
        this.addUniform(type, name, valOrPort, v2, v3, v4, null, null, null, "both");
        this._changedUniforms = true;
    }

    addUniformStruct(structName, uniformName, members, shaderType)
    {
        for (let i = 0; i < members.length; i += 1)
        {
            const member = members[i];
            if ((member.type === "2i" || member.type === "i" || member.type === "3i") && shaderType === "both")
                console.error("Adding an integer struct member to both shaders can potentially error. Please use different structs for each shader. Error occured in struct:", structName, " with member:", member.name, " of type:", member.type, ".");

            if (!this._getUniform(uniformName + "." + member.name))
            {
                this.addUniform(
                    member.type,
                    uniformName + "." + member.name,
                    member.v1,
                    member.v2,
                    member.v3,
                    member.v4,
                    uniformName,
                    structName,
                    member.name,
                    shaderType
                );
            }
        }
        if (!this._getStructUniform(uniformName))
        {
            this._structUniforms.push({
                "structName": structName,
                "uniformName": uniformName,
                "members": members,
                "shaderType": shaderType,
            });
        }
    }

    addUniformStructVert(structName, uniformName, members)
    {
        this.addUniformStruct(structName, uniformName, members, "vert");
    }

    addUniformStructFrag(structName, uniformName, members)
    {
        this.addUniformStruct(structName, uniformName, members, "frag");
    }

    addUniformStructBoth(structName, uniformName, members)
    {
        this.addUniformStruct(structName, uniformName, members, "both");
    }

    addAttribute(attr)
    {
        for (let i = 0; i < this._attributes.length; i++)
        {
            if (this._attributes[i].name == attr.name && this._attributes[i].nameFrag == attr.nameFrag) return;
        }
        this._attributes.push(attr);
    }

    pushTexture(uniformName, tex, texType)
    {
        if (!tex) throw (new Error("no texture given to texturestack"));

        this._textures.push([uniformName, tex, texType]);
        this.needsTexturePush = true;
    }

    _removeUniformFromShader(name, shader)
    {
        if (shader.hasUniform(name)) shader.removeUniform(name);
    }

    removeUniform(name)
    {
        if (this._getUniform(name))
        {
            for (let j = this._uniforms.length - 1; j >= 0; j -= 1)
            {
                const nameToRemove = name;

                if (this._uniforms[j].name == name && !this._uniforms[j].structName)
                {
                    for (const k in this._origShaders)
                    {
                        this._removeUniformFromShader(
                            this.getPrefixedName(nameToRemove),
                            this._origShaders[k].shader
                        );
                    }

                    this._uniforms.splice(j, 1);
                }
            }
            this._changedUniforms = true;
        }
    }

    removeUniformStruct(uniformName)
    {
        if (this._getStructUniform(uniformName))
        {
            for (let i = this._structUniforms.length - 1; i >= 0; i -= 1)
            {
                const structToRemove = this._structUniforms[i];

                if (structToRemove.uniformName === uniformName)
                {
                    for (const j in this._origShaders)
                    {
                        for (let k = 0; k < structToRemove.members.length; k += 1)
                        {
                            const member = structToRemove.members[k];
                            this._removeUniformFromShader(
                                this.getPrefixedName(member.name),
                                this._origShaders[j].shader
                            );
                        }
                    }

                    this._structUniforms.splice(i, 1);
                }
            }

            this._changedUniforms = true;
        }
    }


    getPrefixedName(name)
    {
        const prefix = this._mods[0].group;
        if (prefix === undefined)
        {
            return;
        }
        if (name.startsWith("MOD_"))
        {
            name = name.substr("MOD_".length);
            name = "mod" + prefix + "_" + name;
        }
        return name;
    }

    _updateDefinesShader(shader)
    {
        for (const i in this._defines)
        {
            const name = this.getPrefixedName(i);
            if (this._defines[i] !== null && this._defines[i] !== undefined) shader.define(name, this._defines[i]);
            else shader.removeDefine(name);
        }

        for (const i in this._definesToggled)
        {
            const name = this.getPrefixedName(i);
            shader.toggleDefine(name, this._definesToggled[i]);
        }
    }


    _updateDefines()
    {
        for (const j in this._origShaders) this._updateDefinesShader(this._origShaders[j].shader);

        this._changedDefines = false;
    }

    define(what, value)
    {
        if (value === undefined)value = true;
        this._defines[what] = value;
        this._changedDefines = true;
    }

    removeDefine(name)
    {
        this._defines[name] = null;
        this._changedDefines = true;
    }

    hasDefine(name)
    {
        if (this._defines[name] !== null && this._defines[name] !== undefined) return true;
        return false;
    }

    toggleDefine(name, b)
    {
        this._changedDefines = true;
        this._definesToggled[name] = b;
    }

    currentShader()
    {
        if (!this._boundShader) return null;
        return this._boundShader.shader;
    }

    dispose()
    {

    }
}


export { ShaderModifier };