<!-- NEW4 — TEST 4 · THE QUILT · part 360 -->
ene.height !== height ) {

				this.setSize( width, height );

			}

		}

		// if the first effect is a RenderPass, it renders the scene itself (see end())
		if ( _hasRenderPass === false ) {

			renderer.setRenderTarget( targetScene );

		}

		// disable tone mapping during render - it will be applied in end()
		_savedToneMapping = renderer.toneMapping;
		renderer.toneMapping = NoToneMapping;

		return true;

	};

	this.hasRenderPass = function () {

		return _hasRenderPass;

	};

	this.end = function ( renderer, deltaTime ) {

		// restore tone mapping
		renderer.toneMapping = _savedToneMapping;

		_isCompositing = true;

		// run post-processing effects - the scene lives in the multisampled targetScene,
		// effects ping-pong between the single-sampled targetA and targetB
		let readBuffer = targetScene;
		let writeBuffer = targetA;

		for ( let i = 0; i < _effects.length; i ++ ) {

			const effect = _effects[ i ];

			if ( effect.enabled === false ) continue;

			effect.render( renderer, writeBuffer, readBuffer, deltaTime );

			if ( effect.needsSwap !== false ) {

				// keep targetScene out of the rotation so only the scene pass is multisampled
				readBuffer = writeBuffer;
				writeBuffer = ( writeBuffer === targetA ) ? targetB : targetA;

			}

		}

		// update output material defines if settings changed
		if ( _outputColorSpace !== renderer.outputColorSpace || _outputToneMapping !== renderer.toneMapping ) {

			_outputColorSpace = renderer.outputColorSpace;
			_outputToneMapping = renderer.toneMapping;

			material.defines = {};

			if ( ColorManagement.getTransfer( _outputColorSpace ) === SRGBTransfer ) material.defines.SRGB_TRANSFER = '';

			const toneMapping = toneMappingMap[ _outputToneMapping ];
			if ( toneMapping ) material.defines[ toneMapping ] = '';

			material.needsUpdate = true;

		}

		// final output to canvas (or XR render target)
		material.uniforms.tDiffuse.value = readBuffer.texture;
		renderer.setRenderTarget( _savedRenderTarget );
		renderer.render( mesh, camera );

		_savedRenderTarget = null;
		_isCompositing = false;

	};

	this.isCompositing = function () {

		return _isCompositing;

	};

	this.dispose = function () {

		targetScene.dispose();

		if ( targetA !== null ) targetA.dispose();
		if ( targetB !== null ) targetB.dispose();

		geometry.dispose();
		material.dispose();

	};

}

/**
 * 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
 *
 */


const emptyTexture = /*@__PURE__*/ new Texture();

const emptyShadowTexture = /*@__PURE__*/ new DepthTexture( 1, 1 );

const emptyArrayTexture = /*@__PURE__*/ new DataArrayTexture();
const empty3dTexture = /*@__PURE__*/ new Data3DTexture();
const emptyCubeTexture = /*@__PURE__*/ 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[ 