浏览代码

WebGPURenderer: Add `copyTextureToTexture` support (#27888)

* webgpu: add copytexturetotexture support

* fix dst texture not update on the gpu
Renaud Rohlinger 1 年之前
父节点
当前提交
22a285ad13

+ 1 - 0
examples/files.json

@@ -361,6 +361,7 @@
 		"webgpu_portal",
 		"webgpu_reflection",
 		"webgpu_rtt",
+		"webgpu_materials_texture_partialupdate",
 		"webgpu_sandbox",
 		"webgpu_shadertoy",
 		"webgpu_shadowmap",

+ 10 - 0
examples/jsm/renderers/common/Renderer.js

@@ -979,6 +979,16 @@ class Renderer {
 
 	}
 
+	copyTextureToTexture( position, srcTexture, dstTexture, level = 0 ) {
+
+		this._textures.updateTexture( srcTexture );
+		this._textures.updateTexture( dstTexture );
+
+		this.backend.copyTextureToTexture( position, srcTexture, dstTexture, level );
+
+	}
+
+
 	readRenderTargetPixelsAsync( renderTarget, x, y, width, height ) {
 
 		return this.backend.copyTextureToBuffer( renderTarget.texture, x, y, width, height );

+ 6 - 0
examples/jsm/renderers/webgl/WebGLBackend.js

@@ -1109,6 +1109,12 @@ class WebGLBackend extends Backend {
 
 	}
 
+	copyTextureToTexture( position, srcTexture, dstTexture, level ) {
+
+		this.textureUtils.copyTextureToTexture( position, srcTexture, dstTexture, level );
+
+	}
+
 	copyFramebufferToTexture( texture, renderContext ) {
 
 		this.textureUtils.copyFramebufferToTexture( texture, renderContext );

+ 42 - 0
examples/jsm/renderers/webgl/utils/WebGLTextureUtils.js

@@ -523,6 +523,48 @@ class WebGLTextureUtils {
 
 	}
 
+	copyTextureToTexture( position, srcTexture, dstTexture, level = 0 ) {
+
+		const { gl, backend } = this;
+		const { state } = this.backend;
+
+		const width = srcTexture.image.width;
+		const height = srcTexture.image.height;
+		const { textureGPU: dstTextureGPU, glTextureType, glType, glFormat } = backend.get( dstTexture );
+
+		state.bindTexture( glTextureType, dstTextureGPU );
+
+		// As another texture upload may have changed pixelStorei
+		// parameters, make sure they are correct for the dstTexture
+		gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY );
+		gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha );
+		gl.pixelStorei( gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment );
+
+		if ( srcTexture.isDataTexture ) {
+
+			gl.texSubImage2D( gl.TEXTURE_2D, level, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data );
+
+		} else {
+
+			if ( srcTexture.isCompressedTexture ) {
+
+				gl.compressedTexSubImage2D( gl.TEXTURE_2D, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data );
+
+			} else {
+
+				gl.texSubImage2D( gl.TEXTURE_2D, level, position.x, position.y, glFormat, glType, srcTexture.image );
+
+			}
+
+		}
+
+		// Generate mipmaps only when copying level 0
+		if ( level === 0 && dstTexture.generateMipmaps ) gl.generateMipmap( gl.TEXTURE_2D );
+
+		state.unbindTexture();
+
+	}
+
 	copyFramebufferToTexture( texture, renderContext ) {
 
 		const { gl } = this;

+ 32 - 1
examples/jsm/renderers/webgpu/WebGPUBackend.js

@@ -1236,7 +1236,7 @@ class WebGPUBackend extends Backend {
 
 		if ( ! this.adapter ) {
 
-			console.warn( 'WebGPUBackend: WebGPU adapter has not been initialized yet. Please use detectSupportAsync instead' );
+			console.warn( 'WebGPUBackend: WebGPU adapter has not been initialized yet. Please use hasFeatureAsync instead' );
 
 			return false;
 
@@ -1246,6 +1246,37 @@ class WebGPUBackend extends Backend {
 
 	}
 
+	copyTextureToTexture( position, srcTexture, dstTexture, level = 0 ) {
+
+		const encoder = this.device.createCommandEncoder( { label: 'copyTextureToTexture_' + srcTexture.id + '_' + dstTexture.id } );
+
+		const sourceGPU = this.get( srcTexture ).texture;
+		const destinationGPU = this.get( dstTexture ).texture;
+
+		encoder.copyTextureToTexture(
+			{
+				texture: sourceGPU,
+				mipLevel: level,
+				origin: { x: 0, y: 0, z: 0 }
+			},
+			{
+				texture: destinationGPU,
+				mipLevel: level,
+				origin: { x: position.x, y: position.y, z: position.z }
+			},
+			[
+				srcTexture.image.width,
+				srcTexture.image.height
+			]
+		);
+
+		this.device.queue.submit( [ encoder.finish() ] );
+
+	}
+
+
+
+
 	copyFramebufferToTexture( texture, renderContext ) {
 
 		const renderContextData = this.get( renderContext );

二进制
examples/screenshots/webgpu_materials_texture_partialupdate.jpg


+ 150 - 0
examples/webgpu_materials_texture_partialupdate.html

@@ -0,0 +1,150 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - texture - webgpu partial update</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener noreferrer">three.js</a> - partial webgpu texture update <br/>
+			replace parts of an existing texture with all data of another texture
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/",
+					"three/nodes": "./jsm/nodes/Nodes.js"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+
+			let camera, scene, renderer, clock, dataTexture, diffuseMap;
+
+			let last = 0;
+			const position = new THREE.Vector2();
+			const color = new THREE.Color();
+
+			init();
+
+			function init() {
+
+				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 );
+				camera.position.z = 2;
+
+				scene = new THREE.Scene();
+
+				clock = new THREE.Clock();
+
+				const loader = new THREE.TextureLoader();
+				diffuseMap = loader.load( 'textures/carbon/Carbon.png', animate );
+				diffuseMap.colorSpace = THREE.SRGBColorSpace;
+				diffuseMap.minFilter = THREE.LinearFilter;
+				diffuseMap.generateMipmaps = false;
+
+				const geometry = new THREE.PlaneGeometry( 2, 2 );
+				const material = new THREE.MeshBasicMaterial( { map: diffuseMap } );
+
+				const mesh = new THREE.Mesh( geometry, material );
+				scene.add( mesh );
+
+				//
+
+				const width = 32;
+				const height = 32;
+
+				const data = new Uint8Array( width * height * 4 );
+				dataTexture = new THREE.DataTexture( data, width, height );
+
+				//
+
+				renderer = new WebGPURenderer( { antialias: true, forceWebGL: false } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				document.body.appendChild( renderer.domElement );
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			async function animate() {
+
+				requestAnimationFrame( animate );
+
+				const elapsedTime = clock.getElapsedTime();
+
+
+				await renderer.renderAsync( scene, camera );
+
+
+				if ( elapsedTime - last > 0.1 ) {
+
+					last = elapsedTime;
+
+					position.x = ( 32 * THREE.MathUtils.randInt( 1, 16 ) ) - 32;
+					position.y = ( 32 * THREE.MathUtils.randInt( 1, 16 ) ) - 32;
+
+					// generate new color data
+					updateDataTexture( dataTexture );
+
+					// perform copy from src to dest texture to a random position
+
+					renderer.copyTextureToTexture( position, dataTexture, diffuseMap );
+
+				}
+
+			}
+
+			function updateDataTexture( texture ) {
+
+				const size = texture.image.width * texture.image.height;
+				const data = texture.image.data;
+
+				// generate a random color and update texture data
+
+				color.setHex( Math.random() * 0xffffff );
+
+				const r = Math.floor( color.r * 255 );
+				const g = Math.floor( color.g * 255 );
+				const b = Math.floor( color.b * 255 );
+
+				for ( let i = 0; i < size; i ++ ) {
+
+					const stride = i * 4;
+
+					data[ stride ] = r;
+					data[ stride + 1 ] = g;
+					data[ stride + 2 ] = b;
+					data[ stride + 3 ] = 1;
+
+				}
+
+				texture.needsUpdate = true;
+
+			}
+
+		</script>
+
+	</body>
+</html>