Home Reference Source

viewer/programmanager.js

import {VERTEX_SHADER_SOURCE} from "./shaders/vertex.glsl.js";
import {FRAGMENT_SHADER_SOURCE} from "./shaders/fragment.glsl.js";

export const OBJECT_COLORS = 1;
export const VERTEX_QUANTIZATION = 2;
export const NORMAL_QUANTIZATION = 4;
export const COLOR_QUANTIZATION = 8;
export const REUSE = 16;
export const PICKING = 32;
export const LINE_PRIMITIVES = 64;
export const NORMAL_OCT_ENCODE = 128;
export const LINES = 256;
export const QUANTIZE_COLOR_BUFFER = 512;

/**
 * Keeps track of shader programs, glsl, uniform positions and vertex attributes
 */
export class ProgramManager {
	
	constructor(viewer, gl, settings) {
		this.viewer = viewer;
		this.gl = gl;
		this.settings = settings;
		this.programs = [];
		this.promises = [];
	}

	generateSetup(key) {
		var settings = {
			attributes: [],
			uniforms: []
		};
		if (key & LINE_PRIMITIVES) {
			return key;
		}
		if (key & PICKING) {
			if (key & REUSE) {
				settings.attributes.push("instancePickColors");
			} else {
				settings.attributes.push("vertexPickColor");
			}
		}
		if (key & REUSE) {
			settings.attributes.push("instanceMatrices");
			settings.uniforms.push("numContainedInstances");
			settings.uniforms.push("containedInstances");
			settings.uniforms.push("containedMeansHidden");
			if (!(key & PICKING) && !(key & LINES)) {
				settings.attributes.push("instanceNormalMatrices");
			}
		}
		if (!(key & PICKING)) {
			if (key & OBJECT_COLORS) {
				settings.uniforms.push("objectColor");
			} else {
				if (!(key & LINES)) {
					settings.attributes.push("vertexColor");
				}
			}			
		}
		if (key & NORMAL_QUANTIZATION || key & NORMAL_OCT_ENCODE) {
			// Has no effect on locations
		}
		if (key & VERTEX_QUANTIZATION) {
			settings.uniforms.push("vertexQuantizationMatrix");
		}
		return settings;
	}

	load() {
		var defaultSetup = {
			attributes: [
				"vertexPosition",
				"vertexNormal"
				],
			uniforms: [
				"projectionMatrix",
				"viewNormalMatrix",
				"postProcessingTranslation",
				"viewMatrix",
				"sectionPlane"
			],
			uniformBlocks: [
				"LightData"
			]
		};
		var defaultSetupForLines = {
			attributes: [
				"vertexPosition"
				],
				uniforms: [
					"projectionMatrix",
					"postProcessingTranslation",
					"viewMatrix",
					"sectionPlane"
				],
				uniformBlocks: [
				]
		};
		var defaultSetupForPicking = {
			attributes: [
				"vertexPosition"
				],
			uniforms: [
				"projectionMatrix",
				"postProcessingTranslation",
				"viewMatrix",
				"sectionPlane"
				],
			uniformBlocks: [
			]
		};

		{
			let picking = false;
			for (var instancing of [true, false]) {
				for (var lines of [true, false]) {
					var key = this.createKey(instancing, picking, lines);
					this.generateShaders(lines ? defaultSetupForLines : defaultSetup, key);
				}
			}
		}

		// Line primitives

		// Some line renderer do not use quantization (which is actually better for low-triangle-counts)
		// Others do (outline fore example potentialy draws a lot, also it's faster to copy if the regular geometry is already quantized)
		for (var quantization of [true, false]) {
			let lineUniforms = [
				"matrix",
				"inputColor",
				"projectionMatrix",
				"postProcessingTranslation",
				"viewMatrix",
				"aspect",
				"thickness"
			];
			var key = LINE_PRIMITIVES;
			if (quantization) {
				lineUniforms.push("vertexQuantizationMatrix");
				key |= VERTEX_QUANTIZATION;
			}
			
			this.setupProgram(VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE, {
				attributes: ["vertexPosition", "nextVertexPosition", "direction"],
				uniforms: lineUniforms
			}, this.generateSetup(key), key);
		}

        //  Picking shaders
		{
			let picking = true;
			for (var instancing of [true, false]) {
				var key = this.createKey(instancing, picking);
				this.generateShaders(defaultSetupForPicking, key);
			}
		}

        return Promise.all(this.promises);
	}
	
	generateShaders(defaultSetup, key) {
		this.setupProgram(VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE, defaultSetup, this.generateSetup(key), key);
	}

	getVertexShaderName() {
		return "shaders/vertex.glsl";
	}

	createKey(reuse, picking, lines=false) {
		var key = 0;
		key |= (this.settings.useObjectColors ? OBJECT_COLORS : 0);
		key |= (this.settings.quantizeVertices ? VERTEX_QUANTIZATION : 0);
		key |= (this.viewer.useFloatColorBuffer ? 0 : QUANTIZE_COLOR_BUFFER);
		key |= ((!picking && this.settings.quantizeNormals) ? NORMAL_QUANTIZATION : 0);
		key |= ((!picking && this.settings.loaderSettings.octEncodeNormals) ? NORMAL_OCT_ENCODE : 0);
		key |= ((!picking && this.settings.quantizeColors) ? COLOR_QUANTIZATION : 0);
		key |= (reuse ? REUSE : 0);
		key |= (picking ? PICKING : 0);
		key |= (lines ? LINES : 0);
		return key;
	}
	
	// Only used for debugging
	keyToJson(key) {
		return {
			useObjectColors: (key & OBJECT_COLORS) ? true : false,
			quantizeVertices: (key & VERTEX_QUANTIZATION) ? true : false,
			quantizeNormals: (key & NORMAL_QUANTIZATION) ? true : false,
			octEncodeNormals: (key & NORMAL_OCT_ENCODE) ? true : false,
			quantizeColors: (key & COLOR_QUANTIZATION) ? true : false,
			reuse: (key & REUSE) ? true : false,
			picking: (key & PICKING) ? true : false,
			linePrimitives: (key & LINE_PRIMITIVES) ? true : false,
			lines: (key & LINES) ? true : false,
			quantizeColorBuffer : (key & QUANTIZE_COLOR_BUFFER) ? true : false
		};
	}
	
	getProgram(key) {
//		console.log("getProgram", key, this.keyToJson(key));
		var program = this.programs[key];
		if (program != null) {
			return program;
		}
		console.error("Program not found", key);
//		this.programNames = this.programNames || {};
//		var vertexShaderName = this.getVertexShaderName(key);
//		if (!this.programNames[vertexShaderName]) {
//			console.log("getProgram(..) -> " + vertexShaderName);
//			this.programNames[vertexShaderName] = true;
//		}
//
//		program = this.programs[key];
//		if (program == null) {
//			console.error("Program not found", key);
//		}
//		return program;
	}

	setProgram(key, program) {
		this.programs[key] = program;
	}

	setupProgram(vertexShaderSource, fragmentShaderSource, defaultSetup, specificSetup, key) {
//		console.log("setupProgram", key, this.keyToJson(key));
		var p = new Promise((resolve, reject) => {
			var shaderProgram = this.initShaderProgram(this.gl, "vertex shader", vertexShaderSource, "fragment shader", fragmentShaderSource, key, defaultSetup, specificSetup);

			var programInfo = {
				program: shaderProgram,
				attribLocations: {},
				uniformLocations: {},
				uniformBlocks: {}
			};

			//console.log("----------------------------------------");
			//console.log("setupProgram (" + vertexShader + ", " + fragmentShader + ")");

			for (var setup of [defaultSetup, specificSetup]) {
				if (setup.attributes != null) {
					//console.log("attributes:");
					for (var attribute of setup.attributes) {
						let res = programInfo.attribLocations[attribute] = this.gl.getAttribLocation(shaderProgram, attribute);
						if (res === -1) {
							console.error("Missing attribute location", attribute, vertexShaderSource, this.keyToJson(key));
							debugger;
						}
					}
				}
				if (setup.uniforms != null) {
					// @todo can also use getUniformIndices()
					for (var uniform of setup.uniforms) {
						let res = programInfo.uniformLocations[uniform] = this.gl.getUniformLocation(shaderProgram, uniform);
						if (res === null) {
							console.error("Missing uniform location", uniform, vertexShaderSource, this.keyToJson(key));
							debugger;
						}
					}
				}
				if (setup.uniformBlocks != null) {
					if (setup.uniformBlocks != null) {
						for (var uniformBlock of setup.uniformBlocks) {
							let res = programInfo.uniformBlocks[uniformBlock] = this.gl.getUniformBlockIndex(shaderProgram, uniformBlock);
							if (res == -1) {
								console.error("Missing uniformBlock '" + uniformBlock + "' = " + programInfo.uniformBlocks[uniformBlock], this.keyToJson(key));
								debugger;
							} else {
								this.gl.uniformBlockBinding(shaderProgram, programInfo.uniformBlocks[uniformBlock], 0);
							}
						}
					}
				}
			}
			
			this.setProgram(key, programInfo);

			resolve(programInfo);
		});

		this.promises.push(p);

		return p;
	}

	initShaderProgram(gl, vsName, vsSource, fsName, fsSource, key, defaultSetup, specificSetup) {
		const vertexShader = this.loadShader(gl, gl.VERTEX_SHADER, vsName, vsSource, key);
		if (vertexShader == null) {
			console.error(defaultSetup, specificSetup);
			return;
		}
		const fragmentShader = this.loadShader(gl, gl.FRAGMENT_SHADER, fsName, fsSource, key);
		if (fragmentShader == null) {
			console.error(defaultSetup, specificSetup);
			return;
		}

		const shaderProgram = gl.createProgram();
		gl.attachShader(shaderProgram, vertexShader);
		gl.attachShader(shaderProgram, fragmentShader);
		gl.linkProgram(shaderProgram);

		if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
			console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
			return null;
		}

		return shaderProgram;
	}

	loadShader(gl, type, name, source, key) {
		var fullSource = "#version 300 es\n\n";
		// TODO would be nice to be able to access the constants generically, or use some sort of enum?
		if (key & OBJECT_COLORS) {
			fullSource += `#define WITH_USEOBJECTCOLORS\n`;
		}
		if (key & VERTEX_QUANTIZATION) {
			fullSource += `#define WITH_QUANTIZEVERTICES\n`;
		}
		if (key & NORMAL_QUANTIZATION) {
			fullSource += `#define WITH_QUANTIZENORMALS\n`;
		}
		if (key & NORMAL_OCT_ENCODE) {
			fullSource += `#define WITH_OCT_ENCODE_NORMALS\n`;
		}
		if (key & COLOR_QUANTIZATION) {
			fullSource += `#define WITH_QUANTIZECOLORS\n`;
		}
		if (key & REUSE) {
			fullSource += `#define WITH_INSTANCING\n`;
		}
		if (key & PICKING) {
			fullSource += `#define WITH_PICKING\n`;
		}
		if (key & LINE_PRIMITIVES) {
			fullSource += `#define WITH_LINEPRIMITIVES\n`;
		}
		if (key & LINES) {
			fullSource += `#define WITH_LINES\n`;
		}
		if (key & QUANTIZE_COLOR_BUFFER) {
			fullSource += `#define QUANTIZE_COLOR_BUFFER\n`;
		}
		
		fullSource += "\n" + source;
		const shader = gl.createShader(type);
		gl.shaderSource(shader, fullSource);
		gl.compileShader(shader);
		if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
			console.error(name);
//			console.error(fullSource);
			console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
			gl.deleteShader(shader);
			return null;
		}
		return shader;
	}
}