Home Reference Source Test

src/manager/ProgramManager.js

import Manager from './Manager';
import DOMManager from './DOMManager';
import * as VertexShader from '../const/VertexShader';
import * as FragmentShader from '../const/FragmentShader';


/**
 * Manages WebGL Programs so that they only need to be created once across the engine
 * allowing multiple objects to still share the same reference.
 * 
 * Elliminates the need for the programmer to compile shaders and create programs, and eases 
 * on-the-fly Shader compiling.
 */
export class _ProgramManager extends Manager {
    constructor() {
        super();
        this.programIDCounter = 0;
        this.programs = {};
    }

    /**
     * Creates default programs for easy reference later on.
     */
    start() {
        if (DOMManager.GL == null)
            return;
        this.addProgram('Default', VertexShader.DEFAULT_V, FragmentShader.DEFAULT_F, {
            'u_color': 'uniform4fv',
            'u_matrix': 'uniformMatrix3fv',
            'u_depth': 'uniform1f',
            'u_texture': 'uniform1i',
            'u_subTexcoord': 'uniform4fv',
        });
        this.addProgram('2DGrid', VertexShader.DEFAULT_V, FragmentShader.TILEMAP_F, {
            'u_color': 'uniform4fv',
            'u_matrix': 'uniformMatrix3fv',
            'u_depth': 'uniform1f',
            'u_mapDataTexture': 'uniform1i',
            'u_texture': 'uniform1i',
            'u_tileData': 'uniformMatrix4fv'
        });
    }

    /**
     * Returns a Program JSON Object containing the program, name and id.
     * 
     * @param {string} programKey Name of the program.
     * 
     * @returns {Program Object} Returns a JSON object with Program data.
     */
    getProgram(programKey) {
        if (this.programs[programKey] == null)
            throw 'Program does not exist!';
        return this.programs[programKey];
    }

    /**
     * Creates a Program JSON Object and initializes the program and metadata.
     * The program is added to the programs array.
     * 
     * @param {string} programName Name of the program.
     * @param {string} vertexShaderSource Source code of the vertex shader.
     * @param {string} fragmentShaderSource Source code of the fragment shader.
     * @param {Object} uniforms An object of 'string': 'number' values indicating the uniform variable name & data type enum value.
     */
    addProgram(programName, vertexShaderSource, fragmentShaderSource, uniforms) {
        let program = this._createProgram(vertexShaderSource, fragmentShaderSource);
        let uniformSetters = this._createProgramLocationSetters(program, uniforms);
        this.programs[programName] = {
            name: programName,
            id: this.programIDCounter++,
            program,
            uniformSetters,
            setUniforms: (uniformDataMapping) => {
                let setters = Object.keys(uniformDataMapping);
                for (let i = 0; i < setters.length; i++) {
                    uniformSetters[setters[i]](DOMManager.GL, uniformDataMapping[setters[i]]);
                }
            }
        };
    }

    /**
     * Creates a WebGL program from a compiled vertex and fragment shader. The program is returned.
     * 
     * @param {CompiledVertexShader} vertexShader A compiled vertex shader.
     * @param {CompiledFragmentShader} fragmentShader A compiled fragment shader.
     * 
     * @returns {Program} A WebGL program. Null if unsuccessful.
     */
    _createShadersProgram(vertexShader, fragmentShader) {
        let program = DOMManager.GL.createProgram();
        DOMManager.GL.attachShader(program, vertexShader);
        DOMManager.GL.attachShader(program, fragmentShader);
        DOMManager.GL.linkProgram(program);
        let success = DOMManager.GL.getProgramParameter(program, DOMManager.GL.LINK_STATUS);
        if (success) {
            return program;
        }

        console.error(DOMManager.GL.getProgramInfoLog(program));
        DOMManager.GL.deleteProgram(program);
        return null;
    }

    /**
     * Creates a shader of either Vertex or Fragment type and returns the compiled version.
     * 
     * @param {GLShaderType} type A GL shader type of either VERTEX_SHADER or FRAGMENT_SHADER.
     * @param {string} source Source code for the shader type.
     * 
     * @returns {CompiledShader} A compiled shader. Null if unsuccessful.
     */
    _createShader(type, source) {
        let shader = DOMManager.GL.createShader(type);
        DOMManager.GL.shaderSource(shader, source);
        DOMManager.GL.compileShader(shader);
        let success = DOMManager.GL.getShaderParameter(shader, DOMManager.GL.COMPILE_STATUS);
        if (success) {
            return shader;
        }

        console.error(DOMManager.GL.getShaderInfoLog(shader));
        DOMManager.GL.deleteShader(shader);
        return null;
    }

    /**
     * Compiles the vertex shader and fragment shader and returns a WebGL program.
     * 
     * @param {string} vertexShaderSource Source code for a vertex shader.
     * @param {string} fragmentShaderSource Source code for a fragment shader.
     * 
     * @returns {Program} A program compiled from the two shader sources.
     */
    _createProgram(vertexShaderSource, fragmentShaderSource) {
        let vertexShader = this._createShader(DOMManager.GL.VERTEX_SHADER, vertexShaderSource);
        let fragmentShader = this._createShader(DOMManager.GL.FRAGMENT_SHADER, fragmentShaderSource);

        return this._createShadersProgram(vertexShader, fragmentShader);
    }

    _createProgramLocationSetters(program, uniforms) {
        let uniformLocationSetters = {};
        let uniformKeys = Object.keys(uniforms);
        for (let i = 0; i < uniformKeys.length; i++) {
            let location = DOMManager.GL.getUniformLocation(program, uniformKeys[i]);
            uniformLocationSetters[uniformKeys[i]] = this._getUniformSetterFromString(location, uniforms[uniformKeys[i]]);
        }
        return uniformLocationSetters;
    }

    _getUniformSetterFromString(location, type) {
        return {
            'uniform4fv': (gl, uniformData) => { gl.uniform4fv(location, uniformData); },
            'uniformMatrix3fv': (gl, uniformData) => { gl.uniformMatrix3fv(location, false, uniformData); },
            'uniformMatrix4fv': (gl, uniformData) => { gl.uniformMatrix4fv(location, false, uniformData); },
            'uniform1f': (gl, uniformData) => { gl.uniform1f(location, uniformData); },
            'uniform1i': (gl, uniformData) => { gl.uniform1i(location, uniformData); },
        }[type];
    }
}

/**
 * Singleton reference to the WebGL Program Manager.
 */
const ProgramManager = new _ProgramManager();
export default ProgramManager;