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 nodeBuilder = this.nodes.getForCompute( computeNode ); // programmable stage let stageCompute = this.programs.compute.get( nodeBuilder.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 ); 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 ) { 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 ); } 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 ) { // 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 ); } return pipeline; } _getComputeCacheKey( computeNode, stageCompute ) { return 'compute' + computeNode.id + stageCompute.id; } _getRenderCacheKey( renderObject, stageVertex, stageFragment ) { const { material } = renderObject; const parameters = [ stageVertex.id, stageFragment.id, material.transparent, material.blending, material.premultipliedAlpha, material.blendSrc, material.blendDst, material.blendEquation, material.blendSrcAlpha, material.blendDstAlpha, material.blendEquationAlpha, material.colorWrite, material.depthWrite, material.depthTest, material.depthFunc, material.stencilWrite, material.stencilFunc, material.stencilFail, material.stencilZFail, material.stencilZPass, material.stencilFuncMask, material.stencilWriteMask, material.side, this.backend.getCacheKey( renderObject ) ]; return parameters.join(); } _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 ); const material = renderObject.material; let needsUpdate = this.backend.needsUpdate( renderObject ); // check material state if ( data.material !== material || data.materialVersion !== material.version || data.transparent !== material.transparent || data.blending !== material.blending || data.premultipliedAlpha !== material.premultipliedAlpha || data.blendSrc !== material.blendSrc || data.blendDst !== material.blendDst || data.blendEquation !== material.blendEquation || data.blendSrcAlpha !== material.blendSrcAlpha || data.blendDstAlpha !== material.blendDstAlpha || data.blendEquationAlpha !== material.blendEquationAlpha || data.colorWrite !== material.colorWrite || data.depthWrite !== material.depthWrite || data.depthTest !== material.depthTest || data.depthFunc !== material.depthFunc || data.stencilWrite !== material.stencilWrite || data.stencilFunc !== material.stencilFunc || data.stencilFail !== material.stencilFail || data.stencilZFail !== material.stencilZFail || data.stencilZPass !== material.stencilZPass || data.stencilFuncMask !== material.stencilFuncMask || data.stencilWriteMask !== material.stencilWriteMask || data.side !== material.side || data.alphaToCoverage !== material.alphaToCoverage ) { data.material = material; data.materialVersion = material.version; data.transparent = material.transparent; data.blending = material.blending; data.premultipliedAlpha = material.premultipliedAlpha; data.blendSrc = material.blendSrc; data.blendDst = material.blendDst; data.blendEquation = material.blendEquation; data.blendSrcAlpha = material.blendSrcAlpha; data.blendDstAlpha = material.blendDstAlpha; data.blendEquationAlpha = material.blendEquationAlpha; data.colorWrite = material.colorWrite; data.depthWrite = material.depthWrite; data.depthTest = material.depthTest; data.depthFunc = material.depthFunc; data.stencilWrite = material.stencilWrite; data.stencilFunc = material.stencilFunc; data.stencilFail = material.stencilFail; data.stencilZFail = material.stencilZFail; data.stencilZPass = material.stencilZPass; data.stencilFuncMask = material.stencilFuncMask; data.stencilWriteMask = material.stencilWriteMask; data.side = material.side; data.alphaToCoverage = material.alphaToCoverage; needsUpdate = true; } return needsUpdate || data.pipeline === undefined; } } export default Pipelines;