Skip to content

Commit ce8da66

Browse files
authored
Feat: Extend platform abstraction (#746)
Move Image creation and GL wrapper to Platform. This PR changes: * `createImageBitmap` support to `Platform` * Image fetch to `Platform` (by default using `XMLHttpRequest`) * `ImageWorker` handling to `Platform` * `GLWrapper` to `Platform` * Similar, support for TX compression and SVG handling has been moved to `Platform` Removed: * `createImageBitmap` auto detection - this needs to be done by Platform * Image Fallback handling - this is done by Platform **NOTE: BREAKING CHANGES** This requires a different Platform import / init to make it work. The concept is you create a Platform that matches the functionality the platform exposes. I.e. if the Platform does not support `createImageBitmap` or a less version of `createImageBitmap` you should extend the platform to match that. @estobbart for ION you can create or upstream an ION specific platform (or we can create one) that: * Replace `loadImage` and `createImage` in Platform (possibly `Fetch` too?) * Replace the respective GL function Todo: - [x] Expose platforms in exports - [x] Create Legacy Platform for non-`createImageBitmap` platform - [x] Create Chrome 50 `createImageBitmap` platform - [x] Create `fetch` supported platform
2 parents 6c31969 + c0f9eeb commit ce8da66

36 files changed

+1405
-851
lines changed

README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,99 @@ Any JavaScript language features or browser APIs that cannot be automatically tr
4545

4646
For a more detailed and comprehensive list of browsers and their features please see [browsers](./BROWSERS.md).
4747

48+
## Platform Architecture
49+
50+
The Lightning 3 Renderer uses a modular platform architecture that allows it to adapt to different browser capabilities and environments. This enables optimal performance across a wide range of devices, from modern browsers to legacy embedded systems.
51+
52+
### Available Platforms
53+
54+
The Renderer includes several platform implementations, each tailored for specific browser capabilities:
55+
56+
#### WebPlatform (Default)
57+
58+
The standard platform for modern browsers with full WebGL and createImageBitmap support.
59+
60+
- Uses XMLHttpRequest for loading resources
61+
- Full createImageBitmap API with options (premultiplyAlpha, colorSpaceConversion, imageOrientation)
62+
- Supports image cropping
63+
- Multi-threaded image processing via Web Workers
64+
- **Use case:** Modern browsers, default choice for most applications
65+
66+
#### WebPlatformNext
67+
68+
Platform using the modern Fetch API instead of XMLHttpRequest.
69+
70+
- Uses Fetch API for loading resources (promise-based, Service Worker compatible)
71+
- Full createImageBitmap API with options
72+
- Supports image cropping
73+
- Multi-threaded image processing via Web Workers
74+
- **Use case:** Modern browsers where Fetch API is preferred, progressive web apps
75+
76+
#### WebPlatformChrome50
77+
78+
Compatibility platform for browsers with limited createImageBitmap support (Chrome 50-51).
79+
80+
- Uses XMLHttpRequest for loading resources
81+
- Limited createImageBitmap API (no options or cropping parameters)
82+
- Multi-threaded image processing via Web Workers (if enabled)
83+
- **Use case:** Chrome 50-51, browsers with basic createImageBitmap support
84+
85+
#### WebPlatformLegacy
86+
87+
Legacy platform for older browsers without createImageBitmap support.
88+
89+
- Uses direct Image element loading (no blob conversion for URLs)
90+
- HTMLImageElement instead of createImageBitmap
91+
- Single-threaded image processing (no Web Workers)
92+
- No image cropping support
93+
- No compressed texture support
94+
- **Use case:** Chrome 38-49, older embedded device browsers
95+
96+
### Using a Platform
97+
98+
Platforms can be specified when initializing the Renderer:
99+
100+
```ts
101+
import { RendererMain } from '@lightningjs/renderer';
102+
import { WebGlRenderer } from '@lightningjs/renderer/webgl';
103+
import {
104+
WebPlatform,
105+
WebPlatformNext,
106+
WebPlatformChrome50,
107+
WebPlatformLegacy,
108+
} from '@lightningjs/renderer/platforms';
109+
110+
const renderer = new RendererMain(
111+
{
112+
appWidth: 1920,
113+
appHeight: 1080,
114+
renderEngine: WebGlRenderer,
115+
platform: WebPlatformLegacy, // Use legacy platform for older browsers
116+
// ...Other Renderer Config
117+
},
118+
'app',
119+
);
120+
```
121+
122+
**Note:** `WebPlatformLegacy` automatically sets `numImageWorkers` to 0 since it doesn't support Web Workers.
123+
124+
### Creating Custom Platforms
125+
126+
You can create your own custom platform tailored to your specific device or environment by extending the `Platform` base class or one of the existing platform implementations.
127+
128+
**Key methods you can override:**
129+
130+
- `fetch(url: string): Promise<Blob>` - Resource loading
131+
- `createImage(...)` - Image bitmap creation
132+
- `loadImage(...)` - Complete image loading pipeline
133+
- `loadSvg(...)` - SVG loading
134+
- `loadCompressedTexture(...)` - Compressed texture loading
135+
- `createCanvas()` - Canvas element creation
136+
- `createContext()` - WebGL context creation
137+
- `startLoop(stage)` - Animation loop implementation
138+
139+
This allows you to optimize the Renderer for proprietary platforms, embedded systems, or environments with unique capabilities and constraints.
140+
48141
## Example Tests
49142

50143
The Example Tests sub-project define a set of tests for various Renderer

examples/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ pnpm watch
4141
- `driver` (string, default: "main")
4242
- Core driver to use
4343
- Either `main` or `threadx`
44+
- `platform` (string, default: "web")
45+
- Platform implementation to use
46+
- Options: `web` (XMLHttpRequest + createImageBitmap with full options), `next` (Fetch API + createImageBitmap with full options), `chrome50` (limited createImageBitmap, no options/cropping), `legacy` (HTMLImageElement, no createImageBitmap)
4447
- `overlay` (boolean, default: "true")
4548
- Whether or not to show the text overlay in the bottom-right corner that
4649
displays the current test and driver being used.

examples/index.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ import {
2929
CanvasTextRenderer,
3030
} from '@lightningjs/renderer/canvas';
3131

32+
import {
33+
WebPlatform,
34+
WebPlatformNext,
35+
WebPlatformChrome50,
36+
WebPlatformLegacy,
37+
} from '@lightningjs/renderer/platforms';
3238
import { Inspector } from '@lightningjs/renderer/inspector';
3339
import { assertTruthy } from '@lightningjs/renderer/utils';
3440

@@ -71,6 +77,13 @@ const appHeight = 1080;
7177
const defaultResolution = 720;
7278
const defaultPhysicalPixelRatio = 1;
7379

80+
const platformMap = {
81+
web: WebPlatform,
82+
next: WebPlatformNext,
83+
chrome50: WebPlatformChrome50,
84+
legacy: WebPlatformLegacy,
85+
};
86+
7487
(async () => {
7588
// See README.md for details on the supported URL params
7689
const urlParams = new URLSearchParams(window.location.search);
@@ -91,6 +104,7 @@ const defaultPhysicalPixelRatio = 1;
91104
const textureProcessingLimit =
92105
Number(urlParams.get('textureProcessingLimit')) || 0;
93106
const globalTargetFPS = Number(urlParams.get('targetFPS')) || undefined;
107+
const platform = urlParams.get('platform') || 'web';
94108

95109
const physicalPixelRatio =
96110
Number(urlParams.get('ppr')) || defaultPhysicalPixelRatio;
@@ -105,6 +119,7 @@ const defaultPhysicalPixelRatio = 1;
105119
await runTest(
106120
test,
107121
renderMode,
122+
platform,
108123
urlParams,
109124
showOverlay,
110125
showMemMonitor,
@@ -121,14 +136,15 @@ const defaultPhysicalPixelRatio = 1;
121136
return;
122137
}
123138
assertTruthy(automation);
124-
await runAutomation(renderMode, test, logFps);
139+
await runAutomation(renderMode, platform, test, logFps);
125140
})().catch((err) => {
126141
console.error(err);
127142
});
128143

129144
async function runTest(
130145
test: string,
131146
renderMode: string,
147+
platform: string,
132148
urlParams: URLSearchParams,
133149
showOverlay: boolean,
134150
showMemMonitor: boolean,
@@ -158,6 +174,7 @@ async function runTest(
158174

159175
const { renderer, appElement } = await initRenderer(
160176
renderMode,
177+
platform,
161178
logFps,
162179
enableContextSpy,
163180
logicalPixelRatio,
@@ -233,6 +250,7 @@ async function runTest(
233250

234251
async function initRenderer(
235252
renderMode: string,
253+
platform: string,
236254
logFps: boolean,
237255
enableContextSpy: boolean,
238256
logicalPixelRatio: number,
@@ -256,6 +274,7 @@ async function initRenderer(
256274
enableContextSpy,
257275
forceWebGL2,
258276
inspector,
277+
platform: platformMap[platform] || WebPlatform,
259278
renderEngine: renderMode === 'webgl' ? WebGlRenderer : CanvasRenderer,
260279
fontEngines: [SdfTextRenderer, CanvasTextRenderer],
261280
textureProcessingTimeLimit: textureProcessingTimeLimit,
@@ -365,12 +384,14 @@ function wildcardMatch(string: string, wildcardString: string) {
365384

366385
async function runAutomation(
367386
renderMode: string,
387+
platform: string,
368388
filter: string | null,
369389
logFps: boolean,
370390
) {
371391
const logicalPixelRatio = defaultResolution / appHeight;
372392
const { renderer, appElement } = await initRenderer(
373393
renderMode,
394+
platform,
374395
logFps,
375396
false,
376397
logicalPixelRatio,

exports/platform.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* If not stated otherwise in this file or this component's LICENSE file the
3+
* following copyright and licenses apply:
4+
*
5+
* Copyright 2026 Comcast Cable Communications Management, LLC.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the License);
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
export {
21+
Platform,
22+
type PlatformSettings,
23+
} from '../src/core/platforms/Platform.js';
24+
export { WebPlatform } from '../src/core/platforms/web/WebPlatform.js';
25+
export { WebPlatformNext } from '../src/core/platforms/web/WebPlatformNext.js';
26+
export { WebPlatformChrome50 } from '../src/core/platforms/web/WebPlatformChrome50.js';
27+
export { WebPlatformLegacy } from '../src/core/platforms/web/WebPlatformLegacy.js';
28+
29+
// GL Context Wrappers
30+
export { GlContextWrapper } from '../src/core/platforms/GlContextWrapper.js';
31+
export { WebGlContextWrapper } from '../src/core/platforms/web/WebGlContextWrapper.js';

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"./canvas/shaders": "./dist/exports/canvas-shaders.js",
1212
"./webgl": "./dist/exports/webgl.js",
1313
"./webgl/shaders": "./dist/exports/webgl-shaders.js",
14-
"./inspector": "./dist/exports/inspector.js"
14+
"./inspector": "./dist/exports/inspector.js",
15+
"./platforms": "./dist/exports/platform.js"
1516
},
1617
"scripts": {
1718
"start": "cd examples && pnpm start",

src/core/CoreNode.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2727,7 +2727,8 @@ export class CoreNode extends EventEmitter {
27272727
}
27282728

27292729
draw(renderer: WebGlRenderer) {
2730-
const { glw, options, stage } = renderer;
2730+
const { glw, stage } = renderer;
2731+
const canvas = stage.platform!.canvas!;
27312732
const shader = this.props.shader as any;
27322733

27332734
stage.shManager.useShader(shader.program);
@@ -2741,7 +2742,7 @@ export class CoreNode extends EventEmitter {
27412742
const clipWidth = Math.round(this.clippingRect.w * pixelRatio);
27422743
const clipHeight = Math.round(this.clippingRect.h * pixelRatio);
27432744
let clipY = Math.round(
2744-
options.canvas.height - clipHeight - this.clippingRect.y * pixelRatio,
2745+
canvas.height - clipHeight - this.clippingRect.y * pixelRatio,
27452746
);
27462747
// if parent has render texture, we need to adjust the scissor rect
27472748
// to be relative to the parent's framebuffer

0 commit comments

Comments
 (0)