浏览代码

Experimental support for KTX2 and KHR_texture_basisu

This also removes texture loader support for choosing textures based on
available compression formats. This will be added back in a future
commit.
Gary Hsu 5 年之前
父节点
当前提交
f45f9664f9

文件差异内容过多而无法显示
+ 22 - 0
dist/preview release/libktx.js


二进制
dist/preview release/libktx.wasm


+ 1 - 1
loaders/src/glTF/2.0/Extensions/KHR_lights_punctual.ts

@@ -39,7 +39,7 @@ interface ILights {
 }
 
 /**
- * [Specification](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md)
+ * [Specification](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual)
  */
 export class KHR_lights implements IGLTFLoaderExtension {
     /**

+ 49 - 0
loaders/src/glTF/2.0/Extensions/KHR_texture_basisu.ts

@@ -0,0 +1,49 @@
+import { IGLTFLoaderExtension } from "../glTFLoaderExtension";
+import { GLTFLoader, ArrayItem } from "../glTFLoader";
+import { ITexture } from "../glTFLoaderInterfaces";
+import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture";
+import { Nullable } from "babylonjs/types";
+
+const NAME = "KHR_texture_basisu";
+
+interface IKHRTextureBasisU {
+    source: number;
+}
+
+/**
+ * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1751)
+ * !!! Experimental Extension Subject to Changes !!!
+ */
+export class KHR_texture_basisu implements IGLTFLoaderExtension {
+    /** The name of this extension. */
+    public readonly name = NAME;
+
+    /** Defines whether this extension is enabled. */
+    public enabled = true;
+
+    private _loader: GLTFLoader;
+
+    /** @hidden */
+    constructor(loader: GLTFLoader) {
+        this._loader = loader;
+    }
+
+    /** @hidden */
+    public dispose() {
+        delete this._loader;
+    }
+
+    /** @hidden */
+    public loadTextureAsync(context: string, texture: ITexture, assign: (babylonTexture: BaseTexture) => void): Nullable<Promise<BaseTexture>> {
+        return GLTFLoader.LoadExtensionAsync<IKHRTextureBasisU, BaseTexture>(context, texture, this.name, (extensionContext, extension) => {
+            const sampler = (texture.sampler == undefined ? GLTFLoader.DefaultSampler : ArrayItem.Get(`${context}/sampler`, this._loader.gltf.samplers, texture.sampler));
+            const image = ArrayItem.Get(`${extensionContext}/source`, this._loader.gltf.images, extension.source);
+            return this._loader._createTextureAsync(context, sampler, image, (babylonTexture) => {
+                babylonTexture.gammaSpace = false;
+                assign(babylonTexture);
+            });
+        });
+    }
+}
+
+GLTFLoader.RegisterExtension(NAME, (loader) => new KHR_texture_basisu(loader));

+ 1 - 1
loaders/src/glTF/2.0/Extensions/KHR_texture_transform.ts

@@ -16,7 +16,7 @@ interface IKHRTextureTransform {
 }
 
 /**
- * [Specification](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_texture_transform/README.md)
+ * [Specification](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_texture_transform)
  */
 export class KHR_texture_transform implements IGLTFLoaderExtension {
     /**

+ 1 - 0
loaders/src/glTF/2.0/Extensions/index.ts

@@ -7,6 +7,7 @@ export * from "./KHR_materials_clearcoat";
 export * from "./KHR_materials_sheen";
 export * from "./KHR_materials_specular";
 export * from "./KHR_mesh_quantization";
+export * from "./KHR_texture_basisu";
 export * from "./KHR_texture_transform";
 export * from "./MSFT_audio_emitter";
 export * from "./MSFT_lod";

+ 34 - 27
loaders/src/glTF/2.0/glTFLoader.ts

@@ -120,11 +120,14 @@ export class GLTFLoader implements IGLTFLoader {
     private _progressCallback?: (event: SceneLoaderProgressEvent) => void;
     private _requests = new Array<IFileRequestInfo>();
 
-    private static readonly _DefaultSampler: ISampler = { index: -1 };
-
     private static _RegisteredExtensions: { [name: string]: IRegisteredExtension } = {};
 
     /**
+     * The default glTF sampler.
+     */
+    public static readonly DefaultSampler: ISampler = { index: -1 };
+
+    /**
      * Registers a loader extension.
      * @param name The name of the loader extension.
      * @param factory The factory function that creates the loader extension.
@@ -154,7 +157,7 @@ export class GLTFLoader implements IGLTFLoader {
     }
 
     /**
-     * Gets the loader state.
+     * The loader state.
      */
     public get state(): Nullable<GLTFLoaderState> {
         return this._state;
@@ -1912,29 +1915,33 @@ export class GLTFLoader implements IGLTFLoader {
         return promise;
     }
 
-    private _loadTextureAsync(context: string, texture: ITexture, assign: (babylonTexture: BaseTexture) => void = () => { }): Promise<BaseTexture> {
-        const promises = new Array<Promise<any>>();
+    /** @hidden */
+    public _loadTextureAsync(context: string, texture: ITexture, assign: (babylonTexture: BaseTexture) => void = () => { }): Promise<BaseTexture> {
+        const extensionPromise = this._extensionsLoadTextureAsync(context, texture, assign);
+        if (extensionPromise) {
+            return extensionPromise;
+        }
 
         this.logOpen(`${context} ${texture.name || ""}`);
 
-        const sampler = (texture.sampler == undefined ? GLTFLoader._DefaultSampler : ArrayItem.Get(`${context}/sampler`, this._gltf.samplers, texture.sampler));
+        const sampler = (texture.sampler == undefined ? GLTFLoader.DefaultSampler : ArrayItem.Get(`${context}/sampler`, this._gltf.samplers, texture.sampler));
+        const image = ArrayItem.Get(`${context}/source`, this._gltf.images, texture.source);
+        const promise = this._createTextureAsync(context, sampler, image, assign);
+
+        this.logClose();
+
+        return promise;
+    }
+
+    /** @hidden */
+    public _createTextureAsync(context: string, sampler: ISampler, image: IImage, assign: (babylonTexture: BaseTexture) => void = () => { }): Promise<BaseTexture> {
         const samplerData = this._loadSampler(`/samplers/${sampler.index}`, sampler);
 
-        const image = ArrayItem.Get(`${context}/source`, this._gltf.images, texture.source);
-        let url: Nullable<string> = null;
-        if (image.uri) {
-            if (Tools.IsBase64(image.uri)) {
-                url = image.uri;
-            }
-            else if (this._babylonScene.getEngine().textureFormatInUse) {
-                // If an image uri and a texture format is set like (eg. KTX) load from url instead of blob to support texture format and fallback
-                url = this._rootUrl + image.uri;
-            }
-        }
+        const promises = new Array<Promise<any>>();
 
         const deferred = new Deferred<void>();
         this._babylonScene._blockEntityCollection = this._forAssetContainer;
-        const babylonTexture = new Texture(url, this._babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, () => {
+        const babylonTexture = new Texture(null, this._babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, () => {
             if (!this._disposed) {
                 deferred.resolve();
             }
@@ -1946,20 +1953,16 @@ export class GLTFLoader implements IGLTFLoader {
         this._babylonScene._blockEntityCollection = false;
         promises.push(deferred.promise);
 
-        if (!url) {
-            promises.push(this.loadImageAsync(`/images/${image.index}`, image).then((data) => {
-                const name = image.uri || `${this._fileName}#image${image.index}`;
-                const dataUrl = `data:${this._uniqueRootUrl}${name}`;
-                babylonTexture.updateURL(dataUrl, data);
-            }));
-        }
+        promises.push(this.loadImageAsync(`/images/${image.index}`, image).then((data) => {
+            const name = image.uri || `${this._fileName}#image${image.index}`;
+            const dataUrl = `data:${this._uniqueRootUrl}${name}`;
+            babylonTexture.updateURL(dataUrl, data);
+        }));
 
         babylonTexture.wrapU = samplerData.wrapU;
         babylonTexture.wrapV = samplerData.wrapV;
         assign(babylonTexture);
 
-        this.logClose();
-
         return Promise.all(promises).then(() => {
             return babylonTexture;
         });
@@ -2339,6 +2342,10 @@ export class GLTFLoader implements IGLTFLoader {
         return this._applyExtensions(textureInfo, "loadTextureInfo", (extension) => extension.loadTextureInfoAsync && extension.loadTextureInfoAsync(context, textureInfo, assign));
     }
 
+    private _extensionsLoadTextureAsync(context: string, texture: ITexture, assign: (babylonTexture: BaseTexture) => void): Nullable<Promise<BaseTexture>> {
+        return this._applyExtensions(texture, "loadTexture", (extension) => extension._loadTextureAsync && extension._loadTextureAsync(context, texture, assign));
+    }
+
     private _extensionsLoadAnimationAsync(context: string, animation: IAnimation): Nullable<Promise<AnimationGroup>> {
         return this._applyExtensions(animation, "loadAnimation", (extension) => extension.loadAnimationAsync && extension.loadAnimationAsync(context, animation));
     }

+ 21 - 7
loaders/src/glTF/2.0/glTFLoaderExtension.ts

@@ -8,8 +8,7 @@ import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture";
 import { Mesh } from "babylonjs/Meshes/mesh";
 import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
 import { IDisposable } from "babylonjs/scene";
-
-import { IScene, INode, IMesh, ISkin, ICamera, IMeshPrimitive, IMaterial, ITextureInfo, IAnimation, IBufferView, IBuffer } from "./glTFLoaderInterfaces";
+import { IScene, INode, IMesh, ISkin, ICamera, IMeshPrimitive, IMaterial, ITextureInfo, IAnimation, ITexture, IBufferView, IBuffer } from "./glTFLoaderInterfaces";
 import { IGLTFLoaderExtension as IGLTFBaseLoaderExtension } from "../glTFFileLoader";
 import { IProperty } from 'babylonjs-gltf2interface';
 
@@ -54,7 +53,8 @@ export interface IGLTFLoaderExtension extends IGLTFBaseLoaderExtension, IDisposa
     loadCameraAsync?(context: string, camera: ICamera, assign: (babylonCamera: Camera) => void): Nullable<Promise<Camera>>;
 
     /**
-     * @hidden Define this method to modify the default behavior when loading vertex data for mesh primitives.
+     * @hidden
+     * Define this method to modify the default behavior when loading vertex data for mesh primitives.
      * @param context The context when loading the asset
      * @param primitive The glTF mesh primitive property
      * @returns A promise that resolves with the loaded geometry when the load is complete or null if not handled
@@ -62,7 +62,8 @@ export interface IGLTFLoaderExtension extends IGLTFBaseLoaderExtension, IDisposa
     _loadVertexDataAsync?(context: string, primitive: IMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<Geometry>>;
 
     /**
-     * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+     * @hidden
+     * Define this method to modify the default behavior when loading data for mesh primitives.
      * @param context The context when loading the asset
      * @param name The mesh name when loading the asset
      * @param node The glTF node when loading the asset
@@ -74,7 +75,8 @@ export interface IGLTFLoaderExtension extends IGLTFBaseLoaderExtension, IDisposa
     _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
 
     /**
-     * @hidden Define this method to modify the default behavior when loading materials. Load material creates the material and then loads material properties.
+     * @hidden
+     * Define this method to modify the default behavior when loading materials. Load material creates the material and then loads material properties.
      * @param context The context when loading the asset
      * @param material The glTF material property
      * @param assign A function called synchronously after parsing the glTF properties
@@ -110,6 +112,16 @@ export interface IGLTFLoaderExtension extends IGLTFBaseLoaderExtension, IDisposa
     loadTextureInfoAsync?(context: string, textureInfo: ITextureInfo, assign: (babylonTexture: BaseTexture) => void): Nullable<Promise<BaseTexture>>;
 
     /**
+     * @hidden
+     * Define this method to modify the default behavior when loading textures.
+     * @param context The context when loading the asset
+     * @param texture The glTF texture property
+     * @param assign A function called synchronously after parsing the glTF properties
+     * @returns A promise that resolves with the loaded Babylon texture when the load is complete or null if not handled
+     */
+    _loadTextureAsync?(context: string, texture: ITexture, assign: (babylonTexture: BaseTexture) => void): Nullable<Promise<BaseTexture>>;
+
+    /**
      * Define this method to modify the default behavior when loading animations.
      * @param context The context when loading the asset
      * @param animation The glTF animation property
@@ -118,7 +130,8 @@ export interface IGLTFLoaderExtension extends IGLTFBaseLoaderExtension, IDisposa
     loadAnimationAsync?(context: string, animation: IAnimation): Nullable<Promise<AnimationGroup>>;
 
     /**
-     * @hidden Define this method to modify the default behavior when loading skins.
+     * @hidden
+     * Define this method to modify the default behavior when loading skins.
      * @param context The context when loading the asset
      * @param node The glTF node property
      * @param skin The glTF skin property
@@ -127,7 +140,8 @@ export interface IGLTFLoaderExtension extends IGLTFBaseLoaderExtension, IDisposa
     _loadSkinAsync?(context: string, node: INode, skin: ISkin): Nullable<Promise<void>>;
 
     /**
-     * @hidden Define this method to modify the default behavior when loading uris.
+     * @hidden
+     * Define this method to modify the default behavior when loading uris.
      * @param context The context when loading the asset
      * @param property The glTF property associated with the uri
      * @param uri The uri to load

+ 1 - 0
localDev/index-views.html

@@ -10,6 +10,7 @@
     <script src="../dist/preview%20release/Oimo.js"></script>
     <script src="../dist/preview%20release/ammo.js"></script>
     <script src="../dist/preview%20release/recast.js"></script>
+    <script src="../dist/preview%20release/libktx.js"></script>
     <script src="../Tools/DevLoader/BabylonLoader.js"></script>
 
     <style>

+ 1 - 0
localDev/index.html

@@ -10,6 +10,7 @@
     <script src="../dist/preview%20release/Oimo.js"></script>
     <script src="../dist/preview%20release/ammo.js"></script>
     <script src="../dist/preview%20release/recast.js"></script>
+    <script src="../dist/preview%20release/libktx.js"></script>
     <script src="../Tools/DevLoader/BabylonLoader.js"></script>
 
     <style>

+ 1 - 0
sandbox/debug.html

@@ -36,6 +36,7 @@
     <script src="https://preview.babylonjs.com/ammo.js"></script>
     <script src="https://preview.babylonjs.com/cannon.js"></script>
     <script src="https://preview.babylonjs.com/Oimo.js"></script>
+    <script src="https://preview.babylonjs.com/libktx.js"></script>
     <script src="https://preview.babylonjs.com/babylon.max.js"></script>
     <script src="https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js"></script>
 

+ 1 - 0
sandbox/index-local.html

@@ -10,6 +10,7 @@
     <script src="../dist/preview%20release/ammo.js"></script>
     <script src="../dist/preview%20release/cannon.js"></script>
     <script src="../dist/preview%20release/Oimo.js"></script>
+    <script src="../dist/preview%20release/libktx.js"></script>
     <script src="../Tools/DevLoader/BabylonLoader.js"></script>
 </head>
 

+ 1 - 0
sandbox/index.html

@@ -18,6 +18,7 @@
     <script src="https://preview.babylonjs.com/ammo.js"></script>
     <script src="https://preview.babylonjs.com/cannon.js"></script>
     <script src="https://preview.babylonjs.com/Oimo.js"></script>
+    <script src="https://preview.babylonjs.com/libktx.js"></script>
     <script src="https://preview.babylonjs.com/babylon.js"></script>
     <script src="https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js"></script>
 

+ 3 - 17
src/Engines/Extensions/engine.cubeTexture.ts

@@ -33,12 +33,11 @@ declare module "../../Engines/thinEngine" {
          * @param lodScale defines the scale applied to environment texture. This manages the range of LOD level used for IBL according to the roughness
          * @param lodOffset defines the offset applied to environment texture. This manages first LOD level used for IBL according to the roughness
          * @param fallback defines texture to use while falling back when (compressed) texture file not found.
-         * @param excludeLoaders array of texture loaders that should be excluded when picking a loader for the texture (defualt: empty array)
          * @returns the cube texture as an InternalTexture
          */
         createCubeTexture(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap: boolean | undefined,
             onLoad: Nullable<(data?: any) => void>, onError: Nullable<(message?: string, exception?: any) => void>,
-            format: number | undefined, forcedExtension: any, createPolynomials: boolean, lodScale: number, lodOffset: number, fallback: Nullable<InternalTexture>, excludeLoaders: Array<IInternalTextureLoader>): InternalTexture;
+            format: number | undefined, forcedExtension: any, createPolynomials: boolean, lodScale: number, lodOffset: number, fallback: Nullable<InternalTexture>): InternalTexture;
 
         /**
          * Creates a cube texture
@@ -216,7 +215,7 @@ ThinEngine.prototype._setCubeMapTextureParams = function(loadMipmap: boolean): v
     this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
 };
 
-ThinEngine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap?: boolean, onLoad: Nullable<(data?: any) => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, format?: number, forcedExtension: any = null, createPolynomials: boolean = false, lodScale: number = 0, lodOffset: number = 0, fallback: Nullable<InternalTexture> = null, excludeLoaders: Array<IInternalTextureLoader> = []): InternalTexture {
+ThinEngine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap?: boolean, onLoad: Nullable<(data?: any) => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, format?: number, forcedExtension: any = null, createPolynomials: boolean = false, lodScale: number = 0, lodOffset: number = 0, fallback: Nullable<InternalTexture> = null): InternalTexture {
     var gl = this._gl;
 
     var texture = fallback ? fallback : new InternalTexture(this, InternalTextureSource.Cube);
@@ -233,35 +232,22 @@ ThinEngine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullab
 
     var lastDot = rootUrl.lastIndexOf('.');
     var extension = forcedExtension ? forcedExtension : (lastDot > -1 ? rootUrl.substring(lastDot).toLowerCase() : "");
-    const filteredFormat: Nullable<string> = this.excludedCompressedTextureFormats(rootUrl, this._textureFormatInUse);
 
     let loader: Nullable<IInternalTextureLoader> = null;
     for (let availableLoader of ThinEngine._TextureLoaders) {
-        if (excludeLoaders.indexOf(availableLoader) === -1 && availableLoader.canLoad(extension, filteredFormat, fallback, false, false)) {
+        if (availableLoader.canLoad(extension)) {
             loader = availableLoader;
             break;
         }
     }
 
     let onInternalError = (request?: IWebRequest, exception?: any) => {
-        if (loader) {
-            const fallbackUrl = loader.getFallbackTextureUrl(texture.url, this._textureFormatInUse);
-            Logger.Warn((loader.constructor as any).name + " failed when trying to load " + texture.url + ", falling back to the next supported loader");
-            if (fallbackUrl) {
-                excludeLoaders.push(loader);
-                this.createCubeTexture(fallbackUrl, scene, files, noMipmap, onLoad, onError, format, extension, createPolynomials, lodScale, lodOffset, texture, excludeLoaders);
-                return;
-            }
-        }
-
         if (onError && request) {
             onError(request.status + " " + request.statusText, exception);
         }
     };
 
     if (loader) {
-        rootUrl = loader.transformUrl(rootUrl, filteredFormat);
-
         const onloaddata = (data: ArrayBufferView | ArrayBufferView[]) => {
             this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
             loader!.loadCubeData(data, texture, createPolynomials, onLoad, onError);

+ 0 - 44
src/Engines/engine.ts

@@ -1339,50 +1339,6 @@ export class Engine extends ThinEngine {
     }
 
     /**
-     * Set the compressed texture format to use, based on the formats you have, and the formats
-     * supported by the hardware / browser.
-     *
-     * Khronos Texture Container (.ktx) files are used to support this.  This format has the
-     * advantage of being specifically designed for OpenGL.  Header elements directly correspond
-     * to API arguments needed to compressed textures.  This puts the burden on the container
-     * generator to house the arcane code for determining these for current & future formats.
-     *
-     * for description see https://www.khronos.org/opengles/sdk/tools/KTX/
-     * for file layout see https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
-     *
-     * Note: The result of this call is not taken into account when a texture is base64.
-     *
-     * @param formatsAvailable defines the list of those format families you have created
-     * on your server.  Syntax: '-' + format family + '.ktx'.  (Case and order do not matter.)
-     *
-     * Current families are astc, dxt, pvrtc, etc2, & etc1.
-     * @returns The extension selected.
-     */
-    public setTextureFormatToUse(formatsAvailable: Array<string>): Nullable<string> {
-        for (var i = 0, len1 = this.texturesSupported.length; i < len1; i++) {
-            for (var j = 0, len2 = formatsAvailable.length; j < len2; j++) {
-                if (this._texturesSupported[i] === formatsAvailable[j].toLowerCase()) {
-                    return this._textureFormatInUse = this._texturesSupported[i];
-                }
-            }
-        }
-        // actively set format to nothing, to allow this to be called more than once
-        // and possibly fail the 2nd time
-        this._textureFormatInUse = null;
-        return null;
-    }
-
-    /**
-     * Set the compressed texture extensions or file names to skip.
-     *
-     * @param skippedFiles defines the list of those texture files you want to skip
-     * Example: [".dds", ".env", "myfile.png"]
-     */
-    public setCompressedTextureExclusions(skippedFiles: Array<string>): void {
-        this._excludedCompressedTextures = skippedFiles;
-    }
-
-    /**
      * Force a specific size of the canvas
      * @param width defines the new canvas' width
      * @param height defines the new canvas' height

+ 15 - 42
src/Engines/nativeEngine.ts

@@ -21,7 +21,7 @@ import { WebRequest } from '../Misc/webRequest';
 import { NativeShaderProcessor } from './Native/nativeShaderProcessor';
 import { Logger } from "../Misc/logger";
 import { Constants } from './constants';
-import { ThinEngine } from './thinEngine';
+import { ThinEngine, ISceneLike } from './thinEngine';
 import { IWebRequest } from '../Misc/interfaces/iWebRequest';
 
 interface INativeEngine {
@@ -840,40 +840,32 @@ export class NativeEngine extends Engine {
 
     // TODO: Refactor to share more logic with babylon.engine.ts version.
     /**
-     * Usually called from BABYLON.Texture.ts.
+     * Usually called from Texture.ts.
      * Passed information to create a WebGLTexture
      * @param urlArg defines a value which contains one of the following:
      * * A conventional http URL, e.g. 'http://...' or 'file://...'
      * * A base64 string of in-line texture data, e.g. 'data:image/jpg;base64,/...'
      * * An indicator that data being passed using the buffer parameter, e.g. 'data:mytexture.jpg'
      * @param noMipmap defines a boolean indicating that no mipmaps shall be generated.  Ignored for compressed textures.  They must be in the file
-     * @param invertY when true, image is flipped when loaded.  You probably want true. Ignored for compressed textures.  Must be flipped in the file
+     * @param invertY when true, image is flipped when loaded.  You probably want true. Certain compressed textures may invert this if their default is inverted (eg. ktx)
      * @param scene needed for loading to the correct scene
-     * @param samplingMode mode with should be used sample / access the texture (Default: BABYLON.Texture.TRILINEAR_SAMPLINGMODE)
+     * @param samplingMode mode with should be used sample / access the texture (Default: Texture.TRILINEAR_SAMPLINGMODE)
      * @param onLoad optional callback to be called upon successful completion
      * @param onError optional callback to be called upon failure
-     * @param buffer a source of a file previously fetched as either a base64 string, an ArrayBuffer (compressed or image format), or a Blob
+     * @param buffer a source of a file previously fetched as either a base64 string, an ArrayBuffer (compressed or image format), HTMLImageElement (image format), or a Blob
      * @param fallback an internal argument in case the function must be called again, due to etc1 not having alpha capabilities
      * @param format internal format.  Default: RGB when extension is '.jpg' else RGBA.  Ignored for compressed textures
      * @param forcedExtension defines the extension to use to pick the right loader
+     * @param mimeType defines an optional mime type
      * @returns a InternalTexture for assignment back into BABYLON.Texture
      */
-    public createTexture(
-        urlArg: Nullable<string>,
-        noMipmap: boolean,
-        invertY: boolean,
-        scene: Nullable<Scene>,
-        samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE,
-        onLoad: Nullable<() => void> = null,
-        onError: Nullable<(message: string, exception: any) => void> = null,
-        buffer: Nullable<string | ArrayBuffer | Blob> = null,
-        fallback: Nullable<InternalTexture> = null,
-        format: Nullable<number> = null,
-        forcedExtension: Nullable<string> = null): InternalTexture {
+    public createTexture(urlArg: Nullable<string>, noMipmap: boolean, invertY: boolean, scene: Nullable<ISceneLike>, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE,
+        onLoad: Nullable<() => void> = null, onError: Nullable<(message: string, exception: any) => void> = null,
+        buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob | ImageBitmap> = null, fallback: Nullable<InternalTexture> = null, format: Nullable<number> = null,
+        forcedExtension: Nullable<string> = null, mimeType?: string): InternalTexture {
         var url = String(urlArg); // assign a new string, so that the original is still available in case of fallback
         var fromData = url.substr(0, 5) === "data:";
         var fromBlob = url.substr(0, 5) === "blob:";
-        var isBase64 = fromData && url.indexOf("base64") !== -1;
 
         let texture = fallback ? fallback : new InternalTexture(this, InternalTextureSource.Url);
 
@@ -881,21 +873,14 @@ export class NativeEngine extends Engine {
         var lastDot = url.lastIndexOf('.');
         var extension = forcedExtension ? forcedExtension : (lastDot > -1 ? url.substring(lastDot).toLowerCase() : "");
 
-        // TODO: Add support for compressed texture formats.
-        var textureFormatInUse: Nullable<string> = null;
-
         let loader: Nullable<IInternalTextureLoader> = null;
         for (let availableLoader of Engine._TextureLoaders) {
-            if (availableLoader.canLoad(extension, textureFormatInUse, fallback, isBase64, buffer ? true : false)) {
+            if (availableLoader.canLoad(extension)) {
                 loader = availableLoader;
                 break;
             }
         }
 
-        if (loader) {
-            url = loader.transformUrl(url, textureFormatInUse);
-        }
-
         if (scene) {
             scene._addPendingData(texture);
         }
@@ -921,23 +906,11 @@ export class NativeEngine extends Engine {
                 scene._removePendingData(texture);
             }
 
-            let customFallback = false;
-            if (loader) {
-                const fallbackUrl = loader.getFallbackTextureUrl(url, textureFormatInUse);
-                if (fallbackUrl) {
-                    // Add Back
-                    customFallback = true;
-                    this.createTexture(urlArg, noMipmap, invertY, scene, samplingMode, null, onError, buffer, texture);
-                }
+            if (onLoadObserver) {
+                texture.onLoadedObservable.remove(onLoadObserver);
             }
-
-            if (!customFallback) {
-                if (onLoadObserver) {
-                    texture.onLoadedObservable.remove(onLoadObserver);
-                }
-                if (Tools.UseFallbackTexture) {
-                    this.createTexture(Tools.fallbackTexture, noMipmap, invertY, scene, samplingMode, null, onError, buffer, texture);
-                }
+            if (Tools.UseFallbackTexture) {
+                this.createTexture(Tools.fallbackTexture, noMipmap, invertY, scene, samplingMode, null, onError, buffer, texture);
             }
 
             if (onError) {

+ 6 - 5
src/Engines/nullEngine.ts

@@ -1,6 +1,5 @@
 import { Logger } from "../Misc/logger";
 import { Nullable, FloatArray, IndicesArray } from "../types";
-import { Scene } from "../scene";
 import { Engine } from "../Engines/engine";
 import { RenderTargetCreationOptions } from "../Materials/Textures/renderTargetCreationOptions";
 import { VertexBuffer } from "../Meshes/buffer";
@@ -10,6 +9,7 @@ import { Constants } from "./constants";
 import { IPipelineContext } from './IPipelineContext';
 import { DataBuffer } from '../Meshes/dataBuffer';
 import { IColor4Like, IViewportLike } from '../Maths/math.like';
+import { ISceneLike } from './thinEngine';
 
 declare const global: any;
 
@@ -551,15 +551,16 @@ export class NullEngine extends Engine {
      * @param onLoad optional callback to be called upon successful completion
      * @param onError optional callback to be called upon failure
      * @param buffer a source of a file previously fetched as either a base64 string, an ArrayBuffer (compressed or image format), HTMLImageElement (image format), or a Blob
-     * @param fallBack an internal argument in case the function must be called again, due to etc1 not having alpha capabilities
+     * @param fallback an internal argument in case the function must be called again, due to etc1 not having alpha capabilities
      * @param format internal format.  Default: RGB when extension is '.jpg' else RGBA.  Ignored for compressed textures
      * @param forcedExtension defines the extension to use to pick the right loader
-     * @param excludeLoaders array of texture loaders that should be excluded when picking a loader for the texture (default: empty array)
+     * @param mimeType defines an optional mime type
      * @returns a InternalTexture for assignment back into BABYLON.Texture
      */
-    public createTexture(urlArg: string, noMipmap: boolean, invertY: boolean, scene: Scene, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE,
+    public createTexture(urlArg: Nullable<string>, noMipmap: boolean, invertY: boolean, scene: Nullable<ISceneLike>, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE,
         onLoad: Nullable<() => void> = null, onError: Nullable<(message: string, exception: any) => void> = null,
-        buffer: Nullable<ArrayBuffer | HTMLImageElement> = null, fallBack?: InternalTexture, format?: number): InternalTexture {
+        buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob | ImageBitmap> = null, fallback: Nullable<InternalTexture> = null, format: Nullable<number> = null,
+        forcedExtension: Nullable<string> = null, mimeType?: string): InternalTexture {
         var texture = new InternalTexture(this, InternalTextureSource.Url);
         var url = String(urlArg);
 

+ 11 - 52
src/Engines/thinEngine.ts

@@ -40,7 +40,7 @@ declare type Texture = import("../Materials/Textures/texture").Texture;
  * Defines the interface used by objects working like Scene
  * @hidden
  */
-interface ISceneLike {
+export interface ISceneLike {
     _addPendingData(data: any): void;
     _removePendingData(data: any): void;
     offlineProvider: IOfflineProvider;
@@ -172,29 +172,6 @@ export class ThinEngine {
         Effect.ShadersRepository = value;
     }
 
-    /**
-    * Gets or sets the textures that the engine should not attempt to load as compressed
-    */
-    protected _excludedCompressedTextures: string[] = [];
-
-    /**
-     * Filters the compressed texture formats to only include
-     * files that are not included in the skippable list
-     *
-     * @param url the current extension
-     * @param textureFormatInUse the current compressed texture format
-     * @returns "format" string
-     */
-    public excludedCompressedTextureFormats(url: Nullable<string>, textureFormatInUse: Nullable<string>): Nullable<string> {
-        const skipCompression = (): boolean => {
-            return this._excludedCompressedTextures.some((entry) => {
-                const strRegExPattern: string = '\\b' + entry + '\\b';
-                return (url && (url === entry || url.match(new RegExp(strRegExPattern, 'g'))));
-            });
-        };
-        return skipCompression() ? null : textureFormatInUse;
-    }
-
     // Public members
 
     /** @hidden */
@@ -255,9 +232,10 @@ export class ThinEngine {
 
     /** @hidden */
     public _gl: WebGLRenderingContext;
+    /** @hidden */
+    public _webGLVersion = 1.0;
     protected _renderingCanvas: Nullable<HTMLCanvasElement>;
     protected _windowIsBackground = false;
-    protected _webGLVersion = 1.0;
     protected _creationOptions: EngineOptions;
 
     protected _highPrecisionShadersAllowed = true;
@@ -2788,14 +2766,13 @@ export class ThinEngine {
      * @param fallback an internal argument in case the function must be called again, due to etc1 not having alpha capabilities
      * @param format internal format.  Default: RGB when extension is '.jpg' else RGBA.  Ignored for compressed textures
      * @param forcedExtension defines the extension to use to pick the right loader
-     * @param excludeLoaders array of texture loaders that should be excluded when picking a loader for the texture (default: empty array)
      * @param mimeType defines an optional mime type
      * @returns a InternalTexture for assignment back into BABYLON.Texture
      */
     public createTexture(urlArg: Nullable<string>, noMipmap: boolean, invertY: boolean, scene: Nullable<ISceneLike>, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE,
         onLoad: Nullable<() => void> = null, onError: Nullable<(message: string, exception: any) => void> = null,
         buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob | ImageBitmap> = null, fallback: Nullable<InternalTexture> = null, format: Nullable<number> = null,
-        forcedExtension: Nullable<string> = null, excludeLoaders: Array<IInternalTextureLoader> = [], mimeType?: string): InternalTexture {
+        forcedExtension: Nullable<string> = null, mimeType?: string): InternalTexture {
         var url = String(urlArg); // assign a new string, so that the original is still available in case of fallback
         var fromData = url.substr(0, 5) === "data:";
         var fromBlob = url.substr(0, 5) === "blob:";
@@ -2806,20 +2783,15 @@ export class ThinEngine {
         // establish the file extension, if possible
         var lastDot = url.lastIndexOf('.');
         var extension = forcedExtension ? forcedExtension : (lastDot > -1 ? url.substring(lastDot).toLowerCase() : "");
-        const filteredFormat: Nullable<string> = this.excludedCompressedTextureFormats(url, this._textureFormatInUse);
         let loader: Nullable<IInternalTextureLoader> = null;
 
         for (let availableLoader of ThinEngine._TextureLoaders) {
-            if (excludeLoaders.indexOf(availableLoader) === -1 && availableLoader.canLoad(extension, filteredFormat, fallback, isBase64, buffer ? true : false)) {
+            if (availableLoader.canLoad(extension)) {
                 loader = availableLoader;
                 break;
             }
         }
 
-        if (loader) {
-            url = loader.transformUrl(url, filteredFormat);
-        }
-
         if (scene) {
             scene._addPendingData(texture);
         }
@@ -2845,26 +2817,13 @@ export class ThinEngine {
                 scene._removePendingData(texture);
             }
 
-            let customFallback = false;
-            if (loader) {
-                const fallbackUrl = loader.getFallbackTextureUrl(url, this._textureFormatInUse);
-                if (fallbackUrl) {
-                    // Add Back
-                    customFallback = true;
-                    excludeLoaders.push(loader);
-                    this.createTexture(urlArg, noMipmap, texture.invertY, scene, samplingMode, null, onError, buffer, texture, undefined, undefined, excludeLoaders);
-                    return;
-                }
+            if (onLoadObserver) {
+                texture.onLoadedObservable.remove(onLoadObserver);
             }
 
-            if (!customFallback) {
-                if (onLoadObserver) {
-                    texture.onLoadedObservable.remove(onLoadObserver);
-                }
-                if (EngineStore.UseFallbackTexture) {
-                    this.createTexture(EngineStore.FallbackTexture, noMipmap, texture.invertY, scene, samplingMode, null, onError, buffer, texture);
-                    return;
-                }
+            if (EngineStore.UseFallbackTexture) {
+                this.createTexture(EngineStore.FallbackTexture, noMipmap, texture.invertY, scene, samplingMode, null, onError, buffer, texture);
+                return;
             }
 
             if (onError) {
@@ -2969,7 +2928,7 @@ export class ThinEngine {
                 ThinEngine._FileToolsLoadImage(buffer, onload, onInternalError, scene ? scene.offlineProvider : null, mimeType);
             }
             else if (buffer) {
-                onload(<HTMLImageElement>buffer);
+                onload(buffer);
             }
         }
 

+ 2 - 0
src/Materials/PBR/pbrBaseMaterial.ts

@@ -58,6 +58,7 @@ export class PBRMaterialDefines extends MaterialDefines
     public UV2 = false;
 
     public ALBEDO = false;
+    public GAMMAALBEDO = false;
     public ALBEDODIRECTUV = 0;
     public VERTEXCOLOR = false;
 
@@ -1291,6 +1292,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
 
                 if (this._albedoTexture && MaterialFlags.DiffuseTextureEnabled) {
                     MaterialHelper.PrepareDefinesForMergedUV(this._albedoTexture, defines, "ALBEDO");
+                    defines.GAMMAALBEDO = this._albedoTexture.gammaSpace;
                 } else {
                     defines.ALBEDO = false;
                 }

+ 5 - 28
src/Materials/Textures/Loaders/basisTextureLoader.ts

@@ -4,6 +4,7 @@ import { InternalTexture } from "../../../Materials/Textures/internalTexture";
 import { IInternalTextureLoader } from "../../../Materials/Textures/internalTextureLoader";
 import { BasisTools } from "../../../Misc/basis";
 import { Tools } from '../../../Misc/tools';
+import { StringTools } from '../../../Misc/stringTools';
 
 /**
  * Loader for .basis file format
@@ -17,38 +18,14 @@ export class _BasisTextureLoader implements IInternalTextureLoader {
     /**
      * This returns if the loader support the current file information.
      * @param extension defines the file extension of the file being loaded
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @param fallback defines the fallback internal texture if any
-     * @param isBase64 defines whether the texture is encoded as a base64
-     * @param isBuffer defines whether the texture data are stored as a buffer
      * @returns true if the loader can load the specified file
      */
-    public canLoad(extension: string, textureFormatInUse: Nullable<string>, fallback: Nullable<InternalTexture>, isBase64: boolean, isBuffer: boolean): boolean {
-        return extension.indexOf(".basis") === 0;
+    public canLoad(extension: string): boolean {
+        return StringTools.EndsWith(extension, ".basis");
     }
 
     /**
-     * Transform the url before loading if required.
-     * @param rootUrl the url of the texture
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @returns the transformed texture
-     */
-    public transformUrl(rootUrl: string, textureFormatInUse: Nullable<string>): string {
-        return rootUrl;
-    }
-
-    /**
-     * Gets the fallback url in case the load fail. This can return null to allow the default fallback mecanism to work
-     * @param rootUrl the url of the texture
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @returns the fallback texture
-     */
-    public getFallbackTextureUrl(rootUrl: string, textureFormatInUse: Nullable<string>): Nullable<string> {
-        return null;
-    }
-
-    /**
-     * Uploads the cube texture data to the WebGl Texture. It has already been bound.
+     * Uploads the cube texture data to the WebGL texture. It has already been bound.
      * @param data contains the texture data
      * @param texture defines the BabylonJS internal texture
      * @param createPolynomials will be true if polynomials have been requested
@@ -85,7 +62,7 @@ export class _BasisTextureLoader implements IInternalTextureLoader {
     }
 
     /**
-     * Uploads the 2D texture data to the WebGl Texture. It has alreday been bound once in the callback.
+     * Uploads the 2D texture data to the WebGL texture. It has already been bound once in the callback.
      * @param data contains the texture data
      * @param texture defines the BabylonJS internal texture
      * @param callback defines the method to call once ready to upload

+ 5 - 28
src/Materials/Textures/Loaders/ddsTextureLoader.ts

@@ -4,6 +4,7 @@ import { Engine } from "../../../Engines/engine";
 import { InternalTexture } from "../../../Materials/Textures/internalTexture";
 import { IInternalTextureLoader } from "../../../Materials/Textures/internalTextureLoader";
 import { DDSTools, DDSInfo } from "../../../Misc/dds";
+import { StringTools } from '../../../Misc/stringTools';
 /**
  * Implementation of the DDS Texture Loader.
  * @hidden
@@ -17,38 +18,14 @@ export class _DDSTextureLoader implements IInternalTextureLoader {
     /**
      * This returns if the loader support the current file information.
      * @param extension defines the file extension of the file being loaded
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @param fallback defines the fallback internal texture if any
-     * @param isBase64 defines whether the texture is encoded as a base64
-     * @param isBuffer defines whether the texture data are stored as a buffer
      * @returns true if the loader can load the specified file
      */
-    public canLoad(extension: string, textureFormatInUse: Nullable<string>, fallback: Nullable<InternalTexture>, isBase64: boolean, isBuffer: boolean): boolean {
-        return extension.indexOf(".dds") === 0;
+    public canLoad(extension: string): boolean {
+        return StringTools.EndsWith(extension, ".dds");
     }
 
     /**
-     * Transform the url before loading if required.
-     * @param rootUrl the url of the texture
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @returns the transformed texture
-     */
-    public transformUrl(rootUrl: string, textureFormatInUse: Nullable<string>): string {
-        return rootUrl;
-    }
-
-    /**
-     * Gets the fallback url in case the load fail. This can return null to allow the default fallback mecanism to work
-     * @param rootUrl the url of the texture
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @returns the fallback texture
-     */
-    public getFallbackTextureUrl(rootUrl: string, textureFormatInUse: Nullable<string>): Nullable<string> {
-        return null;
-    }
-
-    /**
-     * Uploads the cube texture data to the WebGl Texture. It has alreday been bound.
+     * Uploads the cube texture data to the WebGL texture. It has already been bound.
      * @param data contains the texture data
      * @param texture defines the BabylonJS internal texture
      * @param createPolynomials will be true if polynomials have been requested
@@ -110,7 +87,7 @@ export class _DDSTextureLoader implements IInternalTextureLoader {
     }
 
     /**
-     * Uploads the 2D texture data to the WebGl Texture. It has alreday been bound once in the callback.
+     * Uploads the 2D texture data to the WebGL texture. It has already been bound once in the callback.
      * @param data contains the texture data
      * @param texture defines the BabylonJS internal texture
      * @param callback defines the method to call once ready to upload

+ 5 - 28
src/Materials/Textures/Loaders/envTextureLoader.ts

@@ -3,6 +3,7 @@ import { Nullable } from "../../../types";
 import { Engine } from "../../../Engines/engine";
 import { InternalTexture } from "../../../Materials/Textures/internalTexture";
 import { IInternalTextureLoader } from "../../../Materials/Textures/internalTextureLoader";
+import { StringTools } from '../../../Misc/stringTools';
 
 /**
  * Implementation of the ENV Texture Loader.
@@ -17,38 +18,14 @@ export class _ENVTextureLoader implements IInternalTextureLoader {
     /**
      * This returns if the loader support the current file information.
      * @param extension defines the file extension of the file being loaded
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @param fallback defines the fallback internal texture if any
-     * @param isBase64 defines whether the texture is encoded as a base64
-     * @param isBuffer defines whether the texture data are stored as a buffer
      * @returns true if the loader can load the specified file
      */
-    public canLoad(extension: string, textureFormatInUse: Nullable<string>, fallback: Nullable<InternalTexture>, isBase64: boolean, isBuffer: boolean): boolean {
-        return extension.indexOf(".env") === 0;
+    public canLoad(extension: string): boolean {
+        return StringTools.EndsWith(extension, ".env");
     }
 
     /**
-     * Transform the url before loading if required.
-     * @param rootUrl the url of the texture
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @returns the transformed texture
-     */
-    public transformUrl(rootUrl: string, textureFormatInUse: Nullable<string>): string {
-        return rootUrl;
-    }
-
-    /**
-     * Gets the fallback url in case the load fail. This can return null to allow the default fallback mecanism to work
-     * @param rootUrl the url of the texture
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @returns the fallback texture
-     */
-    public getFallbackTextureUrl(rootUrl: string, textureFormatInUse: Nullable<string>): Nullable<string> {
-        return null;
-    }
-
-    /**
-     * Uploads the cube texture data to the WebGl Texture. It has alreday been bound.
+     * Uploads the cube texture data to the WebGL texture. It has already been bound.
      * @param data contains the texture data
      * @param texture defines the BabylonJS internal texture
      * @param createPolynomials will be true if polynomials have been requested
@@ -81,7 +58,7 @@ export class _ENVTextureLoader implements IInternalTextureLoader {
     }
 
     /**
-     * Uploads the 2D texture data to the WebGl Texture. It has alreday been bound once in the callback.
+     * Uploads the 2D texture data to the WebGL texture. It has already been bound once in the callback.
      * @param data contains the texture data
      * @param texture defines the BabylonJS internal texture
      * @param callback defines the method to call once ready to upload

+ 28 - 45
src/Materials/Textures/Loaders/ktxTextureLoader.ts

@@ -1,8 +1,11 @@
 import { KhronosTextureContainer } from "../../../Misc/khronosTextureContainer";
+import { KhronosTextureContainer2 } from "../../../Misc/khronosTextureContainer2";
 import { Nullable } from "../../../types";
 import { Engine } from "../../../Engines/engine";
 import { InternalTexture } from "../../../Materials/Textures/internalTexture";
 import { IInternalTextureLoader } from "../../../Materials/Textures/internalTextureLoader";
+import { StringTools } from '../../../Misc/stringTools';
+import { Logger } from '../../../Misc/logger';
 
 /**
  * Implementation of the KTX Texture Loader.
@@ -17,48 +20,15 @@ export class _KTXTextureLoader implements IInternalTextureLoader {
     /**
      * This returns if the loader support the current file information.
      * @param extension defines the file extension of the file being loaded
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @param fallback defines the fallback internal texture if any
-     * @param isBase64 defines whether the texture is encoded as a base64
-     * @param isBuffer defines whether the texture data are stored as a buffer
      * @returns true if the loader can load the specified file
      */
-    public canLoad(extension: string, textureFormatInUse: Nullable<string>, fallback: Nullable<InternalTexture>, isBase64: boolean, isBuffer: boolean): boolean {
-        if (textureFormatInUse && !isBase64 && !fallback && !isBuffer) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Transform the url before loading if required.
-     * @param rootUrl the url of the texture
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @returns the transformed texture
-     */
-    public transformUrl(rootUrl: string, textureFormatInUse: Nullable<string>): string {
-        var lastDot = rootUrl.lastIndexOf('.');
-        if (lastDot != -1 && rootUrl.substring(lastDot + 1) == "ktx") {
-            // Already transformed
-            return rootUrl;
-        }
-        return (lastDot > -1 ? rootUrl.substring(0, lastDot) : rootUrl) + textureFormatInUse;
-    }
-
-    /**
-     * Gets the fallback url in case the load fail. This can return null to allow the default fallback mecanism to work
-     * @param rootUrl the url of the texture
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @returns the fallback texture
-     */
-    public getFallbackTextureUrl(rootUrl: string, textureFormatInUse: Nullable<string>): Nullable<string> {
-        // remove the format appended to the rootUrl in the original createCubeTexture call.
-        var exp = new RegExp("" + textureFormatInUse! + "$");
-        return rootUrl.replace(exp, "");
+    public canLoad(extension: string): boolean {
+        // The ".ktx2" file extension is still up for debate: https://github.com/KhronosGroup/KTX-Specification/issues/18
+        return StringTools.EndsWith(extension, ".ktx") || StringTools.EndsWith(extension, ".ktx2");
     }
 
     /**
-     * Uploads the cube texture data to the WebGl Texture. It has alreday been bound.
+     * Uploads the cube texture data to the WebGL texture. It has already been bound.
      * @param data contains the texture data
      * @param texture defines the BabylonJS internal texture
      * @param createPolynomials will be true if polynomials have been requested
@@ -95,20 +65,33 @@ export class _KTXTextureLoader implements IInternalTextureLoader {
     }
 
     /**
-     * Uploads the 2D texture data to the WebGl Texture. It has alreday been bound once in the callback.
+     * Uploads the 2D texture data to the WebGL texture. It has already been bound once in the callback.
      * @param data contains the texture data
      * @param texture defines the BabylonJS internal texture
      * @param callback defines the method to call once ready to upload
      */
     public loadData(data: ArrayBufferView, texture: InternalTexture,
         callback: (width: number, height: number, loadMipmap: boolean, isCompressed: boolean, done: () => void, loadFailed: boolean) => void): void {
-        // Need to invert vScale as invertY via UNPACK_FLIP_Y_WEBGL is not supported by compressed texture
-        texture._invertVScale = !texture.invertY;
-        var ktx = new KhronosTextureContainer(data, 1);
-
-        callback(ktx.pixelWidth, ktx.pixelHeight, texture.generateMipMaps, true, () => {
-            ktx.uploadLevels(texture, texture.generateMipMaps);
-        }, ktx.isInvalid);
+        if (KhronosTextureContainer.IsValid(data)) {
+            // Need to invert vScale as invertY via UNPACK_FLIP_Y_WEBGL is not supported by compressed texture
+            texture._invertVScale = !texture.invertY;
+            const ktx = new KhronosTextureContainer(data, 1);
+            callback(ktx.pixelWidth, ktx.pixelHeight, texture.generateMipMaps, true, () => {
+                ktx.uploadLevels(texture, texture.generateMipMaps);
+            }, ktx.isInvalid);
+        }
+        else if (KhronosTextureContainer2.IsValid(data)) {
+            const ktx2 = new KhronosTextureContainer2(texture.getEngine());
+            ktx2.uploadAsync(data, texture).then(() => {
+                callback(texture.width, texture.height, false, true, () => {}, false);
+            }, (error) => {
+                Logger.Warn(`Failed to load KTX2 texture data: ${error.message}`);
+                callback(0, 0, false, false, () => {}, true);
+            });
+        }
+        else {
+            callback(0, 0, false, false, () => {}, true);
+        }
     }
 }
 

+ 5 - 28
src/Materials/Textures/Loaders/tgaTextureLoader.ts

@@ -3,6 +3,7 @@ import { Nullable } from "../../../types";
 import { Engine } from "../../../Engines/engine";
 import { InternalTexture } from "../../../Materials/Textures/internalTexture";
 import { IInternalTextureLoader } from "../../../Materials/Textures/internalTextureLoader";
+import { StringTools } from '../../../Misc/stringTools';
 
 /**
  * Implementation of the TGA Texture Loader.
@@ -17,38 +18,14 @@ export class _TGATextureLoader implements IInternalTextureLoader {
     /**
      * This returns if the loader support the current file information.
      * @param extension defines the file extension of the file being loaded
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @param fallback defines the fallback internal texture if any
-     * @param isBase64 defines whether the texture is encoded as a base64
-     * @param isBuffer defines whether the texture data are stored as a buffer
      * @returns true if the loader can load the specified file
      */
-    public canLoad(extension: string, textureFormatInUse: Nullable<string>, fallback: Nullable<InternalTexture>, isBase64: boolean, isBuffer: boolean): boolean {
-        return extension.indexOf(".tga") === 0;
+    public canLoad(extension: string): boolean {
+        return StringTools.EndsWith(extension, ".tga");
     }
 
     /**
-     * Transform the url before loading if required.
-     * @param rootUrl the url of the texture
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @returns the transformed texture
-     */
-    public transformUrl(rootUrl: string, textureFormatInUse: Nullable<string>): string {
-        return rootUrl;
-    }
-
-    /**
-     * Gets the fallback url in case the load fail. This can return null to allow the default fallback mecanism to work
-     * @param rootUrl the url of the texture
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @returns the fallback texture
-     */
-    public getFallbackTextureUrl(rootUrl: string, textureFormatInUse: Nullable<string>): Nullable<string> {
-        return null;
-    }
-
-    /**
-     * Uploads the cube texture data to the WebGl Texture. It has alreday been bound.
+     * Uploads the cube texture data to the WebGL texture. It has already been bound.
      * @param data contains the texture data
      * @param texture defines the BabylonJS internal texture
      * @param createPolynomials will be true if polynomials have been requested
@@ -60,7 +37,7 @@ export class _TGATextureLoader implements IInternalTextureLoader {
     }
 
     /**
-     * Uploads the 2D texture data to the WebGl Texture. It has alreday been bound once in the callback.
+     * Uploads the 2D texture data to the WebGL texture. It has already been bound once in the callback.
      * @param data contains the texture data
      * @param texture defines the BabylonJS internal texture
      * @param callback defines the method to call once ready to upload

+ 4 - 25
src/Materials/Textures/internalTextureLoader.ts

@@ -13,32 +13,12 @@ export interface IInternalTextureLoader {
     /**
      * This returns if the loader support the current file information.
      * @param extension defines the file extension of the file being loaded
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @param fallback defines the fallback internal texture if any
-     * @param isBase64 defines whether the texture is encoded as a base64
-     * @param isBuffer defines whether the texture data are stored as a buffer
      * @returns true if the loader can load the specified file
      */
-    canLoad(extension: string, textureFormatInUse: Nullable<string>, fallback: Nullable<InternalTexture>, isBase64: boolean, isBuffer: boolean): boolean;
+    canLoad(extension: string): boolean;
 
     /**
-     * Transform the url before loading if required.
-     * @param rootUrl the url of the texture
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @returns the transformed texture
-     */
-    transformUrl(rootUrl: string, textureFormatInUse: Nullable<string>): string;
-
-    /**
-     * Gets the fallback url in case the load fail. This can return null to allow the default fallback mecanism to work
-     * @param rootUrl the url of the texture
-     * @param textureFormatInUse defines the current compressed format in use iun the engine
-     * @returns the fallback texture
-     */
-    getFallbackTextureUrl(rootUrl: string, textureFormatInUse: Nullable<string>): Nullable<string>;
-
-    /**
-     * Uploads the cube texture data to the WebGl Texture. It has alreday been bound.
+     * Uploads the cube texture data to the WebGL texture. It has already been bound.
      * @param data contains the texture data
      * @param texture defines the BabylonJS internal texture
      * @param createPolynomials will be true if polynomials have been requested
@@ -48,11 +28,10 @@ export interface IInternalTextureLoader {
     loadCubeData(data: ArrayBufferView | ArrayBufferView[], texture: InternalTexture, createPolynomials: boolean, onLoad: Nullable<(data?: any) => void>, onError: Nullable<(message?: string, exception?: any) => void>): void;
 
     /**
-     * Uploads the 2D texture data to the WebGl Texture. It has alreday been bound once in the callback.
+     * Uploads the 2D texture data to the WebGL texture. It has already been bound once in the callback.
      * @param data contains the texture data
      * @param texture defines the BabylonJS internal texture
      * @param callback defines the method to call once ready to upload
      */
-    loadData(data: ArrayBufferView, texture: InternalTexture,
-        callback: (width: number, height: number, loadMipmap: boolean, isCompressed: boolean, done: () => void, loadFailed?: boolean) => void): void;
+    loadData(data: ArrayBufferView, texture: InternalTexture, callback: (width: number, height: number, loadMipmap: boolean, isCompressed: boolean, done: () => void, loadFailed?: boolean) => void): void;
 }

+ 2 - 2
src/Materials/Textures/texture.ts

@@ -345,7 +345,7 @@ export class Texture extends BaseTexture {
 
         if (!this._texture) {
             if (!scene || !scene.useDelayedTextureLoading) {
-                this._texture = engine.createTexture(this.url, noMipmap, invertY, scene, samplingMode, load, onError, this._buffer, undefined, this._format, null, undefined, mimeType);
+                this._texture = engine.createTexture(this.url, noMipmap, invertY, scene, samplingMode, load, onError, this._buffer, undefined, this._format, null, mimeType);
                 if (deleteBuffer) {
                     delete this._buffer;
                 }
@@ -408,7 +408,7 @@ export class Texture extends BaseTexture {
         this._texture = this._getFromCache(this.url, this._noMipmap, this.samplingMode, this._invertY);
 
         if (!this._texture) {
-            this._texture = scene.getEngine().createTexture(this.url, this._noMipmap, this._invertY, scene, this.samplingMode, this._delayedOnLoad, this._delayedOnError, this._buffer, null, this._format, null, undefined, this._mimeType);
+            this._texture = scene.getEngine().createTexture(this.url, this._noMipmap, this._invertY, scene, this.samplingMode, this._delayedOnLoad, this._delayedOnError, this._buffer, null, this._format, null, this._mimeType);
             if (this._deleteBuffer) {
                 delete this._buffer;
             }

+ 4 - 4
src/Misc/fileTools.ts

@@ -134,16 +134,16 @@ export class FileTools {
      * @param mimeType optional mime type
      * @returns the HTMLImageElement of the loaded image
      */
-    public static LoadImage(input: string | ArrayBuffer | ArrayBufferView | Blob, onLoad: (img: HTMLImageElement | ImageBitmap) => void, onError: (message?: string, exception?: any) => void, offlineProvider: Nullable<IOfflineProvider>, mimeType?: string): Nullable<HTMLImageElement> {
+    public static LoadImage(input: string | ArrayBuffer | ArrayBufferView | Blob, onLoad: (img: HTMLImageElement | ImageBitmap) => void, onError: (message?: string, exception?: any) => void, offlineProvider: Nullable<IOfflineProvider>, mimeType: string = ""): Nullable<HTMLImageElement> {
         let url: string;
         let usingObjectURL = false;
 
         if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {
             if (typeof Blob !== 'undefined') {
-                url = URL.createObjectURL(new Blob([input]));
+                url = URL.createObjectURL(new Blob([input], { type: mimeType }));
                 usingObjectURL = true;
             } else {
-                url = `data:${mimeType || "image/jpg"};base64,` + StringTools.EncodeArrayBufferToBase64(input);
+                url = `data:${mimeType};base64,` + StringTools.EncodeArrayBufferToBase64(input);
             }
         }
         else if (input instanceof Blob) {
@@ -157,7 +157,7 @@ export class FileTools {
 
         if (typeof Image === "undefined") {
             FileTools.LoadFile(url, (data) => {
-                createImageBitmap(new Blob([data])).then((imgBmp) => {
+                createImageBitmap(new Blob([data], { type: mimeType })).then((imgBmp) => {
                     onLoad(imgBmp);
                     if (usingObjectURL) {
                         URL.revokeObjectURL(url);

+ 20 - 6
src/Misc/khronosTextureContainer.ts

@@ -82,12 +82,7 @@ export class KhronosTextureContainer {
     public constructor(
         /** contents of the KTX container file */
         public data: ArrayBufferView, facesExpected: number, threeDExpected?: boolean, textureArrayExpected?: boolean) {
-        // Test that it is a ktx formatted file, based on the first 12 bytes, character representation is:
-        // '�', 'K', 'T', 'X', ' ', '1', '1', '�', '\r', '\n', '\x1A', '\n'
-        // 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
-        var identifier = new Uint8Array(this.data.buffer, this.data.byteOffset, 12);
-        if (identifier[0] !== 0xAB || identifier[1] !== 0x4B || identifier[2] !== 0x54 || identifier[3] !== 0x58 || identifier[4] !== 0x20 || identifier[5] !== 0x31 ||
-            identifier[6] !== 0x31 || identifier[7] !== 0xBB || identifier[8] !== 0x0D || identifier[9] !== 0x0A || identifier[10] !== 0x1A || identifier[11] !== 0x0A) {
+        if (!KhronosTextureContainer.IsValid(data)) {
             this.isInvalid = true;
             Logger.Error("texture missing KTX identifier");
             return;
@@ -181,4 +176,23 @@ export class KhronosTextureContainer {
             height = Math.max(1.0, height * 0.5);
         }
     }
+
+    /**
+     * Checks if the given data starts with a KTX file identifier.
+     * @param data the data to check
+     * @returns true if the data is a KTX file or false otherwise
+     */
+    public static IsValid(data: ArrayBufferView): boolean {
+        if (data.byteLength >= 12)
+        {
+            // '«', 'K', 'T', 'X', ' ', '1', '1', '»', '\r', '\n', '\x1A', '\n'
+            const identifier = new Uint8Array(data.buffer, data.byteOffset, 12);
+            if (identifier[0] === 0xAB && identifier[1] === 0x4B && identifier[2] === 0x54 && identifier[3] === 0x58 && identifier[4] === 0x20 && identifier[5] === 0x31 &&
+                identifier[6] === 0x31 && identifier[7] === 0xBB && identifier[8] === 0x0D && identifier[9] === 0x0A && identifier[10] === 0x1A && identifier[11] === 0x0A) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }

+ 87 - 0
src/Misc/khronosTextureContainer2.ts

@@ -0,0 +1,87 @@
+import { InternalTexture } from "../Materials/Textures/internalTexture";
+import { ThinEngine } from "../Engines/thinEngine";
+import { EngineCapabilities } from '../Engines/engineCapabilities';
+
+declare var LIBKTX: any;
+
+/**
+ * Class for loading KTX2 files
+ * !!! Experimental Extension Subject to Changes !!!
+ * @hidden
+ */
+export class KhronosTextureContainer2 {
+    private static _ModulePromise: Promise<{ module: any }>;
+    private static _TranscodeFormat: number;
+
+    public constructor(engine: ThinEngine) {
+        if (!KhronosTextureContainer2._ModulePromise) {
+            KhronosTextureContainer2._ModulePromise = new Promise((resolve) => {
+                LIBKTX().then((module: any) => {
+                    module.GL.makeContextCurrent(module.GL.registerContext(engine._gl, { majorVersion: engine._webGLVersion }));
+                    KhronosTextureContainer2._TranscodeFormat = this._determineTranscodeFormat(module.TranscodeTarget, engine.getCaps());
+                    resolve({ module: module });
+                });
+            });
+        }
+    }
+
+    public uploadAsync(data: ArrayBufferView, internalTexture: InternalTexture): Promise<void> {
+        return KhronosTextureContainer2._ModulePromise.then((moduleWrapper: any) => {
+            const module = moduleWrapper.module;
+
+            const ktxTexture = new module.ktxTexture(data);
+            try {
+                if (ktxTexture.isBasisSupercompressed) {
+                    ktxTexture.transcodeBasis(KhronosTextureContainer2._TranscodeFormat, 0);
+                }
+
+                internalTexture.width = internalTexture.baseWidth = ktxTexture.baseWidth;
+                internalTexture.height = internalTexture.baseHeight = ktxTexture.baseHeight;
+                internalTexture.generateMipMaps = false;
+
+                const result = ktxTexture.glUpload();
+                if (result.error === 0) {
+                    internalTexture._webGLTexture = result.texture;
+                }
+                else {
+                    throw new Error(`Failed to upload: ${result.error}`);
+                }
+
+                internalTexture.isReady = true;
+            }
+            finally {
+                ktxTexture.delete();
+            }
+        });
+    }
+
+    private _determineTranscodeFormat(transcodeTarget: any, caps: EngineCapabilities): number {
+        if (caps.s3tc) {
+            return transcodeTarget.BC1_OR_3;
+        }
+        else if (caps.etc2) {
+            return transcodeTarget.ETC;
+        }
+
+        throw new Error("No compatible format available");
+    }
+
+    /**
+     * Checks if the given data starts with a KTX2 file identifier.
+     * @param data the data to check
+     * @returns true if the data is a KTX2 file or false otherwise
+     */
+    public static IsValid(data: ArrayBufferView): boolean {
+        if (data.byteLength >= 12)
+        {
+            // '«', 'K', 'T', 'X', ' ', '2', '0', '»', '\r', '\n', '\x1A', '\n'
+            const identifier = new Uint8Array(data.buffer, data.byteOffset, 12);
+            if (identifier[0] === 0xAB && identifier[1] === 0x4B && identifier[2] === 0x54 && identifier[3] === 0x58 && identifier[4] === 0x20 && identifier[5] === 0x32 &&
+                identifier[6] === 0x30 && identifier[7] === 0xBB && identifier[8] === 0x0D && identifier[9] === 0x0A && identifier[10] === 0x1A && identifier[11] === 0x0A) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}

+ 6 - 1
src/Shaders/pbr.fragment.fx

@@ -97,7 +97,12 @@ void main(void) {
         alpha *= albedoTexture.a;
     #endif
 
-    surfaceAlbedo *= toLinearSpace(albedoTexture.rgb);
+    #ifdef GAMMAALBEDO
+        surfaceAlbedo *= toLinearSpace(albedoTexture.rgb);
+    #else
+        surfaceAlbedo *= albedoTexture.rgb;
+    #endif
+
     surfaceAlbedo *= vAlbedoInfos.y;
 #endif
 

+ 1 - 0
tests/validation/validate.html

@@ -9,6 +9,7 @@
 	<script src="https://preview.babylonjs.com/ammo.js"></script>
 	<script src="https://preview.babylonjs.com/cannon.js"></script>
 	<script src="https://preview.babylonjs.com/Oimo.js"></script>
+	<script src="https://preview.babylonjs.com/libktx.js"></script>
 	<script src="https://preview.babylonjs.com/babylon.js"></script>
 	<script src="https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js"></script>