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.

130 lines (129 loc) 6.7 kB
import { _IsSideEffectImplemented, _WarnImport } from "../../Misc/devTools.js"; import { RawTexture2DArray } from "./rawTexture2DArray.js"; /** * Uploads a decoded image source (ImageBitmap, canvas, video, image element...) into a single layer of a 2D array texture. * This is the image-source counterpart to RawTexture2DArray.update, which only accepts raw bytes. * @param texture defines the 2D array texture to upload into * @param source defines the image source to upload * @param layer defines the array layer to upload into * @param options defines optional upload settings (invertY, premultiplyAlpha) */ export function UploadImageToTexture2DArrayLayer(texture, source, layer, options) { const internalTexture = texture.getInternalTexture(); if (!internalTexture) { throw new Error("Cannot upload to a 2D array texture that has no internal texture."); } if (!Number.isInteger(layer) || layer < 0 || layer >= texture.depth) { throw new Error(`Layer ${layer} is out of range for a 2D array texture with ${texture.depth} layers.`); } const scene = texture.getScene(); if (!scene) { throw new Error("Cannot upload to a 2D array texture that is not attached to a scene."); } const engine = scene.getEngine(); // updateTextureArrayLayerFromImageSource is an opt-in engine extension. When the consumer never // imported it, the augmented method is missing, so fail early with the standard side-effect import // message (matching the engine.rawTexture family) instead of a generic "is not a function" error. if (!_IsSideEffectImplemented(engine.updateTextureArrayLayerFromImageSource)) { throw _WarnImport("engine.texture2DArrayImageSource"); } engine.updateTextureArrayLayerFromImageSource(internalTexture, source, layer, options?.invertY ?? false, options?.premultiplyAlpha ?? false); } /** * Fetches an image from a url, decodes it and uploads it into a single layer of a 2D array texture. * @param texture defines the 2D array texture to upload into * @param url defines the url of the image to load * @param layer defines the array layer to upload into * @param options defines optional upload settings (invertY, premultiplyAlpha) * @returns a promise resolved once the layer has been uploaded */ export async function LoadImageToTexture2DArrayLayerAsync(texture, url, layer, options) { const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch image "${url}": ${response.status} ${response.statusText}`); } const blob = await response.blob(); const bitmap = await createImageBitmap(blob); try { UploadImageToTexture2DArrayLayer(texture, bitmap, layer, options); } finally { bitmap.close(); } } /** * Creates a 2D array texture and fills each layer from a list of image urls. * All images must share the same dimensions. * @param scene defines the hosting scene * @param urls defines the url of the image for each layer (at least one) * @param options defines optional creation and upload settings * @returns a promise resolved with the created RawTexture2DArray */ export async function CreateTexture2DArrayFromImageUrlsAsync(scene, urls, options) { // allSettled (not all): a rejected fetch/decode must not leak the layers that // already decoded. Promise.all rejects on the first failure and never enters the // try/finally below, orphaning every ImageBitmap that resolved. Close the // fulfilled ones here, then rethrow the original failure. const results = await Promise.allSettled(urls.map(async (url) => { const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch image "${url}": ${response.status} ${response.statusText}`); } return await createImageBitmap(await response.blob(), options?.imageBitmapOptions); })); const firstRejection = results.find((result) => result.status === "rejected"); if (firstRejection) { for (const result of results) { if (result.status === "fulfilled") { result.value.close(); } } throw firstRejection.reason; } const bitmaps = results.filter((result) => result.status === "fulfilled").map((result) => result.value); try { const width = bitmaps[0].width; const height = bitmaps[0].height; for (let index = 1; index < bitmaps.length; index++) { const bitmap = bitmaps[index]; if (bitmap.width !== width || bitmap.height !== height) { throw new Error(`All images must share the same dimensions. Image at index ${index} is ${bitmap.width}x${bitmap.height}, expected ${width}x${height}.`); } } const texture = new RawTexture2DArray(null, width, height, bitmaps.length, 5, scene, options?.generateMipMaps ?? true, options?.invertY ?? false, options?.samplingMode, options?.textureType); try { const uploadOptions = { invertY: options?.invertY ?? false, premultiplyAlpha: options?.premultiplyAlpha ?? false, }; // updateTextureArrayLayerFromImageSource rebuilds the whole mip chain on each upload when mips // are enabled. Uploading N layers that way is O(N) redundant mip generation, so suppress it // per-layer and let only the final upload regenerate the mips once — a single generateMipmap // covers every layer of the array. const internalTexture = texture.getInternalTexture(); const generateMipMaps = internalTexture?.generateMipMaps ?? false; if (internalTexture && generateMipMaps) { internalTexture.generateMipMaps = false; } for (let layer = 0; layer < bitmaps.length; layer++) { if (internalTexture && generateMipMaps && layer === bitmaps.length - 1) { internalTexture.generateMipMaps = true; } UploadImageToTexture2DArrayLayer(texture, bitmaps[layer], layer, uploadOptions); } return texture; } catch (error) { // A layer upload can throw (out-of-range layer, missing engine extension, backend upload // failure). Dispose the freshly allocated GPU texture so it does not leak, then rethrow. texture.dispose(); throw error; } } finally { for (let index = 0; index < bitmaps.length; index++) { bitmaps[index].close(); } } } //# sourceMappingURL=rawTexture2DArray.functions.js.map