Skip to content

Commit

Permalink
WebGPURenderer: StorageTexture (#26769)
Browse files Browse the repository at this point in the history
* Add StorageTexture

* add `webgpu_compute_texture_pingpong` example
  • Loading branch information
sunag committed Sep 17, 2023
1 parent 80ba01c commit 2c8cfdf
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 11 deletions.
1 change: 1 addition & 0 deletions examples/files.json
Expand Up @@ -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",
Expand Down
4 changes: 1 addition & 3 deletions examples/jsm/renderers/common/Bindings.js
Expand Up @@ -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 ) {

Expand Down
19 changes: 19 additions & 0 deletions 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;
4 changes: 1 addition & 3 deletions examples/jsm/renderers/common/Textures.js
Expand Up @@ -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 );
Expand Down
2 changes: 1 addition & 1 deletion examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js
Expand Up @@ -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;

Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 2 additions & 4 deletions examples/webgpu_compute_texture.html
Expand Up @@ -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;

Expand All @@ -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

Expand Down
178 changes: 178 additions & 0 deletions 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/es-module-shims@1.8.0/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 change: 1 addition & 0 deletions test/e2e/puppeteer.js
Expand Up @@ -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',
Expand Down

0 comments on commit 2c8cfdf

Please sign in to comment.