shader.js

import { gl } from './common.js';
import { shaders } from './shader_source.js';

/**
 * This class instance includes all shader logic.
 * @readonly
 * @type {HB.Shader}
 * @memberof HB
 */
let shader = undefined;

/**
 * (DO NOT USE) Class with all of the shader initialization and handling.
 * @readonly
 * @memberof HB
 */
class Shader {
	/**
	 * (DO NOT USE) Internal use by Hummingbird only.
	 * @constructor
	 * @readonly
	 * @param {Function} vertexFunc - Optional function that returns the vertex shader source, [defaults]{@link https://projects.brandond.nl/Hummingbird/docs/shader_source.js.html}.
	 * @param {Function} fragmentFunc - Optional function that returns the fragment shader source, [defaults]{@link https://projects.brandond.nl/Hummingbird/docs/shader_source.js.html}.
	 * @param {Function} shaderInitFunc - Optional function to (for example) initialize uniforms in the shader, [defaults]{@link https://projects.brandond.nl/Hummingbird/docs/shader_source.js.html}.
	 * @memberof HB
	 */
	constructor(vertexFunc = shaders.colored.vertex, fragmentFunc = shaders.colored.fragment, shaderInitFunc = shaders.colored.init) {
		/**
		 * (DO NOT USE) Internal variable to keep track of the vertex source for compilation.
		 * @readonly
		 */
		this.vertexSource = vertexFunc();
		/**
		 * (DO NOT USE) Internal variable to keep track of the fragment source for compilation.
		 * @readonly
		 */
		this.fragmentSource = fragmentFunc();
		/**
		 * (DO NOT USE) Internal variable for initilization of parts of the shaders (like uniforms).
		 * @readonly
		 */
		this.initFunc = shaderInitFunc;

		/**
		 * (DO NOT USE) Internal variable in which the shader is stored.
		 * @readonly
		 */
		this.id = this.createProgram(this.vertexSource, this.fragmentSource);
		/**
		 * (DO NOT USE) Internal variable for caching shader attributes, to minimize API calls.
		 * @readonly
		 */
		this.attribLocationCache = {};
		/**
		 * (DO NOT USE) Internal variable for caching shader uniforms, to minimize API calls.
		 * @readonly
		 */
		this.uniformLocationCache = {};
	}

	/**
	 * (DO NOT USE) Internal method for creating the {@link HB.shader} instance.
	 * @readonly
	 */
	static init() {
		gl.getExtension('OES_standard_derivatives');
		shader = new Shader();
		shader.bind();
		shader.initFunc();
	}

	/**
	 * (DO NOT USE) Actual method for creating the shader program with compiled shaders.
	 * @readonly
	 * @param {string} vertexShaderSource - Argument for vertex shader to use.
	 * @param {string} fragmentShaderSource - Argument for fragment shader to use.
	 * @returns {WebGLProgram}
	 */
	createProgram(vertexShaderSource, fragmentShaderSource) {
		const program = gl.createProgram();
		const vertexShader = this.compileShader(gl.VERTEX_SHADER, vertexShaderSource);
		const fragmentShader = this.compileShader(gl.FRAGMENT_SHADER, fragmentShaderSource);

		gl.attachShader(program, vertexShader);
		gl.attachShader(program, fragmentShader);
		gl.linkProgram(program);
		gl.validateProgram(program);

		gl.deleteShader(vertexShader);
		gl.deleteShader(fragmentShader);

		return program;
	}

	/**
	 * (DO NOT USE) Method for compiling shaders.
	 * @readonly
	 * @param {number} type - The type of shader, see [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/createShader#parameters}.
	 * @param {string} source - Shader source to use.
	 * @returns {WebGLShader}
	 */
	compileShader(type, source) {
		const shader = gl.createShader(type);
		gl.shaderSource(shader, source);
		gl.compileShader(shader);

		if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) === false) {
			console.error(gl.getShaderInfoLog(shader));
			gl.deleteShader(shader);
			return null;
		}

		return shader;
	}

	/**
	 * (DO NOT USE) Method for getting the location of attributes inside shaders.
	 * @readonly
	 * @param {string} name - Name of the attribute.
	 * @returns {number} ID of attribute inside shader.
	 */
	getAttribLocation(name) {
		if (this.attribLocationCache[name] === undefined) this.attribLocationCache[name] = gl.getAttribLocation(this.id, name);
		return this.attribLocationCache[name];
	}
	/**
	 * (DO NOT USE) Method for getting the location of uniforms inside shaders.
	 * @readonly
	 * @param {string} name - Name of the uniform.
	 * @returns {WebGLUniformLocation} Location of uniform inside shader.
	 */
	getUniformLocation(name) {
		if (this.uniformLocationCache[name] === undefined) this.uniformLocationCache[name] = gl.getUniformLocation(this.id, name);
		return this.uniformLocationCache[name];
	}
	/**
	 * (DO NOT USE) Method for setting a uniform inside shaders.
	 * @readonly
	 * @param {string} type - Type of value, "f" for floats and "i" for integers, see [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniform}.
	 * @param {string} name - Name of the uniform.
	 * @param {Array} values - Array with values, length 1-4, see [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniform}.
	 */
	setUniform(type, name, values) { gl['uniform' + values.length + type](this.getUniformLocation(name), values[0], values[1], values[2], values[3]); }
	/**
	 * (DO NOT USE) Method for setting array uniforms inside shaders.
	 * @readonly
	 * @param {string} type - Type of value, "f" for floats and "i" for integers, see [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniform}.
	 * @param {string} name - Name of the uniform.
	 * @param {Array} array - Array with values, see [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniform}.
	 * @param {number} elementAmount - Amount of elements in each element of the array, i.e. 2 for vec2, see [docs.gl]{@link https://docs.gl/es2/glUniform} and [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniform}.
	 */
	setUniformArray(type, name, array, elementAmount = 1) { gl['uniform' + elementAmount + type + 'v'](this.getUniformLocation(name), array); }
	/**
	 * (DO NOT USE) Method for setting matrix uniforms inside shaders.
	 * @readonly
	 * @param {string} type - Type of value, "f" for floats only, see [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniformMatrix}.
	 * @param {string} name - Name of the uniform.
	 * @param {glMatrix.mat4} matrix - Matrix.
	 */
	setUniformMatrix(type, name, matrix) { gl['uniformMatrix' + Math.sqrt(matrix.length) + type + 'v'](this.getUniformLocation(name), false, matrix); }

	/**
	 * (DO NOT USE) Method to bind this shader program (set it as active).
	 * @readonly
	 */
	bind() { gl.useProgram(this.id); }
	/**
	 * (DO NOT USE) Method to bind an empty shader program (set this one as inactive).
	 * @readonly
	 */
	unbind() { gl.useProgram(null); }
	/**
	 * (DO NOT USE) Method to unbind and then delete this shader program, is called from {@link HB.Renderer#delete}.
	 * @readonly
	 */
	delete() {
		this.unbind();
		gl.deleteProgram(this.id);
	}
}

export {
	Shader,
	shader
};