shaderProgram.js

/**
 * @typedef {Object} Uniform
 * @description an object that contains type and value of an uniform.<br>
 * @property {String} type uniform type
 * @property {*} value value of this uniform that must match the type.
 */

/** 
 * Compiles vertex shader, fragment shader and the gl program.<br>
 * Applies blending modes, depth sort modes and face culling modes.<br>
 * stores a set of its own uniforms as well.
 */
export class ShaderProgram{
    /** 
     * @param {String} vertexSource vertex shader glsl string
     * @param {String} fragmentSource vertex shader glsl string
     * @param {Object<String, Uniform>} uniforms (optional) uniforms for this program.
     */
    constructor( vertexSource, fragmentSource, uniforms ){
        /** 
         * @description glsl source code for vertex shader 
         * @type {String}
         */
        this.vertexSource = vertexSource;
        
        /** 
         * @description glsl source code for fragment shader 
         * @type {String}
         */
        this.fragmentSource = fragmentSource;
        
        /**
         * gl shader object
         * @type {WebGLShader}
         */
        this.vertexShader = null;
        
        /**
         * gl shader object
         * @type {WebGLShader}
         */
        this.fragmentShader = null;
        
        /**
         * gl program object
         * @type {WebGLProgram}
         */
        this.program = null;
        
        /**
         * Used internally to keep track of texture units while assigning texture uniforms.
         * @type {Number}
         * @readonly
         */
        this.textureUnit = 0;
        
        /** 
         * @description uniforms for this shader program
         * @type {Object<String,Uniform>}
         */
        this.uniforms = uniforms || {};
        
        /** 
         * @description cache for uniforms locations to avoid looking them up by webgl every time 
         * @type {Object<String,number>}
         * @readonly
         */
        this.uniformLocations = {};
        
        /** 
         * @description cache for attribute locations to avoid looking them up by webgl every time 
         * @type {Object<string,number>}
         * @readonly
         */
        this.attributeLocations = {};
        
        /**
         * Enable or disable gl.BLEND feature
         * @type {Boolean}
         * @default false
         */
        this.enableBlending = false;
        
        /**
         * gl blending source factor
         * @type {String}
         * @default 'ONE'
         */
        this.blendSRC = 'ONE';
        
        /**
         * gl blending destination factor
         * @type {String}
         * @default 'ONE'
         */
        this.blendDST = 'ONE';
        
        /**
         * Enable or disable gl.DEPTH_SORT feature
         * @type {Boolean}
         * @default true
         */
        this.enableDepth = true;
        
        /**
         * Depth sorting function
         * @type {String}
         * @default 'LEQUAL'
         */
        this.depthFunc = 'LEQUAL';
        
        /**
         * Enable or disable gl.CULL_FACE
         * @type {Boolean}
         * @default true
         */
        this.enableCulling = true;
        
        /**
         * Face culling mode
         * @type {String}
         * @default 'BACK'
         */
        this.cullFace = 'BACK';

        /**
         * used internally to check if shaders have been compiled yet
         * @type {Boolean}
         * @readonly
         */
        this.isCompiled = false;
    }
    /**
     * Creates webgl shader and program objects.<br>
     * Assigns shader sources to them.<br>
     * Compiles shaders, attaches them to program and links the program.<br>
     * <br>
     * This is called from {@link ShaderProgram#use} if {@link ShaderProgram#isCompiled} is false.
     * @param {WebGLRenderingContext} gl 
     */
    compile(gl){
        this.vertexShader = gl.createShader(gl.VERTEX_SHADER);
        this.fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(this.vertexShader, this.vertexSource);
        gl.compileShader(this.vertexShader);
        let message = gl.getShaderInfoLog(this.vertexShader);
        if (message.length > 0) {
            throw " vertex shader could not be compiled! "+message+" "+this.vertexSource;
        }
        gl.shaderSource(this.fragmentShader, this.fragmentSource);
        gl.compileShader(this.fragmentShader);
        message = gl.getShaderInfoLog(this.fragmentShader);
        if (message.length > 0) {
            throw " fragment shader could not be compiled! "+message+" "+this.fragmentSource;
        }
        this.program = gl.createProgram();
        gl.attachShader(this.program, this.vertexShader);
        gl.attachShader(this.program, this.fragmentShader);
        gl.linkProgram(this.program);
        this.isCompiled = true;
    }
    getAttributeLocation(gl, name){
        if(this.attributeLocations[name] === undefined){
            this.attributeLocations[name] = gl.getAttribLocation(this.program, name);
        }
        return this.attributeLocations[name];
    }
    getUniformLocation(gl, name){
        if(this.uniformLocations[name] === undefined){
            this.uniformLocations[name] = gl.getUniformLocation(this.program, name);
        }
        return this.uniformLocations[name];
    }
    setUniform(gl, name, type, value){
        let loc = this.getUniformLocation(gl,name);
        if(loc === null){ return; }
        switch(type){
            case 'm4': gl.uniformMatrix4fv(loc, false, value); break;
            case 'm3': gl.uniformMatrix3fv(loc, false, value); break;
            case 'm2': gl.uniformMatrix2fv(loc, false, value); break;
            case '1f': gl.uniform1f(loc, value); break;
            case '2fv': gl.uniform2fv(loc, value); break;
            case '3fv': gl.uniform3fv(loc, value); break;
            case '4fv': gl.uniform4fv(loc, value); break;
            case 't2d':
                value.setActive(gl, this.textureUnit);
                gl.uniform1i(loc, this.textureUnit);
                this.textureUnit++;
                break;
        }
    }
    /**
     * Sets this program to be active.<br>
     * and sets blending mode, depth sort mode and face culling mode.
     * Also compiles the shaders if they are not compiled already.
     * @param {WebGLRenderingContext} gl 
     */
    use(gl){
        this.textureUnit = 0;
        if(!this.isCompiled){ this.compile(gl); }
        gl.useProgram(this.program);
        for(let name in this.uniforms){
            this.setUniform(gl, name, this.uniforms[name].type, this.uniforms[name].value);
        }
        if(this.enableBlending){
            gl.enable(gl.BLEND);
            gl.blendFunc( this.blendSRC, this.blendDST );
        }else{
            gl.disable(gl.BLEND);
        }
        if(this.enableDepth){ 
            gl.enable(gl.DEPTH_TEST); 
            gl.depthFunc(gl[this.depthFunc]); 
        }else{
            gl.disable(gl.DEPTH_TEST);
        }
        if(this.enableCulling){
            gl.enable(gl.CULL_FACE);
            gl.cullFace(gl[this.cullFace]);
        }else{
            gl.disable(gl.CULL_FACE);
        }
    }
}