@luma.gl/engine 9.0.0-beta.5 → 9.0.0-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/dist/animation/timeline.d.ts.map +1 -1
  2. package/dist/animation/timeline.js +3 -3
  3. package/dist/animation-loop/animation-loop-template.d.ts +1 -1
  4. package/dist/animation-loop/animation-loop-template.d.ts.map +1 -1
  5. package/dist/animation-loop/animation-loop-template.js +3 -1
  6. package/dist/animation-loop/animation-loop.d.ts +2 -2
  7. package/dist/animation-loop/animation-loop.d.ts.map +1 -1
  8. package/dist/animation-loop/animation-loop.js +14 -6
  9. package/dist/animation-loop/animation-props.d.ts +2 -2
  10. package/dist/animation-loop/animation-props.d.ts.map +1 -1
  11. package/dist/animation-loop/make-animation-loop.d.ts +2 -2
  12. package/dist/animation-loop/make-animation-loop.d.ts.map +1 -1
  13. package/dist/animation-loop/make-animation-loop.js +4 -2
  14. package/dist/computation.d.ts +95 -0
  15. package/dist/computation.d.ts.map +1 -0
  16. package/dist/computation.js +248 -0
  17. package/dist/debug/copy-texture-to-image.d.ts.map +1 -1
  18. package/dist/debug/copy-texture-to-image.js +5 -2
  19. package/dist/debug/debug-framebuffer.d.ts.map +1 -1
  20. package/dist/debug/debug-framebuffer.js +0 -1
  21. package/dist/debug/pixel-data-utils.d.ts.map +1 -1
  22. package/dist/debug/pixel-data-utils.js +2 -1
  23. package/dist/dist.dev.js +713 -329
  24. package/dist/geometries/cone-geometry.d.ts +1 -1
  25. package/dist/geometries/cone-geometry.d.ts.map +1 -1
  26. package/dist/geometries/cone-geometry.js +1 -1
  27. package/dist/geometries/cube-geometry.d.ts +1 -1
  28. package/dist/geometries/cube-geometry.d.ts.map +1 -1
  29. package/dist/geometries/cube-geometry.js +16 -14
  30. package/dist/geometries/cylinder-geometry.d.ts +1 -1
  31. package/dist/geometries/cylinder-geometry.d.ts.map +1 -1
  32. package/dist/geometries/cylinder-geometry.js +1 -1
  33. package/dist/geometries/ico-sphere-geometry.d.ts +1 -1
  34. package/dist/geometries/ico-sphere-geometry.d.ts.map +1 -1
  35. package/dist/geometries/ico-sphere-geometry.js +1 -1
  36. package/dist/geometries/plane-geometry.d.ts +1 -1
  37. package/dist/geometries/plane-geometry.d.ts.map +1 -1
  38. package/dist/geometries/plane-geometry.js +2 -2
  39. package/dist/geometries/sphere-geometry.d.ts +1 -1
  40. package/dist/geometries/sphere-geometry.d.ts.map +1 -1
  41. package/dist/geometries/sphere-geometry.js +1 -1
  42. package/dist/geometries/truncated-cone-geometry.d.ts +1 -1
  43. package/dist/geometries/truncated-cone-geometry.d.ts.map +1 -1
  44. package/dist/geometries/truncated-cone-geometry.js +1 -1
  45. package/dist/geometry/geometry-table.d.ts.map +1 -1
  46. package/dist/geometry/geometry-table.js +3 -0
  47. package/dist/geometry/geometry.d.ts.map +1 -1
  48. package/dist/geometry/geometry.js +3 -0
  49. package/dist/geometry/gpu-geometry.d.ts +1 -1
  50. package/dist/geometry/gpu-geometry.d.ts.map +1 -1
  51. package/dist/geometry/gpu-geometry.js +4 -5
  52. package/dist/index.cjs +661 -291
  53. package/dist/index.cjs.map +4 -4
  54. package/dist/index.d.ts +43 -40
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +25 -23
  57. package/dist/lib/clip-space.d.ts +1 -1
  58. package/dist/lib/clip-space.d.ts.map +1 -1
  59. package/dist/lib/clip-space.js +8 -10
  60. package/dist/lib/pipeline-factory.d.ts +10 -6
  61. package/dist/lib/pipeline-factory.d.ts.map +1 -1
  62. package/dist/lib/pipeline-factory.js +47 -22
  63. package/dist/lib/shader-factory.d.ts +17 -0
  64. package/dist/lib/shader-factory.d.ts.map +1 -0
  65. package/dist/lib/shader-factory.js +46 -0
  66. package/dist/model/model.d.ts +58 -45
  67. package/dist/model/model.d.ts.map +1 -1
  68. package/dist/model/model.js +213 -120
  69. package/dist/scenegraph/group-node.d.ts +1 -1
  70. package/dist/scenegraph/group-node.d.ts.map +1 -1
  71. package/dist/scenegraph/group-node.js +10 -5
  72. package/dist/scenegraph/model-node.d.ts +3 -3
  73. package/dist/scenegraph/model-node.d.ts.map +1 -1
  74. package/dist/scenegraph/model-node.js +2 -2
  75. package/dist/scenegraph/scenegraph-node.d.ts.map +1 -1
  76. package/dist/shader-inputs.d.ts.map +1 -1
  77. package/dist/shader-inputs.js +3 -0
  78. package/dist/transform/buffer-transform.d.ts +1 -1
  79. package/dist/transform/buffer-transform.d.ts.map +1 -1
  80. package/dist/transform/buffer-transform.js +7 -6
  81. package/dist/transform/texture-transform.d.ts +1 -1
  82. package/dist/transform/texture-transform.d.ts.map +1 -1
  83. package/dist/transform/texture-transform.js +10 -8
  84. package/dist.min.js +2 -2
  85. package/package.json +2 -2
  86. package/src/animation/timeline.ts +20 -20
  87. package/src/animation-loop/animation-loop-template.ts +10 -8
  88. package/src/animation-loop/animation-loop.ts +20 -10
  89. package/src/animation-loop/animation-props.ts +1 -1
  90. package/src/animation-loop/make-animation-loop.ts +17 -8
  91. package/src/computation.ts +346 -0
  92. package/src/debug/copy-texture-to-image.ts +8 -6
  93. package/src/debug/debug-framebuffer.ts +16 -3
  94. package/src/debug/debug-shader-layout.ts +1 -1
  95. package/src/debug/pixel-data-utils.ts +3 -6
  96. package/src/geometries/cube-geometry.ts +17 -13
  97. package/src/geometries/ico-sphere-geometry.ts +1 -1
  98. package/src/geometries/plane-geometry.ts +1 -1
  99. package/src/geometries/sphere-geometry.ts +1 -1
  100. package/src/geometries/truncated-cone-geometry.ts +2 -1
  101. package/src/geometry/geometry-table.ts +9 -6
  102. package/src/geometry/geometry-utils.ts +1 -1
  103. package/src/geometry/geometry.ts +9 -6
  104. package/src/geometry/gpu-geometry.ts +18 -11
  105. package/src/index.ts +3 -0
  106. package/src/lib/clip-space.ts +14 -18
  107. package/src/lib/pipeline-factory.ts +62 -28
  108. package/src/lib/shader-factory.ts +57 -0
  109. package/src/model/model.ts +249 -146
  110. package/src/scenegraph/group-node.ts +14 -10
  111. package/src/scenegraph/model-node.ts +2 -2
  112. package/src/scenegraph/scenegraph-node.ts +2 -2
  113. package/src/shader-inputs.ts +19 -12
  114. package/src/transform/buffer-transform.ts +15 -7
  115. package/src/transform/texture-transform.ts +14 -13
@@ -5,7 +5,7 @@ import {unpackIndexedGeometry} from '../geometry/geometry-utils';
5
5
  export type PlaneGeometryProps = {
6
6
  id?: string;
7
7
  radius?: number;
8
- attributes?: any
8
+ attributes?: any;
9
9
  };
10
10
 
11
11
  // Primitives inspired by TDL http://code.google.com/p/webglsamples/,
@@ -6,7 +6,7 @@ export type SphereGeometryProps = {
6
6
  radius?: number;
7
7
  nlat?: number;
8
8
  nlong?: number;
9
- attributes?: any
9
+ attributes?: any;
10
10
  };
11
11
 
12
12
  // Primitives inspired by TDL http://code.google.com/p/webglsamples/,
@@ -36,7 +36,8 @@ export class TruncatedConeGeometry extends Geometry {
36
36
  POSITION: {size: 3, value: attributes.POSITION},
37
37
  NORMAL: {size: 3, value: attributes.NORMAL},
38
38
  TEXCOORD_0: {size: 2, value: attributes.TEXCOORD_0},
39
- ...props.attributes}
39
+ ...props.attributes
40
+ }
40
41
  });
41
42
  }
42
43
  }
@@ -1,4 +1,7 @@
1
- // luma.gl, MIT license
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
2
5
  import type {TypedArray, VertexFormat} from '@luma.gl/core';
3
6
 
4
7
  /** Holds one geometry */
@@ -6,11 +9,11 @@ export type GeometryTable = {
6
9
  length: number;
7
10
  schema?: Record<string, VertexFormat>;
8
11
  attributes: {
9
- POSITION: TypedArray,
10
- NORMAL: TypedArray,
11
- TEXCOORD_0: TypedArray,
12
- [key: string]: TypedArray,
12
+ POSITION: TypedArray;
13
+ NORMAL: TypedArray;
14
+ TEXCOORD_0: TypedArray;
15
+ [key: string]: TypedArray;
13
16
  };
14
17
  indices?: Uint16Array | Uint32Array;
15
18
  topology?: 'point-list' | 'line-list' | 'line-strip' | 'triangle-list' | 'triangle-strip';
16
- }
19
+ };
@@ -44,4 +44,4 @@ export function unpackIndexedGeometry(geometry: any) {
44
44
  // normals.set(normal[2], i + 2);
45
45
  // }
46
46
  // const normal = new Vector3(vec1).cross(vec2).normalize();
47
- // }
47
+ // }
@@ -1,4 +1,7 @@
1
- // luma.gl, MIT license
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
2
5
  import type {PrimitiveTopology, TypedArray} from '@luma.gl/core';
3
6
  import {uid, assert} from '@luma.gl/core';
4
7
 
@@ -98,7 +101,7 @@ export class Geometry {
98
101
  return this.vertexCount;
99
102
  }
100
103
 
101
- /**
104
+ /**
102
105
  * Return an object with all attributes plus indices added as a field.
103
106
  * TODO Geometry types are a mess
104
107
  */
@@ -118,10 +121,10 @@ export class Geometry {
118
121
  * type: indices, vertices, uvs
119
122
  * size: elements per vertex
120
123
  * target: WebGL buffer type (string or constant)
121
- *
122
- * @param attributes
123
- * @param indices
124
- * @returns
124
+ *
125
+ * @param attributes
126
+ * @param indices
127
+ * @returns
125
128
  */
126
129
  _setAttributes(attributes: Record<string, GeometryAttribute>, indices: any): this {
127
130
  return this;
@@ -48,11 +48,10 @@ export class GPUGeometry {
48
48
  }
49
49
 
50
50
  destroy(): void {
51
- this.indices.destroy();
52
- this.attributes.positions.destroy();
53
- this.attributes.normals.destroy();
54
- this.attributes.texCoords.destroy();
55
- this.attributes.colors?.destroy();
51
+ this.indices?.destroy();
52
+ for (const attribute of Object.values(this.attributes)) {
53
+ attribute.destroy();
54
+ }
56
55
  }
57
56
 
58
57
  getVertexCount(): number {
@@ -101,7 +100,7 @@ export function getIndexBufferFromGeometry(device: Device, geometry: Geometry):
101
100
  export function getAttributeBuffersFromGeometry(
102
101
  device: Device,
103
102
  geometry: Geometry
104
- ): {attributes: Record<string, Buffer>, bufferLayout: BufferLayout[], vertexCount: number} {
103
+ ): {attributes: Record<string, Buffer>; bufferLayout: BufferLayout[]; vertexCount: number} {
105
104
  const bufferLayout: BufferLayout[] = [];
106
105
 
107
106
  const attributes: Record<string, Buffer> = {};
@@ -109,17 +108,25 @@ export function getAttributeBuffersFromGeometry(
109
108
  let name: string = attributeName;
110
109
  // TODO Map some GLTF attribute names (is this still needed?)
111
110
  switch (attributeName) {
112
- case 'POSITION': name = 'positions'; break;
113
- case 'NORMAL': name = 'normals'; break;
114
- case 'TEXCOORD_0': name = 'texCoords'; break;
115
- case 'COLOR_0': name = 'colors'; break;
111
+ case 'POSITION':
112
+ name = 'positions';
113
+ break;
114
+ case 'NORMAL':
115
+ name = 'normals';
116
+ break;
117
+ case 'TEXCOORD_0':
118
+ name = 'texCoords';
119
+ break;
120
+ case 'COLOR_0':
121
+ name = 'colors';
122
+ break;
116
123
  }
117
124
  attributes[name] = device.createBuffer({data: attribute.value, id: `${attributeName}-buffer`});
118
125
  const {value, size, normalized} = attribute;
119
126
  bufferLayout.push({name, format: getVertexFormatFromAttribute(value, size, normalized)});
120
127
  }
121
128
 
122
- const vertexCount = geometry._calculateVertexCount(geometry.attributes, geometry.indices)
129
+ const vertexCount = geometry._calculateVertexCount(geometry.attributes, geometry.indices);
123
130
 
124
131
  return {attributes, bufferLayout, vertexCount};
125
132
  }
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ export type {TextureTransformProps} from './transform/texture-transform';
23
23
  export {TextureTransform} from './transform/texture-transform';
24
24
 
25
25
  export {PipelineFactory} from './lib/pipeline-factory';
26
+ export {ShaderFactory} from './lib/shader-factory';
26
27
 
27
28
  // Utils
28
29
  export {ClipSpace} from './lib/clip-space';
@@ -58,3 +59,5 @@ export {TruncatedConeGeometry} from './geometries/truncated-cone-geometry';
58
59
  // EXPERIMENTAL
59
60
  export type {ShaderModuleInputs} from './shader-inputs';
60
61
  export {ShaderInputs as _ShaderInputs} from './shader-inputs';
62
+ export type {ComputationProps} from './computation';
63
+ export {Computation} from './computation';
@@ -1,4 +1,3 @@
1
-
2
1
  // ClipSpace
3
2
  import {Device, glsl} from '@luma.gl/core';
4
3
  import {Model, ModelProps} from '../model/model';
@@ -30,24 +29,21 @@ const POSITIONS = [-1, -1, 1, -1, -1, 1, 1, 1];
30
29
  */
31
30
  export class ClipSpace extends Model {
32
31
  constructor(device: Device, opts: Omit<ModelProps, 'vs' | 'vertexCount' | 'geometry'>) {
33
- const TEX_COORDS = POSITIONS.map((coord) => (coord === -1 ? 0 : coord));
32
+ const TEX_COORDS = POSITIONS.map(coord => (coord === -1 ? 0 : coord));
34
33
 
35
- super(
36
- device,
37
- {
38
- ...opts,
39
- vs: CLIPSPACE_VERTEX_SHADER,
34
+ super(device, {
35
+ ...opts,
36
+ vs: CLIPSPACE_VERTEX_SHADER,
37
+ vertexCount: 4,
38
+ geometry: new Geometry({
39
+ topology: 'triangle-strip',
40
40
  vertexCount: 4,
41
- geometry: new Geometry({
42
- topology: 'triangle-strip',
43
- vertexCount: 4,
44
- attributes: {
45
- aClipSpacePosition: {size: 2, value: new Float32Array(POSITIONS)},
46
- aTexCoord: {size: 2, value: new Float32Array(TEX_COORDS)},
47
- aCoordinate: {size: 2, value: new Float32Array(TEX_COORDS)}
48
- }
49
- })
50
- }
51
- );
41
+ attributes: {
42
+ aClipSpacePosition: {size: 2, value: new Float32Array(POSITIONS)},
43
+ aTexCoord: {size: 2, value: new Float32Array(TEX_COORDS)},
44
+ aCoordinate: {size: 2, value: new Float32Array(TEX_COORDS)}
45
+ }
46
+ })
47
+ });
52
48
  }
53
49
  }
@@ -1,6 +1,9 @@
1
- // luma.gl, MIT license
2
- import type {RenderPipelineProps} from '@luma.gl/core';
3
- import {Device, RenderPipeline} from '@luma.gl/core';
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {RenderPipelineProps, ComputePipelineProps} from '@luma.gl/core';
6
+ import {Device, RenderPipeline, ComputePipeline} from '@luma.gl/core';
4
7
 
5
8
  export type PipelineFactoryProps = RenderPipelineProps;
6
9
 
@@ -14,9 +17,16 @@ export class PipelineFactory {
14
17
 
15
18
  private _hashCounter: number = 0;
16
19
  private readonly _hashes: Record<string, number> = {};
17
- private readonly _useCounts: Record<string, number> = {};
18
- private readonly _pipelineCache: Record<string, RenderPipeline> = {};
19
-
20
+ private readonly _renderPipelineCache: Record<
21
+ string,
22
+ {pipeline: RenderPipeline; useCount: number}
23
+ > = {};
24
+ private readonly _computePipelineCache: Record<
25
+ string,
26
+ {pipeline: ComputePipeline; useCount: number}
27
+ > = {};
28
+
29
+ /** Get the singleton default pipeline factory for the specified device */
20
30
  static getDefaultPipelineFactory(device: Device): PipelineFactory {
21
31
  device._lumaData.defaultPipelineFactory =
22
32
  device._lumaData.defaultPipelineFactory || new PipelineFactory(device);
@@ -27,38 +37,62 @@ export class PipelineFactory {
27
37
  this.device = device;
28
38
  }
29
39
 
30
- createRenderPipeline(options: PipelineFactoryProps): RenderPipeline {
31
- const props: Required<PipelineFactoryProps> = {...PipelineFactory.defaultProps, ...options};
32
-
33
- const hash = this._hashRenderPipeline({...props});
40
+ /** Return a RenderPipeline matching props. Reuses a similar pipeline if already created. */
41
+ createRenderPipeline(props: RenderPipelineProps): RenderPipeline {
42
+ const allProps: Required<RenderPipelineProps> = {...RenderPipeline.defaultProps, ...props};
34
43
 
35
- if (!this._pipelineCache[hash]) {
36
- const pipeline = this.device.createRenderPipeline({...props});
44
+ const hash = this._hashRenderPipeline(allProps);
37
45
 
46
+ if (!this._renderPipelineCache[hash]) {
47
+ const pipeline = this.device.createRenderPipeline({
48
+ ...allProps,
49
+ id: allProps.id ? `${allProps.id}-cached` : undefined
50
+ });
38
51
  pipeline.hash = hash;
39
- this._pipelineCache[hash] = pipeline;
40
- this._useCounts[hash] = 0;
52
+ this._renderPipelineCache[hash] = {pipeline, useCount: 0};
41
53
  }
42
54
 
43
- this._useCounts[hash]++;
55
+ this._renderPipelineCache[hash].useCount++;
56
+ return this._renderPipelineCache[hash].pipeline;
57
+ }
58
+
59
+ createComputePipeline(props: ComputePipelineProps): ComputePipeline {
60
+ const allProps: Required<ComputePipelineProps> = {...ComputePipeline.defaultProps, ...props};
61
+
62
+ const hash = this._hashComputePipeline(allProps);
44
63
 
45
- return this._pipelineCache[hash];
64
+ if (!this._computePipelineCache[hash]) {
65
+ const pipeline = this.device.createComputePipeline({
66
+ ...allProps,
67
+ id: allProps.id ? `${allProps.id}-cached` : undefined
68
+ });
69
+ pipeline.hash = hash;
70
+ this._computePipelineCache[hash] = {pipeline, useCount: 0};
71
+ }
72
+
73
+ this._computePipelineCache[hash].useCount++;
74
+ return this._computePipelineCache[hash].pipeline;
46
75
  }
47
76
 
48
- release(pipeline: RenderPipeline): void {
77
+ release(pipeline: RenderPipeline | ComputePipeline): void {
49
78
  const hash = pipeline.hash;
50
- this._useCounts[hash]--;
51
- if (this._useCounts[hash] === 0) {
52
- this._pipelineCache[hash].destroy();
53
- delete this._pipelineCache[hash];
54
- delete this._useCounts[hash];
79
+ const cache =
80
+ pipeline instanceof ComputePipeline ? this._computePipelineCache : this._renderPipelineCache;
81
+ cache[hash].useCount--;
82
+ if (cache[hash].useCount === 0) {
83
+ cache[hash].pipeline.destroy();
84
+ delete cache[hash];
55
85
  }
56
86
  }
57
87
 
58
88
  // PRIVATE
89
+ private _hashComputePipeline(props: ComputePipelineProps): string {
90
+ const shaderHash = this._getHash(props.shader.source);
91
+ return `${shaderHash}`;
92
+ }
59
93
 
60
94
  /** Calculate a hash based on all the inputs for a render pipeline */
61
- private _hashRenderPipeline(props: PipelineFactoryProps): string {
95
+ private _hashRenderPipeline(props: RenderPipelineProps): string {
62
96
  const vsHash = this._getHash(props.vs.source);
63
97
  const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
64
98
 
@@ -68,16 +102,16 @@ export class PipelineFactory {
68
102
  const varyingHash = '-'; // `${varyingHashes.join('/')}B${bufferMode}`
69
103
  const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout));
70
104
 
71
- switch (this.device.info.type) {
72
- case 'webgpu':
105
+ switch (this.device.type) {
106
+ // case 'webgl':
107
+ // WebGL is more dynamic
108
+ // return `${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`;
109
+ default:
73
110
  // On WebGPU we need to rebuild the pipeline if topology, parameters or bufferLayout change
74
111
  const parameterHash = this._getHash(JSON.stringify(props.parameters));
75
112
  // TODO - Can json.stringify() generate different strings for equivalent objects if order of params is different?
76
113
  // create a deepHash() to deduplicate?
77
114
  return `${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}`;
78
- default:
79
- // WebGL is more dynamic
80
- return `${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`;
81
115
  }
82
116
  }
83
117
 
@@ -0,0 +1,57 @@
1
+ import {Device, Shader, ShaderProps} from '@luma.gl/core';
2
+
3
+ /** Manages a cached pool of Shaders for reuse. */
4
+ export class ShaderFactory {
5
+ static readonly defaultProps: Required<ShaderProps> = {...Shader.defaultProps};
6
+
7
+ public readonly device: Device;
8
+
9
+ private readonly _cache: Record<string, {shader: Shader; useCount: number}> = {};
10
+
11
+ /** Returns the default ShaderFactory for the given {@link Device}, creating one if necessary. */
12
+ static getDefaultShaderFactory(device: Device): ShaderFactory {
13
+ device._lumaData.defaultShaderFactory ||= new ShaderFactory(device);
14
+ return device._lumaData.defaultShaderFactory as ShaderFactory;
15
+ }
16
+
17
+ /** @internal */
18
+ constructor(device: Device) {
19
+ this.device = device;
20
+ }
21
+
22
+ /** Requests a {@link Shader} from the cache, creating a new Shader only if necessary. */
23
+ createShader(props: ShaderProps): Shader {
24
+ const key = this._hashShader(props);
25
+
26
+ let cacheEntry = this._cache[key];
27
+ if (!cacheEntry) {
28
+ const shader = this.device.createShader({
29
+ ...props,
30
+ id: props.id ? `${props.id}-cached` : undefined
31
+ });
32
+ this._cache[key] = cacheEntry = {shader, useCount: 0};
33
+ }
34
+
35
+ cacheEntry.useCount++;
36
+ return cacheEntry.shader;
37
+ }
38
+
39
+ /** Releases a previously-requested {@link Shader}, destroying it if no users remain. */
40
+ release(shader: Shader): void {
41
+ const key = this._hashShader(shader);
42
+ const cacheEntry = this._cache[key];
43
+ if (cacheEntry) {
44
+ cacheEntry.useCount--;
45
+ if (cacheEntry.useCount === 0) {
46
+ delete this._cache[key];
47
+ cacheEntry.shader.destroy();
48
+ }
49
+ }
50
+ }
51
+
52
+ // PRIVATE
53
+
54
+ private _hashShader(value: Shader | ShaderProps): string {
55
+ return `${value.stage}:${value.source}`;
56
+ }
57
+ }