Browse Source

WebGPURenderer: StorageTexture (#26769)

* Add StorageTexture

* add `webgpu_compute_texture_pingpong` example
sunag 1 year ago
parent
commit
2c8cfdfc33

+ 1 - 0
examples/files.json

@@ -315,6 +315,7 @@
 		"webgpu_compute",
 		"webgpu_compute_particles",
 		"webgpu_compute_texture",
+		"webgpu_compute_texture_pingpong",
 		"webgpu_cubemap_adjustments",
 		"webgpu_cubemap_dynamic",
 		"webgpu_cubemap_mix",

+ 1 - 3
examples/jsm/renderers/common/Bindings.js

@@ -82,9 +82,7 @@ class Bindings extends DataMap {
 
 			if ( binding.isSampledTexture ) {
 
-				const store = binding.store === true;
-
-				this.textures.updateTexture( binding.texture, { store } );
+				this.textures.updateTexture( binding.texture );
 
 			} else if ( binding.isStorageBuffer ) {
 

+ 19 - 0
examples/jsm/renderers/common/StorageTexture.js

@@ -0,0 +1,19 @@
+import { Texture, LinearFilter } from 'three';
+
+class StorageTexture extends Texture {
+
+	constructor( width = 1, height = 1 ) {
+
+		super();
+
+		this.image = { width, height };
+
+		this.magFilter = LinearFilter;
+		this.minFilter = LinearFilter;
+
+		this.isStorageTexture = true;
+
+	}
+}
+
+export default StorageTexture;

+ 1 - 3
examples/jsm/renderers/common/Textures.js

@@ -153,9 +153,7 @@ class Textures extends DataMap {
 
 		//
 
-		if ( isRenderTarget || options.store === true ) {
-
-			//if ( options.store === true ) options.levels = 1; /* no mipmaps? */
+		if ( isRenderTarget || texture.isStorageTexture === true ) {
 
 			backend.createSampler( texture );
 			backend.createTexture( texture, options );

+ 1 - 1
examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js

@@ -111,7 +111,7 @@ class WebGPUTextureUtils {
 
 		let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC;
 
-		if ( options.store === true ) {
+		if ( texture.isStorageTexture === true ) {
 
 			usage |= GPUTextureUsage.STORAGE_BINDING;
 

BIN
examples/screenshots/webgpu_compute_texture_pingpong.jpg


+ 2 - 4
examples/webgpu_compute_texture.html

@@ -31,6 +31,7 @@
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+			import StorageTexture from 'three/addons/renderers/common/StorageTexture.js';
 
 			let camera, scene, renderer;
 
@@ -57,10 +58,7 @@
 
 				const width = 512, height = 512;
 
-				const storageTexture = new THREE.Texture();
-				storageTexture.image = { width, height };
-				storageTexture.magFilter = THREE.LinearFilter;
-				storageTexture.minFilter = THREE.LinearFilter;
+				const storageTexture = new StorageTexture( width, height );
 
 				// create function
 

+ 178 - 0
examples/webgpu_compute_texture_pingpong.html

@@ -0,0 +1,178 @@
+<html lang="en">
+	<head>
+		<title>three.js - WebGPU - Compute Ping/Pong Texture</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">three.js</a> WebGPU - Compute Ping/Pong Texture
+			<br>Texture generated using GPU Compute.
+		</div>
+
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<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 { texture, textureStore, wgslFn, code, instanceIndex } from 'three/nodes';
+
+			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+			import StorageTexture from 'three/addons/renderers/common/StorageTexture.js';
+
+			let camera, scene, renderer;
+			let computeToPing, computeToPong;
+			let pingTexture, pongTexture;
+			let material;
+			let phase = true;
+
+			init();
+			render();
+
+			function init() {
+
+				if ( WebGPU.isAvailable() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU support' );
+
+				}
+
+				const aspect = window.innerWidth / window.innerHeight;
+				camera = new THREE.OrthographicCamera( - aspect, aspect, 1, - 1, 0, 2 );
+				camera.position.z = 1;
+
+				scene = new THREE.Scene();
+
+				// texture
+
+				const width = 512, height = 512;
+
+				pingTexture = new StorageTexture( width, height );
+				pongTexture = new StorageTexture( width, height );
+
+				// compute init
+
+				const rand2 = code( `
+					fn rand2( n: vec2f ) -> f32 {
+
+						return fract( sin( dot( n, vec2f( 12.9898, 4.1414 ) ) ) * 43758.5453 );
+
+					}
+				` );
+
+				const computeInitWGSL = wgslFn( `
+					fn computeInitWGSL( writeTex: texture_storage_2d<rgba8unorm, write>, index: u32 ) -> void {
+
+						let posX = index % ${ width };
+						let posY = index / ${ width };
+						let indexUV = vec2u( posX, posY );
+						let uv = getUV( posX, posY );
+
+						textureStore( writeTex, indexUV, vec4f( vec3f( rand2( uv ) ), 1 ) );
+
+					}
+
+					fn getUV( posX: u32, posY: u32 ) -> vec2f {
+
+						let uv = vec2f( f32( posX ) / ${ width }.0, f32( posY ) / ${ height }.0 );
+
+						return uv;
+
+					}
+				`, [ rand2 ] );
+
+				const computeInitNode = computeInitWGSL( { writeTex: textureStore( pingTexture ), index: instanceIndex } ).compute( width * height );
+
+				// compute loop
+
+				const computePingPongWGSL = wgslFn( `
+					fn computePingPongWGSL( readTex: texture_2d<f32>, writeTex: texture_storage_2d<rgba8unorm, write>, index: u32 ) -> void {
+
+						let posX = index % ${ width };
+						let posY = index / ${ width };
+						let indexUV = vec2u( posX, posY );
+
+						let color = vec3f( rand2( textureLoad( readTex, indexUV, 0 ).xy ) );
+
+						textureStore( writeTex, indexUV, vec4f( color, 1 ) );
+
+					}
+				`, [ rand2 ] );
+
+				computeToPong = computePingPongWGSL( { readTex: texture( pingTexture ), writeTex: textureStore( pongTexture ), index: instanceIndex } ).compute( width * height );
+				computeToPing = computePingPongWGSL( { readTex: texture( pongTexture ), writeTex: textureStore( pingTexture ), index: instanceIndex } ).compute( width * height );
+
+				//
+
+				material = new THREE.MeshBasicMaterial( { color: 0xffffff, map: pongTexture } );
+
+				const plane = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), material );
+				scene.add( plane );
+
+				renderer = new WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( render );
+				document.body.appendChild( renderer.domElement );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				// compute init
+
+				renderer.compute( computeInitNode );
+
+			}
+
+			function onWindowResize() {
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				const aspect = window.innerWidth / window.innerHeight;
+
+				const frustumHeight = camera.top - camera.bottom;
+
+				camera.left = - frustumHeight * aspect / 2;
+				camera.right = frustumHeight * aspect / 2;
+
+				camera.updateProjectionMatrix();
+
+				render();
+
+			}
+
+			function render() {
+
+				// compute step
+
+				renderer.compute( phase ? computeToPong : computeToPing );
+
+				material.map = phase ? pongTexture : pingTexture;
+
+				phase = ! phase;
+
+				// render step
+
+				// update material texture node
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 1 - 0
test/e2e/puppeteer.js

@@ -114,6 +114,7 @@ const exceptionList = [
 	'webgpu_compute',
 	'webgpu_compute_particles',
 	'webgpu_compute_texture',
+	'webgpu_compute_texture_pingpong',
 	'webgpu_cubemap_dynamic',
 	'webgpu_depth_texture',
 	'webgpu_instance_mesh',