Преглед изворни кода

WebGPURenderer: partial compute() shader support for WebGL backend (#27367)

* compute support

* code bot fixes

* add StorageBufferAttribute

* adapt to storagetBufferAttribute

* dynamically realign the size of storage buffer based on backend

* compute examples takes too much time to init for puppeteer

* Add StorageBufferAttribute.create()

---------

Co-authored-by: aardgoose <[email protected]>
aardgoose пре 1 година
родитељ
комит
9fb80cf753

+ 5 - 3
examples/jsm/nodes/accessors/BufferAttributeNode.js

@@ -67,12 +67,14 @@ class BufferAttributeNode extends InputNode {
 
 		const nodeType = this.getNodeType( builder );
 
-		const nodeUniform = builder.getBufferAttributeFromNode( this, nodeType );
-		const propertyName = builder.getPropertyName( nodeUniform );
+		const nodeAttribute = builder.getBufferAttributeFromNode( this, nodeType );
+		const propertyName = builder.getPropertyName( nodeAttribute );
 
 		let output = null;
 
-		if ( builder.shaderStage === 'vertex' ) {
+		if ( builder.shaderStage === 'vertex' || builder.shaderStage === 'compute' ) {
+
+			this.name = propertyName;
 
 			output = propertyName;
 

+ 27 - 0
examples/jsm/nodes/accessors/StorageBufferNode.js

@@ -1,6 +1,8 @@
 import BufferNode from './BufferNode.js';
+import { bufferAttribute } from './BufferAttributeNode.js';
 import { addNodeClass } from '../core/Node.js';
 import { nodeObject } from '../shadernode/ShaderNode.js';
+import { varying } from '../core/VaryingNode.js';
 
 class StorageBufferNode extends BufferNode {
 
@@ -10,6 +12,9 @@ class StorageBufferNode extends BufferNode {
 
 		this.isStorageBufferNode = true;
 
+		this._attribute = null;
+		this._varying = null;
+
 	}
 
 	getInputType( /*builder*/ ) {
@@ -18,6 +23,28 @@ class StorageBufferNode extends BufferNode {
 
 	}
 
+	generate( builder ) {
+
+		if ( builder.isAvailable( 'storageBuffer' ) ) return super.generate( builder );
+
+		const nodeType = this.getNodeType( builder );
+
+		if ( this._attribute === null ) {
+
+			this._attribute = bufferAttribute( this.value );
+			this._varying = varying( this._attribute );
+
+		}
+
+
+		const output = this._varying.build( builder, nodeType );
+
+		builder.registerTransform( output, this._attribute );
+
+		return output;
+
+	}
+
 }
 
 export default StorageBufferNode;

+ 6 - 0
examples/jsm/nodes/utils/ArrayElementNode.js

@@ -24,6 +24,12 @@ class ArrayElementNode extends Node { // @TODO: If extending from TempNode it br
 		const nodeSnippet = this.node.build( builder );
 		const indexSnippet = this.indexNode.build( builder, 'uint' );
 
+		if ( this.node.isStorageBufferNode && ! builder.isAvailable( 'storageBuffer' ) ) {
+
+			return nodeSnippet;
+
+		}
+
 		return `${nodeSnippet}[ ${indexSnippet} ]`;
 
 	}

+ 6 - 0
examples/jsm/renderers/common/Backend.js

@@ -174,6 +174,12 @@ class Backend {
 
 	}
 
+	has( object ) {
+
+		return this.data.has( object );
+
+	}
+
 	delete( object ) {
 
 		this.data.delete( object );

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

@@ -48,7 +48,7 @@ class Bindings extends DataMap {
 
 			const nodeBuilderState = this.nodes.getForCompute( computeNode );
 
-			const bindings = nodeBuilderState.bindings.compute;
+			const bindings = nodeBuilderState.bindings;
 
 			data.bindings = bindings;
 

+ 4 - 4
examples/jsm/renderers/common/Pipelines.js

@@ -42,18 +42,18 @@ class Pipelines extends DataMap {
 
 			// get shader
 
-			const nodeBuilder = this.nodes.getForCompute( computeNode );
+			const nodeBuilderState = this.nodes.getForCompute( computeNode );
 
 			// programmable stage
 
-			let stageCompute = this.programs.compute.get( nodeBuilder.computeShader );
+			let stageCompute = this.programs.compute.get( nodeBuilderState.computeShader );
 
 			if ( stageCompute === undefined ) {
 
 				if ( previousPipeline && previousPipeline.computeProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.computeProgram );
 
-				stageCompute = new ProgrammableStage( nodeBuilder.computeShader, 'compute' );
-				this.programs.compute.set( nodeBuilder.computeShader, stageCompute );
+				stageCompute = new ProgrammableStage( nodeBuilderState.computeShader, 'compute', nodeBuilderState.transforms, nodeBuilderState.nodeAttributes );
+				this.programs.compute.set( nodeBuilderState.computeShader, stageCompute );
 
 				backend.createProgram( stageCompute );
 

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

@@ -2,12 +2,14 @@ let _id = 0;
 
 class ProgrammableStage {
 
-	constructor( code, type ) {
+	constructor( code, type, transforms = null, attributes = null ) {
 
 		this.id = _id ++;
 
 		this.code = code;
 		this.stage = type;
+		this.transforms = transforms;
+		this.attributes = attributes;
 
 		this.usedTimes = 0;
 

+ 21 - 0
examples/jsm/renderers/common/StorageBufferAttribute.js

@@ -0,0 +1,21 @@
+import { InstancedBufferAttribute } from 'three';
+
+class StorageBufferAttribute extends InstancedBufferAttribute {
+
+	constructor( array, itemSize ) {
+
+		super( array, itemSize );
+
+		this.isStorageBufferAttribute = true;
+
+	}
+
+	static create( count, itemSize, typeClass = Float32Array ) {
+
+		return new StorageBufferAttribute( new typeClass( count * itemSize ), itemSize );
+
+	}
+
+}
+
+export default StorageBufferAttribute;

+ 2 - 1
examples/jsm/renderers/common/nodes/NodeBuilderState.js

@@ -1,10 +1,11 @@
 class NodeBuilderState {
 
-	constructor( vertexShader, fragmentShader, computeShader, nodeAttributes, bindings, updateNodes, updateBeforeNodes ) {
+	constructor( vertexShader, fragmentShader, computeShader, nodeAttributes, bindings, updateNodes, updateBeforeNodes, transforms = [] ) {
 
 		this.vertexShader = vertexShader;
 		this.fragmentShader = fragmentShader;
 		this.computeShader = computeShader;
+		this.transforms = transforms;
 
 		this.nodeAttributes = nodeAttributes;
 		this.bindings = bindings;

+ 3 - 2
examples/jsm/renderers/common/nodes/Nodes.js

@@ -164,7 +164,7 @@ class Nodes extends DataMap {
 
 			nodeBuilderState = this._createNodeBuilderState( nodeBuilder );
 
-			computeData.nodeBuilderState = nodeBuilder;
+			computeData.nodeBuilderState = nodeBuilderState;
 
 		}
 
@@ -181,7 +181,8 @@ class Nodes extends DataMap {
 			nodeBuilder.getAttributesArray(),
 			nodeBuilder.getBindings(),
 			nodeBuilder.updateNodes,
-			nodeBuilder.updateBeforeNodes
+			nodeBuilder.updateBeforeNodes,
+			nodeBuilder.transforms
 		);
 
 	}

+ 334 - 75
examples/jsm/renderers/webgl/WebGLBackend.js

@@ -42,6 +42,10 @@ class WebGLBackend extends Backend {
 		this.state = new WebGLState( this );
 		this.utils = new WebGLUtils( this );
 
+		this.vaoCache = {};
+		this.transformFeedbackCache = {};
+		this.discard = false;
+
 		this.extensions.get( 'EXT_color_buffer_float' );
 		this._currentContext = null;
 
@@ -350,26 +354,76 @@ class WebGLBackend extends Backend {
 
 	beginCompute( /*computeGroup*/ ) {
 
-		console.warn( 'Abstract class.' );
+		const gl = this.gl;
+
+		gl.bindFramebuffer( gl.FRAMEBUFFER, null );
 
 	}
 
-	compute( /*computeGroup, computeNode, bindings, pipeline*/ ) {
+	compute( computeGroup, computeNode, bindings, pipeline ) {
 
-		console.warn( 'Abstract class.' );
+		const gl = this.gl;
+
+		if ( ! this.discard )  {
+
+			// required here to handle async behaviour of render.compute()
+			gl.enable( gl.RASTERIZER_DISCARD );
+			this.discard = true;
+		}
+
+		const { programGPU, transformBuffers, attributes } = this.get( pipeline );
+
+		const vaoKey = this._getVaoKey( null, attributes );
+
+		let vaoGPU = this.vaoCache[ vaoKey ];
+
+		if ( vaoGPU === undefined ) {
+
+			this._createVao( null, attributes );
+
+		} else {
+
+			gl.bindVertexArray( vaoGPU );
+
+		}
+
+		gl.useProgram( programGPU );
+
+		this._bindUniforms( bindings );
+
+		const transformFeedbackGPU = this._getTransformFeedback( transformBuffers );
+
+		gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, transformFeedbackGPU );
+		gl.beginTransformFeedback( gl.POINTS );
+
+		gl.drawArraysInstanced( gl.POINTS, 0, 1, computeNode.count );
+
+		gl.endTransformFeedback();
+		gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, null );
+
+		// switch active buffers
+		for ( let i = 0; i < transformBuffers.length; i ++ ) {
+
+			transformBuffers[ i ].switchBuffers();
+
+		}
 
 	}
 
 	finishCompute( /*computeGroup*/ ) {
 
-		console.warn( 'Abstract class.' );
+		const gl = this.gl;
+
+		this.discard = false;
+
+		gl.disable( gl.RASTERIZER_DISCARD );
 
 	}
 
 	draw( renderObject, info ) {
 
 		const { pipeline, material, context } = renderObject;
-		const { programGPU, vaoGPU } = this.get( pipeline );
+		const { programGPU } = this.get( pipeline );
 
 		const { gl, state } = this;
 
@@ -377,28 +431,34 @@ class WebGLBackend extends Backend {
 
 		//
 
-		const bindings = renderObject.getBindings();
+		this._bindUniforms( renderObject.getBindings() );
 
-		for ( const binding of bindings ) {
+		state.setMaterial( material );
 
-			const bindingData = this.get( binding );
-			const index = bindingData.index;
+		gl.useProgram( programGPU );
 
-			if ( binding.isUniformsGroup || binding.isUniformBuffer ) {
+		//
 
-				gl.bindBufferBase( gl.UNIFORM_BUFFER, index, bindingData.bufferGPU );
+		let vaoGPU = renderObject.staticVao;
 
-			} else if ( binding.isSampledTexture ) {
+		if ( vaoGPU === undefined ) {
 
-				state.bindTexture( bindingData.glTextureType, bindingData.textureGPU, gl.TEXTURE0 + index );
+			const vaoKey = this._getVaoKey( renderObject.getIndex(), renderObject.getAttributes() );
+
+			vaoGPU = this.vaoCache[ vaoKey ];
+
+			if ( vaoGPU === undefined ) {
+
+				let staticVao;
+
+				( { vaoGPU, staticVao } = this._createVao( renderObject.getIndex(), renderObject.getAttributes() ) );
+
+				if ( staticVao ) renderObject.staticVao = vaoGPU;
 
 			}
 
 		}
 
-		state.setMaterial( material );
-
-		gl.useProgram( programGPU );
 		gl.bindVertexArray( vaoGPU );
 
 		//
@@ -480,7 +540,6 @@ class WebGLBackend extends Backend {
 
 			}
 
-
 			info.update( object, indexCount, 1 );
 
 		} else {
@@ -504,8 +563,6 @@ class WebGLBackend extends Backend {
 
 		}
 
-
-
 		//
 
 		gl.bindVertexArray( null );
@@ -586,7 +643,7 @@ class WebGLBackend extends Backend {
 		const gl = this.gl;
 		const { stage, code } = program;
 
-		const shader = stage === 'vertex' ? gl.createShader( gl.VERTEX_SHADER ) : gl.createShader( gl.FRAGMENT_SHADER );
+		const shader = stage === 'fragment' ? gl.createShader( gl.FRAGMENT_SHADER ) : gl.createShader( gl.VERTEX_SHADER );
 
 		gl.shaderSource( shader, code );
 		gl.compileShader( shader );
@@ -634,105 +691,114 @@ class WebGLBackend extends Backend {
 
 		// Bindings
 
-		const bindings = renderObject.getBindings();
+		this._setupBindings( renderObject.getBindings(), programGPU );
 
-		for ( const binding of bindings ) {
+		//
 
-			const bindingData = this.get( binding );
-			const index = bindingData.index;
+		this.set( pipeline, {
+			programGPU
+		} );
 
-			if ( binding.isUniformsGroup || binding.isUniformBuffer ) {
+	}
 
-				const location = gl.getUniformBlockIndex( programGPU, binding.name );
-				gl.uniformBlockBinding( programGPU, location, index );
+	createComputePipeline( computePipeline, bindings ) {
 
-			} else if ( binding.isSampledTexture ) {
+		const gl = this.gl;
 
-				const location = gl.getUniformLocation( programGPU, binding.name );
-				gl.uniform1i( location, index );
+		// Program
 
-			}
+		const fragmentProgram = {
+			stage: 'fragment',
+			code: "#version 300 es\nprecision highp float;\nvoid main() {}"
+		};
 
-		}
+		this.createProgram( fragmentProgram );
 
-		// VAO
+		const { computeProgram } = computePipeline;
 
-		const vaoGPU = gl.createVertexArray();
+		const programGPU = gl.createProgram();
 
-		const index = renderObject.getIndex();
-		const attributes = renderObject.getAttributes();
+		const fragmentShader = this.get( fragmentProgram ).shaderGPU;
+		const vertexShader = this.get( computeProgram ).shaderGPU;
 
-		gl.bindVertexArray( vaoGPU );
+		const transforms = computeProgram.transforms;
 
-		if ( index !== null ) {
+		const transformVaryingNames = [];
+		const transformAttributeNodes = [];
 
-			const indexData = this.get( index );
+		for ( let i = 0; i < transforms.length; i ++ ) {
 
-			gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, indexData.bufferGPU );
+			const transform = transforms[ i ];
+
+			transformVaryingNames.push( transform.varyingName );
+			transformAttributeNodes.push( transform.attributeNode );
 
 		}
 
-		for ( let i = 0; i < attributes.length; i ++ ) {
+		gl.attachShader( programGPU, fragmentShader );
+		gl.attachShader( programGPU, vertexShader );
 
-			const attribute = attributes[ i ];
-			const attributeData = this.get( attribute );
+		gl.transformFeedbackVaryings(
+			programGPU,
+			transformVaryingNames,
+			gl.SEPARATE_ATTRIBS,
+		);
 
-			gl.bindBuffer( gl.ARRAY_BUFFER, attributeData.bufferGPU );
-			gl.enableVertexAttribArray( i );
+		gl.linkProgram( programGPU );
 
-			let stride, offset;
+		if ( gl.getProgramParameter( programGPU, gl.LINK_STATUS ) === false ) {
 
-			if ( attribute.isInterleavedBufferAttribute === true ) {
+			console.error( 'THREE.WebGLBackend:', gl.getProgramInfoLog( programGPU ) );
 
-				stride = attribute.data.stride * attributeData.bytesPerElement;
-				offset = attribute.offset * attributeData.bytesPerElement;
+			console.error( 'THREE.WebGLBackend:', gl.getShaderInfoLog( fragmentShader ) );
+			console.error( 'THREE.WebGLBackend:', gl.getShaderInfoLog( vertexShader ) );
 
-			} else {
+		}
 
-				stride = 0;
-				offset = 0;
+		gl.useProgram( programGPU );
 
-			}
+		// Bindings
 
-			if ( attributeData.isInteger ) {
+		this.createBindings( bindings );
 
-				gl.vertexAttribIPointer( i, attribute.itemSize, attributeData.type, stride, offset );
+		this._setupBindings( bindings, programGPU );
 
-			} else {
+		const attributeNodes = computeProgram.attributes;
+		const attributes = [];
+		const transformBuffers = [];
 
-				gl.vertexAttribPointer( i, attribute.itemSize, attributeData.type, attribute.normalized, stride, offset );
+		for ( let i = 0; i < attributeNodes.length; i ++ ) {
 
-			}
+			const attribute = attributeNodes[ i ].node.attribute;
 
-			if ( attribute.isInstancedBufferAttribute && ! attribute.isInterleavedBufferAttribute ) {
+			attributes.push( attribute );
 
-				gl.vertexAttribDivisor( i, attribute.meshPerAttribute );
+			if ( ! this.has( attribute ) ) this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER );
 
-			} else if ( attribute.isInterleavedBufferAttribute && attribute.data.isInstancedInterleavedBuffer ) {
+		}
 
-				gl.vertexAttribDivisor( i, attribute.data.meshPerAttribute );
+		for ( let i = 0; i < transformAttributeNodes.length; i ++ ) {
 
-			}
+			const attribute = transformAttributeNodes[ i ].attribute;
 
-		}
+			if ( ! this.has( attribute ) ) this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER );
 
-		gl.bindVertexArray( null );
+			const attributeData = this.get( attribute );
+
+			transformBuffers.push( attributeData );
+
+		}
 
 		//
 
-		this.set( pipeline, {
+		this.set( computePipeline, {
 			programGPU,
-			vaoGPU
+			transformBuffers,
+			attributes
 		} );
 
 	}
 
-	createComputePipeline( /*computePipeline, bindings*/ ) {
-
-		console.warn( 'Abstract class.' );
-
-	}
-
 	createBindings( bindings ) {
 
 		this.updateBindings( bindings );
@@ -807,15 +873,17 @@ class WebGLBackend extends Backend {
 
 	createAttribute( attribute ) {
 
+		if ( this.has( attribute ) ) return;
+
 		const gl = this.gl;
 
 		this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER );
 
 	}
 
-	createStorageAttribute( /*attribute*/ ) {
+	createStorageAttribute( attribute ) {
 
-		console.warn( 'Abstract class.' );
+		//console.warn( 'Abstract class.' );
 
 	}
 
@@ -1026,6 +1094,197 @@ class WebGLBackend extends Backend {
 
 	}
 
+
+	_getVaoKey( index, attributes ) {
+
+		let key = [];
+
+		if ( index !== null ) {
+
+			const indexData = this.get( index );
+
+			key += ':' + indexData.id;
+
+		}
+
+		for ( let i = 0; i < attributes.length; i ++ ) {
+
+			const attributeData = this.get( attributes[ i ] );
+
+			key += ':' + attributeData.id;
+
+		}
+
+		return key;
+
+	}
+
+	_createVao( index, attributes ) {
+
+		const { gl } = this;
+
+		const vaoGPU = gl.createVertexArray();
+		let key = '';
+
+		let staticVao = true;
+
+		gl.bindVertexArray( vaoGPU );
+
+		if ( index !== null ) {
+
+			const indexData = this.get( index );
+
+			gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, indexData.bufferGPU );
+
+			key += ':' + indexData.id;
+
+		}
+
+		for ( let i = 0; i < attributes.length; i ++ ) {
+
+			const attribute = attributes[ i ];
+			const attributeData = this.get( attribute );
+
+			key += ':' + attributeData.id;
+
+			gl.bindBuffer( gl.ARRAY_BUFFER, attributeData.bufferGPU );
+			gl.enableVertexAttribArray( i );
+
+			if ( attribute.isStorageBufferAttribute ) staticVao = false;
+
+			let stride, offset;
+
+			if ( attribute.isInterleavedBufferAttribute === true ) {
+
+				stride = attribute.data.stride * attributeData.bytesPerElement;
+				offset = attribute.offset * attributeData.bytesPerElement;
+
+			} else {
+
+				stride = 0;
+				offset = 0;
+
+			}
+
+			if ( attributeData.isInteger ) {
+
+				gl.vertexAttribIPointer( i, attribute.itemSize, attributeData.type, stride, offset );
+
+			} else {
+
+				gl.vertexAttribPointer( i, attribute.itemSize, attributeData.type, attribute.normalized, stride, offset );
+
+			}
+
+			if ( attribute.isInstancedBufferAttribute && ! attribute.isInterleavedBufferAttribute ) {
+
+				gl.vertexAttribDivisor( i, attribute.meshPerAttribute );
+
+			} else if ( attribute.isInterleavedBufferAttribute && attribute.data.isInstancedInterleavedBuffer ) {
+
+				gl.vertexAttribDivisor( i, attribute.data.meshPerAttribute );
+
+			}
+
+		}
+
+		gl.bindBuffer( gl.ARRAY_BUFFER, null );
+
+		this.vaoCache[ key ] = vaoGPU;
+
+		return { vaoGPU, staticVao };
+
+	}
+
+	_getTransformFeedback( transformBuffers ) {
+
+		let key = '';
+
+		for ( let i = 0; i < transformBuffers.length; i ++ ) {
+
+			key += ':' + transformBuffers[ i ].id;
+
+		}
+
+		let transformFeedbackGPU = this.transformFeedbackCache[ key ];
+
+		if ( transformFeedbackGPU !== undefined ) {
+
+			return transformFeedbackGPU;
+
+		}
+
+		const gl = this.gl;
+
+		transformFeedbackGPU = gl.createTransformFeedback();
+
+		gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, transformFeedbackGPU );
+
+		for ( let i = 0; i < transformBuffers.length; i ++ ) {
+
+			const attributeData = transformBuffers[ i ];
+
+			gl.bindBufferBase( gl.TRANSFORM_FEEDBACK_BUFFER, i, attributeData.transformBuffer );
+
+		}
+
+		gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, null );
+
+		this.transformFeedbackCache[ key ] = transformFeedbackGPU;
+
+		return transformFeedbackGPU;
+
+	}
+
+
+	_setupBindings( bindings, programGPU ) {
+
+		const gl = this.gl;
+
+		for ( const binding of bindings ) {
+
+			const bindingData = this.get( binding );
+			const index = bindingData.index;
+
+			if ( binding.isUniformsGroup || binding.isUniformBuffer ) {
+
+				const location = gl.getUniformBlockIndex( programGPU, binding.name );
+				gl.uniformBlockBinding( programGPU, location, index );
+
+			} else if ( binding.isSampledTexture ) {
+
+				const location = gl.getUniformLocation( programGPU, binding.name );
+				gl.uniform1i( location, index );
+
+			}
+
+		}
+
+	}
+
+	_bindUniforms( bindings ) {
+
+		const { gl, state } = this;
+
+		for ( const binding of bindings ) {
+
+			const bindingData = this.get( binding );
+			const index = bindingData.index;
+
+			if ( binding.isUniformsGroup || binding.isUniformBuffer ) {
+
+				gl.bindBufferBase( gl.UNIFORM_BUFFER, index, bindingData.bufferGPU );
+
+			} else if ( binding.isSampledTexture ) {
+
+				state.bindTexture( bindingData.glTextureType, bindingData.textureGPU, gl.TEXTURE0 + index );
+
+			}
+
+		}
+
+	}
+
 }
 
 export default WebGLBackend;

+ 35 - 4
examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js

@@ -36,6 +36,7 @@ class GLSLNodeBuilder extends NodeBuilder {
 		super( object, renderer, new GLSLNodeParser(), scene );
 
 		this.uniformGroups = {};
+		this.transforms = [];
 
 	}
 
@@ -279,7 +280,7 @@ ${ flowData.code }
 
 		let snippet = '';
 
-		if ( shaderStage === 'vertex' ) {
+		if ( shaderStage === 'vertex' || shaderStage === 'compute' ) {
 
 			const attributes = this.getAttributesArray();
 
@@ -346,10 +347,11 @@ ${ flowData.code }
 
 		const varyings = this.varyings;
 
-		if ( shaderStage === 'vertex' ) {
+		if ( shaderStage === 'vertex' || shaderStage === 'compute' ) {
 
 			for ( const varying of varyings ) {
 
+				if ( shaderStage === 'compute' ) varying.needsInterpolation = true;
 				const type = varying.type;
 				const flat = type === 'int' || type === 'uint' ? 'flat ' : '';
 
@@ -421,6 +423,32 @@ ${ flowData.code }
 
 	}
 
+	registerTransform( varyingName, attributeNode ) {
+
+		this.transforms.push( { varyingName, attributeNode } );
+
+	}
+
+	getTransforms( /* shaderStage  */ ) {
+
+		const transforms = this.transforms;
+
+		let snippet = '';
+
+		for ( let i = 0; i < transforms.length; i ++ ) {
+
+			const transform = transforms[ i ];
+
+			const attributeName = this.getPropertyName( transform.attributeNode );
+
+			snippet += `${ transform.varyingName } = ${ attributeName };\n\t`;
+
+		}
+
+		return snippet;
+
+	}
+
 	_getGLSLUniformStruct( name, vars ) {
 
 		return `
@@ -456,6 +484,9 @@ void main() {
 	// vars
 	${shaderData.vars}
 
+	// transforms
+	${shaderData.transforms}
+
 	// flow
 	${shaderData.flow}
 
@@ -558,6 +589,7 @@ void main() {
 			stageData.vars = this.getVars( shaderStage );
 			stageData.structs = this.getStructs( shaderStage );
 			stageData.codes = this.getCodes( shaderStage );
+			stageData.transforms = this.getTransforms( shaderStage );
 			stageData.flow = flow;
 
 		}
@@ -569,8 +601,7 @@ void main() {
 
 		} else {
 
-			console.warn( 'GLSLNodeBuilder: compute shaders are not supported.' );
-			//this.computeShader = this._getGLSLComputeCode( shadersData.compute );
+			this.computeShader = this._getGLSLVertexCode( shadersData.compute );
 
 		}
 

+ 70 - 8
examples/jsm/renderers/webgl/utils/WebGLAttributeUtils.js

@@ -1,5 +1,48 @@
 import { IntType } from 'three';
 
+let _id = 0;
+
+class DualAttributeData {
+
+	constructor( attributeData, dualBuffer ) {
+
+		this.buffers = [ attributeData.bufferGPU, dualBuffer ];
+		this.type = attributeData.type;
+		this.bytesPerElement = attributeData.BYTES_PER_ELEMENT;
+		this.version = attributeData.version;
+		this.isInteger = attributeData.isInteger;
+		this.activeBufferIndex = 0;
+		this.baseId = attributeData.id;
+
+	}
+
+
+	get id() {
+
+		return `${ this.baseId }|${ this.activeBufferIndex }`;
+
+	}
+
+	get bufferGPU() {
+
+		return this.buffers[ this.activeBufferIndex ];
+
+	}
+
+	get transformBuffer() {
+
+		return this.buffers[ this.activeBufferIndex ^ 1 ];
+
+	}
+
+	switchBuffers() {
+
+		this.activeBufferIndex ^= 1;
+
+	}
+
+}
+
 class WebGLAttributeUtils {
 
 	constructor( backend ) {
@@ -23,11 +66,7 @@ class WebGLAttributeUtils {
 
 		if ( bufferGPU === undefined ) {
 
-			bufferGPU = gl.createBuffer();
-
-			gl.bindBuffer( bufferType, bufferGPU );
-			gl.bufferData( bufferType, array, usage );
-			gl.bindBuffer( bufferType, null );
+			bufferGPU = this._createBuffer( gl, bufferType, array, usage );
 
 			bufferData.bufferGPU = bufferGPU;
 			bufferData.bufferType = bufferType;
@@ -85,13 +124,24 @@ class WebGLAttributeUtils {
 
 		}
 
-		backend.set( attribute, {
+		let attributeData = {
 			bufferGPU,
 			type,
 			bytesPerElement: array.BYTES_PER_ELEMENT,
 			version: attribute.version,
-			isInteger: type === gl.INT || type === gl.UNSIGNED_INT || attribute.gpuType === IntType
-		} );
+			isInteger: type === gl.INT || type === gl.UNSIGNED_INT || attribute.gpuType === IntType,
+			id: _id ++
+		};
+
+		if ( attribute.isStorageBufferAttribute ) {
+
+			// create buffer for tranform feedback use
+			const bufferGPUDual = this._createBuffer( gl, bufferType, array, usage );
+			attributeData = new DualAttributeData( attributeData, bufferGPUDual );
+
+		}
+
+		backend.set( attribute, attributeData );
 
 	}
 
@@ -185,6 +235,18 @@ class WebGLAttributeUtils {
 
 	}
 
+	_createBuffer( gl, bufferType, array, usage ) {
+
+		const bufferGPU = gl.createBuffer();
+
+		gl.bindBuffer( bufferType, bufferGPU );
+		gl.bufferData( bufferType, array, usage );
+		gl.bindBuffer( bufferType, null );
+
+		return bufferGPU;
+
+	}
+
 }
 
 export default WebGLAttributeUtils;

+ 2 - 1
examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js

@@ -25,7 +25,8 @@ const gpuShaderStageLib = {
 };
 
 const supports = {
-	instance: true
+	instance: true,
+	storageBuffer: true
 };
 
 const wgslFnOpLib = {

+ 9 - 1
examples/jsm/renderers/webgpu/utils/WebGPUAttributeUtils.js

@@ -42,7 +42,15 @@ class WebGPUAttributeUtils {
 
 			const device = backend.device;
 
-			const array = bufferAttribute.array;
+			let array = bufferAttribute.array;
+
+			if ( bufferAttribute.isStorageBufferAttribute && bufferAttribute.itemSize === 3 ) {
+
+				bufferAttribute.itemSize = 4;
+				array = new array.constructor( bufferAttribute.count * 4 );
+
+			}
+
 			const size = array.byteLength + ( ( 4 - ( array.byteLength % 4 ) ) % 4 ); // ensure 4 byte alignment, see #20441
 
 			buffer = device.createBuffer( {

BIN
examples/screenshots/webgpu_compute_particles_rain.jpg


BIN
examples/screenshots/webgpu_compute_points.jpg


+ 3 - 2
examples/webgpu_compute_audio.html

@@ -35,6 +35,7 @@
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+			import StorageBufferAttribute from 'three/addons/renderers/common/StorageBufferAttribute.js';
 
 			let camera, scene, renderer;
 			let computeNode;
@@ -115,14 +116,14 @@
 
 				// create webgpu buffers
 
-				waveGPUBuffer = new THREE.InstancedBufferAttribute( waveBuffer, 1 );
+				waveGPUBuffer = new StorageBufferAttribute( waveBuffer, 1 );
 
 				const waveStorageNode = storage( waveGPUBuffer, 'float', waveBuffer.length );
 
 
 				// read-only buffer
 
-				const waveNode = storage( new THREE.InstancedBufferAttribute( waveBuffer, 1 ), 'float', waveBuffer.length );
+				const waveNode = storage( new StorageBufferAttribute( waveBuffer, 1 ), 'float', waveBuffer.length );
 
 
 				// params

+ 5 - 3
examples/webgpu_compute_particles.html

@@ -27,7 +27,9 @@
 			import { tslFn, uniform, texture, instanceIndex, float, vec3, storage, SpriteNodeMaterial, If } from 'three/nodes';
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+			import StorageBufferAttribute from 'three/addons/renderers/common/StorageBufferAttribute.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import Stats from 'three/addons/libs/stats.module.js';
@@ -51,11 +53,11 @@
 
 			function init() {
 
-				if ( WebGPU.isAvailable() === false ) {
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
 
 					document.body.appendChild( WebGPU.getErrorMessage() );
 
-					throw new Error( 'No WebGPU support' );
+					throw new Error( 'No WebGPU or WebGL2 support' );
 
 				}
 
@@ -73,7 +75,7 @@
 
 				//
 
-				const createBuffer = () => storage( new THREE.InstancedBufferAttribute( new Float32Array( particleCount * 4 ), 4 ), 'vec3', particleCount );
+				const createBuffer = () => storage( StorageBufferAttribute.create( particleCount, 3 ), 'vec3', particleCount );
 
 				const positionBuffer = createBuffer();
 				const velocityBuffer = createBuffer();

+ 5 - 3
examples/webgpu_compute_particles_rain.html

@@ -27,7 +27,9 @@
 			import { tslFn, texture, uv, uint, positionWorld, modelWorldMatrix, cameraViewMatrix, timerLocal, timerDelta, cameraProjectionMatrix, vec2, instanceIndex, positionGeometry, storage, MeshBasicNodeMaterial, If } from 'three/nodes';
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+			import StorageBufferAttribute from 'three/addons/renderers/common/StorageBufferAttribute.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import Stats from 'three/addons/libs/stats.module.js';
@@ -52,11 +54,11 @@
 
 			function init() {
 
-				if ( WebGPU.isAvailable() === false ) {
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
 
 					document.body.appendChild( WebGPU.getErrorMessage() );
 
-					throw new Error( 'No WebGPU support' );
+					throw new Error( 'No WebGPU or WebGL2 support' );
 
 				}
 
@@ -101,7 +103,7 @@
 
 				//
 
-				const createBuffer = ( type = 'vec3' ) => storage( new THREE.InstancedBufferAttribute( new Float32Array( maxParticleCount * 4 ), 4 ), type, maxParticleCount );
+				const createBuffer = ( type = 'vec3' ) => storage( StorageBufferAttribute.create( maxParticleCount, 3 ), type, maxParticleCount );
 
 				const positionBuffer = createBuffer();
 				const velocityBuffer = createBuffer();

+ 5 - 3
examples/webgpu_compute_particles_snow.html

@@ -29,7 +29,9 @@
 			import { TeapotGeometry } from 'three/addons/geometries/TeapotGeometry.js';
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+			import StorageBufferAttribute from 'three/addons/renderers/common/StorageBufferAttribute.js';
 
 			import PostProcessing from 'three/addons/renderers/common/PostProcessing.js';
 
@@ -50,11 +52,11 @@
 
 			function init() {
 
-				if ( WebGPU.isAvailable() === false ) {
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
 
 					document.body.appendChild( WebGPU.getErrorMessage() );
 
-					throw new Error( 'No WebGPU support' );
+					throw new Error( 'No WebGPU or WebGL2 support' );
 
 				}
 
@@ -102,7 +104,7 @@
 
 				//
 
-				const createBuffer = ( type = 'vec3' ) => storage( new THREE.InstancedBufferAttribute( new Float32Array( maxParticleCount * 4 ), 4 ), type, maxParticleCount );
+				const createBuffer = ( type = 'vec3' ) => storage( StorageBufferAttribute.create( maxParticleCount, type === 'vec4' ? 4 : 3 ), type, maxParticleCount );
 
 				const positionBuffer = createBuffer();
 				const scaleBuffer = createBuffer();

+ 9 - 6
examples/webgpu_compute_points.html

@@ -29,7 +29,10 @@
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
+
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+			import StorageBufferAttribute from 'three/addons/renderers/common/StorageBufferAttribute.js';
 
 			let camera, scene, renderer;
 			let computeNode;
@@ -41,11 +44,11 @@
 
 			function init() {
 
-				if ( WebGPU.isAvailable() === false ) {
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
 
 					document.body.appendChild( WebGPU.getErrorMessage() );
 
-					throw new Error( 'No WebGPU support' );
+					throw new Error( 'No WebGPU or WebGL2 support' );
 
 				}
 
@@ -59,13 +62,13 @@
 				const particleNum = 300000;
 				const particleSize = 2; // vec2
 
-				const particleArray = new Float32Array( particleNum * particleSize );
-				const velocityArray = new Float32Array( particleNum * particleSize );
+//				const particleArray = new Float32Array( particleNum * particleSize );
+//				const velocityArray = new Float32Array( particleNum * particleSize );
 
 				// create buffers
 
-				const particleBuffer = new THREE.InstancedBufferAttribute( particleArray, 2 );
-				const velocityBuffer = new THREE.InstancedBufferAttribute( velocityArray, 2 );
+				const particleBuffer = StorageBufferAttribute.create( particleNum, particleSize );
+				const velocityBuffer = StorageBufferAttribute.create( particleNum, particleSize );
 
 				const particleBufferNode = storage( particleBuffer, 'vec2', particleNum );
 				const velocityBufferNode = storage( velocityBuffer, 'vec2', particleNum );

+ 7 - 5
test/e2e/puppeteer.js

@@ -112,10 +112,6 @@ const exceptionList = [
 	// Awaiting for WebGL backend support
 	'webgpu_clearcoat',
 	'webgpu_compute_audio',
-	'webgpu_compute_particles',
-	'webgpu_compute_particles_rain',
-	'webgpu_compute_particles_snow',
-	'webgpu_compute_points',
 	'webgpu_compute_texture',
 	'webgpu_compute_texture_pingpong',
 	'webgpu_materials',
@@ -137,7 +133,13 @@ const exceptionList = [
 	'webgpu_shadowmap',
 	'webgpu_tsl_editor',
 	'webgpu_tsl_transpiler',
-	'webgpu_portal'
+	'webgpu_portal',
+
+	// WebGPU idleTime and parseTime too low
+	'webgpu_compute_particles',
+	'webgpu_compute_particles_rain',
+	'webgpu_compute_particles_snow',
+	'webgpu_compute_points'
 
 ];