Reference Source

src/viewer/scene/webgl/Program.js

import {Map} from "../utils/Map.js";
import {Shader} from "./Shader.js";
import {Sampler} from "./Sampler.js";
import {Attribute} from "./Attribute.js";

const ids = new Map({});

function joinSansComments(srcLines) {
    const src = [];
    let line;
    let n;
    for (let i = 0, len = srcLines.length; i < len; i++) {
        line = srcLines[i];
        n = line.indexOf("/");
        if (n > 0) {
            if (line.charAt(n + 1) === "/") {
                line = line.substring(0, n);
            }
        }
        src.push(line);
    }
    return src.join("\n");
}

function logErrors(errors) {
    console.error(errors.join("\n"));
}

/**
 * @desc Represents a WebGL program.
 * @private
 */
class Program {

    constructor(gl, shaderSource) {
        this.id = ids.addItem({});
        this.source = shaderSource;
        this.init(gl);
    }

    init(gl) {
        this.gl = gl;
        this.allocated = false;
        this.compiled = false;
        this.linked = false;
        this.validated = false;
        this.errors = null;
        this.uniforms = {};
        this.samplers = {};
        this.attributes = {};
        this._vertexShader = new Shader(gl, gl.VERTEX_SHADER, joinSansComments(this.source.vertex));
        this._fragmentShader = new Shader(gl, gl.FRAGMENT_SHADER, joinSansComments(this.source.fragment));
        if (!this._vertexShader.allocated) {
            this.errors = ["Vertex shader failed to allocate"].concat(this._vertexShader.errors);
            logErrors(this.errors);
            return;
        }
        if (!this._fragmentShader.allocated) {
            this.errors = ["Fragment shader failed to allocate"].concat(this._fragmentShader.errors);
            logErrors(this.errors);
            return;
        }
        this.allocated = true;
        if (!this._vertexShader.compiled) {
            this.errors = ["Vertex shader failed to compile"].concat(this._vertexShader.errors);
            logErrors(this.errors);
            return;
        }
        if (!this._fragmentShader.compiled) {
            this.errors = ["Fragment shader failed to compile"].concat(this._fragmentShader.errors);
            logErrors(this.errors);
            return;
        }
        this.compiled = true;
        let a;
        let i;
        let u;
        let uName;
        let location;
        this.handle = gl.createProgram();
        if (!this.handle) {
            this.errors = ["Failed to allocate program"];
            return;
        }
        gl.attachShader(this.handle, this._vertexShader.handle);
        gl.attachShader(this.handle, this._fragmentShader.handle);
        gl.linkProgram(this.handle);
        this.linked = gl.getProgramParameter(this.handle, gl.LINK_STATUS);
        // HACK: Disable validation temporarily
        // Perhaps we should defer validation until render-time, when the program has values set for all inputs?
        this.validated = true;
        if (!this.linked || !this.validated) {
            this.errors = [];
            this.errors.push("");
            this.errors.push(gl.getProgramInfoLog(this.handle));
            this.errors.push("\nVertex shader:\n");
            this.errors = this.errors.concat(this.source.vertex);
            this.errors.push("\nFragment shader:\n");
            this.errors = this.errors.concat(this.source.fragment);
            logErrors(this.errors);
            return;
        }
        const numUniforms = gl.getProgramParameter(this.handle, gl.ACTIVE_UNIFORMS);
        for (i = 0; i < numUniforms; ++i) {
            u = gl.getActiveUniform(this.handle, i);
            if (u) {
                uName = u.name;
                if (uName[uName.length - 1] === "\u0000") {
                    uName = uName.substr(0, uName.length - 1);
                }
                location = gl.getUniformLocation(this.handle, uName);
                if ((u.type === gl.SAMPLER_2D) || (u.type === gl.SAMPLER_CUBE) || (u.type === 35682)) {
                    this.samplers[uName] = new Sampler(gl, location);
                } else if (gl instanceof WebGL2RenderingContext && (u.type === gl.UNSIGNED_INT_SAMPLER_2D || u.type === gl.INT_SAMPLER_2D)) {
                    this.samplers[uName] = new Sampler(gl, location);
                } else {
                    this.uniforms[uName] = location;
                }
            }
        }
        const numAttribs = gl.getProgramParameter(this.handle, gl.ACTIVE_ATTRIBUTES);
        for (i = 0; i < numAttribs; i++) {
            a = gl.getActiveAttrib(this.handle, i);
            if (a) {
                location = gl.getAttribLocation(this.handle, a.name);
                this.attributes[a.name] = new Attribute(gl, location);
            }
        }
        this.allocated = true;
    }

    bind() {
        if (!this.allocated) {
            return;
        }
        this.gl.useProgram(this.handle);
    }

    getLocation(name) {
        if (!this.allocated) {
            return;
        }
        return this.uniforms[name];
    }

    getAttribute(name) {
        if (!this.allocated) {
            return;
        }
        return this.attributes[name];
    }

    bindTexture(name, texture, unit) {
        if (!this.allocated) {
            return false;
        }
        const sampler = this.samplers[name];
        if (sampler) {
            return sampler.bindTexture(texture, unit);
        } else {
            return false;
        }
    }

    destroy() {
        if (!this.allocated) {
            return;
        }
        ids.removeItem(this.id);
        this.gl.deleteProgram(this.handle);
        this.gl.deleteShader(this._vertexShader.handle);
        this.gl.deleteShader(this._fragmentShader.handle);
        this.handle = null;
        this.attributes = null;
        this.uniforms = null;
        this.samplers = null;
        this.allocated = false;
    }
}

export {Program};