<!-- NEW7 — TEST 7 · THE WOVEN CITY · part 365 -->
ue( gl, name, v );

	}

	static upload( 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 );

			}

		}

	}

	static seqWithValue( 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;

	}

}

function WebGLShader( gl, type, string ) {

	const shader = gl.createShader( type );

	gl.shaderSource( shader, string );
	gl.compileShader( shader );

	return shader;

}

// From https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile/
const COMPLETION_STATUS_KHR = 0x91B1;

let programIdCount = 0;

function handleSource( string, errorLine ) {

	const lines = string.split( '\n' );
	const lines2 = [];

	const from = Math.max( errorLine - 6, 0 );
	const to = Math.min( errorLine + 6, lines.length );

	for ( let i = from; i < to; i ++ ) {

		const line = i + 1;
		lines2.push( `${line === errorLine ? '>' : ' '} ${line}: ${lines[ i ]}` );

	}

	return lines2.join( '\n' );

}

const _m0 = /*@__PURE__*/ new Matrix3();

function getEncodingComponents( colorSpace ) {

	ColorManagement._getMatrix( _m0, ColorManagement.workingColorSpace, colorSpace );

	const encodingMatrix = `mat3( ${ _m0.elements.map( ( v ) => v.toFixed( 4 ) ) } )`;

	switch ( ColorManagement.getTransfer( colorSpace ) ) {

		case LinearTransfer:
			return [ encodingMatrix, 'LinearTransferOETF' ];

		case SRGBTransfer:
			return [ encodingMatrix, 'sRGBTransferOETF' ];

		default:
			warn( 'WebGLProgram: Unsupported color space: ', colorSpace );
			return [ encodingMatrix, 'LinearTransferOETF' ];

	}

}

function getShaderErrors( gl, shader, type ) {

	const status = gl.getShaderParameter( shader, gl.COMPILE_STATUS );

	const shaderInfoLog = gl.getShaderInfoLog( shader ) || '';
	const errors = shaderInfoLog.trim();

	if ( status && errors === '' ) return '';

	const errorMatches = /ERROR: 0:(\d+)/.exec( errors );
	if ( errorMatches ) {

		// --enable-privileged-webgl-extension
		// log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) );

		const errorLine = parseInt( errorMatches[ 1 ] );
		return type.toUpperCase() + '\n\n' + errors + '\n\n' + handleSource( gl.getShaderSource( shader ), errorLine );

	} else {

		return errors;

	}

}

function getTexelEncodingFunction( functionName, colorSpace ) {

	const components = getEncodingComponents( colorSpace );

	return [

		`vec4 ${functionName}( vec4 value ) {`,

		`	return ${components[ 1 ]}( vec4( value.rgb * ${components[ 0 ]}, value.a ) );`,

		'}',

	].join( '\n' );

}

const toneMappingFunctions = {
	[ LinearToneMapping ]: 'Linear',
	[ ReinhardToneMapping ]: 'Reinhard',
	[ CineonToneMapping ]: 'Cineon',
	[ ACESFilmicToneMapping ]: 'ACESFilmic',
	[ AgXToneMapping ]: 'AgX',
	[ NeutralToneMapping ]: 'Neutral',
	[ CustomToneMapping ]: 'Custom'
};

function getToneMappingFunction( functionName, toneMapping ) {

	const toneMappingName = toneMappingFunctions[ toneMapping ];

	if ( toneMappingName === undefined ) {

		warn( 'WebGLProgram: Unsupported toneMapping:', toneMapping );
		return 'vec3 ' + functionName + '( vec3 color ) { return LinearToneMapping( color ); }';

	}

	return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }';

}

const _v0 = /*@__PURE__*/ new Vector3();

function getLuminanceFunction() {

	ColorManagement.getLuminanceCoefficients( _v0 );

	const r = _v0.x.toFixed( 4 );
	const g = _v0.y.toFixed( 4 );
	const b = _v0.z.toFixed( 4 );

	return [

		'float luminance( const in vec3 rgb ) {',

		`	const vec3 weights = vec3( ${ r }, ${ g }, ${ b } );`,

		'	return dot( weights, rgb );',

		'}'

	].join( '\n' );

}

function generateVertexExtensions( parameters ) {

	const chunks = [
		parameters.extensionClipCullDistance ? '#extension GL_ANGLE_clip_cull_distance : require' : '',
		parameters.extensionMultiDraw ? '#extension GL_ANGLE_multi_draw : require' : '',
	];

	return chunks.filter( filterEmptyLine ).join( '\n' );

}

function generateDefines( defines ) {

	const chunks = [];

	for ( const name in defines ) {

		const value = defines[ name ];

		if ( value === false ) continue;

		chunks.push( '#define ' + name + ' ' + value );

	}

	return chunks.join( '\n' );

}

function fetchAttributeLocations( gl, program ) {

	const attributes = {};

	const n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES );

	for ( let i = 0; i < n; i ++ ) {

		const info = gl.getActiveAttrib( program, i );
		const name = info.name;

		let locationSize = 1;
		if ( info.type === gl.FLOAT_MAT2 ) locationSize = 2;
		if ( info.type === gl.FLOAT_MAT3 ) locationSize = 3;
		if ( info.type === gl.FLOAT_MAT4 ) locationSize = 4;

		//