@solidtv/renderer 1.6.0 → 1.6.2

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 (36) hide show
  1. package/dist/src/core/CoreTextureManager.d.ts +31 -0
  2. package/dist/src/core/CoreTextureManager.js +87 -6
  3. package/dist/src/core/CoreTextureManager.js.map +1 -1
  4. package/dist/src/core/Stage.js +2 -1
  5. package/dist/src/core/Stage.js.map +1 -1
  6. package/dist/src/core/lib/ImageWorker.js +29 -20
  7. package/dist/src/core/lib/ImageWorker.js.map +1 -1
  8. package/dist/src/core/lib/validateImageBitmap.d.ts +13 -7
  9. package/dist/src/core/lib/validateImageBitmap.js +31 -10
  10. package/dist/src/core/lib/validateImageBitmap.js.map +1 -1
  11. package/dist/src/core/text-rendering/CanvasFontHandler.js +30 -15
  12. package/dist/src/core/text-rendering/CanvasFontHandler.js.map +1 -1
  13. package/dist/src/core/text-rendering/SdfFontHandler.js +52 -23
  14. package/dist/src/core/text-rendering/SdfFontHandler.js.map +1 -1
  15. package/dist/src/core/text-rendering/TextRenderer.d.ts +10 -1
  16. package/dist/src/core/textures/ImageTexture.js +4 -2
  17. package/dist/src/core/textures/ImageTexture.js.map +1 -1
  18. package/dist/src/main-api/Renderer.d.ts +37 -10
  19. package/dist/src/main-api/Renderer.js +8 -4
  20. package/dist/src/main-api/Renderer.js.map +1 -1
  21. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  22. package/package.json +1 -1
  23. package/src/core/CoreTextureManager.test.ts +147 -0
  24. package/src/core/CoreTextureManager.ts +97 -8
  25. package/src/core/Stage.ts +2 -0
  26. package/src/core/lib/ImageWorker.test.ts +19 -0
  27. package/src/core/lib/ImageWorker.ts +34 -40
  28. package/src/core/lib/validateImageBitmap.test.ts +17 -10
  29. package/src/core/lib/validateImageBitmap.ts +32 -14
  30. package/src/core/text-rendering/CanvasFontHandler.ts +36 -16
  31. package/src/core/text-rendering/SdfFontHandler.ts +55 -25
  32. package/src/core/text-rendering/TextRenderer.ts +10 -1
  33. package/src/core/text-rendering/tests/SdfFontHandler.test.ts +65 -1
  34. package/src/core/textures/ImageTexture.test.ts +94 -26
  35. package/src/core/textures/ImageTexture.ts +4 -2
  36. package/src/main-api/Renderer.ts +47 -14
@@ -2,26 +2,70 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { ImageTexture } from './ImageTexture.js';
3
3
  import type { CoreTextureManager } from '../CoreTextureManager.js';
4
4
 
5
+ const makeTxManager = (
6
+ imageBitmapSupported: {
7
+ basic: boolean;
8
+ options: boolean;
9
+ full: boolean;
10
+ premultiplyHonored: boolean | null;
11
+ },
12
+ createImageBitmapMock: () => Promise<unknown>,
13
+ ) =>
14
+ ({
15
+ imageBitmapSupported,
16
+ platform: {
17
+ createImageBitmap: createImageBitmapMock,
18
+ },
19
+ } as unknown as CoreTextureManager);
20
+
5
21
  describe('ImageTexture.createImageBitmap', () => {
6
22
  beforeEach(() => {
7
23
  vi.stubGlobal('ImageBitmap', class {});
8
24
  });
9
25
 
10
- it('passes options to createImageBitmap when no crop is requested and options/full are supported', async () => {
26
+ it('requests a premultiplied bitmap when options are supported and the option is honored', async () => {
27
+ const createImageBitmapMock = vi.fn(() =>
28
+ Promise.resolve({ close: () => {} }),
29
+ );
30
+ const txManager = makeTxManager(
31
+ { basic: true, options: true, full: true, premultiplyHonored: true },
32
+ createImageBitmapMock,
33
+ );
34
+
35
+ const props = ImageTexture.resolveDefaults({
36
+ src: 'test.png',
37
+ premultiplyAlpha: true,
38
+ });
39
+ const texture = new ImageTexture(txManager, props);
40
+
41
+ const blob = new Blob([], { type: 'image/png' });
42
+ const result = await texture.createImageBitmap(
43
+ blob,
44
+ true,
45
+ null,
46
+ null,
47
+ null,
48
+ null,
49
+ );
50
+
51
+ expect(createImageBitmapMock).toHaveBeenCalledWith(blob, {
52
+ premultiplyAlpha: 'premultiply',
53
+ colorSpaceConversion: 'none',
54
+ imageOrientation: 'none',
55
+ });
56
+
57
+ // The bitmap is already premultiplied; WebGL must not premultiply again
58
+ expect(result.premultiplyAlpha).toBe(false);
59
+ });
60
+
61
+ it('requests a straight bitmap and defers premultiply to WebGL when the option is not honored', async () => {
11
62
  const createImageBitmapMock = vi.fn(() =>
12
63
  Promise.resolve({ close: () => {} }),
13
64
  );
14
- const txManager = {
15
- imageBitmapSupported: {
16
- basic: true,
17
- options: true,
18
- full: true,
19
- premultiplyHonored: false,
20
- },
21
- platform: {
22
- createImageBitmap: createImageBitmapMock,
23
- },
24
- } as unknown as CoreTextureManager;
65
+ const txManager = makeTxManager(
66
+ { basic: true, options: true, full: true, premultiplyHonored: false },
67
+ createImageBitmapMock,
68
+ );
25
69
 
26
70
  const props = ImageTexture.resolveDefaults({
27
71
  src: 'test.png',
@@ -39,14 +83,13 @@ describe('ImageTexture.createImageBitmap', () => {
39
83
  null,
40
84
  );
41
85
 
42
- // It should have called createImageBitmap with the options object
43
86
  expect(createImageBitmapMock).toHaveBeenCalledWith(blob, {
44
87
  premultiplyAlpha: 'none',
45
88
  colorSpaceConversion: 'none',
46
89
  imageOrientation: 'none',
47
90
  });
48
91
 
49
- // Since premultiplyHonored is false, useGlPremultiply should be true
92
+ // Since premultiplyHonored is false, WebGL premultiplies on upload
50
93
  expect(result.premultiplyAlpha).toBe(true);
51
94
  });
52
95
 
@@ -54,17 +97,10 @@ describe('ImageTexture.createImageBitmap', () => {
54
97
  const createImageBitmapMock = vi.fn(() =>
55
98
  Promise.resolve({ close: () => {} }),
56
99
  );
57
- const txManager = {
58
- imageBitmapSupported: {
59
- basic: true,
60
- options: false,
61
- full: false,
62
- premultiplyHonored: null,
63
- },
64
- platform: {
65
- createImageBitmap: createImageBitmapMock,
66
- },
67
- } as unknown as CoreTextureManager;
100
+ const txManager = makeTxManager(
101
+ { basic: true, options: false, full: false, premultiplyHonored: null },
102
+ createImageBitmapMock,
103
+ );
68
104
 
69
105
  const props = ImageTexture.resolveDefaults({
70
106
  src: 'test.png',
@@ -85,7 +121,39 @@ describe('ImageTexture.createImageBitmap', () => {
85
121
  // It should have called basic createImageBitmap without options
86
122
  expect(createImageBitmapMock).toHaveBeenCalledWith(blob);
87
123
 
88
- // And premultiplyAlpha should be false (browser default is assumed to premultiply)
124
+ // The browser default is assumed to premultiply; WebGL must not premultiply again
89
125
  expect(result.premultiplyAlpha).toBe(false);
90
126
  });
127
+
128
+ it('defers premultiply to WebGL on basic-only devices when premultiplyAlphaHonored is false', async () => {
129
+ const createImageBitmapMock = vi.fn(() =>
130
+ Promise.resolve({ close: () => {} }),
131
+ );
132
+ const txManager = makeTxManager(
133
+ { basic: true, options: false, full: false, premultiplyHonored: false },
134
+ createImageBitmapMock,
135
+ );
136
+
137
+ const props = ImageTexture.resolveDefaults({
138
+ src: 'test.png',
139
+ premultiplyAlpha: true,
140
+ });
141
+ const texture = new ImageTexture(txManager, props);
142
+
143
+ const blob = new Blob([], { type: 'image/png' });
144
+ const result = await texture.createImageBitmap(
145
+ blob,
146
+ true,
147
+ null,
148
+ null,
149
+ null,
150
+ null,
151
+ );
152
+
153
+ // Still the basic call — no options are supported on these devices
154
+ expect(createImageBitmapMock).toHaveBeenCalledWith(blob);
155
+
156
+ // Device default returns straight alpha; WebGL premultiplies on upload
157
+ expect(result.premultiplyAlpha).toBe(true);
158
+ });
91
159
  });
@@ -225,10 +225,12 @@ export class ImageTexture extends Texture {
225
225
  ) {
226
226
  // basic createImageBitmap without options or crop
227
227
  // this is supported for Chrome v50 to v52/54 that doesn't support options.
228
- // The browser default premultiplies, so WebGL must not premultiply again.
228
+ // The browser default premultiplies, so WebGL must not premultiply again
229
+ // except on devices whose default returns straight alpha
230
+ // (premultiplyAlphaHonored: false); there WebGL premultiplies on upload.
229
231
  return {
230
232
  data: await this.platform.createImageBitmap(blob),
231
- premultiplyAlpha: false,
233
+ premultiplyAlpha: useGlPremultiply,
232
234
  };
233
235
  }
234
236
 
@@ -481,6 +481,26 @@ export type RendererMainSettings = RendererRuntimeSettings & {
481
481
  */
482
482
  numImageWorkers: number;
483
483
 
484
+ /**
485
+ * Maximum number of image fetch+decode operations allowed to run at once
486
+ * when image workers are unavailable
487
+ *
488
+ * @remarks
489
+ * Only applies when there is no image worker manager (i.e.
490
+ * `numImageWorkers === 0`, or workers/`createImageBitmap` are unsupported).
491
+ * In that mode every image decode runs on the main thread, so a burst — such
492
+ * as a scroll that makes many image nodes renderable in a single tick — can
493
+ * fire dozens of decodes back-to-back and starve the render loop. This caps
494
+ * how many run concurrently; on-screen (priority) textures bypass the cap so
495
+ * they never wait behind off-screen prefetch.
496
+ *
497
+ * Has no effect when image workers are active — the worker pool already
498
+ * bounds concurrency by its size. Set to `0` to disable the cap (unbounded).
499
+ *
500
+ * @defaultValue `4`
501
+ */
502
+ imageDecodeConcurrency: number;
503
+
484
504
  /**
485
505
  * Renderer Engine
486
506
  *
@@ -610,13 +630,17 @@ export type RendererMainSettings = RendererRuntimeSettings & {
610
630
  * - Full - Supports createImageBitmap(image, sx, sy, sw, sh, options)
611
631
  *
612
632
  * Note with auto detection, the renderer will attempt to use the most advanced
613
- * version of the API available. If the API is not available, the renderer will
614
- * fall back to the next available version.
633
+ * version of the API available. If the API is not available (e.g. Chrome < 50,
634
+ * including the Chrome 38 support floor), the renderer falls back to loading
635
+ * images via `new Image()`.
615
636
  *
616
- * This will affect startup performance as the renderer will need to determine
617
- * the supported version of the API.
637
+ * `'auto'` runs a small startup probe (a 1x1 PNG) to determine support; this
638
+ * adds a negligible one-time startup cost. Set an explicit value (`'full'`,
639
+ * `'options'`, `'basic'`) to skip the probe ONLY when the target runtime is
640
+ * known to support that level — forcing a level the runtime lacks will fail
641
+ * to load images. When in doubt, use `'auto'`.
618
642
  *
619
- * @defaultValue `full`
643
+ * @defaultValue `auto`
620
644
  */
621
645
  createImageBitmapSupport: 'auto' | 'basic' | 'options' | 'full';
622
646
 
@@ -630,12 +654,16 @@ export type RendererMainSettings = RendererRuntimeSettings & {
630
654
  * ignore it, returning straight (non-premultiplied) alpha. This causes edge
631
655
  * "ghosting" on images with transparency.
632
656
  *
633
- * Set to `'auto'` to detect via a cheap startup probe (one 1×1 texture
634
- * upload + framebuffer readback). Set to a boolean to force the value. Leave
635
- * unset to assume the option is honored — the default, which preserves
636
- * existing behavior with no probe overhead.
657
+ * `'auto'` (the default) detects via a cheap startup probe (one 1×1 PNG
658
+ * decode + texture upload + framebuffer readback). On devices that honor the
659
+ * option the vast majority — the probe returns `true` and behavior is
660
+ * identical to assuming it honored; only devices that actually ignore it
661
+ * (e.g. the Movistar STB) switch to the WebGL-side premultiply fallback, so
662
+ * the change is invisible everywhere it isn't needed. Set to a boolean to
663
+ * force the value and skip the probe (`false` forces the GL-premultiply
664
+ * fallback; `true` skips it).
637
665
  *
638
- * @defaultValue `true` (assume honored; no probe)
666
+ * @defaultValue `'auto'` (probe once at startup)
639
667
  */
640
668
  premultiplyAlphaHonored?: boolean | 'auto';
641
669
 
@@ -781,6 +809,10 @@ export class RendererMain extends EventEmitter {
781
809
  textLayoutCacheSize: settings.textLayoutCacheSize ?? 250,
782
810
  numImageWorkers:
783
811
  settings.numImageWorkers !== undefined ? settings.numImageWorkers : 2,
812
+ imageDecodeConcurrency:
813
+ settings.imageDecodeConcurrency !== undefined
814
+ ? settings.imageDecodeConcurrency
815
+ : 4,
784
816
  enableContextSpy: settings.enableContextSpy ?? false,
785
817
  forceWebGL2: settings.forceWebGL2 ?? false,
786
818
  disableVertexArrayObject: settings.disableVertexArrayObject ?? false,
@@ -792,12 +824,12 @@ export class RendererMain extends EventEmitter {
792
824
  textBaselineMode: settings.textBaselineMode ?? 'optical',
793
825
  textureProcessingTimeLimit: settings.textureProcessingTimeLimit || 10,
794
826
  canvas: settings.canvas,
795
- createImageBitmapSupport: settings.createImageBitmapSupport || 'full',
796
- // undefined -> true (assume honored, no probe); 'auto' -> probe;
797
- // explicit boolean -> force the value.
827
+ createImageBitmapSupport: settings.createImageBitmapSupport || 'auto',
828
+ // undefined -> 'auto' (probe once at startup); 'auto' -> probe;
829
+ // explicit boolean -> force the value, skip the probe.
798
830
  premultiplyAlphaHonored:
799
831
  settings.premultiplyAlphaHonored === undefined
800
- ? true
832
+ ? 'auto'
801
833
  : settings.premultiplyAlphaHonored,
802
834
  platform: settings.platform || null,
803
835
  maxRetryCount: settings.maxRetryCount ?? 5,
@@ -854,6 +886,7 @@ export class RendererMain extends EventEmitter {
854
886
  fpsUpdateInterval: settings.fpsUpdateInterval!,
855
887
  enableClear: settings.enableClear!,
856
888
  numImageWorkers: settings.numImageWorkers!,
889
+ imageDecodeConcurrency: settings.imageDecodeConcurrency!,
857
890
  renderEngine: settings.renderEngine!,
858
891
  textureMemory: resolvedTxSettings,
859
892
  eventBus: this,