1493 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			1493 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | import { WebGLCoordinateSystem } from 'three'; | ||
|  | 
 | ||
|  | import GLSLNodeBuilder from './nodes/GLSLNodeBuilder.js'; | ||
|  | import Backend from '../common/Backend.js'; | ||
|  | 
 | ||
|  | import WebGLAttributeUtils from './utils/WebGLAttributeUtils.js'; | ||
|  | import WebGLState from './utils/WebGLState.js'; | ||
|  | import WebGLUtils from './utils/WebGLUtils.js'; | ||
|  | import WebGLTextureUtils from './utils/WebGLTextureUtils.js'; | ||
|  | import WebGLExtensions from './utils/WebGLExtensions.js'; | ||
|  | import WebGLCapabilities from './utils/WebGLCapabilities.js'; | ||
|  | import { GLFeatureName } from './utils/WebGLConstants.js'; | ||
|  | import { WebGLBufferRenderer } from './WebGLBufferRenderer.js'; | ||
|  | 
 | ||
|  | //
 | ||
|  | 
 | ||
|  | class WebGLBackend extends Backend { | ||
|  | 
 | ||
|  | 	constructor( parameters = {} ) { | ||
|  | 
 | ||
|  | 		super( parameters ); | ||
|  | 
 | ||
|  | 		this.isWebGLBackend = true; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	init( renderer ) { | ||
|  | 
 | ||
|  | 		super.init( renderer ); | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		const parameters = this.parameters; | ||
|  | 
 | ||
|  | 		const glContext = ( parameters.context !== undefined ) ? parameters.context : renderer.domElement.getContext( 'webgl2' ); | ||
|  | 
 | ||
|  | 		this.gl = glContext; | ||
|  | 
 | ||
|  | 		this.extensions = new WebGLExtensions( this ); | ||
|  | 		this.capabilities = new WebGLCapabilities( this ); | ||
|  | 		this.attributeUtils = new WebGLAttributeUtils( this ); | ||
|  | 		this.textureUtils = new WebGLTextureUtils( this ); | ||
|  | 		this.bufferRenderer = new WebGLBufferRenderer( this ); | ||
|  | 
 | ||
|  | 		this.state = new WebGLState( this ); | ||
|  | 		this.utils = new WebGLUtils( this ); | ||
|  | 
 | ||
|  | 		this.vaoCache = {}; | ||
|  | 		this.transformFeedbackCache = {}; | ||
|  | 		this.discard = false; | ||
|  | 		this.trackTimestamp = ( parameters.trackTimestamp === true ); | ||
|  | 
 | ||
|  | 		this.extensions.get( 'EXT_color_buffer_float' ); | ||
|  | 		this.disjoint = this.extensions.get( 'EXT_disjoint_timer_query_webgl2' ); | ||
|  | 		this.parallel = this.extensions.get( 'KHR_parallel_shader_compile' ); | ||
|  | 		this._currentContext = null; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	get coordinateSystem() { | ||
|  | 
 | ||
|  | 		return WebGLCoordinateSystem; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	async getArrayBufferAsync( attribute ) { | ||
|  | 
 | ||
|  | 		return await this.attributeUtils.getArrayBufferAsync( attribute ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | 	initTimestampQuery( renderContext ) { | ||
|  | 
 | ||
|  | 		if ( ! this.disjoint || ! this.trackTimestamp ) return; | ||
|  | 
 | ||
|  | 		const renderContextData = this.get( renderContext ); | ||
|  | 
 | ||
|  | 		if ( this.queryRunning ) { | ||
|  | 
 | ||
|  | 		  if ( ! renderContextData.queryQueue ) renderContextData.queryQueue = []; | ||
|  | 		  renderContextData.queryQueue.push( renderContext ); | ||
|  | 		  return; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if ( renderContextData.activeQuery ) { | ||
|  | 
 | ||
|  | 		  this.gl.endQuery( this.disjoint.TIME_ELAPSED_EXT ); | ||
|  | 		  renderContextData.activeQuery = null; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		renderContextData.activeQuery = this.gl.createQuery(); | ||
|  | 
 | ||
|  | 		if ( renderContextData.activeQuery !== null ) { | ||
|  | 
 | ||
|  | 		  this.gl.beginQuery( this.disjoint.TIME_ELAPSED_EXT, renderContextData.activeQuery ); | ||
|  | 		  this.queryRunning = true; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// timestamp utils
 | ||
|  | 
 | ||
|  | 	  prepareTimestampBuffer( renderContext ) { | ||
|  | 
 | ||
|  | 		if ( ! this.disjoint || ! this.trackTimestamp ) return; | ||
|  | 
 | ||
|  | 		const renderContextData = this.get( renderContext ); | ||
|  | 
 | ||
|  | 		if ( renderContextData.activeQuery ) { | ||
|  | 
 | ||
|  | 		  this.gl.endQuery( this.disjoint.TIME_ELAPSED_EXT ); | ||
|  | 
 | ||
|  | 		  if ( ! renderContextData.gpuQueries ) renderContextData.gpuQueries = []; | ||
|  | 		  renderContextData.gpuQueries.push( { query: renderContextData.activeQuery } ); | ||
|  | 		  renderContextData.activeQuery = null; | ||
|  | 		  this.queryRunning = false; | ||
|  | 
 | ||
|  | 		  if ( renderContextData.queryQueue && renderContextData.queryQueue.length > 0 ) { | ||
|  | 
 | ||
|  | 				const nextRenderContext = renderContextData.queryQueue.shift(); | ||
|  | 				this.initTimestampQuery( nextRenderContext ); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	  async resolveTimestampAsync( renderContext, type = 'render' ) { | ||
|  | 
 | ||
|  | 		if ( ! this.disjoint || ! this.trackTimestamp ) return; | ||
|  | 
 | ||
|  | 		const renderContextData = this.get( renderContext ); | ||
|  | 
 | ||
|  | 		if ( ! renderContextData.gpuQueries ) renderContextData.gpuQueries = []; | ||
|  | 
 | ||
|  | 		for ( let i = 0; i < renderContextData.gpuQueries.length; i ++ ) { | ||
|  | 
 | ||
|  | 		  const queryInfo = renderContextData.gpuQueries[ i ]; | ||
|  | 		  const available = this.gl.getQueryParameter( queryInfo.query, this.gl.QUERY_RESULT_AVAILABLE ); | ||
|  | 		  const disjoint = this.gl.getParameter( this.disjoint.GPU_DISJOINT_EXT ); | ||
|  | 
 | ||
|  | 		  if ( available && ! disjoint ) { | ||
|  | 
 | ||
|  | 				const elapsed = this.gl.getQueryParameter( queryInfo.query, this.gl.QUERY_RESULT ); | ||
|  | 				const duration = Number( elapsed ) / 1000000; // Convert nanoseconds to milliseconds
 | ||
|  | 				this.gl.deleteQuery( queryInfo.query ); | ||
|  | 				renderContextData.gpuQueries.splice( i, 1 ); // Remove the processed query
 | ||
|  | 				i --; | ||
|  | 				this.renderer.info.updateTimestamp( type, duration ); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	getContext() { | ||
|  | 
 | ||
|  | 		return this.gl; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	beginRender( renderContext ) { | ||
|  | 
 | ||
|  | 		const { gl } = this; | ||
|  | 		const renderContextData = this.get( renderContext ); | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		this.initTimestampQuery( renderContext ); | ||
|  | 
 | ||
|  | 		renderContextData.previousContext = this._currentContext; | ||
|  | 		this._currentContext = renderContext; | ||
|  | 
 | ||
|  | 		this._setFramebuffer( renderContext ); | ||
|  | 
 | ||
|  | 		this.clear( renderContext.clearColor, renderContext.clearDepth, renderContext.clearStencil, renderContext, false ); | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 		if ( renderContext.viewport ) { | ||
|  | 
 | ||
|  | 			this.updateViewport( renderContext ); | ||
|  | 
 | ||
|  | 		} else { | ||
|  | 
 | ||
|  | 			gl.viewport( 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if ( renderContext.scissor ) { | ||
|  | 
 | ||
|  | 			const { x, y, width, height } = renderContext.scissorValue; | ||
|  | 
 | ||
|  | 			gl.scissor( x, y, width, height ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		const occlusionQueryCount = renderContext.occlusionQueryCount; | ||
|  | 
 | ||
|  | 		if ( occlusionQueryCount > 0 ) { | ||
|  | 
 | ||
|  | 			// Get a reference to the array of objects with queries. The renderContextData property
 | ||
|  | 			// can be changed by another render pass before the async reading of all previous queries complete
 | ||
|  | 			renderContextData.currentOcclusionQueries = renderContextData.occlusionQueries; | ||
|  | 			renderContextData.currentOcclusionQueryObjects = renderContextData.occlusionQueryObjects; | ||
|  | 
 | ||
|  | 			renderContextData.lastOcclusionObject = null; | ||
|  | 			renderContextData.occlusionQueries = new Array( occlusionQueryCount ); | ||
|  | 			renderContextData.occlusionQueryObjects = new Array( occlusionQueryCount ); | ||
|  | 			renderContextData.occlusionQueryIndex = 0; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	finishRender( renderContext ) { | ||
|  | 
 | ||
|  | 		const { gl, state } = this; | ||
|  | 		const renderContextData = this.get( renderContext ); | ||
|  | 		const previousContext = renderContextData.previousContext; | ||
|  | 
 | ||
|  | 		const textures = renderContext.textures; | ||
|  | 
 | ||
|  | 		if ( textures !== null ) { | ||
|  | 
 | ||
|  | 			for ( let i = 0; i < textures.length; i ++ ) { | ||
|  | 
 | ||
|  | 				const texture = textures[ i ]; | ||
|  | 
 | ||
|  | 				if ( texture.generateMipmaps ) { | ||
|  | 
 | ||
|  | 					this.generateMipmaps( texture ); | ||
|  | 
 | ||
|  | 				} | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		this._currentContext = previousContext; | ||
|  | 
 | ||
|  | 
 | ||
|  | 		if ( renderContext.textures !== null && renderContext.renderTarget ) { | ||
|  | 
 | ||
|  | 			const renderTargetContextData = this.get( renderContext.renderTarget ); | ||
|  | 
 | ||
|  | 			const { samples } = renderContext.renderTarget; | ||
|  | 			const fb = renderTargetContextData.framebuffer; | ||
|  | 
 | ||
|  | 			const mask = gl.COLOR_BUFFER_BIT; | ||
|  | 
 | ||
|  | 			if ( samples > 0 ) { | ||
|  | 
 | ||
|  | 				const msaaFrameBuffer = renderTargetContextData.msaaFrameBuffer; | ||
|  | 
 | ||
|  | 				const textures = renderContext.textures; | ||
|  | 
 | ||
|  | 				state.bindFramebuffer( gl.READ_FRAMEBUFFER, msaaFrameBuffer ); | ||
|  | 				state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb ); | ||
|  | 
 | ||
|  | 				for ( let i = 0; i < textures.length; i ++ ) { | ||
|  | 
 | ||
|  | 					// TODO Add support for MRT
 | ||
|  | 
 | ||
|  | 					gl.blitFramebuffer( 0, 0, renderContext.width, renderContext.height, 0, 0, renderContext.width, renderContext.height, mask, gl.NEAREST ); | ||
|  | 
 | ||
|  | 					gl.invalidateFramebuffer( gl.READ_FRAMEBUFFER, renderTargetContextData.invalidationArray ); | ||
|  | 
 | ||
|  | 				} | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if ( previousContext !== null ) { | ||
|  | 
 | ||
|  | 			this._setFramebuffer( previousContext ); | ||
|  | 
 | ||
|  | 			if ( previousContext.viewport ) { | ||
|  | 
 | ||
|  | 				this.updateViewport( previousContext ); | ||
|  | 
 | ||
|  | 			} else { | ||
|  | 
 | ||
|  | 				const gl = this.gl; | ||
|  | 
 | ||
|  | 				gl.viewport( 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight ); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		const occlusionQueryCount = renderContext.occlusionQueryCount; | ||
|  | 
 | ||
|  | 		if ( occlusionQueryCount > 0 ) { | ||
|  | 
 | ||
|  | 			const renderContextData = this.get( renderContext ); | ||
|  | 
 | ||
|  | 			if ( occlusionQueryCount > renderContextData.occlusionQueryIndex ) { | ||
|  | 
 | ||
|  | 				const { gl } = this; | ||
|  | 
 | ||
|  | 				gl.endQuery( gl.ANY_SAMPLES_PASSED ); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 			this.resolveOccludedAsync( renderContext ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		this.prepareTimestampBuffer( renderContext ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	resolveOccludedAsync( renderContext ) { | ||
|  | 
 | ||
|  | 		const renderContextData = this.get( renderContext ); | ||
|  | 
 | ||
|  | 		// handle occlusion query results
 | ||
|  | 
 | ||
|  | 		const { currentOcclusionQueries, currentOcclusionQueryObjects } = renderContextData; | ||
|  | 
 | ||
|  | 		if ( currentOcclusionQueries && currentOcclusionQueryObjects ) { | ||
|  | 
 | ||
|  | 			const occluded = new WeakSet(); | ||
|  | 			const { gl } = this; | ||
|  | 
 | ||
|  | 			renderContextData.currentOcclusionQueryObjects = null; | ||
|  | 			renderContextData.currentOcclusionQueries = null; | ||
|  | 
 | ||
|  | 			const check = () => { | ||
|  | 
 | ||
|  | 				let completed = 0; | ||
|  | 
 | ||
|  | 				// check all queries and requeue as appropriate
 | ||
|  | 				for ( let i = 0; i < currentOcclusionQueries.length; i ++ ) { | ||
|  | 
 | ||
|  | 					const query = currentOcclusionQueries[ i ]; | ||
|  | 
 | ||
|  | 					if ( query === null ) continue; | ||
|  | 
 | ||
|  | 					if ( gl.getQueryParameter( query, gl.QUERY_RESULT_AVAILABLE ) ) { | ||
|  | 
 | ||
|  | 						if ( gl.getQueryParameter( query, gl.QUERY_RESULT ) > 0 ) occluded.add( currentOcclusionQueryObjects[ i ] ); | ||
|  | 
 | ||
|  | 						currentOcclusionQueries[ i ] = null; | ||
|  | 						gl.deleteQuery( query ); | ||
|  | 
 | ||
|  | 						completed ++; | ||
|  | 
 | ||
|  | 					} | ||
|  | 
 | ||
|  | 				} | ||
|  | 
 | ||
|  | 				if ( completed < currentOcclusionQueries.length ) { | ||
|  | 
 | ||
|  | 					requestAnimationFrame( check ); | ||
|  | 
 | ||
|  | 				} else { | ||
|  | 
 | ||
|  | 					renderContextData.occluded = occluded; | ||
|  | 
 | ||
|  | 				} | ||
|  | 
 | ||
|  | 			}; | ||
|  | 
 | ||
|  | 			check(); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	isOccluded( renderContext, object ) { | ||
|  | 
 | ||
|  | 		const renderContextData = this.get( renderContext ); | ||
|  | 
 | ||
|  | 		return renderContextData.occluded && renderContextData.occluded.has( object ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	updateViewport( renderContext ) { | ||
|  | 
 | ||
|  | 		const gl = this.gl; | ||
|  | 		const { x, y, width, height } = renderContext.viewportValue; | ||
|  | 
 | ||
|  | 		gl.viewport( x, y, width, height ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	setScissorTest( boolean ) { | ||
|  | 
 | ||
|  | 		const gl = this.gl; | ||
|  | 
 | ||
|  | 		if ( boolean ) { | ||
|  | 
 | ||
|  | 			gl.enable( gl.SCISSOR_TEST ); | ||
|  | 
 | ||
|  | 		} else { | ||
|  | 
 | ||
|  | 			gl.disable( gl.SCISSOR_TEST ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	clear( color, depth, stencil, descriptor = null, setFrameBuffer = true ) { | ||
|  | 
 | ||
|  | 		const { gl } = this; | ||
|  | 
 | ||
|  | 		if ( descriptor === null ) { | ||
|  | 
 | ||
|  | 			descriptor = { | ||
|  | 				textures: null, | ||
|  | 				clearColorValue: this.getClearColor() | ||
|  | 			}; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		let clear = 0; | ||
|  | 
 | ||
|  | 		if ( color ) clear |= gl.COLOR_BUFFER_BIT; | ||
|  | 		if ( depth ) clear |= gl.DEPTH_BUFFER_BIT; | ||
|  | 		if ( stencil ) clear |= gl.STENCIL_BUFFER_BIT; | ||
|  | 
 | ||
|  | 		if ( clear !== 0 ) { | ||
|  | 
 | ||
|  | 			const clearColor = descriptor.clearColorValue || this.getClearColor(); | ||
|  | 
 | ||
|  | 			if ( depth ) this.state.setDepthMask( true ); | ||
|  | 
 | ||
|  | 			if ( descriptor.textures === null ) { | ||
|  | 
 | ||
|  | 				gl.clearColor( clearColor.r, clearColor.g, clearColor.b, clearColor.a ); | ||
|  | 				gl.clear( clear ); | ||
|  | 
 | ||
|  | 			} else { | ||
|  | 
 | ||
|  | 				if ( setFrameBuffer ) this._setFramebuffer( descriptor ); | ||
|  | 
 | ||
|  | 				if ( color ) { | ||
|  | 
 | ||
|  | 					for ( let i = 0; i < descriptor.textures.length; i ++ ) { | ||
|  | 
 | ||
|  | 						gl.clearBufferfv( gl.COLOR, i, [ clearColor.r, clearColor.g, clearColor.b, clearColor.a ] ); | ||
|  | 
 | ||
|  | 					} | ||
|  | 
 | ||
|  | 				} | ||
|  | 
 | ||
|  | 				if ( depth && stencil ) { | ||
|  | 
 | ||
|  | 					gl.clearBufferfi( gl.DEPTH_STENCIL, 0, 1, 0 ); | ||
|  | 
 | ||
|  | 				} else if ( depth ) { | ||
|  | 
 | ||
|  | 					gl.clearBufferfv( gl.DEPTH, 0, [ 1.0 ] ); | ||
|  | 
 | ||
|  | 				} else if ( stencil ) { | ||
|  | 
 | ||
|  | 					gl.clearBufferiv( gl.STENCIL, 0, [ 0 ] ); | ||
|  | 
 | ||
|  | 				} | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	beginCompute( computeGroup ) { | ||
|  | 
 | ||
|  | 		const gl = this.gl; | ||
|  | 
 | ||
|  | 		gl.bindFramebuffer( gl.FRAMEBUFFER, null ); | ||
|  | 		this.initTimestampQuery( computeGroup ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	compute( computeGroup, computeNode, bindings, pipeline ) { | ||
|  | 
 | ||
|  | 		const gl = this.gl; | ||
|  | 
 | ||
|  | 		if ( ! this.discard ) { | ||
|  | 
 | ||
|  | 			// required here to handle async behaviour of render.compute()
 | ||
|  | 			gl.enable( gl.RASTERIZER_DISCARD ); | ||
|  | 			this.discard = true; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		const { programGPU, transformBuffers, attributes } = this.get( pipeline ); | ||
|  | 
 | ||
|  | 		const vaoKey = this._getVaoKey( null, attributes ); | ||
|  | 
 | ||
|  | 		const vaoGPU = this.vaoCache[ vaoKey ]; | ||
|  | 
 | ||
|  | 		if ( vaoGPU === undefined ) { | ||
|  | 
 | ||
|  | 			this._createVao( null, attributes ); | ||
|  | 
 | ||
|  | 		} else { | ||
|  | 
 | ||
|  | 			gl.bindVertexArray( vaoGPU ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		gl.useProgram( programGPU ); | ||
|  | 
 | ||
|  | 		this._bindUniforms( bindings ); | ||
|  | 
 | ||
|  | 		const transformFeedbackGPU = this._getTransformFeedback( transformBuffers ); | ||
|  | 
 | ||
|  | 		gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, transformFeedbackGPU ); | ||
|  | 		gl.beginTransformFeedback( gl.POINTS ); | ||
|  | 
 | ||
|  | 		if ( attributes[ 0 ].isStorageInstancedBufferAttribute ) { | ||
|  | 
 | ||
|  | 			gl.drawArraysInstanced( gl.POINTS, 0, 1, computeNode.count ); | ||
|  | 
 | ||
|  | 		} else { | ||
|  | 
 | ||
|  | 			gl.drawArrays( gl.POINTS, 0, computeNode.count ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		gl.endTransformFeedback(); | ||
|  | 		gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, null ); | ||
|  | 
 | ||
|  | 		// switch active buffers
 | ||
|  | 
 | ||
|  | 		for ( let i = 0; i < transformBuffers.length; i ++ ) { | ||
|  | 
 | ||
|  | 			const dualAttributeData = transformBuffers[ i ]; | ||
|  | 
 | ||
|  | 			if ( dualAttributeData.pbo ) { | ||
|  | 
 | ||
|  | 				this.textureUtils.copyBufferToTexture( dualAttributeData.transformBuffer, dualAttributeData.pbo ); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 			dualAttributeData.switchBuffers(); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	finishCompute( computeGroup ) { | ||
|  | 
 | ||
|  | 		const gl = this.gl; | ||
|  | 
 | ||
|  | 		this.discard = false; | ||
|  | 
 | ||
|  | 		gl.disable( gl.RASTERIZER_DISCARD ); | ||
|  | 
 | ||
|  | 		this.prepareTimestampBuffer( computeGroup ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	draw( renderObject, info ) { | ||
|  | 
 | ||
|  | 		const { object, pipeline, material, context } = renderObject; | ||
|  | 		const { programGPU } = this.get( pipeline ); | ||
|  | 
 | ||
|  | 		const { gl, state } = this; | ||
|  | 
 | ||
|  | 		const contextData = this.get( context ); | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		this._bindUniforms( renderObject.getBindings() ); | ||
|  | 
 | ||
|  | 		const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); | ||
|  | 
 | ||
|  | 		state.setMaterial( material, frontFaceCW ); | ||
|  | 
 | ||
|  | 		gl.useProgram( programGPU ); | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		let vaoGPU = renderObject.staticVao; | ||
|  | 
 | ||
|  | 		if ( vaoGPU === undefined ) { | ||
|  | 
 | ||
|  | 			const vaoKey = this._getVaoKey( renderObject.getIndex(), renderObject.getAttributes() ); | ||
|  | 
 | ||
|  | 			vaoGPU = this.vaoCache[ vaoKey ]; | ||
|  | 
 | ||
|  | 			if ( vaoGPU === undefined ) { | ||
|  | 
 | ||
|  | 				let staticVao; | ||
|  | 
 | ||
|  | 				( { vaoGPU, staticVao } = this._createVao( renderObject.getIndex(), renderObject.getAttributes() ) ); | ||
|  | 
 | ||
|  | 				if ( staticVao ) renderObject.staticVao = vaoGPU; | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		gl.bindVertexArray( vaoGPU ); | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		const index = renderObject.getIndex(); | ||
|  | 
 | ||
|  | 		const geometry = renderObject.geometry; | ||
|  | 		const drawRange = renderObject.drawRange; | ||
|  | 		const firstVertex = drawRange.start; | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		const lastObject = contextData.lastOcclusionObject; | ||
|  | 
 | ||
|  | 		if ( lastObject !== object && lastObject !== undefined ) { | ||
|  | 
 | ||
|  | 			if ( lastObject !== null && lastObject.occlusionTest === true ) { | ||
|  | 
 | ||
|  | 				gl.endQuery( gl.ANY_SAMPLES_PASSED ); | ||
|  | 
 | ||
|  | 				contextData.occlusionQueryIndex ++; | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if ( object.occlusionTest === true ) { | ||
|  | 
 | ||
|  | 				const query = gl.createQuery(); | ||
|  | 
 | ||
|  | 				gl.beginQuery( gl.ANY_SAMPLES_PASSED, query ); | ||
|  | 
 | ||
|  | 				contextData.occlusionQueries[ contextData.occlusionQueryIndex ] = query; | ||
|  | 				contextData.occlusionQueryObjects[ contextData.occlusionQueryIndex ] = object; | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 			contextData.lastOcclusionObject = object; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		const renderer = this.bufferRenderer; | ||
|  | 
 | ||
|  | 		if ( object.isPoints ) renderer.mode = gl.POINTS; | ||
|  | 		else if ( object.isLineSegments ) renderer.mode = gl.LINES; | ||
|  | 		else if ( object.isLine ) renderer.mode = gl.LINE_STRIP; | ||
|  | 		else if ( object.isLineLoop ) renderer.mode = gl.LINE_LOOP; | ||
|  | 		else { | ||
|  | 
 | ||
|  | 			if ( material.wireframe === true ) { | ||
|  | 
 | ||
|  | 				state.setLineWidth( material.wireframeLinewidth * this.renderer.getPixelRatio() ); | ||
|  | 				renderer.mode = gl.LINES; | ||
|  | 
 | ||
|  | 			} else { | ||
|  | 
 | ||
|  | 				renderer.mode = gl.TRIANGLES; | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 		let count; | ||
|  | 
 | ||
|  | 		renderer.object = object; | ||
|  | 
 | ||
|  | 		if ( index !== null ) { | ||
|  | 
 | ||
|  | 			const indexData = this.get( index ); | ||
|  | 			const indexCount = ( drawRange.count !== Infinity ) ? drawRange.count : index.count; | ||
|  | 
 | ||
|  | 			renderer.index = index.count; | ||
|  | 			renderer.type = indexData.type; | ||
|  | 
 | ||
|  | 			count = indexCount; | ||
|  | 
 | ||
|  | 		} else { | ||
|  | 
 | ||
|  | 			renderer.index = 0; | ||
|  | 
 | ||
|  | 			const vertexCount = ( drawRange.count !== Infinity ) ? drawRange.count : geometry.attributes.position.count; | ||
|  | 
 | ||
|  | 			count = vertexCount; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		const instanceCount = this.getInstanceCount( renderObject ); | ||
|  | 
 | ||
|  | 		if ( object.isBatchedMesh ) { | ||
|  | 
 | ||
|  | 			if ( object._multiDrawInstances !== null ) { | ||
|  | 
 | ||
|  | 				renderer.renderMultiDrawInstances( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount, object._multiDrawInstances ); | ||
|  | 
 | ||
|  | 			} else { | ||
|  | 
 | ||
|  | 				renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount ); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} else if ( instanceCount > 1 ) { | ||
|  | 
 | ||
|  | 			renderer.renderInstances( firstVertex, count, instanceCount ); | ||
|  | 
 | ||
|  | 		} else { | ||
|  | 
 | ||
|  | 			renderer.render( firstVertex, count ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		gl.bindVertexArray( null ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	needsRenderUpdate( /*renderObject*/ ) { | ||
|  | 
 | ||
|  | 		return false; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	getRenderCacheKey( renderObject ) { | ||
|  | 
 | ||
|  | 		return renderObject.id; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// textures
 | ||
|  | 
 | ||
|  | 	createDefaultTexture( texture ) { | ||
|  | 
 | ||
|  | 		this.textureUtils.createDefaultTexture( texture ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	createTexture( texture, options ) { | ||
|  | 
 | ||
|  | 		this.textureUtils.createTexture( texture, options ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	updateTexture( texture, options ) { | ||
|  | 
 | ||
|  | 		this.textureUtils.updateTexture( texture, options ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	generateMipmaps( texture ) { | ||
|  | 
 | ||
|  | 		this.textureUtils.generateMipmaps( texture ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | 	destroyTexture( texture ) { | ||
|  | 
 | ||
|  | 		this.textureUtils.destroyTexture( texture ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	copyTextureToBuffer( texture, x, y, width, height ) { | ||
|  | 
 | ||
|  | 		return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	createSampler( /*texture*/ ) { | ||
|  | 
 | ||
|  | 		//console.warn( 'Abstract class.' );
 | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	destroySampler() {} | ||
|  | 
 | ||
|  | 	// node builder
 | ||
|  | 
 | ||
|  | 	createNodeBuilder( object, renderer, scene = null ) { | ||
|  | 
 | ||
|  | 		return new GLSLNodeBuilder( object, renderer, scene ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// program
 | ||
|  | 
 | ||
|  | 	createProgram( program ) { | ||
|  | 
 | ||
|  | 		const gl = this.gl; | ||
|  | 		const { stage, code } = program; | ||
|  | 
 | ||
|  | 		const shader = stage === 'fragment' ? gl.createShader( gl.FRAGMENT_SHADER ) : gl.createShader( gl.VERTEX_SHADER ); | ||
|  | 
 | ||
|  | 		gl.shaderSource( shader, code ); | ||
|  | 		gl.compileShader( shader ); | ||
|  | 
 | ||
|  | 		this.set( program, { | ||
|  | 			shaderGPU: shader | ||
|  | 		} ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	destroyProgram( /*program*/ ) { | ||
|  | 
 | ||
|  | 		console.warn( 'Abstract class.' ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	createRenderPipeline( renderObject, promises ) { | ||
|  | 
 | ||
|  | 		const gl = this.gl; | ||
|  | 		const pipeline = renderObject.pipeline; | ||
|  | 
 | ||
|  | 		// Program
 | ||
|  | 
 | ||
|  | 		const { fragmentProgram, vertexProgram } = pipeline; | ||
|  | 
 | ||
|  | 		const programGPU = gl.createProgram(); | ||
|  | 
 | ||
|  | 		const fragmentShader = this.get( fragmentProgram ).shaderGPU; | ||
|  | 		const vertexShader = this.get( vertexProgram ).shaderGPU; | ||
|  | 
 | ||
|  | 		gl.attachShader( programGPU, fragmentShader ); | ||
|  | 		gl.attachShader( programGPU, vertexShader ); | ||
|  | 		gl.linkProgram( programGPU ); | ||
|  | 
 | ||
|  | 		this.set( pipeline, { | ||
|  | 			programGPU, | ||
|  | 			fragmentShader, | ||
|  | 			vertexShader | ||
|  | 		} ); | ||
|  | 
 | ||
|  | 		if ( promises !== null && this.parallel ) { | ||
|  | 
 | ||
|  | 			const p = new Promise( ( resolve /*, reject*/ ) => { | ||
|  | 
 | ||
|  | 				const parallel = this.parallel; | ||
|  | 				const checkStatus = () => { | ||
|  | 
 | ||
|  | 					if ( gl.getProgramParameter( programGPU, parallel.COMPLETION_STATUS_KHR ) ) { | ||
|  | 
 | ||
|  | 						this._completeCompile( renderObject, pipeline ); | ||
|  | 						resolve(); | ||
|  | 
 | ||
|  | 					} else { | ||
|  | 
 | ||
|  | 						requestAnimationFrame( checkStatus ); | ||
|  | 
 | ||
|  | 					} | ||
|  | 
 | ||
|  | 				}; | ||
|  | 
 | ||
|  | 				checkStatus(); | ||
|  | 
 | ||
|  | 			} ); | ||
|  | 
 | ||
|  | 			promises.push( p ); | ||
|  | 
 | ||
|  | 			return; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		this._completeCompile( renderObject, pipeline ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	_completeCompile( renderObject, pipeline ) { | ||
|  | 
 | ||
|  | 		const gl = this.gl; | ||
|  | 		const pipelineData = this.get( pipeline ); | ||
|  | 		const { programGPU, fragmentShader, vertexShader } = pipelineData; | ||
|  | 
 | ||
|  | 		if ( gl.getProgramParameter( programGPU, gl.LINK_STATUS ) === false ) { | ||
|  | 
 | ||
|  | 			console.error( 'THREE.WebGLBackend:', gl.getProgramInfoLog( programGPU ) ); | ||
|  | 
 | ||
|  | 			console.error( 'THREE.WebGLBackend:', gl.getShaderInfoLog( fragmentShader ) ); | ||
|  | 			console.error( 'THREE.WebGLBackend:', gl.getShaderInfoLog( vertexShader ) ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		gl.useProgram( programGPU ); | ||
|  | 
 | ||
|  | 		// Bindings
 | ||
|  | 
 | ||
|  | 		this._setupBindings( renderObject.getBindings(), programGPU ); | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		this.set( pipeline, { | ||
|  | 			programGPU | ||
|  | 		} ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	createComputePipeline( computePipeline, bindings ) { | ||
|  | 
 | ||
|  | 		const gl = this.gl; | ||
|  | 
 | ||
|  | 		// Program
 | ||
|  | 
 | ||
|  | 		const fragmentProgram = { | ||
|  | 			stage: 'fragment', | ||
|  | 			code: '#version 300 es\nprecision highp float;\nvoid main() {}' | ||
|  | 		}; | ||
|  | 
 | ||
|  | 		this.createProgram( fragmentProgram ); | ||
|  | 
 | ||
|  | 		const { computeProgram } = computePipeline; | ||
|  | 
 | ||
|  | 		const programGPU = gl.createProgram(); | ||
|  | 
 | ||
|  | 		const fragmentShader = this.get( fragmentProgram ).shaderGPU; | ||
|  | 		const vertexShader = this.get( computeProgram ).shaderGPU; | ||
|  | 
 | ||
|  | 		const transforms = computeProgram.transforms; | ||
|  | 
 | ||
|  | 		const transformVaryingNames = []; | ||
|  | 		const transformAttributeNodes = []; | ||
|  | 
 | ||
|  | 		for ( let i = 0; i < transforms.length; i ++ ) { | ||
|  | 
 | ||
|  | 			const transform = transforms[ i ]; | ||
|  | 
 | ||
|  | 			transformVaryingNames.push( transform.varyingName ); | ||
|  | 			transformAttributeNodes.push( transform.attributeNode ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		gl.attachShader( programGPU, fragmentShader ); | ||
|  | 		gl.attachShader( programGPU, vertexShader ); | ||
|  | 
 | ||
|  | 		gl.transformFeedbackVaryings( | ||
|  | 			programGPU, | ||
|  | 			transformVaryingNames, | ||
|  | 			gl.SEPARATE_ATTRIBS, | ||
|  | 		); | ||
|  | 
 | ||
|  | 		gl.linkProgram( programGPU ); | ||
|  | 
 | ||
|  | 		if ( gl.getProgramParameter( programGPU, gl.LINK_STATUS ) === false ) { | ||
|  | 
 | ||
|  | 			console.error( 'THREE.WebGLBackend:', gl.getProgramInfoLog( programGPU ) ); | ||
|  | 
 | ||
|  | 			console.error( 'THREE.WebGLBackend:', gl.getShaderInfoLog( fragmentShader ) ); | ||
|  | 			console.error( 'THREE.WebGLBackend:', gl.getShaderInfoLog( vertexShader ) ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		gl.useProgram( programGPU ); | ||
|  | 
 | ||
|  | 		// Bindings
 | ||
|  | 
 | ||
|  | 		this.createBindings( bindings ); | ||
|  | 
 | ||
|  | 		this._setupBindings( bindings, programGPU ); | ||
|  | 
 | ||
|  | 		const attributeNodes = computeProgram.attributes; | ||
|  | 		const attributes = []; | ||
|  | 		const transformBuffers = []; | ||
|  | 
 | ||
|  | 		for ( let i = 0; i < attributeNodes.length; i ++ ) { | ||
|  | 
 | ||
|  | 			const attribute = attributeNodes[ i ].node.attribute; | ||
|  | 
 | ||
|  | 			attributes.push( attribute ); | ||
|  | 
 | ||
|  | 			if ( ! this.has( attribute ) ) this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		for ( let i = 0; i < transformAttributeNodes.length; i ++ ) { | ||
|  | 
 | ||
|  | 			const attribute = transformAttributeNodes[ i ].attribute; | ||
|  | 
 | ||
|  | 			if ( ! this.has( attribute ) ) this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER ); | ||
|  | 
 | ||
|  | 			const attributeData = this.get( attribute ); | ||
|  | 
 | ||
|  | 			transformBuffers.push( attributeData ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		this.set( computePipeline, { | ||
|  | 			programGPU, | ||
|  | 			transformBuffers, | ||
|  | 			attributes | ||
|  | 		} ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	createBindings( bindings ) { | ||
|  | 
 | ||
|  | 		this.updateBindings( bindings ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	updateBindings( bindings ) { | ||
|  | 
 | ||
|  | 		const { gl } = this; | ||
|  | 
 | ||
|  | 		let groupIndex = 0; | ||
|  | 		let textureIndex = 0; | ||
|  | 
 | ||
|  | 		for ( const binding of bindings ) { | ||
|  | 
 | ||
|  | 			if ( binding.isUniformsGroup || binding.isUniformBuffer ) { | ||
|  | 
 | ||
|  | 				const bufferGPU = gl.createBuffer(); | ||
|  | 				const data = binding.buffer; | ||
|  | 
 | ||
|  | 				gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU ); | ||
|  | 				gl.bufferData( gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW ); | ||
|  | 				gl.bindBufferBase( gl.UNIFORM_BUFFER, groupIndex, bufferGPU ); | ||
|  | 
 | ||
|  | 				this.set( binding, { | ||
|  | 					index: groupIndex ++, | ||
|  | 					bufferGPU | ||
|  | 				} ); | ||
|  | 
 | ||
|  | 			} else if ( binding.isSampledTexture ) { | ||
|  | 
 | ||
|  | 				const { textureGPU, glTextureType } = this.get( binding.texture ); | ||
|  | 
 | ||
|  | 				this.set( binding, { | ||
|  | 					index: textureIndex ++, | ||
|  | 					textureGPU, | ||
|  | 					glTextureType | ||
|  | 				} ); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	updateBinding( binding ) { | ||
|  | 
 | ||
|  | 		const gl = this.gl; | ||
|  | 
 | ||
|  | 		if ( binding.isUniformsGroup || binding.isUniformBuffer ) { | ||
|  | 
 | ||
|  | 			const bindingData = this.get( binding ); | ||
|  | 			const bufferGPU = bindingData.bufferGPU; | ||
|  | 			const data = binding.buffer; | ||
|  | 
 | ||
|  | 			gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU ); | ||
|  | 			gl.bufferData( gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// attributes
 | ||
|  | 
 | ||
|  | 	createIndexAttribute( attribute ) { | ||
|  | 
 | ||
|  | 		const gl = this.gl; | ||
|  | 
 | ||
|  | 		this.attributeUtils.createAttribute( attribute, gl.ELEMENT_ARRAY_BUFFER ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	createAttribute( attribute ) { | ||
|  | 
 | ||
|  | 		if ( this.has( attribute ) ) return; | ||
|  | 
 | ||
|  | 		const gl = this.gl; | ||
|  | 
 | ||
|  | 		this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	createStorageAttribute( attribute ) { | ||
|  | 
 | ||
|  | 		//console.warn( 'Abstract class.' );
 | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	updateAttribute( attribute ) { | ||
|  | 
 | ||
|  | 		this.attributeUtils.updateAttribute( attribute ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	destroyAttribute( attribute ) { | ||
|  | 
 | ||
|  | 		this.attributeUtils.destroyAttribute( attribute ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	updateSize() { | ||
|  | 
 | ||
|  | 		//console.warn( 'Abstract class.' );
 | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	hasFeature( name ) { | ||
|  | 
 | ||
|  | 		const keysMatching = Object.keys( GLFeatureName ).filter( key => GLFeatureName[ key ] === name ); | ||
|  | 
 | ||
|  | 		const extensions = this.extensions; | ||
|  | 
 | ||
|  | 		for ( let i = 0; i < keysMatching.length; i ++ ) { | ||
|  | 
 | ||
|  | 			if ( extensions.has( keysMatching[ i ] ) ) return true; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		return false; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | 	getMaxAnisotropy() { | ||
|  | 
 | ||
|  | 		return this.capabilities.getMaxAnisotropy(); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	copyTextureToTexture( position, srcTexture, dstTexture, level ) { | ||
|  | 
 | ||
|  | 		this.textureUtils.copyTextureToTexture( position, srcTexture, dstTexture, level ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	copyFramebufferToTexture( texture, renderContext ) { | ||
|  | 
 | ||
|  | 		this.textureUtils.copyFramebufferToTexture( texture, renderContext ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	_setFramebuffer( renderContext ) { | ||
|  | 
 | ||
|  | 		const { gl, state } = this; | ||
|  | 
 | ||
|  | 		let currentFrameBuffer = null; | ||
|  | 
 | ||
|  | 		if ( renderContext.textures !== null ) { | ||
|  | 
 | ||
|  | 			const renderTarget = renderContext.renderTarget; | ||
|  | 			const renderTargetContextData = this.get( renderTarget ); | ||
|  | 			const { samples, depthBuffer, stencilBuffer } = renderTarget; | ||
|  | 			const cubeFace = this.renderer._activeCubeFace; | ||
|  | 			const isCube = renderTarget.isWebGLCubeRenderTarget === true; | ||
|  | 
 | ||
|  | 			let msaaFb = renderTargetContextData.msaaFrameBuffer; | ||
|  | 			let depthRenderbuffer = renderTargetContextData.depthRenderbuffer; | ||
|  | 
 | ||
|  | 			let fb; | ||
|  | 
 | ||
|  | 			if ( isCube ) { | ||
|  | 
 | ||
|  | 				if ( renderTargetContextData.cubeFramebuffers === undefined ) { | ||
|  | 
 | ||
|  | 					renderTargetContextData.cubeFramebuffers = []; | ||
|  | 
 | ||
|  | 				} | ||
|  | 
 | ||
|  | 				fb = renderTargetContextData.cubeFramebuffers[ cubeFace ]; | ||
|  | 
 | ||
|  | 			} else { | ||
|  | 
 | ||
|  | 				fb = renderTargetContextData.framebuffer; | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if ( fb === undefined ) { | ||
|  | 
 | ||
|  | 				fb = gl.createFramebuffer(); | ||
|  | 
 | ||
|  | 				state.bindFramebuffer( gl.FRAMEBUFFER, fb ); | ||
|  | 
 | ||
|  | 				const textures = renderContext.textures; | ||
|  | 
 | ||
|  | 				if ( isCube ) { | ||
|  | 
 | ||
|  | 					renderTargetContextData.cubeFramebuffers[ cubeFace ] = fb; | ||
|  | 					const { textureGPU } = this.get( textures[ 0 ] ); | ||
|  | 
 | ||
|  | 					gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + cubeFace, textureGPU, 0 ); | ||
|  | 
 | ||
|  | 				} else { | ||
|  | 
 | ||
|  | 					for ( let i = 0; i < textures.length; i ++ ) { | ||
|  | 
 | ||
|  | 						const texture = textures[ i ]; | ||
|  | 						const textureData = this.get( texture ); | ||
|  | 						textureData.renderTarget = renderContext.renderTarget; | ||
|  | 
 | ||
|  | 						const attachment = gl.COLOR_ATTACHMENT0 + i; | ||
|  | 
 | ||
|  | 						gl.framebufferTexture2D( gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureData.textureGPU, 0 ); | ||
|  | 
 | ||
|  | 					} | ||
|  | 
 | ||
|  | 					renderTargetContextData.framebuffer = fb; | ||
|  | 
 | ||
|  | 					state.drawBuffers( renderContext, fb ); | ||
|  | 
 | ||
|  | 				} | ||
|  | 
 | ||
|  | 				if ( renderContext.depthTexture !== null ) { | ||
|  | 
 | ||
|  | 					const textureData = this.get( renderContext.depthTexture ); | ||
|  | 					const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; | ||
|  | 
 | ||
|  | 					gl.framebufferTexture2D( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0 ); | ||
|  | 
 | ||
|  | 				} | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if ( samples > 0 ) { | ||
|  | 
 | ||
|  | 				if ( msaaFb === undefined ) { | ||
|  | 
 | ||
|  | 					const invalidationArray = []; | ||
|  | 
 | ||
|  | 					msaaFb = gl.createFramebuffer(); | ||
|  | 
 | ||
|  | 					state.bindFramebuffer( gl.FRAMEBUFFER, msaaFb ); | ||
|  | 
 | ||
|  | 					const msaaRenderbuffers = []; | ||
|  | 
 | ||
|  | 					const textures = renderContext.textures; | ||
|  | 
 | ||
|  | 					for ( let i = 0; i < textures.length; i ++ ) { | ||
|  | 
 | ||
|  | 
 | ||
|  | 						msaaRenderbuffers[ i ] = gl.createRenderbuffer(); | ||
|  | 
 | ||
|  | 						gl.bindRenderbuffer( gl.RENDERBUFFER, msaaRenderbuffers[ i ] ); | ||
|  | 
 | ||
|  | 						invalidationArray.push( gl.COLOR_ATTACHMENT0 + i ); | ||
|  | 
 | ||
|  | 						if ( depthBuffer ) { | ||
|  | 
 | ||
|  | 							const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; | ||
|  | 							invalidationArray.push( depthStyle ); | ||
|  | 
 | ||
|  | 						} | ||
|  | 
 | ||
|  | 						const texture = renderContext.textures[ i ]; | ||
|  | 						const textureData = this.get( texture ); | ||
|  | 
 | ||
|  | 						gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, textureData.glInternalFormat, renderContext.width, renderContext.height ); | ||
|  | 						gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, msaaRenderbuffers[ i ] ); | ||
|  | 
 | ||
|  | 
 | ||
|  | 					} | ||
|  | 
 | ||
|  | 					renderTargetContextData.msaaFrameBuffer = msaaFb; | ||
|  | 					renderTargetContextData.msaaRenderbuffers = msaaRenderbuffers; | ||
|  | 
 | ||
|  | 					if ( depthRenderbuffer === undefined ) { | ||
|  | 
 | ||
|  | 						depthRenderbuffer = gl.createRenderbuffer(); | ||
|  | 						this.textureUtils.setupRenderBufferStorage( depthRenderbuffer, renderContext ); | ||
|  | 
 | ||
|  | 						renderTargetContextData.depthRenderbuffer = depthRenderbuffer; | ||
|  | 
 | ||
|  | 						const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; | ||
|  | 						invalidationArray.push( depthStyle ); | ||
|  | 
 | ||
|  | 					} | ||
|  | 
 | ||
|  | 					renderTargetContextData.invalidationArray = invalidationArray; | ||
|  | 
 | ||
|  | 				} | ||
|  | 
 | ||
|  | 				currentFrameBuffer = renderTargetContextData.msaaFrameBuffer; | ||
|  | 
 | ||
|  | 			} else { | ||
|  | 
 | ||
|  | 				currentFrameBuffer = fb; | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		state.bindFramebuffer( gl.FRAMEBUFFER, currentFrameBuffer ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | 	_getVaoKey( index, attributes ) { | ||
|  | 
 | ||
|  | 		let key = []; | ||
|  | 
 | ||
|  | 		if ( index !== null ) { | ||
|  | 
 | ||
|  | 			const indexData = this.get( index ); | ||
|  | 
 | ||
|  | 			key += ':' + indexData.id; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		for ( let i = 0; i < attributes.length; i ++ ) { | ||
|  | 
 | ||
|  | 			const attributeData = this.get( attributes[ i ] ); | ||
|  | 
 | ||
|  | 			key += ':' + attributeData.id; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		return key; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	_createVao( index, attributes ) { | ||
|  | 
 | ||
|  | 		const { gl } = this; | ||
|  | 
 | ||
|  | 		const vaoGPU = gl.createVertexArray(); | ||
|  | 		let key = ''; | ||
|  | 
 | ||
|  | 		let staticVao = true; | ||
|  | 
 | ||
|  | 		gl.bindVertexArray( vaoGPU ); | ||
|  | 
 | ||
|  | 		if ( index !== null ) { | ||
|  | 
 | ||
|  | 			const indexData = this.get( index ); | ||
|  | 
 | ||
|  | 			gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, indexData.bufferGPU ); | ||
|  | 
 | ||
|  | 			key += ':' + indexData.id; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		for ( let i = 0; i < attributes.length; i ++ ) { | ||
|  | 
 | ||
|  | 			const attribute = attributes[ i ]; | ||
|  | 			const attributeData = this.get( attribute ); | ||
|  | 
 | ||
|  | 			key += ':' + attributeData.id; | ||
|  | 
 | ||
|  | 			gl.bindBuffer( gl.ARRAY_BUFFER, attributeData.bufferGPU ); | ||
|  | 			gl.enableVertexAttribArray( i ); | ||
|  | 
 | ||
|  | 			if ( attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute ) staticVao = false; | ||
|  | 
 | ||
|  | 			let stride, offset; | ||
|  | 
 | ||
|  | 			if ( attribute.isInterleavedBufferAttribute === true ) { | ||
|  | 
 | ||
|  | 				stride = attribute.data.stride * attributeData.bytesPerElement; | ||
|  | 				offset = attribute.offset * attributeData.bytesPerElement; | ||
|  | 
 | ||
|  | 			} else { | ||
|  | 
 | ||
|  | 				stride = 0; | ||
|  | 				offset = 0; | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if ( attributeData.isInteger ) { | ||
|  | 
 | ||
|  | 				gl.vertexAttribIPointer( i, attribute.itemSize, attributeData.type, stride, offset ); | ||
|  | 
 | ||
|  | 			} else { | ||
|  | 
 | ||
|  | 				gl.vertexAttribPointer( i, attribute.itemSize, attributeData.type, attribute.normalized, stride, offset ); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if ( attribute.isInstancedBufferAttribute && ! attribute.isInterleavedBufferAttribute ) { | ||
|  | 
 | ||
|  | 				gl.vertexAttribDivisor( i, attribute.meshPerAttribute ); | ||
|  | 
 | ||
|  | 			} else if ( attribute.isInterleavedBufferAttribute && attribute.data.isInstancedInterleavedBuffer ) { | ||
|  | 
 | ||
|  | 				gl.vertexAttribDivisor( i, attribute.data.meshPerAttribute ); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		gl.bindBuffer( gl.ARRAY_BUFFER, null ); | ||
|  | 
 | ||
|  | 		this.vaoCache[ key ] = vaoGPU; | ||
|  | 
 | ||
|  | 		return { vaoGPU, staticVao }; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	_getTransformFeedback( transformBuffers ) { | ||
|  | 
 | ||
|  | 		let key = ''; | ||
|  | 
 | ||
|  | 		for ( let i = 0; i < transformBuffers.length; i ++ ) { | ||
|  | 
 | ||
|  | 			key += ':' + transformBuffers[ i ].id; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		let transformFeedbackGPU = this.transformFeedbackCache[ key ]; | ||
|  | 
 | ||
|  | 		if ( transformFeedbackGPU !== undefined ) { | ||
|  | 
 | ||
|  | 			return transformFeedbackGPU; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		const gl = this.gl; | ||
|  | 
 | ||
|  | 		transformFeedbackGPU = gl.createTransformFeedback(); | ||
|  | 
 | ||
|  | 		gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, transformFeedbackGPU ); | ||
|  | 
 | ||
|  | 		for ( let i = 0; i < transformBuffers.length; i ++ ) { | ||
|  | 
 | ||
|  | 			const attributeData = transformBuffers[ i ]; | ||
|  | 
 | ||
|  | 			gl.bindBufferBase( gl.TRANSFORM_FEEDBACK_BUFFER, i, attributeData.transformBuffer ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, null ); | ||
|  | 
 | ||
|  | 		this.transformFeedbackCache[ key ] = transformFeedbackGPU; | ||
|  | 
 | ||
|  | 		return transformFeedbackGPU; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | 	_setupBindings( bindings, programGPU ) { | ||
|  | 
 | ||
|  | 		const gl = this.gl; | ||
|  | 
 | ||
|  | 		for ( const binding of bindings ) { | ||
|  | 
 | ||
|  | 			const bindingData = this.get( binding ); | ||
|  | 			const index = bindingData.index; | ||
|  | 
 | ||
|  | 			if ( binding.isUniformsGroup || binding.isUniformBuffer ) { | ||
|  | 
 | ||
|  | 				const location = gl.getUniformBlockIndex( programGPU, binding.name ); | ||
|  | 				gl.uniformBlockBinding( programGPU, location, index ); | ||
|  | 
 | ||
|  | 			} else if ( binding.isSampledTexture ) { | ||
|  | 
 | ||
|  | 				const location = gl.getUniformLocation( programGPU, binding.name ); | ||
|  | 				gl.uniform1i( location, index ); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	_bindUniforms( bindings ) { | ||
|  | 
 | ||
|  | 		const { gl, state } = this; | ||
|  | 
 | ||
|  | 		for ( const binding of bindings ) { | ||
|  | 
 | ||
|  | 			const bindingData = this.get( binding ); | ||
|  | 			const index = bindingData.index; | ||
|  | 
 | ||
|  | 			if ( binding.isUniformsGroup || binding.isUniformBuffer ) { | ||
|  | 
 | ||
|  | 				gl.bindBufferBase( gl.UNIFORM_BUFFER, index, bindingData.bufferGPU ); | ||
|  | 
 | ||
|  | 			} else if ( binding.isSampledTexture ) { | ||
|  | 
 | ||
|  | 				state.bindTexture( bindingData.glTextureType, bindingData.textureGPU, gl.TEXTURE0 + index ); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | export default WebGLBackend; |