Forráskód Böngészése

Merge pull request #6454 from TrevorDev/additinalBasisSupport

.basis support (Edge support, mipmaps, web worker)
David Catuhe 6 éve
szülő
commit
1384d374d1

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 1
dist/preview release/basisTranscoder/basis_transcoder.js


+ 1 - 1
dist/preview release/what's new.md

@@ -4,7 +4,7 @@
 - WIP: Node Material (NEED DOC AND SAMPLES) ([Deltakosh](https://github.com/deltakosh/))
 - WIP: Node material editor (NEED DOC AND VIDEOS) ([Deltakosh](https://github.com/deltakosh/)/[TrevorDev](https://github.com/TrevorDev))
 - WIP: WebGPU support (NEED DOC AND SAMPLES) ([Sebavan](https://github.com/sebavan/)
-- WIP: .basis texture file format support [Demo](https://www.babylonjs-playground.com/#4RN0VF) ([TrevorDev](https://github.com/TrevorDev))
+- WIP: .basis texture file format support (mipmaps, web worker) [Demo](https://www.babylonjs-playground.com/#4RN0VF) ([TrevorDev](https://github.com/TrevorDev))
 
 ## Optimizations
 

+ 35 - 17
src/Materials/Textures/Loaders/basisTextureLoader.ts

@@ -5,6 +5,9 @@ import { IInternalTextureLoader } from "../../../Materials/Textures/internalText
 import { _TimeToken } from "../../../Instrumentation/timeToken";
 import { _DepthCullingState, _StencilState, _AlphaState } from "../../../States/index";
 import { BasisTools } from "../../../Misc/basis";
+import { Texture } from '../texture';
+import { Tools } from '../../../Misc/tools';
+import { Scalar } from '../../../Maths/math.scalar';
 
 /**
  * Loader for .basis file format
@@ -68,19 +71,21 @@ export class _BasisTextureLoader implements IInternalTextureLoader {
      */
     public loadData(data: ArrayBuffer, texture: InternalTexture,
         callback: (width: number, height: number, loadMipmap: boolean, isCompressed: boolean, done: () => void) => void): void {
-        // Verify Basis Module is loaded and detect file info and format
-        BasisTools.VerifyBasisModuleAsync().then(() => {
-            var loadedFile = BasisTools.LoadBasisFile(data);
-            var fileInfo = BasisTools.GetFileInfo(loadedFile);
-            var format = BasisTools.GetSupportedTranscodeFormat(texture.getEngine(), fileInfo);
-            texture._invertVScale = true;
-
-            // TODO this should be done in web worker
-            var transcodeResult = BasisTools.TranscodeFile(format, fileInfo, loadedFile);
-
-            // Upload data to texture
-            callback(fileInfo.width, fileInfo.height, false, true, () => {
-                if (transcodeResult.fallbackToRgb565) {
+        var caps = texture.getEngine().getCaps();
+        var transcodeConfig = {
+            supportedCompressionFormats: {
+                etc1: caps.etc1 ? true : false,
+                s3tc: caps.s3tc ? true : false,
+                pvrtc: caps.pvrtc ? true : false,
+                etc2: caps.etc2 ? true : false
+            }
+        };
+        BasisTools.TranscodeAsync(data, transcodeConfig).then((result) => {
+            var rootImage = result.fileInfo.images[0].levels[0];
+            callback(rootImage.width, rootImage.height, false, true, () => {
+                texture._invertVScale = true;
+                if (result.format === -1) {
+                    // No compatable compressed format found, fallback to RGB
                     texture.type = Engine.TEXTURETYPE_UNSIGNED_SHORT_5_6_5;
                     texture.format = Engine.TEXTUREFORMAT_RGB;
 
@@ -89,10 +94,11 @@ export class _BasisTextureLoader implements IInternalTextureLoader {
 
                     source.type = Engine.TEXTURETYPE_UNSIGNED_SHORT_5_6_5;
                     source.format = Engine.TEXTUREFORMAT_RGB;
-                    source.width = fileInfo.alignedWidth;
-                    source.height = fileInfo.alignedHeight;
+                    // Fallback requires aligned width/height
+                    source.width = (rootImage.width + 3) & ~3;
+                    source.height = (rootImage.height + 3) & ~3;
                     texture.getEngine()._bindTextureDirectly(source.getEngine()._gl.TEXTURE_2D, source, true);
-                    texture.getEngine()._uploadDataToTextureDirectly(source, transcodeResult.pixels, 0, 0, Engine.TEXTUREFORMAT_RGB, true);
+                    texture.getEngine()._uploadDataToTextureDirectly(source, rootImage.transcodedPixels, 0, 0, Engine.TEXTUREFORMAT_RGB, true);
 
                     // Resize to power of two
                     source.getEngine()._rescaleTexture(source, texture, texture.getEngine().scenes[0], source.getEngine()._getInternalFormat(Engine.TEXTUREFORMAT_RGB), () => {
@@ -100,7 +106,19 @@ export class _BasisTextureLoader implements IInternalTextureLoader {
                         source.getEngine()._bindTextureDirectly(source.getEngine()._gl.TEXTURE_2D, texture, true);
                     });
                 }else {
-                    texture.getEngine()._uploadCompressedDataToTextureDirectly(texture, BasisTools.GetInternalFormatFromBasisFormat(format!), fileInfo.width, fileInfo.height, transcodeResult.pixels, 0, 0);
+                    texture.width = rootImage.width;
+                    texture.height = rootImage.height;
+
+                    // Upload all mip levels in the file
+                    result.fileInfo.images[0].levels.forEach((level, index) => {
+                        texture.getEngine()._uploadCompressedDataToTextureDirectly(texture, BasisTools.GetInternalFormatFromBasisFormat(result.format!), level.width, level.height, level.transcodedPixels, 0, index);
+                    });
+
+                    if (texture.getEngine().webGLVersion < 2 && (Scalar.Log2(texture.width) % 1 !== 0 || Scalar.Log2(texture.height) % 1 !== 0)) {
+                        Tools.Warn("Loaded .basis texture width and height are not a power of two. Texture wrapping will be set to Texture.CLAMP_ADDRESSMODE as other modes are not supported with non power of two dimensions in webGL 1.");
+                        texture._cachedWrapU = Texture.CLAMP_ADDRESSMODE;
+                    }
+
                 }
             });
         });

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

@@ -289,9 +289,26 @@ export class Texture extends BaseTexture {
         scene.getEngine().onBeforeTextureInitObservable.notifyObservers(this);
 
         let load = () => {
-            if (this._texture && this._texture._invertVScale) {
-                this.vScale = -1;
+            if (this._texture) {
+                if (this._texture._invertVScale) {
+                    this.vScale = -1;
+                }
+
+                // Update texutre to match internal texture's wrapping
+                if (this._texture._cachedWrapU !== null) {
+                    this.wrapU = this._texture._cachedWrapU;
+                    this._texture._cachedWrapU = null;
+                }
+                if (this._texture._cachedWrapV !== null) {
+                    this.wrapV = this._texture._cachedWrapV;
+                    this._texture._cachedWrapV = null;
+                }
+                if (this._texture._cachedWrapR !== null) {
+                    this.wrapR = this._texture._cachedWrapR;
+                    this._texture._cachedWrapR = null;
+                }
             }
+
             if (this.onLoadObservable.hasObservers()) {
                 this.onLoadObservable.notifyObservers(this);
             }

+ 232 - 132
src/Misc/basis.ts

@@ -1,5 +1,4 @@
 import { Nullable } from '../types';
-import { Engine } from '../Engines/engine';
 import { Tools } from './tools';
 
 /**
@@ -11,21 +10,59 @@ class BasisFileInfo {
      */
     public hasAlpha: boolean;
     /**
-     * Width of the image
+     * Info about each image of the basis file
      */
-    public width: number;
+    public images: Array<{levels: Array<{width: number, height: number, transcodedPixels: ArrayBufferView}>}>;
+}
+
+/**
+ * Configuration options for the Basis transcoder
+ */
+export class BasisTranscodeConfiguration {
     /**
-     * Height of the image
+     * Supported compression formats used to determine the supported output format of the transcoder
      */
-    public height: number;
+    supportedCompressionFormats?: {
+        /**
+         * etc1 compression format
+         */
+        etc1?: boolean;
+        /**
+         * s3tc compression format
+         */
+        s3tc?: boolean;
+        /**
+         * pvrtc compression format
+         */
+        pvrtc?: boolean;
+        /**
+         * etc2 compression format
+         */
+        etc2?: boolean;
+    };
     /**
-     * Aligned width used when falling back to Rgb565 ((width + 3) & ~3)
+     * If mipmap levels should be loaded for transcoded images (Default: true)
      */
-    public alignedWidth: number;
+    loadMipmapLevels?: boolean;
     /**
-     * Aligned height used when falling back to Rgb565 ((height + 3) & ~3)
+     * Index of a single image to load (Default: all images)
      */
-    public alignedHeight: number;
+    loadSingleImage?: number;
+}
+
+/**
+ * @hidden
+ * Enum of basis transcoder formats
+ */
+enum BASIS_FORMATS {
+    cTFETC1 = 0,
+    cTFBC1 = 1,
+    cTFBC4 = 2,
+    cTFPVRTC1_4_OPAQUE_ONLY = 3,
+    cTFBC7_M6_OPAQUE_ONLY = 4,
+    cTFETC2 = 5,
+    cTFBC3 = 6,
+    cTFBC5 = 7
 }
 
 /**
@@ -34,9 +71,92 @@ class BasisFileInfo {
  */
 export class BasisTools {
     private static _IgnoreSupportedFormats = false;
-    private static _LoadScriptPromise: any = null;
-    private static _FallbackURL = "https://preview.babylonjs.com/basisTranscoder/basis_transcoder.js";
-    private static _BASIS_FORMAT = {
+    /**
+     * URL to use when loading the basis transcoder
+     */
+    public static JSModuleURL = "https://preview.babylonjs.com/basisTranscoder/basis_transcoder.js";
+    /**
+     * URL to use when loading the wasm module for the transcoder
+     */
+    public static WasmModuleURL = "https://preview.babylonjs.com/basisTranscoder/basis_transcoder.wasm";
+
+    /**
+     * Get the internal format to be passed to texImage2D corresponding to the .basis format value
+     * @param basisFormat format chosen from GetSupportedTranscodeFormat
+     * @returns internal format corresponding to the Basis format
+     */
+    public static GetInternalFormatFromBasisFormat(basisFormat: number) {
+        // Corrisponding internal formats
+        var COMPRESSED_RGB_S3TC_DXT1_EXT  = 0x83F0;
+        var COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
+        var RGB_ETC1_Format = 36196;
+
+        if (basisFormat === BASIS_FORMATS.cTFETC1) {
+            return RGB_ETC1_Format;
+        }else if (basisFormat === BASIS_FORMATS.cTFBC1) {
+            return COMPRESSED_RGB_S3TC_DXT1_EXT;
+        }else if (basisFormat === BASIS_FORMATS.cTFBC3) {
+            return COMPRESSED_RGBA_S3TC_DXT5_EXT;
+        }else {
+            throw "The chosen Basis transcoder format is not currently supported";
+        }
+    }
+
+    private static _WorkerPromise: Nullable<Promise<Worker>> = null;
+    private static _Worker: Nullable<Worker> = null;
+    private static _CreateWorkerAsync() {
+        if (!this._WorkerPromise) {
+            this._WorkerPromise = new Promise((res) => {
+                if (this._Worker) {
+                    res(this._Worker);
+                }else {
+                    Tools.LoadFileAsync(BasisTools.WasmModuleURL).then((wasmBinary) => {
+                        const workerBlobUrl = URL.createObjectURL(new Blob([`(${workerFunc})()`], { type: "application/javascript" }));
+                        this._Worker = new Worker(workerBlobUrl);
+
+                        var initHandler = (msg: any) => {
+                            if (msg.data.action === "init") {
+                                this._Worker!.removeEventListener("message", initHandler);
+                                res(this._Worker!);
+                            }
+                        };
+                        this._Worker.addEventListener("message", initHandler);
+                        this._Worker.postMessage({action: "init", url: BasisTools.JSModuleURL, wasmBinary: wasmBinary});
+                    });
+                }
+            });
+        }
+        return this._WorkerPromise;
+    }
+
+    /**
+     * Transcodes a loaded image file to compressed pixel data
+     * @param imageData image data to transcode
+     * @param config configuration options for the transcoding
+     * @returns a promise resulting in the transcoded image
+     */
+    public static TranscodeAsync(imageData: ArrayBuffer, config: BasisTranscodeConfiguration): Promise<{fileInfo: BasisFileInfo, format: number}> {
+        return new Promise((res) => {
+            this._CreateWorkerAsync().then(() => {
+                var messageHandler = (msg: any) => {
+                    if (msg.data.action === "transcode") {
+                        this._Worker!.removeEventListener("message", messageHandler);
+                        res(msg.data);
+                    }
+                };
+                this._Worker!.addEventListener("message", messageHandler);
+                this._Worker!.postMessage({action: "transcode", imageData: imageData, config: config, ignoreSupportedFormats: this._IgnoreSupportedFormats}, [imageData]);
+            });
+        });
+    }
+}
+
+// WorkerGlobalScope
+declare function importScripts(...urls: string[]): void;
+declare function postMessage(message: any, transfer?: any[]): void;
+declare var Module: any;
+function workerFunc(): void {
+    var _BASIS_FORMAT = {
         cTFETC1: 0,
         cTFBC1: 1,
         cTFBC4: 2,
@@ -46,158 +166,138 @@ export class BasisTools {
         cTFBC3: 6,
         cTFBC5: 7,
     };
-    /**
-     * Basis module can be aquired from https://github.com/BinomialLLC/basis_universal/tree/master/webgl
-     * This should be set prior to loading a .basis texture
-     */
-    public static BasisModule: Nullable<any> = null;
+    var transcoderModulePromise: Nullable<Promise<any>> = null;
+    onmessage = (event) => {
+        if (event.data.action === "init") {
+             // Load the transcoder if it hasn't been yet
+            if (!transcoderModulePromise) {
+                // Override wasm binary
+                Module = { wasmBinary: (event.data.wasmBinary) };
+                importScripts(event.data.url);
+                transcoderModulePromise = new Promise((res) => {
+                    Module.onRuntimeInitialized = () => {
+                        Module.initializeBasis();
+                        res();
+                    };
+                });
+            }
+            transcoderModulePromise.then(() => {
+                postMessage({action: "init"});
+            });
+        }else if (event.data.action === "transcode") {
+            // Transcode the basis image and return the resulting pixels
+            var config: BasisTranscodeConfiguration = event.data.config;
+            var imgData = event.data.imageData;
+            var loadedFile = new Module.BasisFile(new Uint8Array(imgData));
+            var fileInfo = GetFileInfo(loadedFile);
+            var format = event.data.ignoreSupportedFormats ? null : GetSupportedTranscodeFormat(event.data.config, fileInfo);
 
-    /**
-     * Verifies that the BasisModule has been populated and falls back to loading from the web if not availible
-     * @returns promise which will resolve if the basis module was loaded
-     */
-    public static VerifyBasisModuleAsync() {
-        // Complete if module has been populated
-        if (BasisTools.BasisModule) {
-            return Promise.resolve();
-        }
+            var needsConversion = false;
+            if (format === null) {
+                needsConversion = true;
+                format = fileInfo.hasAlpha ? _BASIS_FORMAT.cTFBC3 : _BASIS_FORMAT.cTFBC1;
+            }
 
-        // Otherwise load script from fallback url
-        if (!this._LoadScriptPromise) {
-            this._LoadScriptPromise = Tools.LoadScriptAsync(BasisTools._FallbackURL, "basis_transcoder").then((success) => {
-                return new Promise((res, rej) => {
-                    if ((window as any).Module) {
-                        (window as any).Module.onRuntimeInitialized = () => {
-                            BasisTools.BasisModule = (window as any).Module;
-                            BasisTools.BasisModule.initializeBasis();
-                            res();
-                        };
+            // Begin transcode
+            if (!loadedFile.startTranscoding()) {
+                loadedFile.close();
+                loadedFile.delete();
+                throw "transcode failed";
+            }
+
+            var buffers: Array<any> = [];
+            fileInfo.images.forEach((image, imageIndex) => {
+                if (config.loadSingleImage === undefined || config.loadSingleImage === imageIndex) {
+                    if (config.loadMipmapLevels === false) {
+                        var levelInfo = image.levels[0];
+                        levelInfo.transcodedPixels = TranscodeLevel(loadedFile, imageIndex, 0, format!, needsConversion);
+                        buffers.push(levelInfo.transcodedPixels.buffer);
                     }else {
-                        rej("Unable to load .basis texture, BasisTools.BasisModule should be populated");
+                        image.levels.forEach((levelInfo, levelIndex) => {
+                            levelInfo.transcodedPixels = TranscodeLevel(loadedFile, imageIndex, levelIndex, format!, needsConversion);
+                            buffers.push(levelInfo.transcodedPixels.buffer);
+
+                        });
                     }
-                });
+                }
             });
+
+            // Close file
+            loadedFile.close();
+            loadedFile.delete();
+
+            if (needsConversion) {
+                format = -1;
+            }
+            postMessage({action: "transcode", fileInfo: fileInfo, format: format}, buffers);
         }
-        return this._LoadScriptPromise;
-    }
 
-    /**
-     * Verifies that the basis module has been populated and creates a bsis file from the image data
-     * @param data array buffer of the .basis file
-     * @returns the Basis file
-     */
-    public static LoadBasisFile(data: ArrayBuffer) {
-        return new BasisTools.BasisModule.BasisFile(new Uint8Array(data));
-    }
+    };
 
     /**
      * Detects the supported transcode format for the file
-     * @param engine Babylon engine
+     * @param config transcode config
      * @param fileInfo info about the file
      * @returns the chosed format or null if none are supported
      */
-    public static GetSupportedTranscodeFormat(engine: Engine, fileInfo: BasisFileInfo): Nullable<number> {
-        var caps = engine.getCaps();
+    function GetSupportedTranscodeFormat(config: BasisTranscodeConfiguration, fileInfo: BasisFileInfo): Nullable<number> {
         var format = null;
-        if (caps.etc1) {
-            format = BasisTools._BASIS_FORMAT.cTFETC1;
-        }else if (caps.s3tc) {
-            format = fileInfo.hasAlpha ? BasisTools._BASIS_FORMAT.cTFBC3 : BasisTools._BASIS_FORMAT.cTFBC1;
-        }else if (caps.pvrtc) {
-            format = BasisTools._BASIS_FORMAT.cTFPVRTC1_4_OPAQUE_ONLY;
-        }else if (caps.etc2) {
-            format = BasisTools._BASIS_FORMAT.cTFETC2;
+        if (config.supportedCompressionFormats) {
+            if (config.supportedCompressionFormats.etc1) {
+                format = _BASIS_FORMAT.cTFETC1;
+            }else if (config.supportedCompressionFormats.s3tc) {
+                format = fileInfo.hasAlpha ? _BASIS_FORMAT.cTFBC3 : _BASIS_FORMAT.cTFBC1;
+            }else if (config.supportedCompressionFormats.pvrtc) {
+                format = _BASIS_FORMAT.cTFPVRTC1_4_OPAQUE_ONLY;
+            }else if (config.supportedCompressionFormats.etc2) {
+                format = _BASIS_FORMAT.cTFETC2;
+            }
         }
         return format;
     }
 
     /**
-     * Get the internal format to be passed to texImage2D corresponding to the .basis format value
-     * @param basisFormat format chosen from GetSupportedTranscodeFormat
-     * @returns internal format corresponding to the Basis format
-     */
-    public static GetInternalFormatFromBasisFormat(basisFormat: number) {
-        // TODO more formats need to be added here and validated
-        var COMPRESSED_RGB_S3TC_DXT1_EXT  = 0x83F0;
-        var COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
-        var RGB_ETC1_Format = 36196;
-
-        // var COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
-        // var COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
-
-        if (basisFormat === this._BASIS_FORMAT.cTFETC1) {
-            return RGB_ETC1_Format;
-        }else if (basisFormat === this._BASIS_FORMAT.cTFBC1) {
-            return COMPRESSED_RGB_S3TC_DXT1_EXT;
-        }else if (basisFormat === this._BASIS_FORMAT.cTFBC3) {
-            return COMPRESSED_RGBA_S3TC_DXT5_EXT;
-        }else {
-            // TODO find value for these formats
-            // else if(basisFormat === this.BASIS_FORMAT.cTFBC4){
-            // }else if(basisFormat === this.BASIS_FORMAT.cTFPVRTC1_4_OPAQUE_ONLY){
-            // }else if(basisFormat === this.BASIS_FORMAT.cTFBC7_M6_OPAQUE_ONLY){
-            // }else if(basisFormat === this.BASIS_FORMAT.cTFETC2){
-            // }else if(basisFormat === this.BASIS_FORMAT.cTFBC5){
-            // }
-            throw "Basis format not found or supported";
-        }
-    }
-
-    /**
      * Retreives information about the basis file eg. dimensions
      * @param basisFile the basis file to get the info from
      * @returns information about the basis file
      */
-    public static GetFileInfo(basisFile: any): BasisFileInfo {
+    function GetFileInfo(basisFile: any): BasisFileInfo {
         var hasAlpha = basisFile.getHasAlpha();
-        var width = basisFile.getImageWidth(0, 0);
-        var height = basisFile.getImageHeight(0, 0);
-        var alignedWidth = (width + 3) & ~3;
-        var alignedHeight = (height + 3) & ~3;
-        var info = { hasAlpha, width, height, alignedWidth, alignedHeight };
+        var imageCount = basisFile.getNumImages();
+        var images = [];
+        for (var i = 0; i < imageCount; i++) {
+            var imageInfo = {
+                levels: ([] as Array<any>)
+            };
+            var levelCount = basisFile.getNumLevels(i);
+            for (var level = 0; level < levelCount; level++) {
+                var levelInfo = {
+                    width: basisFile.getImageWidth(i, level),
+                    height: basisFile.getImageHeight(i, level)
+                };
+                imageInfo.levels.push(levelInfo);
+            }
+            images.push(imageInfo);
+        }
+        var info = { hasAlpha, images };
         return info;
     }
 
-    /**
-     * Transcodes the basis file to the requested format to be transferred to the gpu
-     * @param format fromat to be transferred to
-     * @param fileInfo information about the loaded file
-     * @param loadedFile the loaded basis file
-     * @returns the resulting pixels and if the transcode fell back to using Rgb565
-     */
-    public static TranscodeFile(format: Nullable<number>, fileInfo: BasisFileInfo, loadedFile: any) {
-        if (BasisTools._IgnoreSupportedFormats) {
-            format = null;
-        }
-        var needsConversion = false;
-        if (format === null) {
-            needsConversion = true;
-            format = fileInfo.hasAlpha ? BasisTools._BASIS_FORMAT.cTFBC3 : BasisTools._BASIS_FORMAT.cTFBC1;
-        }
-
-        if (!loadedFile.startTranscoding()) {
-            loadedFile.close();
-            loadedFile.delete();
-            throw "transcode failed";
-        }
-        var dstSize = loadedFile.getImageTranscodedSizeInBytes(0, 0, format);
+    function TranscodeLevel(loadedFile: any, imageIndex: number, levelIndex: number, format: number, convertToRgb565: boolean) {
+        var dstSize = loadedFile.getImageTranscodedSizeInBytes(imageIndex, levelIndex, format);
         var dst = new Uint8Array(dstSize);
-        if (!loadedFile.transcodeImage(dst, 0, 0, format, 1, 0)) {
+        if (!loadedFile.transcodeImage(dst, imageIndex, levelIndex, format, 1, 0)) {
             loadedFile.close();
             loadedFile.delete();
             throw "transcode failed";
         }
-        loadedFile.close();
-        loadedFile.delete();
-
         // If no supported format is found, load as dxt and convert to rgb565
-        if (needsConversion) {
-            dst = BasisTools.ConvertDxtToRgb565(dst, 0, fileInfo.alignedWidth, fileInfo.alignedHeight);
+        if (convertToRgb565) {
+            var alignedWidth = (loadedFile.getImageWidth(imageIndex, levelIndex) + 3) & ~3;
+            var alignedHeight = (loadedFile.getImageHeight(imageIndex, levelIndex) + 3) & ~3;
+            dst = ConvertDxtToRgb565(dst, 0, alignedWidth, alignedHeight);
         }
-
-        return {
-            fallbackToRgb565: needsConversion, pixels: dst
-        };
+        return dst;
     }
 
     /**
@@ -211,7 +311,7 @@ export class BasisTools {
      * @param  height aligned height of the image
      * @return the converted pixels
      */
-    public static ConvertDxtToRgb565(src: Uint16Array, srcByteOffset: number, width: number, height: number): Uint16Array {
+    function ConvertDxtToRgb565(src: Uint16Array, srcByteOffset: number, width: number, height: number): Uint16Array {
         var c = new Uint16Array(4);
         var dst = new Uint16Array(width * height);
 
@@ -240,4 +340,4 @@ export class BasisTools {
         }
         return dst;
     }
-}
+}

+ 15 - 0
src/Misc/tools.ts

@@ -1013,6 +1013,21 @@ export class Tools {
     }
 
     /**
+     * Loads a file from a url
+     * @param url the file url to load
+     * @returns a promise containing an ArrayBuffer corrisponding to the loaded file
+     */
+    public static LoadFileAsync(url: string): Promise<ArrayBuffer> {
+        return new Promise((resolve, reject) => {
+            Tools.LoadFile(url, (data) => {
+                resolve(data as ArrayBuffer);
+            }, undefined, undefined, true, (request, exception) => {
+                reject(exception);
+            });
+        });
+    }
+
+    /**
      * Load a script (identified by an url). When the url returns, the
      * content of this file is added into a new script element, attached to the DOM (body element)
      * @param scriptUrl defines the url of the script to laod