@solidtv/renderer 1.2.9 → 1.3.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.
Files changed (54) hide show
  1. package/dist/src/common/EventEmitter.d.ts +6 -0
  2. package/dist/src/common/EventEmitter.js +29 -11
  3. package/dist/src/common/EventEmitter.js.map +1 -1
  4. package/dist/src/core/CoreNode.d.ts +7 -0
  5. package/dist/src/core/CoreNode.js +30 -9
  6. package/dist/src/core/CoreNode.js.map +1 -1
  7. package/dist/src/core/CoreTextNode.js +3 -1
  8. package/dist/src/core/CoreTextNode.js.map +1 -1
  9. package/dist/src/core/CoreTextureManager.d.ts +10 -0
  10. package/dist/src/core/CoreTextureManager.js +38 -5
  11. package/dist/src/core/CoreTextureManager.js.map +1 -1
  12. package/dist/src/core/Stage.d.ts +19 -0
  13. package/dist/src/core/Stage.js +28 -1
  14. package/dist/src/core/Stage.js.map +1 -1
  15. package/dist/src/core/lib/ImageWorker.js +22 -6
  16. package/dist/src/core/lib/ImageWorker.js.map +1 -1
  17. package/dist/src/core/lib/textureSvg.js +4 -1
  18. package/dist/src/core/lib/textureSvg.js.map +1 -1
  19. package/dist/src/core/lib/validateImageBitmap.d.ts +18 -0
  20. package/dist/src/core/lib/validateImageBitmap.js +66 -0
  21. package/dist/src/core/lib/validateImageBitmap.js.map +1 -1
  22. package/dist/src/core/platforms/web/WebPlatform.js +6 -0
  23. package/dist/src/core/platforms/web/WebPlatform.js.map +1 -1
  24. package/dist/src/core/renderers/webgl/WebGlCtxTexture.js +5 -1
  25. package/dist/src/core/renderers/webgl/WebGlCtxTexture.js.map +1 -1
  26. package/dist/src/core/renderers/webgl/WebGlRenderer.d.ts +15 -0
  27. package/dist/src/core/renderers/webgl/WebGlRenderer.js +24 -0
  28. package/dist/src/core/renderers/webgl/WebGlRenderer.js.map +1 -1
  29. package/dist/src/core/textures/ImageTexture.js +21 -7
  30. package/dist/src/core/textures/ImageTexture.js.map +1 -1
  31. package/dist/src/main-api/Renderer.d.ts +41 -0
  32. package/dist/src/main-api/Renderer.js +8 -0
  33. package/dist/src/main-api/Renderer.js.map +1 -1
  34. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  35. package/dist/tsconfig.tsbuildinfo +1 -0
  36. package/package.json +1 -1
  37. package/src/common/EventEmitter.ts +29 -11
  38. package/src/core/CoreNode.test.ts +66 -0
  39. package/src/core/CoreNode.ts +32 -9
  40. package/src/core/CoreTextNode.test.ts +145 -0
  41. package/src/core/CoreTextNode.ts +3 -1
  42. package/src/core/CoreTextureManager.ts +61 -8
  43. package/src/core/Stage.contextLoss.test.ts +45 -0
  44. package/src/core/Stage.ts +30 -0
  45. package/src/core/lib/ImageWorker.ts +30 -6
  46. package/src/core/lib/textureSvg.ts +4 -1
  47. package/src/core/lib/validateImageBitmap.test.ts +119 -0
  48. package/src/core/lib/validateImageBitmap.ts +89 -0
  49. package/src/core/platforms/web/WebPlatform.contextLoss.test.ts +80 -0
  50. package/src/core/platforms/web/WebPlatform.ts +7 -0
  51. package/src/core/renderers/webgl/WebGlCtxTexture.ts +5 -1
  52. package/src/core/renderers/webgl/WebGlRenderer.ts +28 -0
  53. package/src/core/textures/ImageTexture.ts +25 -7
  54. package/src/main-api/Renderer.ts +50 -0
@@ -174,6 +174,8 @@ export class WebGlRenderer extends CoreRenderer {
174
174
  const glw = (this.glw = new WebGlContextWrapper(gl));
175
175
  glw.viewport(0, 0, options.canvas.width, options.canvas.height);
176
176
 
177
+ this.attachContextLossListeners(options.canvas);
178
+
177
179
  this.updateClearColor(this.stage.clearColor);
178
180
 
179
181
  glw.setBlend(true);
@@ -300,6 +302,32 @@ export class WebGlRenderer extends CoreRenderer {
300
302
  ]);
301
303
  }
302
304
 
305
+ /**
306
+ * Listen for WebGL context loss on the canvas.
307
+ *
308
+ * @remarks
309
+ * On low-RAM devices (e.g. Chromium 123+ after backgrounding) the GPU
310
+ * context is dropped, after which `gl.createTexture()` and friends return
311
+ * null and the engine would crash. We pause the render loop via the Stage
312
+ * flag and surface a `contextLost` event so consumers can react.
313
+ *
314
+ * We intentionally do NOT call `event.preventDefault()` (which would ask the
315
+ * browser to restore the context) and do NOT listen for
316
+ * `webglcontextrestored`: the engine cannot rebuild its GPU resources
317
+ * in-place, so the supported recovery is to reload the app. See BROWSERS.md.
318
+ */
319
+ private attachContextLossListeners(
320
+ canvas: HTMLCanvasElement | OffscreenCanvas,
321
+ ): void {
322
+ if ('addEventListener' in canvas === false) {
323
+ return;
324
+ }
325
+ const target = canvas as HTMLCanvasElement;
326
+ target.addEventListener('webglcontextlost', () => {
327
+ this.stage.setContextLost();
328
+ });
329
+ }
330
+
303
331
  reset() {
304
332
  const { glw } = this;
305
333
  if (DIRTY_QUAD_BUFFER) {
@@ -192,6 +192,18 @@ export class ImageTexture extends Texture {
192
192
  const hasAlphaChannel = premultiplyAlpha ?? blob.type.includes('image/png');
193
193
  const imageBitmapSupported = this.txManager.imageBitmapSupported;
194
194
 
195
+ // When the device does NOT honor the createImageBitmap premultiply option
196
+ // (e.g. older Safari), request a straight ('none') bitmap and let WebGL
197
+ // premultiply on upload instead. `premultiplyAlpha` in the returned
198
+ // TextureData means "WebGL should premultiply this source on upload".
199
+ const useGlPremultiply =
200
+ hasAlphaChannel === true &&
201
+ imageBitmapSupported.premultiplyHonored === false;
202
+ const bitmapMode: 'premultiply' | 'none' =
203
+ hasAlphaChannel === true && useGlPremultiply === false
204
+ ? 'premultiply'
205
+ : 'none';
206
+
195
207
  if (imageBitmapSupported.full === true && sw !== null && sh !== null) {
196
208
  // createImageBitmap with crop
197
209
  const bitmap = await this.platform.createImageBitmap(
@@ -201,28 +213,29 @@ export class ImageTexture extends Texture {
201
213
  sw,
202
214
  sh,
203
215
  {
204
- premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none',
216
+ premultiplyAlpha: bitmapMode,
205
217
  colorSpaceConversion: 'none',
206
218
  imageOrientation: 'none',
207
219
  },
208
220
  );
209
- return { data: bitmap, premultiplyAlpha: hasAlphaChannel };
221
+ return { data: bitmap, premultiplyAlpha: useGlPremultiply };
210
222
  } else if (imageBitmapSupported.basic === true) {
211
223
  // basic createImageBitmap without options or crop
212
- // this is supported for Chrome v50 to v52/54 that doesn't support options
224
+ // this is supported for Chrome v50 to v52/54 that doesn't support options.
225
+ // The browser default premultiplies, so WebGL must not premultiply again.
213
226
  return {
214
227
  data: await this.platform.createImageBitmap(blob),
215
- premultiplyAlpha: hasAlphaChannel,
228
+ premultiplyAlpha: false,
216
229
  };
217
230
  }
218
231
 
219
232
  // default createImageBitmap without crop but with options
220
233
  const bitmap = await this.platform.createImageBitmap(blob, {
221
- premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none',
234
+ premultiplyAlpha: bitmapMode,
222
235
  colorSpaceConversion: 'none',
223
236
  imageOrientation: 'none',
224
237
  });
225
- return { data: bitmap, premultiplyAlpha: hasAlphaChannel };
238
+ return { data: bitmap, premultiplyAlpha: useGlPremultiply };
226
239
  }
227
240
 
228
241
  async loadImage(src: string) {
@@ -274,9 +287,14 @@ export class ImageTexture extends Texture {
274
287
  };
275
288
  }
276
289
 
290
+ // The loader computes whether WebGL should premultiply this source on
291
+ // upload (it depends on the source type and on whether createImageBitmap
292
+ // honored the premultiply option). Preserve it; fall back to the prop only
293
+ // when the loader didn't decide.
277
294
  return {
278
295
  data: resp.data,
279
- premultiplyAlpha: this.props.premultiplyAlpha ?? true,
296
+ premultiplyAlpha:
297
+ resp.premultiplyAlpha ?? this.props.premultiplyAlpha ?? true,
280
298
  };
281
299
  }
282
300
 
@@ -145,6 +145,28 @@ export interface RendererMainCriticalCleanupFailedEvent {
145
145
  criticalThreshold: number;
146
146
  }
147
147
 
148
+ /**
149
+ * WebGL Context Lost Event Data
150
+ *
151
+ * @remarks
152
+ * Fired when the underlying WebGL context is lost (e.g. on low-RAM devices
153
+ * running Chromium 123+ after the app has been backgrounded). The render loop
154
+ * stops; in-engine GL resources are NOT rebuilt, so the supported handling is
155
+ * to reload the app.
156
+ *
157
+ * @category Events
158
+ * @example
159
+ * ```typescript
160
+ * renderer.on('contextLost', () => {
161
+ * window.location.reload();
162
+ * });
163
+ * ```
164
+ */
165
+ export interface RendererMainContextLostEvent {
166
+ /** This event has no payload - listen without parameters */
167
+ readonly __eventHasNoPayload?: never;
168
+ }
169
+
148
170
  /**
149
171
  * Settings for the Renderer that can be updated during runtime.
150
172
  */
@@ -436,6 +458,25 @@ export type RendererMainSettings = RendererRuntimeSettings & {
436
458
  */
437
459
  createImageBitmapSupport: 'auto' | 'basic' | 'options' | 'full';
438
460
 
461
+ /**
462
+ * Override for whether `createImageBitmap(..., { premultiplyAlpha: 'premultiply' })`
463
+ * is actually honored by the target device.
464
+ *
465
+ * @remarks
466
+ * Some older browsers (notably older Safari/WebKit) accept the
467
+ * `premultiplyAlpha: 'premultiply'` option without throwing but silently
468
+ * ignore it, returning straight (non-premultiplied) alpha. This causes edge
469
+ * "ghosting" on images with transparency.
470
+ *
471
+ * Set to `'auto'` to detect via a cheap startup probe (one 1×1 texture
472
+ * upload + framebuffer readback). Set to a boolean to force the value. Leave
473
+ * unset to assume the option is honored — the default, which preserves
474
+ * existing behavior with no probe overhead.
475
+ *
476
+ * @defaultValue `true` (assume honored; no probe)
477
+ */
478
+ premultiplyAlphaHonored?: boolean | 'auto';
479
+
439
480
  /**
440
481
  * Provide an alternative platform abstraction layer
441
482
  *
@@ -506,6 +547,7 @@ export type RendererMainSettings = RendererRuntimeSettings & {
506
547
  * @see {@link RendererMainIdleEvent}
507
548
  * @see {@link RendererMainCriticalCleanupEvent}
508
549
  * @see {@link RendererMainCriticalCleanupFailedEvent}
550
+ * @see {@link RendererMainContextLostEvent}
509
551
  *
510
552
  * @fires RendererMain#fpsUpdate
511
553
  * @fires RendererMain#frameTick
@@ -513,6 +555,7 @@ export type RendererMainSettings = RendererRuntimeSettings & {
513
555
  * @fires RendererMain#idle
514
556
  * @fires RendererMain#criticalCleanup
515
557
  * @fires RendererMain#criticalCleanupFailed
558
+ * @fires RendererMain#contextLost
516
559
  */
517
560
  export class RendererMain extends EventEmitter {
518
561
  readonly root: INode;
@@ -562,6 +605,12 @@ export class RendererMain extends EventEmitter {
562
605
  textureProcessingTimeLimit: settings.textureProcessingTimeLimit || 10,
563
606
  canvas: settings.canvas,
564
607
  createImageBitmapSupport: settings.createImageBitmapSupport || 'full',
608
+ // undefined -> true (assume honored, no probe); 'auto' -> probe;
609
+ // explicit boolean -> force the value.
610
+ premultiplyAlphaHonored:
611
+ settings.premultiplyAlphaHonored === undefined
612
+ ? true
613
+ : settings.premultiplyAlphaHonored,
565
614
  platform: settings.platform || null,
566
615
  maxRetryCount: settings.maxRetryCount ?? 5,
567
616
  };
@@ -622,6 +671,7 @@ export class RendererMain extends EventEmitter {
622
671
  targetFPS: settings.targetFPS!,
623
672
  textureProcessingTimeLimit: settings.textureProcessingTimeLimit!,
624
673
  createImageBitmapSupport: settings.createImageBitmapSupport!,
674
+ premultiplyAlphaHonored: settings.premultiplyAlphaHonored,
625
675
  platform,
626
676
  maxRetryCount: settings.maxRetryCount ?? 5,
627
677
  });