diff --git a/examples/jsm/nodes/Nodes.js b/examples/jsm/nodes/Nodes.js index 4421c4dcabae9b..38c9a68eda5658 100644 --- a/examples/jsm/nodes/Nodes.js +++ b/examples/jsm/nodes/Nodes.js @@ -44,6 +44,9 @@ import SkinningNode from './accessors/SkinningNode.js'; import TextureNode from './accessors/TextureNode.js'; import UVNode from './accessors/UVNode.js'; +// gpgpu +import ComputeNode from './gpgpu/ComputeNode.js'; + // display import ColorSpaceNode from './display/ColorSpaceNode.js'; import NormalMapNode from './display/NormalMapNode.js'; @@ -118,6 +121,9 @@ const nodeLib = { VarNode, VaryNode, + // compute + ComputeNode, + // accessors BufferNode, CameraNode, @@ -210,6 +216,9 @@ export { VarNode, VaryNode, + // compute + ComputeNode, + // accessors BufferNode, CameraNode, @@ -265,5 +274,4 @@ export { NodeLoader, NodeObjectLoader, NodeMaterialLoader - }; diff --git a/examples/jsm/nodes/accessors/StorageBufferNode.js b/examples/jsm/nodes/accessors/StorageBufferNode.js new file mode 100644 index 00000000000000..e000bdb3c5ea5c --- /dev/null +++ b/examples/jsm/nodes/accessors/StorageBufferNode.js @@ -0,0 +1,21 @@ +import BufferNode from './BufferNode.js'; + +class StorageBufferNode extends BufferNode { + + constructor( value, bufferType, bufferCount = 0 ) { + + super( value, bufferType, bufferCount ); + + } + + getInputType( /*builder*/ ) { + + return 'storageBuffer'; + + } + +} + +StorageBufferNode.prototype.isStorageBufferNode = true; + +export default StorageBufferNode; diff --git a/examples/jsm/nodes/core/NodeBuilder.js b/examples/jsm/nodes/core/NodeBuilder.js index 2fe31645b8a531..1a541c954b5ed8 100644 --- a/examples/jsm/nodes/core/NodeBuilder.js +++ b/examples/jsm/nodes/core/NodeBuilder.js @@ -8,9 +8,18 @@ import { NodeUpdateType } from './constants.js'; import { REVISION, LinearEncoding } from 'three'; -export const shaderStages = [ 'fragment', 'vertex' ]; +export const defaultShaderStages = [ 'fragment', 'vertex' ]; +export const shaderStages = [ ...defaultShaderStages, 'compute' ]; export const vector = [ 'x', 'y', 'z', 'w' ]; +const typeFromLength = new Map(); +typeFromLength.set( 1, 'float' ); +typeFromLength.set( 2, 'vec2' ); +typeFromLength.set( 3, 'vec3' ); +typeFromLength.set( 4, 'vec4' ); +typeFromLength.set( 9, 'mat3' ); +typeFromLength.set( 16, 'mat4' ); + const toFloat = ( value ) => { value = Number( value ); @@ -24,7 +33,7 @@ class NodeBuilder { constructor( object, renderer, parser ) { this.object = object; - this.material = object.material; + this.material = object.material || null; this.renderer = renderer; this.parser = parser; @@ -34,14 +43,15 @@ class NodeBuilder { this.vertexShader = null; this.fragmentShader = null; + this.computeShader = null; - this.flowNodes = { vertex: [], fragment: [] }; - this.flowCode = { vertex: '', fragment: '' }; - this.uniforms = { vertex: [], fragment: [], index: 0 }; - this.codes = { vertex: [], fragment: [] }; + this.flowNodes = { vertex: [], fragment: [], compute: [] }; + this.flowCode = { vertex: '', fragment: '', compute: [] }; + this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; + this.codes = { vertex: [], fragment: [], compute: [] }; this.attributes = []; this.varys = []; - this.vars = { vertex: [], fragment: [] }; + this.vars = { vertex: [], fragment: [], compute: [] }; this.flow = { code: '' }; this.stack = []; @@ -330,16 +340,9 @@ class NodeBuilder { } - getTypeFromLength( type ) { - - if ( type === 1 ) return 'float'; - if ( type === 2 ) return 'vec2'; - if ( type === 3 ) return 'vec3'; - if ( type === 4 ) return 'vec4'; - if ( type === 9 ) return 'mat3'; - if ( type === 16 ) return 'mat4'; + getTypeFromLength( length ) { - return 0; + return typeFromLength.get( length ); } @@ -369,7 +372,7 @@ class NodeBuilder { if ( nodeData === undefined ) { - nodeData = { vertex: {}, fragment: {} }; + nodeData = { vertex: {}, fragment: {}, compute: {} }; this.nodesData.set( node, nodeData ); @@ -592,7 +595,7 @@ class NodeBuilder { getHash() { - return this.vertexShader + this.fragmentShader; + return this.vertexShader + this.fragmentShader + this.computeShader; } diff --git a/examples/jsm/nodes/gpgpu/ComputeNode.js b/examples/jsm/nodes/gpgpu/ComputeNode.js new file mode 100644 index 00000000000000..4da4469476353f --- /dev/null +++ b/examples/jsm/nodes/gpgpu/ComputeNode.js @@ -0,0 +1,47 @@ +import Node from '../core/Node.js'; +import { NodeUpdateType } from '../core/constants.js'; + +class ComputeNode extends Node { + + constructor( dispatchCount, workgroupSize = [ 64 ] ) { + + super( 'void' ); + + this.updateType = NodeUpdateType.Object; + + this.dispatchCount = dispatchCount; + this.workgroupSize = workgroupSize; + + this.computeNode = null; + + } + + update( { renderer } ) { + + renderer.compute( this ); + + } + + generate( builder ) { + + const { shaderStage } = builder; + + if ( shaderStage === 'compute' ) { + + const snippet = this.computeNode.build( builder, 'void' ); + + if ( snippet !== '' ) { + + builder.addFlowCode( snippet ); + + } + + } + + } + +} + +ComputeNode.prototype.isComputeNode = true; + +export default ComputeNode; diff --git a/examples/jsm/nodes/materials/MeshStandardNodeMaterial.js b/examples/jsm/nodes/materials/MeshStandardNodeMaterial.js index 9a1b6ec1e3252f..34374ce1e68dff 100644 --- a/examples/jsm/nodes/materials/MeshStandardNodeMaterial.js +++ b/examples/jsm/nodes/materials/MeshStandardNodeMaterial.js @@ -1,7 +1,7 @@ import NodeMaterial from './NodeMaterial.js'; import { float, vec3, vec4, - assign, label, mul, invert, mix, + context, assign, label, mul, invert, mix, normalView, materialRoughness, materialMetalness } from '../shadernode/ShaderNodeElements.js'; @@ -61,9 +61,13 @@ export default class MeshStandardNodeMaterial extends NodeMaterial { generateLight( builder, { diffuseColorNode, lightNode } ) { - const outgoingLightNode = super.generateLight( builder, { diffuseColorNode, lightNode, lightingModelNode: PhysicalLightingModel } ); + let outgoingLightNode = super.generateLight( builder, { diffuseColorNode, lightNode, lightingModelNode: PhysicalLightingModel } ); - // @TODO: add IBL code here + // TONE MAPPING + + const renderer = builder.renderer; + + if ( renderer.toneMappingNode ) outgoingLightNode = context( renderer.toneMappingNode, { color: outgoingLightNode } ); return outgoingLightNode; diff --git a/examples/jsm/nodes/materials/NodeMaterial.js b/examples/jsm/nodes/materials/NodeMaterial.js index eaf220c2a61e80..7f51110c62b782 100644 --- a/examples/jsm/nodes/materials/NodeMaterial.js +++ b/examples/jsm/nodes/materials/NodeMaterial.js @@ -4,7 +4,7 @@ import ExpressionNode from '../core/ExpressionNode.js'; import { float, vec3, vec4, assign, label, mul, add, bypass, - positionLocal, skinning, instance, modelViewProjection, context, lightContext, colorSpace, + positionLocal, skinning, instance, modelViewProjection, lightContext, colorSpace, materialAlphaTest, materialColor, materialOpacity } from '../shadernode/ShaderNodeElements.js'; @@ -112,16 +112,10 @@ class NodeMaterial extends ShaderMaterial { generateOutput( builder, { diffuseColorNode, outgoingLightNode } ) { - const { renderer } = builder; - // OUTPUT let outputNode = vec4( outgoingLightNode, diffuseColorNode.a ); - // TONE MAPPING - - if ( renderer.toneMappingNode ) outputNode = context( renderer.toneMappingNode, { color: outputNode } ); - // ENCODING outputNode = colorSpace( outputNode, builder.renderer.outputEncoding ); diff --git a/examples/jsm/nodes/parsers/WGSLNodeFunction.js b/examples/jsm/nodes/parsers/WGSLNodeFunction.js index d442b13812218a..0119477e37ef70 100644 --- a/examples/jsm/nodes/parsers/WGSLNodeFunction.js +++ b/examples/jsm/nodes/parsers/WGSLNodeFunction.js @@ -1,9 +1,13 @@ import NodeFunction from '../core/NodeFunction.js'; import NodeFunctionInput from '../core/NodeFunctionInput.js'; -const declarationRegexp = /^fn\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)\s*\-\>\s*([a-z_0-9]+)?/i; +const declarationRegexp = /^[fn]*\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)\s*[\-\>]*\s*([a-z_0-9]+)?/i; const propertiesRegexp = /[a-z_0-9]+/ig; +const wgslTypeLib = { + f32: 'float' +}; + const parse = ( source ) => { source = source.trim(); @@ -36,7 +40,9 @@ const parse = ( source ) => { // default const name = propsMatches[ i ++ ][ 0 ]; - const type = propsMatches[ i ++ ][ 0 ]; + let type = propsMatches[ i ++ ][ 0 ]; + + type = wgslTypeLib[ type ] || type; // precision @@ -54,7 +60,7 @@ const parse = ( source ) => { const blockCode = source.substring( declaration[ 0 ].length ); const name = declaration[ 1 ] !== undefined ? declaration[ 1 ] : ''; - const type = declaration[ 3 ]; + const type = declaration[ 3 ] || 'void'; return { type, @@ -87,7 +93,9 @@ class WGSLNodeFunction extends NodeFunction { getCode( name = this.name ) { - return `fn ${ name } ( ${ this.inputsCode.trim() } ) -> ${ this.type }` + this.blockCode; + const type = this.type !== 'void' ? '-> ' + this.type : ''; + + return `fn ${ name } ( ${ this.inputsCode.trim() } ) ${ type }` + this.blockCode; } diff --git a/examples/jsm/nodes/shadernode/ShaderNodeElements.js b/examples/jsm/nodes/shadernode/ShaderNodeElements.js index cb11cd525a6c7a..164a48c811e29f 100644 --- a/examples/jsm/nodes/shadernode/ShaderNodeElements.js +++ b/examples/jsm/nodes/shadernode/ShaderNodeElements.js @@ -6,9 +6,11 @@ import UniformNode from '../core/UniformNode.js'; import BypassNode from '../core/BypassNode.js'; import InstanceIndexNode from '../core/InstanceIndexNode.js'; import ContextNode from '../core/ContextNode.js'; +import FunctionNode from '../core/FunctionNode.js'; // accessor nodes import BufferNode from '../accessors/BufferNode.js'; +import StorageBufferNode from '../accessors/StorageBufferNode.js'; import CameraNode from '../accessors/CameraNode.js'; import MaterialNode from '../accessors/MaterialNode.js'; import ModelNode from '../accessors/ModelNode.js'; @@ -20,6 +22,9 @@ import TextureNode from '../accessors/TextureNode.js'; import UVNode from '../accessors/UVNode.js'; import InstanceNode from '../accessors/InstanceNode.js'; +// gpgpu +import ComputeNode from '../gpgpu/ComputeNode.js'; + // math nodes import OperatorNode from '../math/OperatorNode.js'; import CondNode from '../math/CondNode.js'; @@ -29,6 +34,7 @@ import MathNode from '../math/MathNode.js'; import ArrayElementNode from '../utils/ArrayElementNode.js'; import ConvertNode from '../utils/ConvertNode.js'; import JoinNode from '../utils/JoinNode.js'; +import TimerNode from '../utils/TimerNode.js'; // other nodes import ColorSpaceNode from '../display/ColorSpaceNode.js'; @@ -109,9 +115,15 @@ export const join = ( ...params ) => nodeObject( new JoinNode( nodeArray( params export const uv = ( ...params ) => nodeObject( new UVNode( ...params ) ); export const attribute = ( ...params ) => nodeObject( new AttributeNode( ...params ) ); export const buffer = ( ...params ) => nodeObject( new BufferNode( ...params ) ); +export const storage = ( ...params ) => nodeObject( new StorageBufferNode( ...params ) ); export const texture = ( ...params ) => nodeObject( new TextureNode( ...params ) ); export const sampler = ( texture ) => nodeObject( new ConvertNode( texture.isNode === true ? texture : new TextureNode( texture ), 'sampler' ) ); +export const timer = ( ...params ) => nodeObject( new TimerNode( ...params ) ); + +export const compute = ( ...params ) => nodeObject( new ComputeNode( ...params ) ); +export const func = ( ...params ) => nodeObject( new FunctionNode( ...params ) ); + export const cond = nodeProxy( CondNode ); export const add = nodeProxy( OperatorNode, '+' ); diff --git a/examples/jsm/nodes/shadernode/ShaderNodeUtils.js b/examples/jsm/nodes/shadernode/ShaderNodeUtils.js index 81d492e024f0a8..45a5a0c2cc183e 100644 --- a/examples/jsm/nodes/shadernode/ShaderNodeUtils.js +++ b/examples/jsm/nodes/shadernode/ShaderNodeUtils.js @@ -141,13 +141,28 @@ const ShaderNodeProxy = function ( NodeClass, scope = null, factor = null ) { export const ShaderNodeScript = function ( jsFunc ) { - return { call: ( inputs, builder ) => { + //@TODO: Move this to Node extended class - inputs = new ShaderNodeObjects( inputs ); + const self = + { + build: ( builder ) => { - return new ShaderNodeObject( jsFunc( inputs, builder ) ); + self.call( {}, builder ); - } }; + return ''; + + }, + + call: ( inputs, builder ) => { + + inputs = new ShaderNodeObjects( inputs ); + + return new ShaderNodeObject( jsFunc( inputs, builder ) ); + + } + }; + + return self; }; diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js index ba4a75793367cb..7c1a3a8328ca15 100644 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js @@ -1,4 +1,4 @@ -import NodeBuilder, { shaderStages } from 'three-nodes/core/NodeBuilder.js'; +import NodeBuilder, { defaultShaderStages } from 'three-nodes/core/NodeBuilder.js'; import NodeFrame from 'three-nodes/core/NodeFrame.js'; import SlotNode from './SlotNode.js'; import GLSLNodeParser from 'three-nodes/parsers/GLSLNodeParser.js'; @@ -85,6 +85,12 @@ class WebGLNodeBuilder extends NodeBuilder { } + if ( material.isMeshStandardNodeMaterial !== true ) { + + this.replaceCode( 'fragment', getIncludeSnippet( 'tonemapping_fragment' ), '' ); + + } + // parse inputs if ( material.colorNode && material.colorNode.isNode ) { @@ -329,7 +335,7 @@ class WebGLNodeBuilder extends NodeBuilder { const shaderData = {}; - for ( const shaderStage of shaderStages ) { + for ( const shaderStage of defaultShaderStages ) { const uniforms = this.getUniforms( shaderStage ); const attributes = this.getAttributes( shaderStage ); @@ -515,7 +521,7 @@ ${this.shader[ getShaderStageProperty( shaderStage ) ]} } - for ( const shaderStage of shaderStages ) { + for ( const shaderStage of defaultShaderStages ) { this.addCodeAfterSnippet( shaderStage, @@ -529,7 +535,7 @@ ${this.shader[ getShaderStageProperty( shaderStage ) ]} _addUniforms() { - for ( const shaderStage of shaderStages ) { + for ( const shaderStage of defaultShaderStages ) { // uniforms diff --git a/examples/jsm/renderers/webgpu/WebGPUBindings.js b/examples/jsm/renderers/webgpu/WebGPUBindings.js index 35605d314067a3..9e5810fbd9643b 100644 --- a/examples/jsm/renderers/webgpu/WebGPUBindings.js +++ b/examples/jsm/renderers/webgpu/WebGPUBindings.js @@ -30,9 +30,9 @@ class WebGPUBindings { // setup (static) binding layout and (dynamic) binding group - const renderPipeline = this.renderPipelines.get( object ); + const pipeline = object.isNode ? this.computePipelines.get( object ) : this.renderPipelines.get( object ).pipeline; - const bindLayout = renderPipeline.pipeline.getBindGroupLayout( 0 ); + const bindLayout = pipeline.getBindGroupLayout( 0 ); const bindGroup = this._createBindGroup( bindings, bindLayout ); data = { @@ -108,12 +108,12 @@ class WebGPUBindings { if ( binding.isUniformBuffer ) { const buffer = binding.getBuffer(); - const bufferGPU = binding.bufferGPU; - const needsBufferWrite = binding.update(); if ( needsBufferWrite === true ) { + const bufferGPU = binding.bufferGPU; + this.device.queue.writeBuffer( bufferGPU, 0, buffer, 0 ); } @@ -121,6 +121,7 @@ class WebGPUBindings { } else if ( binding.isStorageBuffer ) { const attribute = binding.attribute; + this.attributes.update( attribute, false, binding.usage ); } else if ( binding.isSampler ) { @@ -243,8 +244,8 @@ class WebGPUBindings { } return this.device.createBindGroup( { - layout: layout, - entries: entries + layout, + entries } ); } diff --git a/examples/jsm/renderers/webgpu/WebGPUBuffer.js b/examples/jsm/renderers/webgpu/WebGPUBuffer.js new file mode 100644 index 00000000000000..072dbadb6c23d3 --- /dev/null +++ b/examples/jsm/renderers/webgpu/WebGPUBuffer.js @@ -0,0 +1,43 @@ +import WebGPUBinding from './WebGPUBinding.js'; +import { getFloatLength } from './WebGPUBufferUtils.js'; + +class WebGPUBuffer extends WebGPUBinding { + + constructor( name, type, buffer = null ) { + + super( name ); + + this.bytesPerElement = Float32Array.BYTES_PER_ELEMENT; + this.type = type; + this.visibility = GPUShaderStage.VERTEX; + + this.usage = GPUBufferUsage.COPY_DST; + + this.buffer = buffer; + this.bufferGPU = null; // set by the renderer + + } + + getByteLength() { + + return getFloatLength( this.buffer.byteLength ); + + } + + getBuffer() { + + return this.buffer; + + } + + update() { + + return true; + + } + +} + +WebGPUBuffer.prototype.isBuffer = true; + +export default WebGPUBuffer; diff --git a/examples/jsm/renderers/webgpu/WebGPUComputePipelines.js b/examples/jsm/renderers/webgpu/WebGPUComputePipelines.js index bd604aac006137..8c9f952e4367c8 100644 --- a/examples/jsm/renderers/webgpu/WebGPUComputePipelines.js +++ b/examples/jsm/renderers/webgpu/WebGPUComputePipelines.js @@ -2,9 +2,10 @@ import WebGPUProgrammableStage from './WebGPUProgrammableStage.js'; class WebGPUComputePipelines { - constructor( device ) { + constructor( device, nodes ) { this.device = device; + this.nodes = nodes; this.pipelines = new WeakMap(); this.stages = { @@ -13,9 +14,9 @@ class WebGPUComputePipelines { } - get( param ) { + get( computeNode ) { - let pipeline = this.pipelines.get( param ); + let pipeline = this.pipelines.get( computeNode ); // @TODO: Reuse compute pipeline if possible, introduce WebGPUComputePipeline @@ -23,8 +24,13 @@ class WebGPUComputePipelines { const device = this.device; + // get shader + + const nodeBuilder = this.nodes.get( computeNode ); + const computeShader = nodeBuilder.computeShader; + const shader = { - computeShader: param.shader + computeShader }; // programmable stage @@ -33,7 +39,7 @@ class WebGPUComputePipelines { if ( stageCompute === undefined ) { - stageCompute = new WebGPUProgrammableStage( device, shader.computeShader, 'compute' ); + stageCompute = new WebGPUProgrammableStage( device, computeShader, 'compute' ); this.stages.compute.set( shader, stageCompute ); @@ -43,7 +49,7 @@ class WebGPUComputePipelines { compute: stageCompute.stage } ); - this.pipelines.set( param, pipeline ); + this.pipelines.set( computeNode, pipeline ); } diff --git a/examples/jsm/renderers/webgpu/WebGPURenderPipelines.js b/examples/jsm/renderers/webgpu/WebGPURenderPipelines.js index 1196a2996ef830..8e938c775844d9 100644 --- a/examples/jsm/renderers/webgpu/WebGPURenderPipelines.js +++ b/examples/jsm/renderers/webgpu/WebGPURenderPipelines.js @@ -24,7 +24,6 @@ class WebGPURenderPipelines { get( object ) { const device = this.device; - const material = object.material; const cache = this._getCache( object ); @@ -32,6 +31,8 @@ class WebGPURenderPipelines { if ( this._needsUpdate( object, cache ) ) { + const material = object.material; + // release previous cache if ( cache.currentPipeline !== undefined ) { diff --git a/examples/jsm/renderers/webgpu/WebGPURenderer.js b/examples/jsm/renderers/webgpu/WebGPURenderer.js index eeeb2db2961b77..71abb7fba99c7e 100644 --- a/examples/jsm/renderers/webgpu/WebGPURenderer.js +++ b/examples/jsm/renderers/webgpu/WebGPURenderer.js @@ -186,7 +186,7 @@ class WebGPURenderer { this._textures = new WebGPUTextures( device, this._properties, this._info ); this._objects = new WebGPUObjects( this._geometries, this._info ); this._nodes = new WebGPUNodes( this, this._properties ); - this._computePipelines = new WebGPUComputePipelines( device ); + this._computePipelines = new WebGPUComputePipelines( device, this._nodes ); this._renderPipelines = new WebGPURenderPipelines( this, device, parameters.sampleCount, this._nodes ); this._bindings = this._renderPipelines.bindings = new WebGPUBindings( device, this._info, this._properties, this._textures, this._renderPipelines, this._computePipelines, this._attributes, this._nodes ); this._renderLists = new WebGPURenderLists(); @@ -611,26 +611,30 @@ class WebGPURenderer { } - compute( computeParams ) { + compute( ...computeNodes ) { const device = this._device; const cmdEncoder = device.createCommandEncoder( {} ); const passEncoder = cmdEncoder.beginComputePass(); - for ( const param of computeParams ) { + for ( const computeNode of computeNodes ) { // pipeline - const pipeline = this._computePipelines.get( param ); + const pipeline = this._computePipelines.get( computeNode ); passEncoder.setPipeline( pipeline ); + // node + + //this._nodes.update( computeNode ); + // bind group - const bindGroup = this._bindings.getForCompute( param ).group; - this._bindings.update( param ); + const bindGroup = this._bindings.get( computeNode ).group; + this._bindings.update( computeNode ); passEncoder.setBindGroup( 0, bindGroup ); - passEncoder.dispatch( param.num ); + passEncoder.dispatch( computeNode.dispatchCount ); } diff --git a/examples/jsm/renderers/webgpu/WebGPUStorageBuffer.js b/examples/jsm/renderers/webgpu/WebGPUStorageBuffer.js index 95eb34fbb5aec4..07868a9e7a1bdd 100644 --- a/examples/jsm/renderers/webgpu/WebGPUStorageBuffer.js +++ b/examples/jsm/renderers/webgpu/WebGPUStorageBuffer.js @@ -1,18 +1,15 @@ -import WebGPUBinding from './WebGPUBinding.js'; +import WebGPUBuffer from './WebGPUBuffer.js'; import { GPUBindingType } from './constants.js'; -class WebGPUStorageBuffer extends WebGPUBinding { +class WebGPUStorageBuffer extends WebGPUBuffer { constructor( name, attribute ) { - super( name ); + super( name, GPUBindingType.StorageBuffer, attribute.array ); - this.type = GPUBindingType.StorageBuffer; - - this.usage = GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST; + this.usage |= GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE; this.attribute = attribute; - this.bufferGPU = null; // set by the renderer } diff --git a/examples/jsm/renderers/webgpu/WebGPUUniformBuffer.js b/examples/jsm/renderers/webgpu/WebGPUUniformBuffer.js index 1d47da27e9629c..cc03925e584d20 100644 --- a/examples/jsm/renderers/webgpu/WebGPUUniformBuffer.js +++ b/examples/jsm/renderers/webgpu/WebGPUUniformBuffer.js @@ -1,40 +1,13 @@ -import WebGPUBinding from './WebGPUBinding.js'; -import { getFloatLength } from './WebGPUBufferUtils.js'; - +import WebGPUBuffer from './WebGPUBuffer.js'; import { GPUBindingType } from './constants.js'; -class WebGPUUniformBuffer extends WebGPUBinding { +class WebGPUUniformBuffer extends WebGPUBuffer { constructor( name, buffer = null ) { - super( name ); - - this.bytesPerElement = Float32Array.BYTES_PER_ELEMENT; - this.type = GPUBindingType.UniformBuffer; - this.visibility = GPUShaderStage.VERTEX; - - this.usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST; - - this.buffer = buffer; - this.bufferGPU = null; // set by the renderer - - } - - getByteLength() { - - return getFloatLength( this.buffer.byteLength ); - - } - - getBuffer() { - - return this.buffer; - - } - - update() { + super( name, GPUBindingType.UniformBuffer, buffer ); - return true; + this.usage |= GPUBufferUsage.UNIFORM; } diff --git a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js b/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js index 3a0951fd2f7493..ce9e41554c7c5b 100644 --- a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js +++ b/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js @@ -1,4 +1,4 @@ -import WebGPUNodeUniformsGroup from './WebGPUNodeUniformsGroup.js'; +import WebGPUUniformsGroup from '../WebGPUUniformsGroup.js'; import { FloatNodeUniform, Vector2NodeUniform, Vector3NodeUniform, Vector4NodeUniform, ColorNodeUniform, Matrix3NodeUniform, Matrix4NodeUniform @@ -7,6 +7,7 @@ import WebGPUNodeSampler from './WebGPUNodeSampler.js'; import { WebGPUNodeSampledTexture, WebGPUNodeSampledCubeTexture } from './WebGPUNodeSampledTexture.js'; import WebGPUUniformBuffer from '../WebGPUUniformBuffer.js'; +import WebGPUStorageBuffer from '../WebGPUStorageBuffer.js'; import { getVectorLength, getStrideLength } from '../WebGPUBufferUtils.js'; import NodeBuilder from 'three-nodes/core/NodeBuilder.js'; @@ -16,6 +17,12 @@ import CodeNode from 'three-nodes/core/CodeNode.js'; import { NodeMaterial } from 'three-nodes/materials/Materials.js'; +const gpuShaderStageLib = { + 'vertex': GPUShaderStage.VERTEX, + 'fragment': GPUShaderStage.FRAGMENT, + 'compute': GPUShaderStage.COMPUTE +}; + const supports = { instance: true }; @@ -93,8 +100,8 @@ class WebGPUNodeBuilder extends NodeBuilder { this.lightNode = null; this.fogNode = null; - this.bindings = { vertex: [], fragment: [] }; - this.bindingsOffset = { vertex: 0, fragment: 0 }; + this.bindings = { vertex: [], fragment: [], compute: [] }; + this.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 }; this.uniformsGroup = {}; @@ -104,7 +111,17 @@ class WebGPUNodeBuilder extends NodeBuilder { build() { - NodeMaterial.fromMaterial( this.material ).build( this ); + const { object, material } = this; + + if ( material !== null ) { + + NodeMaterial.fromMaterial( material ).build( this ); + + } else { + + this.addFlow( 'compute', object ); + + } return super.build(); @@ -201,7 +218,7 @@ class WebGPUNodeBuilder extends NodeBuilder { return name; - } else if ( type === 'buffer' ) { + } else if ( type === 'buffer' || type === 'storageBuffer' ) { return `NodeBuffer_${node.node.id}.${name}`; @@ -221,7 +238,7 @@ class WebGPUNodeBuilder extends NodeBuilder { const bindings = this.bindings; - return [ ...bindings.vertex, ...bindings.fragment ]; + return this.material !== null ? [ ...bindings.vertex, ...bindings.fragment ] : bindings.compute; } @@ -270,9 +287,11 @@ class WebGPUNodeBuilder extends NodeBuilder { } - } else if ( type === 'buffer' ) { + } else if ( type === 'buffer' || type === 'storageBuffer' ) { - const buffer = new WebGPUUniformBuffer( 'NodeBuffer_' + node.id, node.value ); + const bufferClass = type === 'storageBuffer' ? WebGPUStorageBuffer : WebGPUUniformBuffer; + const buffer = new bufferClass( 'NodeBuffer_' + node.id, node.value ); + buffer.setVisibility( gpuShaderStageLib[ shaderStage ] ); // add first textures in sequence and group for last const lastBinding = bindings[ bindings.length - 1 ]; @@ -288,7 +307,8 @@ class WebGPUNodeBuilder extends NodeBuilder { if ( uniformsGroup === undefined ) { - uniformsGroup = new WebGPUNodeUniformsGroup( shaderStage ); + uniformsGroup = new WebGPUUniformsGroup( 'nodeUniforms' ); + uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] ); this.uniformsGroup[ shaderStage ] = uniformsGroup; @@ -348,11 +368,7 @@ class WebGPUNodeBuilder extends NodeBuilder { this.builtins.add( 'instance_index' ); - if ( shaderStage === 'vertex' ) { - - return 'instanceIndex'; - - } + return 'instanceIndex'; } @@ -360,9 +376,13 @@ class WebGPUNodeBuilder extends NodeBuilder { const snippets = []; - if ( shaderStage === 'vertex' ) { + if ( shaderStage === 'vertex' || shaderStage === 'compute' ) { + + if ( shaderStage === 'compute' ) { - if ( this.builtins.has( 'instance_index' ) ) { + snippets.push( `@builtin( global_invocation_id ) id : vec3` ); + + } else if ( this.builtins.has( 'instance_index' ) ) { snippets.push( `@builtin( instance_index ) instanceIndex : u32` ); @@ -477,15 +497,17 @@ class WebGPUNodeBuilder extends NodeBuilder { bindingSnippets.push( `@group( 0 ) @binding( ${index ++} ) var ${uniform.name} : texture_cube;` ); - } else if ( uniform.type === 'buffer' ) { + } else if ( uniform.type === 'buffer' || uniform.type === 'storageBuffer' ) { const bufferNode = uniform.node; const bufferType = this.getType( bufferNode.bufferType ); const bufferCount = bufferNode.bufferCount; - const bufferSnippet = `\t${uniform.name} : array< ${bufferType}, ${bufferCount} >\n`; + const bufferCountSnippet = bufferCount > 0 ? ', ' + bufferCount : ''; + const bufferSnippet = `\t${uniform.name} : array< ${bufferType}${bufferCountSnippet} >\n`; + const bufferAccessMode = bufferNode.isStorageBufferNode ? 'storage,read_write' : 'uniform'; - bufferSnippets.push( this._getWGSLUniforms( 'NodeBuffer_' + bufferNode.id, bufferSnippet, index ++ ) ); + bufferSnippets.push( this._getWGSLStructBinding( 'NodeBuffer_' + bufferNode.id, bufferSnippet, bufferAccessMode, index ++ ) ); } else { @@ -512,7 +534,7 @@ class WebGPUNodeBuilder extends NodeBuilder { if ( groupSnippets.length > 0 ) { - code += this._getWGSLUniforms( 'NodeUniforms', groupSnippets.join( ',\n' ), index ++ ); + code += this._getWGSLStructBinding( 'NodeUniforms', groupSnippets.join( ',\n' ), 'uniform', index ++ ); } @@ -522,7 +544,7 @@ class WebGPUNodeBuilder extends NodeBuilder { buildCode() { - const shadersData = { fragment: {}, vertex: {} }; + const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} }; for ( const shaderStage in shadersData ) { @@ -548,7 +570,7 @@ class WebGPUNodeBuilder extends NodeBuilder { flow += `${ flowSlotData.code }\n\t`; - if ( node === mainNode ) { + if ( node === mainNode && shaderStage !== 'compute' ) { flow += '// FLOW RESULT\n\t'; @@ -579,8 +601,16 @@ class WebGPUNodeBuilder extends NodeBuilder { } - this.vertexShader = this._getWGSLVertexCode( shadersData.vertex ); - this.fragmentShader = this._getWGSLFragmentCode( shadersData.fragment ); + if ( this.material !== null ) { + + this.vertexShader = this._getWGSLVertexCode( shadersData.vertex ); + this.fragmentShader = this._getWGSLFragmentCode( shadersData.fragment ); + + } else { + + this.computeShader = this._getWGSLComputeCode( shadersData.compute, ( this.object.workgroupSize || [ 64 ] ).join( ', ' ) ); + + } } @@ -679,6 +709,35 @@ fn main( ${shaderData.varys} ) -> @location( 0 ) vec4 { // flow ${shaderData.flow} +} +`; + + } + + _getWGSLComputeCode( shaderData, workgroupSize ) { + + return `${ this.getSignature() } +// system +var instanceIndex : u32; + +// uniforms +${shaderData.uniforms} + +// codes +${shaderData.codes} + +@stage( compute ) @workgroup_size( ${workgroupSize} ) +fn main( ${shaderData.attributes} ) { + + // system + instanceIndex = id.x * 3u; + + // vars + ${shaderData.vars} + + // flow + ${shaderData.flow} + } `; @@ -693,14 +752,14 @@ ${vars} } - _getWGSLUniforms( name, vars, binding = 0, group = 0 ) { + _getWGSLStructBinding( name, vars, access, binding = 0, group = 0 ) { const structName = name + 'Struct'; const structSnippet = this._getWGSLStruct( structName, vars ); return `${structSnippet} @binding( ${binding} ) @group( ${group} ) -var ${name} : ${structName};`; +var<${access}> ${name} : ${structName};`; } diff --git a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeUniformsGroup.js b/examples/jsm/renderers/webgpu/nodes/WebGPUNodeUniformsGroup.js deleted file mode 100644 index 793c725f2ce411..00000000000000 --- a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeUniformsGroup.js +++ /dev/null @@ -1,20 +0,0 @@ -import WebGPUUniformsGroup from '../WebGPUUniformsGroup.js'; - -class WebGPUNodeUniformsGroup extends WebGPUUniformsGroup { - - constructor( shaderStage ) { - - super( 'nodeUniforms' ); - - let shaderStageVisibility; - - if ( shaderStage === 'vertex' ) shaderStageVisibility = GPUShaderStage.VERTEX; - else if ( shaderStage === 'fragment' ) shaderStageVisibility = GPUShaderStage.FRAGMENT; - - this.setVisibility( shaderStageVisibility ); - - } - -} - -export default WebGPUNodeUniformsGroup; diff --git a/examples/screenshots/webgpu_compute.jpg b/examples/screenshots/webgpu_compute.jpg index 35b06935974bb9..001fe203e0b94e 100644 Binary files a/examples/screenshots/webgpu_compute.jpg and b/examples/screenshots/webgpu_compute.jpg differ diff --git a/examples/screenshots/webgpu_depth_texture.jpg b/examples/screenshots/webgpu_depth_texture.jpg index 35b06935974bb9..001fe203e0b94e 100644 Binary files a/examples/screenshots/webgpu_depth_texture.jpg and b/examples/screenshots/webgpu_depth_texture.jpg differ diff --git a/examples/screenshots/webgpu_instance_mesh.jpg b/examples/screenshots/webgpu_instance_mesh.jpg index 35b06935974bb9..001fe203e0b94e 100644 Binary files a/examples/screenshots/webgpu_instance_mesh.jpg and b/examples/screenshots/webgpu_instance_mesh.jpg differ diff --git a/examples/screenshots/webgpu_instance_uniform.jpg b/examples/screenshots/webgpu_instance_uniform.jpg index ed64a2bef3e0f0..001fe203e0b94e 100644 Binary files a/examples/screenshots/webgpu_instance_uniform.jpg and b/examples/screenshots/webgpu_instance_uniform.jpg differ diff --git a/examples/screenshots/webgpu_lights_custom.jpg b/examples/screenshots/webgpu_lights_custom.jpg index 35b06935974bb9..001fe203e0b94e 100644 Binary files a/examples/screenshots/webgpu_lights_custom.jpg and b/examples/screenshots/webgpu_lights_custom.jpg differ diff --git a/examples/screenshots/webgpu_lights_selective.jpg b/examples/screenshots/webgpu_lights_selective.jpg index 35b06935974bb9..001fe203e0b94e 100644 Binary files a/examples/screenshots/webgpu_lights_selective.jpg and b/examples/screenshots/webgpu_lights_selective.jpg differ diff --git a/examples/screenshots/webgpu_materials.jpg b/examples/screenshots/webgpu_materials.jpg index 35b06935974bb9..001fe203e0b94e 100644 Binary files a/examples/screenshots/webgpu_materials.jpg and b/examples/screenshots/webgpu_materials.jpg differ diff --git a/examples/screenshots/webgpu_nodes_playground.jpg b/examples/screenshots/webgpu_nodes_playground.jpg index 35b06935974bb9..001fe203e0b94e 100644 Binary files a/examples/screenshots/webgpu_nodes_playground.jpg and b/examples/screenshots/webgpu_nodes_playground.jpg differ diff --git a/examples/screenshots/webgpu_rtt.jpg b/examples/screenshots/webgpu_rtt.jpg index 35b06935974bb9..001fe203e0b94e 100644 Binary files a/examples/screenshots/webgpu_rtt.jpg and b/examples/screenshots/webgpu_rtt.jpg differ diff --git a/examples/screenshots/webgpu_sandbox.jpg b/examples/screenshots/webgpu_sandbox.jpg index 35b06935974bb9..001fe203e0b94e 100644 Binary files a/examples/screenshots/webgpu_sandbox.jpg and b/examples/screenshots/webgpu_sandbox.jpg differ diff --git a/examples/screenshots/webgpu_skinning.jpg b/examples/screenshots/webgpu_skinning.jpg index 35b06935974bb9..001fe203e0b94e 100644 Binary files a/examples/screenshots/webgpu_skinning.jpg and b/examples/screenshots/webgpu_skinning.jpg differ diff --git a/examples/screenshots/webgpu_skinning_instancing.jpg b/examples/screenshots/webgpu_skinning_instancing.jpg index 35b06935974bb9..001fe203e0b94e 100644 Binary files a/examples/screenshots/webgpu_skinning_instancing.jpg and b/examples/screenshots/webgpu_skinning_instancing.jpg differ diff --git a/examples/screenshots/webgpu_skinning_points.jpg b/examples/screenshots/webgpu_skinning_points.jpg index 35b06935974bb9..001fe203e0b94e 100644 Binary files a/examples/screenshots/webgpu_skinning_points.jpg and b/examples/screenshots/webgpu_skinning_points.jpg differ diff --git a/examples/webgpu_compute.html b/examples/webgpu_compute.html index 9b92cefb2bd20d..12c96d40ce0464 100644 --- a/examples/webgpu_compute.html +++ b/examples/webgpu_compute.html @@ -28,23 +28,23 @@ import * as THREE from 'three'; import * as Nodes from 'three-nodes/Nodes.js'; + import { + compute, + color, add, uniform, element, storage, func, + assign, float, mul, + positionLocal, instanceIndex + } from 'three-nodes/Nodes.js'; + import { GUI } from './jsm/libs/lil-gui.module.min.js'; import WebGPU from './jsm/capabilities/WebGPU.js'; import WebGPURenderer from './jsm/renderers/webgpu/WebGPURenderer.js'; - import WebGPUStorageBuffer from './jsm/renderers/webgpu/WebGPUStorageBuffer.js'; - import WebGPUUniformBuffer from './jsm/renderers/webgpu/WebGPUUniformBuffer.js'; - import * as WebGPUBufferUtils from './jsm/renderers/webgpu/WebGPUBufferUtils.js'; - import WebGPUUniformsGroup from './jsm/renderers/webgpu/WebGPUUniformsGroup.js'; - import { Vector2Uniform } from './jsm/renderers/webgpu/WebGPUUniform.js'; - let camera, scene, renderer; - let pointer; - let scaleUniformBuffer; - const scaleVector = new THREE.Vector3( 1, 1, 1 ); + let computeNode; - const computeParams = []; + const pointer = new THREE.Vector2( - 10.0, - 10.0 ); // Out of bounds first + const scaleVector = new THREE.Vector2( 1, 1 ); init().then( animate ).catch( error ); @@ -62,10 +62,11 @@ camera.position.z = 1; scene = new THREE.Scene(); - scene.background = new THREE.Color( 0x000000 ); + + // initialize particles const particleNum = 65000; // 16-bit limit - const particleSize = 4; // 16-byte stride align + const particleSize = 3; // vec3 const particleArray = new Float32Array( particleNum * particleSize ); const velocityArray = new Float32Array( particleNum * particleSize ); @@ -74,81 +75,25 @@ const r = Math.random() * 0.01 + 0.0005; const degree = Math.random() * 360; + velocityArray[ i + 0 ] = r * Math.sin( degree * Math.PI / 180 ); velocityArray[ i + 1 ] = r * Math.cos( degree * Math.PI / 180 ); } - const particleBuffer = new WebGPUStorageBuffer( 'particle', new THREE.BufferAttribute( particleArray, particleSize ) ); - const velocityBuffer = new WebGPUStorageBuffer( 'velocity', new THREE.BufferAttribute( velocityArray, particleSize ) ); - - const scaleUniformLength = WebGPUBufferUtils.getVectorLength( 2, 3 ); // two vector3 for array - - scaleUniformBuffer = new WebGPUUniformBuffer( 'scaleUniform', new Float32Array( scaleUniformLength ) ); - - pointer = new THREE.Vector2( - 10.0, - 10.0 ); // Out of bounds first - - const pointerGroup = new WebGPUUniformsGroup( 'mouseUniforms' ).addUniform( - new Vector2Uniform( 'pointer', pointer ) - ); - - // Object keys need follow the binding shader sequence - - const computeBindings = [ - particleBuffer, - velocityBuffer, - scaleUniformBuffer, - pointerGroup - ]; - - const computeShader = ` - - // - // Buffer - // - - struct Particle { - value : array< vec4 > - }; - @binding( 0 ) @group( 0 ) - var particle : Particle; - - struct Velocity { - value : array< vec4 > - }; - @binding( 1 ) @group( 0 ) - var velocity : Velocity; - - // - // Uniforms - // - - struct Scale { - value : array< vec3, 2 > - }; - @binding( 2 ) @group( 0 ) - var scaleUniform : Scale; - - struct MouseUniforms { - pointer : vec2 - }; - @binding( 3 ) @group( 0 ) - var mouseUniforms : MouseUniforms; + // create buffers - @stage( compute ) @workgroup_size( 64 ) - fn main( @builtin(global_invocation_id) id : vec3 ) { + const particleBuffer = new THREE.BufferAttribute( particleArray, particleSize ); + const velocityBuffer = new THREE.BufferAttribute( velocityArray, particleSize ); - // get particle index + const particleBufferNode = storage( particleBuffer, 'vec3' ); + const velocityBufferNode = storage( velocityBuffer, 'vec3' ); - let index : u32 = id.x * 3u; + // create wgsl function - // update speed + const WGSLFnNode = func( `( pointer:vec2, limit:vec2 ) { - var position : vec4 = particle.value[ index ] + velocity.value[ index ]; - - // update limit - - let limit : vec2 = scaleUniform.value[ 0 ].xy; + var position = particle + velocity; if ( abs( position.x ) >= limit.x ) { @@ -162,7 +107,7 @@ } - velocity.value[ index ].x = - velocity.value[ index ].x; + velocity.x = - velocity.x; } @@ -178,17 +123,15 @@ } - velocity.value[ index ].y = - velocity.value[ index ].y; + velocity.y = - velocity.y ; } - // update mouse - - let POINTER_SIZE : f32 = .1; + let POINTER_SIZE = .1; - let dx : f32 = mouseUniforms.pointer.x - position.x; - let dy : f32 = mouseUniforms.pointer.y - position.y; - let distanceFromPointer : f32 = sqrt( dx * dx + dy * dy ); + let dx = pointer.x - position.x; + let dy = pointer.y - position.y; + let distanceFromPointer = sqrt( dx * dx + dy * dy ); if ( distanceFromPointer <= POINTER_SIZE ) { @@ -198,28 +141,51 @@ } - // update buffer - - particle.value[ index ] = position; + particle = position; } + ` ); + + // define particle and velocity keywords in wgsl function + // it's used in case of needed change a global variable like this storageBuffer + + const particleNode = element( particleBufferNode, instanceIndex ); + const velocityNode = element( velocityBufferNode, instanceIndex ); + + WGSLFnNode.keywords[ 'particle' ] = particleNode; + WGSLFnNode.keywords[ 'velocity' ] = velocityNode; + + // compute + + computeNode = compute( particleNum ); - `; + // Example 1: Calling a WGSL function - computeParams.push( { - num: particleNum, - shader: computeShader, - bindings: computeBindings + computeNode.computeNode = WGSLFnNode.call( { + pointer: uniform( pointer ), + limit: uniform( scaleVector ) } ); - // Use a compute shader to animate the point cloud's vertex data. + // Example 2: Creating single storage assign - const pointsGeometry = new THREE.BufferGeometry().setAttribute( - 'position', particleBuffer.attribute - ); + //computeNode.computeNode = assign( particleNode, add( particleNode, velocityNode ) ); + + // Example 3: Creating multiples storage assign + + /*computeNode.computeNode = new Nodes.ShaderNode( ( {}, builder ) => { + + assign( particleNode, add( particleNode, velocityNode ) ).build( builder ); + assign( velocityNode, mul( velocityNode, float( 0.99 ) ) ).build( builder ); + + } );/**/ + + // use a compute shader to animate the point cloud's vertex data. + + const pointsGeometry = new THREE.BufferGeometry(); + pointsGeometry.setAttribute( 'position', particleBuffer ); const pointsMaterial = new Nodes.PointsNodeMaterial(); - pointsMaterial.colorNode = new Nodes.OperatorNode( '+', new Nodes.PositionNode(), new Nodes.UniformNode( new THREE.Color( 0xFFFFFF ) ) ); + pointsMaterial.colorNode = add( positionLocal, color( 0xFFFFFF ) ); const mesh = new THREE.Points( pointsGeometry, pointsMaterial ); scene.add( mesh ); @@ -270,11 +236,9 @@ requestAnimationFrame( animate ); - renderer.compute( computeParams ); + renderer.compute( computeNode ); renderer.render( scene, camera ); - scaleVector.toArray( scaleUniformBuffer.buffer, 0 ); - } function error( error ) { diff --git a/examples/webgpu_nodes_playground.html b/examples/webgpu_nodes_playground.html index 228be5a0921ec0..3d7792b0c2e3c5 100644 --- a/examples/webgpu_nodes_playground.html +++ b/examples/webgpu_nodes_playground.html @@ -73,7 +73,6 @@ let stats; let camera, scene, renderer; let model; - let nodeLights; init().then( animate ).catch( error => console.error( error ) );