Explorar o código

WebGPURenderer: Read Only and Read/Write Storage Textures (#28455)

* Sketched out API for changing storageTextureAccess on storageTextures. Still need to gain a comprehensive understanding of what texture formats are permitted to be accessed in which ways (for instance, testing revealed that rgba16floats seemingly cannot be accessed with a GPUTextureAccess of 'read-write'. Instead, it only works with 'read-only' or 'write-only' according to the wgsl compiler errors

* Fix example code formatting

* minor adjustments

* compute with read-write access

* Utility storageTexture functions

* revert read-write

* extend support

---------

Co-authored-by: Renaud Rohlinger <[email protected]>
Christian Helgeson hai 1 ano
pai
achega
24a29a54fd

+ 1 - 1
examples/jsm/nodes/Nodes.js

@@ -103,7 +103,7 @@ export { default as SceneNode, backgroundBlurriness, backgroundIntensity } from
 export { default as StorageBufferNode, storage, storageObject } from './accessors/StorageBufferNode.js';
 export * from './accessors/TangentNode.js';
 export { default as TextureNode, texture, textureLoad, /*textureLevel,*/ sampler } from './accessors/TextureNode.js';
-export { default as TextureStoreNode, textureStore } from './accessors/TextureStoreNode.js';
+export { default as StorageTextureNode, storageTexture, textureStore, storageTextureReadOnly, storageTextureReadWrite } from './accessors/StorageTextureNode.js';
 export { default as Texture3DNode, texture3D } from './accessors/Texture3DNode.js';
 export * from './accessors/UVNode.js';
 export { default as UserDataNode, userData } from './accessors/UserDataNode.js';

+ 19 - 6
examples/jsm/nodes/accessors/TextureStoreNode.js → examples/jsm/nodes/accessors/StorageTextureNode.js

@@ -1,8 +1,9 @@
 import { addNodeClass } from '../core/Node.js';
 import TextureNode from './TextureNode.js';
 import { nodeProxy } from '../shadernode/ShaderNode.js';
+import { GPUStorageTextureAccess } from '../../renderers/webgpu/utils/WebGPUConstants.js';
 
-class TextureStoreNode extends TextureNode {
+class StorageTextureNode extends TextureNode {
 
 	constructor( value, uvNode, storeNode = null ) {
 
@@ -10,7 +11,9 @@ class TextureStoreNode extends TextureNode {
 
 		this.storeNode = storeNode;
 
-		this.isStoreTextureNode = true;
+		this.isStorageTextureNode = true;
+
+		this.access = GPUStorageTextureAccess.WriteOnly;
 
 	}
 
@@ -29,6 +32,13 @@ class TextureStoreNode extends TextureNode {
 
 	}
 
+	setAccess( value ) {
+
+		this.access = value;
+		return this;
+
+	}
+
 	generate( builder, output ) {
 
 		let snippet;
@@ -65,13 +75,16 @@ class TextureStoreNode extends TextureNode {
 
 }
 
-export default TextureStoreNode;
+export default StorageTextureNode;
+
+export const storageTexture = nodeProxy( StorageTextureNode );
 
-const textureStoreBase = nodeProxy( TextureStoreNode );
+export const storageTextureReadOnly = ( value, uvNode, storeNode ) => storageTexture( value, uvNode, storeNode ).setAccess( 'read-only' );
+export const storageTextureReadWrite = ( value, uvNode, storeNode ) => storageTexture( value, uvNode, storeNode ).setAccess( 'read-write' );
 
 export const textureStore = ( value, uvNode, storeNode ) => {
 
-	const node = textureStoreBase( value, uvNode, storeNode );
+	const node = storageTexture( value, uvNode, storeNode );
 
 	if ( storeNode !== null ) node.append();
 
@@ -79,4 +92,4 @@ export const textureStore = ( value, uvNode, storeNode ) => {
 
 };
 
-addNodeClass( 'TextureStoreNode', TextureStoreNode );
+addNodeClass( 'StorageTextureNode', StorageTextureNode );

+ 7 - 5
examples/jsm/renderers/common/nodes/NodeSampledTexture.js

@@ -2,12 +2,14 @@ import { SampledTexture } from '../SampledTexture.js';
 
 class NodeSampledTexture extends SampledTexture {
 
-	constructor( name, textureNode ) {
+	constructor( name, textureNode, access = null ) {
 
 		super( name, textureNode ? textureNode.value : null );
 
 		this.textureNode = textureNode;
 
+		this.access = access;
+
 	}
 
 	get needsBindingsUpdate() {
@@ -36,9 +38,9 @@ class NodeSampledTexture extends SampledTexture {
 
 class NodeSampledCubeTexture extends NodeSampledTexture {
 
-	constructor( name, textureNode ) {
+	constructor( name, textureNode, access ) {
 
-		super( name, textureNode );
+		super( name, textureNode, access );
 
 		this.isSampledCubeTexture = true;
 
@@ -48,9 +50,9 @@ class NodeSampledCubeTexture extends NodeSampledTexture {
 
 class NodeSampledTexture3D extends NodeSampledTexture {
 
-	constructor( name, textureNode ) {
+	constructor( name, textureNode, access ) {
 
-		super( name, textureNode );
+		super( name, textureNode, access );
 
 		this.isSampledTexture3D = true;
 

+ 45 - 7
examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js

@@ -13,6 +13,8 @@ import { NodeBuilder, CodeNode } from '../../../nodes/Nodes.js';
 import { getFormat } from '../utils/WebGPUTextureUtils.js';
 
 import WGSLNodeParser from './WGSLNodeParser.js';
+import { GPUStorageTextureAccess } from '../utils/WebGPUConstants.js';
+
 
 // GPUShaderStage is not defined in browsers not supporting WebGPU
 const GPUShaderStage = self.GPUShaderStage;
@@ -358,6 +360,41 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 	}
 
+	getStorageAccess( node ) {
+
+		if ( node.isStorageTextureNode ) {
+
+			switch ( node.access ) {
+
+				case GPUStorageTextureAccess.ReadOnly: {
+
+					return 'read';
+
+				}
+
+				case GPUStorageTextureAccess.WriteOnly: {
+
+					return 'write';
+
+				}
+
+				default: {
+
+					return 'read_write';
+
+				}
+
+			}
+
+		} else {
+
+			// @TODO: Account for future read-only storage buffer pull request
+			return 'read_write';
+
+		}
+
+	}
+
 	getUniformFromNode( node, type, shaderStage, name = null ) {
 
 		const uniformNode = super.getUniformFromNode( node, type, shaderStage, name );
@@ -375,19 +412,19 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 				if ( type === 'texture' || type === 'storageTexture' ) {
 
-					texture = new NodeSampledTexture( uniformNode.name, uniformNode.node );
+					texture = new NodeSampledTexture( uniformNode.name, uniformNode.node, node.access ? node.access : null );
 
 				} else if ( type === 'cubeTexture' ) {
 
-					texture = new NodeSampledCubeTexture( uniformNode.name, uniformNode.node );
+					texture = new NodeSampledCubeTexture( uniformNode.name, uniformNode.node, node.access ? node.access : null );
 
 				} else if ( type === 'texture3D' ) {
 
-					texture = new NodeSampledTexture3D( uniformNode.name, uniformNode.node );
+					texture = new NodeSampledTexture3D( uniformNode.name, uniformNode.node, node.access ? node.access : null );
 
 				}
 
-				texture.store = node.isStoreTextureNode === true;
+				texture.store = node.isStorageTextureNode === true;
 				texture.setVisibility( gpuShaderStageLib[ shaderStage ] );
 
 				if ( shaderStage === 'fragment' && this.isUnfilterable( node.value ) === false && texture.store === false ) {
@@ -742,7 +779,7 @@ ${ flowData.code }
 
 				const texture = uniform.node.value;
 
-				if ( shaderStage === 'fragment' && this.isUnfilterable( texture ) === false && uniform.node.isStoreTextureNode !== true ) {
+				if ( shaderStage === 'fragment' && this.isUnfilterable( texture ) === false && uniform.node.isStorageTextureNode !== true ) {
 
 					if ( texture.isDepthTexture === true && texture.compareFunction !== null ) {
 
@@ -778,11 +815,12 @@ ${ flowData.code }
 
 					textureType = 'texture_3d<f32>';
 
-				} else if ( uniform.node.isStoreTextureNode === true ) {
+				} else if ( uniform.node.isStorageTextureNode === true ) {
 
 					const format = getFormat( texture );
+					const access = this.getStorageAccess( uniform.node );
 
-					textureType = `texture_storage_2d<${ format }, write>`;
+					textureType = `texture_storage_2d<${ format }, ${access}>`;
 
 				} else {
 

+ 2 - 1
examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js

@@ -62,8 +62,9 @@ class WebGPUBindingUtils {
 			} else if ( binding.isSampledTexture && binding.store ) {
 
 				const format = this.backend.get( binding.texture ).texture.format;
+				const access = binding.access;
 
-				bindingGPU.storageTexture = { format }; // GPUStorageTextureBindingLayout
+				bindingGPU.storageTexture = { format, access }; // GPUStorageTextureBindingLayout
 
 			} else if ( binding.isSampledTexture ) {
 

+ 6 - 0
examples/jsm/renderers/webgpu/utils/WebGPUConstants.js

@@ -269,6 +269,12 @@ export const GPUBufferBindingType = {
 	ReadOnlyStorage: 'read-only-storage'
 };
 
+export const GPUStorageTextureAccess = {
+	WriteOnly: 'write-only',
+	ReadOnly: 'read-only',
+	ReadWrite: 'read-write',
+};
+
 export const GPUSamplerBindingType = {
 	Filtering: 'filtering',
 	NonFiltering: 'non-filtering',

+ 16 - 11
examples/webgpu_compute_texture_pingpong.html

@@ -24,7 +24,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { texture, textureStore, wgslFn, code, instanceIndex, uniform } from 'three/nodes';
+			import { storageTexture, wgslFn, code, instanceIndex, uniform } from 'three/nodes';
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
@@ -73,6 +73,11 @@
 				}
 
 				const wgslFormat = hdr ? 'rgba16float' : 'rgba8unorm';
+			
+				const readPing = storageTexture( pingTexture ).setAccess( 'read-only' );
+				const writePing = storageTexture( pingTexture ).setAccess( 'write-only' );
+				const readPong = storageTexture( pongTexture ).setAccess( 'read-only' );
+				const writePong = storageTexture( pongTexture ).setAccess( 'write-only' );
 
 				// compute init
 
@@ -83,15 +88,15 @@
 
 					}
 
-					fn blur( image : texture_2d<f32>, uv : vec2i ) -> vec4f {
+					fn blur( image : texture_storage_2d<${wgslFormat}, read>, uv : vec2i ) -> vec4f {
 
 						var color = vec4f( 0.0 );
 
-						color += textureLoad( image, uv + vec2i( - 1, 1 ), 0 );
-						color += textureLoad( image, uv + vec2i( - 1, - 1 ), 0 );
-						color += textureLoad( image, uv + vec2i( 0, 0 ), 0 );
-						color += textureLoad( image, uv + vec2i( 1, - 1 ), 0 );
-						color += textureLoad( image, uv + vec2i( 1, 1 ), 0 );
+						color += textureLoad( image, uv + vec2i( - 1, 1 ));
+						color += textureLoad( image, uv + vec2i( - 1, - 1 ));
+						color += textureLoad( image, uv + vec2i( 0, 0 ));
+						color += textureLoad( image, uv + vec2i( 1, - 1 ));
+						color += textureLoad( image, uv + vec2i( 1, 1 ));
 
 						return color / 5.0; 
 					}
@@ -122,12 +127,12 @@
 					}
 				`, [ rand2 ] );
 
-				computeInitNode = computeInitWGSL( { writeTex: textureStore( pingTexture ), index: instanceIndex, seed } ).compute( width * height );
+				computeInitNode = computeInitWGSL( { writeTex: storageTexture( pingTexture ), index: instanceIndex, seed } ).compute( width * height );
 
 				// compute loop
 
 				const computePingPongWGSL = wgslFn( `
-					fn computePingPongWGSL( readTex: texture_2d<f32>, writeTex: texture_storage_2d<${ wgslFormat }, write>, index: u32 ) -> void {
+					fn computePingPongWGSL( readTex: texture_storage_2d<${wgslFormat}, read>, writeTex: texture_storage_2d<${ wgslFormat }, write>, index: u32 ) -> void {
 
 						let posX = index % ${ width };
 						let posY = index / ${ width };
@@ -142,8 +147,8 @@
 
 				//
 
-				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 );
+				computeToPong = computePingPongWGSL( { readTex: readPing, writeTex: writePong, index: instanceIndex } ).compute( width * height );
+				computeToPing = computePingPongWGSL( { readTex: readPong, writeTex: writePing, index: instanceIndex } ).compute( width * height );
 
 				//