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;
	}
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