Skip to content

Commit 2c8cfdf

Browse files
authored
WebGPURenderer: StorageTexture (#26769)
* Add StorageTexture * add `webgpu_compute_texture_pingpong` example
1 parent 80ba01c commit 2c8cfdf

9 files changed

+204
-11
lines changed

Diff for: examples/files.json

+1
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@
315315
"webgpu_compute",
316316
"webgpu_compute_particles",
317317
"webgpu_compute_texture",
318+
"webgpu_compute_texture_pingpong",
318319
"webgpu_cubemap_adjustments",
319320
"webgpu_cubemap_dynamic",
320321
"webgpu_cubemap_mix",

Diff for: examples/jsm/renderers/common/Bindings.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,7 @@ class Bindings extends DataMap {
8282

8383
if ( binding.isSampledTexture ) {
8484

85-
const store = binding.store === true;
86-
87-
this.textures.updateTexture( binding.texture, { store } );
85+
this.textures.updateTexture( binding.texture );
8886

8987
} else if ( binding.isStorageBuffer ) {
9088

Diff for: examples/jsm/renderers/common/StorageTexture.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Texture, LinearFilter } from 'three';
2+
3+
class StorageTexture extends Texture {
4+
5+
constructor( width = 1, height = 1 ) {
6+
7+
super();
8+
9+
this.image = { width, height };
10+
11+
this.magFilter = LinearFilter;
12+
this.minFilter = LinearFilter;
13+
14+
this.isStorageTexture = true;
15+
16+
}
17+
}
18+
19+
export default StorageTexture;

Diff for: examples/jsm/renderers/common/Textures.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,7 @@ class Textures extends DataMap {
153153

154154
//
155155

156-
if ( isRenderTarget || options.store === true ) {
157-
158-
//if ( options.store === true ) options.levels = 1; /* no mipmaps? */
156+
if ( isRenderTarget || texture.isStorageTexture === true ) {
159157

160158
backend.createSampler( texture );
161159
backend.createTexture( texture, options );

Diff for: examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class WebGPUTextureUtils {
111111

112112
let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC;
113113

114-
if ( options.store === true ) {
114+
if ( texture.isStorageTexture === true ) {
115115

116116
usage |= GPUTextureUsage.STORAGE_BINDING;
117117

3.33 KB
Loading

Diff for: examples/webgpu_compute_texture.html

+2-4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import WebGPU from 'three/addons/capabilities/WebGPU.js';
3333
import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
34+
import StorageTexture from 'three/addons/renderers/common/StorageTexture.js';
3435

3536
let camera, scene, renderer;
3637

@@ -57,10 +58,7 @@
5758

5859
const width = 512, height = 512;
5960

60-
const storageTexture = new THREE.Texture();
61-
storageTexture.image = { width, height };
62-
storageTexture.magFilter = THREE.LinearFilter;
63-
storageTexture.minFilter = THREE.LinearFilter;
61+
const storageTexture = new StorageTexture( width, height );
6462

6563
// create function
6664

Diff for: examples/webgpu_compute_texture_pingpong.html

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
<html lang="en">
2+
<head>
3+
<title>three.js - WebGPU - Compute Ping/Pong Texture</title>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
6+
<link type="text/css" rel="stylesheet" href="main.css">
7+
</head>
8+
<body>
9+
10+
<div id="info">
11+
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> WebGPU - Compute Ping/Pong Texture
12+
<br>Texture generated using GPU Compute.
13+
</div>
14+
15+
<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
16+
17+
<script type="importmap">
18+
{
19+
"imports": {
20+
"three": "../build/three.module.js",
21+
"three/addons/": "./jsm/",
22+
"three/nodes": "./jsm/nodes/Nodes.js"
23+
}
24+
}
25+
</script>
26+
27+
<script type="module">
28+
29+
import * as THREE from 'three';
30+
import { texture, textureStore, wgslFn, code, instanceIndex } from 'three/nodes';
31+
32+
import WebGPU from 'three/addons/capabilities/WebGPU.js';
33+
import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
34+
import StorageTexture from 'three/addons/renderers/common/StorageTexture.js';
35+
36+
let camera, scene, renderer;
37+
let computeToPing, computeToPong;
38+
let pingTexture, pongTexture;
39+
let material;
40+
let phase = true;
41+
42+
init();
43+
render();
44+
45+
function init() {
46+
47+
if ( WebGPU.isAvailable() === false ) {
48+
49+
document.body.appendChild( WebGPU.getErrorMessage() );
50+
51+
throw new Error( 'No WebGPU support' );
52+
53+
}
54+
55+
const aspect = window.innerWidth / window.innerHeight;
56+
camera = new THREE.OrthographicCamera( - aspect, aspect, 1, - 1, 0, 2 );
57+
camera.position.z = 1;
58+
59+
scene = new THREE.Scene();
60+
61+
// texture
62+
63+
const width = 512, height = 512;
64+
65+
pingTexture = new StorageTexture( width, height );
66+
pongTexture = new StorageTexture( width, height );
67+
68+
// compute init
69+
70+
const rand2 = code( `
71+
fn rand2( n: vec2f ) -> f32 {
72+
73+
return fract( sin( dot( n, vec2f( 12.9898, 4.1414 ) ) ) * 43758.5453 );
74+
75+
}
76+
` );
77+
78+
const computeInitWGSL = wgslFn( `
79+
fn computeInitWGSL( writeTex: texture_storage_2d<rgba8unorm, write>, index: u32 ) -> void {
80+
81+
let posX = index % ${ width };
82+
let posY = index / ${ width };
83+
let indexUV = vec2u( posX, posY );
84+
let uv = getUV( posX, posY );
85+
86+
textureStore( writeTex, indexUV, vec4f( vec3f( rand2( uv ) ), 1 ) );
87+
88+
}
89+
90+
fn getUV( posX: u32, posY: u32 ) -> vec2f {
91+
92+
let uv = vec2f( f32( posX ) / ${ width }.0, f32( posY ) / ${ height }.0 );
93+
94+
return uv;
95+
96+
}
97+
`, [ rand2 ] );
98+
99+
const computeInitNode = computeInitWGSL( { writeTex: textureStore( pingTexture ), index: instanceIndex } ).compute( width * height );
100+
101+
// compute loop
102+
103+
const computePingPongWGSL = wgslFn( `
104+
fn computePingPongWGSL( readTex: texture_2d<f32>, writeTex: texture_storage_2d<rgba8unorm, write>, index: u32 ) -> void {
105+
106+
let posX = index % ${ width };
107+
let posY = index / ${ width };
108+
let indexUV = vec2u( posX, posY );
109+
110+
let color = vec3f( rand2( textureLoad( readTex, indexUV, 0 ).xy ) );
111+
112+
textureStore( writeTex, indexUV, vec4f( color, 1 ) );
113+
114+
}
115+
`, [ rand2 ] );
116+
117+
computeToPong = computePingPongWGSL( { readTex: texture( pingTexture ), writeTex: textureStore( pongTexture ), index: instanceIndex } ).compute( width * height );
118+
computeToPing = computePingPongWGSL( { readTex: texture( pongTexture ), writeTex: textureStore( pingTexture ), index: instanceIndex } ).compute( width * height );
119+
120+
//
121+
122+
material = new THREE.MeshBasicMaterial( { color: 0xffffff, map: pongTexture } );
123+
124+
const plane = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), material );
125+
scene.add( plane );
126+
127+
renderer = new WebGPURenderer( { antialias: true } );
128+
renderer.setPixelRatio( window.devicePixelRatio );
129+
renderer.setSize( window.innerWidth, window.innerHeight );
130+
renderer.setAnimationLoop( render );
131+
document.body.appendChild( renderer.domElement );
132+
133+
window.addEventListener( 'resize', onWindowResize );
134+
135+
// compute init
136+
137+
renderer.compute( computeInitNode );
138+
139+
}
140+
141+
function onWindowResize() {
142+
143+
renderer.setSize( window.innerWidth, window.innerHeight );
144+
145+
const aspect = window.innerWidth / window.innerHeight;
146+
147+
const frustumHeight = camera.top - camera.bottom;
148+
149+
camera.left = - frustumHeight * aspect / 2;
150+
camera.right = frustumHeight * aspect / 2;
151+
152+
camera.updateProjectionMatrix();
153+
154+
render();
155+
156+
}
157+
158+
function render() {
159+
160+
// compute step
161+
162+
renderer.compute( phase ? computeToPong : computeToPing );
163+
164+
material.map = phase ? pongTexture : pingTexture;
165+
166+
phase = ! phase;
167+
168+
// render step
169+
170+
// update material texture node
171+
172+
renderer.render( scene, camera );
173+
174+
}
175+
176+
</script>
177+
</body>
178+
</html>

Diff for: test/e2e/puppeteer.js

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ const exceptionList = [
114114
'webgpu_compute',
115115
'webgpu_compute_particles',
116116
'webgpu_compute_texture',
117+
'webgpu_compute_texture_pingpong',
117118
'webgpu_cubemap_dynamic',
118119
'webgpu_depth_texture',
119120
'webgpu_instance_mesh',

0 commit comments

Comments
 (0)