UNPKG

ogl

Version:
215 lines (187 loc) 6.88 kB
import { Texture } from '../core/Texture.js'; import { KTXTexture } from './KTXTexture.js'; // For compressed textures, generate using https://github.com/TimvanScherpenzeel/texture-compressor let cache = {}; const supportedExtensions = []; export class TextureLoader { static load( gl, { src, // string or object of extension:src key-values // { // pvrtc: '...ktx', // s3tc: '...ktx', // etc: '...ktx', // etc1: '...ktx', // astc: '...ktx', // webp: '...webp', // jpg: '...jpg', // png: '...png', // } // Only props relevant to KTXTexture wrapS = gl.CLAMP_TO_EDGE, wrapT = gl.CLAMP_TO_EDGE, anisotropy = 0, // For regular images format = gl.RGBA, internalFormat = format, generateMipmaps = true, minFilter = generateMipmaps ? gl.NEAREST_MIPMAP_LINEAR : gl.LINEAR, magFilter = gl.LINEAR, premultiplyAlpha = false, unpackAlignment = 4, flipY = true, } = {} ) { const support = this.getSupportedExtensions(gl); let ext = 'none'; // If src is string, determine which format from the extension if (typeof src === 'string') { ext = src.split('.').pop().split('?')[0].toLowerCase(); } // If src is object, use supported extensions and provided list to choose best option // Get first supported match, so put in order of preference if (typeof src === 'object') { for (const prop in src) { if (support.includes(prop.toLowerCase())) { ext = prop.toLowerCase(); src = src[prop]; break; } } } // Stringify props const cacheID = src + wrapS + wrapT + anisotropy + format + internalFormat + generateMipmaps + minFilter + magFilter + premultiplyAlpha + unpackAlignment + flipY + gl.renderer.id; // Check cache for existing texture if (cache[cacheID]) return cache[cacheID]; let texture; switch (ext) { case 'ktx': case 'pvrtc': case 's3tc': case 'etc': case 'etc1': case 'astc': // Load compressed texture using KTX format texture = new KTXTexture(gl, { src, wrapS, wrapT, anisotropy, minFilter, magFilter, }); texture.loaded = this.loadKTX(src, texture); break; case 'webp': case 'jpg': case 'jpeg': case 'png': texture = new Texture(gl, { wrapS, wrapT, anisotropy, format, internalFormat, generateMipmaps, minFilter, magFilter, premultiplyAlpha, unpackAlignment, flipY, }); texture.loaded = this.loadImage(gl, src, texture); break; default: console.warn('No supported format supplied'); texture = new Texture(gl); } texture.ext = ext; cache[cacheID] = texture; return texture; } static getSupportedExtensions(gl) { if (supportedExtensions.length) return supportedExtensions; const extensions = { pvrtc: gl.renderer.getExtension('WEBGL_compressed_texture_pvrtc') || gl.renderer.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc'), s3tc: gl.renderer.getExtension('WEBGL_compressed_texture_s3tc') || gl.renderer.getExtension('MOZ_WEBGL_compressed_texture_s3tc') || gl.renderer.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc'), etc: gl.renderer.getExtension('WEBGL_compressed_texture_etc'), etc1: gl.renderer.getExtension('WEBGL_compressed_texture_etc1'), astc: gl.renderer.getExtension('WEBGL_compressed_texture_astc'), }; for (const ext in extensions) if (extensions[ext]) supportedExtensions.push(ext); // Check for WebP support if (detectWebP) supportedExtensions.push('webp'); // Formats supported by all supportedExtensions.push('png', 'jpg'); return supportedExtensions; } static loadKTX(src, texture) { return fetch(src) .then((res) => res.arrayBuffer()) .then((buffer) => texture.parseBuffer(buffer)); } static loadImage(gl, src, texture) { return decodeImage(src).then((imgBmp) => { // Catch non POT textures and update params to avoid errors if (!powerOfTwo(imgBmp.width) || !powerOfTwo(imgBmp.height)) { if (texture.generateMipmaps) texture.generateMipmaps = false; if (texture.minFilter === gl.NEAREST_MIPMAP_LINEAR) texture.minFilter = gl.LINEAR; if (texture.wrapS === gl.REPEAT) texture.wrapS = texture.wrapT = gl.CLAMP_TO_EDGE; } texture.image = imgBmp; // For createImageBitmap, close once uploaded texture.onUpdate = () => { if (imgBmp.close) imgBmp.close(); texture.onUpdate = null; }; return imgBmp; }); } static clearCache() { cache = {}; } } function detectWebP() { return document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') == 0; } function powerOfTwo(value) { return Math.log2(value) % 1 === 0; } function decodeImage(src) { return new Promise((resolve) => { const img = new Image(); img.crossOrigin = ''; img.src = src; // Only chrome's implementation of createImageBitmap is fully supported const isChrome = navigator.userAgent.toLowerCase().includes('chrome'); if (!!window.createImageBitmap && isChrome) { img.onload = () => { createImageBitmap(img, { imageOrientation: 'flipY', premultiplyAlpha: 'none', }).then((imgBmp) => { resolve(imgBmp); }); }; } else { img.onload = () => resolve(img); } }); }