浏览代码

Merge pull request #9104 from Popov72/ktx2decoder-improve

KTX2 decoder: Multiple fixes and adds
David Catuhe 4 年之前
父节点
当前提交
840f3f0660

+ 6 - 0
ktx2Decoder/src/Transcoders/liteTranscoder_UASTC_ASTC.ts

@@ -14,6 +14,12 @@ export class LiteTranscoder_UASTC_ASTC extends LiteTranscoder {
         return src === sourceTextureFormat.UASTC4x4 && dst === transcodeTarget.ASTC_4x4_RGBA;
         return src === sourceTextureFormat.UASTC4x4 && dst === transcodeTarget.ASTC_4x4_RGBA;
     }
     }
 
 
+    public static Name = "UniversalTranscoder_UASTC_ASTC";
+
+    public getName(): string {
+        return LiteTranscoder_UASTC_ASTC.Name;
+    }
+
     public initialize(): void {
     public initialize(): void {
         this.setModulePath(LiteTranscoder_UASTC_ASTC.WasmModuleURL);
         this.setModulePath(LiteTranscoder_UASTC_ASTC.WasmModuleURL);
     }
     }

+ 6 - 0
ktx2Decoder/src/Transcoders/liteTranscoder_UASTC_BC7.ts

@@ -14,6 +14,12 @@ export class LiteTranscoder_UASTC_BC7 extends LiteTranscoder {
         return src === sourceTextureFormat.UASTC4x4 && dst === transcodeTarget.BC7_RGBA;
         return src === sourceTextureFormat.UASTC4x4 && dst === transcodeTarget.BC7_RGBA;
     }
     }
 
 
+    public static Name = "UniversalTranscoder_UASTC_BC7";
+
+    public getName(): string {
+        return LiteTranscoder_UASTC_BC7.Name;
+    }
+
     public initialize(): void {
     public initialize(): void {
         this.setModulePath(LiteTranscoder_UASTC_BC7.WasmModuleURL);
         this.setModulePath(LiteTranscoder_UASTC_BC7.WasmModuleURL);
     }
     }

+ 6 - 0
ktx2Decoder/src/Transcoders/mscTranscoder.ts

@@ -21,6 +21,12 @@ export class MSCTranscoder extends Transcoder {
 
 
     public static UseFromWorkerThread = true;
     public static UseFromWorkerThread = true;
 
 
+    public static Name = "MSCTranscoder";
+
+    public getName(): string {
+        return MSCTranscoder.Name;
+    }
+
     private _mscBasisTranscoderPromise: Promise<any>;
     private _mscBasisTranscoderPromise: Promise<any>;
     private _mscBasisModule: any;
     private _mscBasisModule: any;
 
 

+ 40 - 13
ktx2Decoder/src/ktx2Decoder.ts

@@ -18,7 +18,7 @@ import { ZSTDDecoder } from './zstddec';
 
 
 const COMPRESSED_RGBA_BPTC_UNORM_EXT = 0x8E8C;
 const COMPRESSED_RGBA_BPTC_UNORM_EXT = 0x8E8C;
 const COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0;
 const COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0;
-const COMPRESSED_RGB_S3TC_DXT1_EXT  = 0x83F0;
+const COMPRESSED_RGB_S3TC_DXT1_EXT  = 0x83F1;
 const COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
 const COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
 const COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02;
 const COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02;
 const COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00;
 const COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00;
@@ -34,6 +34,7 @@ export interface IDecodedData {
     mipmaps: Array<IMipmap>;
     mipmaps: Array<IMipmap>;
     isInGammaSpace: boolean;
     isInGammaSpace: boolean;
     errors?: string;
     errors?: string;
+    transcoderName?: string;
 }
 }
 
 
 export interface IMipmap {
 export interface IMipmap {
@@ -51,13 +52,29 @@ export interface ICompressedFormatCapabilities {
     etc1?: boolean;
     etc1?: boolean;
 }
 }
 
 
+export interface IKTX2DecoderOptions {
+    /** use RGBA format if ASTC and BC7 are not available as transcoded format */
+    useRGBAIfASTCBC7NotAvailable?: boolean;
+
+    /** force to always use RGBA for transcoded format */
+    forceRGBA?: boolean;
+
+    /**
+     * list of transcoders to bypass when looking for a suitable transcoder. The available transcoders are:
+     *      UniversalTranscoder_UASTC_ASTC
+     *      UniversalTranscoder_UASTC_BC7
+     *      MSCTranscoder
+    */
+    bypassTranscoders?: string[];
+}
+
 const isPowerOfTwo = (value: number)  => {
 const isPowerOfTwo = (value: number)  => {
     return (value & (value - 1)) === 0 && value !== 0;
     return (value & (value - 1)) === 0 && value !== 0;
 };
 };
 
 
 /**
 /**
  * Class for decoding KTX2 files
  * Class for decoding KTX2 files
- * 
+ *
  */
  */
 export class KTX2Decoder {
 export class KTX2Decoder {
 
 
@@ -68,7 +85,7 @@ export class KTX2Decoder {
         this._transcoderMgr = new TranscoderManager();
         this._transcoderMgr = new TranscoderManager();
     }
     }
 
 
-    public decode(data: Uint8Array, caps: ICompressedFormatCapabilities): Promise<IDecodedData | null> {
+    public decode(data: Uint8Array, caps: ICompressedFormatCapabilities, options?: IKTX2DecoderOptions): Promise<IDecodedData | null> {
         return Promise.resolve().then(() => {
         return Promise.resolve().then(() => {
             const kfr = new KTX2FileReader(data);
             const kfr = new KTX2FileReader(data);
 
 
@@ -84,15 +101,15 @@ export class KTX2Decoder {
                 }
                 }
 
 
                 return this._zstdDecoder.init().then(() => {
                 return this._zstdDecoder.init().then(() => {
-                    return this._decodeData(kfr, caps);
+                    return this._decodeData(kfr, caps, options);
                 });
                 });
             }
             }
 
 
-            return this._decodeData(kfr, caps);
+            return this._decodeData(kfr, caps, options);
         });
         });
     }
     }
 
 
-    private _decodeData(kfr: KTX2FileReader, caps: ICompressedFormatCapabilities): Promise<IDecodedData> {
+    private _decodeData(kfr: KTX2FileReader, caps: ICompressedFormatCapabilities, options?: IKTX2DecoderOptions): Promise<IDecodedData> {
         const width = kfr.header.pixelWidth;
         const width = kfr.header.pixelWidth;
         const height = kfr.header.pixelHeight;
         const height = kfr.header.pixelHeight;
         const srcTexFormat = kfr.textureFormat;
         const srcTexFormat = kfr.textureFormat;
@@ -102,13 +119,22 @@ export class KTX2Decoder {
 
 
         let targetFormat = -1;
         let targetFormat = -1;
         let transcodedFormat = -1;
         let transcodedFormat = -1;
+        let roundToMultiple4 = true;
 
 
-        if (caps.astc) {
+        if (options?.forceRGBA) {
+            targetFormat = transcodeTarget.RGBA32;
+            transcodedFormat = RGBA8Format;
+            roundToMultiple4 = false;
+        } else if (caps.astc) {
             targetFormat = transcodeTarget.ASTC_4x4_RGBA;
             targetFormat = transcodeTarget.ASTC_4x4_RGBA;
             transcodedFormat = COMPRESSED_RGBA_ASTC_4x4_KHR;
             transcodedFormat = COMPRESSED_RGBA_ASTC_4x4_KHR;
         } else if (caps.bptc) {
         } else if (caps.bptc) {
             targetFormat = transcodeTarget.BC7_RGBA;
             targetFormat = transcodeTarget.BC7_RGBA;
             transcodedFormat = COMPRESSED_RGBA_BPTC_UNORM_EXT;
             transcodedFormat = COMPRESSED_RGBA_BPTC_UNORM_EXT;
+        } else if (options?.useRGBAIfASTCBC7NotAvailable) {
+            targetFormat = transcodeTarget.RGBA32;
+            transcodedFormat = RGBA8Format;
+            roundToMultiple4 = false;
         } else if (caps.s3tc) {
         } else if (caps.s3tc) {
             targetFormat = kfr.hasAlpha ? transcodeTarget.BC3_RGBA : transcodeTarget.BC1_RGB;
             targetFormat = kfr.hasAlpha ? transcodeTarget.BC3_RGBA : transcodeTarget.BC1_RGB;
             transcodedFormat = kfr.hasAlpha ? COMPRESSED_RGBA_S3TC_DXT5_EXT : COMPRESSED_RGB_S3TC_DXT1_EXT;
             transcodedFormat = kfr.hasAlpha ? COMPRESSED_RGBA_S3TC_DXT5_EXT : COMPRESSED_RGB_S3TC_DXT1_EXT;
@@ -124,9 +150,10 @@ export class KTX2Decoder {
         } else {
         } else {
             targetFormat = transcodeTarget.RGBA32;
             targetFormat = transcodeTarget.RGBA32;
             transcodedFormat = RGBA8Format;
             transcodedFormat = RGBA8Format;
+            roundToMultiple4 = false;
         }
         }
 
 
-        const transcoder = this._transcoderMgr.findTranscoder(srcTexFormat, targetFormat);
+        const transcoder = this._transcoderMgr.findTranscoder(srcTexFormat, targetFormat, options?.bypassTranscoders);
 
 
         if (transcoder === null) {
         if (transcoder === null) {
             throw new Error(`no transcoder found to transcode source texture format "${sourceTextureFormat[srcTexFormat]}" to format "${transcodeTarget[targetFormat]}"`);
             throw new Error(`no transcoder found to transcode source texture format "${sourceTextureFormat[srcTexFormat]}" to format "${transcodeTarget[targetFormat]}"`);
@@ -135,7 +162,7 @@ export class KTX2Decoder {
         const mipmaps: Array<IMipmap> = [];
         const mipmaps: Array<IMipmap> = [];
         const dataPromises: Array<Promise<Uint8Array | null>> = [];
         const dataPromises: Array<Promise<Uint8Array | null>> = [];
         const mipmapBuffers: Array<ArrayBuffer> = [];
         const mipmapBuffers: Array<ArrayBuffer> = [];
-        const decodedData: IDecodedData = { width: 0, height: 0, transcodedFormat, mipmaps, isInGammaSpace: kfr.isInGammaSpace };
+        const decodedData: IDecodedData = { width: 0, height: 0, transcodedFormat, mipmaps, isInGammaSpace: kfr.isInGammaSpace, transcoderName: transcoder.getName() };
 
 
         let firstImageDescIndex = 0;
         let firstImageDescIndex = 0;
 
 
@@ -144,8 +171,8 @@ export class KTX2Decoder {
                 firstImageDescIndex += Math.max(kfr.header.layerCount, 1) * kfr.header.faceCount * Math.max(kfr.header.pixelDepth >> (level - 1), 1);
                 firstImageDescIndex += Math.max(kfr.header.layerCount, 1) * kfr.header.faceCount * Math.max(kfr.header.pixelDepth >> (level - 1), 1);
             }
             }
 
 
-            const levelWidth = Math.ceil(width / (1 << level));
-            const levelHeight = Math.ceil(height / (1 << level));
+            const levelWidth = Math.floor(width / (1 << level));
+            const levelHeight = Math.floor(height / (1 << level));
 
 
             const numImagesInLevel = kfr.header.faceCount; // note that cubemap are not supported yet (see KTX2FileReader), so faceCount == 1
             const numImagesInLevel = kfr.header.faceCount; // note that cubemap are not supported yet (see KTX2FileReader), so faceCount == 1
             const levelImageByteLength = ((levelWidth + 3) >> 2) * ((levelHeight + 3) >> 2) * kfr.dfdBlock.bytesPlane[0];
             const levelImageByteLength = ((levelWidth + 3) >> 2) * ((levelHeight + 3) >> 2) * kfr.dfdBlock.bytesPlane[0];
@@ -163,8 +190,8 @@ export class KTX2Decoder {
             }
             }
 
 
             if (level === 0) {
             if (level === 0) {
-                decodedData.width = levelWidth;
-                decodedData.height = levelHeight;
+                decodedData.width = roundToMultiple4 ? (levelWidth + 3) & ~3 : levelWidth;
+                decodedData.height = roundToMultiple4 ? (levelHeight + 3) & ~3 : levelHeight;
             }
             }
 
 
             for (let imageIndex = 0; imageIndex < numImagesInLevel; imageIndex ++) {
             for (let imageIndex = 0; imageIndex < numImagesInLevel; imageIndex ++) {

+ 6 - 0
ktx2Decoder/src/transcoder.ts

@@ -33,6 +33,12 @@ export class Transcoder {
         return false;
         return false;
     }
     }
 
 
+    public static Name = "Transcoder";
+
+    public getName(): string {
+        return Transcoder.Name;
+    }
+
     public initialize(): void {
     public initialize(): void {
     }
     }
 
 

+ 25 - 6
ktx2Decoder/src/transcoderManager.ts

@@ -12,17 +12,18 @@ export class TranscoderManager {
         TranscoderManager._Transcoders.push(transcoder);
         TranscoderManager._Transcoders.push(transcoder);
     }
     }
 
 
-    private static _transcoderInstances: { [key: string]: Transcoder } = {};
+    private static _transcoderInstances: { [key: string]: Array<Transcoder> } = {};
 
 
     private _wasmMemoryManager: WASMMemoryManager;
     private _wasmMemoryManager: WASMMemoryManager;
 
 
-    public findTranscoder(src: sourceTextureFormat, dst: transcodeTarget): Transcoder | null {
+    public findTranscoder(src: sourceTextureFormat, dst: transcodeTarget, bypass?: string[]): Transcoder | null {
         let transcoder: Transcoder | null = null;
         let transcoder: Transcoder | null = null;
 
 
+        const key = sourceTextureFormat[src] + "_" + transcodeTarget[dst];
+
         for (let i = 0; i < TranscoderManager._Transcoders.length; ++i) {
         for (let i = 0; i < TranscoderManager._Transcoders.length; ++i) {
-            if (TranscoderManager._Transcoders[i].CanTranscode(src, dst)) {
-                const key = sourceTextureFormat[src] + "_" + transcodeTarget[dst];
-                transcoder = TranscoderManager._transcoderInstances[key];
+            if (TranscoderManager._Transcoders[i].CanTranscode(src, dst) && (!bypass || bypass.indexOf(TranscoderManager._Transcoders[i].Name) < 0)) {
+                transcoder = this._getExistingTranscoder(key, TranscoderManager._Transcoders[i].Name);
                 if (!transcoder) {
                 if (!transcoder) {
                     transcoder = new TranscoderManager._Transcoders[i]();
                     transcoder = new TranscoderManager._Transcoders[i]();
                     transcoder!.initialize();
                     transcoder!.initialize();
@@ -32,7 +33,10 @@ export class TranscoderManager {
                         }
                         }
                         transcoder!.setMemoryManager(this._wasmMemoryManager);
                         transcoder!.setMemoryManager(this._wasmMemoryManager);
                     }
                     }
-                    TranscoderManager._transcoderInstances[key] = transcoder;
+                    if (!TranscoderManager._transcoderInstances[key]) {
+                        TranscoderManager._transcoderInstances[key] = [];
+                    }
+                    TranscoderManager._transcoderInstances[key].push(transcoder);
                 }
                 }
                 break;
                 break;
             }
             }
@@ -40,4 +44,19 @@ export class TranscoderManager {
 
 
         return transcoder;
         return transcoder;
     }
     }
+
+    private _getExistingTranscoder(key: string, transcoderName: string): Transcoder | null {
+        let transcoders = TranscoderManager._transcoderInstances[key];
+
+        if (transcoders) {
+            for (let t = 0; t < transcoders.length; ++t) {
+                const transcoder = transcoders[t];
+                if (transcoderName === transcoder.getName()) {
+                    return transcoder;
+                }
+            }
+        }
+
+        return null;
+    }
 }
 }

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

@@ -33,11 +33,12 @@ 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 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 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 fallback defines texture to use while falling back when (compressed) texture file not found.
+         * @param loaderOptions options to be passed to the loader
          * @returns the cube texture as an InternalTexture
          * @returns the cube texture as an InternalTexture
          */
          */
         createCubeTexture(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap: boolean | undefined,
         createCubeTexture(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap: boolean | undefined,
             onLoad: Nullable<(data?: any) => void>, onError: Nullable<(message?: string, exception?: any) => void>,
             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>): InternalTexture;
+            format: number | undefined, forcedExtension: any, createPolynomials: boolean, lodScale: number, lodOffset: number, fallback: Nullable<InternalTexture>, loaderOptions: any): InternalTexture;
 
 
         /**
         /**
          * Creates a cube texture
          * Creates a cube texture
@@ -215,7 +216,10 @@ ThinEngine.prototype._setCubeMapTextureParams = function(loadMipmap: boolean): v
     this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
     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): 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, loaderOptions?: any): InternalTexture
+{
     const gl = this._gl;
     const gl = this._gl;
 
 
     const texture = fallback ? fallback : new InternalTexture(this, InternalTextureSource.Cube);
     const texture = fallback ? fallback : new InternalTexture(this, InternalTextureSource.Cube);
@@ -255,7 +259,7 @@ ThinEngine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullab
         else {
         else {
             // fall back to the original url if the transformed url fails to load
             // fall back to the original url if the transformed url fails to load
             Logger.Warn(`Failed to load ${rootUrl}, falling back to the ${originalRootUrl}`);
             Logger.Warn(`Failed to load ${rootUrl}, falling back to the ${originalRootUrl}`);
-            this.createCubeTexture(originalRootUrl, scene, files, noMipmap, onLoad, onError, format, forcedExtension, createPolynomials, lodScale, lodOffset, texture);
+            this.createCubeTexture(originalRootUrl, scene, files, noMipmap, onLoad, onError, format, forcedExtension, createPolynomials, lodScale, lodOffset, texture, loaderOptions);
         }
         }
     };
     };
 
 

+ 3 - 2
src/Engines/nativeEngine.ts

@@ -1002,12 +1002,13 @@ export class NativeEngine extends Engine {
      * @param format internal format.  Default: RGB when extension is '.jpg' else RGBA.  Ignored for compressed textures
      * @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 forcedExtension defines the extension to use to pick the right loader
      * @param mimeType defines an optional mime type
      * @param mimeType defines an optional mime type
+     * @param loaderOptions options to be passed to the loader
      * @returns a InternalTexture for assignment back into BABYLON.Texture
      * @returns a InternalTexture for assignment back into BABYLON.Texture
      */
      */
     public createTexture(url: Nullable<string>, noMipmap: boolean, invertY: boolean, scene: Nullable<ISceneLike>, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE,
     public createTexture(url: 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,
         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,
         buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob | ImageBitmap> = null, fallback: Nullable<InternalTexture> = null, format: Nullable<number> = null,
-        forcedExtension: Nullable<string> = null, mimeType?: string): InternalTexture {
+        forcedExtension: Nullable<string> = null, mimeType?: string, loaderOptions?: any): InternalTexture {
         url = url || "";
         url = url || "";
         const fromData = url.substr(0, 5) === "data:";
         const fromData = url.substr(0, 5) === "data:";
         //const fromBlob = url.substr(0, 5) === "blob:";
         //const fromBlob = url.substr(0, 5) === "blob:";
@@ -1073,7 +1074,7 @@ export class NativeEngine extends Engine {
             else {
             else {
                 // fall back to the original url if the transformed url fails to load
                 // fall back to the original url if the transformed url fails to load
                 Logger.Warn(`Failed to load ${url}, falling back to ${originalUrl}`);
                 Logger.Warn(`Failed to load ${url}, falling back to ${originalUrl}`);
-                this.createTexture(originalUrl, noMipmap, texture.invertY, scene, samplingMode, onLoad, onError, buffer, texture, format, forcedExtension, mimeType);
+                this.createTexture(originalUrl, noMipmap, texture.invertY, scene, samplingMode, onLoad, onError, buffer, texture, format, forcedExtension, mimeType, loaderOptions);
             }
             }
         };
         };
 
 

+ 4 - 3
src/Engines/thinEngine.ts

@@ -2864,12 +2864,13 @@ export class ThinEngine {
      * @param format internal format.  Default: RGB when extension is '.jpg' else RGBA.  Ignored for compressed textures
      * @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 forcedExtension defines the extension to use to pick the right loader
      * @param mimeType defines an optional mime type
      * @param mimeType defines an optional mime type
+     * @param loaderOptions options to be passed to the loader
      * @returns a InternalTexture for assignment back into BABYLON.Texture
      * @returns a InternalTexture for assignment back into BABYLON.Texture
      */
      */
     public createTexture(url: Nullable<string>, noMipmap: boolean, invertY: boolean, scene: Nullable<ISceneLike>, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE,
     public createTexture(url: 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,
         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,
         buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob | ImageBitmap> = null, fallback: Nullable<InternalTexture> = null, format: Nullable<number> = null,
-        forcedExtension: Nullable<string> = null, mimeType?: string): InternalTexture {
+        forcedExtension: Nullable<string> = null, mimeType?: string, loaderOptions?: any): InternalTexture {
         url = url || "";
         url = url || "";
         const fromData = url.substr(0, 5) === "data:";
         const fromData = url.substr(0, 5) === "data:";
         const fromBlob = url.substr(0, 5) === "blob:";
         const fromBlob = url.substr(0, 5) === "blob:";
@@ -2942,7 +2943,7 @@ export class ThinEngine {
             else {
             else {
                 // fall back to the original url if the transformed url fails to load
                 // fall back to the original url if the transformed url fails to load
                 Logger.Warn(`Failed to load ${url}, falling back to ${originalUrl}`);
                 Logger.Warn(`Failed to load ${url}, falling back to ${originalUrl}`);
-                this.createTexture(originalUrl, noMipmap, texture.invertY, scene, samplingMode, onLoad, onError, buffer, texture, format, forcedExtension, mimeType);
+                this.createTexture(originalUrl, noMipmap, texture.invertY, scene, samplingMode, onLoad, onError, buffer, texture, format, forcedExtension, mimeType, loaderOptions);
             }
             }
         };
         };
 
 
@@ -2958,7 +2959,7 @@ export class ThinEngine {
                             return false;
                             return false;
                         }, samplingMode);
                         }, samplingMode);
                     }
                     }
-                });
+                }, loaderOptions);
             };
             };
 
 
             if (!buffer) {
             if (!buffer) {

+ 2 - 2
src/Materials/Textures/Loaders/ktxTextureLoader.ts

@@ -72,7 +72,7 @@ export class _KTXTextureLoader implements IInternalTextureLoader {
      * @param callback defines the method to call once ready to upload
      * @param callback defines the method to call once ready to upload
      */
      */
     public loadData(data: ArrayBufferView, texture: InternalTexture,
     public loadData(data: ArrayBufferView, texture: InternalTexture,
-        callback: (width: number, height: number, loadMipmap: boolean, isCompressed: boolean, done: () => void, loadFailed: boolean) => void): void {
+        callback: (width: number, height: number, loadMipmap: boolean, isCompressed: boolean, done: () => void, loadFailed: boolean) => void, options?: any): void {
         if (KhronosTextureContainer.IsValid(data)) {
         if (KhronosTextureContainer.IsValid(data)) {
             // Need to invert vScale as invertY via UNPACK_FLIP_Y_WEBGL is not supported by compressed texture
             // Need to invert vScale as invertY via UNPACK_FLIP_Y_WEBGL is not supported by compressed texture
             texture._invertVScale = !texture.invertY;
             texture._invertVScale = !texture.invertY;
@@ -83,7 +83,7 @@ export class _KTXTextureLoader implements IInternalTextureLoader {
         }
         }
         else if (KhronosTextureContainer2.IsValid(data)) {
         else if (KhronosTextureContainer2.IsValid(data)) {
             const ktx2 = new KhronosTextureContainer2(texture.getEngine());
             const ktx2 = new KhronosTextureContainer2(texture.getEngine());
-            ktx2.uploadAsync(data, texture).then(() => {
+            ktx2.uploadAsync(data, texture, options).then(() => {
                 callback(texture.width, texture.height, texture.generateMipMaps, true, () => {}, false);
                 callback(texture.width, texture.height, texture.generateMipMaps, true, () => {}, false);
             }, (error) => {
             }, (error) => {
                 Logger.Warn(`Failed to load KTX2 texture data: ${error.message}`);
                 Logger.Warn(`Failed to load KTX2 texture data: ${error.message}`);

+ 6 - 3
src/Materials/Textures/cubeTexture.ts

@@ -103,6 +103,7 @@ export class CubeTexture extends BaseTexture {
 
 
     private _format: number;
     private _format: number;
     private _createPolynomials: boolean;
     private _createPolynomials: boolean;
+    private _loaderOptions: any;
 
 
     /**
     /**
      * Creates a cube texture from an array of image urls
      * Creates a cube texture from an array of image urls
@@ -154,12 +155,13 @@ export class CubeTexture extends BaseTexture {
      * @param createPolynomials defines whether or not to create polynomial harmonics from the texture data if necessary
      * @param createPolynomials defines whether or not to create polynomial harmonics from the texture data if necessary
      * @param lodScale defines the scale applied to environment texture. This manages the range of LOD level used for IBL according to the roughness
      * @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 lodOffset defines the offset applied to environment texture. This manages first LOD level used for IBL according to the roughness
+     * @param loaderOptions options to be passed to the loader
      * @return the cube texture
      * @return the cube texture
      */
      */
     constructor(rootUrl: string, sceneOrEngine: Scene | ThinEngine, extensions: Nullable<string[]> = null, noMipmap: boolean = false, files: Nullable<string[]> = null,
     constructor(rootUrl: string, sceneOrEngine: Scene | ThinEngine, extensions: Nullable<string[]> = null, noMipmap: boolean = false, files: Nullable<string[]> = null,
         onLoad: Nullable<() => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, format: number = Constants.TEXTUREFORMAT_RGBA, prefiltered = false,
         onLoad: Nullable<() => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, format: number = Constants.TEXTUREFORMAT_RGBA, prefiltered = false,
         forcedExtension: any = null, createPolynomials: boolean = false,
         forcedExtension: any = null, createPolynomials: boolean = false,
-        lodScale: number = 0.8, lodOffset: number = 0) {
+        lodScale: number = 0.8, lodOffset: number = 0, loaderOptions?: any) {
         super(sceneOrEngine);
         super(sceneOrEngine);
 
 
         this.name = rootUrl;
         this.name = rootUrl;
@@ -174,6 +176,7 @@ export class CubeTexture extends BaseTexture {
         this._extensions = extensions;
         this._extensions = extensions;
         this._files = files;
         this._files = files;
         this._forcedExtension = forcedExtension;
         this._forcedExtension = forcedExtension;
+        this._loaderOptions = loaderOptions;
 
 
         if (!rootUrl && !files) {
         if (!rootUrl && !files) {
             return;
             return;
@@ -231,7 +234,7 @@ export class CubeTexture extends BaseTexture {
                     this._texture = this._getEngine()!.createPrefilteredCubeTexture(rootUrl, scene, lodScale, lodOffset, onLoad, onError, format, forcedExtension, this._createPolynomials);
                     this._texture = this._getEngine()!.createPrefilteredCubeTexture(rootUrl, scene, lodScale, lodOffset, onLoad, onError, format, forcedExtension, this._createPolynomials);
                 }
                 }
                 else {
                 else {
-                    this._texture = this._getEngine()!.createCubeTexture(rootUrl, scene, files, noMipmap, onLoad, onError, this._format, forcedExtension, false, lodScale, lodOffset);
+                    this._texture = this._getEngine()!.createCubeTexture(rootUrl, scene, files, noMipmap, onLoad, onError, this._format, forcedExtension, false, lodScale, lodOffset, null, loaderOptions);
                 }
                 }
                 this._texture?.onLoadedObservable.add(() => this.onLoadObservable.notifyObservers(this));
                 this._texture?.onLoadedObservable.add(() => this.onLoadObservable.notifyObservers(this));
 
 
@@ -305,7 +308,7 @@ export class CubeTexture extends BaseTexture {
                 this._texture = this._getEngine()!.createPrefilteredCubeTexture(this.url, scene, 0.8, 0, this._delayedOnLoad, undefined, this._format, undefined, this._createPolynomials);
                 this._texture = this._getEngine()!.createPrefilteredCubeTexture(this.url, scene, 0.8, 0, this._delayedOnLoad, undefined, this._format, undefined, this._createPolynomials);
             }
             }
             else {
             else {
-                this._texture = this._getEngine()!.createCubeTexture(this.url, scene, this._files, this._noMipmap, this._delayedOnLoad, null, this._format, forcedExtension);
+                this._texture = this._getEngine()!.createCubeTexture(this.url, scene, this._files, this._noMipmap, this._delayedOnLoad, null, this._format, forcedExtension, false, 0, 0, null, this._loaderOptions);
             }
             }
 
 
             this._texture?.onLoadedObservable.add(() => this.onLoadObservable.notifyObservers(this));
             this._texture?.onLoadedObservable.add(() => this.onLoadObservable.notifyObservers(this));

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

@@ -25,14 +25,16 @@ export interface IInternalTextureLoader {
      * @param createPolynomials will be true if polynomials have been requested
      * @param createPolynomials will be true if polynomials have been requested
      * @param onLoad defines the callback to trigger once the texture is ready
      * @param onLoad defines the callback to trigger once the texture is ready
      * @param onError defines the callback to trigger in case of error
      * @param onError defines the callback to trigger in case of error
+     * @param options options to be passed to the loader
      */
      */
-    loadCubeData(data: ArrayBufferView | ArrayBufferView[], texture: InternalTexture, createPolynomials: boolean, onLoad: Nullable<(data?: any) => void>, onError: Nullable<(message?: string, exception?: any) => void>): void;
+    loadCubeData(data: ArrayBufferView | ArrayBufferView[], texture: InternalTexture, createPolynomials: boolean, onLoad: Nullable<(data?: any) => void>, onError: Nullable<(message?: string, exception?: any) => void>, options?: any): void;
 
 
     /**
     /**
      * Uploads the 2D texture data to the WebGL texture. It has already 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 data contains the texture data
      * @param texture defines the BabylonJS internal texture
      * @param texture defines the BabylonJS internal texture
      * @param callback defines the method to call once ready to upload
      * @param callback defines the method to call once ready to upload
+     * @param options options to be passed to the loader
      */
      */
-    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, options?: any): void;
 }
 }

+ 9 - 3
src/Materials/Textures/texture.ts

@@ -234,6 +234,7 @@ export class Texture extends BaseTexture {
     private _delayedOnLoad: Nullable<() => void> = null;
     private _delayedOnLoad: Nullable<() => void> = null;
     private _delayedOnError: Nullable<() => void> = null;
     private _delayedOnError: Nullable<() => void> = null;
     private _mimeType?: string;
     private _mimeType?: string;
+    private _loaderOptions?: any;
 
 
     /** Returns the texture mime type if it was defined by a loader (undefined else) */
     /** Returns the texture mime type if it was defined by a loader (undefined else) */
     public get mimeType() {
     public get mimeType() {
@@ -291,8 +292,12 @@ export class Texture extends BaseTexture {
      * @param deleteBuffer defines if the buffer we are loading the texture from should be deleted after load
      * @param deleteBuffer defines if the buffer we are loading the texture from should be deleted after load
      * @param format defines the format of the texture we are trying to load (Engine.TEXTUREFORMAT_RGBA...)
      * @param format defines the format of the texture we are trying to load (Engine.TEXTUREFORMAT_RGBA...)
      * @param mimeType defines an optional mime type information
      * @param mimeType defines an optional mime type information
+     * @param loaderOptions options to be passed to the loader
      */
      */
-    constructor(url: Nullable<string>, sceneOrEngine: Nullable<Scene | ThinEngine>, noMipmap: boolean = false, invertY: boolean = true, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, onLoad: Nullable<() => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob | ImageBitmap> = null, deleteBuffer: boolean = false, format?: number, mimeType?: string) {
+    constructor(url: Nullable<string>, sceneOrEngine: Nullable<Scene | ThinEngine>, noMipmap: boolean = false, invertY: boolean = true, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE,
+            onLoad: Nullable<() => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob | ImageBitmap> = null,
+            deleteBuffer: boolean = false, format?: number, mimeType?: string, loaderOptions?: any)
+    {
         super(sceneOrEngine);
         super(sceneOrEngine);
 
 
         this.name = url || "";
         this.name = url || "";
@@ -303,6 +308,7 @@ export class Texture extends BaseTexture {
         this._buffer = buffer;
         this._buffer = buffer;
         this._deleteBuffer = deleteBuffer;
         this._deleteBuffer = deleteBuffer;
         this._mimeType = mimeType;
         this._mimeType = mimeType;
+        this._loaderOptions = loaderOptions;
         if (format) {
         if (format) {
             this._format = format;
             this._format = format;
         }
         }
@@ -359,7 +365,7 @@ export class Texture extends BaseTexture {
 
 
         if (!this._texture) {
         if (!this._texture) {
             if (!scene || !scene.useDelayedTextureLoading) {
             if (!scene || !scene.useDelayedTextureLoading) {
-                this._texture = engine.createTexture(this.url, noMipmap, invertY, scene, samplingMode, load, onError, this._buffer, undefined, this._format, null, mimeType);
+                this._texture = engine.createTexture(this.url, noMipmap, invertY, scene, samplingMode, load, onError, this._buffer, undefined, this._format, null, mimeType, loaderOptions);
                 if (deleteBuffer) {
                 if (deleteBuffer) {
                     this._buffer = null;
                     this._buffer = null;
                 }
                 }
@@ -421,7 +427,7 @@ export class Texture extends BaseTexture {
         this._texture = this._getFromCache(this.url, this._noMipmap, this.samplingMode, this._invertY);
         this._texture = this._getFromCache(this.url, this._noMipmap, this.samplingMode, this._invertY);
 
 
         if (!this._texture) {
         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, 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, this._loaderOptions);
             if (this._deleteBuffer) {
             if (this._deleteBuffer) {
                 this._buffer = null;
                 this._buffer = null;
             }
             }

+ 4 - 4
src/Misc/khronosTextureContainer2.ts

@@ -109,7 +109,7 @@ export class KhronosTextureContainer2 {
     }
     }
 
 
     /** @hidden */
     /** @hidden */
-    public uploadAsync(data: ArrayBufferView, internalTexture: InternalTexture): Promise<void> {
+    public uploadAsync(data: ArrayBufferView, internalTexture: InternalTexture, options?: any): Promise<void> {
         const caps = this._engine.getCaps();
         const caps = this._engine.getCaps();
 
 
         const compressedTexturesCaps = {
         const compressedTexturesCaps = {
@@ -154,7 +154,7 @@ export class KhronosTextureContainer2 {
                         worker.addEventListener("message", onMessage);
                         worker.addEventListener("message", onMessage);
 
 
                         // note: we can't transfer the ownership of data.buffer because if using a fallback texture the data.buffer buffer will be used by the current thread
                         // note: we can't transfer the ownership of data.buffer because if using a fallback texture the data.buffer buffer will be used by the current thread
-                        worker.postMessage({ action: "decode", data, caps: compressedTexturesCaps }/*, [data.buffer]*/);
+                        worker.postMessage({ action: "decode", data, caps: compressedTexturesCaps, options }/*, [data.buffer]*/);
                     });
                     });
                 });
                 });
             });
             });
@@ -187,7 +187,7 @@ export class KhronosTextureContainer2 {
         delete KhronosTextureContainer2._WorkerPoolPromise;
         delete KhronosTextureContainer2._WorkerPoolPromise;
     }
     }
 
 
-    protected _createTexture(data: any /* IEncodedData */, internalTexture: InternalTexture) {
+    protected _createTexture(data: any /* IDecodedData */, internalTexture: InternalTexture) {
         this._engine._bindTextureDirectly(this._engine._gl.TEXTURE_2D, internalTexture);
         this._engine._bindTextureDirectly(this._engine._gl.TEXTURE_2D, internalTexture);
 
 
         if (data.transcodedFormat === 0x8058 /* RGBA8 */) {
         if (data.transcodedFormat === 0x8058 /* RGBA8 */) {
@@ -277,7 +277,7 @@ function workerFunc(): void {
                 postMessage({ action: "init" });
                 postMessage({ action: "init" });
                 break;
                 break;
             case "decode":
             case "decode":
-                ktx2Decoder.decode(event.data.data, event.data.caps).then((data: any) => {
+                ktx2Decoder.decode(event.data.data, event.data.caps, event.data.options).then((data: any) => {
                     const buffers = [];
                     const buffers = [];
                     for (let mip = 0; mip < data.mipmaps.length; ++mip) {
                     for (let mip = 0; mip < data.mipmaps.length; ++mip) {
                         const mipmap = data.mipmaps[mip];
                         const mipmap = data.mipmaps[mip];