UNPKG

ogl

Version:
306 lines (260 loc) 11.3 kB
// TODO: upload empty texture if null ? maybe not // TODO: upload identity matrix if null ? // TODO: sampler Cube let ID = 1; // cache of typed arrays used to flatten uniform arrays const arrayCacheF32 = {}; export class Program { constructor( gl, { vertex, fragment, uniforms = {}, transparent = false, cullFace = gl.BACK, frontFace = gl.CCW, depthTest = true, depthWrite = true, depthFunc = gl.LESS, } = {} ) { if (!gl.canvas) console.error('gl not passed as fist argument to Program'); this.gl = gl; this.uniforms = uniforms; this.id = ID++; if (!vertex) console.warn('vertex shader not supplied'); if (!fragment) console.warn('fragment shader not supplied'); // Store program state this.transparent = transparent; this.cullFace = cullFace; this.frontFace = frontFace; this.depthTest = depthTest; this.depthWrite = depthWrite; this.depthFunc = depthFunc; this.blendFunc = {}; this.blendEquation = {}; // set default blendFunc if transparent flagged if (this.transparent && !this.blendFunc.src) { if (this.gl.renderer.premultipliedAlpha) this.setBlendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA); else this.setBlendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); } // compile vertex shader and log errors const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertex); gl.compileShader(vertexShader); if (gl.getShaderInfoLog(vertexShader) !== '') { console.warn(`${gl.getShaderInfoLog(vertexShader)}\nVertex Shader\n${addLineNumbers(vertex)}`); } // compile fragment shader and log errors const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragment); gl.compileShader(fragmentShader); if (gl.getShaderInfoLog(fragmentShader) !== '') { console.warn(`${gl.getShaderInfoLog(fragmentShader)}\nFragment Shader\n${addLineNumbers(fragment)}`); } // compile program and log errors this.program = gl.createProgram(); gl.attachShader(this.program, vertexShader); gl.attachShader(this.program, fragmentShader); gl.linkProgram(this.program); if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) { return console.warn(gl.getProgramInfoLog(this.program)); } // Remove shader once linked gl.deleteShader(vertexShader); gl.deleteShader(fragmentShader); // Get active uniform locations this.uniformLocations = new Map(); let numUniforms = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS); for (let uIndex = 0; uIndex < numUniforms; uIndex++) { let uniform = gl.getActiveUniform(this.program, uIndex); this.uniformLocations.set(uniform, gl.getUniformLocation(this.program, uniform.name)); // split uniforms' names to separate array and struct declarations const split = uniform.name.match(/(\w+)/g); uniform.uniformName = split[0]; if (split.length === 3) { uniform.isStructArray = true; uniform.structIndex = Number(split[1]); uniform.structProperty = split[2]; } else if (split.length === 2 && isNaN(Number(split[1]))) { uniform.isStruct = true; uniform.structProperty = split[1]; } } // Get active attribute locations this.attributeLocations = new Map(); const locations = []; const numAttribs = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES); for (let aIndex = 0; aIndex < numAttribs; aIndex++) { const attribute = gl.getActiveAttrib(this.program, aIndex); const location = gl.getAttribLocation(this.program, attribute.name); locations[location] = attribute.name; this.attributeLocations.set(attribute, location); } this.attributeOrder = locations.join(''); } setBlendFunc(src, dst, srcAlpha, dstAlpha) { this.blendFunc.src = src; this.blendFunc.dst = dst; this.blendFunc.srcAlpha = srcAlpha; this.blendFunc.dstAlpha = dstAlpha; if (src) this.transparent = true; } setBlendEquation(modeRGB, modeAlpha) { this.blendEquation.modeRGB = modeRGB; this.blendEquation.modeAlpha = modeAlpha; } applyState() { if (this.depthTest) this.gl.renderer.enable(this.gl.DEPTH_TEST); else this.gl.renderer.disable(this.gl.DEPTH_TEST); if (this.cullFace) this.gl.renderer.enable(this.gl.CULL_FACE); else this.gl.renderer.disable(this.gl.CULL_FACE); if (this.blendFunc.src) this.gl.renderer.enable(this.gl.BLEND); else this.gl.renderer.disable(this.gl.BLEND); if (this.cullFace) this.gl.renderer.setCullFace(this.cullFace); this.gl.renderer.setFrontFace(this.frontFace); this.gl.renderer.setDepthMask(this.depthWrite); this.gl.renderer.setDepthFunc(this.depthFunc); if (this.blendFunc.src) this.gl.renderer.setBlendFunc(this.blendFunc.src, this.blendFunc.dst, this.blendFunc.srcAlpha, this.blendFunc.dstAlpha); this.gl.renderer.setBlendEquation(this.blendEquation.modeRGB, this.blendEquation.modeAlpha); } use({ flipFaces = false } = {}) { let textureUnit = -1; const programActive = this.gl.renderer.currentProgram === this.id; // Avoid gl call if program already in use if (!programActive) { this.gl.useProgram(this.program); this.gl.renderer.currentProgram = this.id; } // Set only the active uniforms found in the shader this.uniformLocations.forEach((location, activeUniform) => { let name = activeUniform.uniformName; // get supplied uniform let uniform = this.uniforms[name]; // For structs, get the specific property instead of the entire object if (activeUniform.isStruct) { uniform = uniform[activeUniform.structProperty]; name += `.${activeUniform.structProperty}`; } if (activeUniform.isStructArray) { uniform = uniform[activeUniform.structIndex][activeUniform.structProperty]; name += `[${activeUniform.structIndex}].${activeUniform.structProperty}`; } if (!uniform) { return warn(`Active uniform ${name} has not been supplied`); } if (uniform && uniform.value === undefined) { return warn(`${name} uniform is missing a value parameter`); } if (uniform.value.texture) { textureUnit = textureUnit + 1; // Check if texture needs to be updated uniform.value.update(textureUnit); return setUniform(this.gl, activeUniform.type, location, textureUnit); } // For texture arrays, set uniform as an array of texture units instead of just one if (uniform.value.length && uniform.value[0].texture) { const textureUnits = []; uniform.value.forEach((value) => { textureUnit = textureUnit + 1; value.update(textureUnit); textureUnits.push(textureUnit); }); return setUniform(this.gl, activeUniform.type, location, textureUnits); } setUniform(this.gl, activeUniform.type, location, uniform.value); }); this.applyState(); if (flipFaces) this.gl.renderer.setFrontFace(this.frontFace === this.gl.CCW ? this.gl.CW : this.gl.CCW); } remove() { this.gl.deleteProgram(this.program); } } function setUniform(gl, type, location, value) { value = value.length ? flatten(value) : value; const setValue = gl.renderer.state.uniformLocations.get(location); // Avoid redundant uniform commands if (value.length) { if (setValue === undefined || setValue.length !== value.length) { // clone array to store as cache gl.renderer.state.uniformLocations.set(location, value.slice(0)); } else { if (arraysEqual(setValue, value)) return; // Update cached array values setValue.set ? setValue.set(value) : setArray(setValue, value); gl.renderer.state.uniformLocations.set(location, setValue); } } else { if (setValue === value) return; gl.renderer.state.uniformLocations.set(location, value); } switch (type) { case 5126: return value.length ? gl.uniform1fv(location, value) : gl.uniform1f(location, value); // FLOAT case 35664: return gl.uniform2fv(location, value); // FLOAT_VEC2 case 35665: return gl.uniform3fv(location, value); // FLOAT_VEC3 case 35666: return gl.uniform4fv(location, value); // FLOAT_VEC4 case 35670: // BOOL case 5124: // INT case 35678: // SAMPLER_2D case 35680: return value.length ? gl.uniform1iv(location, value) : gl.uniform1i(location, value); // SAMPLER_CUBE case 35671: // BOOL_VEC2 case 35667: return gl.uniform2iv(location, value); // INT_VEC2 case 35672: // BOOL_VEC3 case 35668: return gl.uniform3iv(location, value); // INT_VEC3 case 35673: // BOOL_VEC4 case 35669: return gl.uniform4iv(location, value); // INT_VEC4 case 35674: return gl.uniformMatrix2fv(location, false, value); // FLOAT_MAT2 case 35675: return gl.uniformMatrix3fv(location, false, value); // FLOAT_MAT3 case 35676: return gl.uniformMatrix4fv(location, false, value); // FLOAT_MAT4 } } function addLineNumbers(string) { let lines = string.split('\n'); for (let i = 0; i < lines.length; i++) { lines[i] = i + 1 + ': ' + lines[i]; } return lines.join('\n'); } function flatten(a) { const arrayLen = a.length; const valueLen = a[0].length; if (valueLen === undefined) return a; const length = arrayLen * valueLen; let value = arrayCacheF32[length]; if (!value) arrayCacheF32[length] = value = new Float32Array(length); for (let i = 0; i < arrayLen; i++) value.set(a[i], i * valueLen); return value; } function arraysEqual(a, b) { if (a.length !== b.length) return false; for (let i = 0, l = a.length; i < l; i++) { if (a[i] !== b[i]) return false; } return true; } function setArray(a, b) { for (let i = 0, l = a.length; i < l; i++) { a[i] = b[i]; } } let warnCount = 0; function warn(message) { if (warnCount > 100) return; console.warn(message); warnCount++; if (warnCount > 100) console.warn('More than 100 program warnings - stopping logs.'); }