diff --git a/webgl/encode_test/assets/kodim03.png b/webgl/encode_test/assets/kodim03.png
new file mode 100644
index 0000000..639a417
Binary files /dev/null and b/webgl/encode_test/assets/kodim03.png differ
diff --git a/webgl/encode_test/assets/kodim18.png b/webgl/encode_test/assets/kodim18.png
new file mode 100644
index 0000000..187fec2
Binary files /dev/null and b/webgl/encode_test/assets/kodim18.png differ
diff --git a/webgl/encode_test/assets/kodim18_64x64.png b/webgl/encode_test/assets/kodim18_64x64.png
new file mode 100644
index 0000000..1166093
Binary files /dev/null and b/webgl/encode_test/assets/kodim18_64x64.png differ
diff --git a/webgl/encode_test/assets/kodim26_uastc_1024.basis b/webgl/encode_test/assets/kodim26_uastc_1024.basis
new file mode 100644
index 0000000..671d188
Binary files /dev/null and b/webgl/encode_test/assets/kodim26_uastc_1024.basis differ
diff --git a/webgl/encode_test/dxt-to-rgb565.js b/webgl/encode_test/dxt-to-rgb565.js
new file mode 100644
index 0000000..8e35be4
--- /dev/null
+++ b/webgl/encode_test/dxt-to-rgb565.js
@@ -0,0 +1,128 @@
+/**
+ * Transcodes DXT into RGB565.
+ * This is an optimized version of dxtToRgb565Unoptimized() below.
+ * Optimizations:
+ * 1. Use integer math to compute c2 and c3 instead of floating point
+ * math. Specifically:
+ * c2 = 5/8 * c0 + 3/8 * c1
+ * c3 = 3/8 * c0 + 5/8 * c1
+ * This is about a 40% performance improvement. It also appears to
+ * match what hardware DXT decoders do, as the colors produced
+ * by this integer math match what hardware produces, while the
+ * floating point in dxtToRgb565Unoptimized() produce slightly
+ * different colors (for one GPU this was tested on).
+ * 2. Unroll the inner loop. Another ~10% improvement.
+ * 3. Compute r0, g0, b0, r1, g1, b1 only once instead of twice.
+ * Another 10% improvement.
+ * 4. Use a Uint16Array instead of a Uint8Array. Another 10% improvement.
+ * @param {Uint16Array} src The src DXT bits as a Uint16Array.
+ * @param {number} srcByteOffset
+ * @param {number} width
+ * @param {number} height
+ * @return {Uint16Array} dst
+ */
+function dxtToRgb565(src, src16Offset, width, height) {
+ var c = new Uint16Array(4);
+ var dst = new Uint16Array(width * height);
+ var nWords = (width * height) / 4;
+ var m = 0;
+ var dstI = 0;
+ var i = 0;
+ var r0 = 0, g0 = 0, b0 = 0, r1 = 0, g1 = 0, b1 = 0;
+
+ var blockWidth = width / 4;
+ var blockHeight = height / 4;
+ for (var blockY = 0; blockY < blockHeight; blockY++) {
+ for (var blockX = 0; blockX < blockWidth; blockX++) {
+ i = src16Offset + 4 * (blockY * blockWidth + blockX);
+ c[0] = src[i];
+ c[1] = src[i + 1];
+
+ r0 = c[0] & 0x1f;
+ g0 = c[0] & 0x7e0;
+ b0 = c[0] & 0xf800;
+ r1 = c[1] & 0x1f;
+ g1 = c[1] & 0x7e0;
+ b1 = c[1] & 0xf800;
+ // Interpolate between c0 and c1 to get c2 and c3.
+ // Note that we approximate 1/3 as 3/8 and 2/3 as 5/8 for
+ // speed. This also appears to be what the hardware DXT
+ // decoder in many GPUs does :)
+
+ // rg FIXME: This is most likely leading to wrong results vs. a GPU
+
+ c[2] = ((5 * r0 + 3 * r1) >> 3)
+ | (((5 * g0 + 3 * g1) >> 3) & 0x7e0)
+ | (((5 * b0 + 3 * b1) >> 3) & 0xf800);
+ c[3] = ((5 * r1 + 3 * r0) >> 3)
+ | (((5 * g1 + 3 * g0) >> 3) & 0x7e0)
+ | (((5 * b1 + 3 * b0) >> 3) & 0xf800);
+ m = src[i + 2];
+ dstI = (blockY * 4) * width + blockX * 4;
+ dst[dstI] = c[m & 0x3];
+ dst[dstI + 1] = c[(m >> 2) & 0x3];
+ dst[dstI + 2] = c[(m >> 4) & 0x3];
+ dst[dstI + 3] = c[(m >> 6) & 0x3];
+ dstI += width;
+ dst[dstI] = c[(m >> 8) & 0x3];
+ dst[dstI + 1] = c[(m >> 10) & 0x3];
+ dst[dstI + 2] = c[(m >> 12) & 0x3];
+ dst[dstI + 3] = c[(m >> 14)];
+ m = src[i + 3];
+ dstI += width;
+ dst[dstI] = c[m & 0x3];
+ dst[dstI + 1] = c[(m >> 2) & 0x3];
+ dst[dstI + 2] = c[(m >> 4) & 0x3];
+ dst[dstI + 3] = c[(m >> 6) & 0x3];
+ dstI += width;
+ dst[dstI] = c[(m >> 8) & 0x3];
+ dst[dstI + 1] = c[(m >> 10) & 0x3];
+ dst[dstI + 2] = c[(m >> 12) & 0x3];
+ dst[dstI + 3] = c[(m >> 14)];
+ }
+ }
+ return dst;
+}
+
+
+/**
+ * An unoptimized version of dxtToRgb565. Also, the floating
+ * point math used to compute the colors actually results in
+ * slightly different colors compared to hardware DXT decoders.
+ * @param {Uint8Array} src
+ * @param {number} srcByteOffset
+ * @param {number} width
+ * @param {number} height
+ * @return {Uint16Array} dst
+ */
+function dxtToRgb565Unoptimized(src, srcByteOffset, width, height) {
+ var c = new Uint16Array(4);
+ var dst = new Uint16Array(width * height);
+ var nWords = (width * height) / 4;
+
+ var blockWidth = width / 4;
+ var blockHeight = height / 4;
+ for (var blockY = 0; blockY < blockHeight; blockY++) {
+ for (var blockX = 0; blockX < blockWidth; blockX++) {
+ var i = srcByteOffset + 8 * (blockY * blockWidth + blockX);
+ c[0] = src[i] | (src[i + 1] << 8);
+ c[1] = src[i + 2] | (src[i + 3] << 8);
+ c[2] = (2 * (c[0] & 0x1f) + 1 * (c[1] & 0x1f)) / 3
+ | (((2 * (c[0] & 0x7e0) + 1 * (c[1] & 0x7e0)) / 3) & 0x7e0)
+ | (((2 * (c[0] & 0xf800) + 1 * (c[1] & 0xf800)) / 3) & 0xf800);
+ c[3] = (2 * (c[1] & 0x1f) + 1 * (c[0] & 0x1f)) / 3
+ | (((2 * (c[1] & 0x7e0) + 1 * (c[0] & 0x7e0)) / 3) & 0x7e0)
+ | (((2 * (c[1] & 0xf800) + 1 * (c[0] & 0xf800)) / 3) & 0xf800);
+ for (var row = 0; row < 4; row++) {
+ var m = src[i + 4 + row];
+ var dstI = (blockY * 4 + row) * width + blockX * 4;
+ dst[dstI++] = c[m & 0x3];
+ dst[dstI++] = c[(m >> 2) & 0x3];
+ dst[dstI++] = c[(m >> 4) & 0x3];
+ dst[dstI++] = c[(m >> 6) & 0x3];
+ }
+ }
+ }
+ return dst;
+}
+
diff --git a/webgl/encode_test/index.html b/webgl/encode_test/index.html
new file mode 100644
index 0000000..8be010c
--- /dev/null
+++ b/webgl/encode_test/index.html
@@ -0,0 +1,589 @@
+
+
+
+
+
+
+
+
+
+
+ Basis Universal compressed texture transcoding and encoding test
+
+
+
This demo uses the Basis C++ transcoder (compiled to Javascript using Emscripten) to transcode a .basis file to FORMAT
+
Thanks to Evan Parker for providing webgl-texture-utils and this test bed.
+
+
+ .basis file:
+
+
+
+
+
+ .png file:
+
+
+
+
+
+ Debug:
+
+
+ Compute Stats:
+
+
+ sRGB:
+
+
+ Mipmaps:
+
+
+ UASTC:
+
+
+
+
+
+ ETC1S Quality:
+
+
+
+
+
+
+
+
+
+
+
+ NOTE: Your browser does not support several compressed texture format, so using RGB565.
+
+
+
+
+
+
+
+
diff --git a/webgl/encode_test/preview.png b/webgl/encode_test/preview.png
new file mode 100644
index 0000000..f1c1a6b
Binary files /dev/null and b/webgl/encode_test/preview.png differ
diff --git a/webgl/encode_test/renderer.js b/webgl/encode_test/renderer.js
new file mode 100644
index 0000000..26f1a65
--- /dev/null
+++ b/webgl/encode_test/renderer.js
@@ -0,0 +1,246 @@
+/**
+ * Constructs a renderer object.
+ * @param {WebGLRenderingContext} gl The GL context.
+ * @constructor
+ */
+var Renderer = function(gl) {
+ /**
+ * The GL context.
+ * @type {WebGLRenderingContext}
+ * @private
+ */
+ this.gl_ = gl;
+
+ /**
+ * The WebGLProgram.
+ * @type {WebGLProgram}
+ * @private
+ */
+ this.program_ = gl.createProgram();
+
+ /**
+ * @type {WebGLShader}
+ * @private
+ */
+ this.vertexShader_ = this.compileShader_(
+ Renderer.vertexShaderSource_, gl.VERTEX_SHADER);
+
+ /**
+ * @type {WebGLShader}
+ * @private
+ */
+ this.fragmentShader_ = this.compileShader_(
+ Renderer.fragmentShaderSource_, gl.FRAGMENT_SHADER);
+
+ /**
+ * Cached uniform locations.
+ * @type {Object.}
+ * @private
+ */
+ this.uniformLocations_ = {};
+
+ /**
+ * Cached attribute locations.
+ * @type {Object.}
+ * @private
+ */
+ this.attribLocations_ = {};
+
+ /**
+ * A vertex buffer containing a single quad with xy coordinates from [-1,-1]
+ * to [1,1] and uv coordinates from [0,0] to [1,1].
+ * @private
+ */
+ this.quadVertexBuffer_ = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer_);
+ var vertices = new Float32Array(
+ [-1.0, -1.0, 0.0, 1.0,
+ +1.0, -1.0, 1.0, 1.0,
+ -1.0, +1.0, 0.0, 0.0,
+ 1.0, +1.0, 1.0, 0.0]);
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
+
+
+ // init shaders
+
+ gl.attachShader(this.program_, this.vertexShader_);
+ gl.attachShader(this.program_, this.fragmentShader_);
+ gl.bindAttribLocation(this.program_, 0, 'vert');
+ gl.linkProgram(this.program_);
+ gl.useProgram(this.program_);
+ gl.enableVertexAttribArray(0);
+
+ gl.enable(gl.DEPTH_TEST);
+ gl.disable(gl.CULL_FACE);
+
+ var count = gl.getProgramParameter(this.program_, gl.ACTIVE_UNIFORMS);
+ for (var i = 0; i < /** @type {number} */(count); i++) {
+ var info = gl.getActiveUniform(this.program_, i);
+ var result = gl.getUniformLocation(this.program_, info.name);
+ this.uniformLocations_[info.name] = result;
+ }
+
+ count = gl.getProgramParameter(this.program_, gl.ACTIVE_ATTRIBUTES);
+ for (var i = 0; i < /** @type {number} */(count); i++) {
+ var info = gl.getActiveAttrib(this.program_, i);
+ var result = gl.getAttribLocation(this.program_, info.name);
+ this.attribLocations_[info.name] = result;
+ }
+};
+
+
+Renderer.prototype.finishInit = function() {
+ this.draw();
+};
+
+
+Renderer.prototype.createDxtTexture = function(dxtData, width, height, format) {
+ var gl = this.gl_;
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.compressedTexImage2D(
+ gl.TEXTURE_2D,
+ 0,
+ format,
+ width,
+ height,
+ 0,
+ dxtData);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ //gl.generateMipmap(gl.TEXTURE_2D)
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ return tex;
+};
+
+Renderer.prototype.createCompressedTexture = function(data, width, height, format) {
+ var gl = this.gl_;
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.compressedTexImage2D(
+ gl.TEXTURE_2D,
+ 0,
+ format,
+ width,
+ height,
+ 0,
+ data);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ //gl.generateMipmap(gl.TEXTURE_2D)
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ return tex;
+};
+
+
+Renderer.prototype.createRgb565Texture = function(rgb565Data, width, height) {
+ var gl = this.gl_;
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(
+ gl.TEXTURE_2D,
+ 0,
+ gl.RGB,
+ width,
+ height,
+ 0,
+ gl.RGB,
+ gl.UNSIGNED_SHORT_5_6_5,
+ rgb565Data);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ //gl.generateMipmap(gl.TEXTURE_2D)
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ return tex;
+};
+
+
+Renderer.prototype.drawTexture = function(texture, width, height, mode) {
+ var gl = this.gl_;
+ // draw scene
+ gl.clearColor(0, 0, 0, 1);
+ gl.clearDepth(1.0);
+ gl.viewport(0, 0, width, height);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
+
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.uniform1i(this.uniformLocations_.texSampler, 0);
+
+ var x = 0.0;
+ var y = 0.0;
+ if (mode == 1)
+ x = 1.0;
+ else if (mode == 2)
+ y = 1.0;
+
+ gl.uniform4f(this.uniformLocations_.control, x, y, 0.0, 0.0);
+
+ gl.enableVertexAttribArray(this.attribLocations_.vert);
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer_);
+ gl.vertexAttribPointer(this.attribLocations_.vert, 4, gl.FLOAT,
+ false, 0, 0);
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+};
+
+
+/**
+ * Compiles a GLSL shader and returns a WebGLShader.
+ * @param {string} shaderSource The shader source code string.
+ * @param {number} type Either VERTEX_SHADER or FRAGMENT_SHADER.
+ * @return {WebGLShader} The new WebGLShader.
+ * @private
+ */
+Renderer.prototype.compileShader_ = function(shaderSource, type) {
+ var gl = this.gl_;
+ var shader = gl.createShader(type);
+ gl.shaderSource(shader, shaderSource);
+ gl.compileShader(shader);
+ return shader;
+};
+
+
+/**
+ * @type {string}
+ * @private
+ */
+Renderer.vertexShaderSource_ = [
+ 'attribute vec4 vert;',
+ 'varying vec2 v_texCoord;',
+ 'void main() {',
+ ' gl_Position = vec4(vert.xy, 0.0, 1.0);',
+ ' v_texCoord = vert.zw;',
+ '}'
+ ].join('\n');
+
+
+/**
+ * @type {string}
+ * @private ' gl_FragColor = texture2D(texSampler, v_texCoord);',
+ */
+Renderer.fragmentShaderSource_ = [
+ 'precision highp float;',
+ 'uniform sampler2D texSampler;',
+ 'uniform vec4 control;',
+ 'varying vec2 v_texCoord;',
+ 'void main() {',
+ ' vec4 c;',
+ ' c = texture2D(texSampler, v_texCoord);',
+ ' if (control.x > 0.0)',
+ ' {',
+ ' c.w = 1.0;',
+ ' }',
+ ' else if (control.y > 0.0)',
+ ' {',
+ ' c.rgb = c.aaa; c.w = 1.0;',
+ ' }',
+ ' gl_FragColor = c;',
+ '}'
+ ].join('\n');
+
diff --git a/webgl/encoder/.gitignore b/webgl/encoder/.gitignore
new file mode 100644
index 0000000..1ab0538
--- /dev/null
+++ b/webgl/encoder/.gitignore
@@ -0,0 +1,4 @@
+build/*
+!build/basis_loader.js
+!build/basis_encoder.js
+!build/basis_encoder.wasm
diff --git a/webgl/encoder/CMakeLists.txt b/webgl/encoder/CMakeLists.txt
new file mode 100644
index 0000000..ed014a0
--- /dev/null
+++ b/webgl/encoder/CMakeLists.txt
@@ -0,0 +1,51 @@
+cmake_minimum_required(VERSION 3.0)
+
+project(basisu_encoder_js)
+
+if (EMSCRIPTEN)
+ set(CMAKE_CXX_STANDARD 11)
+
+ add_executable(basis_encoder.js
+ ../transcoder/basis_wrappers.cpp
+ ../../transcoder/basisu_transcoder.cpp
+ ../../encoder/basisu_backend.cpp
+ ../../encoder/basisu_basis_file.cpp
+ ../../encoder/basisu_comp.cpp
+ ../../encoder/basisu_enc.cpp
+ ../../encoder/basisu_etc.cpp
+ ../../encoder/basisu_frontend.cpp
+ ../../encoder/basisu_global_selector_palette_helpers.cpp
+ ../../encoder/basisu_gpu_texture.cpp
+ ../../encoder/basisu_pvrtc1_4.cpp
+ ../../encoder/basisu_resampler.cpp
+ ../../encoder/basisu_resample_filters.cpp
+ ../../encoder/basisu_ssim.cpp
+ ../../encoder/basisu_astc_decomp.cpp
+ ../../encoder/basisu_uastc_enc.cpp
+ ../../encoder/basisu_bc7enc.cpp
+ ../../encoder/lodepng.cpp
+ ../../encoder/apg_bmp.c
+ ../../encoder/jpgd.cpp
+ )
+
+ #target_compile_definitions(basis_encoder.js PRIVATE NDEBUG BASISD_SUPPORT_UASTC=1 BASISD_SUPPORT_BC7=1 BASISD_SUPPORT_ATC=0 BASISD_SUPPORT_ASTC_HIGHER_OPAQUE_QUALITY=0 BASISD_SUPPORT_PVRTC2=0 BASISD_SUPPORT_FXT1=0 BASISD_SUPPORT_ETC2_EAC_RG11=0 BASISU_SUPPORT_ENCODING=1)
+ #target_compile_options(basis_encoder.js PRIVATE -fno-strict-aliasing -O3)
+
+ #target_compile_definitions(basis_encoder.js PRIVATE DEBUG BASISD_SUPPORT_UASTC=1 BASISD_SUPPORT_BC7=1 BASISD_SUPPORT_ATC=0 BASISD_SUPPORT_ASTC_HIGHER_OPAQUE_QUALITY=0 BASISD_SUPPORT_PVRTC2=0 BASISD_SUPPORT_FXT1=0 BASISD_SUPPORT_ETC2_EAC_RG11=0 BASISU_SUPPORT_ENCODING=1)
+ #target_compile_options(basis_encoder.js PRIVATE -fno-strict-aliasing -g -O1 -fsanitize=undefined -fsanitize=address)
+
+ target_compile_definitions(basis_encoder.js PRIVATE NDEBUG BASISD_SUPPORT_UASTC=1 BASISD_SUPPORT_BC7=1 BASISD_SUPPORT_ATC=0 BASISD_SUPPORT_ASTC_HIGHER_OPAQUE_QUALITY=0 BASISD_SUPPORT_PVRTC2=0 BASISD_SUPPORT_FXT1=0 BASISD_SUPPORT_ETC2_EAC_RG11=0 BASISU_SUPPORT_ENCODING=1)
+ target_compile_options(basis_encoder.js PRIVATE -fno-strict-aliasing -O3)
+
+ target_include_directories(basis_encoder.js PRIVATE ../../transcoder)
+
+ set_target_properties(basis_encoder.js PROPERTIES
+ OUTPUT_NAME "basis_encoder"
+ SUFFIX ".js"
+
+ #LINK_FLAGS "--bind -s ALLOW_MEMORY_GROWTH=1 -O3 -s ASSERTIONS=0 -s MALLOC=emmalloc -s MODULARIZE=1 -s EXPORT_NAME=BASIS ")
+
+ #LINK_FLAGS "--bind -s INITIAL_MEMORY=299958272 -g -s DEMANGLE_SUPPORT=1 -s ALLOW_MEMORY_GROWTH=1 -O1 -s ASSERTIONS=1 -s MALLOC=emmalloc -s MODULARIZE=1 -s EXPORT_NAME=BASIS -fsanitize=undefined -fsanitize=address")
+
+ LINK_FLAGS "--bind -s ALLOW_MEMORY_GROWTH=1 -O3 -s ASSERTIONS=0 -s INITIAL_MEMORY=299958272 -s MALLOC=emmalloc -s MODULARIZE=1 -s EXPORT_NAME=BASIS")
+endif()
diff --git a/webgl/encoder/README.md b/webgl/encoder/README.md
new file mode 100644
index 0000000..2055d02
--- /dev/null
+++ b/webgl/encoder/README.md
@@ -0,0 +1,7 @@
+Prebuilt versions of `basis_encoder.js` and `basis_encoder.wasm` are included in the `build/` folder, and are sufficient for local demos. Note the encoder also includes the transcoder. To build the encoder yourself, first install emscripten ([tutorial](https://webassembly.org/getting-started/developers-guide/)) and cmake ([download](https://cmake.org/download/)). Then run:
+
+```shell
+cd webgl/encoder/build/
+emcmake cmake ../
+make
+```
diff --git a/webgl/texture/assets/kodim01_mipmapped.basis b/webgl/texture/assets/kodim01_mipmapped.basis
new file mode 100644
index 0000000..dfaf2b5
Binary files /dev/null and b/webgl/texture/assets/kodim01_mipmapped.basis differ
diff --git a/webgl_videotest/basis.wasm b/webgl_videotest/basis.wasm
new file mode 100644
index 0000000..4061f92
Binary files /dev/null and b/webgl_videotest/basis.wasm differ