From 3d4aff5bc9045227cf02ddb2c78f98a5156cea80 Mon Sep 17 00:00:00 2001 From: sunag Date: Sun, 2 Jul 2023 14:16:24 -0300 Subject: [PATCH 1/4] WebGPUPipelines: Reuse cache --- examples/jsm/renderers/common/Pipelines.js | 118 +++++++++++++-------- 1 file changed, 76 insertions(+), 42 deletions(-) diff --git a/examples/jsm/renderers/common/Pipelines.js b/examples/jsm/renderers/common/Pipelines.js index 378deae6df949f..3a3569f19aebb2 100644 --- a/examples/jsm/renderers/common/Pipelines.js +++ b/examples/jsm/renderers/common/Pipelines.js @@ -31,9 +31,14 @@ class Pipelines extends DataMap { if ( data.pipeline === undefined ) { - // release previous cache + const previousPipeline = data.pipeline; - const previousPipeline = this._releasePipeline( computeNode ); + if ( previousPipeline ) { + + previousPipeline.usedTimes --; + previousPipeline.computeProgram.usedTimes --; + + } // get shader @@ -45,7 +50,7 @@ class Pipelines extends DataMap { if ( stageCompute === undefined ) { - if ( previousPipeline ) this._releaseProgram( previousPipeline.computeShader ); + if ( previousPipeline && previousPipeline.computeProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.computeProgram ); stageCompute = new ProgrammableStage( nodeBuilder.computeShader, 'compute' ); this.programs.compute.set( nodeBuilder.computeShader, stageCompute ); @@ -56,7 +61,17 @@ class Pipelines extends DataMap { // determine compute pipeline - const pipeline = this._getComputePipeline( stageCompute ); + const cacheKey = this._getComputeCacheKey( computeNode, stageCompute ); + + let pipeline = this.caches.get( cacheKey ); + + if ( pipeline === undefined ) { + + if ( previousPipeline && previousPipeline.usedTimes === 0 ) this._releasePipeline( computeNode ); + + pipeline = this._getComputePipeline( computeNode, stageCompute, cacheKey ); + + } // keep track of all used times @@ -81,9 +96,15 @@ class Pipelines extends DataMap { if ( this._needsUpdate( renderObject ) ) { - // release previous cache + const previousPipeline = data.pipeline; - const previousPipeline = this._releasePipeline( renderObject ); + if ( previousPipeline ) { + + previousPipeline.usedTimes --; + previousPipeline.vertexProgram.usedTimes --; + previousPipeline.fragmentProgram.usedTimes --; + + } // get shader @@ -95,7 +116,7 @@ class Pipelines extends DataMap { if ( stageVertex === undefined ) { - if ( previousPipeline ) this._releaseProgram( previousPipeline.vertexProgram ); + if ( previousPipeline && previousPipeline.vertexProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.vertexProgram ); stageVertex = new ProgrammableStage( nodeBuilder.vertexShader, 'vertex' ); this.programs.vertex.set( nodeBuilder.vertexShader, stageVertex ); @@ -108,7 +129,7 @@ class Pipelines extends DataMap { if ( stageFragment === undefined ) { - if ( previousPipeline ) this._releaseProgram( previousPipeline.fragmentShader ); + if ( previousPipeline && previousPipeline.fragmentProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.fragmentProgram ); stageFragment = new ProgrammableStage( nodeBuilder.fragmentShader, 'fragment' ); this.programs.fragment.set( nodeBuilder.fragmentShader, stageFragment ); @@ -119,7 +140,21 @@ class Pipelines extends DataMap { // determine render pipeline - const pipeline = this._getRenderPipeline( renderObject, stageVertex, stageFragment ); + const cacheKey = this._getRenderCacheKey( renderObject, stageVertex, stageFragment ); + + let pipeline = this.caches.get( cacheKey ); + + if ( pipeline === undefined ) { + + if ( previousPipeline && previousPipeline.usedTimes === 0 ) this._releasePipeline( previousPipeline ); + + pipeline = this._getRenderPipeline( renderObject, stageVertex, stageFragment, cacheKey ); + + } else { + + renderObject.pipeline = pipeline; + + } // keep track of all used times @@ -139,18 +174,31 @@ class Pipelines extends DataMap { delete( object ) { - const pipeline = this._releasePipeline( object ); + const pipeline = this.get( object ).pipeline; + + if ( pipeline ) { - if ( pipeline && pipeline.usedTimes === 0 ) { + // pipeline + + pipeline.usedTimes --; + + if ( pipeline.usedTimes === 0 ) this._releasePipeline( pipeline ); + + // programs if ( pipeline.isComputePipeline ) { - this._releaseProgram( pipeline.computeProgram ); + pipeline.computeProgram.usedTimes --; + + if ( pipeline.computeProgram.usedTimes === 0 ) this._releaseProgram( pipeline.computeProgram ); } else { - this._releaseProgram( pipeline.vertexProgram ); - this._releaseProgram( pipeline.fragmentProgram ); + pipeline.fragmentProgram.usedTimes --; + pipeline.vertexProgram.usedTimes --; + + if ( pipeline.vertexProgram.usedTimes === 0 ) this._releaseProgram( pipeline.vertexProgram ); + if ( pipeline.fragmentProgram.usedTimes === 0 ) this._releaseProgram( pipeline.fragmentProgram ); } @@ -173,11 +221,11 @@ class Pipelines extends DataMap { } - _getComputePipeline( stageCompute ) { + _getComputePipeline( computeNode, stageCompute, cacheKey ) { // check for existing pipeline - const cacheKey = 'compute:' + stageCompute.id; + cacheKey = cacheKey || this._getComputeCacheKey( computeNode, stageCompute ); let pipeline = this.caches.get( cacheKey ); @@ -195,11 +243,11 @@ class Pipelines extends DataMap { } - _getRenderPipeline( renderObject, stageVertex, stageFragment ) { + _getRenderPipeline( renderObject, stageVertex, stageFragment, cacheKey ) { // check for existing pipeline - const cacheKey = this._getRenderCacheKey( renderObject, stageVertex, stageFragment ); + cacheKey = cacheKey || this._getRenderCacheKey( renderObject, stageVertex, stageFragment ); let pipeline = this.caches.get( cacheKey ); @@ -213,15 +261,15 @@ class Pipelines extends DataMap { this.backend.createRenderPipeline( renderObject ); - } else { + } - // assign a shared pipeline to renderObject + return pipeline; - renderObject.pipeline = pipeline; + } - } + _getComputeCacheKey( computeNode, stageCompute ) { - return pipeline; + return 'compute' + computeNode.id + stageCompute.id; } @@ -247,32 +295,18 @@ class Pipelines extends DataMap { } - _releasePipeline( object ) { + _releasePipeline( pipeline ) { - const pipeline = this.get( object ).pipeline; - - //this.bindings.delete( object ); - - if ( pipeline && -- pipeline.usedTimes === 0 ) { - - this.caches.delete( pipeline.cacheKey ); - - } - - return pipeline; + this.caches.delete( pipeline.cacheKey ); } _releaseProgram( program ) { - if ( -- program.usedTimes === 0 ) { - - const code = program.code; - const stage = program.stage; + const code = program.code; + const stage = program.stage; - this.programs[ stage ].delete( code ); - - } + this.programs[ stage ].delete( code ); } From e7897e96f679369f0f16f0eec7855f3ce9233173 Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 3 Jul 2023 22:05:25 -0300 Subject: [PATCH 2/4] ComputeNode: Add .needsUpdate --- examples/jsm/nodes/gpgpu/ComputeNode.js | 11 +++++++++++ examples/jsm/renderers/common/Pipelines.js | 15 ++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/examples/jsm/nodes/gpgpu/ComputeNode.js b/examples/jsm/nodes/gpgpu/ComputeNode.js index 8f7a911e737ecd..42b3ee78702c26 100644 --- a/examples/jsm/nodes/gpgpu/ComputeNode.js +++ b/examples/jsm/nodes/gpgpu/ComputeNode.js @@ -16,12 +16,23 @@ class ComputeNode extends Node { this.workgroupSize = workgroupSize; this.dispatchCount = 0; + this.version = 1; this.updateType = NodeUpdateType.OBJECT; this.updateDispatchCount(); } + set needsUpdate( value ) { + + if ( value === true ) { + + this.version ++; + + } + + } + updateDispatchCount() { const { count, workgroupSize } = this; diff --git a/examples/jsm/renderers/common/Pipelines.js b/examples/jsm/renderers/common/Pipelines.js index 3a3569f19aebb2..40c7ef9099cd56 100644 --- a/examples/jsm/renderers/common/Pipelines.js +++ b/examples/jsm/renderers/common/Pipelines.js @@ -29,7 +29,7 @@ class Pipelines extends DataMap { const data = this.get( computeNode ); - if ( data.pipeline === undefined ) { + if ( this._needsComputeUpdate( computeNode ) ) { const previousPipeline = data.pipeline; @@ -80,6 +80,7 @@ class Pipelines extends DataMap { // + data.version = computeNode.version; data.pipeline = pipeline; } @@ -94,7 +95,7 @@ class Pipelines extends DataMap { const data = this.get( renderObject ); - if ( this._needsUpdate( renderObject ) ) { + if ( this._needsRenderUpdate( renderObject ) ) { const previousPipeline = data.pipeline; @@ -310,7 +311,15 @@ class Pipelines extends DataMap { } - _needsUpdate( renderObject ) { + _needsComputeUpdate( computeNode ) { + + const data = this.get( computeNode ); + + return data.pipeline === undefined || data.version !== computeNode.version; + + } + + _needsRenderUpdate( renderObject ) { const data = this.get( renderObject ); const material = renderObject.material; From d4d28bcab174d450127f0c921951fd2768083973 Mon Sep 17 00:00:00 2001 From: sunag Date: Tue, 4 Jul 2023 13:34:28 -0300 Subject: [PATCH 3/4] fix definition used in tests --- examples/jsm/renderers/common/Pipelines.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/jsm/renderers/common/Pipelines.js b/examples/jsm/renderers/common/Pipelines.js index 40c7ef9099cd56..8996214c1fed35 100644 --- a/examples/jsm/renderers/common/Pipelines.js +++ b/examples/jsm/renderers/common/Pipelines.js @@ -355,7 +355,7 @@ class Pipelines extends DataMap { } - return needsUpdate || data.pipeline !== undefined; + return needsUpdate || data.pipeline === undefined; } From d3c6c66cb41c540b325fb969b906eb2b080577cb Mon Sep 17 00:00:00 2001 From: sunag Date: Sat, 8 Jul 2023 23:56:17 -0300 Subject: [PATCH 4/4] ComputeNode: Added .dispose() --- examples/jsm/nodes/core/Node.js | 13 +++++-- examples/jsm/nodes/gpgpu/ComputeNode.js | 10 +++--- .../jsm/renderers/common/RenderObjects.js | 4 ++- examples/jsm/renderers/common/Renderer.js | 34 ++++++++++++++----- 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/examples/jsm/nodes/core/Node.js b/examples/jsm/nodes/core/Node.js index 5f5b5ec5be75f6..2f05569b7abe09 100644 --- a/examples/jsm/nodes/core/Node.js +++ b/examples/jsm/nodes/core/Node.js @@ -1,3 +1,4 @@ +import { EventDispatcher } from 'three'; import { NodeUpdateType } from './constants.js'; import { getNodeChildren, getCacheKey } from './NodeUtils.js'; import { MathUtils } from 'three'; @@ -6,11 +7,11 @@ const NodeClasses = new Map(); let _nodeId = 0; -class Node { +class Node extends EventDispatcher { constructor( nodeType = null ) { - this.isNode = true; + super(); this.nodeType = nodeType; @@ -19,6 +20,8 @@ class Node { this.uuid = MathUtils.generateUUID(); + this.isNode = true; + Object.defineProperty( this, 'id', { value: _nodeId ++ } ); } @@ -52,6 +55,12 @@ class Node { } + dispose() { + + this.dispatchEvent( { type: 'dispose' } ); + + } + traverse( callback, replaceNode = null ) { callback( this, replaceNode ); diff --git a/examples/jsm/nodes/gpgpu/ComputeNode.js b/examples/jsm/nodes/gpgpu/ComputeNode.js index 42b3ee78702c26..05e97e6bd2fbc8 100644 --- a/examples/jsm/nodes/gpgpu/ComputeNode.js +++ b/examples/jsm/nodes/gpgpu/ComputeNode.js @@ -23,13 +23,15 @@ class ComputeNode extends Node { } - set needsUpdate( value ) { + dispose() { - if ( value === true ) { + this.dispatchEvent( { type: 'dispose' } ); - this.version ++; + } - } + set needsUpdate( value ) { + + if ( value === true ) this.version ++; } diff --git a/examples/jsm/renderers/common/RenderObjects.js b/examples/jsm/renderers/common/RenderObjects.js index 980828d63df2e9..f9d9ee4c04b39d 100644 --- a/examples/jsm/renderers/common/RenderObjects.js +++ b/examples/jsm/renderers/common/RenderObjects.js @@ -4,7 +4,7 @@ import RenderObject from './RenderObject.js'; class RenderObjects extends ChainMap { - constructor( renderer, nodes, geometries, pipelines, info ) { + constructor( renderer, nodes, geometries, pipelines, bindings, info ) { super(); @@ -12,6 +12,7 @@ class RenderObjects extends ChainMap { this.nodes = nodes; this.geometries = geometries; this.pipelines = pipelines; + this.bindings = bindings; this.info = info; this.dataMap = new DataMap(); @@ -69,6 +70,7 @@ class RenderObjects extends ChainMap { this.dataMap.delete( renderObject ); this.pipelines.delete( renderObject ); + this.bindings.delete( renderObject ); this.nodes.delete( renderObject ); this.delete( renderObject.getChainArray() ); diff --git a/examples/jsm/renderers/common/Renderer.js b/examples/jsm/renderers/common/Renderer.js index 9ccd91ef9ce2af..60a75ffa5bd4fe 100644 --- a/examples/jsm/renderers/common/Renderer.js +++ b/examples/jsm/renderers/common/Renderer.js @@ -137,7 +137,7 @@ class Renderer { this._textures = new Textures( backend, this._info ); this._pipelines = new Pipelines( backend, this._nodes ); this._bindings = new Bindings( backend, this._nodes, this._textures, this._attributes, this._pipelines, this._info ); - this._objects = new RenderObjects( this, this._nodes, this._geometries, this._pipelines, this._info ); + this._objects = new RenderObjects( this, this._nodes, this._geometries, this._pipelines, this._bindings, this._info ); this._renderLists = new RenderLists(); this._renderContexts = new RenderContexts(); @@ -587,31 +587,47 @@ class Renderer { const backend = this.backend; const pipelines = this._pipelines; - const computeGroup = Array.isArray( computeNodes ) ? computeNodes : [ computeNodes ]; + const bindings = this._bindings; + const nodes = this._nodes; + const computeList = Array.isArray( computeNodes ) ? computeNodes : [ computeNodes ]; - backend.beginCompute( computeGroup ); + backend.beginCompute( computeNodes ); - for ( const computeNode of computeGroup ) { + for ( const computeNode of computeList ) { // onInit if ( pipelines.has( computeNode ) === false ) { + const dispose = () => { + + computeNode.removeEventListener( 'dispose', dispose ); + + pipelines.delete( computeNode ); + bindings.delete( computeNode ); + nodes.delete( computeNode ); + + }; + + computeNode.addEventListener( 'dispose', dispose ); + + // + computeNode.onInit( { renderer: this } ); } - this._nodes.updateForCompute( computeNode ); - this._bindings.updateForCompute( computeNode ); + nodes.updateForCompute( computeNode ); + bindings.updateForCompute( computeNode ); const computePipeline = pipelines.getForCompute( computeNode ); - const computeBindings = this._bindings.getForCompute( computeNode ); + const computeBindings = bindings.getForCompute( computeNode ); - backend.compute( computeGroup, computeNode, computeBindings, computePipeline ); + backend.compute( computeNodes, computeNode, computeBindings, computePipeline ); } - backend.finishCompute( computeGroup ); + backend.finishCompute( computeNodes ); }