Browse Source

WebGPURenderer: implement compileAsync() (#27098)

* compileAsync

* fix typo

* take account of targetScene

* add getter

* cleanup

* minor fixes

---------

Co-authored-by: aardgoose <[email protected]>
aardgoose 1 year ago
parent
commit
3449f17e1e

+ 4 - 4
examples/jsm/renderers/common/Pipelines.js

@@ -89,7 +89,7 @@ class Pipelines extends DataMap {
 
 	}
 
-	getForRender( renderObject ) {
+	getForRender( renderObject, promises = null ) {
 
 		const { backend } = this;
 
@@ -149,7 +149,7 @@ class Pipelines extends DataMap {
 
 				if ( previousPipeline && previousPipeline.usedTimes === 0 ) this._releasePipeline( previousPipeline );
 
-				pipeline = this._getRenderPipeline( renderObject, stageVertex, stageFragment, cacheKey );
+				pipeline = this._getRenderPipeline( renderObject, stageVertex, stageFragment, cacheKey, promises );
 
 			} else {
 
@@ -250,7 +250,7 @@ class Pipelines extends DataMap {
 
 	}
 
-	_getRenderPipeline( renderObject, stageVertex, stageFragment, cacheKey ) {
+	_getRenderPipeline( renderObject, stageVertex, stageFragment, cacheKey, promises ) {
 
 		// check for existing pipeline
 
@@ -266,7 +266,7 @@ class Pipelines extends DataMap {
 
 			renderObject.pipeline = pipeline;
 
-			this.backend.createRenderPipeline( renderObject );
+			this.backend.createRenderPipeline( renderObject, promises );
 
 		}
 

+ 144 - 6
examples/jsm/renderers/common/Renderer.js

@@ -101,9 +101,13 @@ class Renderer {
 		this._renderObjectFunction = null;
 		this._currentRenderObjectFunction = null;
 
+		this._handleObjectFunction = this._renderObjectDirect;
+
 		this._initialized = false;
 		this._initPromise = null;
 
+		this._compilationPromises = null;
+
 		// backwards compatibility
 
 		this.shadowMap = {
@@ -176,9 +180,125 @@ class Renderer {
 
 	}
 
-	async compile( /*scene, camera*/ ) {
+	async compileAsync( scene, camera, targetScene = null ) {
+
+		if ( this._initialized === false ) await this.init();
+
+		// preserve render tree
+
+		const nodeFrame = this._nodes.nodeFrame;
+
+		const previousRenderId = nodeFrame.renderId;
+		const previousRenderContext = this._currentRenderContext;
+		const previousRenderObjectFunction = this._currentRenderObjectFunction;
+		const previousCompilationPromises = this._compilationPromises;
+
+		//
+
+		const sceneRef = ( scene.isScene === true ) ? scene : _scene;
+
+		if ( targetScene === null ) targetScene = scene;
+
+		const renderTarget = this._renderTarget;
+		const renderContext = this._renderContexts.get( targetScene, camera, renderTarget );
+		const activeMipmapLevel = this._activeMipmapLevel;
+
+		const compilationPromises = [];
+
+		this._currentRenderContext = renderContext;
+		this._currentRenderObjectFunction = this.renderObject;
+
+		this._handleObjectFunction = this._createObjectPipeline;
+
+		this._compilationPromises = compilationPromises;
+
+		nodeFrame.renderId ++;
+
+		//
+
+		nodeFrame.update();
 
-		console.warn( 'THREE.Renderer: .compile() is not implemented yet.' );
+		//
+
+		renderContext.depth = this.depth;
+		renderContext.stencil = this.stencil;
+
+		//
+
+		sceneRef.onBeforeRender( this, scene, camera, renderTarget );
+
+		//
+
+		const renderList = this._renderLists.get( scene, camera );
+		renderList.begin();
+
+		this._projectObject( scene, camera, 0, renderList );
+
+		// include lights from target scene
+		if ( targetScene !== scene ) {
+
+			targetScene.traverseVisible( function ( object ) {
+
+				if ( object.isLight && object.layers.test( camera.layers ) ) {
+
+					renderList.pushLight( object );
+
+				}
+
+			} );
+
+		}
+
+		renderList.finish();
+
+		//
+
+		if ( renderTarget !== null ) {
+
+			this._textures.updateRenderTarget( renderTarget, activeMipmapLevel );
+
+			const renderTargetData = this._textures.get( renderTarget );
+
+			renderContext.textures = renderTargetData.textures;
+			renderContext.depthTexture = renderTargetData.depthTexture;
+
+		} else {
+
+			renderContext.textures = null;
+			renderContext.depthTexture = null;
+
+		}
+
+		//
+
+		this._nodes.updateScene( sceneRef );
+
+		//
+
+		this._background.update( sceneRef, renderList, renderContext );
+
+		// process render lists
+
+		const opaqueObjects = renderList.opaque;
+		const transparentObjects = renderList.transparent;
+		const lightsNode = renderList.lightsNode;
+
+		if ( opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, sceneRef, lightsNode );
+		if ( transparentObjects.length > 0 ) this._renderObjects( transparentObjects, camera, sceneRef, lightsNode );
+
+		// restore render tree
+
+		nodeFrame.renderId = previousRenderId;
+
+		this._currentRenderContext = previousRenderContext;
+		this._currentRenderObjectFunction = previousRenderObjectFunction;
+		this._compilationPromises = previousCompilationPromises;
+
+		this._handleObjectFunction = this._renderObjectDirect;
+
+		// wait for all promises setup by backends awaiting compilation/linking/pipeline creation to complete
+
+		await Promise.all( compilationPromises );
 
 	}
 
@@ -995,16 +1115,16 @@ class Renderer {
 		if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) {
 
 			material.side = BackSide;
-			this._renderObjectDirect( object, material, scene, camera, lightsNode, 'backSide' ); // create backSide pass id
+			this._handleObjectFunction( object, material, scene, camera, lightsNode, 'backSide' ); // create backSide pass id
 
 			material.side = FrontSide;
-			this._renderObjectDirect( object, material, scene, camera, lightsNode ); // use default pass id
+			this._handleObjectFunction( object, material, scene, camera, lightsNode ); // use default pass id
 
 			material.side = DoubleSide;
 
 		} else {
 
-			this._renderObjectDirect( object, material, scene, camera, lightsNode );
+			this._handleObjectFunction( object, material, scene, camera, lightsNode );
 
 		}
 
@@ -1048,10 +1168,28 @@ class Renderer {
 
 	}
 
+	_createObjectPipeline( object, material, scene, camera, lightsNode, passId ) {
+
+		const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, passId );
+
+		//
+
+		this._nodes.updateBefore( renderObject );
+
+		//
+
+		this._nodes.updateForRender( renderObject );
+		this._geometries.updateForRender( renderObject );
+		this._bindings.updateForRender( renderObject );
+
+		this._pipelines.getForRender( renderObject, this._compilationPromises );
+
+	}
 
 	get compute() {
 
-		return this.computeAsync;
+		console.warn( 'THREE.Renderer: compile() is deprecated and will be removed in r170, use compileAsync instead.' ); // @deprecated, r170
+		return this.compileAsync;
 
 	}
 

+ 48 - 1
examples/jsm/renderers/webgl/WebGLBackend.js

@@ -47,6 +47,7 @@ class WebGLBackend extends Backend {
 		this.discard = false;
 
 		this.extensions.get( 'EXT_color_buffer_float' );
+		this.parallel = this.extensions.get( 'KHR_parallel_shader_compile' );
 		this._currentContext = null;
 
 	}
@@ -685,7 +686,7 @@ class WebGLBackend extends Backend {
 
 	}
 
-	createRenderPipeline( renderObject ) {
+	createRenderPipeline( renderObject, promises ) {
 
 		const gl = this.gl;
 		const pipeline = renderObject.pipeline;
@@ -703,6 +704,52 @@ class WebGLBackend extends Backend {
 		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 ) );

+ 2 - 2
examples/jsm/renderers/webgpu/WebGPUBackend.js

@@ -1140,9 +1140,9 @@ class WebGPUBackend extends Backend {
 
 	// pipelines
 
-	createRenderPipeline( renderObject ) {
+	createRenderPipeline( renderObject, promises ) {
 
-		this.pipelineUtils.createRenderPipeline( renderObject );
+		this.pipelineUtils.createRenderPipeline( renderObject, promises );
 
 	}
 

+ 24 - 3
examples/jsm/renderers/webgpu/utils/WebGPUPipelineUtils.js

@@ -23,7 +23,7 @@ class WebGPUPipelineUtils {
 
 	}
 
-	createRenderPipeline( renderObject ) {
+	createRenderPipeline( renderObject, promises ) {
 
 		const { object, material, geometry, pipeline } = renderObject;
 		const { vertexProgram, fragmentProgram } = pipeline;
@@ -117,7 +117,7 @@ class WebGPUPipelineUtils {
 
 		}
 
-		pipelineData.pipeline = device.createRenderPipeline( {
+		const pipelineDescriptor = {
 			vertex: Object.assign( {}, vertexModule, { buffers: vertexBuffers } ),
 			fragment: Object.assign( {}, fragmentModule, { targets } ),
 			primitive: primitiveState,
@@ -137,7 +137,28 @@ class WebGPUPipelineUtils {
 			layout: device.createPipelineLayout( {
 				bindGroupLayouts: [ bindingsData.layout ]
 			} )
-		} );
+		};
+
+		if ( promises === null ) {
+
+			pipelineData.pipeline = device.createRenderPipeline( pipelineDescriptor );
+
+		} else {
+
+			const p = new Promise( ( resolve /*, reject*/ ) => {
+
+				device.createRenderPipelineAsync( pipelineDescriptor ).then( pipeline => {
+
+					pipelineData.pipeline = pipeline;
+					resolve();
+
+				} );
+
+			} );
+
+			promises.push( p );
+
+		}
 
 	}
 

+ 6 - 2
examples/webgpu_occlusion.html

@@ -70,9 +70,8 @@
 			}
 
 			init();
-			render();
 
-			function init() {
+			async function init() {
 
 				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
 
@@ -120,6 +119,11 @@
 				renderer = new WebGPURenderer( { antialias: true } );
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				// ensure shaders/pipelines are all complete before rendering
+
+				await renderer.compileAsync( scene, camera );
+
 				renderer.setAnimationLoop( render );
 				document.body.appendChild( renderer.domElement );