Source: og/webgl/Program.js

/**
 * @module og/webgl/Program
 */

"use strict";

import { callbacks } from "./callbacks.js";
import { cons } from "../cons.js";
import { typeStr, types } from "./types.js";

const itemTypes = ["BYTE", "SHORT", "UNSIGNED_BYTE", "UNSIGNED_SHORT", "FLOAT", "HALF_FLOAT"];

/**
 * Represents more comfortable using WebGL shader program.
 * @class
 * @param {string} name - Shader program name identificator.
 * @param {object} material - Object stores uniforms, attributes and program codes:
 * @param {object} material.uniforms - Uniforms definition section.
 * @param {object} material.attributes - Attributes definition section.
 * @param {string} material.vertexShader - Vertex glsl code.
 * @param {string} material.fragmentShader - Fragment glsl code.
 */
class Program {
    constructor(name, material) {
        /**
         * Shader progarm name.
         * @public
         * @type {string}
         */
        this.name = name;

        this.attributes = {};
        this.uniforms = {};

        /**
         * Attributes.
         * @public
         * @type {Object}
         */
        this._attributes = {};
        for (let t in material.attributes) {
            if (
                typeof material.attributes[t] === "string" ||
                typeof material.attributes[t] === "number"
            ) {
                this._attributes[t] = { type: material.attributes[t] };
            } else {
                this._attributes[t] = material.attributes[t];
            }
        }

        /**
         * Uniforms.
         * @public
         * @type {Object}
         */
        this._uniforms = {};
        for (let t in material.uniforms) {
            if (
                typeof material.uniforms[t] === "string" ||
                typeof material.uniforms[t] === "number"
            ) {
                this._uniforms[t] = { type: material.uniforms[t] };
            } else {
                this._uniforms[t] = material.uniforms[t];
            }
        }

        /**
         * Vertex shader.
         * @public
         * @type {string}
         */
        this.vertexShader = material.vertexShader;

        /**
         * Fragment shader.
         * @public
         * @type {string}
         */
        this.fragmentShader = material.fragmentShader;

        /**
         * Webgl context.
         * @public
         * @type {Object}
         */
        this.gl = null;

        /**
         * All program variables.
         * @private
         * @type {Object}
         */
        this._variables = {};

        /**
         * Program pointer.
         * @private
         * @type {Object}
         */
        this._p = null;

        /**
         * Texture counter.
         * @prvate
         * @type {number}
         */
        this._textureID = 0;

        /**
         * Program attributes array.
         * @private
         * @type {Array.<Object>}
         */
        this._attribArrays = [];

        /**
         * Program attributes divisor.
         * @private
         * @type {Array.<Object>}
         */
        this._attribDivisor = [];
    }

    /**
     * Bind program buffer.
     * @function
     * @param {Program} program - Used program.
     * @param {Object} variable - Variable represents buffer data.
     */
    static bindBuffer(program, variable) {
        var gl = program.gl;
        gl.bindBuffer(gl.ARRAY_BUFFER, variable.value);
        gl.vertexAttribPointer(
            variable._pName,
            variable.value.itemSize,
            variable.itemType,
            variable.normalized,
            0,
            0
        );
    }

    /**
     * Sets the current program frame.
     * @public
     */
    use() {
        this.gl.useProgram(this._p);
    }

    /**
     * Sets program variables.
     * @public
     * @param {Object} material - Variables and values object.
     */
    set(material) {
        this._textureID = 0;
        for (var i in material) {
            this._variables[i].value = material[i];
            this._variables[i]._callback(this, this._variables[i]);
        }
    }

    /**
     * Apply current variables.
     * @public
     */
    apply() {
        this._textureID = 0;
        var v = this._variables;
        for (var i in v) {
            v[i]._callback(this, v[i]);
        }
    }

    /**
     * Calls drawElements index buffer function.
     * @public
     * @param {number} mode - Draw mode(GL_TRIANGLES, GL_LINESTRING etc.).
     * @param {Object} buffer - Index buffer.
     */
    drawIndexBuffer(mode, buffer) {
        this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, buffer);
        this.gl.drawElements(mode, buffer.numItems, this.gl.UNSIGNED_SHORT, 0);
    }

    /**
     * Calls drawArrays function.
     * @public
     * @param {number} mode - Draw mode(GL_TRIANGLES, GL_LINESTRING etc.).
     * @param {number} numItems - Curent binded buffer drawing items count.
     */
    drawArrays(mode, numItems) {
        this.gl.drawArrays(mode, 0, numItems);
    }

    /**
     * Check and log for an shader compile errors and warnings. Returns True - if no errors otherwise returns False.
     * @private
     * @param {Object} shader - WebGl shader program.
     * @param {string} src - Shader program source.
     * @returns {boolean} -
     */
    _getShaderCompileStatus(shader, src) {
        this.gl.shaderSource(shader, src);
        this.gl.compileShader(shader);
        if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
            cons.logErr(
                "og/Program/Program:" + this.name + " - " + this.gl.getShaderInfoLog(shader) + "."
            );
            return false;
        }
        return true;
    }

    /**
     * Returns compiled vertex shader program pointer.
     * @private
     * @param {string} src - Vertex shader source code.
     * @returns {Object} -
     */
    _createVertexShader(src) {
        var shader = this.gl.createShader(this.gl.VERTEX_SHADER);
        if (!this._getShaderCompileStatus(shader, src)) {
            return null;
        }
        return shader;
    }

    /**
     * Returns compiled fragment shader program pointer.
     * @private
     * @param {string} src - Vertex shader source code.
     * @returns {Object} -
     */
    _createFragmentShader(src) {
        var shader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
        if (!this._getShaderCompileStatus(shader, src)) {
            return null;
        }
        return shader;
    }

    /**
     * Disable current program vertexAttribArrays.
     * @public
     */
    disableAttribArrays() {
        var gl = this.gl;
        var a = this._attribArrays;
        for (let i = 0, len = a.length; i < len; i++) {
            gl.disableVertexAttribArray(a[i]);
            gl.vertexAttribDivisor(a[i], 0);
        }
    }

    /**
     * Enable current program vertexAttribArrays.
     * @public
     */
    enableAttribArrays() {
        var gl = this.gl;
        var a = this._attribArrays;
        var d = this._attribDivisor;
        for (let i = 0, len = a.length; i < len; i++) {
            gl.enableVertexAttribArray(a[i]);
            gl.vertexAttribDivisor(a[i], d[i]);
        }
    }

    /**
     * Delete program.
     * @public
     */
    delete() {
        this.gl.deleteProgram(this._p);
    }

    /**
     * Creates program.
     * @public
     * @param {Object} gl - WebGl context.
     */
    createProgram(gl) {
        this.gl = gl;
        this._p = this.gl.createProgram();

        var fs = this._createFragmentShader(this.fragmentShader);
        var vs = this._createVertexShader(this.vertexShader);
        gl.attachShader(this._p, fs);
        gl.attachShader(this._p, vs);
        gl.linkProgram(this._p);

        if (!gl.getProgramParameter(this._p, gl.LINK_STATUS)) {
            cons.logErr(
                "og/Program/Program:" +
                    this.name +
                    " - couldn't initialise shaders. " +
                    gl.getProgramInfoLog(this._p) +
                    "."
            );
            gl.deleteProgram(this._p);
            return;
        }

        this.use();

        for (var a in this._attributes) {
            //this.attributes[a]._name = a;
            this._variables[a] = this._attributes[a];

            this._attributes[a]._callback = Program.bindBuffer;

            let itemTypeStr = this._attributes[a].itemType
                ? this._attributes[a].itemType.trim().toUpperCase()
                : "FLOAT";

            if (itemTypes.indexOf(itemTypeStr) == -1) {
                cons.logErr(
                    `og/Program/Program: ${this.name}- attribute '${a}', item type ${this._attributes[a].itemType} not exists.`
                );
                this._attributes[a].itemType = gl.FLOAT;
            } else {
                this._attributes[a].itemType = gl[itemTypeStr];
            }

            this._attributes[a].normalized = this._attributes[a].normalized || false;
            this._attributes[a].divisor = this._attributes[a].divisor || 0;

            this._p[a] = gl.getAttribLocation(this._p, a);

            if (this._p[a] == undefined) {
                cons.logErr(
                    "og/Program/Program:" + this.name + " - attribute '" + a + "' is not exists."
                );
                gl.deleteProgram(this._p);
                return;
            }

            let type = this._attributes[a].type;
            if (typeof type === "string") {
                type = typeStr[type.trim().toLowerCase()];
            }

            let d = this._attributes[a].divisor;
            if (type === types.MAT4) {
                let loc = this._p[a];
                this._attribArrays.push(loc, loc + 1, loc + 2, loc + 3);
                this._attribDivisor.push(d, d, d, d);
            } else {
                this._attribArrays.push(this._p[a]);
                this._attribDivisor.push(d);
            }

            gl.enableVertexAttribArray(this._p[a]);

            this._attributes[a]._pName = this._p[a];
            this.attributes[a] = this._p[a];
        }

        for (var u in this._uniforms) {
            //this.uniforms[u]._name = u;

            if (typeof this._uniforms[u].type === "string") {
                this._uniforms[u]._callback =
                    callbacks.u[typeStr[this._uniforms[u].type.trim().toLowerCase()]];
            } else {
                this._uniforms[u]._callback = callbacks.u[this._uniforms[u].type];
            }

            this._variables[u] = this._uniforms[u];
            this._p[u] = gl.getUniformLocation(this._p, u);

            if (this._p[u] == undefined) {
                cons.logErr(
                    "og/Program/Program:" + this.name + " - uniform '" + u + "' is not exists."
                );
                gl.deleteProgram(this._p);
                return;
            }

            this._uniforms[u]._pName = this._p[u];
            this.uniforms[u] = this._p[u];
        }

        gl.detachShader(this._p, fs);
        gl.detachShader(this._p, vs);

        gl.deleteShader(fs);
        gl.deleteShader(vs);
    }
}

export { Program };