@solidtv/renderer 1.1.1 → 1.1.3

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 (52) hide show
  1. package/README.md +1 -4
  2. package/dist/src/core/CoreNode.d.ts +27 -0
  3. package/dist/src/core/CoreNode.js +164 -43
  4. package/dist/src/core/CoreNode.js.map +1 -1
  5. package/dist/src/core/CoreTextureManager.d.ts +0 -4
  6. package/dist/src/core/CoreTextureManager.js +70 -17
  7. package/dist/src/core/CoreTextureManager.js.map +1 -1
  8. package/dist/src/core/Stage.js +5 -4
  9. package/dist/src/core/Stage.js.map +1 -1
  10. package/dist/src/core/lib/Matrix3d.d.ts +9 -0
  11. package/dist/src/core/lib/Matrix3d.js +29 -3
  12. package/dist/src/core/lib/Matrix3d.js.map +1 -1
  13. package/dist/src/core/lib/WebGlContextWrapper.js +0 -4
  14. package/dist/src/core/lib/WebGlContextWrapper.js.map +1 -1
  15. package/dist/src/core/lib/collectionUtils.js +1 -1
  16. package/dist/src/core/lib/collectionUtils.js.map +1 -1
  17. package/dist/src/core/renderers/canvas/CanvasRenderer.d.ts +3 -3
  18. package/dist/src/core/renderers/canvas/CanvasRenderer.js +3 -3
  19. package/dist/src/core/renderers/canvas/CanvasRenderer.js.map +1 -1
  20. package/dist/src/core/text-rendering/CanvasFontHandler.d.ts +1 -1
  21. package/dist/src/core/text-rendering/CanvasFontHandler.js +3 -3
  22. package/dist/src/core/text-rendering/CanvasFontHandler.js.map +1 -1
  23. package/dist/src/core/text-rendering/CanvasTextRenderer.d.ts +2 -3
  24. package/dist/src/core/text-rendering/CanvasTextRenderer.js +2 -1
  25. package/dist/src/core/text-rendering/CanvasTextRenderer.js.map +1 -1
  26. package/dist/src/core/text-rendering/SdfFontHandler.js +3 -3
  27. package/dist/src/core/text-rendering/SdfFontHandler.js.map +1 -1
  28. package/dist/src/core/text-rendering/Utils.js.map +1 -1
  29. package/dist/src/core/textures/ImageTexture.js +1 -1
  30. package/dist/src/core/textures/ImageTexture.js.map +1 -1
  31. package/dist/src/core/textures/SubTexture.js.map +1 -1
  32. package/dist/src/core/textures/Texture.d.ts +6 -0
  33. package/dist/src/core/textures/Texture.js +6 -0
  34. package/dist/src/core/textures/Texture.js.map +1 -1
  35. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  36. package/package.json +7 -7
  37. package/src/core/CoreNode.test.ts +438 -0
  38. package/src/core/CoreNode.ts +176 -61
  39. package/src/core/CoreTextureManager.ts +77 -20
  40. package/src/core/Stage.ts +10 -7
  41. package/src/core/lib/Matrix3d.test.ts +72 -0
  42. package/src/core/lib/Matrix3d.ts +30 -3
  43. package/src/core/lib/WebGlContextWrapper.ts +0 -2
  44. package/src/core/lib/collectionUtils.ts +1 -1
  45. package/src/core/renderers/canvas/CanvasRenderer.ts +3 -3
  46. package/src/core/text-rendering/CanvasFontHandler.ts +4 -4
  47. package/src/core/text-rendering/CanvasTextRenderer.ts +7 -2
  48. package/src/core/text-rendering/SdfFontHandler.ts +4 -4
  49. package/src/core/text-rendering/Utils.ts +0 -2
  50. package/src/core/textures/ImageTexture.ts +1 -4
  51. package/src/core/textures/SubTexture.ts +0 -2
  52. package/src/core/textures/Texture.ts +7 -0
package/README.md CHANGED
@@ -100,10 +100,7 @@ at start up so they are ready when your application is rendered.
100
100
  ```ts
101
101
  import { RendererMain } from '@solidtv/renderer';
102
102
 
103
- import {
104
- WebGlCoreRenderer,
105
- SdfTextRenderer,
106
- } from '@solidtv/renderer/webgl';
103
+ import { WebGlCoreRenderer, SdfTextRenderer } from '@solidtv/renderer/webgl';
107
104
  import { CanvasTextRenderer } from '@solidtv/renderer/canvas';
108
105
 
109
106
  const renderer = new RendererMain(
@@ -685,6 +685,11 @@ export declare class CoreNode extends EventEmitter {
685
685
  protected _id: number;
686
686
  readonly props: CoreNodeProps;
687
687
  readonly isCoreNode: true;
688
+ /**
689
+ * Lazily allocated on first `animateProp` call. Animations are rare across
690
+ * the scene graph, so deferring the object literal avoids per-node GC
691
+ * pressure during construction.
692
+ */
688
693
  private _animations;
689
694
  renderOpBufferIdx: number;
690
695
  numQuads: number;
@@ -719,6 +724,28 @@ export declare class CoreNode extends EventEmitter {
719
724
  isRenderable: boolean;
720
725
  renderState: CoreNodeRenderState;
721
726
  isSimple: boolean;
727
+ /**
728
+ * `true` when `localTransform` is in identity-shape (ta=1, tb=0, tc=0, td=1)
729
+ * — i.e. a pure translation. Lets the simple-path `updateLocalTransform`
730
+ * skip redundant ta/tb/tc/td writes between frames. Defaults to `true`
731
+ * because the matrix is eagerly allocated as an identity in the constructor.
732
+ */
733
+ _localIsTranslate: boolean;
734
+ /**
735
+ * Cached result of the texture `contain` resizeMode check used by
736
+ * `updateLocalTransform` and `updateIsSimple`. Updated whenever the
737
+ * texture or textureOptions change (via `updateIsSimple`), so the hot
738
+ * paths can avoid the optional-chain + string compare on every frame.
739
+ */
740
+ _hasContainResize: boolean;
741
+ /**
742
+ * `true` when `globalTransform` is in identity-shape (ta=1, tb=0, tc=0, td=1).
743
+ * Propagates from parent: a node's global is translate-only iff the parent's
744
+ * global is translate-only AND the node itself is `isSimple`. Default `true`
745
+ * because freshly-constructed nodes have no transform applied yet, and the
746
+ * Stage root is configured with an identity-shape global.
747
+ */
748
+ _globalIsTranslate: boolean;
722
749
  worldAlpha: number;
723
750
  premultipliedColorTl: number;
724
751
  premultipliedColorTr: number;
@@ -144,7 +144,12 @@ export class CoreNode extends EventEmitter {
144
144
  _id = getNewId();
145
145
  props;
146
146
  isCoreNode = true;
147
- _animations = {};
147
+ /**
148
+ * Lazily allocated on first `animateProp` call. Animations are rare across
149
+ * the scene graph, so deferring the object literal avoids per-node GC
150
+ * pressure during construction.
151
+ */
152
+ _animations = null;
148
153
  // WebGL Render Op State
149
154
  renderOpBufferIdx = 0;
150
155
  numQuads = 0;
@@ -185,6 +190,28 @@ export class CoreNode extends EventEmitter {
185
190
  isRenderable = false;
186
191
  renderState = CoreNodeRenderState.Init;
187
192
  isSimple = true;
193
+ /**
194
+ * `true` when `localTransform` is in identity-shape (ta=1, tb=0, tc=0, td=1)
195
+ * — i.e. a pure translation. Lets the simple-path `updateLocalTransform`
196
+ * skip redundant ta/tb/tc/td writes between frames. Defaults to `true`
197
+ * because the matrix is eagerly allocated as an identity in the constructor.
198
+ */
199
+ _localIsTranslate = true;
200
+ /**
201
+ * Cached result of the texture `contain` resizeMode check used by
202
+ * `updateLocalTransform` and `updateIsSimple`. Updated whenever the
203
+ * texture or textureOptions change (via `updateIsSimple`), so the hot
204
+ * paths can avoid the optional-chain + string compare on every frame.
205
+ */
206
+ _hasContainResize = false;
207
+ /**
208
+ * `true` when `globalTransform` is in identity-shape (ta=1, tb=0, tc=0, td=1).
209
+ * Propagates from parent: a node's global is translate-only iff the parent's
210
+ * global is translate-only AND the node itself is `isSimple`. Default `true`
211
+ * because freshly-constructed nodes have no transform applied yet, and the
212
+ * Stage root is configured with an identity-shape global.
213
+ */
214
+ _globalIsTranslate = true;
188
215
  worldAlpha = 1;
189
216
  premultipliedColorTl = 0;
190
217
  premultipliedColorTr = 0;
@@ -205,6 +232,12 @@ export class CoreNode extends EventEmitter {
205
232
  constructor(stage, props) {
206
233
  super();
207
234
  this.stage = stage;
235
+ // Eagerly allocate the local/global transform matrices as identity.
236
+ // This keeps the field's hidden class monomorphic (Matrix3d, not
237
+ // Matrix3d|undefined) from construction onward and lets the simple
238
+ // / translate-only fast paths take effect on the very first update.
239
+ this.localTransform = Matrix3d.identity();
240
+ this.globalTransform = Matrix3d.identity();
208
241
  //inital update type
209
242
  let initialUpdateType = UpdateType.Local | UpdateType.RenderBounds | UpdateType.RenderState;
210
243
  // Use the incoming props object directly — resolveNodeDefaults already
@@ -421,28 +454,54 @@ export class CoreNode extends EventEmitter {
421
454
  const p = this.props;
422
455
  const { x, y } = p;
423
456
  if (this.isSimple) {
457
+ // Fast path: when localTransform is already in identity-shape
458
+ // (ta=1, tb=0, tc=0, td=1), only tx/ty change between frames, so we
459
+ // skip the 4 redundant field writes Matrix3d.translate would do.
460
+ // _localIsTranslate becomes stale when a node was non-simple (had
461
+ // rotation/scale/mount) on a previous frame — in that case do a
462
+ // full reset to identity-translate.
463
+ if (this._localIsTranslate === true) {
464
+ this.localTransform.setTranslate(x, y);
465
+ return;
466
+ }
424
467
  this.localTransform = Matrix3d.translate(x, y, this.localTransform);
468
+ this._localIsTranslate = true;
425
469
  return;
426
470
  }
427
471
  const { w, h } = p;
428
472
  const mountTranslateX = p.mountX * w;
429
473
  const mountTranslateY = p.mountY * h;
430
- if (p.rotation !== 0 || p.scaleX !== 1 || p.scaleY !== 1) {
431
- const scaleRotate = Matrix3d.rotate(p.rotation, Matrix3d.temp).scale(p.scaleX, p.scaleY);
474
+ const rotation = p.rotation;
475
+ const scaleX = p.scaleX;
476
+ const scaleY = p.scaleY;
477
+ if (rotation !== 0) {
478
+ // Full rotation (+ optional scale + pivot)
479
+ const scaleRotate = Matrix3d.rotate(rotation, Matrix3d.temp).scale(scaleX, scaleY);
432
480
  const pivotTranslateX = p.pivotX * w;
433
481
  const pivotTranslateY = p.pivotY * h;
434
482
  this.localTransform = Matrix3d.translate(x - mountTranslateX + pivotTranslateX, y - mountTranslateY + pivotTranslateY, this.localTransform)
435
483
  .multiply(scaleRotate)
436
484
  .translate(-pivotTranslateX, -pivotTranslateY);
437
485
  }
486
+ else if (scaleX !== 1 || scaleY !== 1) {
487
+ // Scale (+ optional pivot) without rotation — skip the rotate matrix
488
+ // and the 8-mul multiply; `.scale()` is a 4-mul in-place op.
489
+ const pivotTranslateX = p.pivotX * w;
490
+ const pivotTranslateY = p.pivotY * h;
491
+ this.localTransform = Matrix3d.translate(x - mountTranslateX + pivotTranslateX, y - mountTranslateY + pivotTranslateY, this.localTransform)
492
+ .scale(scaleX, scaleY)
493
+ .translate(-pivotTranslateX, -pivotTranslateY);
494
+ }
438
495
  else {
496
+ // Mount (or texture-contain) only — pure translation.
439
497
  this.localTransform = Matrix3d.translate(x - mountTranslateX, y - mountTranslateY, this.localTransform);
440
498
  }
441
- // Handle 'contain' resize mode
499
+ // Handle 'contain' resize mode (cached check; dimensions still need
500
+ // a runtime null-check because they're populated asynchronously).
442
501
  const texture = p.texture;
443
- if (texture &&
444
- texture.dimensions &&
445
- p.textureOptions.resizeMode?.type === 'contain') {
502
+ if (this._hasContainResize === true &&
503
+ texture !== null &&
504
+ texture.dimensions !== null) {
446
505
  let resizeModeScaleX = 1;
447
506
  let resizeModeScaleY = 1;
448
507
  let extraX = 0;
@@ -473,16 +532,21 @@ export class CoreNode extends EventEmitter {
473
532
  .translate(extraX, extraY)
474
533
  .scale(resizeModeScaleX, resizeModeScaleY);
475
534
  }
535
+ this._localIsTranslate = false;
476
536
  }
477
537
  updateIsSimple() {
478
538
  const p = this.props;
539
+ // Cache the texture-contain check so updateLocalTransform doesn't have to
540
+ // run the optional-chain + string compare on every Local update.
541
+ this._hasContainResize =
542
+ p.texture !== null && p.textureOptions?.resizeMode?.type === 'contain';
479
543
  this.isSimple =
480
544
  p.rotation === 0 &&
481
545
  p.scaleX === 1 &&
482
546
  p.scaleY === 1 &&
483
547
  p.mountX === 0 &&
484
548
  p.mountY === 0 &&
485
- !(p.texture && p.textureOptions.resizeMode?.type === 'contain');
549
+ this._hasContainResize === false;
486
550
  }
487
551
  /**
488
552
  * @todo: test for correct calculation flag
@@ -515,15 +579,20 @@ export class CoreNode extends EventEmitter {
515
579
  this.hasRTTupdates = true;
516
580
  }
517
581
  if (updateType & UpdateType.Global) {
582
+ const lt = this.localTransform;
583
+ const gt = this.globalTransform;
584
+ let fastPathApplied = false;
518
585
  if (USE_RTT &&
519
586
  this.parentHasRenderTexture === true &&
520
587
  parent.rtt === true) {
521
588
  // we are at the start of the RTT chain, so we need to reset the globalTransform
522
589
  // for correct RTT rendering
523
- this.globalTransform = Matrix3d.identity(this.globalTransform);
590
+ Matrix3d.identity(gt);
524
591
  // Maintain a full scene global transform for bounds detection
525
592
  const parentTransform = parent.globalTransform || Matrix3d.identity(Matrix3d.temp);
526
- this.sceneGlobalTransform = Matrix3d.copy(parentTransform, this.sceneGlobalTransform).translateOrMultiply(this.localTransform);
593
+ this.sceneGlobalTransform = Matrix3d.copy(parentTransform, this.sceneGlobalTransform).translateOrMultiply(lt);
594
+ // identity * local => translate-only iff this node is simple
595
+ this._globalIsTranslate = this.isSimple;
527
596
  }
528
597
  else if (USE_RTT &&
529
598
  this.parentHasRenderTexture === true &&
@@ -531,18 +600,44 @@ export class CoreNode extends EventEmitter {
531
600
  // we're part of an RTT chain but our parent is not the main RTT node
532
601
  // so we need to propogate the sceneGlobalTransform of the parent
533
602
  // to maintain a full scene global transform for bounds detection
534
- const parentSceneTransform = parent.sceneGlobalTransform || this.localTransform;
535
- this.sceneGlobalTransform = Matrix3d.copy(parentSceneTransform, this.sceneGlobalTransform).translateOrMultiply(this.localTransform);
536
- this.globalTransform = Matrix3d.copy(parent.globalTransform || this.localTransform, this.globalTransform);
603
+ const parentSceneTransform = parent.sceneGlobalTransform || lt;
604
+ this.sceneGlobalTransform = Matrix3d.copy(parentSceneTransform, this.sceneGlobalTransform).translateOrMultiply(lt);
605
+ Matrix3d.copy(parent.globalTransform, gt);
606
+ // Conservative: RTT chains rarely hit the translate fast path
607
+ this._globalIsTranslate = false;
537
608
  }
538
609
  else {
539
- this.globalTransform = Matrix3d.copy(parent.globalTransform || this.localTransform, this.globalTransform);
540
- }
541
- if (this.isSimple) {
542
- this.globalTransform.translate(this.localTransform.tx, this.localTransform.ty);
610
+ // Common non-RTT path
611
+ const parentGT = parent.globalTransform;
612
+ if (this.isSimple === true && parent._globalIsTranslate === true) {
613
+ // Translate-only fast path: parent global and local are both pure
614
+ // translations, so the resulting global is also a pure translation
615
+ // and collapses to 2 adds on tx/ty.
616
+ if (this._globalIsTranslate === false) {
617
+ // Transitioning back into translate-only — reset ta/tb/tc/td
618
+ // that may have been left non-identity by a prior frame.
619
+ gt.ta = 1;
620
+ gt.tb = 0;
621
+ gt.tc = 0;
622
+ gt.td = 1;
623
+ }
624
+ gt.setTranslate(parentGT.tx + lt.tx, parentGT.ty + lt.ty);
625
+ this._globalIsTranslate = true;
626
+ fastPathApplied = true;
627
+ }
628
+ else {
629
+ Matrix3d.copy(parentGT, gt);
630
+ this._globalIsTranslate =
631
+ this.isSimple === true && parent._globalIsTranslate === true;
632
+ }
543
633
  }
544
- else {
545
- this.globalTransform.translateOrMultiply(this.localTransform);
634
+ if (fastPathApplied === false) {
635
+ if (this.isSimple) {
636
+ gt.translate(lt.tx, lt.ty);
637
+ }
638
+ else {
639
+ gt.translateOrMultiply(lt);
640
+ }
546
641
  }
547
642
  this.calculateRenderCoords();
548
643
  this.updateBoundingRect();
@@ -637,15 +732,31 @@ export class CoreNode extends EventEmitter {
637
732
  if (USE_RTT && this.rtt === true) {
638
733
  childClippingRect = NO_CLIPPING_RECT;
639
734
  }
640
- for (let i = 0, length = this.children.length; i < length; i++) {
641
- const child = this.children[i];
642
- if (childUpdateType !== 0) {
735
+ const children = this.children;
736
+ const length = children.length;
737
+ if (childUpdateType !== 0) {
738
+ // Specialized loop: OR-in the inherited update bits for every child,
739
+ // then update if non-zero. Avoids the per-iter `childUpdateType !== 0`
740
+ // compare.
741
+ for (let i = 0; i < length; i++) {
742
+ const child = children[i];
643
743
  child.updateType |= childUpdateType;
744
+ if (child.updateType === 0) {
745
+ continue;
746
+ }
747
+ child.update(delta, childClippingRect);
644
748
  }
645
- if (child.updateType === 0) {
646
- continue;
749
+ }
750
+ else {
751
+ // Specialized loop: nothing to inherit, so only walk children that
752
+ // already have pending work of their own.
753
+ for (let i = 0; i < length; i++) {
754
+ const child = children[i];
755
+ if (child.updateType === 0) {
756
+ continue;
757
+ }
758
+ child.update(delta, childClippingRect);
647
759
  }
648
- child.update(delta, childClippingRect);
649
760
  }
650
761
  }
651
762
  // If the node has an RTT parent and requires a texture re-render, inform the RTT parent
@@ -735,7 +846,7 @@ export class CoreNode extends EventEmitter {
735
846
  this.globalTransform);
736
847
  const renderCoords = (this.sceneRenderCoords ||
737
848
  this.renderCoords);
738
- if (transform.tb === 0 || transform.tc === 0) {
849
+ if (transform.tb === 0 && transform.tc === 0) {
739
850
  this.renderBound = createBound(renderCoords.x1, renderCoords.y1, renderCoords.x3, renderCoords.y3, this.renderBound);
740
851
  }
741
852
  else {
@@ -1782,31 +1893,41 @@ export class CoreNode extends EventEmitter {
1782
1893
  return createAnimation(this.stage.animationManager, this, props, settings);
1783
1894
  }
1784
1895
  animateProp(name, value, settings) {
1785
- const existing = this._animations[name];
1786
- if (existing && existing.settings === settings) {
1787
- const controller = existing.controller;
1788
- const values = controller.props ? controller.props[name] : null;
1789
- if (values) {
1790
- values.start = this[name] ?? 0;
1791
- values.target = value;
1792
- controller.progress = 0;
1793
- if (settings.adaptiveDuration === true) {
1794
- const now = performance.now();
1795
- const elapsed = now - controller.lastRunTime;
1796
- controller.lastRunTime = now;
1797
- const duration = settings.duration ?? controller.duration;
1798
- controller.duration = elapsed < duration ? elapsed : duration;
1896
+ let animations = this._animations;
1897
+ if (animations !== null) {
1898
+ const existing = animations[name];
1899
+ if (existing && existing.settings === settings) {
1900
+ const controller = existing.controller;
1901
+ const values = controller.props ? controller.props[name] : null;
1902
+ if (values) {
1903
+ values.start = this[name] ?? 0;
1904
+ values.target = value;
1905
+ controller.progress = 0;
1906
+ if (settings.adaptiveDuration === true) {
1907
+ const now = performance.now();
1908
+ const elapsed = now - controller.lastRunTime;
1909
+ controller.lastRunTime = now;
1910
+ const duration = settings.duration ?? controller.duration;
1911
+ controller.duration = elapsed < duration ? elapsed : duration;
1912
+ }
1913
+ return controller.start();
1799
1914
  }
1800
- return controller.start();
1801
1915
  }
1802
1916
  }
1917
+ else {
1918
+ animations = this._animations = {};
1919
+ }
1803
1920
  const animationProps = { [name]: value };
1804
1921
  const controller = createAnimation(this.stage.animationManager, this, animationProps, settings);
1805
- this._animations[name] = { controller, settings };
1922
+ animations[name] = { controller, settings };
1806
1923
  return controller.start();
1807
1924
  }
1808
1925
  animateToTarget(prop) {
1809
- const animation = this._animations[prop];
1926
+ const animations = this._animations;
1927
+ if (animations === null) {
1928
+ return undefined;
1929
+ }
1930
+ const animation = animations[prop];
1810
1931
  if (!animation) {
1811
1932
  return undefined;
1812
1933
  }