@loaders.gl/textures
Version:
Framework-independent loaders for compressed and super compressed (basis) textures
981 lines (964 loc) • 31.5 kB
JavaScript
;
(() => {
// ../loader-utils/src/lib/module-utils/js-module-utils.ts
function registerJSModules(modules) {
globalThis.loaders ||= {};
globalThis.loaders.modules ||= {};
Object.assign(globalThis.loaders.modules, modules);
}
function getJSModuleOrNull(name) {
const module = globalThis.loaders?.modules?.[name];
return module || null;
}
// ../worker-utils/src/lib/env-utils/version.ts
function getVersion() {
if (!globalThis._loadersgl_?.version) {
globalThis._loadersgl_ = globalThis._loadersgl_ || {};
if (false) {
console.warn(
"loaders.gl: The __VERSION__ variable is not injected using babel plugin. Latest unstable workers would be fetched from the CDN."
);
globalThis._loadersgl_.version = NPM_TAG;
warningIssued = true;
} else {
globalThis._loadersgl_.version = "4.4.2";
}
}
return globalThis._loadersgl_.version;
}
var VERSION = getVersion();
// ../worker-utils/src/lib/env-utils/assert.ts
function assert(condition, message) {
if (!condition) {
throw new Error(message || "loaders.gl assertion failed.");
}
}
// ../worker-utils/src/lib/env-utils/globals.ts
var globals = {
self: typeof self !== "undefined" && self,
window: typeof window !== "undefined" && window,
global: typeof global !== "undefined" && global,
document: typeof document !== "undefined" && document
};
var self_ = globals.self || globals.window || globals.global || {};
var window_ = globals.window || globals.self || globals.global || {};
var global_ = globals.global || globals.self || globals.window || {};
var document_ = globals.document || {};
var isBrowser = (
// @ts-ignore process.browser
typeof process !== "object" || String(process) !== "[object process]" || process.browser
);
var isWorker = typeof importScripts === "function";
var isMobile = typeof window !== "undefined" && typeof window.orientation !== "undefined";
var matches = typeof process !== "undefined" && process.version && /v([0-9]*)/.exec(process.version);
var nodeVersion = matches && parseFloat(matches[1]) || 0;
// ../worker-utils/src/lib/node/worker_threads-browser.ts
var parentPort = null;
// ../worker-utils/src/lib/worker-utils/get-transfer-list.ts
function getTransferList(object, recursive = true, transfers) {
const transfersSet = transfers || /* @__PURE__ */ new Set();
if (!object) {
} else if (isTransferable(object)) {
transfersSet.add(object);
} else if (isTransferable(object.buffer)) {
transfersSet.add(object.buffer);
} else if (ArrayBuffer.isView(object)) {
} else if (recursive && typeof object === "object") {
for (const key in object) {
getTransferList(object[key], recursive, transfersSet);
}
}
return transfers === void 0 ? Array.from(transfersSet) : [];
}
function isTransferable(object) {
if (!object) {
return false;
}
if (object instanceof ArrayBuffer) {
return true;
}
if (typeof MessagePort !== "undefined" && object instanceof MessagePort) {
return true;
}
if (typeof ImageBitmap !== "undefined" && object instanceof ImageBitmap) {
return true;
}
if (typeof OffscreenCanvas !== "undefined" && object instanceof OffscreenCanvas) {
return true;
}
return false;
}
// ../worker-utils/src/lib/worker-farm/worker-body.ts
async function getParentPort() {
return parentPort;
}
var onMessageWrapperMap = /* @__PURE__ */ new Map();
var WorkerBody = class {
/** Check that we are actually in a worker thread */
static async inWorkerThread() {
return typeof self !== "undefined" || Boolean(await getParentPort());
}
/*
* (type: WorkerMessageType, payload: WorkerMessagePayload) => any
*/
static set onmessage(onMessage) {
async function handleMessage(message) {
const parentPort2 = await getParentPort();
const { type, payload } = parentPort2 ? message : message.data;
onMessage(type, payload);
}
getParentPort().then((parentPort2) => {
if (parentPort2) {
parentPort2.on("message", (message) => {
handleMessage(message);
});
parentPort2.on("exit", () => console.debug("Node worker closing"));
} else {
globalThis.onmessage = handleMessage;
}
});
}
static async addEventListener(onMessage) {
let onMessageWrapper = onMessageWrapperMap.get(onMessage);
if (!onMessageWrapper) {
onMessageWrapper = async (message) => {
if (!isKnownMessage(message)) {
return;
}
const parentPort3 = await getParentPort();
const { type, payload } = parentPort3 ? message : message.data;
onMessage(type, payload);
};
}
const parentPort2 = await getParentPort();
if (parentPort2) {
console.error("not implemented");
} else {
globalThis.addEventListener("message", onMessageWrapper);
}
}
static async removeEventListener(onMessage) {
const onMessageWrapper = onMessageWrapperMap.get(onMessage);
onMessageWrapperMap.delete(onMessage);
const parentPort2 = await getParentPort();
if (parentPort2) {
console.error("not implemented");
} else {
globalThis.removeEventListener("message", onMessageWrapper);
}
}
/**
* Send a message from a worker to creating thread (main thread)
* @param type
* @param payload
*/
static async postMessage(type, payload) {
const data = { source: "loaders.gl", type, payload };
const transferList = getTransferList(payload);
const parentPort2 = await getParentPort();
if (parentPort2) {
parentPort2.postMessage(data, transferList);
} else {
globalThis.postMessage(data, transferList);
}
}
};
function isKnownMessage(message) {
const { type, data } = message;
return type === "message" && data && typeof data.source === "string" && data.source.startsWith("loaders.gl");
}
// ../worker-utils/src/lib/library-utils/library-utils.ts
var loadLibraryPromises = {};
function extractLoadLibraryOptions(options = {}) {
const useLocalLibraries = options.useLocalLibraries ?? options.core?.useLocalLibraries;
const CDN = options.CDN ?? options.core?.CDN;
const modules = options.modules;
return {
...useLocalLibraries !== void 0 ? { useLocalLibraries } : {},
...CDN !== void 0 ? { CDN } : {},
...modules !== void 0 ? { modules } : {}
};
}
async function loadLibrary(libraryUrl, moduleName = null, options = {}, libraryName = null) {
if (moduleName) {
libraryUrl = getLibraryUrl(libraryUrl, moduleName, options, libraryName);
}
loadLibraryPromises[libraryUrl] = // eslint-disable-next-line @typescript-eslint/no-misused-promises
loadLibraryPromises[libraryUrl] || loadLibraryFromFile(libraryUrl);
return await loadLibraryPromises[libraryUrl];
}
function getLibraryUrl(library, moduleName, options = {}, libraryName = null) {
if (options?.core) {
throw new Error("loadLibrary: options.core must be pre-normalized");
}
if (!options.useLocalLibraries && library.startsWith("http")) {
return library;
}
libraryName = libraryName || library;
const modules = options.modules || {};
if (modules[libraryName]) {
return modules[libraryName];
}
if (!isBrowser) {
return `modules/${moduleName}/dist/libs/${libraryName}`;
}
if (options.CDN) {
assert(options.CDN.startsWith("http"));
return `${options.CDN}/${moduleName}@${VERSION}/dist/libs/${libraryName}`;
}
if (isWorker) {
return `../src/libs/${libraryName}`;
}
return `modules/${moduleName}/src/libs/${libraryName}`;
}
async function loadLibraryFromFile(libraryUrl) {
if (libraryUrl.endsWith("wasm")) {
return await loadAsArrayBuffer(libraryUrl);
}
if (!isBrowser) {
const { requireFromFile } = globalThis.loaders || {};
try {
const result = await requireFromFile?.(libraryUrl);
if (result || !libraryUrl.includes("/dist/libs/")) {
return result;
}
return await requireFromFile?.(libraryUrl.replace("/dist/libs/", "/src/libs/"));
} catch (error) {
if (libraryUrl.includes("/dist/libs/")) {
try {
return await requireFromFile?.(libraryUrl.replace("/dist/libs/", "/src/libs/"));
} catch {
}
}
console.error(error);
return null;
}
}
if (isWorker) {
return importScripts(libraryUrl);
}
const scriptSource = await loadAsText(libraryUrl);
return loadLibraryFromString(scriptSource, libraryUrl);
}
function loadLibraryFromString(scriptSource, id) {
if (!isBrowser) {
const { requireFromString } = globalThis.loaders || {};
return requireFromString?.(scriptSource, id);
}
if (isWorker) {
eval.call(globalThis, scriptSource);
return null;
}
const script = document.createElement("script");
script.id = id;
try {
script.appendChild(document.createTextNode(scriptSource));
} catch (e) {
script.text = scriptSource;
}
document.body.appendChild(script);
return null;
}
async function loadAsArrayBuffer(url) {
const { readFileAsArrayBuffer } = globalThis.loaders || {};
if (isBrowser || !readFileAsArrayBuffer || url.startsWith("http")) {
const response = await fetch(url);
return await response.arrayBuffer();
}
try {
return await readFileAsArrayBuffer(url);
} catch {
if (url.includes("/dist/libs/")) {
return await readFileAsArrayBuffer(url.replace("/dist/libs/", "/src/libs/"));
}
throw new Error(`Failed to load ArrayBuffer from ${url}`);
}
}
async function loadAsText(url) {
const { readFileAsText } = globalThis.loaders || {};
if (isBrowser || !readFileAsText || url.startsWith("http")) {
const response = await fetch(url);
return await response.text();
}
try {
return await readFileAsText(url);
} catch {
if (url.includes("/dist/libs/")) {
return await readFileAsText(url.replace("/dist/libs/", "/src/libs/"));
}
throw new Error(`Failed to load text from ${url}`);
}
}
// ../loader-utils/src/lib/worker-loader-utils/create-loader-worker.ts
var requestId = 0;
async function createLoaderWorker(loader) {
if (!await WorkerBody.inWorkerThread()) {
return;
}
WorkerBody.onmessage = async (type, payload) => {
switch (type) {
case "process":
try {
const { input, options = {}, context = {} } = payload;
const result = await parseData({
loader,
arrayBuffer: input,
options,
// @ts-expect-error fetch missing
context: {
...context,
_parse: parseOnMainThread
}
});
WorkerBody.postMessage("done", { result });
} catch (error) {
const message = error instanceof Error ? error.message : "";
WorkerBody.postMessage("error", { error: message });
}
break;
default:
}
};
}
function parseOnMainThread(arrayBuffer, loader, options, context) {
return new Promise((resolve, reject) => {
const id = requestId++;
const onMessage = (type, payload2) => {
if (payload2.id !== id) {
return;
}
switch (type) {
case "done":
WorkerBody.removeEventListener(onMessage);
resolve(payload2.result);
break;
case "error":
WorkerBody.removeEventListener(onMessage);
reject(payload2.error);
break;
default:
}
};
WorkerBody.addEventListener(onMessage);
const payload = { id, input: arrayBuffer, options };
WorkerBody.postMessage("process", payload);
});
}
async function parseData({
loader,
arrayBuffer,
options,
context
}) {
let data;
let parser;
if (loader.parseSync || loader.parse) {
data = arrayBuffer;
parser = loader.parseSync || loader.parse;
} else if (loader.parseTextSync) {
const textDecoder = new TextDecoder();
data = textDecoder.decode(arrayBuffer);
parser = loader.parseTextSync;
} else {
throw new Error(`Could not load data with ${loader.name} loader`);
}
options = {
...options,
modules: loader && loader.options && loader.options.modules || {},
core: {
...options.core,
worker: false
}
};
return await parser(data, { ...options }, context, loader);
}
// src/lib/utils/version.ts
var VERSION2 = true ? "4.4.2" : "latest";
// src/lib/parsers/basis-module-loader.ts
var BASIS_EXTERNAL_LIBRARIES = {
/** Basis transcoder, javascript wrapper part */
TRANSCODER: "basis_transcoder.js",
/** Basis transcoder, compiled web assembly part */
TRANSCODER_WASM: "basis_transcoder.wasm",
/** Basis encoder, javascript wrapper part */
ENCODER: "basis_encoder.js",
/** Basis encoder, compiled web assembly part */
ENCODER_WASM: "basis_encoder.wasm"
};
var loadBasisTranscoderPromise;
async function loadBasisTranscoderModule(options) {
registerJSModules(options.modules);
const basis = getJSModuleOrNull("basis");
if (basis) {
return basis;
}
loadBasisTranscoderPromise ||= loadBasisTranscoder(options);
return await loadBasisTranscoderPromise;
}
async function loadBasisTranscoder(options) {
let BASIS = null;
let wasmBinary = null;
[BASIS, wasmBinary] = await Promise.all([
await loadLibrary(BASIS_EXTERNAL_LIBRARIES.TRANSCODER, "textures", options),
await loadLibrary(BASIS_EXTERNAL_LIBRARIES.TRANSCODER_WASM, "textures", options)
]);
BASIS = BASIS || globalThis.BASIS;
return await initializeBasisTranscoderModule(BASIS, wasmBinary);
}
function initializeBasisTranscoderModule(BasisModule, wasmBinary) {
const options = {};
if (wasmBinary) {
options.wasmBinary = wasmBinary;
}
return new Promise((resolve) => {
BasisModule(options).then((module) => {
const { BasisFile, initializeBasis } = module;
initializeBasis();
resolve({ BasisFile });
});
});
}
var loadBasisEncoderPromise;
async function loadBasisEncoderModule(options) {
const modules = options.modules || {};
if (modules.basisEncoder) {
return modules.basisEncoder;
}
loadBasisEncoderPromise = loadBasisEncoderPromise || loadBasisEncoder(options);
return await loadBasisEncoderPromise;
}
async function loadBasisEncoder(options) {
let BASIS_ENCODER = null;
let wasmBinary = null;
[BASIS_ENCODER, wasmBinary] = await Promise.all([
await loadLibrary(BASIS_EXTERNAL_LIBRARIES.ENCODER, "textures", options),
await loadLibrary(BASIS_EXTERNAL_LIBRARIES.ENCODER_WASM, "textures", options)
]);
BASIS_ENCODER = BASIS_ENCODER || globalThis.BASIS;
return await initializeBasisEncoderModule(BASIS_ENCODER, wasmBinary);
}
function initializeBasisEncoderModule(BasisEncoderModule, wasmBinary) {
const options = {};
if (wasmBinary) {
options.wasmBinary = wasmBinary;
}
return new Promise((resolve) => {
BasisEncoderModule(options).then((module) => {
const { BasisFile, KTX2File, initializeBasis, BasisEncoder } = module;
initializeBasis();
resolve({ BasisFile, KTX2File, BasisEncoder });
});
});
}
// src/lib/gl-extensions.ts
var GL_RGBA4 = 32854;
var GL_RGBA8 = 32856;
var GL_RGB565 = 36194;
var GL_COMPRESSED_RGB_S3TC_DXT1_EXT = 33776;
var GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 33779;
var GL_COMPRESSED_RGBA8_ETC2_EAC = 37493;
var GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 35840;
var GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 35842;
var GL_COMPRESSED_RGB_ETC1_WEBGL = 36196;
var GL_COMPRESSED_RGB_ATC_WEBGL = 35986;
var GL_COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL = 34798;
var GL_COMPRESSED_RGBA_ASTC_4x4_KHR = 37808;
var GL_COMPRESSED_RED_RGTC1_EXT = 36283;
var GL_COMPRESSED_RED_GREEN_RGTC2_EXT = 36285;
var GL_COMPRESSED_RGBA_BPTC_UNORM_EXT = 36492;
// src/lib/utils/detect-supported-texture-formats.ts
var BROWSER_PREFIXES = ["", "WEBKIT_", "MOZ_"];
var WEBGL_TEXTURE_FORMATS = {
/* eslint-disable camelcase */
WEBGL_compressed_texture_s3tc: [
"bc1-rgb-unorm-webgl",
"bc1-rgba-unorm",
"bc2-rgba-unorm",
"bc3-rgba-unorm"
],
WEBGL_compressed_texture_s3tc_srgb: [
"bc1-rgb-unorm-srgb-webgl",
"bc1-rgba-unorm-srgb",
"bc2-rgba-unorm-srgb",
"bc3-rgba-unorm-srgb"
],
EXT_texture_compression_rgtc: ["bc4-r-unorm", "bc4-r-snorm", "bc5-rg-unorm", "bc5-rg-snorm"],
EXT_texture_compression_bptc: [
"bc6h-rgb-ufloat",
"bc6h-rgb-float",
"bc7-rgba-unorm",
"bc7-rgba-unorm-srgb"
],
WEBGL_compressed_texture_etc1: ["etc1-rgb-unorm-webgl"],
WEBGL_compressed_texture_etc: [
"etc2-rgb8unorm",
"etc2-rgb8unorm-srgb",
"etc2-rgb8a1unorm",
"etc2-rgb8a1unorm-srgb",
"etc2-rgba8unorm",
"etc2-rgba8unorm-srgb",
"eac-r11unorm",
"eac-r11snorm",
"eac-rg11unorm",
"eac-rg11snorm"
],
WEBGL_compressed_texture_pvrtc: [
"pvrtc-rgb4unorm-webgl",
"pvrtc-rgba4unorm-webgl",
"pvrtc-rgb2unorm-webgl",
"pvrtc-rgba2unorm-webgl"
],
WEBGL_compressed_texture_atc: [
"atc-rgb-unorm-webgl",
"atc-rgba-unorm-webgl",
"atc-rgbai-unorm-webgl"
],
WEBGL_compressed_texture_astc: [
"astc-4x4-unorm",
"astc-4x4-unorm-srgb",
"astc-5x4-unorm",
"astc-5x4-unorm-srgb",
"astc-5x5-unorm",
"astc-5x5-unorm-srgb",
"astc-6x5-unorm",
"astc-6x5-unorm-srgb",
"astc-6x6-unorm",
"astc-6x6-unorm-srgb",
"astc-8x5-unorm",
"astc-8x5-unorm-srgb",
"astc-8x6-unorm",
"astc-8x6-unorm-srgb",
"astc-8x8-unorm",
"astc-8x8-unorm-srgb",
"astc-10x5-unorm",
"astc-10x5-unorm-srgb",
"astc-10x6-unorm",
"astc-10x6-unorm-srgb",
"astc-10x8-unorm",
"astc-10x8-unorm-srgb",
"astc-10x10-unorm",
"astc-10x10-unorm-srgb",
"astc-12x10-unorm",
"astc-12x10-unorm-srgb",
"astc-12x12-unorm",
"astc-12x12-unorm-srgb"
]
/* eslint-enable camelcase */
};
var textureFormats = null;
function detectSupportedTextureFormats(gl) {
if (!textureFormats) {
gl = gl || getWebGLContext() || void 0;
textureFormats = /* @__PURE__ */ new Set();
for (const prefix of BROWSER_PREFIXES) {
for (const extension in WEBGL_TEXTURE_FORMATS) {
if (gl && gl.getExtension(`${prefix}${extension}`)) {
for (const textureFormat of WEBGL_TEXTURE_FORMATS[extension]) {
textureFormats.add(textureFormat);
}
}
}
}
}
return textureFormats;
}
function getWebGLContext() {
try {
const canvas = document.createElement("canvas");
return canvas.getContext("webgl");
} catch (error) {
return null;
}
}
// src/lib/parsers/parse-ktx.ts
var KTX2_ID = [
// '´', 'K', 'T', 'X', '2', '0', 'ª', '\r', '\n', '\x1A', '\n'
171,
75,
84,
88,
32,
50,
48,
187,
13,
10,
26,
10
];
function isKTX(data) {
const id = new Uint8Array(data);
const notKTX = id.byteLength < KTX2_ID.length || id[0] !== KTX2_ID[0] || // '´'
id[1] !== KTX2_ID[1] || // 'K'
id[2] !== KTX2_ID[2] || // 'T'
id[3] !== KTX2_ID[3] || // 'X'
id[4] !== KTX2_ID[4] || // ' '
id[5] !== KTX2_ID[5] || // '2'
id[6] !== KTX2_ID[6] || // '0'
id[7] !== KTX2_ID[7] || // 'ª'
id[8] !== KTX2_ID[8] || // '\r'
id[9] !== KTX2_ID[9] || // '\n'
id[10] !== KTX2_ID[10] || // '\x1A'
id[11] !== KTX2_ID[11];
return !notKTX;
}
// src/lib/parsers/parse-basis.ts
var basisTranscodingLock = Promise.resolve();
var BASIS_FORMAT_TO_OUTPUT_OPTIONS = {
etc1: {
basisFormat: 0,
compressed: true,
format: GL_COMPRESSED_RGB_ETC1_WEBGL,
textureFormat: "etc1-rgb-unorm-webgl"
},
etc2: {
basisFormat: 1,
compressed: true,
format: GL_COMPRESSED_RGBA8_ETC2_EAC,
textureFormat: "etc2-rgba8unorm"
},
bc1: {
basisFormat: 2,
compressed: true,
format: GL_COMPRESSED_RGB_S3TC_DXT1_EXT,
textureFormat: "bc1-rgb-unorm-webgl"
},
bc3: {
basisFormat: 3,
compressed: true,
format: GL_COMPRESSED_RGBA_S3TC_DXT5_EXT,
textureFormat: "bc3-rgba-unorm"
},
bc4: {
basisFormat: 4,
compressed: true,
format: GL_COMPRESSED_RED_RGTC1_EXT,
textureFormat: "bc4-r-unorm"
},
bc5: {
basisFormat: 5,
compressed: true,
format: GL_COMPRESSED_RED_GREEN_RGTC2_EXT,
textureFormat: "bc5-rg-unorm"
},
"bc7-m6-opaque-only": {
basisFormat: 6,
compressed: true,
format: GL_COMPRESSED_RGBA_BPTC_UNORM_EXT,
textureFormat: "bc7-rgba-unorm"
},
"bc7-m5": {
basisFormat: 7,
compressed: true,
format: GL_COMPRESSED_RGBA_BPTC_UNORM_EXT,
textureFormat: "bc7-rgba-unorm"
},
"pvrtc1-4-rgb": {
basisFormat: 8,
compressed: true,
format: GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG,
textureFormat: "pvrtc-rgb4unorm-webgl"
},
"pvrtc1-4-rgba": {
basisFormat: 9,
compressed: true,
format: GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG,
textureFormat: "pvrtc-rgba4unorm-webgl"
},
"astc-4x4": {
basisFormat: 10,
compressed: true,
format: GL_COMPRESSED_RGBA_ASTC_4x4_KHR,
textureFormat: "astc-4x4-unorm"
},
"atc-rgb": {
basisFormat: 11,
compressed: true,
format: GL_COMPRESSED_RGB_ATC_WEBGL,
textureFormat: "atc-rgb-unorm-webgl"
},
"atc-rgba-interpolated-alpha": {
basisFormat: 12,
compressed: true,
format: GL_COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL,
textureFormat: "atc-rgbai-unorm-webgl"
},
rgba32: {
basisFormat: 13,
compressed: false,
format: GL_RGBA8,
textureFormat: "rgba8unorm"
},
rgb565: {
basisFormat: 14,
compressed: false,
format: GL_RGB565,
textureFormat: "rgb565unorm-webgl"
},
bgr565: {
basisFormat: 15,
compressed: false,
format: GL_RGB565,
textureFormat: "rgb565unorm-webgl"
},
rgba4444: {
basisFormat: 16,
compressed: false,
format: GL_RGBA4,
textureFormat: "rgba4unorm-webgl"
}
};
var BASIS_FORMATS = Object.freeze(
Object.keys(BASIS_FORMAT_TO_OUTPUT_OPTIONS)
);
async function withBasisTranscodingLock(transcode) {
const previousLock = basisTranscodingLock;
let releaseLock;
basisTranscodingLock = new Promise((resolve) => {
releaseLock = resolve;
});
await previousLock;
try {
return await transcode();
} finally {
releaseLock();
}
}
async function parseBasis(data, options = {}) {
const loadLibraryOptions = extractLoadLibraryOptions(options);
return await withBasisTranscodingLock(async () => {
if (!options.basis?.containerFormat || options.basis.containerFormat === "auto") {
if (isKTX(data)) {
const fileConstructors = await loadBasisEncoderModule(loadLibraryOptions);
return parseKTX2File(fileConstructors.KTX2File, data, options);
}
const { BasisFile } = await loadBasisTranscoderModule(loadLibraryOptions);
return parseBasisFile(BasisFile, data, options);
}
switch (options.basis.module) {
case "encoder":
const fileConstructors = await loadBasisEncoderModule(loadLibraryOptions);
switch (options.basis.containerFormat) {
case "ktx2":
return parseKTX2File(fileConstructors.KTX2File, data, options);
case "basis":
default:
return parseBasisFile(fileConstructors.BasisFile, data, options);
}
case "transcoder":
default:
const { BasisFile } = await loadBasisTranscoderModule(loadLibraryOptions);
return parseBasisFile(BasisFile, data, options);
}
});
}
function parseBasisFile(BasisFile, data, options) {
const basisFile = new BasisFile(new Uint8Array(data));
try {
if (!basisFile.startTranscoding()) {
throw new Error("Failed to start basis transcoding");
}
const imageCount = basisFile.getNumImages();
const images = [];
for (let imageIndex = 0; imageIndex < imageCount; imageIndex++) {
const levelsCount = basisFile.getNumLevels(imageIndex);
const levels = [];
for (let levelIndex = 0; levelIndex < levelsCount; levelIndex++) {
levels.push(transcodeImage(basisFile, imageIndex, levelIndex, options));
}
images.push(levels);
}
return images;
} finally {
basisFile.close();
basisFile.delete();
}
}
function transcodeImage(basisFile, imageIndex, levelIndex, options) {
const width = basisFile.getImageWidth(imageIndex, levelIndex);
const height = basisFile.getImageHeight(imageIndex, levelIndex);
const hasAlpha = basisFile.getHasAlpha(
/* imageIndex, levelIndex */
);
const { compressed, format, basisFormat, textureFormat } = getBasisOptions(options, hasAlpha);
const decodedSize = basisFile.getImageTranscodedSizeInBytes(imageIndex, levelIndex, basisFormat);
const decodedData = new Uint8Array(decodedSize);
if (!basisFile.transcodeImage(decodedData, imageIndex, levelIndex, basisFormat, 0, 0)) {
throw new Error("failed to start Basis transcoding");
}
return {
// standard loaders.gl image category payload
shape: "texture-level",
width,
height,
data: decodedData,
compressed,
...format !== void 0 ? { format } : {},
...textureFormat !== void 0 ? { textureFormat } : {},
// Additional fields
// Add levelSize field.
hasAlpha
};
}
function parseKTX2File(KTX2File, data, options) {
const ktx2File = new KTX2File(new Uint8Array(data));
try {
if (!ktx2File.startTranscoding()) {
throw new Error("failed to start KTX2 transcoding");
}
const levelsCount = ktx2File.getLevels();
const levels = [];
for (let levelIndex = 0; levelIndex < levelsCount; levelIndex++) {
levels.push(transcodeKTX2Image(ktx2File, levelIndex, options));
}
return [levels];
} finally {
ktx2File.close();
ktx2File.delete();
}
}
function transcodeKTX2Image(ktx2File, levelIndex, options) {
const { alphaFlag, height, width } = ktx2File.getImageLevelInfo(levelIndex, 0, 0);
const { compressed, format, basisFormat, textureFormat } = getBasisOptions(options, alphaFlag);
const decodedSize = ktx2File.getImageTranscodedSizeInBytes(
levelIndex,
0,
0,
basisFormat
);
const decodedData = new Uint8Array(decodedSize);
if (!ktx2File.transcodeImage(
decodedData,
levelIndex,
0,
0,
basisFormat,
0,
-1,
-1
/* channel1 */
)) {
throw new Error("Failed to transcode KTX2 image");
}
return {
// standard loaders.gl image category payload
shape: "texture-level",
width,
height,
data: decodedData,
compressed,
...format !== void 0 ? { format } : {},
...textureFormat !== void 0 ? { textureFormat } : {},
// Additional fields
levelSize: decodedSize,
hasAlpha: alphaFlag
};
}
function getBasisOptions(options, hasAlpha) {
let format = options.basis?.format || "auto";
if (format === "auto") {
format = options.basis?.supportedTextureFormats ? selectSupportedBasisFormat(options.basis.supportedTextureFormats) : selectSupportedBasisFormat();
}
if (typeof format === "object") {
format = hasAlpha ? format.alpha : format.noAlpha;
}
const normalizedFormat = format.toLowerCase();
const basisOutputOptions = BASIS_FORMAT_TO_OUTPUT_OPTIONS[normalizedFormat];
if (!basisOutputOptions) {
throw new Error(`Unknown Basis format ${format}`);
}
return basisOutputOptions;
}
function selectSupportedBasisFormat(supportedTextureFormats = detectSupportedTextureFormats()) {
const textureFormats2 = new Set(supportedTextureFormats);
if (hasSupportedTextureFormat(textureFormats2, ["astc-4x4-unorm", "astc-4x4-unorm-srgb"])) {
return "astc-4x4";
} else if (hasSupportedTextureFormat(textureFormats2, ["bc7-rgba-unorm", "bc7-rgba-unorm-srgb"])) {
return {
alpha: "bc7-m5",
noAlpha: "bc7-m6-opaque-only"
};
} else if (hasSupportedTextureFormat(textureFormats2, [
"bc1-rgb-unorm-webgl",
"bc1-rgb-unorm-srgb-webgl",
"bc1-rgba-unorm",
"bc1-rgba-unorm-srgb",
"bc2-rgba-unorm",
"bc2-rgba-unorm-srgb",
"bc3-rgba-unorm",
"bc3-rgba-unorm-srgb"
])) {
return {
alpha: "bc3",
noAlpha: "bc1"
};
} else if (hasSupportedTextureFormat(textureFormats2, [
"pvrtc-rgb4unorm-webgl",
"pvrtc-rgba4unorm-webgl",
"pvrtc-rgb2unorm-webgl",
"pvrtc-rgba2unorm-webgl"
])) {
return {
alpha: "pvrtc1-4-rgba",
noAlpha: "pvrtc1-4-rgb"
};
} else if (hasSupportedTextureFormat(textureFormats2, [
"etc2-rgb8unorm",
"etc2-rgb8unorm-srgb",
"etc2-rgb8a1unorm",
"etc2-rgb8a1unorm-srgb",
"etc2-rgba8unorm",
"etc2-rgba8unorm-srgb",
"eac-r11unorm",
"eac-r11snorm",
"eac-rg11unorm",
"eac-rg11snorm"
])) {
return "etc2";
} else if (textureFormats2.has("etc1-rgb-unorm-webgl")) {
return "etc1";
} else if (hasSupportedTextureFormat(textureFormats2, [
"atc-rgb-unorm-webgl",
"atc-rgba-unorm-webgl",
"atc-rgbai-unorm-webgl"
])) {
return {
alpha: "atc-rgba-interpolated-alpha",
noAlpha: "atc-rgb"
};
}
return "rgb565";
}
function hasSupportedTextureFormat(supportedTextureFormats, candidateTextureFormats) {
return candidateTextureFormats.some(
(textureFormat) => supportedTextureFormats.has(textureFormat)
);
}
// src/basis-loader.ts
var BasisWorkerLoader = {
dataType: null,
batchType: null,
name: "Basis",
id: "basis",
module: "textures",
version: VERSION2,
worker: true,
extensions: ["basis", "ktx2"],
mimeTypes: ["application/octet-stream", "image/ktx2"],
tests: ["sB"],
binary: true,
options: {
basis: {
format: "auto",
containerFormat: "auto",
module: "transcoder"
}
}
};
var BasisLoader = {
...BasisWorkerLoader,
parse: parseBasis
};
// src/workers/basis-worker.ts
createLoaderWorker(BasisLoader);
})();