@luma.gl/engine
Version:
3D Engine Components for luma.gl
127 lines (103 loc) • 4.76 kB
text/typescript
// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import type {RenderPipelineProps, ComputePipelineProps} from '@luma.gl/core';
import {Device, RenderPipeline, ComputePipeline} from '@luma.gl/core';
export type PipelineFactoryProps = RenderPipelineProps;
type RenderPipelineCacheItem = {pipeline: RenderPipeline; useCount: number};
type ComputePipelineCacheItem = {pipeline: ComputePipeline; useCount: number};
/**
* Efficiently creates / caches pipelines
*/
export class PipelineFactory {
static defaultProps: Required<PipelineFactoryProps> = {...RenderPipeline.defaultProps};
/** Get the singleton default pipeline factory for the specified device */
static getDefaultPipelineFactory(device: Device): PipelineFactory {
device._lumaData.defaultPipelineFactory =
device._lumaData.defaultPipelineFactory || new PipelineFactory(device);
return device._lumaData.defaultPipelineFactory as PipelineFactory;
}
readonly device: Device;
readonly destroyPolicy: 'unused' | 'never';
private _hashCounter: number = 0;
private readonly _hashes: Record<string, number> = {};
private readonly _renderPipelineCache: Record<string, RenderPipelineCacheItem> = {};
private readonly _computePipelineCache: Record<string, ComputePipelineCacheItem> = {};
constructor(device: Device) {
this.device = device;
this.destroyPolicy = device.props._factoryDestroyPolicy;
}
/** Return a RenderPipeline matching props. Reuses a similar pipeline if already created. */
createRenderPipeline(props: RenderPipelineProps): RenderPipeline {
const allProps: Required<RenderPipelineProps> = {...RenderPipeline.defaultProps, ...props};
const hash = this._hashRenderPipeline(allProps);
if (!this._renderPipelineCache[hash]) {
const pipeline = this.device.createRenderPipeline({
...allProps,
id: allProps.id ? `${allProps.id}-cached` : undefined
});
pipeline.hash = hash;
this._renderPipelineCache[hash] = {pipeline, useCount: 0};
}
this._renderPipelineCache[hash].useCount++;
return this._renderPipelineCache[hash].pipeline;
}
createComputePipeline(props: ComputePipelineProps): ComputePipeline {
const allProps: Required<ComputePipelineProps> = {...ComputePipeline.defaultProps, ...props};
const hash = this._hashComputePipeline(allProps);
if (!this._computePipelineCache[hash]) {
const pipeline = this.device.createComputePipeline({
...allProps,
id: allProps.id ? `${allProps.id}-cached` : undefined
});
pipeline.hash = hash;
this._computePipelineCache[hash] = {pipeline, useCount: 0};
}
this._computePipelineCache[hash].useCount++;
return this._computePipelineCache[hash].pipeline;
}
release(pipeline: RenderPipeline | ComputePipeline): void {
const hash = pipeline.hash;
const cache =
pipeline instanceof ComputePipeline ? this._computePipelineCache : this._renderPipelineCache;
cache[hash].useCount--;
if (cache[hash].useCount === 0) {
if (this.destroyPolicy === 'unused') {
cache[hash].pipeline.destroy();
delete cache[hash];
}
}
}
// PRIVATE
private _hashComputePipeline(props: ComputePipelineProps): string {
const shaderHash = this._getHash(props.shader.source);
return `${shaderHash}`;
}
/** Calculate a hash based on all the inputs for a render pipeline */
private _hashRenderPipeline(props: RenderPipelineProps): string {
const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
// WebGL specific
// const {varyings = [], bufferMode = {}} = props;
// const varyingHashes = varyings.map((v) => this._getHash(v));
const varyingHash = '-'; // `${varyingHashes.join('/')}B${bufferMode}`
const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout));
switch (this.device.type) {
case 'webgl':
// WebGL is more dynamic
return `${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`;
default:
// On WebGPU we need to rebuild the pipeline if topology, parameters or bufferLayout change
const parameterHash = this._getHash(JSON.stringify(props.parameters));
// TODO - Can json.stringify() generate different strings for equivalent objects if order of params is different?
// create a deepHash() to deduplicate?
return `${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}`;
}
}
private _getHash(key: string): number {
if (this._hashes[key] === undefined) {
this._hashes[key] = this._hashCounter++;
}
return this._hashes[key];
}
}