@vectojs/core 1.21.0 → 1.22.0

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.
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
 
11
11
 
12
- var _chunkGKSCJ6AFjs = require('./chunk-GKSCJ6AF.js');
12
+ var _chunkLHONR3GOjs = require('./chunk-LHONR3GO.js');
13
13
 
14
14
 
15
15
 
@@ -2463,6 +2463,27 @@ var Scene = (_class7 = class _Scene {
2463
2463
  get webgpuDisabled() {
2464
2464
  return this._webgpuDisabled || this.particleBackend === "cpu";
2465
2465
  }
2466
+ /**
2467
+ * Draw accounting for the WebGL point layer, or null when that layer is not in
2468
+ * use.
2469
+ *
2470
+ * Null and all-zero mean different things: null is "this backend is not
2471
+ * running", zero is "it ran and drew nothing". A readout that conflates them
2472
+ * sends someone looking for a performance problem in a backend that was never
2473
+ * active.
2474
+ */
2475
+ get webglDrawStats() {
2476
+ return _nullishCoalesce(_optionalChain([this, 'access', _27 => _27.pointRenderer, 'optionalAccess', _28 => _28.stats, 'optionalCall', _29 => _29()]), () => ( null));
2477
+ }
2478
+ /**
2479
+ * Whether a WebGPU device is currently live for particle compute.
2480
+ *
2481
+ * The WebGPU path only activates when a `ComputeParticleEntity` is present, so
2482
+ * most scenes never touch it.
2483
+ */
2484
+ get webgpuActive() {
2485
+ return this.device !== null && !this.deviceLost;
2486
+ }
2466
2487
  set webgpuDisabled(value) {
2467
2488
  this._webgpuDisabled = value;
2468
2489
  }
@@ -2494,8 +2515,8 @@ var Scene = (_class7 = class _Scene {
2494
2515
  static _devModeDetected() {
2495
2516
  if (_Scene.devMode) return true;
2496
2517
  const gp = typeof globalThis !== "undefined" ? globalThis : void 0;
2497
- if (_optionalChain([gp, 'optionalAccess', _27 => _27.__DEV__])) return true;
2498
- if (_optionalChain([gp, 'optionalAccess', _28 => _28.process, 'optionalAccess', _29 => _29.env, 'optionalAccess', _30 => _30.NODE_ENV]) === "development") return true;
2518
+ if (_optionalChain([gp, 'optionalAccess', _30 => _30.__DEV__])) return true;
2519
+ if (_optionalChain([gp, 'optionalAccess', _31 => _31.process, 'optionalAccess', _32 => _32.env, 'optionalAccess', _33 => _33.NODE_ENV]) === "development") return true;
2499
2520
  return false;
2500
2521
  }
2501
2522
 
@@ -2526,9 +2547,9 @@ var Scene = (_class7 = class _Scene {
2526
2547
  let checked = 0;
2527
2548
  const walkProjections = (node) => {
2528
2549
  if (checked > 10) return;
2529
- const proj = _optionalChain([node, 'access', _31 => _31.getContentProjection, 'optionalCall', _32 => _32()]);
2530
- if (_optionalChain([proj, 'optionalAccess', _33 => _33.text]) && proj.selectable !== false) {
2531
- const el = _optionalChain([this, 'access', _34 => _34.contentElements, 'optionalAccess', _35 => _35.get, 'call', _36 => _36(node.id)]);
2550
+ const proj = _optionalChain([node, 'access', _34 => _34.getContentProjection, 'optionalCall', _35 => _35()]);
2551
+ if (_optionalChain([proj, 'optionalAccess', _36 => _36.text]) && proj.selectable !== false) {
2552
+ const el = _optionalChain([this, 'access', _37 => _37.contentElements, 'optionalAccess', _38 => _38.get, 'call', _39 => _39(node.id)]);
2532
2553
  if (el) {
2533
2554
  const projectedText = el.textContent || "";
2534
2555
  if (projectedText !== "" && projectedText !== proj.text) {
@@ -2549,8 +2570,8 @@ var Scene = (_class7 = class _Scene {
2549
2570
  this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
2550
2571
  this.maxDPR = options.maxDPR;
2551
2572
  if (this.disableWindowResize) {
2552
- const styleWidth = parseInlinePx(_optionalChain([canvas, 'access', _37 => _37.style, 'optionalAccess', _38 => _38.width]));
2553
- const styleHeight = parseInlinePx(_optionalChain([canvas, 'access', _39 => _39.style, 'optionalAccess', _40 => _40.height]));
2573
+ const styleWidth = parseInlinePx(_optionalChain([canvas, 'access', _40 => _40.style, 'optionalAccess', _41 => _41.width]));
2574
+ const styleHeight = parseInlinePx(_optionalChain([canvas, 'access', _42 => _42.style, 'optionalAccess', _43 => _43.height]));
2554
2575
  this.width = _nullishCoalesce(styleWidth, () => ( (canvas.width || canvas.clientWidth || 0)));
2555
2576
  this.height = _nullishCoalesce(styleHeight, () => ( (canvas.height || canvas.clientHeight || 0)));
2556
2577
  } else {
@@ -2558,7 +2579,7 @@ var Scene = (_class7 = class _Scene {
2558
2579
  this.height = typeof window !== "undefined" ? window.innerHeight : canvas.clientHeight || canvas.height || 600;
2559
2580
  }
2560
2581
  const globalProcess = typeof globalThis !== "undefined" ? globalThis.process : void 0;
2561
- const isTest = globalProcess && (_optionalChain([globalProcess, 'access', _41 => _41.env, 'optionalAccess', _42 => _42.NODE_ENV]) === "test" || _optionalChain([globalProcess, 'access', _43 => _43.env, 'optionalAccess', _44 => _44.VITEST]) === "true");
2582
+ const isTest = globalProcess && (_optionalChain([globalProcess, 'access', _44 => _44.env, 'optionalAccess', _45 => _45.NODE_ENV]) === "test" || _optionalChain([globalProcess, 'access', _46 => _46.env, 'optionalAccess', _47 => _47.VITEST]) === "true");
2562
2583
  this.maxFPS = _nullishCoalesce(options.maxFPS, () => ( (isTest ? 0 : 60)));
2563
2584
  this.respectReducedMotion = _nullishCoalesce(options.respectReducedMotion, () => ( true));
2564
2585
  this.autoThrottle = _nullishCoalesce(options.autoThrottle, () => ( true));
@@ -2572,7 +2593,7 @@ var Scene = (_class7 = class _Scene {
2572
2593
  if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
2573
2594
  this.forcedColorsQuery = window.matchMedia("(forced-colors: active)");
2574
2595
  this.forcedColorsChangeHandler = () => this.markDirty();
2575
- _optionalChain([this, 'access', _45 => _45.forcedColorsQuery, 'access', _46 => _46.addEventListener, 'optionalCall', _47 => _47("change", this.forcedColorsChangeHandler)]);
2596
+ _optionalChain([this, 'access', _48 => _48.forcedColorsQuery, 'access', _49 => _49.addEventListener, 'optionalCall', _50 => _50("change", this.forcedColorsChangeHandler)]);
2576
2597
  }
2577
2598
  this.root = new class RootEntity extends _chunkAGP4VLF4js.Entity {
2578
2599
  isPointInside() {
@@ -2594,15 +2615,15 @@ var Scene = (_class7 = class _Scene {
2594
2615
  if (options.renderer) {
2595
2616
  this.renderer = options.renderer;
2596
2617
  } else {
2597
- this.renderer = new (0, _chunkGKSCJ6AFjs.CanvasRenderer)(
2618
+ this.renderer = new (0, _chunkLHONR3GOjs.CanvasRenderer)(
2598
2619
  canvas,
2599
2620
  this.disableWindowResize ? { width: this.width, height: this.height } : void 0,
2600
2621
  this.maxDPR
2601
2622
  );
2602
2623
  }
2603
- _optionalChain([this, 'access', _48 => _48.renderer, 'access', _49 => _49.onContextRestored, 'optionalCall', _50 => _50(() => {
2624
+ _optionalChain([this, 'access', _51 => _51.renderer, 'access', _52 => _52.onContextRestored, 'optionalCall', _53 => _53(() => {
2604
2625
  this.markDirty();
2605
- if (_optionalChain([this, 'access', _51 => _51.renderer, 'access', _52 => _52.isContextLost, 'optionalCall', _53 => _53()]) !== true) this.render(this.renderer);
2626
+ if (_optionalChain([this, 'access', _54 => _54.renderer, 'access', _55 => _55.isContextLost, 'optionalCall', _56 => _56()]) !== true) this.render(this.renderer);
2606
2627
  })]);
2607
2628
  if (typeof document !== "undefined") {
2608
2629
  this.a11yRoot = document.createElement("div");
@@ -2771,7 +2792,7 @@ var Scene = (_class7 = class _Scene {
2771
2792
  watchDevicePixelRatio() {
2772
2793
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
2773
2794
  if (this.dprMediaQuery && this.dprChangeHandler) {
2774
- _optionalChain([this, 'access', _54 => _54.dprMediaQuery, 'access', _55 => _55.removeEventListener, 'optionalCall', _56 => _56("change", this.dprChangeHandler)]);
2795
+ _optionalChain([this, 'access', _57 => _57.dprMediaQuery, 'access', _58 => _58.removeEventListener, 'optionalCall', _59 => _59("change", this.dprChangeHandler)]);
2775
2796
  }
2776
2797
  const dpr = window.devicePixelRatio || 1;
2777
2798
  const query = window.matchMedia(`(resolution: ${dpr}dppx)`);
@@ -2779,7 +2800,7 @@ var Scene = (_class7 = class _Scene {
2779
2800
  this.resize(this.width, this.height);
2780
2801
  this.watchDevicePixelRatio();
2781
2802
  };
2782
- _optionalChain([query, 'access', _57 => _57.addEventListener, 'optionalCall', _58 => _58("change", handler)]);
2803
+ _optionalChain([query, 'access', _60 => _60.addEventListener, 'optionalCall', _61 => _61("change", handler)]);
2783
2804
  this.dprMediaQuery = query;
2784
2805
  this.dprChangeHandler = handler;
2785
2806
  }
@@ -2799,7 +2820,7 @@ var Scene = (_class7 = class _Scene {
2799
2820
  if (typeof gl.addEventListener !== "function") return;
2800
2821
  this.glContextLostHandler = (e) => {
2801
2822
  e.preventDefault();
2802
- _optionalChain([this, 'access', _59 => _59.pointRenderer, 'optionalAccess', _60 => _60.destroy, 'call', _61 => _61()]);
2823
+ _optionalChain([this, 'access', _62 => _62.pointRenderer, 'optionalAccess', _63 => _63.destroy, 'call', _64 => _64()]);
2803
2824
  this.pointRenderer = null;
2804
2825
  };
2805
2826
  this.glContextRestoredHandler = () => {
@@ -2829,26 +2850,26 @@ var Scene = (_class7 = class _Scene {
2829
2850
  * to the live DOM selection.
2830
2851
  */
2831
2852
  contentGridSelectionLine(el) {
2832
- const candidates = [_optionalChain([this, 'access', _62 => _62.contentSelectionAnchor, 'optionalAccess', _63 => _63.node])];
2853
+ const candidates = [_optionalChain([this, 'access', _65 => _65.contentSelectionAnchor, 'optionalAccess', _66 => _66.node])];
2833
2854
  if (typeof window !== "undefined" && typeof window.getSelection === "function") {
2834
2855
  const selection = window.getSelection();
2835
- candidates.push(_optionalChain([selection, 'optionalAccess', _64 => _64.anchorNode]), _optionalChain([selection, 'optionalAccess', _65 => _65.focusNode]));
2856
+ candidates.push(_optionalChain([selection, 'optionalAccess', _67 => _67.anchorNode]), _optionalChain([selection, 'optionalAccess', _68 => _68.focusNode]));
2836
2857
  }
2837
2858
  for (const candidate of candidates) {
2838
2859
  if (!candidate || !el.contains(candidate)) continue;
2839
2860
  let cursor = candidate;
2840
2861
  while (cursor && cursor.parentNode !== el) cursor = cursor.parentNode;
2841
- const lineIndex = _optionalChain([cursor, 'optionalAccess', _66 => _66.dataset, 'optionalAccess', _67 => _67.vectoGridLine]);
2862
+ const lineIndex = _optionalChain([cursor, 'optionalAccess', _69 => _69.dataset, 'optionalAccess', _70 => _70.vectoGridLine]);
2842
2863
  if (lineIndex !== void 0) return Number(lineIndex);
2843
2864
  }
2844
2865
  return null;
2845
2866
  }
2846
2867
  releaseContentSelectionForRebuild(el) {
2847
2868
  const selection = typeof window !== "undefined" && typeof window.getSelection === "function" ? window.getSelection() : null;
2848
- const ownsSelection = this.contentSelectionAnchor && el.contains(this.contentSelectionAnchor.node) || (_optionalChain([selection, 'optionalAccess', _68 => _68.anchorNode]) ? el.contains(selection.anchorNode) : false) || (_optionalChain([selection, 'optionalAccess', _69 => _69.focusNode]) ? el.contains(selection.focusNode) : false);
2869
+ const ownsSelection = this.contentSelectionAnchor && el.contains(this.contentSelectionAnchor.node) || (_optionalChain([selection, 'optionalAccess', _71 => _71.anchorNode]) ? el.contains(selection.anchorNode) : false) || (_optionalChain([selection, 'optionalAccess', _72 => _72.focusNode]) ? el.contains(selection.focusNode) : false);
2849
2870
  if (!ownsSelection) return;
2850
2871
  this.endContentSelectionDrag();
2851
- _optionalChain([selection, 'optionalAccess', _70 => _70.removeAllRanges, 'call', _71 => _71()]);
2872
+ _optionalChain([selection, 'optionalAccess', _73 => _73.removeAllRanges, 'call', _74 => _74()]);
2852
2873
  }
2853
2874
  /**
2854
2875
  * Rebuild a content-projection element's DOM (`rebuild`) while preserving a
@@ -2907,7 +2928,7 @@ var Scene = (_class7 = class _Scene {
2907
2928
  }
2908
2929
  /** Convert browser viewport coordinates into this Scene's logical coordinates. */
2909
2930
  clientToScene(clientX, clientY) {
2910
- const rect = _optionalChain([this, 'access', _72 => _72.canvas, 'access', _73 => _73.getBoundingClientRect, 'optionalCall', _74 => _74()]);
2931
+ const rect = _optionalChain([this, 'access', _75 => _75.canvas, 'access', _76 => _76.getBoundingClientRect, 'optionalCall', _77 => _77()]);
2911
2932
  if (!rect) return { x: clientX, y: clientY };
2912
2933
  const cssWidth = rect.width || this.canvas.clientWidth || this.width;
2913
2934
  const cssHeight = rect.height || this.canvas.clientHeight || this.height;
@@ -2945,7 +2966,7 @@ var Scene = (_class7 = class _Scene {
2945
2966
  cancelAnimationFrame(calibrationFrame);
2946
2967
  }
2947
2968
  this.contentGridCalibrationFrames.delete(entityId);
2948
- _optionalChain([this, 'access', _75 => _75.contentGridCalibrationProbes, 'access', _76 => _76.get, 'call', _77 => _77(entityId), 'optionalAccess', _78 => _78.remove, 'call', _79 => _79()]);
2969
+ _optionalChain([this, 'access', _78 => _78.contentGridCalibrationProbes, 'access', _79 => _79.get, 'call', _80 => _80(entityId), 'optionalAccess', _81 => _81.remove, 'call', _82 => _82()]);
2949
2970
  this.contentGridCalibrationProbes.delete(entityId);
2950
2971
  delete el.dataset.vectoGridCalibrationPending;
2951
2972
  delete el.dataset.vectoGridCalibration;
@@ -3099,12 +3120,12 @@ var Scene = (_class7 = class _Scene {
3099
3120
  this.canvasResizeObserver = null;
3100
3121
  }
3101
3122
  if (this.dprMediaQuery && this.dprChangeHandler) {
3102
- _optionalChain([this, 'access', _80 => _80.dprMediaQuery, 'access', _81 => _81.removeEventListener, 'optionalCall', _82 => _82("change", this.dprChangeHandler)]);
3123
+ _optionalChain([this, 'access', _83 => _83.dprMediaQuery, 'access', _84 => _84.removeEventListener, 'optionalCall', _85 => _85("change", this.dprChangeHandler)]);
3103
3124
  this.dprMediaQuery = null;
3104
3125
  this.dprChangeHandler = null;
3105
3126
  }
3106
3127
  if (this.forcedColorsQuery && this.forcedColorsChangeHandler) {
3107
- _optionalChain([this, 'access', _83 => _83.forcedColorsQuery, 'access', _84 => _84.removeEventListener, 'optionalCall', _85 => _85("change", this.forcedColorsChangeHandler)]);
3128
+ _optionalChain([this, 'access', _86 => _86.forcedColorsQuery, 'access', _87 => _87.removeEventListener, 'optionalCall', _88 => _88("change", this.forcedColorsChangeHandler)]);
3108
3129
  this.forcedColorsQuery = null;
3109
3130
  this.forcedColorsChangeHandler = null;
3110
3131
  }
@@ -3122,9 +3143,9 @@ var Scene = (_class7 = class _Scene {
3122
3143
  }
3123
3144
  this.pointerEventTarget = null;
3124
3145
  }
3125
- _optionalChain([this, 'access', _86 => _86.a11yRoot, 'optionalAccess', _87 => _87.remove, 'call', _88 => _88()]);
3146
+ _optionalChain([this, 'access', _89 => _89.a11yRoot, 'optionalAccess', _90 => _90.remove, 'call', _91 => _91()]);
3126
3147
  this.focusSentinel = null;
3127
- _optionalChain([this, 'access', _89 => _89.portalRoot, 'optionalAccess', _90 => _90.remove, 'call', _91 => _91()]);
3148
+ _optionalChain([this, 'access', _92 => _92.portalRoot, 'optionalAccess', _93 => _93.remove, 'call', _94 => _94()]);
3128
3149
  this.a11yElements.clear();
3129
3150
  for (const el of this.contentElements.values()) el.remove();
3130
3151
  this.contentElements.clear();
@@ -3147,10 +3168,10 @@ var Scene = (_class7 = class _Scene {
3147
3168
  }
3148
3169
  this.glContextLostHandler = null;
3149
3170
  this.glContextRestoredHandler = null;
3150
- _optionalChain([this, 'access', _92 => _92.pointRenderer, 'optionalAccess', _93 => _93.destroy, 'call', _94 => _94()]);
3151
- _optionalChain([this, 'access', _95 => _95.renderer, 'access', _96 => _96.dispose, 'optionalCall', _97 => _97()]);
3152
- _optionalChain([this, 'access', _98 => _98.glCanvas, 'optionalAccess', _99 => _99.remove, 'call', _100 => _100()]);
3153
- _optionalChain([this, 'access', _101 => _101.gpuCanvas, 'optionalAccess', _102 => _102.remove, 'call', _103 => _103()]);
3171
+ _optionalChain([this, 'access', _95 => _95.pointRenderer, 'optionalAccess', _96 => _96.destroy, 'call', _97 => _97()]);
3172
+ _optionalChain([this, 'access', _98 => _98.renderer, 'access', _99 => _99.dispose, 'optionalCall', _100 => _100()]);
3173
+ _optionalChain([this, 'access', _101 => _101.glCanvas, 'optionalAccess', _102 => _102.remove, 'call', _103 => _103()]);
3174
+ _optionalChain([this, 'access', _104 => _104.gpuCanvas, 'optionalAccess', _105 => _105.remove, 'call', _106 => _106()]);
3154
3175
  this.gpuCanvas = null;
3155
3176
  this.gpuContext = null;
3156
3177
  if (this.recoveryTimerId) {
@@ -3162,7 +3183,7 @@ var Scene = (_class7 = class _Scene {
3162
3183
  this.manager = null;
3163
3184
  }
3164
3185
  if (this.device) {
3165
- _optionalChain([this, 'access', _104 => _104.device, 'access', _105 => _105.destroy, 'optionalCall', _106 => _106()]);
3186
+ _optionalChain([this, 'access', _107 => _107.device, 'access', _108 => _108.destroy, 'optionalCall', _109 => _109()]);
3166
3187
  this.device = null;
3167
3188
  }
3168
3189
  }
@@ -3594,7 +3615,7 @@ var Scene = (_class7 = class _Scene {
3594
3615
  el.addEventListener("compositionupdate", (e) => {
3595
3616
  const data = _nullishCoalesce(e.data, () => ( ""));
3596
3617
  composition = {
3597
- start: _nullishCoalesce(_optionalChain([composition, 'optionalAccess', _107 => _107.start]), () => ( 0)),
3618
+ start: _nullishCoalesce(_optionalChain([composition, 'optionalAccess', _110 => _110.start]), () => ( 0)),
3598
3619
  length: data.length
3599
3620
  };
3600
3621
  forward();
@@ -3667,7 +3688,7 @@ var Scene = (_class7 = class _Scene {
3667
3688
  this.syncOptionalAttribute(
3668
3689
  el,
3669
3690
  "href",
3670
- attrs.href === void 0 ? void 0 : _chunkGKSCJ6AFjs.sanitizeUrl.call(void 0, attrs.href)
3691
+ attrs.href === void 0 ? void 0 : _chunkLHONR3GOjs.sanitizeUrl.call(void 0, attrs.href)
3671
3692
  );
3672
3693
  this.syncOptionalAttribute(el, "target", attrs.target);
3673
3694
  }
@@ -4047,9 +4068,9 @@ var Scene = (_class7 = class _Scene {
4047
4068
  for (let lineIndex = 0; lineIndex < grid.lines.length; lineIndex++) {
4048
4069
  const gridLine = grid.lines[lineIndex];
4049
4070
  const projectedLine = projectionLines[lineIndex];
4050
- const lineHeight = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _108 => _108.lineHeight]), () => ( grid.lineHeight));
4051
- const baseline = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _109 => _109.baseline]), () => ( grid.baseline));
4052
- const lineFont = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _110 => _110.font]), () => ( grid.font));
4071
+ const lineHeight = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _111 => _111.lineHeight]), () => ( grid.lineHeight));
4072
+ const baseline = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _112 => _112.baseline]), () => ( grid.baseline));
4073
+ const lineFont = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _113 => _113.font]), () => ( grid.font));
4053
4074
  const lineSignature = contentGridLineSignature(
4054
4075
  grid,
4055
4076
  gridLine,
@@ -4069,8 +4090,8 @@ var Scene = (_class7 = class _Scene {
4069
4090
  lineElement.dir = "ltr";
4070
4091
  lineElement.dataset.vectoGridLine = `${lineIndex}`;
4071
4092
  lineElement.style.position = "absolute";
4072
- lineElement.style.left = `${_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _111 => _111.x]), () => ( 0))}px`;
4073
- lineElement.style.top = `${(_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _112 => _112.y]), () => ( lineIndex * grid.lineHeight))) + baseline - _text.cssLineBoxBaseline.call(void 0, lineFont, lineHeight)}px`;
4093
+ lineElement.style.left = `${_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _114 => _114.x]), () => ( 0))}px`;
4094
+ lineElement.style.top = `${(_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _115 => _115.y]), () => ( lineIndex * grid.lineHeight))) + baseline - _text.cssLineBoxBaseline.call(void 0, lineFont, lineHeight)}px`;
4074
4095
  lineElement.style.width = `${gridLine.width}px`;
4075
4096
  lineElement.style.height = `${lineHeight}px`;
4076
4097
  lineElement.style.whiteSpace = "pre";
@@ -4140,7 +4161,7 @@ var Scene = (_class7 = class _Scene {
4140
4161
  if (selectionLine !== null && selectionLine >= grid.lines.length) {
4141
4162
  rebuiltSelectionLine = true;
4142
4163
  }
4143
- _optionalChain([el, 'access', _113 => _113.lastElementChild, 'optionalAccess', _114 => _114.remove, 'call', _115 => _115()]);
4164
+ _optionalChain([el, 'access', _116 => _116.lastElementChild, 'optionalAccess', _117 => _117.remove, 'call', _118 => _118()]);
4144
4165
  }
4145
4166
  if (rebuiltSelectionLine) this.releaseContentSelectionForRebuild(el);
4146
4167
  el.dataset.vectoProjectionLines = signature;
@@ -4204,7 +4225,7 @@ var Scene = (_class7 = class _Scene {
4204
4225
  if (previous !== void 0 && typeof cancelAnimationFrame === "function") {
4205
4226
  cancelAnimationFrame(previous);
4206
4227
  }
4207
- _optionalChain([this, 'access', _116 => _116.contentGridCalibrationProbes, 'access', _117 => _117.get, 'call', _118 => _118(entityId), 'optionalAccess', _119 => _119.remove, 'call', _120 => _120()]);
4228
+ _optionalChain([this, 'access', _119 => _119.contentGridCalibrationProbes, 'access', _120 => _120.get, 'call', _121 => _121(entityId), 'optionalAccess', _122 => _122.remove, 'call', _123 => _123()]);
4208
4229
  this.contentGridCalibrationProbes.delete(entityId);
4209
4230
  const calibrationStart = typeof performance !== "undefined" ? performance.now() : 0;
4210
4231
  const probe = document.createElement("div");
@@ -4238,7 +4259,7 @@ var Scene = (_class7 = class _Scene {
4238
4259
  target.dataset.vectoGridCalib = generation;
4239
4260
  continue;
4240
4261
  }
4241
- const sourceText = _nullishCoalesce(_optionalChain([target, 'access', _121 => _121.textContent, 'optionalAccess', _122 => _122.slice, 'call', _123 => _123(0, sourceLength)]), () => ( ""));
4262
+ const sourceText = _nullishCoalesce(_optionalChain([target, 'access', _124 => _124.textContent, 'optionalAccess', _125 => _125.slice, 'call', _126 => _126(0, sourceLength)]), () => ( ""));
4242
4263
  if (!sourceText) {
4243
4264
  target.dataset.vectoGridCalib = generation;
4244
4265
  continue;
@@ -4449,12 +4470,12 @@ var Scene = (_class7 = class _Scene {
4449
4470
  syncOverlayGeometry() {
4450
4471
  const parent = this.canvas.parentElement;
4451
4472
  if (!parent) return;
4452
- const canvasRect = _optionalChain([this, 'access', _124 => _124.canvas, 'access', _125 => _125.getBoundingClientRect, 'optionalCall', _126 => _126()]);
4453
- const parentRect = _optionalChain([parent, 'access', _127 => _127.getBoundingClientRect, 'optionalCall', _128 => _128()]);
4454
- const cssWidth = _optionalChain([canvasRect, 'optionalAccess', _129 => _129.width]) || this.canvas.clientWidth || this.width;
4455
- const cssHeight = _optionalChain([canvasRect, 'optionalAccess', _130 => _130.height]) || this.canvas.clientHeight || this.height;
4456
- const left = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _131 => _131.left]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _132 => _132.left]), () => ( 0))) - (parent.clientLeft || 0) + parent.scrollLeft;
4457
- const top = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _133 => _133.top]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _134 => _134.top]), () => ( 0))) - (parent.clientTop || 0) + parent.scrollTop;
4473
+ const canvasRect = _optionalChain([this, 'access', _127 => _127.canvas, 'access', _128 => _128.getBoundingClientRect, 'optionalCall', _129 => _129()]);
4474
+ const parentRect = _optionalChain([parent, 'access', _130 => _130.getBoundingClientRect, 'optionalCall', _131 => _131()]);
4475
+ const cssWidth = _optionalChain([canvasRect, 'optionalAccess', _132 => _132.width]) || this.canvas.clientWidth || this.width;
4476
+ const cssHeight = _optionalChain([canvasRect, 'optionalAccess', _133 => _133.height]) || this.canvas.clientHeight || this.height;
4477
+ const left = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _134 => _134.left]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _135 => _135.left]), () => ( 0))) - (parent.clientLeft || 0) + parent.scrollLeft;
4478
+ const top = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _136 => _136.top]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _137 => _137.top]), () => ( 0))) - (parent.clientTop || 0) + parent.scrollTop;
4458
4479
  const scaleX = this.width > 0 ? cssWidth / this.width : 1;
4459
4480
  const scaleY = this.height > 0 ? cssHeight / this.height : 1;
4460
4481
  const prev = this._overlayGeometry;
@@ -4593,7 +4614,7 @@ var Scene = (_class7 = class _Scene {
4593
4614
  * (and {@link respectReducedMotion} is on). `0` means uncapped.
4594
4615
  */
4595
4616
  effectiveMaxFPS() {
4596
- const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access', _135 => _135.reducedMotionQuery, 'optionalAccess', _136 => _136.matches]);
4617
+ const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access', _138 => _138.reducedMotionQuery, 'optionalAccess', _139 => _139.matches]);
4597
4618
  if (reduced)
4598
4619
  return this.maxFPS > 0 ? Math.min(this.maxFPS, REDUCED_MOTION_FPS) : REDUCED_MOTION_FPS;
4599
4620
  return this.maxFPS;
@@ -4666,7 +4687,7 @@ var Scene = (_class7 = class _Scene {
4666
4687
  * @param time - Current absolute time in milliseconds (default 0).
4667
4688
  */
4668
4689
  render(renderer, dt = 0, time = 0) {
4669
- if (_optionalChain([renderer, 'access', _137 => _137.isContextLost, 'optionalCall', _138 => _138()])) return;
4690
+ if (_optionalChain([renderer, 'access', _140 => _140.isContextLost, 'optionalCall', _141 => _141()])) return;
4670
4691
  const isMainRenderer = renderer === this.renderer;
4671
4692
  if (isMainRenderer && this.a11yRoot && this.canvas.parentElement) {
4672
4693
  const parentStyle = this.canvas.parentElement.style;
@@ -4792,7 +4813,7 @@ var Scene = (_class7 = class _Scene {
4792
4813
  }
4793
4814
  renderer.clear();
4794
4815
  if (isMainRenderer) {
4795
- _optionalChain([this, 'access', _139 => _139.pointRenderer, 'optionalAccess', _140 => _140.begin, 'call', _141 => _141()]);
4816
+ _optionalChain([this, 'access', _142 => _142.pointRenderer, 'optionalAccess', _143 => _143.begin, 'call', _144 => _144()]);
4796
4817
  }
4797
4818
  const vw = this.width;
4798
4819
  const vh = this.height;
@@ -4989,9 +5010,9 @@ var Scene = (_class7 = class _Scene {
4989
5010
  const flushT0 = this._phaseTiming ? performance.now() : 0;
4990
5011
  renderer.flush();
4991
5012
  if (isMainRenderer) {
4992
- _optionalChain([this, 'access', _142 => _142.pointRenderer, 'optionalAccess', _143 => _143.flush, 'call', _144 => _144()]);
5013
+ _optionalChain([this, 'access', _145 => _145.pointRenderer, 'optionalAccess', _146 => _146.flush, 'call', _147 => _147()]);
4993
5014
  }
4994
- _optionalChain([renderer, 'access', _145 => _145.present, 'optionalCall', _146 => _146()]);
5015
+ _optionalChain([renderer, 'access', _148 => _148.present, 'optionalCall', _149 => _149()]);
4995
5016
  if (this._phaseTiming) this._recordPhase("flush", performance.now() - flushT0);
4996
5017
  if (this._devActive) {
4997
5018
  this._devFrameCount++;
@@ -5002,7 +5023,7 @@ var Scene = (_class7 = class _Scene {
5002
5023
  * Export the current scene state to a lightweight, flat SVG XML string.
5003
5024
  */
5004
5025
  toSVG() {
5005
- const renderer = new (0, _chunkGKSCJ6AFjs.SVGRenderer)(this.width, this.height);
5026
+ const renderer = new (0, _chunkLHONR3GOjs.SVGRenderer)(this.width, this.height);
5006
5027
  this.render(renderer, 0, 0);
5007
5028
  return renderer.toXMLString();
5008
5029
  }
@@ -5271,8 +5292,8 @@ function pointInBounds(b, x, y) {
5271
5292
  function contentGridLineSignature(grid, line, projected, lineHeight, baseline, font, isFirstLine) {
5272
5293
  const parts = [
5273
5294
  // Line box: position, size, and the font that resolves its baseline.
5274
- `${_nullishCoalesce(_optionalChain([projected, 'optionalAccess', _147 => _147.x]), () => ( 0))}`,
5275
- `${_nullishCoalesce(_optionalChain([projected, 'optionalAccess', _148 => _148.y]), () => ( ""))}`,
5295
+ `${_nullishCoalesce(_optionalChain([projected, 'optionalAccess', _150 => _150.x]), () => ( 0))}`,
5296
+ `${_nullishCoalesce(_optionalChain([projected, 'optionalAccess', _151 => _151.y]), () => ( ""))}`,
5276
5297
  `${lineHeight}`,
5277
5298
  `${baseline}`,
5278
5299
  font,
@@ -5459,7 +5480,7 @@ var GridTextEntity = (_class9 = class extends _chunkAGP4VLF4js.Entity {
5459
5480
  updateGrid(ascii) {
5460
5481
  this.grid = ascii;
5461
5482
  this.rows = ascii.length;
5462
- this.cols = _optionalChain([ascii, 'access', _149 => _149[0], 'optionalAccess', _150 => _150.length]) || 0;
5483
+ this.cols = _optionalChain([ascii, 'access', _152 => _152[0], 'optionalAccess', _153 => _153.length]) || 0;
5463
5484
  }
5464
5485
  isPointInside(_globalX, _globalY) {
5465
5486
  return false;
@@ -5569,7 +5590,7 @@ var SplineEntity = (_class10 = class extends _chunkAGP4VLF4js.Entity {
5569
5590
  this.width = this.bounds.width;
5570
5591
  this.height = this.bounds.height;
5571
5592
  const isGradient = (c) => c !== null && !Array.isArray(c);
5572
- this.containsGradient = (_nullishCoalesce(_optionalChain([this, 'access', _151 => _151.doc, 'access', _152 => _152.equations, 'optionalAccess', _153 => _153.some, 'call', _154 => _154((eq) => isGradient(eq.color_rgb))]), () => ( false))) || (_nullishCoalesce(_optionalChain([this, 'access', _155 => _155.doc, 'access', _156 => _156.paths, 'optionalAccess', _157 => _157.some, 'call', _158 => _158((p) => isGradient(p.color_rgb))]), () => ( false)));
5593
+ this.containsGradient = (_nullishCoalesce(_optionalChain([this, 'access', _154 => _154.doc, 'access', _155 => _155.equations, 'optionalAccess', _156 => _156.some, 'call', _157 => _157((eq) => isGradient(eq.color_rgb))]), () => ( false))) || (_nullishCoalesce(_optionalChain([this, 'access', _158 => _158.doc, 'access', _159 => _159.paths, 'optionalAccess', _160 => _160.some, 'call', _161 => _161((p) => isGradient(p.color_rgb))]), () => ( false)));
5573
5594
  this.interactive = true;
5574
5595
  }
5575
5596
  computeBounds() {
@@ -6063,8 +6084,8 @@ var DOMPortalEntity = (_class11 = class extends _chunkAGP4VLF4js.Entity {
6063
6084
  }, _class11);
6064
6085
 
6065
6086
  // src/index.ts
6066
- Scene.registerWebGLPointRendererCreator(_chunkGKSCJ6AFjs.createWebGLPointRenderer);
6067
- Scene.registerWebGPUParticleSystemManager(_chunkGKSCJ6AFjs.WebGPUParticleSystemManager);
6087
+ Scene.registerWebGLPointRendererCreator(_chunkLHONR3GOjs.createWebGLPointRenderer);
6088
+ Scene.registerWebGPUParticleSystemManager(_chunkLHONR3GOjs.WebGPUParticleSystemManager);
6068
6089
 
6069
6090
 
6070
6091
 
@@ -6100,4 +6121,4 @@ Scene.registerWebGPUParticleSystemManager(_chunkGKSCJ6AFjs.WebGPUParticleSystemM
6100
6121
 
6101
6122
 
6102
6123
 
6103
- exports.CanvasRenderer = _chunkGKSCJ6AFjs.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Entity = _chunkAGP4VLF4js.Entity; exports.GlyphRasterAtlas = _chunkGKSCJ6AFjs.GlyphRasterAtlas; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.MSDFTextEntity = _chunkAGP4VLF4js.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.Rect = Rect; exports.SVGEntity = _chunkAGP4VLF4js.SVGEntity; exports.SVGRenderer = _chunkGKSCJ6AFjs.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkGKSCJ6AFjs.TextRasterCache; exports.VectoJSEvent = _chunkAGP4VLF4js.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkGKSCJ6AFjs.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkGKSCJ6AFjs.createWebGLPointRenderer; exports.isSafeUrl = _chunkGKSCJ6AFjs.isSafeUrl; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkGKSCJ6AFjs.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkGKSCJ6AFjs.sanitizeUrl;
6124
+ exports.CanvasRenderer = _chunkLHONR3GOjs.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Entity = _chunkAGP4VLF4js.Entity; exports.GlyphRasterAtlas = _chunkLHONR3GOjs.GlyphRasterAtlas; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.MSDFTextEntity = _chunkAGP4VLF4js.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.Rect = Rect; exports.SVGEntity = _chunkAGP4VLF4js.SVGEntity; exports.SVGRenderer = _chunkLHONR3GOjs.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkLHONR3GOjs.TextRasterCache; exports.VectoJSEvent = _chunkAGP4VLF4js.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkLHONR3GOjs.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkLHONR3GOjs.createWebGLPointRenderer; exports.isSafeUrl = _chunkLHONR3GOjs.isSafeUrl; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkLHONR3GOjs.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkLHONR3GOjs.sanitizeUrl;
package/dist/index.mjs CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  isSafeUrl,
9
9
  parseColorToRGBA,
10
10
  sanitizeUrl
11
- } from "./chunk-RTENOAYT.mjs";
11
+ } from "./chunk-UPULSLKA.mjs";
12
12
  import {
13
13
  Entity,
14
14
  MSDFTextEntity,
@@ -2462,6 +2462,27 @@ var Scene = class _Scene {
2462
2462
  get webgpuDisabled() {
2463
2463
  return this._webgpuDisabled || this.particleBackend === "cpu";
2464
2464
  }
2465
+ /**
2466
+ * Draw accounting for the WebGL point layer, or null when that layer is not in
2467
+ * use.
2468
+ *
2469
+ * Null and all-zero mean different things: null is "this backend is not
2470
+ * running", zero is "it ran and drew nothing". A readout that conflates them
2471
+ * sends someone looking for a performance problem in a backend that was never
2472
+ * active.
2473
+ */
2474
+ get webglDrawStats() {
2475
+ return this.pointRenderer?.stats?.() ?? null;
2476
+ }
2477
+ /**
2478
+ * Whether a WebGPU device is currently live for particle compute.
2479
+ *
2480
+ * The WebGPU path only activates when a `ComputeParticleEntity` is present, so
2481
+ * most scenes never touch it.
2482
+ */
2483
+ get webgpuActive() {
2484
+ return this.device !== null && !this.deviceLost;
2485
+ }
2465
2486
  set webgpuDisabled(value) {
2466
2487
  this._webgpuDisabled = value;
2467
2488
  }
@@ -1,4 +1,4 @@
1
- import { IRenderer } from './IRenderer';
1
+ import { type DrawCounters, IRenderer } from './IRenderer';
2
2
  export declare class CanvasRenderer implements IRenderer {
3
3
  private ctx;
4
4
  private width;
@@ -33,6 +33,17 @@ export declare class CanvasRenderer implements IRenderer {
33
33
  private batchCount;
34
34
  private _cachedFont;
35
35
  private _cachedFill;
36
+ /** Backend discriminator; see {@link IRenderer.kind}. */
37
+ readonly kind = "canvas2d";
38
+ /**
39
+ * Draw counters, allocated only once counting is enabled.
40
+ *
41
+ * Null when off, so the guard on every op is a single null test and an inactive
42
+ * renderer carries no counter object at all.
43
+ */
44
+ private counters;
45
+ /** Accumulated primitive area, kept separately so the ratio is derived on read. */
46
+ private drawnArea;
36
47
  /**
37
48
  * @param canvas - The target canvas. Its backing store is resized to the
38
49
  * logical size × devicePixelRatio.
@@ -79,6 +90,12 @@ export declare class CanvasRenderer implements IRenderer {
79
90
  * restored). The owner skips its render pass while this is true. */
80
91
  isContextLost(): boolean;
81
92
  /** @inheritdoc */
93
+ /** @inheritdoc */
94
+ setDrawCounters(enabled: boolean): void;
95
+ /** @inheritdoc */
96
+ getDrawCounters(): DrawCounters | null;
97
+ /** @inheritdoc */
98
+ clearDrawCounters(): void;
82
99
  clear(): void;
83
100
  /** @inheritdoc */
84
101
  save(): void;
@@ -12,7 +12,70 @@
12
12
  * r.fill('#38bdf8');
13
13
  * }
14
14
  */
15
+ /**
16
+ * Per-backend draw counters, for a DevTools GPU readout.
17
+ *
18
+ * Opt-in via {@link IRenderer.setDrawCounters}: when off, counting compiles to a
19
+ * single boolean test per op, matching how `Scene` gates its phase timing and
20
+ * dirty tracking. Totals are cumulative until cleared rather than per-frame,
21
+ * because a per-frame log answers the question less directly at far more memory —
22
+ * the same reasoning `Scene.renderPhases` documents.
23
+ */
24
+ export interface DrawCounters {
25
+ /** `fill()` calls, each committing one path. */
26
+ fills: number;
27
+ /** `stroke()` calls. */
28
+ strokes: number;
29
+ /** `fillText()` calls. */
30
+ texts: number;
31
+ /** `drawImage`/`drawImageRect` blits. */
32
+ images: number;
33
+ /** `fillCircle()` calls — batched, so this exceeds the fills they produce. */
34
+ circles: number;
35
+ /** Batch commits from {@link IRenderer.flush}. */
36
+ flushes: number;
37
+ /** `save()` calls. */
38
+ saves: number;
39
+ /** `restore()` calls. */
40
+ restores: number;
41
+ /** `clip()` calls. */
42
+ clips: number;
43
+ /**
44
+ * Times a font or fill style actually changed, as opposed to being re-set to
45
+ * the value it already had.
46
+ *
47
+ * The renderer already elides redundant sets; this counts the ones that got
48
+ * through, which is the number that costs anything.
49
+ */
50
+ stateSwitches: number;
51
+ /**
52
+ * Sum of drawn primitive areas divided by canvas area.
53
+ *
54
+ * A PROXY for overdraw, not a measurement: Canvas2D exposes no pixel-coverage
55
+ * readback, so this counts area submitted, ignoring clipping and off-screen
56
+ * rejection. It overstates, sometimes badly. Useful as a trend between two
57
+ * states of the same scene; meaningless as an absolute.
58
+ */
59
+ overdrawRatio: number;
60
+ }
15
61
  export interface IRenderer {
62
+ /**
63
+ * Stable backend identifier.
64
+ *
65
+ * A discriminator rather than `constructor.name`, which minifies to something
66
+ * unusable in a production bundle — and a debug tool that cannot name the
67
+ * backend in the build where it matters is not much of a debug tool.
68
+ */
69
+ readonly kind?: 'canvas2d' | 'svg' | 'three' | string;
70
+ /**
71
+ * Enable or disable draw counting. Optional; a backend that cannot count omits
72
+ * it and a reader treats the absence as "not available".
73
+ */
74
+ setDrawCounters?(enabled: boolean): void;
75
+ /** Current counter totals, or null when counting is off. */
76
+ getDrawCounters?(): DrawCounters | null;
77
+ /** Zero the totals without disabling counting. */
78
+ clearDrawCounters?(): void;
16
79
  /** Clear the entire drawing surface to transparent / background color. */
17
80
  clear(): void;
18
81
  /** Push the current transform + state onto the renderer's stack. */
@@ -13,6 +13,8 @@ export interface SVGLinearGradient {
13
13
  createMatrix: number[];
14
14
  }
15
15
  export declare class SVGRenderer implements IRenderer {
16
+ /** Backend discriminator; see {@link IRenderer.kind}. */
17
+ readonly kind = "svg";
16
18
  private width;
17
19
  private height;
18
20
  private buffer;
@@ -4,6 +4,29 @@
4
4
  * render `getBatchCircle()` / `getBatchRect()` entities — the point-cloud /
5
5
  * particle case where Canvas2D tops out at ~7 fps for 100k primitives.
6
6
  */
7
+ /** Per-frame and cumulative WebGL draw accounting. */
8
+ export interface WebGLDrawStats {
9
+ /** Draw calls issued for the last completed frame. */
10
+ drawCalls: number;
11
+ /** Cumulative draw calls since creation. */
12
+ totalDrawCalls: number;
13
+ /** Cumulative mid-frame MSDF atlas switches, each costing an extra draw. */
14
+ atlasSwitches: number;
15
+ /** Programs compiled at creation — a fixed capability, not a per-frame cost. */
16
+ programs: number;
17
+ /** Textures currently allocated (colour atlas and/or MSDF atlas). */
18
+ textures: number;
19
+ /**
20
+ * Circles routed to the quad path rather than `gl.POINTS`, cumulatively.
21
+ *
22
+ * A circle takes the quad path when it could clip off-viewport or exceeds the
23
+ * driver's maximum aliased point size. A high share means the POINTS fast path
24
+ * is not being used and each circle costs four vertices instead of one.
25
+ */
26
+ circleQuadFallbacks: number;
27
+ /** Circles drawn through the `gl.POINTS` fast path, cumulatively. */
28
+ circlePoints: number;
29
+ }
7
30
  export interface PointRenderer {
8
31
  /** Resize the backing buffer + GL viewport to a logical `w × h` (DPR applied). */
9
32
  resize(width: number, height: number): void;
@@ -21,6 +44,16 @@ export interface PointRenderer {
21
44
  maxDPR?: number;
22
45
  /** Begin a frame: reset the accumulated primitive buffers. */
23
46
  begin(): void;
47
+ /**
48
+ * Draw-call counters for the most recent frame, plus cumulative totals.
49
+ *
50
+ * Batching here is by primitive type — one draw per active type — so draw calls
51
+ * and batches are the same number, and both are bounded at five plus one per
52
+ * mid-frame MSDF atlas switch. That switch is the only variable term and the
53
+ * only thing worth watching: it forces a commit of glyphs batched against the
54
+ * previous font.
55
+ */
56
+ stats?(): WebGLDrawStats;
24
57
  /** Add one circle in world (CSS-pixel) coordinates; `alpha` multiplies the color's. */
25
58
  addCircle(x: number, y: number, radius: number, color: string, alpha?: number): void;
26
59
  /**