@luma.gl/engine
Version:
3D Engine Components for luma.gl
119 lines • 3.97 kB
JavaScript
// luma.gl, MIT license
// Copyright (c) vis.gl contributors
import { loadImageBitmap } from "../application-utils/load-file.js";
import { uid } from "../utils/uid.js";
/**
* It is very convenient to be able to initialize textures with promises
* This can add considerable complexity to the Texture class, and doesn't
* fit with the immutable nature of WebGPU resources.
* Instead, luma.gl offers async textures as a separate class.
*/
export class AsyncTexture {
device;
id;
// TODO - should we type these as possibly `null`? It will make usage harder?
// @ts-expect-error
texture;
// @ts-expect-error
sampler;
// @ts-expect-error
view;
ready;
isReady = false;
destroyed = false;
resolveReady = () => { };
rejectReady = () => { };
get [Symbol.toStringTag]() {
return 'AsyncTexture';
}
toString() {
return `AsyncTexture:"${this.id}"(${this.isReady ? 'ready' : 'loading'})`;
}
constructor(device, props) {
this.device = device;
this.id = props.id || uid('async-texture');
// this.id = typeof props?.data === 'string' ? props.data.slice(-20) : uid('async-texture');
// Signature: new AsyncTexture(device, {data: url})
if (typeof props?.data === 'string' && props.dimension === '2d') {
props = { ...props, data: loadImageBitmap(props.data) };
}
this.ready = new Promise((resolve, reject) => {
this.resolveReady = () => {
this.isReady = true;
resolve();
};
this.rejectReady = reject;
});
this.initAsync(props);
}
async initAsync(props) {
const asyncData = props.data;
let data;
try {
data = await awaitAllPromises(asyncData);
}
catch (error) {
this.rejectReady(error);
}
// Check that we haven't been destroyed while waiting for texture data to load
if (this.destroyed) {
return;
}
// Now we can actually create the texture
// @ts-expect-error Discriminated union
const syncProps = { ...props, data };
this.texture = this.device.createTexture(syncProps);
this.sampler = this.texture.sampler;
this.view = this.texture.view;
this.isReady = true;
this.resolveReady();
}
destroy() {
if (this.texture) {
this.texture.destroy();
// @ts-expect-error
this.texture = null;
}
this.destroyed = true;
}
/**
* Textures are immutable and cannot be resized after creation,
* but we can create a similar texture with the same parameters but a new size.
* @note Does not copy contents of the texture
* @todo Abort pending promise and create a texture with the new size?
*/
resize(size) {
if (!this.isReady) {
throw new Error('Cannot resize texture before it is ready');
}
if (size.width === this.texture.width && size.height === this.texture.height) {
return false;
}
if (this.texture) {
const texture = this.texture;
this.texture = texture.clone(size);
texture.destroy();
}
return true;
}
}
// HELPERS
/** Resolve all promises in a nested data structure */
async function awaitAllPromises(x) {
x = await x;
if (Array.isArray(x)) {
return await Promise.all(x.map(awaitAllPromises));
}
if (x && typeof x === 'object' && x.constructor === Object) {
const object = x;
const values = await Promise.all(Object.values(object));
const keys = Object.keys(object);
const resolvedObject = {};
for (let i = 0; i < keys.length; i++) {
resolvedObject[keys[i]] = values[i];
}
return resolvedObject;
}
return x;
}
//# sourceMappingURL=async-texture.js.map