UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

216 lines (215 loc) 10.2 kB
import { EffectRenderer, EffectWrapper } from "../Materials/effectRenderer.js"; import { Tools } from "./tools.js"; import { Clamp } from "../Maths/math.scalar.functions.js"; import { EngineStore } from "../Engines/engineStore.js"; import { Logger } from "./logger.js"; let ResourcesPromise = null; async function _CreateDumpResourcesAsync() { // Create a compatible canvas. Prefer an HTMLCanvasElement if possible to avoid alpha issues with OffscreenCanvas + WebGL in many browsers. const canvas = (EngineStore.LastCreatedEngine?.createCanvas(100, 100) ?? new OffscreenCanvas(100, 100)); // 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."); } // 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.js"); if (!thinEngineClass.IsSupported) { if (!canvas.getContext("bitmaprenderer")) { throw new Error("DumpData: No WebGL or bitmap rendering context available. Cannot dump data."); } return { canvas }; } const options = { preserveDrawingBuffer: true, depth: false, stencil: false, alpha: true, premultipliedAlpha: false, antialias: false, failIfMajorPerformanceCaveat: false, }; 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(); // 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.js"); const wrapper = new EffectWrapper({ engine, name: passPixelShader.name, fragmentShader: passPixelShader.shader, samplerNames: ["textureSampler"], }); return { canvas: canvas, dumpEngine: { engine, renderer, wrapper }, }; } async function _GetDumpResourcesAsync() { if (!ResourcesPromise) { ResourcesPromise = _CreateDumpResourcesAsync(); } return await ResourcesPromise; } /** * Dumps the current bound framebuffer * @param width defines the rendering width * @param height defines the rendering height * @param engine defines the hosting engine * @param successCallback defines the callback triggered once the data are available * @param mimeType defines the mime type of the result * @param fileName defines the filename to download. If present, the result will automatically be downloaded * @param quality The quality of the image if lossy mimeType is used (e.g. image/jpeg, image/webp). See {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob | HTMLCanvasElement.toBlob()}'s `quality` parameter. * @returns a void promise */ // Should have "Async" in the name but this is a public API and we can't break it now // eslint-disable-next-line no-restricted-syntax export async function DumpFramebuffer(width, height, engine, successCallback, mimeType = "image/png", fileName, quality) { // Read the contents of the framebuffer const bufferView = await engine.readPixels(0, 0, width, height); const data = new Uint8Array(bufferView.buffer); DumpData(width, height, data, successCallback, mimeType, fileName, true, undefined, quality); } /** * Dumps an array buffer * @param width defines the rendering width * @param height defines the rendering height * @param data the data array * @param mimeType defines the mime type of the result * @param fileName defines the filename to download. If present, the result will automatically be downloaded * @param invertY true to invert the picture in the Y dimension * @param toArrayBuffer true to convert the data to an ArrayBuffer (encoded as `mimeType`) instead of a base64 string * @param quality The quality of the image if lossy mimeType is used (e.g. image/jpeg, image/webp). See {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob | HTMLCanvasElement.toBlob()}'s `quality` parameter. * @returns a promise that resolve to the final data */ export async function DumpDataAsync(width, height, data, mimeType = "image/png", fileName, invertY = false, toArrayBuffer = false, 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 resources = await _GetDumpResourcesAsync(); // Keep the async render + read from the shared canvas atomic // 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, 5, false, !invertY, 1); 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"); 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); 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) => { const arrayBuffer = event.target.result; resolve(arrayBuffer); }; fileReader.readAsArrayBuffer(blob); }, mimeType, quality); } else { Tools.EncodeScreenshotCanvasData(resources.canvas, resolve, mimeType, fileName, quality); } }); } /** * Dumps an array buffer * @param width defines the rendering width * @param height defines the rendering height * @param data the data array * @param successCallback defines the callback triggered once the data are available * @param mimeType defines the mime type of the result * @param fileName defines the filename to download. If present, the result will automatically be downloaded * @param invertY true to invert the picture in the Y dimension * @param toArrayBuffer true to convert the data to an ArrayBuffer (encoded as `mimeType`) instead of a base64 string * @param quality The quality of the image if lossy mimeType is used (e.g. image/jpeg, image/webp). See {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob | HTMLCanvasElement.toBlob()}'s `quality` parameter. */ export function DumpData(width, height, data, successCallback, mimeType = "image/png", fileName, invertY = false, toArrayBuffer = false, quality) { // 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); } }); } /** * Dispose the dump tools associated resources */ export function Dispose() { 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 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(); } }); ResourcesPromise = null; } /** * Object containing a set of static utilities functions to dump data from a canvas * @deprecated use functions */ export const DumpTools = { // eslint-disable-next-line @typescript-eslint/naming-convention DumpData, // eslint-disable-next-line @typescript-eslint/naming-convention DumpDataAsync, // eslint-disable-next-line @typescript-eslint/naming-convention DumpFramebuffer, // eslint-disable-next-line @typescript-eslint/naming-convention Dispose, }; /** * This will be executed automatically for UMD and es5. * If esm dev wants the side effects to execute they will have to run it manually * Once we build native modules those need to be exported. * @internal */ const InitSideEffects = () => { // References the dependencies. Tools.DumpData = DumpData; Tools.DumpDataAsync = DumpDataAsync; Tools.DumpFramebuffer = DumpFramebuffer; }; InitSideEffects(); //# sourceMappingURL=dumpTools.js.map