@luma.gl/core 9.3.0-alpha.2 → 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 (121) hide show
  1. package/dist/adapter/canvas-context.d.ts +6 -181
  2. package/dist/adapter/canvas-context.d.ts.map +1 -1
  3. package/dist/adapter/canvas-context.js +5 -450
  4. package/dist/adapter/canvas-context.js.map +1 -1
  5. package/dist/adapter/canvas-observer.d.ts +32 -0
  6. package/dist/adapter/canvas-observer.d.ts.map +1 -0
  7. package/dist/adapter/canvas-observer.js +90 -0
  8. package/dist/adapter/canvas-observer.js.map +1 -0
  9. package/dist/adapter/canvas-surface.d.ts +150 -0
  10. package/dist/adapter/canvas-surface.d.ts.map +1 -0
  11. package/dist/adapter/canvas-surface.js +392 -0
  12. package/dist/adapter/canvas-surface.js.map +1 -0
  13. package/dist/adapter/device.d.ts +64 -9
  14. package/dist/adapter/device.d.ts.map +1 -1
  15. package/dist/adapter/device.js +108 -4
  16. package/dist/adapter/device.js.map +1 -1
  17. package/dist/adapter/luma.js +1 -1
  18. package/dist/adapter/presentation-context.d.ts +11 -0
  19. package/dist/adapter/presentation-context.d.ts.map +1 -0
  20. package/dist/adapter/presentation-context.js +12 -0
  21. package/dist/adapter/presentation-context.js.map +1 -0
  22. package/dist/adapter/resources/buffer.d.ts +1 -1
  23. package/dist/adapter/resources/buffer.d.ts.map +1 -1
  24. package/dist/adapter/resources/buffer.js +14 -6
  25. package/dist/adapter/resources/buffer.js.map +1 -1
  26. package/dist/adapter/resources/command-encoder.d.ts +27 -6
  27. package/dist/adapter/resources/command-encoder.d.ts.map +1 -1
  28. package/dist/adapter/resources/command-encoder.js +65 -1
  29. package/dist/adapter/resources/command-encoder.js.map +1 -1
  30. package/dist/adapter/resources/fence.d.ts +1 -1
  31. package/dist/adapter/resources/fence.d.ts.map +1 -1
  32. package/dist/adapter/resources/fence.js +3 -1
  33. package/dist/adapter/resources/fence.js.map +1 -1
  34. package/dist/adapter/resources/framebuffer.d.ts.map +1 -1
  35. package/dist/adapter/resources/framebuffer.js +9 -11
  36. package/dist/adapter/resources/framebuffer.js.map +1 -1
  37. package/dist/adapter/resources/query-set.d.ts +17 -1
  38. package/dist/adapter/resources/query-set.d.ts.map +1 -1
  39. package/dist/adapter/resources/query-set.js.map +1 -1
  40. package/dist/adapter/resources/render-pipeline.d.ts +19 -7
  41. package/dist/adapter/resources/render-pipeline.d.ts.map +1 -1
  42. package/dist/adapter/resources/render-pipeline.js +20 -2
  43. package/dist/adapter/resources/render-pipeline.js.map +1 -1
  44. package/dist/adapter/resources/resource.d.ts +8 -0
  45. package/dist/adapter/resources/resource.d.ts.map +1 -1
  46. package/dist/adapter/resources/resource.js +240 -14
  47. package/dist/adapter/resources/resource.js.map +1 -1
  48. package/dist/adapter/resources/shader.js +27 -25
  49. package/dist/adapter/resources/shader.js.map +1 -1
  50. package/dist/adapter/resources/shared-render-pipeline.d.ts +22 -0
  51. package/dist/adapter/resources/shared-render-pipeline.d.ts.map +1 -0
  52. package/dist/adapter/resources/shared-render-pipeline.js +25 -0
  53. package/dist/adapter/resources/shared-render-pipeline.js.map +1 -0
  54. package/dist/adapter/resources/texture.d.ts +78 -12
  55. package/dist/adapter/resources/texture.d.ts.map +1 -1
  56. package/dist/adapter/resources/texture.js +182 -30
  57. package/dist/adapter/resources/texture.js.map +1 -1
  58. package/dist/adapter-utils/format-compiler-log.d.ts.map +1 -1
  59. package/dist/adapter-utils/format-compiler-log.js +23 -15
  60. package/dist/adapter-utils/format-compiler-log.js.map +1 -1
  61. package/dist/dist.dev.js +1265 -362
  62. package/dist/dist.min.js +10 -9
  63. package/dist/index.cjs +1027 -243
  64. package/dist/index.cjs.map +4 -4
  65. package/dist/index.d.ts +5 -1
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +3 -0
  68. package/dist/index.js.map +1 -1
  69. package/dist/shadertypes/data-types/decode-shader-types.d.ts +2 -2
  70. package/dist/shadertypes/data-types/decode-shader-types.d.ts.map +1 -1
  71. package/dist/shadertypes/data-types/decode-shader-types.js +11 -2
  72. package/dist/shadertypes/data-types/decode-shader-types.js.map +1 -1
  73. package/dist/shadertypes/textures/pixel-utils.js +4 -4
  74. package/dist/shadertypes/textures/pixel-utils.js.map +1 -1
  75. package/dist/shadertypes/textures/texture-format-decoder.d.ts.map +1 -1
  76. package/dist/shadertypes/textures/texture-format-decoder.js +53 -8
  77. package/dist/shadertypes/textures/texture-format-decoder.js.map +1 -1
  78. package/dist/shadertypes/textures/texture-format-table.d.ts.map +1 -1
  79. package/dist/shadertypes/textures/texture-format-table.js +10 -9
  80. package/dist/shadertypes/textures/texture-format-table.js.map +1 -1
  81. package/dist/shadertypes/textures/texture-formats.d.ts +5 -2
  82. package/dist/shadertypes/textures/texture-formats.d.ts.map +1 -1
  83. package/dist/shadertypes/textures/texture-formats.js.map +1 -1
  84. package/dist/shadertypes/textures/texture-layout.d.ts +1 -1
  85. package/dist/utils/array-equal.d.ts +1 -1
  86. package/dist/utils/array-equal.d.ts.map +1 -1
  87. package/dist/utils/array-equal.js +15 -9
  88. package/dist/utils/array-equal.js.map +1 -1
  89. package/dist/utils/assert.d.ts +5 -0
  90. package/dist/utils/assert.d.ts.map +1 -0
  91. package/dist/utils/assert.js +17 -0
  92. package/dist/utils/assert.js.map +1 -0
  93. package/dist/utils/stats-manager.d.ts.map +1 -1
  94. package/dist/utils/stats-manager.js +61 -1
  95. package/dist/utils/stats-manager.js.map +1 -1
  96. package/package.json +6 -6
  97. package/src/adapter/canvas-context.ts +7 -590
  98. package/src/adapter/canvas-observer.ts +130 -0
  99. package/src/adapter/canvas-surface.ts +521 -0
  100. package/src/adapter/device.ts +174 -13
  101. package/src/adapter/presentation-context.ts +16 -0
  102. package/src/adapter/resources/buffer.ts +13 -5
  103. package/src/adapter/resources/command-encoder.ts +96 -7
  104. package/src/adapter/resources/fence.ts +3 -1
  105. package/src/adapter/resources/framebuffer.ts +9 -11
  106. package/src/adapter/resources/query-set.ts +17 -1
  107. package/src/adapter/resources/render-pipeline.ts +42 -13
  108. package/src/adapter/resources/resource.ts +284 -14
  109. package/src/adapter/resources/shader.ts +28 -28
  110. package/src/adapter/resources/shared-render-pipeline.ts +40 -0
  111. package/src/adapter/resources/texture.ts +269 -40
  112. package/src/adapter-utils/format-compiler-log.ts +23 -15
  113. package/src/index.ts +8 -0
  114. package/src/shadertypes/data-types/decode-shader-types.ts +13 -4
  115. package/src/shadertypes/textures/pixel-utils.ts +4 -4
  116. package/src/shadertypes/textures/texture-format-decoder.ts +73 -8
  117. package/src/shadertypes/textures/texture-format-table.ts +10 -9
  118. package/src/shadertypes/textures/texture-formats.ts +6 -1
  119. package/src/utils/array-equal.ts +21 -9
  120. package/src/utils/assert.ts +18 -0
  121. package/src/utils/stats-manager.ts +76 -2
@@ -3,8 +3,76 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import type {Device} from '../device';
6
+ import type {Stat, Stats} from '@probe.gl/stats';
6
7
  import {uid} from '../../utils/uid';
7
8
 
9
+ const CPU_HOTSPOT_PROFILER_MODULE = 'cpu-hotspot-profiler';
10
+ const RESOURCE_COUNTS_STATS = 'GPU Resource Counts';
11
+ const LEGACY_RESOURCE_COUNTS_STATS = 'Resource Counts';
12
+ const GPU_TIME_AND_MEMORY_STATS = 'GPU Time and Memory';
13
+ const BASE_RESOURCE_COUNT_ORDER = [
14
+ 'Resources',
15
+ 'Buffers',
16
+ 'Textures',
17
+ 'Samplers',
18
+ 'TextureViews',
19
+ 'Framebuffers',
20
+ 'QuerySets',
21
+ 'Shaders',
22
+ 'RenderPipelines',
23
+ 'ComputePipelines',
24
+ 'PipelineLayouts',
25
+ 'VertexArrays',
26
+ 'RenderPasss',
27
+ 'ComputePasss',
28
+ 'CommandEncoders',
29
+ 'CommandBuffers'
30
+ ] as const;
31
+ const WEBGL_RESOURCE_COUNT_ORDER = [
32
+ 'Resources',
33
+ 'Buffers',
34
+ 'Textures',
35
+ 'Samplers',
36
+ 'TextureViews',
37
+ 'Framebuffers',
38
+ 'QuerySets',
39
+ 'Shaders',
40
+ 'RenderPipelines',
41
+ 'SharedRenderPipelines',
42
+ 'ComputePipelines',
43
+ 'PipelineLayouts',
44
+ 'VertexArrays',
45
+ 'RenderPasss',
46
+ 'ComputePasss',
47
+ 'CommandEncoders',
48
+ 'CommandBuffers'
49
+ ] as const;
50
+ const BASE_RESOURCE_COUNT_STAT_ORDER = BASE_RESOURCE_COUNT_ORDER.flatMap(resourceType => [
51
+ `${resourceType} Created`,
52
+ `${resourceType} Active`
53
+ ]);
54
+ const WEBGL_RESOURCE_COUNT_STAT_ORDER = WEBGL_RESOURCE_COUNT_ORDER.flatMap(resourceType => [
55
+ `${resourceType} Created`,
56
+ `${resourceType} Active`
57
+ ]);
58
+ const ORDERED_STATS_CACHE = new WeakMap<
59
+ Stats,
60
+ {orderedStatNames: readonly string[]; statCount: number}
61
+ >();
62
+ const ORDERED_STAT_NAME_SET_CACHE = new WeakMap<readonly string[], Set<string>>();
63
+
64
+ type CpuHotspotProfiler = {
65
+ enabled?: boolean;
66
+ activeDefaultFramebufferAcquireDepth?: number;
67
+ statsBookkeepingTimeMs?: number;
68
+ statsBookkeepingCalls?: number;
69
+ transientCanvasResourceCreates?: number;
70
+ transientCanvasTextureCreates?: number;
71
+ transientCanvasTextureViewCreates?: number;
72
+ transientCanvasSamplerCreates?: number;
73
+ transientCanvasFramebufferCreates?: number;
74
+ };
75
+
8
76
  export type ResourceProps = {
9
77
  /** Name of resource, mainly for debugging purposes. A unique name will be assigned if not provided */
10
78
  id?: string;
@@ -48,6 +116,8 @@ export abstract class Resource<Props extends ResourceProps> {
48
116
  destroyed: boolean = false;
49
117
  /** For resources that allocate GPU memory */
50
118
  private allocatedBytes: number = 0;
119
+ /** Stats bucket currently holding the tracked allocation */
120
+ private allocatedBytesName: string | null = null;
51
121
  /** Attached resources will be destroyed when this resource is destroyed. Tracks auto-created "sub" resources. */
52
122
  private _attachedResources = new Set<Resource<ResourceProps>>();
53
123
 
@@ -74,6 +144,9 @@ export abstract class Resource<Props extends ResourceProps> {
74
144
  * destroy can be called on any resource to release it before it is garbage collected.
75
145
  */
76
146
  destroy(): void {
147
+ if (this.destroyed) {
148
+ return;
149
+ }
77
150
  this.destroyResource();
78
151
  }
79
152
 
@@ -119,7 +192,7 @@ export abstract class Resource<Props extends ResourceProps> {
119
192
 
120
193
  /** Destroy all owned resources. Make sure the resources are no longer needed before calling. */
121
194
  destroyAttachedResources(): void {
122
- for (const resource of Object.values(this._attachedResources)) {
195
+ for (const resource of this._attachedResources) {
123
196
  resource.destroy();
124
197
  }
125
198
  // don't remove while we are iterating
@@ -130,6 +203,9 @@ export abstract class Resource<Props extends ResourceProps> {
130
203
 
131
204
  /** Perform all destroy steps. Can be called by derived resources when overriding destroy() */
132
205
  protected destroyResource(): void {
206
+ if (this.destroyed) {
207
+ return;
208
+ }
133
209
  this.destroyAttachedResources();
134
210
  this.removeStats();
135
211
  this.destroyed = true;
@@ -137,34 +213,111 @@ export abstract class Resource<Props extends ResourceProps> {
137
213
 
138
214
  /** Called by .destroy() to track object destruction. Subclass must call if overriding destroy() */
139
215
  protected removeStats(): void {
140
- const stats = this._device.statsManager.getStats('Resource Counts');
141
- const name = this[Symbol.toStringTag];
142
- stats.get(`${name}s Active`).decrementCount();
216
+ const profiler = getCpuHotspotProfiler(this._device);
217
+ const startTime = profiler ? getTimestamp() : 0;
218
+ const statsObjects = [
219
+ this._device.statsManager.getStats(RESOURCE_COUNTS_STATS),
220
+ this._device.statsManager.getStats(LEGACY_RESOURCE_COUNTS_STATS)
221
+ ];
222
+ const orderedStatNames = getResourceCountStatOrder(this._device);
223
+ for (const stats of statsObjects) {
224
+ initializeStats(stats, orderedStatNames);
225
+ }
226
+ const name = this.getStatsName();
227
+ for (const stats of statsObjects) {
228
+ stats.get('Resources Active').decrementCount();
229
+ stats.get(`${name}s Active`).decrementCount();
230
+ }
231
+ if (profiler) {
232
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
233
+ profiler.statsBookkeepingTimeMs =
234
+ (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
235
+ }
143
236
  }
144
237
 
145
238
  /** Called by subclass to track memory allocations */
146
- protected trackAllocatedMemory(bytes: number, name = this[Symbol.toStringTag]): void {
147
- const stats = this._device.statsManager.getStats('Resource Counts');
239
+ protected trackAllocatedMemory(bytes: number, name = this.getStatsName()): void {
240
+ const profiler = getCpuHotspotProfiler(this._device);
241
+ const startTime = profiler ? getTimestamp() : 0;
242
+ const stats = this._device.statsManager.getStats(GPU_TIME_AND_MEMORY_STATS);
243
+
244
+ if (this.allocatedBytes > 0 && this.allocatedBytesName) {
245
+ stats.get('GPU Memory').subtractCount(this.allocatedBytes);
246
+ stats.get(`${this.allocatedBytesName} Memory`).subtractCount(this.allocatedBytes);
247
+ }
248
+
148
249
  stats.get('GPU Memory').addCount(bytes);
149
250
  stats.get(`${name} Memory`).addCount(bytes);
251
+ if (profiler) {
252
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
253
+ profiler.statsBookkeepingTimeMs =
254
+ (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
255
+ }
150
256
  this.allocatedBytes = bytes;
257
+ this.allocatedBytesName = name;
258
+ }
259
+
260
+ /** Called by subclass to track handle-backed memory allocations separately from owned allocations */
261
+ protected trackReferencedMemory(bytes: number, name = this.getStatsName()): void {
262
+ this.trackAllocatedMemory(bytes, `Referenced ${name}`);
151
263
  }
152
264
 
153
265
  /** Called by subclass to track memory deallocations */
154
- protected trackDeallocatedMemory(name = this[Symbol.toStringTag]): void {
155
- const stats = this._device.statsManager.getStats('Resource Counts');
266
+ protected trackDeallocatedMemory(name = this.getStatsName()): void {
267
+ if (this.allocatedBytes === 0) {
268
+ this.allocatedBytesName = null;
269
+ return;
270
+ }
271
+
272
+ const profiler = getCpuHotspotProfiler(this._device);
273
+ const startTime = profiler ? getTimestamp() : 0;
274
+ const stats = this._device.statsManager.getStats(GPU_TIME_AND_MEMORY_STATS);
156
275
  stats.get('GPU Memory').subtractCount(this.allocatedBytes);
157
- stats.get(`${name} Memory`).subtractCount(this.allocatedBytes);
276
+ stats.get(`${this.allocatedBytesName || name} Memory`).subtractCount(this.allocatedBytes);
277
+ if (profiler) {
278
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
279
+ profiler.statsBookkeepingTimeMs =
280
+ (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
281
+ }
158
282
  this.allocatedBytes = 0;
283
+ this.allocatedBytesName = null;
284
+ }
285
+
286
+ /** Called by subclass to deallocate handle-backed memory tracked via trackReferencedMemory() */
287
+ protected trackDeallocatedReferencedMemory(name = this.getStatsName()): void {
288
+ this.trackDeallocatedMemory(`Referenced ${name}`);
159
289
  }
160
290
 
161
291
  /** Called by resource constructor to track object creation */
162
292
  private addStats(): void {
163
- const stats = this._device.statsManager.getStats('Resource Counts');
164
- const name = this[Symbol.toStringTag];
165
- stats.get('Resources Created').incrementCount();
166
- stats.get(`${name}s Created`).incrementCount();
167
- stats.get(`${name}s Active`).incrementCount();
293
+ const name = this.getStatsName();
294
+ const profiler = getCpuHotspotProfiler(this._device);
295
+ const startTime = profiler ? getTimestamp() : 0;
296
+ const statsObjects = [
297
+ this._device.statsManager.getStats(RESOURCE_COUNTS_STATS),
298
+ this._device.statsManager.getStats(LEGACY_RESOURCE_COUNTS_STATS)
299
+ ];
300
+ const orderedStatNames = getResourceCountStatOrder(this._device);
301
+ for (const stats of statsObjects) {
302
+ initializeStats(stats, orderedStatNames);
303
+ }
304
+ for (const stats of statsObjects) {
305
+ stats.get('Resources Created').incrementCount();
306
+ stats.get('Resources Active').incrementCount();
307
+ stats.get(`${name}s Created`).incrementCount();
308
+ stats.get(`${name}s Active`).incrementCount();
309
+ }
310
+ if (profiler) {
311
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
312
+ profiler.statsBookkeepingTimeMs =
313
+ (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
314
+ }
315
+ recordTransientCanvasResourceCreate(this._device, name);
316
+ }
317
+
318
+ /** Canonical resource name used for stats buckets. */
319
+ protected getStatsName(): string {
320
+ return getCanonicalResourceName(this);
168
321
  }
169
322
  }
170
323
 
@@ -183,3 +336,120 @@ function selectivelyMerge<Props>(props: Props, defaultProps: Required<Props>): R
183
336
  }
184
337
  return mergedProps;
185
338
  }
339
+
340
+ function initializeStats(stats: Stats, orderedStatNames: readonly string[]): void {
341
+ const statsMap = stats.stats;
342
+ let addedOrderedStat = false;
343
+ for (const statName of orderedStatNames) {
344
+ if (!statsMap[statName]) {
345
+ stats.get(statName);
346
+ addedOrderedStat = true;
347
+ }
348
+ }
349
+
350
+ const statCount = Object.keys(statsMap).length;
351
+ const cachedStats = ORDERED_STATS_CACHE.get(stats);
352
+ if (
353
+ !addedOrderedStat &&
354
+ cachedStats?.orderedStatNames === orderedStatNames &&
355
+ cachedStats.statCount === statCount
356
+ ) {
357
+ return;
358
+ }
359
+
360
+ const reorderedStats: Record<string, Stat> = {};
361
+ let orderedStatNamesSet = ORDERED_STAT_NAME_SET_CACHE.get(orderedStatNames);
362
+ if (!orderedStatNamesSet) {
363
+ orderedStatNamesSet = new Set(orderedStatNames);
364
+ ORDERED_STAT_NAME_SET_CACHE.set(orderedStatNames, orderedStatNamesSet);
365
+ }
366
+
367
+ for (const statName of orderedStatNames) {
368
+ if (statsMap[statName]) {
369
+ reorderedStats[statName] = statsMap[statName];
370
+ }
371
+ }
372
+
373
+ for (const [statName, stat] of Object.entries(statsMap)) {
374
+ if (!orderedStatNamesSet.has(statName)) {
375
+ reorderedStats[statName] = stat;
376
+ }
377
+ }
378
+
379
+ for (const statName of Object.keys(statsMap)) {
380
+ delete statsMap[statName];
381
+ }
382
+
383
+ Object.assign(statsMap, reorderedStats);
384
+ ORDERED_STATS_CACHE.set(stats, {orderedStatNames, statCount});
385
+ }
386
+
387
+ function getResourceCountStatOrder(device: Device): readonly string[] {
388
+ return device.type === 'webgl' ? WEBGL_RESOURCE_COUNT_STAT_ORDER : BASE_RESOURCE_COUNT_STAT_ORDER;
389
+ }
390
+
391
+ function getCpuHotspotProfiler(device: Device): CpuHotspotProfiler | null {
392
+ const profiler = device.userData[CPU_HOTSPOT_PROFILER_MODULE] as CpuHotspotProfiler | undefined;
393
+ return profiler?.enabled ? profiler : null;
394
+ }
395
+
396
+ function getTimestamp(): number {
397
+ return globalThis.performance?.now?.() ?? Date.now();
398
+ }
399
+
400
+ function recordTransientCanvasResourceCreate(device: Device, name: string): void {
401
+ const profiler = getCpuHotspotProfiler(device);
402
+ if (!profiler || !profiler.activeDefaultFramebufferAcquireDepth) {
403
+ return;
404
+ }
405
+
406
+ profiler.transientCanvasResourceCreates = (profiler.transientCanvasResourceCreates || 0) + 1;
407
+
408
+ switch (name) {
409
+ case 'Texture':
410
+ profiler.transientCanvasTextureCreates = (profiler.transientCanvasTextureCreates || 0) + 1;
411
+ break;
412
+ case 'TextureView':
413
+ profiler.transientCanvasTextureViewCreates =
414
+ (profiler.transientCanvasTextureViewCreates || 0) + 1;
415
+ break;
416
+ case 'Sampler':
417
+ profiler.transientCanvasSamplerCreates = (profiler.transientCanvasSamplerCreates || 0) + 1;
418
+ break;
419
+ case 'Framebuffer':
420
+ profiler.transientCanvasFramebufferCreates =
421
+ (profiler.transientCanvasFramebufferCreates || 0) + 1;
422
+ break;
423
+ default:
424
+ break;
425
+ }
426
+ }
427
+
428
+ function getCanonicalResourceName(resource: Resource<any>): string {
429
+ let prototype = Object.getPrototypeOf(resource);
430
+
431
+ while (prototype) {
432
+ const parentPrototype = Object.getPrototypeOf(prototype);
433
+ if (!parentPrototype || parentPrototype === Resource.prototype) {
434
+ return (
435
+ getPrototypeToStringTag(prototype) ||
436
+ resource[Symbol.toStringTag] ||
437
+ resource.constructor.name
438
+ );
439
+ }
440
+ prototype = parentPrototype;
441
+ }
442
+
443
+ return resource[Symbol.toStringTag] || resource.constructor.name;
444
+ }
445
+
446
+ function getPrototypeToStringTag(prototype: object): string | null {
447
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, Symbol.toStringTag);
448
+ if (typeof descriptor?.get === 'function') {
449
+ return descriptor.get.call(prototype);
450
+ }
451
+ if (typeof descriptor?.value === 'string') {
452
+ return descriptor.value;
453
+ }
454
+ return null;
455
+ }
@@ -106,38 +106,38 @@ export abstract class Shader extends Resource<ShaderProps> {
106
106
 
107
107
  const shaderName: string = shaderId; // getShaderName(this.source) || ;
108
108
  const shaderTitle: string = `${this.stage} shader "${shaderName}"`;
109
- let htmlLog = formatCompilerLog(messages, this.source, {showSourceCode: 'all', html: true});
109
+ const htmlLog = formatCompilerLog(messages, this.source, {showSourceCode: 'all', html: true});
110
110
  // Show translated source if available
111
111
  const translatedSource = this.getTranslatedSource();
112
+
113
+ const container = document.createElement('div');
114
+ container.innerHTML = `\
115
+ <h1>Compilation error in ${shaderTitle}</h1>
116
+ <div style="display:flex;position:fixed;top:10px;right:20px;gap:2px;">
117
+ <button id="copy">Copy source</button><br/>
118
+ <button id="close">Close</button>
119
+ </div>
120
+ <code><pre>${htmlLog}</pre></code>`;
112
121
  if (translatedSource) {
113
- htmlLog += `<br /><br /><h1>Translated Source</h1><br /><br /><code style="user-select:text;"><pre>${translatedSource}</pre></code>`;
122
+ container.innerHTML += `<br /><h1>Translated Source</h1><br /><br /><code><pre>${translatedSource}</pre></code>`;
114
123
  }
115
- // Make it clickable so we can copy to clipboard
116
- const button = document.createElement('Button');
117
- button.innerHTML = `
118
- <h1>Compilation error in ${shaderTitle}</h1><br /><br />
119
- <code style="user-select:text;"><pre>
120
- ${htmlLog}
121
- </pre></code>`;
122
- button.style.top = '10px';
123
- button.style.left = '10px';
124
- button.style.position = 'absolute';
125
- button.style.zIndex = '9999';
126
- button.style.width = '100%';
127
- button.style.textAlign = 'left';
128
- document.body.appendChild(button);
129
-
130
- const errors = document.getElementsByClassName('luma-compiler-log-error');
131
- errors[0]?.scrollIntoView();
132
-
133
- // TODO - add a small embedded copy button (instead of main button)
134
- button.onclick = () => {
135
- // const source = this.source.replaceAll('\n', '<br />');
136
- const dataURI = `data:text/plain,${encodeURIComponent(this.source)}`;
137
- navigator.clipboard.writeText(dataURI);
124
+ container.style.top = '0';
125
+ container.style.left = '0';
126
+ container.style.background = 'white';
127
+ container.style.position = 'fixed';
128
+ container.style.zIndex = '9999';
129
+ container.style.maxWidth = '100vw';
130
+ container.style.maxHeight = '100vh';
131
+ container.style.overflowY = 'auto';
132
+ document.body.appendChild(container);
133
+ const error = container.querySelector('.luma-compiler-log-error');
134
+ error?.scrollIntoView();
135
+ (container.querySelector('button#close') as HTMLButtonElement).onclick = () => {
136
+ container.remove();
137
+ };
138
+ (container.querySelector('button#copy') as HTMLButtonElement).onclick = () => {
139
+ navigator.clipboard.writeText(this.source);
138
140
  };
139
-
140
- // TODO - add a small embedded close button
141
141
  }
142
142
 
143
143
  static override defaultProps: Required<ShaderProps> = {
@@ -162,5 +162,5 @@ function getShaderIdFromProps(props: ShaderProps): string {
162
162
  function getShaderName(shader: string, defaultName: string = 'unnamed'): string {
163
163
  const SHADER_NAME_REGEXP = /#define[\s*]SHADER_NAME[\s*]([A-Za-z0-9_-]+)[\s*]/;
164
164
  const match = SHADER_NAME_REGEXP.exec(shader);
165
- return match ? match[1] : defaultName;
165
+ return match?.[1] ?? defaultName;
166
166
  }
@@ -0,0 +1,40 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {Device} from '../device';
6
+ import type {Shader} from './shader';
7
+ import {Resource, type ResourceProps} from './resource';
8
+
9
+ export type SharedRenderPipelineProps = ResourceProps & {
10
+ handle?: unknown;
11
+ vs: Shader;
12
+ fs: Shader;
13
+ varyings?: string[];
14
+ bufferMode?: number;
15
+ };
16
+
17
+ /**
18
+ * Internal base class for backend-specific shared render-pipeline implementations.
19
+ * Backends may use this to share expensive linked/program state across multiple
20
+ * `RenderPipeline` wrappers.
21
+ */
22
+ export abstract class SharedRenderPipeline extends Resource<SharedRenderPipelineProps> {
23
+ override get [Symbol.toStringTag](): string {
24
+ return 'SharedRenderPipeline';
25
+ }
26
+
27
+ abstract override readonly device: Device;
28
+ abstract override readonly handle: unknown;
29
+
30
+ constructor(device: Device, props: SharedRenderPipelineProps) {
31
+ super(device, props, {
32
+ ...Resource.defaultProps,
33
+ handle: undefined!,
34
+ vs: undefined!,
35
+ fs: undefined!,
36
+ varyings: undefined!,
37
+ bufferMode: undefined!
38
+ });
39
+ }
40
+ }