From 63335c834199996c6df7acd7ac14ed769d112478 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:19:46 -0400 Subject: [PATCH 1/5] Refactor dump tools to support bitmaprenderer --- packages/dev/core/src/Misc/dumpTools.ts | 277 ++++++++++++------------ 1 file changed, 144 insertions(+), 133 deletions(-) diff --git a/packages/dev/core/src/Misc/dumpTools.ts b/packages/dev/core/src/Misc/dumpTools.ts index 00840a58395..a5f0dd2cb96 100644 --- a/packages/dev/core/src/Misc/dumpTools.ts +++ b/packages/dev/core/src/Misc/dumpTools.ts @@ -1,94 +1,113 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { _WarnImport } from "./devTools"; -import type { ThinEngine } from "../Engines/thinEngine"; import { Constants } from "../Engines/constants"; import { EffectRenderer, EffectWrapper } from "../Materials/effectRenderer"; import { Tools } from "./tools"; -import type { Nullable } from "../types"; import { Clamp } from "../Maths/math.scalar.functions"; import type { AbstractEngine } from "../Engines/abstractEngine"; import { EngineStore } from "../Engines/engineStore"; +import { Logger } from "./logger"; type DumpToolsEngine = { - canvas: HTMLCanvasElement | OffscreenCanvas; - engine: ThinEngine; - renderer: EffectRenderer; - wrapper: EffectWrapper; + dumpCanvas: HTMLCanvasElement | OffscreenCanvas; + drawToDumpCanvasAsync: (width: number, height: number, data: ArrayBufferView, invertY?: boolean) => Promise; + dispose?: () => void; }; -let DumpToolsEngine: Nullable; - let EnginePromise: Promise | null = null; async function _CreateDumpRendererAsync(): Promise { + // Create a compatible canvas. Prefer an HTMLCanvasElement if possible to avoid alpha issues with OffscreenCanvas + WebGL in many browsers. + const dumpCanvas = (EngineStore.LastCreatedEngine?.createCanvas(100, 100) ?? new OffscreenCanvas(100, 100)) as HTMLCanvasElement | OffscreenCanvas; // will be resized later + if (dumpCanvas instanceof OffscreenCanvas) { + Logger.Warn("DumpData: OffscreenCanvas will be used for dumping data. This may result in lossy alpha values."); + } + + // If WebGL via ThinEngine is not available (e.g. Native), use the BitmapRenderer. + // If https://github.com/whatwg/html/issues/10142 is resolved, we can migrate to just BitmapRenderer and avoid an engine dependency altogether. + const { ThinEngine: thinEngineClass } = await import("../Engines/thinEngine"); + if (!thinEngineClass.IsSupported) { + if (!dumpCanvas.getContext("bitmaprenderer")) { + throw new Error("DumpData: No WebGL or bitmap rendering context available. Cannot dump data."); + } + + return { + dumpCanvas, + drawToDumpCanvasAsync: async (width: number, height: number, data: ArrayBufferView, invertY?: boolean) => { + const ctx = dumpCanvas.getContext("bitmaprenderer")!; + dumpCanvas.width = width; + dumpCanvas.height = height; + + const imageData = new ImageData(width, height); // ImageData(data, sw, sh) ctor not yet widely implemented + imageData.data.set(data as Uint8ClampedArray); + const imageBitmap = await createImageBitmap(imageData, { premultiplyAlpha: "none", imageOrientation: invertY ? "flipY" : "from-image" }); + + ctx.transferFromImageBitmap(imageBitmap); + }, + }; + } + + const options = { + preserveDrawingBuffer: true, + depth: false, + stencil: false, + alpha: true, + premultipliedAlpha: false, + antialias: false, + failIfMajorPerformanceCaveat: false, + }; + const engine = new thinEngineClass(dumpCanvas, false, options); + + // remove this engine from the list of instances to avoid using it for other purposes + EngineStore.Instances.pop(); + // However, make sure to dispose it when no other engines are left + EngineStore.OnEnginesDisposedObservable.add((e) => { + // guaranteed to run when no other instances are left + // only dispose if it's not the current engine + if (engine && e !== engine && !engine.isDisposed && EngineStore.Instances.length === 0) { + // Dump the engine and the associated resources + Dispose(); + } + }); + + engine.getCaps().parallelShaderCompile = undefined; + + const renderer = new EffectRenderer(engine); + const { passPixelShader } = await import("../Shaders/pass.fragment"); + const wrapper = new EffectWrapper({ + engine, + name: passPixelShader.name, + fragmentShader: passPixelShader.shader, + samplerNames: ["textureSampler"], + }); + + return { + dumpCanvas, + drawToDumpCanvasAsync: async (width: number, height: number, data: ArrayBufferView, invertY?: boolean) => { + engine.setSize(width, height, true); + + // Create the image + const texture = engine.createRawTexture(data, width, height, Constants.TEXTUREFORMAT_RGBA, false, !invertY, Constants.TEXTURE_NEAREST_NEAREST); + + renderer.setViewport(); + renderer.applyEffectWrapper(wrapper); + wrapper.effect._bindTexture("textureSampler", texture); + renderer.draw(); + + texture.dispose(); + }, + dispose: () => { + wrapper.dispose(); + renderer.dispose(); + engine.dispose(); + }, + }; +} + +async function _GetDumpRendererAsync() { if (!EnginePromise) { - EnginePromise = new Promise((resolve, reject) => { - let canvas: HTMLCanvasElement | OffscreenCanvas; - let engine: Nullable = null; - const options = { - preserveDrawingBuffer: true, - depth: false, - stencil: false, - alpha: true, - premultipliedAlpha: false, - antialias: false, - failIfMajorPerformanceCaveat: false, - }; - import("../Engines/thinEngine") - // eslint-disable-next-line github/no-then - .then(({ ThinEngine: thinEngineClass }) => { - const engineInstanceCount = EngineStore.Instances.length; - try { - canvas = new OffscreenCanvas(100, 100); // will be resized later - engine = new thinEngineClass(canvas, false, options); - } catch (e) { - if (engineInstanceCount < EngineStore.Instances.length) { - // The engine was created by another instance, let's use it - EngineStore.Instances.pop()?.dispose(); - } - // The browser either does not support OffscreenCanvas or WebGL context in OffscreenCanvas, fallback on a regular canvas - canvas = document.createElement("canvas"); - engine = new thinEngineClass(canvas, false, options); - } - // remove this engine from the list of instances to avoid using it for other purposes - EngineStore.Instances.pop(); - // However, make sure to dispose it when no other engines are left - EngineStore.OnEnginesDisposedObservable.add((e) => { - // guaranteed to run when no other instances are left - // only dispose if it's not the current engine - if (engine && e !== engine && !engine.isDisposed && EngineStore.Instances.length === 0) { - // Dump the engine and the associated resources - Dispose(); - } - }); - engine.getCaps().parallelShaderCompile = undefined; - const renderer = new EffectRenderer(engine); - // eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then - import("../Shaders/pass.fragment").then(({ passPixelShader }) => { - if (!engine) { - // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors - reject("Engine is not defined"); - return; - } - const wrapper = new EffectWrapper({ - engine, - name: passPixelShader.name, - fragmentShader: passPixelShader.shader, - samplerNames: ["textureSampler"], - }); - DumpToolsEngine = { - canvas, - engine, - renderer, - wrapper, - }; - resolve(DumpToolsEngine); - }); - }) - // eslint-disable-next-line github/no-then - .catch(reject); - }); + EnginePromise = _CreateDumpRendererAsync(); } return await EnginePromise; } @@ -165,8 +184,38 @@ export async function DumpDataAsync( toArrayBuffer = false, quality?: number ): Promise { - return await new Promise((resolve) => { - DumpData(width, height, data, (result) => resolve(result), mimeType, fileName, invertY, toArrayBuffer, quality); + // Convert if data are float32 + if (data instanceof Float32Array) { + const data2 = new Uint8Array(data.length); + let n = data.length; + while (n--) { + const v = data[n]; + data2[n] = Math.round(Clamp(v) * 255); + } + data = data2; + } + + const renderer = await _GetDumpRendererAsync(); + await renderer.drawToDumpCanvasAsync(width, height, data, invertY); + + return await new Promise((resolve) => { + if (toArrayBuffer) { + Tools.ToBlob( + renderer.dumpCanvas, + (blob) => { + const fileReader = new FileReader(); + fileReader.onload = (event: any) => { + const arrayBuffer = event.target!.result as ArrayBuffer; + resolve(arrayBuffer); + }; + fileReader.readAsArrayBuffer(blob!); + }, + mimeType, + quality + ); + } else { + Tools.EncodeScreenshotCanvasData(renderer.dumpCanvas, (base64Data) => resolve(base64Data), mimeType, fileName, quality); + } }); } @@ -193,72 +242,34 @@ export function DumpData( toArrayBuffer = false, quality?: number ): void { - // eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then - _CreateDumpRendererAsync().then((renderer) => { - renderer.engine.setSize(width, height, true); - - // Convert if data are float32 - if (data instanceof Float32Array) { - const data2 = new Uint8Array(data.length); - let n = data.length; - while (n--) { - const v = data[n]; - data2[n] = Math.round(Clamp(v) * 255); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + DumpDataAsync(width, height, data, mimeType, fileName, invertY, toArrayBuffer, quality) + // eslint-disable-next-line github/no-then + .then((result) => { + if (successCallback) { + successCallback(result); } - data = data2; - } - - // Create the image - const texture = renderer.engine.createRawTexture(data, width, height, Constants.TEXTUREFORMAT_RGBA, false, !invertY, Constants.TEXTURE_NEAREST_NEAREST); - - renderer.renderer.setViewport(); - renderer.renderer.applyEffectWrapper(renderer.wrapper); - renderer.wrapper.effect._bindTexture("textureSampler", texture); - renderer.renderer.draw(); - - if (toArrayBuffer) { - Tools.ToBlob( - renderer.canvas, - (blob) => { - const fileReader = new FileReader(); - fileReader.onload = (event: any) => { - const arrayBuffer = event.target!.result as ArrayBuffer; - if (successCallback) { - successCallback(arrayBuffer); - } - }; - fileReader.readAsArrayBuffer(blob!); - }, - mimeType, - quality - ); - } else { - Tools.EncodeScreenshotCanvasData(renderer.canvas, successCallback, mimeType, fileName, quality); - } - - texture.dispose(); - }); + }); } /** * Dispose the dump tools associated resources */ export function Dispose() { - if (DumpToolsEngine) { - DumpToolsEngine.wrapper.dispose(); - DumpToolsEngine.renderer.dispose(); - DumpToolsEngine.engine.dispose(); - } else { - // in cases where the engine is not yet created, we need to wait for it to dispose it - // eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then - EnginePromise?.then((dumpToolsEngine) => { - dumpToolsEngine.wrapper.dispose(); - dumpToolsEngine.renderer.dispose(); - dumpToolsEngine.engine.dispose(); - }); + if (!EnginePromise) { + return; } + + // in cases where the engine is not yet created, we need to wait for it to dispose it + // eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then + EnginePromise?.then((dumpToolsEngine) => { + if (dumpToolsEngine.dumpCanvas instanceof HTMLCanvasElement) { + dumpToolsEngine.dumpCanvas.remove(); + } + dumpToolsEngine.dispose?.(); + }); + EnginePromise = null; - DumpToolsEngine = null; } /** From 0308f22d634c24aed30b53bbacf36de8674fd4f4 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Tue, 12 Aug 2025 13:17:52 -0400 Subject: [PATCH 2/5] nit --- packages/dev/core/src/Misc/dumpTools.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/core/src/Misc/dumpTools.ts b/packages/dev/core/src/Misc/dumpTools.ts index a5f0dd2cb96..7da49aa16b9 100644 --- a/packages/dev/core/src/Misc/dumpTools.ts +++ b/packages/dev/core/src/Misc/dumpTools.ts @@ -214,7 +214,7 @@ export async function DumpDataAsync( quality ); } else { - Tools.EncodeScreenshotCanvasData(renderer.dumpCanvas, (base64Data) => resolve(base64Data), mimeType, fileName, quality); + Tools.EncodeScreenshotCanvasData(renderer.dumpCanvas, resolve, mimeType, fileName, quality); } }); } From e02d32c26f0345dc25bc97de261274c3cf560232 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Tue, 12 Aug 2025 13:37:08 -0400 Subject: [PATCH 3/5] type fix (should fix these all in a separate PR that addresses ICanvas) --- packages/dev/core/src/Misc/dumpTools.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/core/src/Misc/dumpTools.ts b/packages/dev/core/src/Misc/dumpTools.ts index 7da49aa16b9..670cbf9fbbf 100644 --- a/packages/dev/core/src/Misc/dumpTools.ts +++ b/packages/dev/core/src/Misc/dumpTools.ts @@ -35,7 +35,7 @@ async function _CreateDumpRendererAsync(): Promise { return { dumpCanvas, drawToDumpCanvasAsync: async (width: number, height: number, data: ArrayBufferView, invertY?: boolean) => { - const ctx = dumpCanvas.getContext("bitmaprenderer")!; + const ctx = dumpCanvas.getContext("bitmaprenderer") as ImageBitmapRenderingContext; dumpCanvas.width = width; dumpCanvas.height = height; From 548ec4bb459d3779047342fdec6bf43c65b4f0f1 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Wed, 13 Aug 2025 10:35:53 -0400 Subject: [PATCH 4/5] Keep the render + canvas read (in toBlob) atomic --- packages/dev/core/src/Misc/dumpTools.ts | 48 +++++++++++++++---------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/packages/dev/core/src/Misc/dumpTools.ts b/packages/dev/core/src/Misc/dumpTools.ts index 670cbf9fbbf..d94ad867b25 100644 --- a/packages/dev/core/src/Misc/dumpTools.ts +++ b/packages/dev/core/src/Misc/dumpTools.ts @@ -8,8 +8,10 @@ import { Clamp } from "../Maths/math.scalar.functions"; import type { AbstractEngine } from "../Engines/abstractEngine"; import { EngineStore } from "../Engines/engineStore"; import { Logger } from "./logger"; +import { AsyncLock } from "./asyncLock"; type DumpToolsEngine = { + asyncLock: AsyncLock; dumpCanvas: HTMLCanvasElement | OffscreenCanvas; drawToDumpCanvasAsync: (width: number, height: number, data: ArrayBufferView, invertY?: boolean) => Promise; dispose?: () => void; @@ -24,6 +26,9 @@ async function _CreateDumpRendererAsync(): Promise { Logger.Warn("DumpData: OffscreenCanvas will be used for dumping data. This may result in lossy alpha values."); } + // Use an async lock for a quick way to ensure that the rendering and reading from the canvas is always atomic. + const asyncLock = new AsyncLock(); + // If WebGL via ThinEngine is not available (e.g. Native), use the BitmapRenderer. // If https://github.com/whatwg/html/issues/10142 is resolved, we can migrate to just BitmapRenderer and avoid an engine dependency altogether. const { ThinEngine: thinEngineClass } = await import("../Engines/thinEngine"); @@ -33,6 +38,7 @@ async function _CreateDumpRendererAsync(): Promise { } return { + asyncLock, dumpCanvas, drawToDumpCanvasAsync: async (width: number, height: number, data: ArrayBufferView, invertY?: boolean) => { const ctx = dumpCanvas.getContext("bitmaprenderer") as ImageBitmapRenderingContext; @@ -83,6 +89,7 @@ async function _CreateDumpRendererAsync(): Promise { }); return { + asyncLock, dumpCanvas, drawToDumpCanvasAsync: async (width: number, height: number, data: ArrayBufferView, invertY?: boolean) => { engine.setSize(width, height, true); @@ -196,26 +203,29 @@ export async function DumpDataAsync( } const renderer = await _GetDumpRendererAsync(); - await renderer.drawToDumpCanvasAsync(width, height, data, invertY); - return await new Promise((resolve) => { - if (toArrayBuffer) { - Tools.ToBlob( - renderer.dumpCanvas, - (blob) => { - const fileReader = new FileReader(); - fileReader.onload = (event: any) => { - const arrayBuffer = event.target!.result as ArrayBuffer; - resolve(arrayBuffer); - }; - fileReader.readAsArrayBuffer(blob!); - }, - mimeType, - quality - ); - } else { - Tools.EncodeScreenshotCanvasData(renderer.dumpCanvas, resolve, mimeType, fileName, quality); - } + // Keep the async render + read from the shared canvas atomic + return await renderer.asyncLock.lockAsync(async () => { + await renderer.drawToDumpCanvasAsync(width, height, data, invertY); + return await new Promise((resolve) => { + if (toArrayBuffer) { + Tools.ToBlob( + renderer.dumpCanvas, + (blob) => { + const fileReader = new FileReader(); + fileReader.onload = (event: any) => { + const arrayBuffer = event.target!.result as ArrayBuffer; + resolve(arrayBuffer); + }; + fileReader.readAsArrayBuffer(blob!); + }, + mimeType, + quality + ); + } else { + Tools.EncodeScreenshotCanvasData(renderer.dumpCanvas, resolve, mimeType, fileName, quality); + } + }); }); } From 7cb36bd3f4141965f3084de42db5a8a1521c8743 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Wed, 13 Aug 2025 11:22:03 -0400 Subject: [PATCH 5/5] Keep things atomic in a different way, without AsyncLock --- packages/dev/core/src/Misc/dumpTools.ts | 160 ++++++++++++------------ 1 file changed, 77 insertions(+), 83 deletions(-) diff --git a/packages/dev/core/src/Misc/dumpTools.ts b/packages/dev/core/src/Misc/dumpTools.ts index d94ad867b25..947b42aec68 100644 --- a/packages/dev/core/src/Misc/dumpTools.ts +++ b/packages/dev/core/src/Misc/dumpTools.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { _WarnImport } from "./devTools"; +import type { ThinEngine } from "../Engines/thinEngine"; import { Constants } from "../Engines/constants"; import { EffectRenderer, EffectWrapper } from "../Materials/effectRenderer"; import { Tools } from "./tools"; @@ -8,50 +9,33 @@ import { Clamp } from "../Maths/math.scalar.functions"; import type { AbstractEngine } from "../Engines/abstractEngine"; import { EngineStore } from "../Engines/engineStore"; import { Logger } from "./logger"; -import { AsyncLock } from "./asyncLock"; -type DumpToolsEngine = { - asyncLock: AsyncLock; - dumpCanvas: HTMLCanvasElement | OffscreenCanvas; - drawToDumpCanvasAsync: (width: number, height: number, data: ArrayBufferView, invertY?: boolean) => Promise; - dispose?: () => void; +type DumpResources = { + canvas: HTMLCanvasElement | OffscreenCanvas; + dumpEngine?: { + engine: ThinEngine; + renderer: EffectRenderer; + wrapper: EffectWrapper; + }; }; -let EnginePromise: Promise | null = null; +let ResourcesPromise: Promise | null = null; -async function _CreateDumpRendererAsync(): Promise { +async function _CreateDumpResourcesAsync(): Promise { // Create a compatible canvas. Prefer an HTMLCanvasElement if possible to avoid alpha issues with OffscreenCanvas + WebGL in many browsers. - const dumpCanvas = (EngineStore.LastCreatedEngine?.createCanvas(100, 100) ?? new OffscreenCanvas(100, 100)) as HTMLCanvasElement | OffscreenCanvas; // will be resized later - if (dumpCanvas instanceof OffscreenCanvas) { + const canvas = (EngineStore.LastCreatedEngine?.createCanvas(100, 100) ?? new OffscreenCanvas(100, 100)) as HTMLCanvasElement | OffscreenCanvas; // will be resized later + if (canvas instanceof OffscreenCanvas) { Logger.Warn("DumpData: OffscreenCanvas will be used for dumping data. This may result in lossy alpha values."); } - // Use an async lock for a quick way to ensure that the rendering and reading from the canvas is always atomic. - const asyncLock = new AsyncLock(); - // If WebGL via ThinEngine is not available (e.g. Native), use the BitmapRenderer. // If https://github.com/whatwg/html/issues/10142 is resolved, we can migrate to just BitmapRenderer and avoid an engine dependency altogether. const { ThinEngine: thinEngineClass } = await import("../Engines/thinEngine"); if (!thinEngineClass.IsSupported) { - if (!dumpCanvas.getContext("bitmaprenderer")) { + if (!canvas.getContext("bitmaprenderer")) { throw new Error("DumpData: No WebGL or bitmap rendering context available. Cannot dump data."); } - - return { - asyncLock, - dumpCanvas, - drawToDumpCanvasAsync: async (width: number, height: number, data: ArrayBufferView, invertY?: boolean) => { - const ctx = dumpCanvas.getContext("bitmaprenderer") as ImageBitmapRenderingContext; - dumpCanvas.width = width; - dumpCanvas.height = height; - - const imageData = new ImageData(width, height); // ImageData(data, sw, sh) ctor not yet widely implemented - imageData.data.set(data as Uint8ClampedArray); - const imageBitmap = await createImageBitmap(imageData, { premultiplyAlpha: "none", imageOrientation: invertY ? "flipY" : "from-image" }); - - ctx.transferFromImageBitmap(imageBitmap); - }, - }; + return { canvas }; } const options = { @@ -63,7 +47,7 @@ async function _CreateDumpRendererAsync(): Promise { antialias: false, failIfMajorPerformanceCaveat: false, }; - const engine = new thinEngineClass(dumpCanvas, false, options); + const engine = new thinEngineClass(canvas, false, options); // remove this engine from the list of instances to avoid using it for other purposes EngineStore.Instances.pop(); @@ -89,34 +73,16 @@ async function _CreateDumpRendererAsync(): Promise { }); return { - asyncLock, - dumpCanvas, - drawToDumpCanvasAsync: async (width: number, height: number, data: ArrayBufferView, invertY?: boolean) => { - engine.setSize(width, height, true); - - // Create the image - const texture = engine.createRawTexture(data, width, height, Constants.TEXTUREFORMAT_RGBA, false, !invertY, Constants.TEXTURE_NEAREST_NEAREST); - - renderer.setViewport(); - renderer.applyEffectWrapper(wrapper); - wrapper.effect._bindTexture("textureSampler", texture); - renderer.draw(); - - texture.dispose(); - }, - dispose: () => { - wrapper.dispose(); - renderer.dispose(); - engine.dispose(); - }, + canvas: canvas, + dumpEngine: { engine, renderer, wrapper }, }; } -async function _GetDumpRendererAsync() { - if (!EnginePromise) { - EnginePromise = _CreateDumpRendererAsync(); +async function _GetDumpResourcesAsync() { + if (!ResourcesPromise) { + ResourcesPromise = _CreateDumpResourcesAsync(); } - return await EnginePromise; + return await ResourcesPromise; } /** @@ -202,30 +168,54 @@ export async function DumpDataAsync( data = data2; } - const renderer = await _GetDumpRendererAsync(); + const resources = await _GetDumpResourcesAsync(); // Keep the async render + read from the shared canvas atomic - return await renderer.asyncLock.lockAsync(async () => { - await renderer.drawToDumpCanvasAsync(width, height, data, invertY); - return await new Promise((resolve) => { - if (toArrayBuffer) { - Tools.ToBlob( - renderer.dumpCanvas, - (blob) => { - const fileReader = new FileReader(); - fileReader.onload = (event: any) => { - const arrayBuffer = event.target!.result as ArrayBuffer; - resolve(arrayBuffer); - }; - fileReader.readAsArrayBuffer(blob!); - }, - mimeType, - quality - ); - } else { - Tools.EncodeScreenshotCanvasData(renderer.dumpCanvas, resolve, mimeType, fileName, quality); - } - }); + // eslint-disable-next-line no-async-promise-executor + return await new Promise(async (resolve) => { + if (resources.dumpEngine) { + const dumpEngine = resources.dumpEngine; + dumpEngine.engine.setSize(width, height, true); + + // Create the image + const texture = dumpEngine.engine.createRawTexture(data, width, height, Constants.TEXTUREFORMAT_RGBA, false, !invertY, Constants.TEXTURE_NEAREST_NEAREST); + + dumpEngine.renderer.setViewport(); + dumpEngine.renderer.applyEffectWrapper(dumpEngine.wrapper); + dumpEngine.wrapper.effect._bindTexture("textureSampler", texture); + dumpEngine.renderer.draw(); + + texture.dispose(); + } else { + const ctx = resources.canvas.getContext("bitmaprenderer") as ImageBitmapRenderingContext; + resources.canvas.width = width; + resources.canvas.height = height; + + const imageData = new ImageData(width, height); // ImageData(data, sw, sh) ctor not yet widely implemented + imageData.data.set(data as Uint8ClampedArray); + const imageBitmap = await createImageBitmap(imageData, { premultiplyAlpha: "none", imageOrientation: invertY ? "flipY" : "from-image" }); + + ctx.transferFromImageBitmap(imageBitmap); + } + + // Download the result + if (toArrayBuffer) { + Tools.ToBlob( + resources.canvas, + (blob) => { + const fileReader = new FileReader(); + fileReader.onload = (event: any) => { + const arrayBuffer = event.target!.result as ArrayBuffer; + resolve(arrayBuffer); + }; + fileReader.readAsArrayBuffer(blob!); + }, + mimeType, + quality + ); + } else { + Tools.EncodeScreenshotCanvasData(resources.canvas, resolve, mimeType, fileName, quality); + } }); } @@ -266,20 +256,24 @@ export function DumpData( * Dispose the dump tools associated resources */ export function Dispose() { - if (!EnginePromise) { + if (!ResourcesPromise) { return; } // in cases where the engine is not yet created, we need to wait for it to dispose it // eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then - EnginePromise?.then((dumpToolsEngine) => { - if (dumpToolsEngine.dumpCanvas instanceof HTMLCanvasElement) { - dumpToolsEngine.dumpCanvas.remove(); + ResourcesPromise?.then((resources) => { + if (resources.canvas instanceof HTMLCanvasElement) { + resources.canvas.remove(); + } + if (resources.dumpEngine) { + resources.dumpEngine.engine.dispose(); + resources.dumpEngine.renderer.dispose(); + resources.dumpEngine.wrapper.dispose(); } - dumpToolsEngine.dispose?.(); }); - EnginePromise = null; + ResourcesPromise = null; } /**