@luma.gl/engine 9.3.0-alpha.4 → 9.3.0-alpha.6

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 (88) hide show
  1. package/dist/animation-loop/animation-loop.d.ts +8 -4
  2. package/dist/animation-loop/animation-loop.d.ts.map +1 -1
  3. package/dist/animation-loop/animation-loop.js +70 -43
  4. package/dist/animation-loop/animation-loop.js.map +1 -1
  5. package/dist/animation-loop/make-animation-loop.js +7 -1
  6. package/dist/animation-loop/make-animation-loop.js.map +1 -1
  7. package/dist/animation-loop/request-animation-frame.d.ts.map +1 -1
  8. package/dist/animation-loop/request-animation-frame.js +23 -6
  9. package/dist/animation-loop/request-animation-frame.js.map +1 -1
  10. package/dist/dist.dev.js +679 -927
  11. package/dist/dist.min.js +39 -240
  12. package/dist/dynamic-texture/dynamic-texture.d.ts +3 -3
  13. package/dist/dynamic-texture/dynamic-texture.d.ts.map +1 -1
  14. package/dist/dynamic-texture/dynamic-texture.js +170 -52
  15. package/dist/dynamic-texture/dynamic-texture.js.map +1 -1
  16. package/dist/dynamic-texture/texture-data.d.ts +4 -0
  17. package/dist/dynamic-texture/texture-data.d.ts.map +1 -1
  18. package/dist/dynamic-texture/texture-data.js +8 -0
  19. package/dist/dynamic-texture/texture-data.js.map +1 -1
  20. package/dist/factories/pipeline-factory.d.ts +7 -5
  21. package/dist/factories/pipeline-factory.d.ts.map +1 -1
  22. package/dist/factories/pipeline-factory.js +71 -36
  23. package/dist/factories/pipeline-factory.js.map +1 -1
  24. package/dist/factories/shader-factory.d.ts +0 -3
  25. package/dist/factories/shader-factory.d.ts.map +1 -1
  26. package/dist/factories/shader-factory.js +13 -19
  27. package/dist/factories/shader-factory.js.map +1 -1
  28. package/dist/geometries/cone-geometry.d.ts +3 -1
  29. package/dist/geometries/cone-geometry.d.ts.map +1 -1
  30. package/dist/geometries/cone-geometry.js.map +1 -1
  31. package/dist/geometries/cylinder-geometry.d.ts +2 -1
  32. package/dist/geometries/cylinder-geometry.d.ts.map +1 -1
  33. package/dist/geometries/cylinder-geometry.js.map +1 -1
  34. package/dist/index.cjs +683 -922
  35. package/dist/index.cjs.map +4 -4
  36. package/dist/model/model.d.ts +3 -1
  37. package/dist/model/model.d.ts.map +1 -1
  38. package/dist/model/model.js +11 -9
  39. package/dist/model/model.js.map +1 -1
  40. package/dist/models/billboard-texture-model.d.ts.map +1 -1
  41. package/dist/models/billboard-texture-model.js +10 -8
  42. package/dist/models/billboard-texture-model.js.map +1 -1
  43. package/dist/models/clip-space.js +7 -7
  44. package/dist/modules/picking/index-picking.d.ts +1 -1
  45. package/dist/modules/picking/index-picking.d.ts.map +1 -1
  46. package/dist/modules/picking/index-picking.js +0 -6
  47. package/dist/modules/picking/index-picking.js.map +1 -1
  48. package/dist/passes/get-fragment-shader.js +11 -30
  49. package/dist/passes/get-fragment-shader.js.map +1 -1
  50. package/dist/passes/shader-pass-renderer.d.ts +0 -2
  51. package/dist/passes/shader-pass-renderer.d.ts.map +1 -1
  52. package/dist/passes/shader-pass-renderer.js +4 -31
  53. package/dist/passes/shader-pass-renderer.js.map +1 -1
  54. package/dist/scenegraph/group-node.d.ts +5 -0
  55. package/dist/scenegraph/group-node.d.ts.map +1 -1
  56. package/dist/scenegraph/group-node.js +12 -0
  57. package/dist/scenegraph/group-node.js.map +1 -1
  58. package/dist/scenegraph/model-node.d.ts +2 -2
  59. package/dist/scenegraph/model-node.d.ts.map +1 -1
  60. package/dist/scenegraph/model-node.js.map +1 -1
  61. package/dist/scenegraph/scenegraph-node.d.ts +1 -1
  62. package/dist/scenegraph/scenegraph-node.d.ts.map +1 -1
  63. package/dist/scenegraph/scenegraph-node.js +23 -15
  64. package/dist/scenegraph/scenegraph-node.js.map +1 -1
  65. package/package.json +2 -2
  66. package/src/animation-loop/animation-loop.ts +75 -46
  67. package/src/animation-loop/make-animation-loop.ts +13 -5
  68. package/src/animation-loop/request-animation-frame.ts +32 -6
  69. package/src/dynamic-texture/dynamic-texture.ts +226 -65
  70. package/src/dynamic-texture/texture-data.ts +14 -0
  71. package/src/factories/pipeline-factory.ts +87 -46
  72. package/src/factories/shader-factory.ts +16 -20
  73. package/src/geometries/cone-geometry.ts +6 -1
  74. package/src/geometries/cylinder-geometry.ts +5 -1
  75. package/src/model/model.ts +14 -10
  76. package/src/models/billboard-texture-model.ts +10 -8
  77. package/src/models/clip-space.ts +7 -7
  78. package/src/modules/picking/index-picking.ts +0 -6
  79. package/src/passes/get-fragment-shader.ts +11 -30
  80. package/src/passes/shader-pass-renderer.ts +4 -33
  81. package/src/scenegraph/group-node.ts +16 -0
  82. package/src/scenegraph/model-node.ts +2 -2
  83. package/src/scenegraph/scenegraph-node.ts +27 -16
  84. package/dist/dynamic-texture/mipmaps.d.ts +0 -6
  85. package/dist/dynamic-texture/mipmaps.d.ts.map +0 -1
  86. package/dist/dynamic-texture/mipmaps.js +0 -441
  87. package/dist/dynamic-texture/mipmaps.js.map +0 -1
  88. package/src/dynamic-texture/mipmaps.ts +0 -517
@@ -2,15 +2,14 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import type {RenderPipelineProps, ComputePipelineProps} from '@luma.gl/core';
6
- import {Device, RenderPipeline, ComputePipeline, log} from '@luma.gl/core';
5
+ import type {RenderPipelineProps, ComputePipelineProps, SharedRenderPipeline} from '@luma.gl/core';
6
+ import {Device, RenderPipeline, ComputePipeline, Resource, log} from '@luma.gl/core';
7
7
  import type {EngineModuleState} from '../types';
8
8
  import {uid} from '../utils/uid';
9
9
 
10
10
  export type PipelineFactoryProps = RenderPipelineProps;
11
11
 
12
- type RenderPipelineCacheItem = {pipeline: RenderPipeline; useCount: number};
13
- type ComputePipelineCacheItem = {pipeline: ComputePipeline; useCount: number};
12
+ type CacheItem<ResourceT extends Resource<any>> = {resource: ResourceT; useCount: number};
14
13
 
15
14
  /**
16
15
  * Efficiently creates / caches pipelines
@@ -26,14 +25,12 @@ export class PipelineFactory {
26
25
  }
27
26
 
28
27
  readonly device: Device;
29
- readonly cachingEnabled: boolean;
30
- readonly destroyPolicy: 'unused' | 'never';
31
- readonly debug: boolean;
32
28
 
33
29
  private _hashCounter: number = 0;
34
30
  private readonly _hashes: Record<string, number> = {};
35
- private readonly _renderPipelineCache: Record<string, RenderPipelineCacheItem> = {};
36
- private readonly _computePipelineCache: Record<string, ComputePipelineCacheItem> = {};
31
+ private readonly _renderPipelineCache: Record<string, CacheItem<RenderPipeline>> = {};
32
+ private readonly _computePipelineCache: Record<string, CacheItem<ComputePipeline>> = {};
33
+ private readonly _sharedRenderPipelineCache: Record<string, CacheItem<SharedRenderPipeline>> = {};
37
34
 
38
35
  get [Symbol.toStringTag](): string {
39
36
  return 'PipelineFactory';
@@ -45,14 +42,11 @@ export class PipelineFactory {
45
42
 
46
43
  constructor(device: Device) {
47
44
  this.device = device;
48
- this.cachingEnabled = device.props._cachePipelines;
49
- this.destroyPolicy = device.props._cacheDestroyPolicy;
50
- this.debug = device.props.debugFactories;
51
45
  }
52
46
 
53
47
  /** Return a RenderPipeline matching supplied props. Reuses an equivalent pipeline if already created. */
54
48
  createRenderPipeline(props: RenderPipelineProps): RenderPipeline {
55
- if (!this.cachingEnabled) {
49
+ if (!this.device.props._cachePipelines) {
56
50
  return this.device.createRenderPipeline(props);
57
51
  }
58
52
 
@@ -61,23 +55,28 @@ export class PipelineFactory {
61
55
  const cache = this._renderPipelineCache;
62
56
  const hash = this._hashRenderPipeline(allProps);
63
57
 
64
- let pipeline: RenderPipeline = cache[hash]?.pipeline;
58
+ let pipeline: RenderPipeline = cache[hash]?.resource;
65
59
  if (!pipeline) {
60
+ const sharedRenderPipeline =
61
+ this.device.type === 'webgl' && this.device.props._sharePipelines
62
+ ? this.createSharedRenderPipeline(allProps)
63
+ : undefined;
66
64
  pipeline = this.device.createRenderPipeline({
67
65
  ...allProps,
68
- id: allProps.id ? `${allProps.id}-cached` : uid('unnamed-cached')
66
+ id: allProps.id ? `${allProps.id}-cached` : uid('unnamed-cached'),
67
+ _sharedRenderPipeline: sharedRenderPipeline
69
68
  });
70
69
  pipeline.hash = hash;
71
- cache[hash] = {pipeline, useCount: 1};
72
- if (this.debug) {
70
+ cache[hash] = {resource: pipeline, useCount: 1};
71
+ if (this.device.props.debugFactories) {
73
72
  log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
74
73
  }
75
74
  } else {
76
75
  cache[hash].useCount++;
77
- if (this.debug) {
76
+ if (this.device.props.debugFactories) {
78
77
  log.log(
79
78
  3,
80
- `${this}: ${cache[hash].pipeline} reused, count=${cache[hash].useCount}, (id=${props.id})`
79
+ `${this}: ${cache[hash].resource} reused, count=${cache[hash].useCount}, (id=${props.id})`
81
80
  )();
82
81
  }
83
82
  }
@@ -87,7 +86,7 @@ export class PipelineFactory {
87
86
 
88
87
  /** Return a ComputePipeline matching supplied props. Reuses an equivalent pipeline if already created. */
89
88
  createComputePipeline(props: ComputePipelineProps): ComputePipeline {
90
- if (!this.cachingEnabled) {
89
+ if (!this.device.props._cachePipelines) {
91
90
  return this.device.createComputePipeline(props);
92
91
  }
93
92
 
@@ -96,23 +95,23 @@ export class PipelineFactory {
96
95
  const cache = this._computePipelineCache;
97
96
  const hash = this._hashComputePipeline(allProps);
98
97
 
99
- let pipeline: ComputePipeline = cache[hash]?.pipeline;
98
+ let pipeline: ComputePipeline = cache[hash]?.resource;
100
99
  if (!pipeline) {
101
100
  pipeline = this.device.createComputePipeline({
102
101
  ...allProps,
103
102
  id: allProps.id ? `${allProps.id}-cached` : undefined
104
103
  });
105
104
  pipeline.hash = hash;
106
- cache[hash] = {pipeline, useCount: 1};
107
- if (this.debug) {
105
+ cache[hash] = {resource: pipeline, useCount: 1};
106
+ if (this.device.props.debugFactories) {
108
107
  log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
109
108
  }
110
109
  } else {
111
110
  cache[hash].useCount++;
112
- if (this.debug) {
111
+ if (this.device.props.debugFactories) {
113
112
  log.log(
114
113
  3,
115
- `${this}: ${cache[hash].pipeline} reused, count=${cache[hash].useCount}, (id=${props.id})`
114
+ `${this}: ${cache[hash].resource} reused, count=${cache[hash].useCount}, (id=${props.id})`
116
115
  )();
117
116
  }
118
117
  }
@@ -121,7 +120,7 @@ export class PipelineFactory {
121
120
  }
122
121
 
123
122
  release(pipeline: RenderPipeline | ComputePipeline): void {
124
- if (!this.cachingEnabled) {
123
+ if (!this.device.props._cachePipelines) {
125
124
  pipeline.destroy();
126
125
  return;
127
126
  }
@@ -132,40 +131,72 @@ export class PipelineFactory {
132
131
  cache[hash].useCount--;
133
132
  if (cache[hash].useCount === 0) {
134
133
  this._destroyPipeline(pipeline);
135
- if (this.debug) {
134
+ if (this.device.props.debugFactories) {
136
135
  log.log(3, `${this}: ${pipeline} released and destroyed`)();
137
136
  }
138
137
  } else if (cache[hash].useCount < 0) {
139
138
  log.error(`${this}: ${pipeline} released, useCount < 0, resetting`)();
140
139
  cache[hash].useCount = 0;
141
- } else if (this.debug) {
140
+ } else if (this.device.props.debugFactories) {
142
141
  log.log(3, `${this}: ${pipeline} released, count=${cache[hash].useCount}`)();
143
142
  }
144
143
  }
145
144
 
145
+ createSharedRenderPipeline(props: RenderPipelineProps): SharedRenderPipeline {
146
+ const sharedPipelineHash = this._hashSharedRenderPipeline(props);
147
+ let sharedCacheItem = this._sharedRenderPipelineCache[sharedPipelineHash];
148
+ if (!sharedCacheItem) {
149
+ const sharedRenderPipeline = this.device._createSharedRenderPipelineWebGL(props);
150
+ sharedCacheItem = {resource: sharedRenderPipeline, useCount: 0};
151
+ this._sharedRenderPipelineCache[sharedPipelineHash] = sharedCacheItem;
152
+ }
153
+ sharedCacheItem.useCount++;
154
+ return sharedCacheItem.resource;
155
+ }
156
+
157
+ releaseSharedRenderPipeline(pipeline: RenderPipeline): void {
158
+ if (!pipeline.sharedRenderPipeline) {
159
+ return;
160
+ }
161
+
162
+ const sharedPipelineHash = this._hashSharedRenderPipeline(pipeline.sharedRenderPipeline.props);
163
+ const sharedCacheItem = this._sharedRenderPipelineCache[sharedPipelineHash];
164
+ if (!sharedCacheItem) {
165
+ return;
166
+ }
167
+
168
+ sharedCacheItem.useCount--;
169
+ if (sharedCacheItem.useCount === 0) {
170
+ sharedCacheItem.resource.destroy();
171
+ delete this._sharedRenderPipelineCache[sharedPipelineHash];
172
+ }
173
+ }
174
+
146
175
  // PRIVATE
147
176
 
148
- /** Destroy a cached pipeline, removing it from the cache (depending on destroy policy) */
177
+ /** Destroy a cached pipeline, removing it from the cache if configured to do so. */
149
178
  private _destroyPipeline(pipeline: RenderPipeline | ComputePipeline): boolean {
150
179
  const cache = this._getCache(pipeline);
151
180
 
152
- switch (this.destroyPolicy) {
153
- case 'never':
154
- return false;
155
- case 'unused':
156
- delete cache[pipeline.hash];
157
- pipeline.destroy();
158
- return true;
181
+ if (!this.device.props._destroyPipelines) {
182
+ return false;
183
+ }
184
+
185
+ delete cache[pipeline.hash];
186
+ pipeline.destroy();
187
+ if (pipeline instanceof RenderPipeline) {
188
+ this.releaseSharedRenderPipeline(pipeline);
159
189
  }
190
+ return true;
160
191
  }
161
192
 
162
193
  /** Get the appropriate cache for the type of pipeline */
163
194
  private _getCache(
164
195
  pipeline: RenderPipeline | ComputePipeline
165
- ): Record<string, RenderPipelineCacheItem> | Record<string, ComputePipelineCacheItem> {
196
+ ): Record<string, CacheItem<RenderPipeline>> | Record<string, CacheItem<ComputePipeline>> {
166
197
  let cache:
167
- | Record<string, RenderPipelineCacheItem>
168
- | Record<string, ComputePipelineCacheItem>
198
+ | Record<string, CacheItem<RenderPipeline>>
199
+ | Record<string, CacheItem<ComputePipeline>>
169
200
  | undefined;
170
201
  if (pipeline instanceof ComputePipeline) {
171
202
  cache = this._computePipelineCache;
@@ -193,18 +224,16 @@ export class PipelineFactory {
193
224
  private _hashRenderPipeline(props: RenderPipelineProps): string {
194
225
  const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
195
226
  const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
196
-
197
- // WebGL specific
198
- // const {varyings = [], bufferMode = {}} = props;
199
- // const varyingHashes = varyings.map((v) => this._getHash(v));
200
- const varyingHash = '-'; // `${varyingHashes.join('/')}B${bufferMode}`
227
+ const varyingHash = this._getWebGLVaryingHash(props);
201
228
  const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout));
202
229
 
203
230
  const {type} = this.device;
204
231
  switch (type) {
205
232
  case 'webgl':
206
- // WebGL is more dynamic
207
- return `${type}/R/${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`;
233
+ // WebGL wrappers preserve default topology and parameter semantics for direct
234
+ // callers, even though the underlying linked program may be shared separately.
235
+ const webglParameterHash = this._getHash(JSON.stringify(props.parameters));
236
+ return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${webglParameterHash}BL${bufferLayoutHash}`;
208
237
 
209
238
  case 'webgpu':
210
239
  default:
@@ -216,10 +245,22 @@ export class PipelineFactory {
216
245
  }
217
246
  }
218
247
 
248
+ private _hashSharedRenderPipeline(props: RenderPipelineProps): string {
249
+ const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
250
+ const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
251
+ const varyingHash = this._getWebGLVaryingHash(props);
252
+ return `webgl/S/${vsHash}/${fsHash}V${varyingHash}`;
253
+ }
254
+
219
255
  private _getHash(key: string): number {
220
256
  if (this._hashes[key] === undefined) {
221
257
  this._hashes[key] = this._hashCounter++;
222
258
  }
223
259
  return this._hashes[key];
224
260
  }
261
+
262
+ private _getWebGLVaryingHash(props: RenderPipelineProps): number {
263
+ const {varyings = [], bufferMode = null} = props;
264
+ return this._getHash(JSON.stringify({varyings, bufferMode}));
265
+ }
225
266
  }
@@ -5,6 +5,8 @@
5
5
  import {Device, Shader, ShaderProps, log} from '@luma.gl/core';
6
6
  import type {EngineModuleState} from '../types';
7
7
 
8
+ type CacheItem = {resource: Shader; useCount: number};
9
+
8
10
  /** Manages a cached pool of Shaders for reuse. */
9
11
  export class ShaderFactory {
10
12
  static readonly defaultProps: Required<ShaderProps> = {...Shader.defaultProps};
@@ -17,11 +19,8 @@ export class ShaderFactory {
17
19
  }
18
20
 
19
21
  public readonly device: Device;
20
- readonly cachingEnabled: boolean;
21
- readonly destroyPolicy: 'unused' | 'never';
22
- readonly debug: boolean;
23
22
 
24
- private readonly _cache: Record<string, {shader: Shader; useCount: number}> = {};
23
+ private readonly _cache: Record<string, CacheItem> = {};
25
24
 
26
25
  get [Symbol.toStringTag](): string {
27
26
  return 'ShaderFactory';
@@ -34,14 +33,11 @@ export class ShaderFactory {
34
33
  /** @internal */
35
34
  constructor(device: Device) {
36
35
  this.device = device;
37
- this.cachingEnabled = device.props._cacheShaders;
38
- this.destroyPolicy = device.props._cacheDestroyPolicy;
39
- this.debug = true; // device.props.debugFactories;
40
36
  }
41
37
 
42
38
  /** Requests a {@link Shader} from the cache, creating a new Shader only if necessary. */
43
39
  createShader(props: ShaderProps): Shader {
44
- if (!this.cachingEnabled) {
40
+ if (!this.device.props._cacheShaders) {
45
41
  return this.device.createShader(props);
46
42
  }
47
43
 
@@ -49,30 +45,30 @@ export class ShaderFactory {
49
45
 
50
46
  let cacheEntry = this._cache[key];
51
47
  if (!cacheEntry) {
52
- const shader = this.device.createShader({
48
+ const resource = this.device.createShader({
53
49
  ...props,
54
50
  id: props.id ? `${props.id}-cached` : undefined
55
51
  });
56
- this._cache[key] = cacheEntry = {shader, useCount: 1};
57
- if (this.debug) {
58
- log.log(3, `${this}: Created new shader ${shader.id}`)();
52
+ this._cache[key] = cacheEntry = {resource, useCount: 1};
53
+ if (this.device.props.debugFactories) {
54
+ log.log(3, `${this}: Created new shader ${resource.id}`)();
59
55
  }
60
56
  } else {
61
57
  cacheEntry.useCount++;
62
- if (this.debug) {
58
+ if (this.device.props.debugFactories) {
63
59
  log.log(
64
60
  3,
65
- `${this}: Reusing shader ${cacheEntry.shader.id} count=${cacheEntry.useCount}`
61
+ `${this}: Reusing shader ${cacheEntry.resource.id} count=${cacheEntry.useCount}`
66
62
  )();
67
63
  }
68
64
  }
69
65
 
70
- return cacheEntry.shader;
66
+ return cacheEntry.resource;
71
67
  }
72
68
 
73
69
  /** Releases a previously-requested {@link Shader}, destroying it if no users remain. */
74
70
  release(shader: Shader): void {
75
- if (!this.cachingEnabled) {
71
+ if (!this.device.props._cacheShaders) {
76
72
  shader.destroy();
77
73
  return;
78
74
  }
@@ -82,16 +78,16 @@ export class ShaderFactory {
82
78
  if (cacheEntry) {
83
79
  cacheEntry.useCount--;
84
80
  if (cacheEntry.useCount === 0) {
85
- if (this.destroyPolicy === 'unused') {
81
+ if (this.device.props._destroyShaders) {
86
82
  delete this._cache[key];
87
- cacheEntry.shader.destroy();
88
- if (this.debug) {
83
+ cacheEntry.resource.destroy();
84
+ if (this.device.props.debugFactories) {
89
85
  log.log(3, `${this}: Releasing shader ${shader.id}, destroyed`)();
90
86
  }
91
87
  }
92
88
  } else if (cacheEntry.useCount < 0) {
93
89
  throw new Error(`ShaderFactory: Shader ${shader.id} released too many times`);
94
- } else if (this.debug) {
90
+ } else if (this.device.props.debugFactories) {
95
91
  log.log(3, `${this}: Releasing shader ${shader.id} count=${cacheEntry.useCount}`)();
96
92
  }
97
93
  }
@@ -2,13 +2,18 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
+ import type {TruncatedConeGeometryProps} from './truncated-cone-geometry';
5
6
  import {TruncatedConeGeometry} from './truncated-cone-geometry';
6
7
  import {uid} from '../utils/uid';
7
8
 
8
- export type ConeGeometryProps = {
9
+ export type ConeGeometryProps = Omit<
10
+ TruncatedConeGeometryProps,
11
+ 'topRadius' | 'bottomRadius' | 'topCap' | 'bottomCap'
12
+ > & {
9
13
  id?: string;
10
14
  radius?: number;
11
15
  cap?: boolean;
16
+ attributes?: any;
12
17
  };
13
18
 
14
19
  export class ConeGeometry extends TruncatedConeGeometry {
@@ -2,10 +2,14 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
+ import type {TruncatedConeGeometryProps} from './truncated-cone-geometry';
5
6
  import {TruncatedConeGeometry} from './truncated-cone-geometry';
6
7
  import {uid} from '../utils/uid';
7
8
 
8
- export type CylinderGeometryProps = {
9
+ export type CylinderGeometryProps = Omit<
10
+ TruncatedConeGeometryProps,
11
+ 'topRadius' | 'bottomRadius'
12
+ > & {
9
13
  id?: string;
10
14
  radius?: number;
11
15
  attributes?: any;
@@ -64,6 +64,8 @@ export type ModelProps = Omit<RenderPipelineProps, 'vs' | 'fs' | 'bindings'> & {
64
64
  shaderInputs?: ShaderInputs;
65
65
  /** Bindings */
66
66
  bindings?: Record<string, Binding | DynamicTexture>;
67
+ /** WebGL-only uniforms */
68
+ uniforms?: Record<string, unknown>;
67
69
  /** Parameters that are built into the pipeline */
68
70
  parameters?: RenderPipelineParameters;
69
71
 
@@ -114,7 +116,7 @@ export type ModelProps = Omit<RenderPipelineProps, 'vs' | 'fs' | 'bindings'> & {
114
116
  * - Reuses and lazily recompiles {@link RenderPipeline | pipelines} as needed.
115
117
  * - Integrates with `@luma.gl/shadertools` to assemble GLSL or WGSL from shader modules.
116
118
  * - Manages geometry attributes and buffer bindings.
117
- * - Accepts textures, samplers and uniform buffers as bindings, including `AsyncTexture`.
119
+ * - Accepts textures, samplers and uniform buffers as bindings, including `DynamicTexture`.
118
120
  * - Provides detailed debug logging and optional shader source inspection.
119
121
  */
120
122
  export class Model {
@@ -132,6 +134,8 @@ export class Model {
132
134
  indexBuffer: null,
133
135
  attributes: {},
134
136
  constantAttributes: {},
137
+ bindings: {},
138
+ uniforms: {},
135
139
  varyings: [],
136
140
 
137
141
  isInstanced: undefined!,
@@ -357,7 +361,7 @@ export class Model {
357
361
  this.pipelineFactory.release(this.pipeline);
358
362
  // Release the shaders
359
363
  this.shaderFactory.release(this.pipeline.vs);
360
- if (this.pipeline.fs) {
364
+ if (this.pipeline.fs && this.pipeline.fs !== this.pipeline.vs) {
361
365
  this.shaderFactory.release(this.pipeline.fs);
362
366
  }
363
367
  this._uniformStore.destroy();
@@ -422,14 +426,7 @@ export class Model {
422
426
  // Application can call Model.predraw() to avoid this.
423
427
  this.pipeline = this._updatePipeline();
424
428
 
425
- // Set pipeline state, we may be sharing a pipeline so we need to set all state on every draw
426
- // Any caching needs to be done inside the pipeline functions
427
- // TODO this is a busy initialized check for all bindings every frame
428
-
429
429
  const syncBindings = this._getBindings();
430
- this.pipeline.setBindings(syncBindings, {
431
- disableWarnings: this.props.disableWarnings
432
- });
433
430
 
434
431
  const {indexBuffer} = this.vertexArray;
435
432
  const indexCount = indexBuffer
@@ -444,6 +441,11 @@ export class Model {
444
441
  instanceCount: this.instanceCount,
445
442
  indexCount,
446
443
  transformFeedback: this.transformFeedback || undefined,
444
+ // Pipelines may be shared across models when caching is enabled, so bindings
445
+ // and WebGL uniforms must be supplied on every draw instead of being stored
446
+ // on the pipeline instance.
447
+ bindings: syncBindings,
448
+ uniforms: this.props.uniforms,
447
449
  // WebGL shares underlying cached pipelines even for models that have different parameters and topology,
448
450
  // so we must provide our unique parameters to each draw
449
451
  // (In WebGPU most parameters are encoded in the pipeline and cannot be changed per draw call)
@@ -827,7 +829,9 @@ export class Model {
827
829
  );
828
830
 
829
831
  if (prevShaderVs) this.shaderFactory.release(prevShaderVs);
830
- if (prevShaderFs) this.shaderFactory.release(prevShaderFs);
832
+ if (prevShaderFs && prevShaderFs !== prevShaderVs) {
833
+ this.shaderFactory.release(prevShaderFs);
834
+ }
831
835
  }
832
836
  return this.pipeline;
833
837
  }
@@ -22,15 +22,15 @@ struct backgroundUniforms {
22
22
  };
23
23
  @group(0) @binding(2) var<uniform> background: backgroundUniforms;
24
24
 
25
- fn billboardTexture_getTextureUV(coordinates: vec2<f32>) -> vec2<f32> {
25
+ fn billboardTexture_getTextureUV(uv: vec2<f32>) -> vec2<f32> {
26
26
  let scale: vec2<f32> = background.scale;
27
- var position: vec2<f32> = (coordinates - vec2<f32>(0.5, 0.5)) / scale + vec2<f32>(0.5, 0.5);
27
+ var position: vec2<f32> = (uv - vec2<f32>(0.5, 0.5)) / scale + vec2<f32>(0.5, 0.5);
28
28
  return position;
29
29
  }
30
30
 
31
31
  @fragment
32
32
  fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> {
33
- let position: vec2<f32> = billboardTexture_getTextureUV(inputs.coordinate);
33
+ let position: vec2<f32> = billboardTexture_getTextureUV(inputs.uv);
34
34
  return textureSample(backgroundTexture, backgroundTextureSampler, position);
35
35
  }
36
36
  `;
@@ -79,21 +79,23 @@ export class BackgroundTextureModel extends ClipSpace {
79
79
 
80
80
  constructor(device: Device, props: BackgroundTextureModelProps) {
81
81
  super(device, {
82
+ ...props,
82
83
  id: props.id || 'background-texture-model',
83
84
  source: BACKGROUND_FS_WGSL,
84
85
  fs: BACKGROUND_FS,
85
- modules: [backgroundModule],
86
+ modules: [...(props.modules || []), backgroundModule],
86
87
  parameters: {
87
88
  depthWriteEnabled: false,
89
+ ...(props.parameters || {}),
88
90
  ...(props.blend
89
91
  ? {
90
92
  blend: true,
91
93
  blendColorOperation: 'add',
92
94
  blendAlphaOperation: 'add',
93
- blendColorSrcFactor: 'one',
94
- blendColorDstFactor: 'one-minus-src',
95
- blendAlphaSrcFactor: 'one',
96
- blendAlphaDstFactor: 'one-minus-src-alpha'
95
+ blendColorSrcFactor: 'one-minus-dst-alpha',
96
+ blendColorDstFactor: 'one',
97
+ blendAlphaSrcFactor: 'one-minus-dst-alpha',
98
+ blendAlphaDstFactor: 'one'
97
99
  }
98
100
  : {})
99
101
  }
@@ -10,9 +10,9 @@ import {uid} from '../utils/uid';
10
10
 
11
11
  const CLIPSPACE_VERTEX_SHADER_WGSL = /* wgsl */ `\
12
12
  struct VertexInputs {
13
- @location(0) clipSpacePosition: vec2<f32>,
14
- @location(1) texCoord: vec2<f32>,
15
- @location(2) coordinate: vec2<f32>
13
+ @location(0) clipSpacePositions: vec2<f32>,
14
+ @location(1) texCoords: vec2<f32>,
15
+ @location(2) coordinates: vec2<f32>
16
16
  }
17
17
 
18
18
  struct FragmentInputs {
@@ -25,10 +25,10 @@ struct FragmentInputs {
25
25
  @vertex
26
26
  fn vertexMain(inputs: VertexInputs) -> FragmentInputs {
27
27
  var outputs: FragmentInputs;
28
- outputs.Position = vec4(inputs.clipSpacePosition, 0., 1.);
29
- outputs.position = inputs.clipSpacePosition;
30
- outputs.coordinate = inputs.coordinate;
31
- outputs.uv = inputs.texCoord;
28
+ outputs.Position = vec4(inputs.clipSpacePositions, 0., 1.);
29
+ outputs.position = inputs.clipSpacePositions;
30
+ outputs.coordinate = inputs.coordinates;
31
+ outputs.uv = inputs.texCoords;
32
32
  return outputs;
33
33
  }
34
34
  `;
@@ -16,12 +16,6 @@ const INDEX_PICKING_MODE_INSTANCE = 0;
16
16
  const INDEX_PICKING_MODE_CUSTOM = 1;
17
17
  const INDEX_PICKING_INVALID_INDEX = ${INVALID_INDEX}; // 2^32 - 1
18
18
 
19
- struct indexPickingFragmentInputs = {
20
- objectIndex: int32;
21
- };
22
-
23
- let indexPickingFragmentInputs: indexPickingFragmentInputs;
24
-
25
19
  /**
26
20
  * Vertex shaders should call this function to set the object index.
27
21
  * If using instance or vertex mode, argument will be ignored, 0 can be supplied.
@@ -36,26 +36,16 @@ export function getFragmentShaderForRenderPass(options: {
36
36
  /** Get a filtering WGSL fragment shader */
37
37
  function getFilterShaderWGSL(func: string) {
38
38
  return /* wgsl */ `\
39
- // Binding 0:1 is reserved for shader passes
40
- // @group(0) @binding(0) var<uniform> brightnessContrast : brightnessContrastUniforms;
41
- @group(0) @binding(1) var texture: texture_2d<f32>;
42
- @group(0) @binding(2) var textureSampler: sampler;
43
-
44
- // This needs to be aligned with
45
- // struct FragmentInputs {
46
- // @location(0) fragUV: vec2f,
47
- // @location(1) fragPosition: vec4f,
48
- // @location(2) fragCoordinate: vec4f
49
- // };
39
+ @group(0) @binding(0) var sourceTexture: texture_2d<f32>;
40
+ @group(0) @binding(2) var sourceTextureSampler: sampler;
50
41
 
51
42
  @fragment
52
43
  fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
53
- let fragUV = inputs.uv;
54
- let fragCoordinate = inputs.coordinate;
55
- let texSize = vec2f(textureDimensions(texture, 0));
44
+ let texCoord = inputs.coordinate;
45
+ let texSize = vec2f(textureDimensions(sourceTexture));
56
46
 
57
- var fragColor = textureSample(texture, textureSampler, fragUV);
58
- fragColor = ${func}(fragColor, texSize, fragCoordinate);
47
+ var fragColor = textureSample(sourceTexture, sourceTextureSampler, texCoord);
48
+ fragColor = ${func}(fragColor, texSize, texCoord);
59
49
  return fragColor;
60
50
  }
61
51
  `;
@@ -64,23 +54,14 @@ fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
64
54
  /** Get a sampling WGSL fragment shader */
65
55
  function getSamplerShaderWGSL(func: string) {
66
56
  return /* wgsl */ `\
67
- // Binding 0:1 is reserved for shader passes
68
- @group(0) @binding(0) var<uniform> brightnessContrast : brightnessContrastUniforms;
69
- @group(0) @binding(1) var texture: texture_2d<f32>;
70
- @group(0) @binding(2) var sampler: sampler;
71
-
72
- struct FragmentInputs = {
73
- @location(0) fragUV: vec2f,
74
- @location(1) fragPosition: vec4f,
75
- @location(2) fragCoordinate: vec4f
76
- };
57
+ @group(0) @binding(0) var sourceTexture: texture_2d<f32>;
58
+ @group(0) @binding(2) var sourceTextureSampler: sampler;
77
59
 
78
60
  @fragment
79
61
  fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
80
- let texSize = vec2f(textureDimensions(texture, 0));
81
- var fragColor = textureSample(texture, sampler, fragUV);
82
- fragColor = ${func}(fragColor, texSize, texCoord);
83
- return fragColor;
62
+ let texCoord = inputs.coordinate;
63
+ let texSize = vec2f(textureDimensions(sourceTexture));
64
+ return ${func}(sourceTexture, sourceTextureSampler, texSize, texCoord);
84
65
  }
85
66
  `;
86
67
  }