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: + + +
+ +
+ + + + +
+ + +
+

+
+ + + 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