nanogl-gltf
Version:
117 lines (116 loc) • 4.76 kB
JavaScript
import Image, { filterHasMipmap } from "../../elements/Image";
import Texture from "../../elements/Texture";
import { createNativeSignal } from "../../lib/cancellation";
import GltfTypes from "../../types/GltfTypes";
const EXT_ID = 'KHR_texture_basisu';
class BasisImage extends Image {
constructor(_decoder) {
super();
this._decoder = _decoder;
this._decodePromise = null;
}
get hasAlpha() {
return false;
}
async loadImage(gltfLoader) {
this._basisFile = await this.loadBuffer(gltfLoader.abortSignal);
}
async loadBuffer(abortSignal) {
if (this.bufferView) {
// mimeType is guaranted here
const ptr = this.bufferView.getByteOffset();
return this.bufferView.buffer._bytes.slice(ptr, ptr + this.bufferView.byteLength);
}
else {
// assume uri is defained as uri or data uri
const signal = createNativeSignal(abortSignal);
const request = await fetch(this.resolvedUri, { signal });
const buffer = await request.arrayBuffer();
return buffer;
}
}
decode(gl) {
if (this._decodePromise === null) {
this._decodePromise = this._decoder.decode(gl, this._basisFile);
}
return this._decodePromise;
}
async setupTexture(texture, wrapS, wrapT, minFilter, magFilter) {
const gl = texture.gl;
const { buffer, webglFormat, mipLevels } = await this.decode(gl);
gl.bindTexture(gl.TEXTURE_2D, texture.id);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, mipLevels.length > 1 || webglFormat.uncompressed ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);
let levelData = null;
for (let mipLevel of mipLevels) {
if (!webglFormat.uncompressed) {
levelData = new Uint8Array(buffer, mipLevel.offset, mipLevel.size);
gl.compressedTexImage2D(gl.TEXTURE_2D, mipLevel.level, webglFormat.format, mipLevel.width, mipLevel.height, 0, levelData);
}
else {
switch (webglFormat.type) {
case WebGLRenderingContext.UNSIGNED_SHORT_4_4_4_4:
case WebGLRenderingContext.UNSIGNED_SHORT_5_5_5_1:
case WebGLRenderingContext.UNSIGNED_SHORT_5_6_5:
levelData = new Uint16Array(buffer, mipLevel.offset, mipLevel.size / 2);
break;
default:
levelData = new Uint8Array(buffer, mipLevel.offset, mipLevel.size);
break;
}
gl.texImage2D(gl.TEXTURE_2D, mipLevel.level, webglFormat.format, mipLevel.width, mipLevel.height, 0, webglFormat.format, webglFormat.type, levelData);
}
}
if (filterHasMipmap(minFilter) && webglFormat.uncompressed && mipLevels.length == 1) {
gl.generateMipmap(gl.TEXTURE_2D);
}
}
}
class BasisTexture extends Texture {
async parse(gltfLoader, data) {
if (data.sampler !== undefined) {
this.sampler = await gltfLoader.getElement(GltfTypes.SAMPLER, data.sampler);
}
const sourceId = data.extensions[EXT_ID].source;
this.source = await gltfLoader.getElement(GltfTypes.IMAGE, sourceId);
}
}
class Instance {
constructor(gltfLoader, _decoder) {
this._decoder = _decoder;
this.name = EXT_ID;
this.priority = 1;
this.loader = gltfLoader;
}
acceptElement(data, element) {
return null;
}
loadElement(data) {
var _a;
if (data.gltftype === GltfTypes.IMAGE && (data.mimeType === 'image/ktx2' || ((_a = data.uri) === null || _a === void 0 ? void 0 : _a.endsWith('ktx2')))) {
const basisImg = new BasisImage(this._decoder);
return basisImg.parse(this.loader, data).then(() => basisImg);
}
if (data.gltftype === GltfTypes.TEXTURE && data.extensions && data.extensions[EXT_ID]) {
const tex = new BasisTexture();
return tex.parse(this.loader, data).then(() => tex);
}
return null;
}
}
/**
* This extension allows Textures to use the basisu format and Images to have an uri with a .ktx2 extension.
* This format is useful to reduce texture size and memory usage.
*/
export default class KHR_texture_basisu {
/**
* @param decoder The decoder to use to decode the basis file
*/
constructor(decoder) {
this.decoder = decoder;
this.name = EXT_ID;
}
createInstance(gltfLoader) {
return new Instance(gltfLoader, this.decoder);
}
}