Merge pull request #126 from brunogm0/master

Further optimize img!
diff --git a/README.md b/README.md
index aa95636..8e5dc90 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,10 @@
 
 ### Important Usage Notes
 
+Probably the most important concept to understand about Basis Universal before using it: The system supports **two** very different universal texture modes: The original "ETC1S" mode is low/medium quality, but the resulting file sizes are very small because the system has built-in compression for ETC1S texture format files. This is the command line encoding tool's default mode. ETC1S textures work best on images, photos, map data, or albedo/specular/etc. textures, but don't work as well on normal maps. There's the second "UASTC" mode, which is significantly higher quality (near-BC7 grade), and is usable on all texture types including complex normal maps. UASTC mode purposely does not have built-in file compression like ETC1S mode does, so the resulting files are quite large (8-bits/texel - same as BC7) compared to ETC1S mode. The UASTC encoder has an optional Rate Distortion Optimization (RDO) encoding mode (implemented as a post-process over the encoded UASTC texture data), which lowers the output data's entropy in a way that results in better compression when UASTC .basis files are compressed with Deflate/Zstd, etc. In UASTC mode, you must losslessly compress the file yourself.
+
+Basis Universal is not an image compression codec. It's a texture compression codec. It can be used just like an image compression codec, but that's not the only use case. Here's a [good intro](http://renderingpipeline.com/2012/07/texture-compression/) to GPU texture compression. If you're looking to primarily use the system as an image compression codec on sRGB photographic content, use the default ETC1S mode, because it has built-in compression. 
+
 **The "-q X" option controls the output quality in ETC1S mode.** The default is quality level 128. "-q 255" will increase quality quite a bit. If you want even higher quality, try "-max_selectors 16128 -max_endpoints 16128" instead of -q. -q internally tries to set the codebook sizes (or the # of quantization intervals for endpoints/selectors) for you. You need to experiment with the quality level on your content.
 
 For tangent space normal maps, you should separate X into RGB and Y into Alpha, and provide the compressor with 32-bit/pixel input images. Or use the "-separate_rg_to_color_alpha" command line option which does this for you. The internal texture format that Basis Universal uses (ETC1S) doesn't handle tangent space normal maps encoded into RGB well. You need to separate the channels and recover Z in the pixel shader using z=sqrt(1-x^2-y^2).
@@ -150,7 +154,7 @@
 
 For best quality, you must supply basisu with original uncompressed source images. Any other type of lossy compression applied before basisu (including ETC1/BC1-5, BC7, JPEG, etc.) will cause multi-generational artifacts to appear in the final output textures. 
 
-For the maximum possible achievable quality with the current format and encoder (completely ignoring encoding speed!), use:
+For the maximum possible achievable ETC1S mode quality with the current format and encoder (completely ignoring encoding speed!), use:
 
 `basisu x.png -comp_level 5 -max_endpoints 16128 -max_selectors 16128 -no_selector_rdo -no_endpoint_rdo`
 
@@ -187,7 +191,10 @@
 ### More detailed examples
 
 `basisu x.png`\
-Compress sRGB image x.png to x.basis using default settings (multiple filenames OK)
+Compress sRGB image x.png to a ETC1S format x.basis file using default settings (multiple filenames OK). ETC1S format files are typically very small on disk (around .5-1.5 bits/texel).
+
+`basisu -uastc x.png`\
+Compress image x.png to a UASTC format x.basis file using default settings (multiple filenames OK). UASTC files are the same size as BC7 on disk (8-bpp). Be sure to compress UASTC .basis files yourself using Deflate, zstd, etc. To increase .basis file compressibility (trading off quality for smaller compressed files) use the "-uastc_rdo_q X" command line parameter.
 
 `basisu -q 255 x.png`\
 Compress sRGB image x.png to x.basis at max quality level achievable without  manually setting the codebook sizes (multiple filenames OK)
@@ -238,7 +245,7 @@
 
 ### Encoder speed
 
-Total time for basisu.exe to compress a 1024x1024 texture on a 7 year old 4-core 2.2GHz Core i7 laptop - timings are "without mipmaps/with mipmaps":
+Total time for basisu.exe to compress a 1024x1024 ETC1S texture on a 7 year old 4-core 2.2GHz Core i7 laptop - timings are "without mipmaps/with mipmaps":
 
 * -comp_level 0: 
 
diff --git a/basisu.vcxproj b/basisu.vcxproj
index c23a715..3407176 100644
--- a/basisu.vcxproj
+++ b/basisu.vcxproj
@@ -69,16 +69,16 @@
   </ImportGroup>
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
-    <OutDir>$(SolutionDir)\bin</OutDir>
+    <OutDir>$(SolutionDir)\bin\</OutDir>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
-    <OutDir>$(SolutionDir)\bin</OutDir>
+    <OutDir>$(SolutionDir)\bin\</OutDir>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
-    <OutDir>$(SolutionDir)\bin</OutDir>
+    <OutDir>$(SolutionDir)\bin\</OutDir>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
-    <OutDir>$(SolutionDir)\bin</OutDir>
+    <OutDir>$(SolutionDir)\bin\</OutDir>
     <TargetName>$(ProjectName)D</TargetName>
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
@@ -113,11 +113,10 @@
       <Optimization>MaxSpeed</Optimization>
       <FunctionLevelLinking>true</FunctionLevelLinking>
       <IntrinsicFunctions>true</IntrinsicFunctions>
-      <SDLCheck>true</SDLCheck>
       <OpenMPSupport>true</OpenMPSupport>
       <AdditionalIncludeDirectories>
       </AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>NDEBUG;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions>NDEBUG;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <BufferSecurityCheck>false</BufferSecurityCheck>
       <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
     </ClCompile>
@@ -133,9 +132,8 @@
       <Optimization>MaxSpeed</Optimization>
       <FunctionLevelLinking>true</FunctionLevelLinking>
       <IntrinsicFunctions>true</IntrinsicFunctions>
-      <SDLCheck>true</SDLCheck>
       <AdditionalIncludeDirectories>ispc_texcomp</AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>NDEBUG;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions>NDEBUG;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <BufferSecurityCheck>false</BufferSecurityCheck>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
       <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
diff --git a/basisu_tool.cpp b/basisu_tool.cpp
index 813d08b..fdca902 100644
--- a/basisu_tool.cpp
+++ b/basisu_tool.cpp
@@ -110,13 +110,14 @@
 		"Mipmap generation options:\n"
 		" -mipmap: Generate mipmaps for each source image\n"
 		" -mip_srgb: Convert image to linear before filtering, then back to sRGB\n"
-		" -mip_linear: Keep image in linear light during mipmap filtering\n"
+		" -mip_linear: Keep image in linear light during mipmap filtering (i.e. do not convert to/from sRGB for filtering purposes)\n"
 		" -mip_scale X: Set mipmap filter kernel's scale, lower=sharper, higher=more blurry, default is 1.0\n"
 		" -mip_filter X: Set mipmap filter kernel, default is kaiser, filters: box, tent, bell, blackman, catmullrom, mitchell, etc.\n"
 		" -mip_renorm: Renormalize normal map to unit length vectors after filtering\n"
 		" -mip_clamp: Use clamp addressing on borders, instead of wrapping\n"
 		" -mip_smallest X: Set smallest pixel dimension for generated mipmaps, default is 1 pixel\n"
-		"By default, mipmap filtering will occur in sRGB space (for the RGB color channels) unless -linear is specified. You can override this behavior with -mip_srgb/-mip_linear.\n"
+		"By default, textures will be converted from sRGB to linear light before mipmap filtering, then back to sRGB (for the RGB color channels) unless -linear is specified.\n"
+		"You can override this behavior with -mip_srgb/-mip_linear.\n"
 		"\n"
 		"Backend endpoint/selector RDO codec options:\n"
 		" -no_selector_rdo: Disable backend's selector rate distortion optimizations (slightly faster, less noisy output, but lower quality per output bit)\n"
diff --git a/basisu_uastc_enc.cpp b/basisu_uastc_enc.cpp
index ba5083a..c4fb9eb 100644
--- a/basisu_uastc_enc.cpp
+++ b/basisu_uastc_enc.cpp
@@ -1256,8 +1256,7 @@
 			ccell_results_rgb.m_pSelectors_temp = &ccell_result_selectors_temp[0];
 
 			uint64_t part_err_rgb = color_cell_compression(255, &ccell_params_rgb, &ccell_results_rgb, &comp_params);
-			assert(ccell_results_rgb.m_low_endpoint.m_c[rot_comp] == ccell_results_rgb.m_high_endpoint.m_c[rot_comp]);
-
+			
 			color_cell_compressor_params ccell_params_a;
 			memset(&ccell_params_a, 0, sizeof(ccell_params_a));
 
diff --git a/webgl/transcoder/.gitignore b/webgl/transcoder/.gitignore
index 707a2b1..324ab6c 100644
--- a/webgl/transcoder/.gitignore
+++ b/webgl/transcoder/.gitignore
@@ -1,3 +1,4 @@
 build/*
+!build/basis_loader.js
 !build/basis_transcoder.js
 !build/basis_transcoder.wasm
diff --git a/webgl/transcoder/build/basis_loader.js b/webgl/transcoder/build/basis_loader.js
new file mode 100644
index 0000000..f5cd071
--- /dev/null
+++ b/webgl/transcoder/build/basis_loader.js
@@ -0,0 +1,494 @@
+/*

+ * Basis Loader

+ *

+ * Usage:

+ * // basis_loader.js should be loaded from the same directory as

+ * // basis_transcoder.js and basis_transcoder.wasm

+ *

+ * // Create the texture loader and set the WebGL context it should use. Spawns

+ * // a worker which handles all of the transcoding.

+ * let basisLoader = new BasisLoader();

+ * basisLoader.setWebGLContext(gl);

+ *

+ * // To allow separate color and alpha textures to be returned in cases where

+ * // it would provide higher quality:

+ * basisLoader.allowSeparateAlpha = true;

+ *

+ * // loadFromUrl() returns a promise which resolves to a completed WebGL

+ * // texture or rejects if there's an error loading.

+ * basisBasics.loadFromUrl(fullPathToTexture).then((result) => {

+ *   // WebGL color+alpha texture;

+ *   result.texture;

+ *

+ *   // WebGL alpha texture, only if basisLoader.allowSeparateAlpha is true.

+ *   // null if alpha is encoded in result.texture or result.alpha is false.

+ *   result.alphaTexture;

+ *

+ *   // True if the texture contained an alpha channel.

+ *   result.alpha;

+ *

+ *   // Number of mip levels in texture/alphaTexture

+ *   result.mipLevels;

+ *

+ *   // Dimensions of the base mip level.

+ *   result.width;

+ *   result.height;

+ * });

+ */

+

+// This file contains the code both for the main thread interface and the

+// worker that does the transcoding.

+const IN_WORKER = typeof importScripts === "function";

+const SCRIPT_PATH = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;

+

+if (!IN_WORKER) {

+  //

+  // Main Thread

+  //

+  class PendingTextureRequest {

+    constructor(gl, url) {

+      this.gl = gl;

+      this.url = url;

+      this.texture = null;

+      this.alphaTexture = null;

+      this.promise = new Promise((resolve, reject) => {

+        this.resolve = resolve;

+        this.reject = reject;

+      });

+    }

+

+    uploadImageData(webglFormat, buffer, mipLevels) {

+      let gl = this.gl;

+      let texture = gl.createTexture();

+      gl.bindTexture(gl.TEXTURE_2D, texture);

+      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

+      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, mipLevels.length > 1 || webglFormat.uncompressed ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);

+

+      let levelData = null;

+

+      for (let mipLevel of mipLevels) {

+        if (!webglFormat.uncompressed) {

+          levelData = new Uint8Array(buffer, mipLevel.offset, mipLevel.size);

+          gl.compressedTexImage2D(

+            gl.TEXTURE_2D,

+            mipLevel.level,

+            webglFormat.format,

+            mipLevel.width,

+            mipLevel.height,

+            0,

+            levelData);

+        } else {

+          switch (webglFormat.type) {

+            case WebGLRenderingContext.UNSIGNED_SHORT_4_4_4_4:

+            case WebGLRenderingContext.UNSIGNED_SHORT_5_5_5_1:

+            case WebGLRenderingContext.UNSIGNED_SHORT_5_6_5:

+              levelData = new Uint16Array(buffer, mipLevel.offset, mipLevel.size / 2);

+              break;

+            default:

+              levelData = new Uint8Array(buffer, mipLevel.offset, mipLevel.size);

+              break;

+          }

+          gl.texImage2D(

+            gl.TEXTURE_2D,

+            mipLevel.level,

+            webglFormat.format,

+            mipLevel.width,

+            mipLevel.height,

+            0,

+            webglFormat.format,

+            webglFormat.type,

+            levelData);

+        }

+      }

+

+      if (webglFormat.uncompressed && mipLevels.length == 1) {

+        gl.generateMipmap(gl.TEXTURE_2D);

+      }

+

+      return texture;

+    }

+  };

+

+  class BasisLoader {

+    constructor() {

+      this.gl = null;

+      this.supportedFormats = {};

+      this.pendingTextures = {};

+      this.nextPendingTextureId = 1;

+      this.allowSeparateAlpha = false;

+

+      // Reload the current script as a worker

+      this.worker = new Worker(SCRIPT_PATH);

+      this.worker.onmessage = (msg) => {

+        // Find the pending texture associated with the data we just received

+        // from the worker.

+        let pendingTexture = this.pendingTextures[msg.data.id];

+        if (!pendingTexture) {

+          if (msg.data.error) {

+            console.error(`Basis transcode failed: ${msg.data.error}`);

+          }

+          console.error(`Invalid pending texture ID: ${msg.data.id}`);

+          return;

+        }

+

+        // Remove the pending texture from the waiting list.

+        delete this.pendingTextures[msg.data.id];

+

+        // If the worker indicated an error has occured handle it now.

+        if (msg.data.error) {

+          console.error(`Basis transcode failed: ${msg.data.error}`);

+          pendingTexture.reject(`${msg.data.error}`);

+          return;

+        }

+

+        // Upload the image data returned by the worker.

+        pendingTexture.texture = pendingTexture.uploadImageData(

+            msg.data.webglFormat,

+            msg.data.buffer,

+            msg.data.mipLevels);

+

+        if (msg.data.alphaBuffer) {

+          pendingTexture.alphaTexture = pendingTexture.uploadImageData(

+              msg.data.webglFormat,

+              msg.data.alphaBuffer,

+              msg.data.mipLevels);

+        }

+

+        pendingTexture.resolve({

+          mipLevels: msg.data.mipLevels.length,

+          width: msg.data.mipLevels[0].width,

+          height: msg.data.mipLevels[0].height,

+          alpha: msg.data.hasAlpha,

+          texture: pendingTexture.texture,

+          alphaTexture: pendingTexture.alphaTexture,

+        });

+      };

+    }

+

+    setWebGLContext(gl) {

+      if (this.gl != gl) {

+        this.gl = gl;

+        if (gl) {

+          this.supportedFormats = {

+            s3tc: !!gl.getExtension('WEBGL_compressed_texture_s3tc'),

+            etc1: !!gl.getExtension('WEBGL_compressed_texture_etc1'),

+            etc2: !!gl.getExtension('WEBGL_compressed_texture_etc'),

+            pvrtc: !!gl.getExtension('WEBGL_compressed_texture_pvrtc'),

+            astc: !!gl.getExtension('WEBGL_compressed_texture_astc'),

+            bptc: !!gl.getExtension('EXT_texture_compression_bptc')

+          };

+        } else {

+          this.supportedFormats = {};

+        }

+      }

+    }

+

+    // This method changes the active texture unit's TEXTURE_2D binding

+    // immediately prior to resolving the returned promise.

+    loadFromUrl(url) {

+      let pendingTexture = new PendingTextureRequest(this.gl, url);

+      this.pendingTextures[this.nextPendingTextureId] = pendingTexture;

+

+      this.worker.postMessage({

+        id: this.nextPendingTextureId,

+        url: url,

+        allowSeparateAlpha: this.allowSeparateAlpha,

+        supportedFormats: this.supportedFormats

+      });

+

+      this.nextPendingTextureId++;

+      return pendingTexture.promise;

+    }

+  }

+

+  window.BasisLoader = BasisLoader;

+

+} else {

+  //

+  // Worker

+  //

+  importScripts('basis_transcoder.js');

+

+  let BasisFile = null;

+

+  const BASIS_INITIALIZED = BASIS().then((module) => {

+    BasisFile = module.BasisFile;

+    module.initializeBasis();

+  });

+

+  // Copied from enum class transcoder_texture_format in basisu_transcoder.h with minor javascript-ification

+  const BASIS_FORMAT = {

+    // Compressed formats

+

+    // ETC1-2

+    cTFETC1_RGB: 0,							// Opaque only, returns RGB or alpha data if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified

+    cTFETC2_RGBA: 1,							// Opaque+alpha, ETC2_EAC_A8 block followed by a ETC1 block, alpha channel will be opaque for opaque .basis files

+

+    // BC1-5, BC7 (desktop, some mobile devices)

+    cTFBC1_RGB: 2,							// Opaque only, no punchthrough alpha support yet, transcodes alpha slice if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified

+    cTFBC3_RGBA: 3, 							// Opaque+alpha, BC4 followed by a BC1 block, alpha channel will be opaque for opaque .basis files

+    cTFBC4_R: 4,								// Red only, alpha slice is transcoded to output if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified

+    cTFBC5_RG: 5,								// XY: Two BC4 blocks, X=R and Y=Alpha, .basis file should have alpha data (if not Y will be all 255's)

+    cTFBC7_RGBA: 6,							// RGB or RGBA, mode 5 for ETC1S, modes (1,2,3,5,6,7) for UASTC

+

+    // PVRTC1 4bpp (mobile, PowerVR devices)

+    cTFPVRTC1_4_RGB: 8,						// Opaque only, RGB or alpha if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified, nearly lowest quality of any texture format.

+    cTFPVRTC1_4_RGBA: 9,					// Opaque+alpha, most useful for simple opacity maps. If .basis file doesn't have alpha cTFPVRTC1_4_RGB will be used instead. Lowest quality of any supported texture format.

+

+    // ASTC (mobile, Intel devices, hopefully all desktop GPU's one day)

+    cTFASTC_4x4_RGBA: 10,					// Opaque+alpha, ASTC 4x4, alpha channel will be opaque for opaque .basis files. Transcoder uses RGB/RGBA/L/LA modes, void extent, and up to two ([0,47] and [0,255]) endpoint precisions.

+

+    // Uncompressed (raw pixel) formats

+    cTFRGBA32: 13,							// 32bpp RGBA image stored in raster (not block) order in memory, R is first byte, A is last byte.

+    cTFRGB565: 14,							// 166pp RGB image stored in raster (not block) order in memory, R at bit position 11

+    cTFBGR565: 15,							// 16bpp RGB image stored in raster (not block) order in memory, R at bit position 0

+    cTFRGBA4444: 16,							// 16bpp RGBA image stored in raster (not block) order in memory, R at bit position 12, A at bit position 0

+

+    cTFTotalTextureFormats: 22,

+  };

+

+  // WebGL compressed formats types, from:

+  // http://www.khronos.org/registry/webgl/extensions/

+

+  // https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/

+  const COMPRESSED_RGB_S3TC_DXT1_EXT  = 0x83F0;

+  const COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;

+  const COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;

+  const COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;

+

+  // https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc1/

+  const COMPRESSED_RGB_ETC1_WEBGL = 0x8D64;

+

+  // https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc/

+  const COMPRESSED_R11_EAC                        = 0x9270;

+  const COMPRESSED_SIGNED_R11_EAC                 = 0x9271;

+  const COMPRESSED_RG11_EAC                       = 0x9272;

+  const COMPRESSED_SIGNED_RG11_EAC                = 0x9273;

+  const COMPRESSED_RGB8_ETC2                      = 0x9274;

+  const COMPRESSED_SRGB8_ETC2                     = 0x9275;

+  const COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2  = 0x9276;

+  const COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9277;

+  const COMPRESSED_RGBA8_ETC2_EAC                 = 0x9278;

+  const COMPRESSED_SRGB8_ALPHA8_ETC2_EAC          = 0x9279;

+

+  // https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/

+  const COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0;

+

+  // https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/

+  const COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00;

+  const COMPRESSED_RGB_PVRTC_2BPPV1_IMG  = 0x8C01;

+  const COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02;

+  const COMPRESSED_RGBA_PVRTC_2BPPV1_IMG = 0x8C03;

+

+  // https://www.khronos.org/registry/webgl/extensions/EXT_texture_compression_bptc/

+  const COMPRESSED_RGBA_BPTC_UNORM_EXT = 0x8E8C;

+  const COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT = 0x8E8D;

+  const COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT = 0x8E8E;

+  const COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT = 0x8E8F;

+

+  const BASIS_WEBGL_FORMAT_MAP = {};

+  // Compressed formats

+  BASIS_WEBGL_FORMAT_MAP[BASIS_FORMAT.cTFBC1_RGB] = { format: COMPRESSED_RGB_S3TC_DXT1_EXT };

+  BASIS_WEBGL_FORMAT_MAP[BASIS_FORMAT.cTFBC3_RGBA] = { format: COMPRESSED_RGBA_S3TC_DXT5_EXT };

+  BASIS_WEBGL_FORMAT_MAP[BASIS_FORMAT.cTFBC7_RGBA] = { format: COMPRESSED_RGBA_BPTC_UNORM_EXT };

+  BASIS_WEBGL_FORMAT_MAP[BASIS_FORMAT.cTFETC1_RGB] = { format: COMPRESSED_RGB_ETC1_WEBGL };

+  BASIS_WEBGL_FORMAT_MAP[BASIS_FORMAT.cTFETC2_RGBA] = { format: COMPRESSED_RGBA8_ETC2_EAC };

+  BASIS_WEBGL_FORMAT_MAP[BASIS_FORMAT.cTFASTC_4x4_RGBA] = { format: COMPRESSED_RGBA_ASTC_4x4_KHR };

+  BASIS_WEBGL_FORMAT_MAP[BASIS_FORMAT.cTFPVRTC1_4_RGB] = { format: COMPRESSED_RGB_PVRTC_4BPPV1_IMG };

+  BASIS_WEBGL_FORMAT_MAP[BASIS_FORMAT.cTFPVRTC1_4_RGBA] = { format: COMPRESSED_RGBA_PVRTC_4BPPV1_IMG };

+

+  // Uncompressed formats

+  BASIS_WEBGL_FORMAT_MAP[BASIS_FORMAT.cTFRGBA32] = { uncompressed: true, format: WebGLRenderingContext.RGBA, type: WebGLRenderingContext.UNSIGNED_BYTE };

+  BASIS_WEBGL_FORMAT_MAP[BASIS_FORMAT.cTFRGB565] = { uncompressed: true, format: WebGLRenderingContext.RGB, type: WebGLRenderingContext.UNSIGNED_SHORT_5_6_5 };

+  BASIS_WEBGL_FORMAT_MAP[BASIS_FORMAT.cTFRGBA4444] = { uncompressed: true, format: WebGLRenderingContext.RGBA, type: WebGLRenderingContext.UNSIGNED_SHORT_4_4_4_4 };

+

+  // Notifies the main thread when a texture has failed to load for any reason.

+  function fail(id, errorMsg) {

+    postMessage({

+      id: id,

+      error: errorMsg

+    });

+  }

+

+  function basisFileFail(id, basisFile, errorMsg) {

+    fail(id, errorMsg);

+    basisFile.close();

+    basisFile.delete();

+  }

+

+  // This utility currently only transcodes the first image in the file.

+  const IMAGE_INDEX = 0;

+  const TOP_LEVEL_MIP = 0;

+

+  function transcode(id, arrayBuffer, supportedFormats, allowSeparateAlpha) {

+    let basisData = new Uint8Array(arrayBuffer);

+

+    let basisFile = new BasisFile(basisData);

+    let images = basisFile.getNumImages();

+    let levels = basisFile.getNumLevels(IMAGE_INDEX);

+    let hasAlpha = basisFile.getHasAlpha();

+    if (!images || !levels) {

+      basisFileFail(id, basisFile, 'Invalid Basis data');

+      return;

+    }

+

+    if (!basisFile.startTranscoding()) {

+      basisFileFail(id, basisFile, 'startTranscoding failed');

+      return;

+    }

+

+    let basisFormat = undefined;

+    let needsSecondaryAlpha = false;

+    if (hasAlpha) {

+      if (supportedFormats.etc2) {

+        basisFormat = BASIS_FORMAT.cTFETC2_RGBA;

+      } else if (supportedFormats.bptc) {

+        basisFormat = BASIS_FORMAT.cTFBC7_RGBA;

+      } else if (supportedFormats.s3tc) {

+        basisFormat = BASIS_FORMAT.cTFBC3_RGBA;

+      } else if (supportedFormats.astc) {

+        basisFormat = BASIS_FORMAT.cTFASTC_4x4_RGBA;

+      } else if (supportedFormats.pvrtc) {

+        if (allowSeparateAlpha) {

+          basisFormat = BASIS_FORMAT.cTFPVRTC1_4_RGB;

+          needsSecondaryAlpha = true;

+        } else {

+          basisFormat = BASIS_FORMAT.cTFPVRTC1_4_RGBA;

+        }

+      } else if (supportedFormats.etc1 && allowSeparateAlpha) {

+        basisFormat = BASIS_FORMAT.cTFETC1_RGB;

+        needsSecondaryAlpha = true;

+      } else {

+        // If we don't support any appropriate compressed formats transcode to

+        // raw pixels. This is something of a last resort, because the GPU

+        // upload will be significantly slower and take a lot more memory, but

+        // at least it prevents you from needing to store a fallback JPG/PNG and

+        // the download size will still likely be smaller.

+        basisFormat = BASIS_FORMAT.RGBA32;

+      }

+    } else {

+      if (supportedFormats.etc1) {

+        // Should be the highest quality, so use when available.

+        // http://richg42.blogspot.com/2018/05/basis-universal-gpu-texture-format.html

+        basisFormat = BASIS_FORMAT.cTFETC1_RGB;

+      } else if (supportedFormats.bptc) {

+        basisFormat = BASIS_FORMAT.cTFBC7_RGBA;

+      } else if (supportedFormats.s3tc) {

+        basisFormat = BASIS_FORMAT.cTFBC1_RGB;

+      } else if (supportedFormats.etc2) {

+        basisFormat = BASIS_FORMAT.cTFETC2_RGBA;

+      } else if (supportedFormats.astc) {

+        basisFormat = BASIS_FORMAT.cTFASTC_4x4_RGBA;

+      } else if (supportedFormats.pvrtc) {

+        basisFormat = BASIS_FORMAT.cTFPVRTC1_4_RGB;

+      } else {

+        // See note on uncompressed transcode above.

+        basisFormat = BASIS_FORMAT.cTFRGB565;

+      }

+    }

+

+    if (basisFormat === undefined) {

+      basisFileFail(id, basisFile, 'No supported transcode formats');

+      return;

+    }

+

+    let webglFormat = BASIS_WEBGL_FORMAT_MAP[basisFormat];

+

+    // If we're not using compressed textures it'll be cheaper to generate

+    // mipmaps on the fly, so only transcode a single level.

+    if (webglFormat.uncompressed) {

+      levels = 1;

+    }

+

+    // Gather information about each mip level to be transcoded.

+    let mipLevels = [];

+    let totalTranscodeSize = 0;

+

+    for (let mipLevel = 0; mipLevel < levels; ++mipLevel) {

+      let transcodeSize = basisFile.getImageTranscodedSizeInBytes(IMAGE_INDEX, mipLevel, basisFormat);

+      mipLevels.push({

+        level: mipLevel,

+        offset: totalTranscodeSize,

+        size: transcodeSize,

+        width: basisFile.getImageWidth(IMAGE_INDEX, mipLevel),

+        height: basisFile.getImageHeight(IMAGE_INDEX, mipLevel),

+      });

+      totalTranscodeSize += transcodeSize;

+    }

+

+    // Allocate a buffer large enough to hold all of the transcoded mip levels at once.

+    let transcodeData = new Uint8Array(totalTranscodeSize);

+    let alphaTranscodeData = needsSecondaryAlpha ? new Uint8Array(totalTranscodeSize) : null;

+

+    // Transcode each mip level into the appropriate section of the overall buffer.

+    for (let mipLevel of mipLevels) {

+      let levelData = new Uint8Array(transcodeData.buffer, mipLevel.offset, mipLevel.size);

+      if (!basisFile.transcodeImage(levelData, IMAGE_INDEX, mipLevel.level, basisFormat, 1, 0)) {

+        basisFileFail(id, basisFile, 'transcodeImage failed');

+        return;

+      }

+      if (needsSecondaryAlpha) {

+        let alphaLevelData = new Uint8Array(alphaTranscodeData.buffer, mipLevel.offset, mipLevel.size);

+        if (!basisFile.transcodeImage(alphaLevelData, IMAGE_INDEX, mipLevel.level, basisFormat, 1, 1)) {

+          basisFileFail(id, basisFile, 'alpha transcodeImage failed');

+          return;

+        }

+      }

+    }

+

+    basisFile.close();

+    basisFile.delete();

+

+    // Post the transcoded results back to the main thread.

+    let transferList = [transcodeData.buffer];

+    if (needsSecondaryAlpha) {

+      transferList.push(alphaTranscodeData.buffer);

+    }

+    postMessage({

+      id: id,

+      buffer: transcodeData.buffer,

+      alphaBuffer: needsSecondaryAlpha ? alphaTranscodeData.buffer : null,

+      webglFormat: webglFormat,

+      mipLevels: mipLevels,

+      hasAlpha: hasAlpha,

+    }, transferList);

+  }

+

+  onmessage = (msg) => {

+    // Each call to the worker must contain:

+    let url = msg.data.url; // The URL of the basis image OR

+    let buffer = msg.data.buffer; // An array buffer with the basis image data

+    let allowSeparateAlpha = msg.data.allowSeparateAlpha;

+    let supportedFormats = msg.data.supportedFormats; // The formats this device supports

+    let id = msg.data.id; // A unique ID for the texture

+

+    if (url) {

+      // Make the call to fetch the basis texture data

+      fetch(url).then(function(response) {

+        if (response.ok) {

+          response.arrayBuffer().then((arrayBuffer) => {

+            if (BasisFile) {

+              transcode(id, arrayBuffer, supportedFormats, allowSeparateAlpha);

+            } else {

+              BASIS_INITIALIZED.then(() => {

+                transcode(id, arrayBuffer, supportedFormats, allowSeparateAlpha);

+              });

+            }

+          });

+        } else {

+          fail(id, `Fetch failed: ${response.status}, ${response.statusText}`);

+        }

+      });

+    } else if (buffer) {

+      if (BasisFile) {

+        transcode(id, buffer, supportedFormats, allowSeparateAlpha);

+      } else {

+        BASIS_INITIALIZED.then(() => {

+          transcode(id, buffer, supportedFormats, allowSeparateAlpha);

+        });

+      }

+    } else {

+      fail(id, `No url or buffer specified`);

+    }

+  };

+}
\ No newline at end of file