@solidtv/renderer 1.6.1 → 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 (28) hide show
  1. package/dist/src/core/lib/ImageWorker.js +29 -20
  2. package/dist/src/core/lib/ImageWorker.js.map +1 -1
  3. package/dist/src/core/lib/validateImageBitmap.d.ts +13 -7
  4. package/dist/src/core/lib/validateImageBitmap.js +31 -10
  5. package/dist/src/core/lib/validateImageBitmap.js.map +1 -1
  6. package/dist/src/core/text-rendering/CanvasFontHandler.js +30 -15
  7. package/dist/src/core/text-rendering/CanvasFontHandler.js.map +1 -1
  8. package/dist/src/core/text-rendering/SdfFontHandler.js +52 -23
  9. package/dist/src/core/text-rendering/SdfFontHandler.js.map +1 -1
  10. package/dist/src/core/text-rendering/TextRenderer.d.ts +10 -1
  11. package/dist/src/core/textures/ImageTexture.js +4 -2
  12. package/dist/src/core/textures/ImageTexture.js.map +1 -1
  13. package/dist/src/main-api/Renderer.d.ts +10 -6
  14. package/dist/src/main-api/Renderer.js +3 -3
  15. package/dist/src/main-api/Renderer.js.map +1 -1
  16. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  17. package/package.json +1 -1
  18. package/src/core/lib/ImageWorker.test.ts +19 -0
  19. package/src/core/lib/ImageWorker.ts +34 -40
  20. package/src/core/lib/validateImageBitmap.test.ts +17 -10
  21. package/src/core/lib/validateImageBitmap.ts +32 -14
  22. package/src/core/text-rendering/CanvasFontHandler.ts +36 -16
  23. package/src/core/text-rendering/SdfFontHandler.ts +55 -25
  24. package/src/core/text-rendering/TextRenderer.ts +10 -1
  25. package/src/core/text-rendering/tests/SdfFontHandler.test.ts +65 -1
  26. package/src/core/textures/ImageTexture.test.ts +94 -26
  27. package/src/core/textures/ImageTexture.ts +4 -2
  28. package/src/main-api/Renderer.ts +13 -9
@@ -65,37 +65,57 @@ export const loadFont = (
65
65
  ): Promise<void> => {
66
66
  const { fontFamily, fontUrl, metrics } = options;
67
67
 
68
+ // A single load may register the font under several names. The first entry
69
+ // is the primary name used to key the in-flight promise; the rest are
70
+ // aliases registered against the same source URL (the browser dedupes the
71
+ // download by URL).
72
+ const names = Array.isArray(fontFamily) ? fontFamily : [fontFamily];
73
+ const primary = names[0]!;
74
+
68
75
  // If already loaded, return immediately
69
- if (fontCache.has(fontFamily) === true) {
76
+ if (fontCache.has(primary) === true) {
70
77
  return Promise.resolve();
71
78
  }
72
79
 
73
- const existingPromise = fontLoadPromises.get(fontFamily);
80
+ const existingPromise = fontLoadPromises.get(primary);
74
81
  // If already loading, return the existing promise
75
82
  if (existingPromise !== undefined) {
76
83
  return existingPromise;
77
84
  }
78
85
 
79
- const nwff: CoreTextNode[] = (nodesWaitingForFont[fontFamily] = []);
80
- // Create and store the loading promise
81
- const loadPromise = new FontFace(fontFamily, `url(${fontUrl})`)
82
- .load()
83
- .then((loadedFont) => {
84
- stage.platform.addFont(loadedFont);
85
- processFontData(fontFamily, loadedFont, metrics);
86
- fontLoadPromises.delete(fontFamily);
87
- for (let key in nwff) {
88
- nwff[key]!.setUpdateType(UpdateType.Local);
86
+ for (let i = 0; i < names.length; i++) {
87
+ nodesWaitingForFont[names[i]!] = [];
88
+ }
89
+ // Register a FontFace under every name (all share the same source URL) and
90
+ // wait for all of them to be ready before waking parked nodes.
91
+ const loadPromise = Promise.all(
92
+ names.map((name) =>
93
+ new FontFace(name, `url(${fontUrl})`).load().then((loadedFont) => {
94
+ stage.platform.addFont(loadedFont);
95
+ processFontData(name, loadedFont, metrics);
96
+ }),
97
+ ),
98
+ )
99
+ .then(() => {
100
+ fontLoadPromises.delete(primary);
101
+ for (let i = 0; i < names.length; i++) {
102
+ const name = names[i]!;
103
+ const nwff = nodesWaitingForFont[name];
104
+ if (nwff !== undefined) {
105
+ for (let key in nwff) {
106
+ nwff[key]!.setUpdateType(UpdateType.Local);
107
+ }
108
+ delete nodesWaitingForFont[name];
109
+ }
89
110
  }
90
- delete nodesWaitingForFont[fontFamily];
91
111
  })
92
112
  .catch((error) => {
93
- fontLoadPromises.delete(fontFamily);
94
- console.error(`Failed to load font: ${fontFamily}`, error);
113
+ fontLoadPromises.delete(primary);
114
+ console.error(`Failed to load font: ${primary}`, error);
95
115
  throw error;
96
116
  });
97
117
 
98
- fontLoadPromises.set(fontFamily, loadPromise);
118
+ fontLoadPromises.set(primary, loadPromise);
99
119
  return loadPromise;
100
120
  };
101
121
 
@@ -309,30 +309,39 @@ export const loadFont = (
309
309
  options: FontLoadOptions,
310
310
  ): Promise<void> => {
311
311
  const { fontFamily, atlasUrl, atlasDataUrl, metrics } = options;
312
+ // A single load may register the font under several names (the atlas is
313
+ // fetched once and shared). The first entry is the primary name used to key
314
+ // the in-flight promise and drive the fetch; the rest are aliases.
315
+ const names = Array.isArray(fontFamily) ? fontFamily : [fontFamily];
316
+ const primary = names[0]!;
317
+
312
318
  // Early return if already loaded
313
- if (fontCache.get(fontFamily) !== undefined) {
319
+ if (fontCache.get(primary) !== undefined) {
314
320
  return Promise.resolve();
315
321
  }
316
322
 
317
323
  // Early return if already loading
318
- const existingPromise = fontLoadPromises.get(fontFamily);
324
+ const existingPromise = fontLoadPromises.get(primary);
319
325
  if (existingPromise !== undefined) {
320
326
  return existingPromise;
321
327
  }
322
328
 
323
329
  if (atlasDataUrl === undefined) {
324
330
  return Promise.reject(
325
- new Error(`Atlas data URL must be provided for SDF font: ${fontFamily}`),
331
+ new Error(`Atlas data URL must be provided for SDF font: ${primary}`),
326
332
  );
327
333
  }
328
334
 
329
- // Reuse an existing waiter list. A previous load attempt for this font may
330
- // have failed and left nodes parked here; overwriting the list would strand
331
- // them, so a successful retry could never wake them. The list is consumed
332
- // (and deleted) on the next successful load.
333
- let nwff = nodesWaitingForFont[fontFamily];
334
- if (nwff === undefined) {
335
- nwff = nodesWaitingForFont[fontFamily] = [];
335
+ // Ensure every name has a waiter list so nodes requesting an alias can park
336
+ // and be woken on load. Reuse existing lists a previous load attempt for a
337
+ // name may have failed and left nodes parked there; overwriting the list
338
+ // would strand them, so a successful retry could never wake them. Lists are
339
+ // consumed (and deleted) on the next successful load.
340
+ for (let i = 0; i < names.length; i++) {
341
+ const name = names[i]!;
342
+ if (nodesWaitingForFont[name] === undefined) {
343
+ nodesWaitingForFont[name] = [];
344
+ }
336
345
  }
337
346
  // One attempt at fetching + decoding the JSON atlas description. A fresh
338
347
  // XHR runs per attempt so a transient network/parse failure can recover.
@@ -390,17 +399,34 @@ export const loadFont = (
390
399
  premultiplyAlpha: false,
391
400
  });
392
401
 
393
- atlasTexture.setRenderableOwner(fontFamily, true);
402
+ // Register every name as a renderable owner so the shared atlas stays
403
+ // alive as long as any of its names is in use.
404
+ for (let i = 0; i < names.length; i++) {
405
+ atlasTexture.setRenderableOwner(names[i]!, true);
406
+ }
394
407
  atlasTexture.preventCleanup = true; // Prevent automatic cleanup
395
408
 
396
409
  const onLoaded = () => {
397
- // Process and cache font data
398
- processFontData(fontFamily, fontData, atlasTexture, metrics);
410
+ // Process and cache font data under the primary name...
411
+ processFontData(primary, fontData, atlasTexture, metrics);
412
+ // ...then alias the remaining names onto the same cache entry so they
413
+ // resolve to the identical glyph/kerning/atlas data.
414
+ const cached = fontCache.get(primary)!;
415
+ for (let i = 1; i < names.length; i++) {
416
+ fontCache.set(names[i]!, cached);
417
+ }
399
418
 
400
- for (let key in nwff) {
401
- nwff[key]!.setUpdateType(UpdateType.Local);
419
+ // Wake every parked node across all names and clear their lists.
420
+ for (let i = 0; i < names.length; i++) {
421
+ const name = names[i]!;
422
+ const list = nodesWaitingForFont[name];
423
+ if (list !== undefined) {
424
+ for (let key in list) {
425
+ list[key]!.setUpdateType(UpdateType.Local);
426
+ }
427
+ delete nodesWaitingForFont[name];
428
+ }
402
429
  }
403
- delete nodesWaitingForFont[fontFamily];
404
430
  resolve();
405
431
  };
406
432
 
@@ -419,7 +445,9 @@ export const loadFont = (
419
445
  atlasTexture.on('failed', (_target, error: TextureError) => {
420
446
  // Drop the failed atlas so a retry builds a fresh texture rather than
421
447
  // getting this dead instance back from the createTexture key-cache.
422
- atlasTexture.setRenderableOwner(fontFamily, false);
448
+ for (let i = 0; i < names.length; i++) {
449
+ atlasTexture.setRenderableOwner(names[i]!, false);
450
+ }
423
451
  stage.txManager.removeTextureFromCache(atlasTexture);
424
452
  reject(error);
425
453
  });
@@ -434,15 +462,15 @@ export const loadFont = (
434
462
  await loadAtlas(await fetchFontData());
435
463
  // Success: clear the in-flight marker (the font now lives in fontCache)
436
464
  // — parked nodes were already woken inside loadAtlas.
437
- fontLoadPromises.delete(fontFamily);
465
+ fontLoadPromises.delete(primary);
438
466
  return;
439
467
  } catch (error) {
440
468
  lastError = error;
441
469
  if (attempt < MAX_FONT_LOAD_RETRIES) {
442
470
  console.warn(
443
- `SDF font "${fontFamily}" failed to load (attempt ${
444
- attempt + 1
445
- } of ${MAX_FONT_LOAD_RETRIES + 1}), retrying.`,
471
+ `SDF font "${primary}" failed to load (attempt ${attempt + 1} of ${
472
+ MAX_FONT_LOAD_RETRIES + 1
473
+ }), retrying.`,
446
474
  error,
447
475
  );
448
476
  }
@@ -455,13 +483,15 @@ export const loadFont = (
455
483
  // loadFont() (which reuses the list) can still wake them if the font
456
484
  // eventually loads. The list shrinks as nodes self-remove via
457
485
  // stopWaitingForFont on destroy.
458
- fontLoadPromises.delete(fontFamily);
459
- fontCache.delete(fontFamily);
460
- console.error(`Failed to load SDF font: ${fontFamily}`, lastError);
486
+ fontLoadPromises.delete(primary);
487
+ for (let i = 0; i < names.length; i++) {
488
+ fontCache.delete(names[i]!);
489
+ }
490
+ console.error(`Failed to load SDF font: ${primary}`, lastError);
461
491
  throw lastError;
462
492
  })();
463
493
 
464
- fontLoadPromises.set(fontFamily, loadPromise);
494
+ fontLoadPromises.set(primary, loadPromise);
465
495
  return loadPromise;
466
496
  };
467
497
 
@@ -349,7 +349,16 @@ export interface TextLayout {
349
349
  }
350
350
 
351
351
  export interface FontLoadOptions {
352
- fontFamily: string;
352
+ /**
353
+ * Font family name to register the font under.
354
+ *
355
+ * Pass an array to register the same font under several names in a single
356
+ * load — the atlas/font resource is fetched once and every name resolves to
357
+ * the same loaded font. The first entry is the primary name; the rest are
358
+ * aliases. Useful when one physical font is referenced by multiple families
359
+ * (e.g. `['Roboto', 'Roboto500']`).
360
+ */
361
+ fontFamily: string | string[];
353
362
  metrics?: FontMetrics;
354
363
  // For Canvas/traditional font loading
355
364
  fontUrl?: string;
@@ -3,6 +3,7 @@ import {
3
3
  loadFont,
4
4
  waitingForFont,
5
5
  isFontLoaded,
6
+ getFontData,
6
7
  MAX_FONT_LOAD_RETRIES,
7
8
  } from '../SdfFontHandler.js';
8
9
  import { EventEmitter } from '../../../common/EventEmitter.js';
@@ -71,7 +72,7 @@ function makeStage(): { stage: Stage; textures: FakeTexture[] } {
71
72
  return { stage, textures };
72
73
  }
73
74
 
74
- const opts = (fontFamily: string) =>
75
+ const opts = (fontFamily: string | string[]) =>
75
76
  ({
76
77
  fontFamily,
77
78
  atlasUrl: 'atlas.png',
@@ -256,3 +257,66 @@ describe('SdfFontHandler loadFont — automatic retry', () => {
256
257
  expect(node.setUpdateType).toHaveBeenCalledTimes(1);
257
258
  });
258
259
  });
260
+
261
+ describe('SdfFontHandler loadFont — multiple names for one font', () => {
262
+ let originalXHR: unknown;
263
+
264
+ beforeEach(() => {
265
+ originalXHR = (globalThis as unknown as { XMLHttpRequest: unknown })
266
+ .XMLHttpRequest;
267
+ (globalThis as unknown as { XMLHttpRequest: unknown }).XMLHttpRequest =
268
+ FakeXHR;
269
+ });
270
+
271
+ afterEach(() => {
272
+ (globalThis as unknown as { XMLHttpRequest: unknown }).XMLHttpRequest =
273
+ originalXHR;
274
+ });
275
+
276
+ it('fetches once and registers every name against the same font data', async () => {
277
+ const { stage, textures } = makeStage();
278
+ const names = ['Roboto', 'Roboto500', 'RobotoMedium'];
279
+
280
+ const promise = loadFont(stage, opts(names));
281
+ await flush();
282
+ textures[0]!.emit('loaded');
283
+ await promise;
284
+
285
+ // A single atlas texture was created for all three names.
286
+ expect(textures.length).toBe(1);
287
+
288
+ // Every name reports loaded and resolves to the identical cache entry.
289
+ const primaryData = getFontData(names[0]!);
290
+ expect(primaryData).not.toBe(undefined);
291
+ for (let i = 0; i < names.length; i++) {
292
+ expect(isFontLoaded(names[i]!)).toBe(true);
293
+ expect(getFontData(names[i]!)).toBe(primaryData);
294
+ }
295
+ });
296
+
297
+ it('wakes nodes parked under an alias, not just the primary name', async () => {
298
+ const { stage, textures } = makeStage();
299
+ const names = ['MultiWakePrimary', 'MultiWakeAlias'];
300
+
301
+ const promise = loadFont(stage, opts(names));
302
+
303
+ // Park one node under the primary name and one under the alias.
304
+ const primaryNode = { id: 1, setUpdateType: vi.fn() };
305
+ const aliasNode = { id: 2, setUpdateType: vi.fn() };
306
+ waitingForFont(
307
+ names[0]!,
308
+ primaryNode as unknown as Parameters<typeof waitingForFont>[1],
309
+ );
310
+ waitingForFont(
311
+ names[1]!,
312
+ aliasNode as unknown as Parameters<typeof waitingForFont>[1],
313
+ );
314
+
315
+ await flush();
316
+ textures[0]!.emit('loaded');
317
+ await promise;
318
+
319
+ expect(primaryNode.setUpdateType).toHaveBeenCalledTimes(1);
320
+ expect(aliasNode.setUpdateType).toHaveBeenCalledTimes(1);
321
+ });
322
+ });
@@ -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
 
@@ -654,12 +654,16 @@ export type RendererMainSettings = RendererRuntimeSettings & {
654
654
  * ignore it, returning straight (non-premultiplied) alpha. This causes edge
655
655
  * "ghosting" on images with transparency.
656
656
  *
657
- * Set to `'auto'` to detect via a cheap startup probe (one 1×1 texture
658
- * upload + framebuffer readback). Set to a boolean to force the value. Leave
659
- * unset to assume the option is honored — the default, which preserves
660
- * existing behavior with no probe overhead.
661
- *
662
- * @defaultValue `true` (assume honored; no probe)
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).
665
+ *
666
+ * @defaultValue `'auto'` (probe once at startup)
663
667
  */
664
668
  premultiplyAlphaHonored?: boolean | 'auto';
665
669
 
@@ -821,11 +825,11 @@ export class RendererMain extends EventEmitter {
821
825
  textureProcessingTimeLimit: settings.textureProcessingTimeLimit || 10,
822
826
  canvas: settings.canvas,
823
827
  createImageBitmapSupport: settings.createImageBitmapSupport || 'auto',
824
- // undefined -> true (assume honored, no probe); 'auto' -> probe;
825
- // explicit boolean -> force the value.
828
+ // undefined -> 'auto' (probe once at startup); 'auto' -> probe;
829
+ // explicit boolean -> force the value, skip the probe.
826
830
  premultiplyAlphaHonored:
827
831
  settings.premultiplyAlphaHonored === undefined
828
- ? true
832
+ ? 'auto'
829
833
  : settings.premultiplyAlphaHonored,
830
834
  platform: settings.platform || null,
831
835
  maxRetryCount: settings.maxRetryCount ?? 5,