import DataMap from './DataMap.js'; import RenderPipeline from './RenderPipeline.js'; import ComputePipeline from './ComputePipeline.js'; import ProgrammableStage from './ProgrammableStage.js'; class Pipelines extends DataMap { constructor( backend, nodes ) { super(); this.backend = backend; this.nodes = nodes; this.bindings = null; // set by the bindings this.caches = new Map(); this.programs = { vertex: new Map(), fragment: new Map(), compute: new Map() }; } getForCompute( computeNode, bindings ) { const { backend } = this; const data = this.get( computeNode ); if ( this._needsComputeUpdate( computeNode ) ) { const previousPipeline = data.pipeline; if ( previousPipeline ) { previousPipeline.usedTimes --; previousPipeline.computeProgram.usedTimes --; } // get shader const nodeBuilderState = this.nodes.getForCompute( computeNode ); // programmable stage let stageCompute = this.programs.compute.get( nodeBuilderState.computeShader ); if ( stageCompute === undefined ) { if ( previousPipeline && previousPipeline.computeProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.computeProgram ); stageCompute = new ProgrammableStage( nodeBuilderState.computeShader, 'compute', nodeBuilderState.transforms, nodeBuilderState.nodeAttributes ); this.programs.compute.set( nodeBuilderState.computeShader, stageCompute ); backend.createProgram( stageCompute ); } // determine compute pipeline 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, bindings ); } // keep track of all used times pipeline.usedTimes ++; stageCompute.usedTimes ++; // data.version = computeNode.version; data.pipeline = pipeline; } return data.pipeline; } getForRender( renderObject, promises = null ) { const { backend } = this; const data = this.get( renderObject ); if ( this._needsRenderUpdate( renderObject ) ) { const previousPipeline = data.pipeline; if ( previousPipeline ) { previousPipeline.usedTimes --; previousPipeline.vertexProgram.usedTimes --; previousPipeline.fragmentProgram.usedTimes --; } // get shader const nodeBuilderState = renderObject.getNodeBuilderState(); // programmable stages let stageVertex = this.programs.vertex.get( nodeBuilderState.vertexShader ); if ( stageVertex === undefined ) { if ( previousPipeline && previousPipeline.vertexProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.vertexProgram ); stageVertex = new ProgrammableStage( nodeBuilderState.vertexShader, 'vertex' ); this.programs.vertex.set( nodeBuilderState.vertexShader, stageVertex ); backend.createProgram( stageVertex ); } let stageFragment = this.programs.fragment.get( nodeBuilderState.fragmentShader ); if ( stageFragment === undefined ) { if ( previousPipeline && previousPipeline.fragmentProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.fragmentProgram ); stageFragment = new ProgrammableStage( nodeBuilderState.fragmentShader, 'fragment' ); this.programs.fragment.set( nodeBuilderState.fragmentShader, stageFragment ); backend.createProgram( stageFragment ); } // determine render pipeline 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, promises ); } else { renderObject.pipeline = pipeline; } // keep track of all used times pipeline.usedTimes ++; stageVertex.usedTimes ++; stageFragment.usedTimes ++; // data.pipeline = pipeline; } return data.pipeline; } delete( object ) { const pipeline = this.get( object ).pipeline; if ( pipeline ) { // pipeline pipeline.usedTimes --; if ( pipeline.usedTimes === 0 ) this._releasePipeline( pipeline ); // programs if ( pipeline.isComputePipeline ) { pipeline.computeProgram.usedTimes --; if ( pipeline.computeProgram.usedTimes === 0 ) this._releaseProgram( pipeline.computeProgram ); } else { pipeline.fragmentProgram.usedTimes --; pipeline.vertexProgram.usedTimes --; if ( pipeline.vertexProgram.usedTimes === 0 ) this._releaseProgram( pipeline.vertexProgram ); if ( pipeline.fragmentProgram.usedTimes === 0 ) this._releaseProgram( pipeline.fragmentProgram ); } } super.delete( object ); } dispose() { super.dispose(); this.caches = new Map(); this.programs = { vertex: new Map(), fragment: new Map(), compute: new Map() }; } updateForRender( renderObject ) { this.getForRender( renderObject ); } _getComputePipeline( computeNode, stageCompute, cacheKey, bindings ) { // check for existing pipeline cacheKey = cacheKey || this._getComputeCacheKey( computeNode, stageCompute ); let pipeline = this.caches.get( cacheKey ); if ( pipeline === undefined ) { pipeline = new ComputePipeline( cacheKey, stageCompute ); this.caches.set( cacheKey, pipeline ); this.backend.createComputePipeline( pipeline, bindings ); } return pipeline; } _getRenderPipeline( renderObject, stageVertex, stageFragment, cacheKey, promises ) { // check for existing pipeline cacheKey = cacheKey || this._getRenderCacheKey( renderObject, stageVertex, stageFragment ); let pipeline = this.caches.get( cacheKey ); if ( pipeline === undefined ) { pipeline = new RenderPipeline( cacheKey, stageVertex, stageFragment ); this.caches.set( cacheKey, pipeline ); renderObject.pipeline = pipeline; this.backend.createRenderPipeline( renderObject, promises ); } return pipeline; } _getComputeCacheKey( computeNode, stageCompute ) { return computeNode.id + ',' + stageCompute.id; } _getRenderCacheKey( renderObject, stageVertex, stageFragment ) { return stageVertex.id + ',' + stageFragment.id + ',' + this.backend.getRenderCacheKey( renderObject ); } _releasePipeline( pipeline ) { this.caches.delete( pipeline.cacheKey ); } _releaseProgram( program ) { const code = program.code; const stage = program.stage; this.programs[ stage ].delete( code ); } _needsComputeUpdate( computeNode ) { const data = this.get( computeNode ); return data.pipeline === undefined || data.version !== computeNode.version; } _needsRenderUpdate( renderObject ) { const data = this.get( renderObject ); return data.pipeline === undefined || this.backend.needsRenderUpdate( renderObject ); } } export default Pipelines;