Skip to content

Commit

Permalink
Fixes for Chrome v38 (#456)
Browse files Browse the repository at this point in the history
Fixes for handling base64 src strings on Chrome v38, rework Image
texture fallback & detection.

This PR includes:
* Using a 1x1 PNG pixel to determine which `createImageBitmap` signature
is supported on the browser
* Fallback the image worker and main thread `ImageTexture` handling
according to the detected signature
* Support for base64 `src` on Chrome v38
* Support for `createImageBitmap(blob)` without a config object for
Chrome v50 (linked to #454)
* Support for ES5 legacy output of the `examples` directory (will be
published on https://lightning-js.github.io/renderer/ automatically)
* Upgrade `vite` and `vitest` to 2.x devDependencies, pin to TS 5.6.x
for now (as 5.7.x has issues with other dependencies)
* Upgrade Playwright 1.39 to 1.49 for automated tests
* Refactor Visual Regression test pixel matching and snapshot handling
by using [pixelmatch](https://www.npmjs.com/package/pixelmatch) and
[pngjs](https://www.npmjs.com/package/pngjs) over a homebrew detection +
upng (which is stale).
* SDF font initialization waits for image support detection before
uploading the SDF texture atlas.
* Added documentation on the fallback process and references to chrome
versions in `BROWSERS.md`
  • Loading branch information
wouterlucas authored Nov 27, 2024
2 parents 89ae392 + 095cc08 commit 2b3a866
Show file tree
Hide file tree
Showing 22 changed files with 3,757 additions and 1,674 deletions.
14 changes: 0 additions & 14 deletions .eslintignore

This file was deleted.

55 changes: 0 additions & 55 deletions .eslintrc.cjs

This file was deleted.

100 changes: 100 additions & 0 deletions BROWSERS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Browser Support

LightningJS is designed with legacy devices in mind, ensuring compatibility with a wide variety of browsers. From **Chrome v38**, released in late 2014, to the latest modern browsers, LightningJS provides a reliable and efficient rendering experience.

## WebGL Compatibility

LightningJS relies on **WebGL 1.x** (based on OpenGL ES 2.0) or newer for its rendering capabilities. If WebGL 1.x is supported in the browser, LightningJS will run without issues. Here are some key points about our WebGL implementation:

- **Independent Rendering**: LightningJS is a contained renderer that uses WebGL, giving us full pixel-for-pixel control over the output.
- **No CSS Dependency**: Unlike traditional DOM/CSS rendering, LightningJS avoids reliance on CSS features, extensions, or browser-specific CSS implementations.
- **Consistency**: In our experience, WebGL support is consistent across browsers. Once LightningJS is confirmed to work in a browser, it will deliver uniform output.

## Supported Browsers and Quirks

Below is a detailed breakdown of confirmed browsers that work with LightningJS, along with the quirks or features specific to their versions:

| **Browser** | **Version** | **Quirks / Features** |
| ------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **Chrome** | v38 | Bottom line for support; introduces Promises. Lacks `createImageBitmap`, iterable `UInt8Array/UInt8ClampedArray`. `ImageData` is mostly broken. |
| | v48 | Adds support for `Blob:` and `Data:` in the Fetch API. |
| | v49 | Introduces Proxies, improving reactive property handling in Blits. Not used in the Renderer. |
| | v50 | Adds `createImageBitmap` (without options object). Enables Image Worker in a web worker. |
| | v52 | Adds `config` for `createImageBitmap`, including support for alpha channel. |
| | v54 | Enables resizing in `createImageBitmap`. |
| | v63 | Adds support for dynamic imports. |
| | v71 | Introduces `globalThis`. |
| **WPEWebKit** | 2017 | Adds support for Image Worker and `createImageBitmap`. |
| | 2.22 | Full support, including Lightning Native mode (disables DOM/CSS compositing except for `<canvas>`, `<video>`, `<audio>` tags). |
| | 2.28 | Full support. |
| | 2.36 | Adds offscreen canvas and shared array buffers with await support for multithreading. |
| | 2.46 | (Upcoming) Adds FTL support for 32-bit devices and offscreen rendering support for Lightning Native. |

For createImageBitmap the Lightning 3 renderer will use a 1x1 PNG Pixel to validate whether the createImageBitmap API is available and which version can be used.
If the createImageBitmap API is not available it will gracefully fallback to `new Image()` with a performance decrease.

Lightning 3 prefers to run on **WPEWebKit** with `Lightning Native` (also known as nonCompositedWebGL) enabled for maximum performance!
For more information please see (https://wpewebkit.org/)[https://wpewebkit.org/]

## Running Lightning in older Browsers

To run LightningJS in older browsers, you can use **Vite's legacy plugin** and specific polyfills.

### Installation

Install the required dependencies:

```bash
pnpm i -D @vitejs/plugin-legacy whatwg-fetch
```

### Configuration

Add the following to your \`vite.config.js\`:

```javascript
import legacy from '@vitejs/plugin-legacy';

export default {
plugins: [
legacy({
targets: ['chrome>=38'],
modernPolyfills: true,
additionalLegacyPolyfills: ['whatwg-fetch'],
}),
],
};
```

### Adjusting Targets

Modify the \`chrome>=38\` target to match the browser version you need to support. If the target version is **Chrome v71** or higher, or **WPEWebKit 2.22** or newer, the legacy plugin is not required. You can adjust the target in your build configuration:

```javascript
build: {
target: prodTarget,
},
esbuild: {
target: devTarget,
},
```

Define the respective targets as follows:

```javascript
const devTarget = 'es2020';
const prodTarget = 'es2019';
```

For newer browsers, you can specify a higher target to reduce the use of polyfills and leverage native browser APIs for better performance.

---

## Performance Considerations

When evaluating performance, it is essential to understand the context of your development and target devices:

- **64-bit vs. 32-bit**: Your development browser on a PC/Mac runs in 64-bit mode, utilizing higher JIT tiers than the SmartTV/STB devices targeted by LightningJS.
- **Browser Age**: Development browsers are often much newer than the browsers found on Smart TVs or set-top boxes.
- **Targeted Output**: Align your build output with the legacy plugin/targets in Vite to match the browser version for deployment. Minimizing polyfills and leveraging native browser APIs will improve performance.
- **Device Testing**: Always test on target devices early and frequently. An **RPI3b+ with WPEWebKit** or similar ([WPE](https://github.com/webplatformforembedded/buildroot)) can be a great tool for replicating target environments.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Use Playwright's base image
FROM mcr.microsoft.com/playwright:v1.39.0-jammy
FROM mcr.microsoft.com/playwright:v1.49.0-jammy

# Set the working directory
WORKDIR /work
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ The Lightning 3 Renderer's goal is to work with the following browser versions a

Any JavaScript language features or browser APIs that cannot be automatically transpiled or polyfilled by industry standard transpilers (such as Babel) to target these versions must be carefully considered before use.

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

## Example Tests

The Example Tests sub-project define a set of tests for various Renderer
Expand All @@ -59,6 +61,11 @@ The Example Tests can be launched with:
pnpm start
```

A hosted version can be found at:
(https://https://lightning-js.github.io/renderer/)[https://lightning-js.github.io/renderer/]

This supports modern browsers as well as Chrome 38 and above through a legacy build.

See [examples/README.md](./examples/README.md) for more info.

## Visual Regression Tests
Expand Down
50 changes: 50 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import eslintPluginTs from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';

export default [
{
files: ['**/*.ts'],
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
project: [
'./tsconfig.json',
'./tsconfig.vitest.json',
'./tsconfig.cfg.json',
'./examples/tsconfig.json',
'./visual-regression/tsconfig.json',
'./performance/tsconfig.json',
],
},
},
plugins: {
'@typescript-eslint': eslintPluginTs,
},
rules: {
...eslintPluginTs.configs.recommended.rules,
'@typescript-eslint/no-unsafe-argument': 'warn',
'@typescript-eslint/no-unsafe-assignment': 'warn',
'@typescript-eslint/no-unsafe-return': 'warn',
'@typescript-eslint/no-unsafe-call': 'warn',
'@typescript-eslint/no-non-null-assertion': 'warn',
'@typescript-eslint/no-unsafe-member-access': 'warn',
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-empty-object-type': 'warn',
'@typescript-eslint/no-unused-expressions': 'warn',
},
},
{
ignores: [
'**/dist/**',
'node_modules',
'**config**.ts',
'**/docs/**',
'dist-vitest',
'dist-cfg',
],
},
];
10 changes: 10 additions & 0 deletions examples/common/setupMathRandom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export async function setupMathRandom() {
const mt19937 = await import('@stdlib/random-base-mt19937');

const factory = mt19937.factory || mt19937.default.factory;
const rand = factory({ seed: 1234 });
Math.random = function () {
return rand() / rand.MAX;
};
console.log('Math.random overridden with mt19937');
}
22 changes: 8 additions & 14 deletions examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ import {

import { Inspector } from '@lightningjs/renderer/inspector';
import { assertTruthy } from '@lightningjs/renderer/utils';
import * as mt19937 from '@stdlib/random-base-mt19937';

import type {
ExampleSettings,
SnapshotOptions,
} from './common/ExampleSettings.js';
import { StatTracker } from './common/StatTracker.js';
import { installFonts } from './common/installFonts.js';
import { MemMonitor } from './common/MemMonitor.js';
import { setupMathRandom } from './common/setupMathRandom.js';

interface TestModule {
default: (settings: ExampleSettings) => Promise<void>;
Expand Down Expand Up @@ -141,9 +142,8 @@ async function runTest(
throw new Error(`Test "${test}" not found`);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
const module = await testModule();
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call

const customSettings: Partial<RendererMainSettings> =
typeof module.customSettings === 'function'
? module.customSettings(urlParams)
Expand Down Expand Up @@ -216,7 +216,6 @@ async function runTest(
memMonitor,
};

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
await module.default(exampleSettings);
}

Expand Down Expand Up @@ -374,20 +373,15 @@ async function runAutomation(
continue;
}
assertTruthy(testModule);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment

// Setup Math.random to use a seeded random number generator for consistent
// results in automation mode.
await setupMathRandom();

const { automation, customSettings } = await testModule();
console.log(`Attempting to run automation for ${testName}...`);
if (automation) {
console.log(`Running automation for ${testName}...`);
// Override Math.random() as stable random number generator
// - Each test gets the same sequence of random numbers
// - This only is in effect when tests are run in automation mode
// eslint-disable-next-line @typescript-eslint/unbound-method
const factory = mt19937.factory || mt19937.default.factory;
const rand = factory({ seed: 1234 });
Math.random = function () {
return rand() / rand.MAX;
};
if (customSettings) {
console.error('customSettings not supported for automation');
} else {
Expand Down
4 changes: 3 additions & 1 deletion examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
"@stdlib/random-base-mt19937": "^0.2.1"
},
"devDependencies": {
"vite": "^5.0.0"
"@vitejs/plugin-legacy": "^5.4.2",
"vite": "^5.4.8",
"whatwg-fetch": "^3.6.2"
},
"engines": {
"npm": "please-use-pnpm"
Expand Down
41 changes: 41 additions & 0 deletions examples/tests/texture-base64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* If not stated otherwise in this file or this component's LICENSE file the
* following copyright and licenses apply:
*
* Copyright 2023 Comcast Cable Communications Management, LLC.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { ExampleSettings } from '../common/ExampleSettings.js';

export async function automation(settings: ExampleSettings) {
await test(settings);
await settings.snapshot();
}

/**
* Load a texture from a base64 encoded string.
* @param settings
*/
export default async function test(settings: ExampleSettings) {
const { renderer, testRoot } = settings;
const node = renderer.createNode({
x: 400,
y: 200,
width: 228,
height: 228,
parent: testRoot,
src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAAANHUlEQVR4nOzXjdcWdH3HcW69UcdUsnK4JWtNfDh3SDkyBfF0TM9Ec2SajJwPcE5LDQmtOSNTO5ajURZuuq2EwdZtDxsHXA0EVmFio81QGg3xgIZLGBJYaE+guf0Vn3M65/N6/QGf332u67rP+3wHL7/wqhFJd+76RnT/stPnRPePf35CdH/XTeui+0sueCa6v/srH4zu33LFwuj+2humRfcP2/bT6P6o18+L7o/76NTo/ozr3hDdv/WXn4zur7n/Q9H9J487NLp/SHQdgF9bAgBQSgAASgkAQCkBACglAAClBACglAAAlBIAgFICAFBKAABKCQBAKQEAKCUAAKUEAKCUAACUEgCAUgIAUEoAAEoJAEApAQAoJQAApQQAoJQAAJQSAIBSAgBQSgAASgkAQCkBACglAAClBACg1OCW0S9FHxg3tCy6v27WlOj+D097Lrp/246To/vjLz8Y3b/8qs3R/aPPfF90f9EJK6L7+07N/n7WXropuv/ak+ZE96eNfjG6//Kl347uH33vs9H9Y/dkv18XAEApAQAoJQAApQQAoJQAAJQSAIBSAgBQSgAASgkAQCkBACglAAClBACglAAAlBIAgFICAFBKAABKCQBAKQEAKCUAAKUEAKCUAACUEgCAUgIAUEoAAEoJAEApAQAoJQAApQQAoJQAAJQSAIBSAgBQauA3fjAh+sCOd58c3R9/5vui+88MPxTdP+Glg9H9t45aHt1ffdym6P55n/pWdP/p88+J7k/726Ho/uvueiy6f8Zp46P7P9y6Mbo/5bbjovvbNn4kuj970cvRfRcAQCkBACglAAClBACglAAAlBIAgFICAFBKAABKCQBAKQEAKCUAAKUEAKCUAACUEgCAUgIAUEoAAEoJAEApAQAoJQAApQQAoJQAAJQSAIBSAgBQSgAASgkAQCkBACglAAClBACglAAAlBIAgFICAFBqYMTMN0cfmPp4tjEzvjsrur9o6e7o/sH/fkN0/6wl66P7v/XdTdH9kY/eGd1/6pj90f2HZvxndP8dK2+J7p/xtiej+/M+9IXo/qS5S6P7570/+/nc9M6J0X0XAEApAQAoJQAApQQAoJQAAJQSAIBSAgBQSgAASgkAQCkBACglAAClBACglAAAlBIAgFICAFBK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',
});
}
Loading

0 comments on commit 2b3a866

Please sign in to comment.