@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
JavaScript
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