import DataMap from './DataMap.js';
import RenderPipeline from './RenderPipeline.js';
import ComputePipeline from './ComputePipeline.js';
import ProgrammableStage from './ProgrammableStage.js';
/**
* This renderer module manages the pipelines of the renderer.
*
* @private
* @augments DataMap
*/
class Pipelines extends DataMap {
/**
* Constructs a new pipeline management component.
*
* @param {Backend} backend - The renderer's backend.
* @param {Nodes} nodes - Renderer component for managing nodes related logic.
*/
constructor( backend, nodes ) {
super();
/**
* The renderer's backend.
*
* @type {Backend}
*/
this.backend = backend;
/**
* Renderer component for managing nodes related logic.
*
* @type {Nodes}
*/
this.nodes = nodes;
/**
* A references to the bindings management component.
* This reference will be set inside the `Bindings`
* constructor.
*
* @type {Bindings?}
* @default null
*/
this.bindings = null;
/**
* Internal cache for maintaining pipelines.
* The key of the map is a cache key, the value the pipeline.
*
* @type {Map<String,Pipeline>}
*/
this.caches = new Map();
/**
* This dictionary maintains for each shader stage type (vertex,
* fragment and compute) the programmable stage objects which
* represent the actual shader code.
*
* @type {Object<String,Map>}
*/
this.programs = {
vertex: new Map(),
fragment: new Map(),
compute: new Map()
};
}
/**
* Returns a compute pipeline for the given compute node.
*
* @param {Node} computeNode - The compute node.
* @param {Array<BindGroup>} bindings - The bindings.
* @return {ComputePipeline} The compute pipeline.
*/
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', computeNode.name, 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( previousPipeline );
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;
}
/**
* Returns a render pipeline for the given render object.
*
* @param {RenderObject} renderObject - The render object.
* @param {Array<Promise>?} [promises=null] - An array of compilation promises which is only relevant in context of `Renderer.compileAsync()`.
* @return {RenderPipeline} The render 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();
const name = renderObject.material ? renderObject.material.name : '';
// 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', name );
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', name );
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;
}
/**
* Deletes the pipeline for the given render object.
*
* @param {RenderObject} object - The render object.
* @return {Object?} The deleted dictionary.
*/
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 );
}
}
return super.delete( object );
}
/**
* Frees internal resources.
*/
dispose() {
super.dispose();
this.caches = new Map();
this.programs = {
vertex: new Map(),
fragment: new Map(),
compute: new Map()
};
}
/**
* Updates the pipeline for the given render object.
*
* @param {RenderObject} renderObject - The render object.
*/
updateForRender( renderObject ) {
this.getForRender( renderObject );
}
/**
* Returns a compute pipeline for the given parameters.
*
* @private
* @param {Node} computeNode - The compute node.
* @param {ProgrammableStage} stageCompute - The programmable stage representing the compute shader.
* @param {String} cacheKey - The cache key.
* @param {Array<BindGroup>} bindings - The bindings.
* @return {ComputePipeline} The compute pipeline.
*/
_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;
}
/**
* Returns a render pipeline for the given parameters.
*
* @private
* @param {RenderObject} renderObject - The render object.
* @param {ProgrammableStage} stageVertex - The programmable stage representing the vertex shader.
* @param {ProgrammableStage} stageFragment - The programmable stage representing the fragment shader.
* @param {String} cacheKey - The cache key.
* @param {Array<Promise>?} promises - An array of compilation promises which is only relevant in context of `Renderer.compileAsync()`.
* @return {ComputePipeline} The compute 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;
// The `promises` array is `null` by default and only set to an empty array when
// `Renderer.compileAsync()` is used. The next call actually fills the array with
// pending promises that resolve when the render pipelines are ready for rendering.
this.backend.createRenderPipeline( renderObject, promises );
}
return pipeline;
}
/**
* Computes a cache key representing a compute pipeline.
*
* @private
* @param {Node} computeNode - The compute node.
* @param {ProgrammableStage} stageCompute - The programmable stage representing the compute shader.
* @return {String} The cache key.
*/
_getComputeCacheKey( computeNode, stageCompute ) {
return computeNode.id + ',' + stageCompute.id;
}
/**
* Computes a cache key representing a render pipeline.
*
* @private
* @param {RenderObject} renderObject - The render object.
* @param {ProgrammableStage} stageVertex - The programmable stage representing the vertex shader.
* @param {ProgrammableStage} stageFragment - The programmable stage representing the fragment shader.
* @return {String} The cache key.
*/
_getRenderCacheKey( renderObject, stageVertex, stageFragment ) {
return stageVertex.id + ',' + stageFragment.id + ',' + this.backend.getRenderCacheKey( renderObject );
}
/**
* Releases the given pipeline.
*
* @private
* @param {Pipeline} pipeline - The pipeline to release.
*/
_releasePipeline( pipeline ) {
this.caches.delete( pipeline.cacheKey );
}
/**
* Releases the shader program.
*
* @private
* @param {Object} program - The shader program to release.
*/
_releaseProgram( program ) {
const code = program.code;
const stage = program.stage;
this.programs[ stage ].delete( code );
}
/**
* Returns `true` if the compute pipeline for the given compute node requires an update.
*
* @private
* @param {Node} computeNode - The compute node.
* @return {Boolean} Whether the compute pipeline for the given compute node requires an update or not.
*/
_needsComputeUpdate( computeNode ) {
const data = this.get( computeNode );
return data.pipeline === undefined || data.version !== computeNode.version;
}
/**
* Returns `true` if the render pipeline for the given render object requires an update.
*
* @private
* @param {RenderObject} renderObject - The render object.
* @return {Boolean} Whether the render object for the given render object requires an update or not.
*/
_needsRenderUpdate( renderObject ) {
const data = this.get( renderObject );
return data.pipeline === undefined || this.backend.needsRenderUpdate( renderObject );
}
}
export default Pipelines;