2
0
Эх сурвалжийг харах

WebGLRenderer: Add "readRenderTargetPixelsAsync" function (#28291)

* Copy to async implementation from #24466

* probeSync cleanup

* More simplification

* Simplification

* Remove tangential functions

* More simplification

* Convert to thrown errors

* Remove comma

* Update docs, probe frequency
Garrett Johnson 1 жил өмнө
parent
commit
7a02475928

+ 11 - 4
docs/api/en/renderers/WebGLRenderer.html

@@ -511,16 +511,23 @@ document.body.appendChild( renderer.domElement );
 			This is a wrapper around
 			[link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/readPixels WebGLRenderingContext.readPixels]().
 		</p>
-		<p>
-			See the [example:webgl_interactive_cubes_gpu interactive / cubes / gpu]
-			example.
-		</p>
 		<p>
 			For reading out a [page:WebGLCubeRenderTarget WebGLCubeRenderTarget] use
 			the optional parameter activeCubeFaceIndex to determine which face should
 			be read.
 		</p>
 
+		<h3>
+			[method:Promise readRenderTargetPixelsAsync]( [param:WebGLRenderTarget renderTarget], [param:Float x], [param:Float y], [param:Float width], [param:Float height], [param:TypedArray buffer], [param:Integer activeCubeFaceIndex] )
+		</h3>
+		<p>
+			Asynchronous, non-blocking version of [page:WebGLRenderer.readRenderTargetPixels .readRenderTargetPixels]. The
+			returned promise resolves once the buffer data is ready to be used.
+		</p>
+		<p>
+			See the [example:webgl_interactive_cubes_gpu interactive / cubes / gpu] example.
+		</p>
+
 		<h3>
 			[method:undefined render]( [param:Object3D scene], [param:Camera camera] )
 		</h3>

+ 16 - 12
examples/webgl_interactive_cubes_gpu.html

@@ -261,23 +261,27 @@
 				const pixelBuffer = new Int32Array( 4 );
 
 				// read the pixel
-				renderer.readRenderTargetPixels( pickingTexture, 0, 0, 1, 1, pixelBuffer );
+				renderer
+					.readRenderTargetPixelsAsync( pickingTexture, 0, 0, 1, 1, pixelBuffer )
+					.then( () => {
 
-				const id = pixelBuffer[ 0 ];
-				if ( id !== - 1 ) {
+						const id = pixelBuffer[ 0 ];
+						if ( id !== - 1 ) {
 
-					// move our highlightBox so that it surrounds the picked object
-					const data = pickingData[ id ];
-					highlightBox.position.copy( data.position );
-					highlightBox.rotation.copy( data.rotation );
-					highlightBox.scale.copy( data.scale ).add( offset );
-					highlightBox.visible = true;
+							// move our highlightBox so that it surrounds the picked object
+							const data = pickingData[ id ];
+							highlightBox.position.copy( data.position );
+							highlightBox.rotation.copy( data.rotation );
+							highlightBox.scale.copy( data.scale ).add( offset );
+							highlightBox.visible = true;
 
-				} else {
+						} else {
 
-					highlightBox.visible = false;
+							highlightBox.visible = false;
 
-				}
+						}
+
+					} );
 
 			}
 

+ 80 - 1
src/renderers/WebGLRenderer.js

@@ -54,7 +54,7 @@ import { WebGLUtils } from './webgl/WebGLUtils.js';
 import { WebXRManager } from './webxr/WebXRManager.js';
 import { WebGLMaterials } from './webgl/WebGLMaterials.js';
 import { WebGLUniformsGroups } from './webgl/WebGLUniformsGroups.js';
-import { createCanvasElement } from '../utils.js';
+import { createCanvasElement, probeAsync } from '../utils.js';
 import { ColorManagement } from '../math/ColorManagement.js';
 
 class WebGLRenderer {
@@ -2357,6 +2357,85 @@ class WebGLRenderer {
 
 		};
 
+		this.readRenderTargetPixelsAsync = async function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) {
+
+			if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) {
+
+				throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' );
+
+			}
+
+			let framebuffer = properties.get( renderTarget ).__webglFramebuffer;
+			if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) {
+
+				framebuffer = framebuffer[ activeCubeFaceIndex ];
+
+			}
+
+			if ( framebuffer ) {
+
+				state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );
+
+				try {
+
+					const texture = renderTarget.texture;
+					const textureFormat = texture.format;
+					const textureType = texture.type;
+
+					if ( ! capabilities.textureFormatReadable( textureFormat ) ) {
+
+						throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in RGBA or implementation defined format.' );
+
+					}
+
+					if ( ! capabilities.textureTypeReadable( textureType ) ) {
+
+						throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in UnsignedByteType or implementation defined type.' );
+
+					}
+
+					// the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604)
+					if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) {
+
+						const glBuffer = _gl.createBuffer();
+						_gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer );
+						_gl.bufferData( _gl.PIXEL_PACK_BUFFER, buffer.byteLength, _gl.STREAM_READ );
+						_gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), 0 );
+						_gl.flush();
+
+						// check if the commands have finished every 8 ms
+						const sync = _gl.fenceSync( _gl.SYNC_GPU_COMMANDS_COMPLETE, 0 );
+						await probeAsync( _gl, sync, 4 );
+
+						try {
+
+							_gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer );
+							_gl.getBufferSubData( _gl.PIXEL_PACK_BUFFER, 0, buffer );
+
+						} finally {
+
+							_gl.deleteBuffer( glBuffer );
+							_gl.deleteSync( sync );
+
+						}
+
+						return buffer;
+
+					}
+
+				} finally {
+
+					// restore framebuffer of current render target if necessary
+
+					const framebuffer = ( _currentRenderTarget !== null ) ? properties.get( _currentRenderTarget ).__webglFramebuffer : null;
+					state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );
+
+				}
+
+			}
+
+		};
+
 		this.copyFramebufferToTexture = function ( position, texture, level = 0 ) {
 
 			const levelScale = Math.pow( 2, - level );

+ 30 - 1
src/utils.js

@@ -88,4 +88,33 @@ function warnOnce( message ) {
 
 }
 
-export { arrayMin, arrayMax, arrayNeedsUint32, getTypedArray, createElementNS, createCanvasElement, warnOnce };
+function probeAsync( gl, sync, interval ) {
+
+	return new Promise( function ( resolve, reject ) {
+
+		function probe() {
+
+			switch ( gl.clientWaitSync( sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0 ) ) {
+
+				case gl.WAIT_FAILED:
+					reject();
+					break;
+
+				case gl.TIMEOUT_EXPIRED:
+					setTimeout( probe, interval );
+					break;
+
+				default:
+					resolve();
+
+			}
+
+		}
+
+		setTimeout( probe, interval );
+
+	} );
+
+}
+
+export { arrayMin, arrayMax, arrayNeedsUint32, getTypedArray, createElementNS, createCanvasElement, warnOnce, probeAsync };