From fc0ee7d454057e0c7e0d8acdb55b6fde2ca50e9d Mon Sep 17 00:00:00 2001 From: Frank Weindel <6070611+frank-weindel@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:34:49 -0400 Subject: [PATCH 1/3] Remove reliance on createImageBitmap(ImageData) This browser feature lacks legacy support. Specifically WPE 2.28. Fixes #43 --- .../renderers/webgl/WebGlCoreCtxSubTexture.ts | 4 +-- .../renderers/webgl/WebGlCoreCtxTexture.ts | 24 ++++++++++-------- src/core/textures/ColorTexture.ts | 11 ++++---- src/core/textures/ImageTexture.ts | 25 +++++++++++-------- src/core/textures/NoiseTexture.ts | 6 ++--- src/core/textures/SubTexture.ts | 8 +++--- src/core/textures/Texture.ts | 15 +++++++++-- 7 files changed, 56 insertions(+), 37 deletions(-) diff --git a/src/core/renderers/webgl/WebGlCoreCtxSubTexture.ts b/src/core/renderers/webgl/WebGlCoreCtxSubTexture.ts index 7145f11f..7ab1bb0c 100644 --- a/src/core/renderers/webgl/WebGlCoreCtxSubTexture.ts +++ b/src/core/renderers/webgl/WebGlCoreCtxSubTexture.ts @@ -29,8 +29,8 @@ export class WebGlCoreCtxSubTexture extends WebGlCoreCtxTexture { override async onLoadRequest(): Promise { const props = await (this.textureSource as SubTexture).getTextureData(); return { - width: props.width || 0, - height: props.height || 0, + width: props.data?.width || 0, + height: props.data?.height || 0, }; } } diff --git a/src/core/renderers/webgl/WebGlCoreCtxTexture.ts b/src/core/renderers/webgl/WebGlCoreCtxTexture.ts index 12a5123b..c00368fd 100644 --- a/src/core/renderers/webgl/WebGlCoreCtxTexture.ts +++ b/src/core/renderers/webgl/WebGlCoreCtxTexture.ts @@ -133,25 +133,27 @@ export class WebGlCoreCtxTexture extends CoreContextTexture { assertTruthy(this._nativeCtxTexture); // If textureData is null, the texture is empty (0, 0) and we don't need to // upload any data to the GPU. - if (textureData instanceof ImageBitmap) { - width = textureData.width; - height = textureData.height; + if ( + textureData.data instanceof ImageBitmap || + textureData.data instanceof ImageData + ) { + const data = textureData.data; + width = data.width; + height = data.height; gl.bindTexture(gl.TEXTURE_2D, this._nativeCtxTexture); - gl.texImage2D( - gl.TEXTURE_2D, - 0, - gl.RGBA, - gl.RGBA, - gl.UNSIGNED_BYTE, - textureData, + gl.pixelStorei( + gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, + !!textureData.premultiplyAlpha, ); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data); + // generate mipmaps for power-of-2 textures or in WebGL2RenderingContext if (isWebGl2(gl) || (isPowerOfTwo(width) && isPowerOfTwo(height))) { gl.generateMipmap(gl.TEXTURE_2D); } - } else if (textureData === null) { + } else if (textureData.data === null) { width = 0; height = 0; // Reset to a 1x1 transparent texture diff --git a/src/core/textures/ColorTexture.ts b/src/core/textures/ColorTexture.ts index b6157dbe..b9754c5b 100644 --- a/src/core/textures/ColorTexture.ts +++ b/src/core/textures/ColorTexture.ts @@ -18,7 +18,7 @@ */ import type { CoreTextureManager } from '../CoreTextureManager.js'; -import { Texture } from './Texture.js'; +import { Texture, type TextureData } from './Texture.js'; /** * Properties of the {@link ColorTexture} @@ -60,12 +60,13 @@ export class ColorTexture extends Texture { this.props.color = color; } - override async getTextureData(): Promise { + override async getTextureData(): Promise { const pixelData32 = new Uint32Array([this.color]); const pixelData8 = new Uint8ClampedArray(pixelData32.buffer); - return await createImageBitmap(new ImageData(pixelData8, 1, 1), { - premultiplyAlpha: 'none', - }); + return { + data: new ImageData(pixelData8, 1, 1), + premultiplyAlpha: true, + }; } static override makeCacheKey(props: ColorTextureProps): string { diff --git a/src/core/textures/ImageTexture.ts b/src/core/textures/ImageTexture.ts index c3f2433a..fee9f2b4 100644 --- a/src/core/textures/ImageTexture.ts +++ b/src/core/textures/ImageTexture.ts @@ -73,22 +73,25 @@ export class ImageTexture extends Texture { override async getTextureData(): Promise { const { src, premultiplyAlpha } = this.props; if (!src) { - return null; + return { + data: null, + }; } if (src instanceof ImageData) { - return await createImageBitmap(src, { - premultiplyAlpha: premultiplyAlpha ? 'premultiply' : 'none', - colorSpaceConversion: 'none', - imageOrientation: 'none', - }); + return { + data: src, + premultiplyAlpha, + }; } const response = await fetch(src); const blob = await response.blob(); - return await createImageBitmap(blob, { - premultiplyAlpha: premultiplyAlpha ? 'premultiply' : 'none', - colorSpaceConversion: 'none', - imageOrientation: 'none', - }); + return { + data: await createImageBitmap(blob, { + premultiplyAlpha: premultiplyAlpha ? 'premultiply' : 'none', + colorSpaceConversion: 'none', + imageOrientation: 'none', + }), + }; } static override makeCacheKey(props: ImageTextureProps): string | false { diff --git a/src/core/textures/NoiseTexture.ts b/src/core/textures/NoiseTexture.ts index d220d66d..a634c71d 100644 --- a/src/core/textures/NoiseTexture.ts +++ b/src/core/textures/NoiseTexture.ts @@ -72,9 +72,9 @@ export class NoiseTexture extends Texture { pixelData8[i + 2] = v; pixelData8[i + 3] = 255; } - return await createImageBitmap(new ImageData(pixelData8, width, height), { - premultiplyAlpha: 'none', - }); + return { + data: new ImageData(pixelData8, width, height), + }; } static override makeCacheKey(props: NoiseTextureProps): string { diff --git a/src/core/textures/SubTexture.ts b/src/core/textures/SubTexture.ts index e5daa5c5..aceaf91a 100644 --- a/src/core/textures/SubTexture.ts +++ b/src/core/textures/SubTexture.ts @@ -23,7 +23,7 @@ import type { } from '../../common/CommonTypes.js'; import type { TextureRef } from '../../main-api/RendererMain.js'; import type { CoreTextureManager } from '../CoreTextureManager.js'; -import { Texture } from './Texture.js'; +import { Texture, type TextureData } from './Texture.js'; /** * Properties of the {@link SubTexture} @@ -115,8 +115,10 @@ export class SubTexture extends Texture { this.setState('failed', error); }; - override async getTextureData(): Promise { - return this.props; + override async getTextureData(): Promise { + return { + data: this.props, + }; } // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/src/core/textures/Texture.ts b/src/core/textures/Texture.ts index 69988c2b..eb876266 100644 --- a/src/core/textures/Texture.ts +++ b/src/core/textures/Texture.ts @@ -28,9 +28,20 @@ import type { import { EventEmitter } from '../../common/EventEmitter.js'; /** - * Texture sources that are used to populate a CoreContextTexture + * TextureData that is used to populate a CoreContextTexture */ -export type TextureData = ImageBitmap | SubTextureProps | null; +export interface TextureData { + /** + * The texture data + */ + data: ImageBitmap | ImageData | SubTextureProps | null; + /** + * Premultiply alpha when uploading texture data to the GPU + * + * @defaultValue `false` + */ + premultiplyAlpha?: boolean; +} export type TextureState = 'loading' | 'loaded' | 'failed'; From b80c66962563174caa7983a0703b2dbcfbee2d4d Mon Sep 17 00:00:00 2001 From: Frank Weindel <6070611+frank-weindel@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:51:55 -0400 Subject: [PATCH 2/3] Add ECMAScript target and easy way to run examples in prod mode Target set to ES2019 which seems to be supported by WPE 2.28. Fixes #44 --- README.md | 7 ++++++- examples/package.json | 2 +- examples/vite.config.ts | 23 +++++++++++++++++++++++ package.json | 1 + 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 76dc2682..feca12b6 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,13 @@ npm test # Build API Documentation (builds into ./docs folder) npm run typedoc -# Launch test examples (includes Build Renderer (watch mode)) +# Launch test examples in dev mode (includes Build Renderer (watch mode)) npm start + +# Launch test examples in production mode +# IMPORTANT: To run test examples on embedded devices that use older browser versions +# you MUST run the examples in this mode. +npm run prod ``` ## Test Examples diff --git a/examples/package.json b/examples/package.json index 64da2b3c..45856558 100644 --- a/examples/package.json +++ b/examples/package.json @@ -8,7 +8,7 @@ "start": "concurrently -c \"auto\" \"npm:watch-renderer\" \"npm:dev\"", "dev": "vite --open", "build": "vite build", - "preview": "vite preview", + "preview": "vite preview --host", "watch-renderer": "cd .. && npm run watch" }, "author": "Frank Weindel", diff --git a/examples/vite.config.ts b/examples/vite.config.ts index ba8a84a0..ce3aea50 100644 --- a/examples/vite.config.ts +++ b/examples/vite.config.ts @@ -21,6 +21,20 @@ import { defineConfig } from 'vite'; import * as path from 'path'; import { importChunkUrl } from '@lightningjs/vite-plugin-import-chunk-url'; +/** + * Targeting ES2019 gets us at least to WPE 2.28 + * + * Despite setting the target in 3 different places in the Vite config below + * this does not seem to have an effect on the output when running Vite in + * development mode (`npm start`). In order to properly test on embedded devices + * that require the set target, you must run `npm run build` and then serve the + * content via `npm run preview -- --host`. + * + * See the following for any updates on this: + * https://github.com/vitejs/vite/issues/13756#issuecomment-1751085158 + */ +const target = 'es2019'; + /** * Vite Config */ @@ -30,7 +44,16 @@ export default defineConfig(({ command, mode, ssrBuild }) => { worker: { format: 'es', }, + esbuild: { + target, + }, + optimizeDeps: { + esbuildOptions: { + target, + }, + }, build: { + target, minify: false, sourcemap: true, outDir: path.resolve(__dirname, 'dist'), diff --git a/package.json b/package.json index 84bd1521..f1f8de00 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "scripts": { "start": "cd examples && npm start", + "prod": "npm run build && cd examples && npm run build && npm run preview", "build": "tsc --build", "watch": "tsc --build --watch", "test": "vitest", From d047a217072530448bb08e4c833f8d30f11cbbf2 Mon Sep 17 00:00:00 2001 From: Frank Weindel <6070611+frank-weindel@users.noreply.github.com> Date: Fri, 6 Oct 2023 14:03:21 -0400 Subject: [PATCH 3/3] RectangleShader: Remove unused 'a_textureIndex' attribute Fixes #45 --- src/core/renderers/webgl/WebGlCoreShader.ts | 4 ++-- src/core/renderers/webgl/shaders/RoundedRectangle.ts | 9 +-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/core/renderers/webgl/WebGlCoreShader.ts b/src/core/renderers/webgl/WebGlCoreShader.ts index 2a47e297..2868a2fd 100644 --- a/src/core/renderers/webgl/WebGlCoreShader.ts +++ b/src/core/renderers/webgl/WebGlCoreShader.ts @@ -166,13 +166,13 @@ export abstract class WebGlCoreShader extends CoreShader { const location = gl.getAttribLocation(this.program, attributeName); if (location < 0) { throw new Error( - `Vertex shader must have an attribute "${attributeName}"!`, + `${this.constructor.name}: Vertex shader must have an attribute "${attributeName}"!`, ); } const buffer = gl.createBuffer(); if (!buffer) { throw new Error( - `Could not create buffer for attribute "${attributeName}"`, + `${this.constructor.name}: Could not create buffer for attribute "${attributeName}"`, ); } diff --git a/src/core/renderers/webgl/shaders/RoundedRectangle.ts b/src/core/renderers/webgl/shaders/RoundedRectangle.ts index 69edfbe9..0325ef88 100644 --- a/src/core/renderers/webgl/shaders/RoundedRectangle.ts +++ b/src/core/renderers/webgl/shaders/RoundedRectangle.ts @@ -45,12 +45,7 @@ export class RoundedRectangle extends WebGlCoreShader { constructor(renderer: WebGlCoreRenderer) { super({ renderer, - attributes: [ - 'a_position', - 'a_textureCoordinate', - 'a_color', - 'a_textureIndex', - ], + attributes: ['a_position', 'a_textureCoordinate', 'a_color'], uniforms: [ { name: 'u_resolution', uniform: 'uniform2fv' }, { name: 'u_pixelRatio', uniform: 'uniform1f' }, @@ -115,7 +110,6 @@ export class RoundedRectangle extends WebGlCoreShader { varying vec4 v_color; varying vec2 v_textureCoordinate; - varying float v_textureIndex; void main() { vec2 normalized = a_position * u_pixelRatio / u_resolution; @@ -125,7 +119,6 @@ export class RoundedRectangle extends WebGlCoreShader { // pass to fragment v_color = a_color; v_textureCoordinate = a_textureCoordinate; - v_textureIndex = a_textureIndex; // flip y gl_Position = vec4(clip_space * vec2(1.0, -1.0), 0, 1);