/** * Uniforms of a program. * Those form a tree structure with a special top-level container for the root, * which you get by calling 'new WebGLUniforms( gl, program )'. * * * Properties of inner nodes including the top-level container: * * .seq - array of nested uniforms * .map - nested uniforms by name * * * Methods of all nodes except the top-level container: * * .setValue( gl, value, [textures] ) * * uploads a uniform value(s) * the 'textures' parameter is needed for sampler uniforms * * * Static methods of the top-level container (textures factorizations): * * .upload( gl, seq, values, textures ) * * sets uniforms in 'seq' to 'values[id].value' * * .seqWithValue( seq, values ) : filteredSeq * * filters 'seq' entries with corresponding entry in values * * * Methods of the top-level container (textures factorizations): * * .setValue( gl, name, value, textures ) * * sets uniform with name 'name' to 'value' * * .setOptional( gl, obj, prop ) * * like .set for an optional property of the object * */ import { CubeTexture } from '../../textures/CubeTexture.js'; import { Texture } from '../../textures/Texture.js'; import { DataTexture2DArray } from '../../textures/DataTexture2DArray.js'; import { DataTexture3D } from '../../textures/DataTexture3D.js'; const emptyTexture = new Texture(); const emptyTexture2dArray = new DataTexture2DArray(); const emptyTexture3d = new DataTexture3D(); const emptyCubeTexture = new CubeTexture(); // --- Utilities --- // Array Caches (provide typed arrays for temporary by size) const arrayCacheF32 = []; const arrayCacheI32 = []; // Float32Array caches used for uploading Matrix uniforms const mat4array = new Float32Array(16); const mat3array = new Float32Array(9); const mat2array = new Float32Array(4); // Flattening for arrays of vectors and matrices function flatten(array, nBlocks, blockSize) { const firstElem = array[0]; if (firstElem <= 0 || firstElem > 0) return array; // unoptimized: ! isNaN( firstElem ) // see http://jacksondunstan.com/articles/983 const n = nBlocks * blockSize; let r = arrayCacheF32[n]; if (r === undefined) { r = new Float32Array(n); arrayCacheF32[n] = r; } if (nBlocks !== 0) { firstElem.toArray(r, 0); for (let i = 1, offset = 0; i !== nBlocks; ++i) { offset += blockSize; array[i].toArray(r, offset); } } return r; } 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 copyArray(a, b) { for (let i = 0, l = b.length; i < l; i++) { a[i] = b[i]; } } // Texture unit allocation function allocTexUnits(textures, n) { let r = arrayCacheI32[n]; if (r === undefined) { r = new Int32Array(n); arrayCacheI32[n] = r; } for (let i = 0; i !== n; ++i) { r[i] = textures.allocateTextureUnit(); } return r; } // --- Setters --- // Note: Defining these methods externally, because they come in a bunch // and this way their names minify. // Single scalar function setValueV1f(gl, v) { const cache = this.cache; if (cache[0] === v) return; gl.uniform1f(this.addr, v); cache[0] = v; } // Single float vector (from flat array or THREE.VectorN) function setValueV2f(gl, v) { const cache = this.cache; if (v.x !== undefined) { if (cache[0] !== v.x || cache[1] !== v.y) { gl.uniform2f(this.addr, v.x, v.y); cache[0] = v.x; cache[1] = v.y; } } else { if (arraysEqual(cache, v)) return; gl.uniform2fv(this.addr, v); copyArray(cache, v); } } function setValueV3f(gl, v) { const cache = this.cache; if (v.x !== undefined) { if (cache[0] !== v.x || cache[1] !== v.y || cache[2] !== v.z) { gl.uniform3f(this.addr, v.x, v.y, v.z); cache[0] = v.x; cache[1] = v.y; cache[2] = v.z; } } else if (v.r !== undefined) { if (cache[0] !== v.r || cache[1] !== v.g || cache[2] !== v.b) { gl.uniform3f(this.addr, v.r, v.g, v.b); cache[0] = v.r; cache[1] = v.g; cache[2] = v.b; } } else { if (arraysEqual(cache, v)) return; gl.uniform3fv(this.addr, v); copyArray(cache, v); } } function setValueV4f(gl, v) { const cache = this.cache; if (v.x !== undefined) { if (cache[0] !== v.x || cache[1] !== v.y || cache[2] !== v.z || cache[3] !== v.w) { gl.uniform4f(this.addr, v.x, v.y, v.z, v.w); cache[0] = v.x; cache[1] = v.y; cache[2] = v.z; cache[3] = v.w; } } else { if (arraysEqual(cache, v)) return; gl.uniform4fv(this.addr, v); copyArray(cache, v); } } // Single matrix (from flat array or THREE.MatrixN) function setValueM2(gl, v) { const cache = this.cache; const elements = v.elements; if (elements === undefined) { if (arraysEqual(cache, v)) return; gl.uniformMatrix2fv(this.addr, false, v); copyArray(cache, v); } else { if (arraysEqual(cache, elements)) return; mat2array.set(elements); gl.uniformMatrix2fv(this.addr, false, mat2array); copyArray(cache, elements); } } function setValueM3(gl, v) { const cache = this.cache; const elements = v.elements; if (elements === undefined) { if (arraysEqual(cache, v)) return; gl.uniformMatrix3fv(this.addr, false, v); copyArray(cache, v); } else { if (arraysEqual(cache, elements)) return; mat3array.set(elements); gl.uniformMatrix3fv(this.addr, false, mat3array); copyArray(cache, elements); } } function setValueM4(gl, v) { const cache = this.cache; const elements = v.elements; if (elements === undefined) { if (arraysEqual(cache, v)) return; gl.uniformMatrix4fv(this.addr, false, v); copyArray(cache, v); } else { if (arraysEqual(cache, elements)) return; mat4array.set(elements); gl.uniformMatrix4fv(this.addr, false, mat4array); copyArray(cache, elements); } } // Single integer / boolean function setValueV1i(gl, v) { const cache = this.cache; if (cache[0] === v) return; gl.uniform1i(this.addr, v); cache[0] = v; } // Single integer / boolean vector (from flat array) function setValueV2i(gl, v) { const cache = this.cache; if (arraysEqual(cache, v)) return; gl.uniform2iv(this.addr, v); copyArray(cache, v); } function setValueV3i(gl, v) { const cache = this.cache; if (arraysEqual(cache, v)) return; gl.uniform3iv(this.addr, v); copyArray(cache, v); } function setValueV4i(gl, v) { const cache = this.cache; if (arraysEqual(cache, v)) return; gl.uniform4iv(this.addr, v); copyArray(cache, v); } // Single unsigned integer function setValueV1ui(gl, v) { const cache = this.cache; if (cache[0] === v) return; gl.uniform1ui(this.addr, v); cache[0] = v; } // Single unsigned integer vector (from flat array) function setValueV2ui(gl, v) { const cache = this.cache; if (arraysEqual(cache, v)) return; gl.uniform2uiv(this.addr, v); copyArray(cache, v); } function setValueV3ui(gl, v) { const cache = this.cache; if (arraysEqual(cache, v)) return; gl.uniform3uiv(this.addr, v); copyArray(cache, v); } function setValueV4ui(gl, v) { const cache = this.cache; if (arraysEqual(cache, v)) return; gl.uniform4uiv(this.addr, v); copyArray(cache, v); } // Single texture (2D / Cube) function setValueT1(gl, v, textures) { const cache = this.cache; const unit = textures.allocateTextureUnit(); if (cache[0] !== unit) { gl.uniform1i(this.addr, unit); cache[0] = unit; } textures.safeSetTexture2D(v || emptyTexture, unit); } function setValueT3D1(gl, v, textures) { const cache = this.cache; const unit = textures.allocateTextureUnit(); if (cache[0] !== unit) { gl.uniform1i(this.addr, unit); cache[0] = unit; } textures.setTexture3D(v || emptyTexture3d, unit); } function setValueT6(gl, v, textures) { const cache = this.cache; const unit = textures.allocateTextureUnit(); if (cache[0] !== unit) { gl.uniform1i(this.addr, unit); cache[0] = unit; } textures.safeSetTextureCube(v || emptyCubeTexture, unit); } function setValueT2DArray1(gl, v, textures) { const cache = this.cache; const unit = textures.allocateTextureUnit(); if (cache[0] !== unit) { gl.uniform1i(this.addr, unit); cache[0] = unit; } textures.setTexture2DArray(v || emptyTexture2dArray, unit); } // Helper to pick the right setter for the singular case function getSingularSetter(type) { switch (type) { case 0x1406: return setValueV1f; // FLOAT case 0x8b50: return setValueV2f; // _VEC2 case 0x8b51: return setValueV3f; // _VEC3 case 0x8b52: return setValueV4f; // _VEC4 case 0x8b5a: return setValueM2; // _MAT2 case 0x8b5b: return setValueM3; // _MAT3 case 0x8b5c: return setValueM4; // _MAT4 case 0x1404: case 0x8b56: return setValueV1i; // INT, BOOL case 0x8b53: case 0x8b57: return setValueV2i; // _VEC2 case 0x8b54: case 0x8b58: return setValueV3i; // _VEC3 case 0x8b55: case 0x8b59: return setValueV4i; // _VEC4 case 0x1405: return setValueV1ui; // UINT case 0x8dc6: return setValueV2ui; // _VEC2 case 0x8dc7: return setValueV3ui; // _VEC3 case 0x8dc8: return setValueV4ui; // _VEC4 case 0x8b5e: // SAMPLER_2D case 0x8d66: // SAMPLER_EXTERNAL_OES case 0x8dca: // INT_SAMPLER_2D case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D case 0x8b62: // SAMPLER_2D_SHADOW return setValueT1; case 0x8b5f: // SAMPLER_3D case 0x8dcb: // INT_SAMPLER_3D case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D return setValueT3D1; case 0x8b60: // SAMPLER_CUBE case 0x8dcc: // INT_SAMPLER_CUBE case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE case 0x8dc5: // SAMPLER_CUBE_SHADOW return setValueT6; case 0x8dc1: // SAMPLER_2D_ARRAY case 0x8dcf: // INT_SAMPLER_2D_ARRAY case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW return setValueT2DArray1; } } // Array of scalars function setValueV1fArray(gl, v) { gl.uniform1fv(this.addr, v); } // Array of vectors (from flat array or array of THREE.VectorN) function setValueV2fArray(gl, v) { const data = flatten(v, this.size, 2); gl.uniform2fv(this.addr, data); } function setValueV3fArray(gl, v) { const data = flatten(v, this.size, 3); gl.uniform3fv(this.addr, data); } function setValueV4fArray(gl, v) { const data = flatten(v, this.size, 4); gl.uniform4fv(this.addr, data); } // Array of matrices (from flat array or array of THREE.MatrixN) function setValueM2Array(gl, v) { const data = flatten(v, this.size, 4); gl.uniformMatrix2fv(this.addr, false, data); } function setValueM3Array(gl, v) { const data = flatten(v, this.size, 9); gl.uniformMatrix3fv(this.addr, false, data); } function setValueM4Array(gl, v) { const data = flatten(v, this.size, 16); gl.uniformMatrix4fv(this.addr, false, data); } // Array of integer / boolean function setValueV1iArray(gl, v) { gl.uniform1iv(this.addr, v); } // Array of integer / boolean vectors (from flat array) function setValueV2iArray(gl, v) { gl.uniform2iv(this.addr, v); } function setValueV3iArray(gl, v) { gl.uniform3iv(this.addr, v); } function setValueV4iArray(gl, v) { gl.uniform4iv(this.addr, v); } // Array of unsigned integer function setValueV1uiArray(gl, v) { gl.uniform1uiv(this.addr, v); } // Array of unsigned integer vectors (from flat array) function setValueV2uiArray(gl, v) { gl.uniform2uiv(this.addr, v); } function setValueV3uiArray(gl, v) { gl.uniform3uiv(this.addr, v); } function setValueV4uiArray(gl, v) { gl.uniform4uiv(this.addr, v); } // Array of textures (2D / 3D / Cube / 2DArray) function setValueT1Array(gl, v, textures) { const n = v.length; const units = allocTexUnits(textures, n); gl.uniform1iv(this.addr, units); for (let i = 0; i !== n; ++i) { textures.safeSetTexture2D(v[i] || emptyTexture, units[i]); } } function setValueT3DArray(gl, v, textures) { const n = v.length; const units = allocTexUnits(textures, n); gl.uniform1iv(this.addr, units); for (let i = 0; i !== n; ++i) { textures.setTexture3D(v[i] || emptyTexture3d, units[i]); } } function setValueT6Array(gl, v, textures) { const n = v.length; const units = allocTexUnits(textures, n); gl.uniform1iv(this.addr, units); for (let i = 0; i !== n; ++i) { textures.safeSetTextureCube(v[i] || emptyCubeTexture, units[i]); } } function setValueT2DArrayArray(gl, v, textures) { const n = v.length; const units = allocTexUnits(textures, n); gl.uniform1iv(this.addr, units); for (let i = 0; i !== n; ++i) { textures.setTexture2DArray(v[i] || emptyTexture2dArray, units[i]); } } // Helper to pick the right setter for a pure (bottom-level) array function getPureArraySetter(type) { switch (type) { case 0x1406: return setValueV1fArray; // FLOAT case 0x8b50: return setValueV2fArray; // _VEC2 case 0x8b51: return setValueV3fArray; // _VEC3 case 0x8b52: return setValueV4fArray; // _VEC4 case 0x8b5a: return setValueM2Array; // _MAT2 case 0x8b5b: return setValueM3Array; // _MAT3 case 0x8b5c: return setValueM4Array; // _MAT4 case 0x1404: case 0x8b56: return setValueV1iArray; // INT, BOOL case 0x8b53: case 0x8b57: return setValueV2iArray; // _VEC2 case 0x8b54: case 0x8b58: return setValueV3iArray; // _VEC3 case 0x8b55: case 0x8b59: return setValueV4iArray; // _VEC4 case 0x1405: return setValueV1uiArray; // UINT case 0x8dc6: return setValueV2uiArray; // _VEC2 case 0x8dc7: return setValueV3uiArray; // _VEC3 case 0x8dc8: return setValueV4uiArray; // _VEC4 case 0x8b5e: // SAMPLER_2D case 0x8d66: // SAMPLER_EXTERNAL_OES case 0x8dca: // INT_SAMPLER_2D case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D case 0x8b62: // SAMPLER_2D_SHADOW return setValueT1Array; case 0x8b5f: // SAMPLER_3D case 0x8dcb: // INT_SAMPLER_3D case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D return setValueT3DArray; case 0x8b60: // SAMPLER_CUBE case 0x8dcc: // INT_SAMPLER_CUBE case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE case 0x8dc5: // SAMPLER_CUBE_SHADOW return setValueT6Array; case 0x8dc1: // SAMPLER_2D_ARRAY case 0x8dcf: // INT_SAMPLER_2D_ARRAY case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW return setValueT2DArrayArray; } } // --- Uniform Classes --- function SingleUniform(id, activeInfo, addr) { this.id = id; this.addr = addr; this.cache = []; this.setValue = getSingularSetter(activeInfo.type); // this.path = activeInfo.name; // DEBUG } function PureArrayUniform(id, activeInfo, addr) { this.id = id; this.addr = addr; this.cache = []; this.size = activeInfo.size; this.setValue = getPureArraySetter(activeInfo.type); // this.path = activeInfo.name; // DEBUG } PureArrayUniform.prototype.updateCache = function (data) { const cache = this.cache; if (data instanceof Float32Array && cache.length !== data.length) { this.cache = new Float32Array(data.length); } copyArray(cache, data); }; function StructuredUniform(id) { this.id = id; this.seq = []; this.map = {}; } StructuredUniform.prototype.setValue = function (gl, value, textures) { const seq = this.seq; for (let i = 0, n = seq.length; i !== n; ++i) { const u = seq[i]; u.setValue(gl, value[u.id], textures); } }; // --- Top-level --- // Parser - builds up the property tree from the path strings const RePathPart = /(\w+)(\])?(\[|\.)?/g; // extracts // - the identifier (member name or array index) // - followed by an optional right bracket (found when array index) // - followed by an optional left bracket or dot (type of subscript) // // Note: These portions can be read in a non-overlapping fashion and // allow straightforward parsing of the hierarchy that WebGL encodes // in the uniform names. function addUniform(container, uniformObject) { container.seq.push(uniformObject); container.map[uniformObject.id] = uniformObject; } function parseUniform(activeInfo, addr, container) { const path = activeInfo.name, pathLength = path.length; // reset RegExp object, because of the early exit of a previous run RePathPart.lastIndex = 0; while (true) { const match = RePathPart.exec(path), matchEnd = RePathPart.lastIndex; let id = match[1]; const idIsIndex = match[2] === ']', subscript = match[3]; if (idIsIndex) id = id | 0; // convert to integer if (subscript === undefined || (subscript === '[' && matchEnd + 2 === pathLength)) { // bare name or "pure" bottom-level array "[0]" suffix addUniform(container, subscript === undefined ? new SingleUniform(id, activeInfo, addr) : new PureArrayUniform(id, activeInfo, addr)); break; } else { // step into inner node / create it in case it doesn't exist const map = container.map; let next = map[id]; if (next === undefined) { next = new StructuredUniform(id); addUniform(container, next); } container = next; } } } // Root Container function WebGLUniforms(gl, program) { this.seq = []; this.map = {}; const n = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); for (let i = 0; i < n; ++i) { const info = gl.getActiveUniform(program, i), addr = gl.getUniformLocation(program, info.name); parseUniform(info, addr, this); } } WebGLUniforms.prototype.setValue = function (gl, name, value, textures) { const u = this.map[name]; if (u !== undefined) u.setValue(gl, value, textures); }; WebGLUniforms.prototype.setOptional = function (gl, object, name) { const v = object[name]; if (v !== undefined) this.setValue(gl, name, v); }; // Static interface WebGLUniforms.upload = function (gl, seq, values, textures) { for (let i = 0, n = seq.length; i !== n; ++i) { const u = seq[i], v = values[u.id]; if (v.needsUpdate !== false) { // note: always updating when .needsUpdate is undefined u.setValue(gl, v.value, textures); } } }; WebGLUniforms.seqWithValue = function (seq, values) { const r = []; for (let i = 0, n = seq.length; i !== n; ++i) { const u = seq[i]; if (u.id in values) r.push(u); } return r; }; export { WebGLUniforms };