This commit is contained in:
2026-06-14 19:09:18 +01:00
parent 14bd1a9271
commit 13fa90a0e9
3958 changed files with 999286 additions and 4 deletions
+61
View File
@@ -0,0 +1,61 @@
# WebGL Examples
Requires WebAssembly and WebGL support.
## Texture
[Live demo: `texture/index.html`](https://basis-universal-webgl.now.sh/texture/)
(Note the Live texture demo hasn't been updated to the latest release yet.)
Renders a single texture, using the transcoder (compiled to WASM with emscripten) to generate one of the following compressed texture formats:
* ASTC
* BC1 (no alpha)
* BC3
* ETC1 (no alpha)
* PVRTC
On browsers that don't support any compressed texture format, there's a low-quality fallback code path for opaque textures. Note that the fallback path only converts to 16-bit RGB images at the moment, so the quality isn't as good as it should be.
![Screenshot showing a basis texture rendered as a 2D image in a webpage.](texture/preview.png)
## glTF 3D Model
[Live demo: `gltf/index.html`](https://basis-universal-webgl.now.sh/gltf/)
Renders a glTF 3D model with `.basis` texture files, transcoded into one of the following compressed texture formats:
* ASTC
* Tested in Chrome on Android, Pixel 3 XL.
* DTX (BC1/BC3)
* Tested in Chrome (Linux and macOS) and Firefox (macOS).
* ETC1
* Tested in Chrome on Android, Pixel 3 XL.
* PVRTC
* Tested in Chrome and Safari on iOS iPhone 6 Plus.
The glTF model in this demo uses a hypothetical `GOOGLE_texture_basis` extension. That extension is defined for the sake of example only - the glTF format will officially embed Basis files within a KTX2 wrapper, through a new
extension that is [currently in development](https://github.com/KhronosGroup/glTF/pull/1612).
![Screenshot showing a basis texture rendered as the base color texture for a 3D model in a webpage.](gltf/preview.png)
## Compressor (encode_test)
This demo shows how to use the compressor from JavaScript. To use it, select a .PNG file then hit the "Encode!" button. The compressor will dynamically generate a .basis file in memory which will then be immediately transcoded and displayed. Hit the "Download!" button to locally download the generated .basis file.
To view the compressor's textual debug output, open your browser's developer debug console (under Developer Tools in Chrome) and enable the Debug checkbox before hitting the "Encode!" button. Multithreading is not currently supported when the compressor is compiled to WebAssembly, so compression will be slower than using the stand-alone command line tool.
![Screenshot showing the encode_test demo](encode_test/preview.png)
## Testing locally
See [how to run things locally](https://threejs.org/docs/#manual/en/introduction/How-to-run-things-locally), or (with [Node.js](https://nodejs.org/en/) installed), run:
```
npx serve
```
The console will display a `localhost` URL for local testing, and (on supported WiFi networks and devices) may also display an IP address accessible by other devices on the same network. Note that mobile devices must support WebAssembly to run this demo. Learn more about [remote debugging your android devices](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/).
Alternately, use [Web Server for Chrome](https://chrome.google.com/webstore/detail/web-server-for-chrome/ofhbbkphhbklhfoeikjpcbhemlocgigb), browse to your local "webgl" directory, and then select 127.0.0.1:8887 under "Web Server URL(s).
Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

+128
View File
@@ -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;
}
+595
View File
@@ -0,0 +1,595 @@
<html>
<head>
<script src="renderer.js"></script>
<script src="dxt-to-rgb565.js"></script>
<script src="../encoder/build/basis_encoder.js"></script>
<script type="text/javascript">
function log(s) {
var div = document.createElement('div');
div.innerHTML = s;
document.getElementById('logger').appendChild(div);
}
function logTime(desc, t) {
log(t + 'ms ' + desc);
}
function isDef(v) {
return typeof v != 'undefined';
}
function elem(id) {
return document.getElementById(id);
}
formatTable = function(rows) {
var colLengths = [];
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
for (var j = 0; j < row.length; j++) {
if (colLengths.length <= j) colLengths.push(0);
if (colLengths[j] < row[j].length) colLengths[j] = row[j].length;
}
}
function formatRow(row) {
var parts = [];
for (var i = 0; i < colLengths.length; i++) {
var s = row.length > i ? row[i] : '';
var padding = (new Array(1 + colLengths[i] - s.length)).join(' ');
if (s && s[0] >= '0' && s[0] <= '9') {
// Right-align numbers.
parts.push(padding + s);
} else {
parts.push(s + padding);
}
}
return parts.join(' | ');
}
var width = 0;
for (var i = 0; i < colLengths.length; i++) {
width += colLengths[i];
// Add another 3 for the separator.
if (i != 0) width += 3;
}
var lines = [];
lines.push(formatRow(rows[0]));
lines.push((new Array(width + 1)).join('-'));
for (var i = 1; i < rows.length; i++) {
lines.push(formatRow(rows[i]));
}
return lines.join('\n');
};
function loadArrayBuffer(uri, callback) {
log('Loading ' + uri + '...');
var xhr = new XMLHttpRequest();
xhr.responseType = "arraybuffer";
xhr.open('GET', uri, true);
xhr.onreadystatechange = function(e) {
if (xhr.readyState == 4 && xhr.status == 200) {
callback(xhr.response);
}
}
xhr.send(null);
}
// ASTC format, from:
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0;
// DXT formats, from:
// http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0;
COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
// BC7 format, from:
// https://www.khronos.org/registry/webgl/extensions/EXT_texture_compression_bptc/
COMPRESSED_RGBA_BPTC_UNORM = 0x8E8C;
// ETC format, from:
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc1/
COMPRESSED_RGB_ETC1_WEBGL = 0x8D64;
// PVRTC format, from:
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/
COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00;
COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02;
// Same as the Module.transcoder_texture_format enum
BASIS_FORMAT = {
cTFETC1: 0,
cTFETC2: 1,
cTFBC1: 2,
cTFBC3: 3,
cTFBC4: 4,
cTFBC5: 5,
cTFBC7: 6,
cTFPVRTC1_4_RGB: 8,
cTFPVRTC1_4_RGBA: 9,
cTFASTC_4x4: 10,
cTFATC_RGB: 11,
cTFATC_RGBA_INTERPOLATED_ALPHA: 12,
cTFRGBA32: 13,
cTFRGB565: 14,
cTFBGR565: 15,
cTFRGBA4444: 16,
cTFFXT1_RGB: 17,
cTFPVRTC2_4_RGB: 18,
cTFPVRTC2_4_RGBA: 19,
cTFETC2_EAC_R11: 20,
cTFETC2_EAC_RG11: 21
};
BASIS_FORMAT_NAMES = {};
for (var name in BASIS_FORMAT) {
BASIS_FORMAT_NAMES[BASIS_FORMAT[name]] = name;
}
DXT_FORMAT_MAP = {};
DXT_FORMAT_MAP[BASIS_FORMAT.cTFBC1] = COMPRESSED_RGB_S3TC_DXT1_EXT;
DXT_FORMAT_MAP[BASIS_FORMAT.cTFBC3] = COMPRESSED_RGBA_S3TC_DXT5_EXT;
DXT_FORMAT_MAP[BASIS_FORMAT.cTFBC7] = COMPRESSED_RGBA_BPTC_UNORM;
var astcSupported = false;
var etcSupported = false;
var dxtSupported = false;
var bc7Supported = false;
var pvrtcSupported = false;
var drawMode = 0;
var tex, width, height, images, levels, have_alpha, alignedWidth, alignedHeight, format, displayWidth, displayHeight;
function redraw()
{
if (!width)
return;
renderer.drawTexture(tex, displayWidth, displayHeight, drawMode);
}
function dumpBasisFileDesc(basisFile)
{
var basisFileDesc = basisFile.getFileDesc();
log('------');
log('getFileDesc():');
log('version: ' + basisFileDesc.version);
log('us per frame: ' + basisFileDesc.usPerFrame);
log('total images: ' + basisFileDesc.totalImages);
log('userdata0: ' + basisFileDesc.userdata0 + ' userdata1: ' + basisFileDesc.userdata1);
log('texFormat: ' + basisFileDesc.texFormat);
log('yFlipped: ' + basisFileDesc.yFlipped + ' hasAlphaSlices: ' + basisFileDesc.hasAlphaSlices);
if (basisFileDesc.texFormat == Module.basis_tex_format.cETC1S.value)
{
log('numEndpoints: ' + basisFileDesc.numEndpoints);
log('endpointPaletteOfs: ' + basisFileDesc.endpointPaletteOfs);
log('endpointPaletteLen: ' + basisFileDesc.endpointPaletteLen);
log('numSelectors: ' + basisFileDesc.numSelectors);
log('selectorPaletteOfs: ' + basisFileDesc.selectorPaletteOfs);
log('selectorPaletteLen: ' + basisFileDesc.selectorPaletteLen);
log('tablesOfs: ' + basisFileDesc.tablesOfs);
log('tablesLen: ' + basisFileDesc.tablesLen);
}
log('------');
log('getImageDesc() for all images:');
var image_index;
for (image_index = 0; image_index < basisFileDesc.totalImages; image_index++)
{
log('image: ' + image_index);
var basisImageDesc = basisFile.getImageDesc(image_index);
log('origWidth: ' + basisImageDesc.origWidth + ' origWidth: ' + basisImageDesc.origHeight);
log('numBlocksX: ' + basisImageDesc.numBlocksX + ' origWidth: ' + basisImageDesc.numBlocksY);
log('numLevels: ' + basisImageDesc.numLevels);
log('alphaFlag: ' + basisImageDesc.alphaFlag + ' iframeFlag: ' + basisImageDesc.iframeFlag);
log('getImageLevelDesc() for all mipmap levels:');
var level_index;
for (level_index = 0; level_index < basisImageDesc.numLevels; level_index++)
{
var basisImageLevelDesc = basisFile.getImageLevelDesc(image_index, level_index);
log('level: ' + level_index +
' rgb_file_offset: ' + basisImageLevelDesc.rgbFileOfs + ' rgb_file_len: ' + basisImageLevelDesc.rgbFileLen);
if (basisFileDesc.hasAlphaSlices)
log('alpha_file_offset: ' + basisImageLevelDesc.alphaFileOfs + ' alpha_file_len: ' + basisImageLevelDesc.alphaFileLen);
}
}
log('------');
}
function dataLoaded(data)
{
log('Done loading .basis file, decoded header:');
const { BasisFile, initializeBasis, encodeBasisTexture } = Module;
initializeBasis();
const startTime = performance.now();
const basisFile = new BasisFile(new Uint8Array(data));
width = basisFile.getImageWidth(0, 0);
height = basisFile.getImageHeight(0, 0);
images = basisFile.getNumImages();
levels = basisFile.getNumLevels(0);
has_alpha = basisFile.getHasAlpha();
dumpBasisFileDesc(basisFile);
if (!width || !height || !images || !levels) {
console.warn('Invalid .basis file');
basisFile.close();
basisFile.delete();
return;
}
// Note: If the file is UASTC, the preferred formats are ASTC/BC7.
// If the file is ETC1S and doesn't have alpha, the preferred formats are ETC1 and BC1. For alpha, the preferred formats are ETC2, BC3 or BC7.
var formatString = 'UNKNOWN';
if (astcSupported)
{
formatString = 'ASTC';
format = BASIS_FORMAT.cTFASTC_4x4;
}
else if (bc7Supported)
{
formatString = 'BC7';
format = BASIS_FORMAT.cTFBC7;
}
else if (dxtSupported)
{
if (has_alpha)
{
formatString = 'BC3';
format = BASIS_FORMAT.cTFBC3;
}
else
{
formatString = 'BC1';
format = BASIS_FORMAT.cTFBC1;
}
}
else if (pvrtcSupported)
{
if (has_alpha)
{
formatString = 'PVRTC1_RGBA';
format = BASIS_FORMAT.cTFPVRTC1_4_RGBA;
}
else
{
formatString = 'PVRTC1_RGB';
format = BASIS_FORMAT.cTFPVRTC1_4_RGB;
}
if (
((width & (width - 1)) != 0) || ((height & (height - 1)) != 0)
)
{
log('ERROR: PVRTC1 requires square power of 2 textures');
}
if (width != height)
{
log('ERROR: PVRTC1 requires square power of 2 textures');
}
}
else if (etcSupported)
{
formatString = 'ETC1';
format = BASIS_FORMAT.cTFETC1;
}
else
{
formatString = 'RGB565';
format = BASIS_FORMAT.cTFRGB565;
log('Decoding .basis data to 565');
}
elem('format').innerText = formatString;
if (!basisFile.startTranscoding()) {
log('startTranscoding failed');
console.warn('startTranscoding failed');
basisFile.close();
basisFile.delete();
return;
}
const dstSize = basisFile.getImageTranscodedSizeInBytes(0, 0, format);
const dst = new Uint8Array(dstSize);
//log(dstSize);
// if (!basisFile.transcodeImage(dst, 0, 0, format, 1, 0)) {
if (!basisFile.transcodeImage(dst, 0, 0, format, 0, 0)) {
log('basisFile.transcodeImage failed');
console.warn('transcodeImage failed');
basisFile.close();
basisFile.delete();
return;
}
const elapsed = performance.now() - startTime;
basisFile.close();
basisFile.delete();
log('width: ' + width);
log('height: ' + height);
log('images: ' + images);
log('first image mipmap levels: ' + levels);
log('has_alpha: ' + has_alpha);
logTime('transcoding time', elapsed.toFixed(2));
alignedWidth = (width + 3) & ~3;
alignedHeight = (height + 3) & ~3;
displayWidth = alignedWidth;
displayHeight = alignedHeight;
var canvas = elem('canvas');
canvas.width = alignedWidth;
canvas.height = alignedHeight;
if (format === BASIS_FORMAT.cTFASTC_4x4)
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGBA_ASTC_4x4_KHR);
}
else if ((format === BASIS_FORMAT.cTFBC3) || (format === BASIS_FORMAT.cTFBC1) || (format == BASIS_FORMAT.cTFBC7))
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, DXT_FORMAT_MAP[format]);
}
else if (format === BASIS_FORMAT.cTFETC1)
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGB_ETC1_WEBGL);
}
else if (format === BASIS_FORMAT.cTFPVRTC1_4_RGB)
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGB_PVRTC_4BPPV1_IMG);
}
else if (format === BASIS_FORMAT.cTFPVRTC1_4_RGBA)
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGBA_PVRTC_4BPPV1_IMG);
}
else
{
canvas.width = width;
canvas.height = height;
displayWidth = width;
displayHeight = height;
// Create 565 texture.
var dstTex = new Uint16Array(width * height);
// Convert the array of bytes to an array of uint16's.
var pix = 0;
for (var y = 0; y < height; y++)
for (var x = 0; x < width; x++, pix++)
dstTex[pix] = dst[2 * pix + 0] | (dst[2 * pix + 1] << 8);
tex = renderer.createRgb565Texture(dstTex, width, height);
}
redraw();
}
function download_file(filename, body)
{
var element = document.createElement('a');
//element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
const blob = new Blob([body]);
const url = URL.createObjectURL(blob);
element.setAttribute('href', url);
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
var encodedBasisFile;
function PNGDataLoaded(data)
{
const { BasisFile, BasisEncoder, initializeBasis, encodeBasisTexture } = Module;
initializeBasis();
// Create a destination buffer to hold the compressed .basis file data. If this buffer isn't large enough compression will fail.
var basisFileData = new Uint8Array(1024*1024*10);
var num_output_bytes;
// Compress using the BasisEncoder class.
log('BasisEncoder::encode() started:');
const basisEncoder = new BasisEncoder();
const qualityLevel = parseInt(elem('EncodeQuality').value, 10);
const uastcFlag = elem('EncodeUASTC').checked;
basisEncoder.setSliceSourceImage(0, new Uint8Array(data), 0, 0, true);
basisEncoder.setDebug(elem('Debug').checked);
basisEncoder.setComputeStats(elem('ComputeStats').checked);
basisEncoder.setPerceptual(elem('SRGB').checked);
basisEncoder.setMipSRGB(elem('SRGB').checked);
basisEncoder.setQualityLevel(qualityLevel);
basisEncoder.setUASTC(uastcFlag);
basisEncoder.setMipGen(elem('Mipmaps').checked);
if (!uastcFlag)
log('Encoding at ETC1S quality level ' + qualityLevel);
const startTime = performance.now();
num_output_bytes = basisEncoder.encode(basisFileData);
const elapsed = performance.now() - startTime;
logTime('encoding time', elapsed.toFixed(2));
var actualBasisFileData = new Uint8Array(basisFileData.buffer, 0, num_output_bytes);
basisEncoder.delete();
if (num_output_bytes == 0)
{
log('encodeBasisTexture() failed!');
}
else
{
log('encodeBasisTexture() succeeded, output size ' + num_output_bytes);
encodedBasisFile = actualBasisFileData;
//download("test.basis", actualBasisFileData);
}
if (num_output_bytes != 0)
{
dataLoaded(actualBasisFileData);
}
}
function runLoadFile() {
elem('logger').innerHTML = '';
loadArrayBuffer(elem('file').value, dataLoaded);
}
function runEncodePNGFile() {
elem('logger').innerHTML = '';
loadArrayBuffer(elem('pngfile').value, PNGDataLoaded);
}
function alphaBlend() { drawMode = 0; redraw(); }
function viewRGB() { drawMode = 1; redraw(); }
function viewAlpha() { drawMode = 2; redraw(); }
function downloadEncodedFile()
{
if (encodedBasisFile)
{
if (encodedBasisFile.length)
download_file("encoded_file.basis", encodedBasisFile);
}
}
</script>
</head>
<body>
<br>
<div style="font-size: 24pt; font-weight: bold">
Basis Universal compressed texture transcoding and encoding test
</div>
<br>This demo uses the Basis C++ transcoder (compiled to Javascript using Emscripten) to transcode a .basis file to <b id='format'>FORMAT</b>
<br>Thanks to Evan Parker for providing <a href="https://github.com/toji/webgl-texture-utils">webgl-texture-utils</a> and this test bed.
<br>
<br>
.basis file:
<input id="file" type="text" size=30 value="assets/kodim26_uastc_1024.basis"></input>
<input type="button" value="Transcode!" onclick="runLoadFile()"></input>
<br>
<br>
.png file:
<input id="pngfile" type="text" size=30 value="assets/kodim18_64x64.png"></input>
<input type="button" value="Encode!" onclick="runEncodePNGFile()"></input>
<br>
<input type="button" value="Download Encoded .basis File" onclick="downloadEncodedFile()">
<br>
Debug:
<input type="checkbox" id="Debug">
<br>
Compute Stats:
<input type="checkbox" id="ComputeStats">
<br>
sRGB:
<input type="checkbox" id="SRGB">
<br>
Mipmaps:
<input type="checkbox" id="Mipmaps">
<br>
UASTC:
<input type="checkbox" id="EncodeUASTC">
<br>
ETC1S Quality:
<input type="range" min="1" max="255" value="10" class="slider" id="EncodeQuality">
<br>
<br>
<input type="button" value="Alpha blend" onclick="alphaBlend()"></input>
<input type="button" value="View RGB" onclick="viewRGB()"></input>
<input type="button" value="View Alpha" onclick="viewAlpha()"></input>
<div style="position:absolute; left: 525px; top:130px; font-size: 20pt; font-weight: bold; color: red">
<div id="no-compressed-tex" style="display: none; width: 768px; font-size: 20pt; font-weight: bold; color: red">
NOTE: Your browser does not support several compressed texture format, so using RGB565.
</div>
<canvas id='canvas'></canvas>
</div>
<br><br>
<div id='logger'></div>
</body>
<script>
BASIS({onRuntimeInitialized : () => {
elem('SRGB').checked = true;
var gl = elem('canvas').getContext('webgl');
astcSupported = !!gl.getExtension('WEBGL_compressed_texture_astc');
etcSupported = !!gl.getExtension('WEBGL_compressed_texture_etc1');
dxtSupported = !!gl.getExtension('WEBGL_compressed_texture_s3tc');
pvrtcSupported = !!(gl.getExtension('WEBGL_compressed_texture_pvrtc')) || !!(gl.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc'));
bc7Supported = !!gl.getExtension('EXT_texture_compression_bptc');
// HACK HACK - for testing uncompressed
//astcSupported = false;
//etcSupported = false;
//dxtSupported = false;
//bc7Supported = false;
//pvrtcSupported = false;
window.renderer = new Renderer(gl);
elem('file').addEventListener('keydown', function(e) {
if (e.keyCode == 13) {
runLoadFile();
}
}, false);
if (!(astcSupported || etcSupported || dxtSupported || pvrtcSupported))
{
// elem('nodxt').style.display = 'block';
}
runLoadFile();
}}).then(module => window.Module = module);
</script>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

+246
View File
@@ -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.<string, WebGLUniformLocation>}
* @private
*/
this.uniformLocations_ = {};
/**
* Cached attribute locations.
* @type {Object.<string, WebGLActiveInfo>}
* @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');
@@ -0,0 +1,4 @@
build/*
!build/basis_loader.js
!build/basis_encoder.js
!build/basis_encoder.wasm
+69
View File
@@ -0,0 +1,69 @@
cmake_minimum_required(VERSION 3.0)
project(basisu_encoder_js)
# The encoder always supports generating KTX2 files, but Zstandard support is optional. If it's disabled, KTX2 UASTC files will always be uncompressed.
# If you know you'll never be encoding UASTC+Zstd KTX2 files you can set KTX2_ZSTANDARD to 0 to reduce the size of the compiled encoder.
option(KTX2_ZSTANDARD "KTX2_ZSTANDARD" TRUE)
message("KTX2_ZSTANDARD=${KTX2_ZSTANDARD}")
if (EMSCRIPTEN)
set(CMAKE_CXX_STANDARD 11)
set(SRC_LIST
../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_gpu_texture.cpp
../../encoder/basisu_pvrtc1_4.cpp
../../encoder/basisu_resampler.cpp
../../encoder/basisu_resample_filters.cpp
../../encoder/basisu_ssim.cpp
../../encoder/basisu_uastc_enc.cpp
../../encoder/basisu_bc7enc.cpp
../../encoder/basisu_kernels_sse.cpp
../../encoder/basisu_opencl.cpp
../../encoder/pvpngreader.cpp
../../encoder/jpgd.cpp
)
if (KTX2_ZSTANDARD)
set(SRC_LIST ${SRC_LIST}
../../zstd/zstd.c
)
set(ZSTD_DEFINITION BASISD_SUPPORT_KTX2_ZSTD=1)
else()
set(ZSTD_DEFINITION BASISD_SUPPORT_KTX2_ZSTD=0)
endif()
add_executable(basis_encoder.js ${SRC_LIST})
#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 BASISU_SUPPORT_SSE=0 ${ZSTD_DEFINITION} )
#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 BASISU_SUPPORT_SSE=0 ${ZSTD_DEFINITION} )
#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 BASISU_SUPPORT_SSE=0 ${ZSTD_DEFINITION} )
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")
# TODO: 300MB is really large - probably not necessary?
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()
+7
View File
@@ -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
```
+518
View File
@@ -0,0 +1,518 @@
/**
* @author Don McCurdy / https://www.donmccurdy.com
* @author Austin Eng / https://github.com/austinEng
* @author Shrek Shao / https://github.com/shrekshao
*/
/**
* Loader for Basis Universal GPU Texture Codec.
*
* Basis Universal is a "supercompressed" GPU texture and texture video
* compression system that outputs a highly compressed intermediate file format
* (.basis) that can be quickly transcoded to a wide variety of GPU texture
* compression formats.
*
* This loader parallelizes the transcoding process across a configurable number
* of web workers, before transferring the transcoded compressed texture back
* to the main thread.
*/
THREE.BasisTextureLoader = function ( manager ) {
THREE.Loader.call( this, manager );
this.transcoderPath = '';
this.transcoderBinary = null;
this.transcoderPending = null;
this.workerLimit = 4;
this.workerPool = [];
this.workerNextTaskID = 1;
this.workerSourceURL = '';
this.workerConfig = {
format: null,
astcSupported: false,
etcSupported: false,
dxtSupported: false,
pvrtcSupported: false,
};
};
THREE.BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
constructor: THREE.BasisTextureLoader,
setTranscoderPath: function ( path ) {
this.transcoderPath = path;
return this;
},
setWorkerLimit: function ( workerLimit ) {
this.workerLimit = workerLimit;
return this;
},
detectSupport: function ( renderer ) {
var config = this.workerConfig;
config.bc7Supported = !! renderer.extensions.get( 'EXT_texture_compression_bptc' );
config.astcSupported = !! renderer.extensions.get( 'WEBGL_compressed_texture_astc' );
config.etcSupported = !! renderer.extensions.get( 'WEBGL_compressed_texture_etc1' );
config.dxtSupported = !! renderer.extensions.get( 'WEBGL_compressed_texture_s3tc' );
config.pvrtcSupported = !! renderer.extensions.get( 'WEBGL_compressed_texture_pvrtc' )
|| !! renderer.extensions.get( 'WEBKIT_WEBGL_compressed_texture_pvrtc' );
if ( config.astcSupported ) {
config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFASTC_4x4;
} else if ( config.bc7Supported ) {
config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC7_M6_OPAQUE_ONLY;
} else if ( config.dxtSupported ) {
config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC3;
} else if ( config.etcSupported ) {
config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFETC1;
} else if ( config.pvrtcSupported ) {
config.format = this.useAlpha ? THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA : THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB;
} else {
throw new Error( 'THREE.BasisTextureLoader: No suitable compressed texture format found.' );
}
return this;
},
load: function ( url, onLoad, onProgress, onError ) {
var loader = new THREE.FileLoader( this.manager );
loader.setResponseType( 'arraybuffer' );
loader.load( url, ( buffer ) => {
this._createTexture( buffer )
.then( onLoad )
.catch( onError );
}, onProgress, onError );
},
/**
* @param {ArrayBuffer} buffer
* @return {Promise<THREE.CompressedTexture>}
*/
_createTexture: function ( buffer ) {
var worker;
var taskID;
var taskCost = buffer.byteLength;
var texturePending = this._allocateWorker( taskCost )
.then( ( _worker ) => {
worker = _worker;
taskID = this.workerNextTaskID ++;
return new Promise( ( resolve, reject ) => {
worker._callbacks[ taskID ] = { resolve, reject };
worker.postMessage( { type: 'transcode', id: taskID, buffer }, [ buffer ] );
} );
} )
.then( ( message ) => {
var config = this.workerConfig;
var { width, height, mipmaps, format } = message;
var texture;
switch ( format ) {
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFASTC_4x4:
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGBA_ASTC_4x4_Format );
break;
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC7_M6_OPAQUE_ONLY:
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.BasisTextureLoader.COMPRESSED_RGBA_BPTC_UNORM );
break;
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC1:
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC3:
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.BasisTextureLoader.DXT_FORMAT_MAP[ config.format ], THREE.UnsignedByteType );
break;
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFETC1:
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_ETC1_Format );
break;
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB:
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_PVRTC_4BPPV1_Format );
break;
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA:
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGBA_PVRTC_4BPPV1_Format );
break;
default:
throw new Error( 'THREE.BasisTextureLoader: No supported format available.' );
}
texture.minFilter = mipmaps.length === 1 ? THREE.LinearFilter : THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.generateMipmaps = false;
texture.needsUpdate = true;
return texture;
} );
texturePending
.finally( () => {
if ( worker && taskID ) {
worker._taskLoad -= taskCost;
delete worker._callbacks[ taskID ];
}
} );
return texturePending;
},
_initTranscoder: function () {
if ( ! this.transcoderPending ) {
// Load transcoder wrapper.
var jsLoader = new THREE.FileLoader( this.manager );
jsLoader.setPath( this.transcoderPath );
var jsContent = new Promise( ( resolve, reject ) => {
jsLoader.load( 'basis_transcoder.js', resolve, undefined, reject );
} );
// Load transcoder WASM binary.
var binaryLoader = new THREE.FileLoader( this.manager );
binaryLoader.setPath( this.transcoderPath );
binaryLoader.setResponseType( 'arraybuffer' );
var binaryContent = new Promise( ( resolve, reject ) => {
binaryLoader.load( 'basis_transcoder.wasm', resolve, undefined, reject );
} );
this.transcoderPending = Promise.all( [ jsContent, binaryContent ] )
.then( ( [ jsContent, binaryContent ] ) => {
var fn = THREE.BasisTextureLoader.BasisWorker.toString();
var body = [
'/* basis_transcoder.js */',
jsContent,
'/* worker */',
fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) )
].join( '\n' );
this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
this.transcoderBinary = binaryContent;
} );
}
return this.transcoderPending;
},
_allocateWorker: function ( taskCost ) {
return this._initTranscoder().then( () => {
if ( this.workerPool.length < this.workerLimit ) {
var worker = new Worker( this.workerSourceURL );
worker._callbacks = {};
worker._taskLoad = 0;
worker.postMessage( {
type: 'init',
config: this.workerConfig,
transcoderBinary: this.transcoderBinary,
} );
worker.onmessage = function ( e ) {
var message = e.data;
switch ( message.type ) {
case 'transcode':
worker._callbacks[ message.id ].resolve( message );
break;
case 'error':
worker._callbacks[ message.id ].reject( message );
break;
default:
console.error( 'THREE.BasisTextureLoader: Unexpected message, "' + message.type + '"' );
}
};
this.workerPool.push( worker );
} else {
this.workerPool.sort( function ( a, b ) {
return a._taskLoad > b._taskLoad ? - 1 : 1;
} );
}
var worker = this.workerPool[ this.workerPool.length - 1 ];
worker._taskLoad += taskCost;
return worker;
} );
},
dispose: function () {
for ( var i = 0; i < this.workerPool.length; i ++ ) {
this.workerPool[ i ].terminate();
}
this.workerPool.length = 0;
return this;
}
} );
/* CONSTANTS */
THREE.BasisTextureLoader.BASIS_FORMAT = {
cTFETC1: 0,
cTFETC2: 1,
cTFBC1: 2,
cTFBC3: 3,
cTFBC4: 4,
cTFBC5: 5,
cTFBC7_M6_OPAQUE_ONLY: 6,
cTFBC7_M5: 7,
cTFPVRTC1_4_RGB: 8,
cTFPVRTC1_4_RGBA: 9,
cTFASTC_4x4: 10,
cTFATC_RGB: 11,
cTFATC_RGBA_INTERPOLATED_ALPHA: 12,
cTFRGBA32: 13,
cTFRGB565: 14,
cTFBGR565: 15,
cTFRGBA4444: 16,
};
// DXT formats, from:
// http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
THREE.BasisTextureLoader.DXT_FORMAT = {
COMPRESSED_RGB_S3TC_DXT1_EXT: 0x83F0,
COMPRESSED_RGBA_S3TC_DXT1_EXT: 0x83F1,
COMPRESSED_RGBA_S3TC_DXT3_EXT: 0x83F2,
COMPRESSED_RGBA_S3TC_DXT5_EXT: 0x83F3,
};
THREE.BasisTextureLoader.DXT_FORMAT_MAP = {};
THREE.BasisTextureLoader.DXT_FORMAT_MAP[ THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC1 ] =
THREE.BasisTextureLoader.DXT_FORMAT.COMPRESSED_RGB_S3TC_DXT1_EXT;
THREE.BasisTextureLoader.DXT_FORMAT_MAP[ THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC3 ] =
THREE.BasisTextureLoader.DXT_FORMAT.COMPRESSED_RGBA_S3TC_DXT5_EXT;
// ASTC formats, from:
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
THREE.BasisTextureLoader.COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0;
// BC7/BPTC format, from:
// https://www.khronos.org/registry/webgl/extensions/EXT_texture_compression_bptc/
THREE.BasisTextureLoader.COMPRESSED_RGBA_BPTC_UNORM = 0x8E8C;
/* WEB WORKER */
THREE.BasisTextureLoader.BasisWorker = function () {
var config;
var transcoderPending;
var _BasisFile;
onmessage = function ( e ) {
var message = e.data;
switch ( message.type ) {
case 'init':
config = message.config;
init( message.transcoderBinary );
break;
case 'transcode':
transcoderPending.then( () => {
try {
var { width, height, hasAlpha, mipmaps, format } = transcode( message.buffer );
var buffers = [];
for ( var i = 0; i < mipmaps.length; ++ i ) {
buffers.push( mipmaps[ i ].data.buffer );
}
self.postMessage( { type: 'transcode', id: message.id, width, height, hasAlpha, mipmaps, format }, buffers );
} catch ( error ) {
console.error( error );
self.postMessage( { type: 'error', id: message.id, error: error.message } );
}
} );
break;
}
};
function init( wasmBinary ) {
var BasisModule;
transcoderPending = new Promise( ( resolve ) => {
BasisModule = { wasmBinary, onRuntimeInitialized: resolve };
BASIS( BasisModule );
} ).then( () => {
var { BasisFile, initializeBasis } = BasisModule;
_BasisFile = BasisFile;
initializeBasis();
} );
}
function transcode( buffer ) {
var basisFile = new _BasisFile( new Uint8Array( buffer ) );
var width = basisFile.getImageWidth( 0, 0 );
var height = basisFile.getImageHeight( 0, 0 );
var levels = basisFile.getNumLevels( 0 );
var hasAlpha = basisFile.getHasAlpha();
function cleanup() {
basisFile.close();
basisFile.delete();
}
if ( ! hasAlpha ) {
switch ( config.format ) {
case 9: // Hardcoded: THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA
config.format = 8; // Hardcoded: THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB;
break;
default:
break;
}
}
if ( ! width || ! height || ! levels ) {
cleanup();
throw new Error( 'THREE.BasisTextureLoader: Invalid .basis file' );
}
if ( ! basisFile.startTranscoding() ) {
cleanup();
throw new Error( 'THREE.BasisTextureLoader: .startTranscoding failed' );
}
var mipmaps = [];
for ( var mip = 0; mip < levels; mip ++ ) {
var mipWidth = basisFile.getImageWidth( 0, mip );
var mipHeight = basisFile.getImageHeight( 0, mip );
var dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, mip, config.format ) );
var status = basisFile.transcodeImage(
dst,
0,
mip,
config.format,
0,
hasAlpha
);
if ( ! status ) {
cleanup();
throw new Error( 'THREE.BasisTextureLoader: .transcodeImage failed.' );
}
mipmaps.push( { data: dst, width: mipWidth, height: mipHeight } );
}
cleanup();
return { width, height, hasAlpha, mipmaps, format: config.format };
}
};
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
## Credits
* Contributors:
* [Don McCurdy](https://www.donmccurdy.com)
* [Austin Eng](https://github.com/austinEng)
* [Shrek Shao](https://github.com/shrekshao)
* Made with [three.js](https://threejs.org/).
* Thanks to [AGI](http://agi.com/) for providing the glTF model.
+254
View File
@@ -0,0 +1,254 @@
{
"accessors": [
{
"byteOffset": 0,
"componentType": 5126,
"type": "VEC3",
"count": 55381,
"min": [
-527.3590087890625,
-104.39354705810547,
-495.3590087890625
],
"max": [
599.3590087890625,
-33.72303009033203,
561.3590087890625
],
"bufferView": 0,
"name": "mesh-0-0_Accessor_POSITION"
},
{
"byteOffset": 0,
"componentType": 5126,
"type": "VEC2",
"count": 55381,
"min": [
0.0000017028407910402166,
0.00000289904824057885
],
"max": [
0.9993386268615723,
0.9999556541442871
],
"bufferView": 1,
"name": "mesh-0-0_Accessor_TEXCOORD_0"
},
{
"byteOffset": 0,
"componentType": 5123,
"type": "SCALAR",
"count": 151158,
"bufferView": 4,
"name": "mesh-0-0_Accessor_indices"
},
{
"byteOffset": 0,
"componentType": 5126,
"type": "VEC3",
"count": 30806,
"min": [
-527.3590087890625,
-105.48443603515625,
-495.3590087890625
],
"max": [
599.3590087890625,
-26.287290573120117,
561.3590087890625
],
"bufferView": 2,
"name": "mesh-1-0_Accessor_POSITION"
},
{
"byteOffset": 0,
"componentType": 5126,
"type": "VEC2",
"count": 30806,
"min": [
0.00000964943137660157,
0.000003775880941248033
],
"max": [
0.9999449253082275,
0.9999914169311523
],
"bufferView": 3,
"name": "mesh-1-0_Accessor_TEXCOORD_0"
},
{
"byteOffset": 302316,
"componentType": 5123,
"type": "SCALAR",
"count": 58476,
"bufferView": 4,
"name": "mesh-1-0_Accessor_indices"
}
],
"asset": {
"generator": "obj2gltf",
"version": "2.0"
},
"buffers": [
{
"name": "mesh-0-0_Buffer_POSITION",
"byteLength": 2143008,
"uri": "mesh-0-0_Buffer_POSITION.bin"
}
],
"bufferViews": [
{
"buffer": 0,
"byteLength": 664572,
"byteOffset": 0,
"target": 34962,
"name": "bufferView_0",
"byteStride": 12
},
{
"buffer": 0,
"byteLength": 443048,
"byteOffset": 664572,
"target": 34962,
"name": "bufferView_0",
"byteStride": 8
},
{
"buffer": 0,
"byteLength": 369672,
"byteOffset": 1107620,
"target": 34962,
"name": "bufferView_0",
"byteStride": 12
},
{
"buffer": 0,
"byteLength": 246448,
"byteOffset": 1477292,
"target": 34962,
"name": "bufferView_0",
"byteStride": 8
},
{
"buffer": 0,
"byteLength": 419268,
"byteOffset": 1723740,
"target": 34963,
"name": "bufferView_1"
}
],
"materials": [
{
"name": "Texture",
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0,
"texCoord": 0
},
"metallicFactor": 0,
"roughnessFactor": 1,
"baseColorFactor": [
1,
1,
1,
1
]
},
"emissiveTexture": {
"index": 0,
"texCoord": 0
},
"alphaMode": "OPAQUE",
"doubleSided": false,
"emissiveFactor": [
0,
0,
0
]
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 0,
"TEXCOORD_0": 1
},
"indices": 2,
"material": 0,
"mode": 4
}
],
"name": "mesh-split_1"
},
{
"primitives": [
{
"attributes": {
"POSITION": 3,
"TEXCOORD_0": 4
},
"indices": 5,
"material": 0,
"mode": 4
}
],
"name": "mesh-split_2"
}
],
"nodes": [
{
"children": [
1
],
"name": "rootNode_0",
"mesh": 0
},
{
"mesh": 1
}
],
"samplers": [
{
"magFilter": 9729,
"minFilter": 9729,
"wrapS": 33071,
"wrapT": 33071,
"name": "sampler_0"
}
],
"scene": 0,
"scenes": [
{
"nodes": [
0
],
"name": "scene"
}
],
"textures": [
{
"sampler": 0,
"name": "textureAtlas",
"extensions": {
"GOOGLE_texture_basis": {
"source": 0
}
}
}
],
"images": [
{
"name": "textureAtlasImage",
"mimeType": "image/basis",
"uri": "textureAtlasImage.basis"
}
],
"extensionsUsed": [
"GOOGLE_texture_basis"
],
"extensionsRequired": [
"GOOGLE_texture_basis"
]
}
Binary file not shown.
Binary file not shown.
+131
View File
@@ -0,0 +1,131 @@
<!DOCTYPE html>
<head>
<!-- <script src="https://cdn.jsdelivr.net/npm/three@v0.108.0"></script> -->
<script src="three.min.js"></script>
<script src="GLTFLoader.js"></script>
<script src="BasisTextureLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@v0.104.0/examples/js/controls/OrbitControls.js"></script>
<style>
html, body { width:100%; height:100%; margin:0; padding:0; }
canvas { display:block; }
#panel { position: absolute; top: 10px; left: 10px; color: white; background-color:rgba(0.3, 0.3, 0.3, 0.3); padding: 0.5em; max-width: 400px;}
</style>
</head>
<body>
<div id="panel">
<strong>Basis Texture Transcoder glTF Demo</strong>
<div id="log"></div>
</div>
<script>
const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.gammaOutput = true;
renderer.gammaFactor = 2.2;
const scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf0f0f0 );
const light = new THREE.AmbientLight();
scene.add( light );
const light2 = new THREE.PointLight();
scene.add( light2 );
const camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.set( 8, 6, 5 );
camera.lookAt( new THREE.Vector3( 0, -2, 0 ) );
const controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.autoRotate = true;
// Create BasisTextureLoader and detect supported target formats.
const basisLoader = new THREE.BasisTextureLoader();
basisLoader.setTranscoderPath( '../transcoder/build/' );
basisLoader.useAlpha = false;
basisLoader.detectSupport( renderer );
let formatName = 'Unknown format';
switch(basisLoader.workerConfig.format)
{
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFASTC_4x4:
formatName = 'ASTC';
break;
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC1:
formatName = 'BC1';
break;
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC3:
formatName = 'BC3';
break;
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC7_M6_OPAQUE_ONLY:
formatName = 'BC7';
break;
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB:
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA:
formatName = 'PVRTC';
break;
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFETC1:
formatName = 'ETC1';
break;
default:
break;
}
log(`Transcode to: <strong>${formatName}</strong>`);
// Register BasisTextureLoader for .basis extension.
let loadingManager = new THREE.LoadingManager();
loadingManager.addHandler( /\.basis$/, basisLoader );
// Create GLTFLoader, load model, and render.
const loader = new THREE.GLTFLoader(loadingManager);
loader.load( 'assets/AgiHqSmall.gltf', ( gltf ) => {
const model = gltf.scene;
model.scale.set( 0.01, 0.01, 0.01 );
scene.add( model );
document.body.appendChild( renderer.domElement );
animate();
}, undefined, ( e ) => console.error( e ) );
// Main render loop.
function animate() {
requestAnimationFrame( animate );
controls.update();
renderer.render( scene, camera );
}
// Support viewport resizing.
window.addEventListener( 'resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}, false );
function log(s) {
const div = document.createElement('div');
div.innerHTML = s;
document.getElementById('log').appendChild(div);
}
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

File diff suppressed because one or more lines are too long
+17
View File
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<style>
body {
max-width: 500px;
}
</style>
<h1>Basis WebGL demos</h1>
<p>
Example webpages using the transcoder (compiled to WASM) to render
Basis/KTX2 textures in WebGL. Both demos require WebAssembly and WebGL support.
</p>
<ul>
<li><a href="texture/">.basis texture transcoding</a></li>
<li><a href="encode_test/">.basis texture encoding/transcoding</a></li>
<li><a href="ktx2_encode_test/">.ktx2 texture encoding/transcoding</a></li>
<li><a href="gltf/">3D model (glTF 2.0)</a></li>
</ul>
Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.
@@ -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;
}
+675
View File
@@ -0,0 +1,675 @@
<html>
<head>
<script src="renderer.js"></script>
<script src="dxt-to-rgb565.js"></script>
<script src="../encoder/build/basis_encoder.js"></script>
<script type="text/javascript">
function log(s) {
var div = document.createElement('div');
div.innerHTML = s;
document.getElementById('logger').appendChild(div);
}
function logTime(desc, t) {
log(t + 'ms ' + desc);
}
function isDef(v) {
return typeof v != 'undefined';
}
function elem(id) {
return document.getElementById(id);
}
formatTable = function(rows) {
var colLengths = [];
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
for (var j = 0; j < row.length; j++) {
if (colLengths.length <= j) colLengths.push(0);
if (colLengths[j] < row[j].length) colLengths[j] = row[j].length;
}
}
function formatRow(row) {
var parts = [];
for (var i = 0; i < colLengths.length; i++) {
var s = row.length > i ? row[i] : '';
var padding = (new Array(1 + colLengths[i] - s.length)).join(' ');
if (s && s[0] >= '0' && s[0] <= '9') {
// Right-align numbers.
parts.push(padding + s);
} else {
parts.push(s + padding);
}
}
return parts.join(' | ');
}
var width = 0;
for (var i = 0; i < colLengths.length; i++) {
width += colLengths[i];
// Add another 3 for the separator.
if (i != 0) width += 3;
}
var lines = [];
lines.push(formatRow(rows[0]));
lines.push((new Array(width + 1)).join('-'));
for (var i = 1; i < rows.length; i++) {
lines.push(formatRow(rows[i]));
}
return lines.join('\n');
};
function loadArrayBuffer(uri, callback) {
log('Loading ' + uri + '...');
var xhr = new XMLHttpRequest();
xhr.responseType = "arraybuffer";
xhr.open('GET', uri, true);
xhr.onreadystatechange = function(e) {
if (xhr.readyState == 4 && xhr.status == 200) {
callback(xhr.response);
}
}
xhr.send(null);
}
// ASTC format, from:
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0;
// DXT formats, from:
// http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0;
COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
// BC7 format, from:
// https://www.khronos.org/registry/webgl/extensions/EXT_texture_compression_bptc/
COMPRESSED_RGBA_BPTC_UNORM = 0x8E8C;
// ETC format, from:
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc1/
COMPRESSED_RGB_ETC1_WEBGL = 0x8D64;
// PVRTC format, from:
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/
COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00;
COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02;
// Same as the Module.transcoder_texture_format enum
BASIS_FORMAT = {
cTFETC1: 0,
cTFETC2: 1,
cTFBC1: 2,
cTFBC3: 3,
cTFBC4: 4,
cTFBC5: 5,
cTFBC7: 6,
cTFPVRTC1_4_RGB: 8,
cTFPVRTC1_4_RGBA: 9,
cTFASTC_4x4: 10,
cTFATC_RGB: 11,
cTFATC_RGBA_INTERPOLATED_ALPHA: 12,
cTFRGBA32: 13,
cTFRGB565: 14,
cTFBGR565: 15,
cTFRGBA4444: 16,
cTFFXT1_RGB: 17,
cTFPVRTC2_4_RGB: 18,
cTFPVRTC2_4_RGBA: 19,
cTFETC2_EAC_R11: 20,
cTFETC2_EAC_RG11: 21
};
BASIS_FORMAT_NAMES = {};
for (var name in BASIS_FORMAT) {
BASIS_FORMAT_NAMES[BASIS_FORMAT[name]] = name;
}
DXT_FORMAT_MAP = {};
DXT_FORMAT_MAP[BASIS_FORMAT.cTFBC1] = COMPRESSED_RGB_S3TC_DXT1_EXT;
DXT_FORMAT_MAP[BASIS_FORMAT.cTFBC3] = COMPRESSED_RGBA_S3TC_DXT5_EXT;
DXT_FORMAT_MAP[BASIS_FORMAT.cTFBC7] = COMPRESSED_RGBA_BPTC_UNORM;
var astcSupported = false;
var etcSupported = false;
var dxtSupported = false;
var bc7Supported = false;
var pvrtcSupported = false;
var drawMode = 0;
var tex, width, height, images, levels, have_alpha, alignedWidth, alignedHeight, format, displayWidth, displayHeight;
function redraw()
{
if (!width)
return;
renderer.drawTexture(tex, displayWidth, displayHeight, drawMode);
}
function dumpKTX2FileDesc(ktx2File)
{
log('------');
log('Width: ' + ktx2File.getWidth());
log('Height: ' + ktx2File.getHeight());
log('Faces: ' + ktx2File.getFaces());
log('Layers: ' + ktx2File.getLayers());
log('Levels: ' + ktx2File.getLevels());
log('isUASTC: ' + ktx2File.isUASTC());
log('isETC1S: ' + ktx2File.isETC1S());
log('Format: ' + ktx2File.getFormat());
log('Has alpha: ' + ktx2File.getHasAlpha());
log('Total Keys: ' + ktx2File.getTotalKeys());
log('DFD Size: ' + ktx2File.getDFDSize());
log('DFD Color Model: ' + ktx2File.getDFDColorModel());
log('DFD Color Primaries: ' + ktx2File.getDFDColorPrimaries());
log('DFD Transfer Function: ' + ktx2File.getDFDTransferFunc());
log('DFD Flags: ' + ktx2File.getDFDFlags());
log('DFD Total Samples: ' + ktx2File.getDFDTotalSamples());
log('DFD Channel0: ' + ktx2File.getDFDChannelID0());
log('DFD Channel1: ' + ktx2File.getDFDChannelID1());
log('Is Video: ' + ktx2File.isVideo());
var dfdSize = ktx2File.getDFDSize();
var dvdData = new Uint8Array(dfdSize);
ktx2File.getDFD(dvdData);
log('DFD bytes:' + dvdData.toString());
log('--');
log('--');
log('Key values:');
var key_index;
for (key_index = 0; key_index < ktx2File.getTotalKeys(); key_index++)
{
var key_name = ktx2File.getKey(key_index);
log('Key ' + key_index + ': "' + key_name + '"');
var valSize = ktx2File.getKeyValueSize(key_name);
if (valSize != 0)
{
var val_data = new Uint8Array(valSize);
var status = ktx2File.getKeyValue(key_name, val_data);
if (!status)
log('getKeyValue() failed');
else
{
log('value size: ' + val_data.length);
var i, str = "";
for (i = 0; i < val_data.length; i++)
{
var c = val_data[i];
str = str + String.fromCharCode(c);
}
log(str);
}
}
else
log('<empty value>');
}
log('--');
log('Image level information:');
var level_index;
for (level_index = 0; level_index < ktx2File.getLevels(); level_index++)
{
var layer_index;
for (layer_index = 0; layer_index < Math.max(1, ktx2File.getLayers()); layer_index++)
{
var face_index;
for (face_index = 0; face_index < ktx2File.getFaces(); face_index++)
{
var imageLevelInfo = ktx2File.getImageLevelInfo(level_index, layer_index, face_index);
log('level: ' + level_index + ' layer: ' + layer_index + ' face: ' + face_index);
log('orig_width: ' + imageLevelInfo.origWidth);
log('orig_height: ' + imageLevelInfo.origHeight);
log('width: ' + imageLevelInfo.width);
log('height: ' + imageLevelInfo.height);
log('numBlocksX: ' + imageLevelInfo.numBlocksX);
log('numBlocksY: ' + imageLevelInfo.numBlocksY);
log('totalBlocks: ' + imageLevelInfo.totalBlocks);
log('alphaFlag: ' + imageLevelInfo.alphaFlag);
log('iframeFlag: ' + imageLevelInfo.iframeFlag);
if (ktx2File.isETC1S())
log('ETC1S image desc image flags: ' + ktx2File.getETC1SImageDescImageFlags(level_index, layer_index, face_index));
log('--');
}
}
}
log('--');
log('KTX2 header:');
var hdr = ktx2File.getHeader();
log('vkFormat: ' + hdr.vkFormat);
log('typeSize: ' + hdr.typeSize);
log('pixelWidth: ' + hdr.pixelWidth);
log('pixelHeight: ' + hdr.pixelHeight);
log('pixelDepth: ' + hdr.pixelDepth);
log('layerCount: ' + hdr.layerCount);
log('faceCount: ' + hdr.faceCount);
log('levelCount: ' + hdr.levelCount);
log('superCompressionScheme: ' + hdr.supercompressionScheme);
log('dfdByteOffset: ' + hdr.dfdByteOffset);
log('dfdByteLength: ' + hdr.dfdByteLength);
log('kvdByteOffset: ' + hdr.kvdByteOffset);
log('kvdByteLength: ' + hdr.kvdByteLength);
log('sgdByteOffset: ' + hdr.sgdByteOffset);
log('sgdByteLength: ' + hdr.sgdByteLength);
log('------');
}
function dataLoaded(data)
{
log('Done loading .ktx2 file, decoded header:');
const { KTX2File, initializeBasis, encodeBasisTexture } = Module;
initializeBasis();
const startTime = performance.now();
const ktx2File = new KTX2File(new Uint8Array(data));
if (!ktx2File.isValid())
{
console.warn('Invalid or unsupported .ktx2 file');
ktx2File.close();
ktx2File.delete();
return;
}
width = ktx2File.getWidth();
height = ktx2File.getHeight();
layers = ktx2File.getLayers();
levels = ktx2File.getLevels();
faces = ktx2File.getFaces();
has_alpha = ktx2File.getHasAlpha();
if (!width || !height || !levels) {
console.warn('Invalid .ktx2 file');
ktx2File.close();
ktx2File.delete();
return;
}
// Note: If the file is UASTC, the preferred formats are ASTC/BC7.
// If the file is ETC1S and doesn't have alpha, the preferred formats are ETC1 and BC1. For alpha, the preferred formats are ETC2, BC3 or BC7.
var formatString = 'UNKNOWN';
if (astcSupported)
{
formatString = 'ASTC';
format = BASIS_FORMAT.cTFASTC_4x4;
}
else if (bc7Supported)
{
formatString = 'BC7';
format = BASIS_FORMAT.cTFBC7;
}
else if (dxtSupported)
{
if (has_alpha)
{
formatString = 'BC3';
format = BASIS_FORMAT.cTFBC3;
}
else
{
formatString = 'BC1';
format = BASIS_FORMAT.cTFBC1;
}
}
else if (pvrtcSupported)
{
if (has_alpha)
{
formatString = 'PVRTC1_RGBA';
format = BASIS_FORMAT.cTFPVRTC1_4_RGBA;
}
else
{
formatString = 'PVRTC1_RGB';
format = BASIS_FORMAT.cTFPVRTC1_4_RGB;
}
if (
((width & (width - 1)) != 0) || ((height & (height - 1)) != 0)
)
{
log('ERROR: PVRTC1 requires square power of 2 textures');
}
if (width != height)
{
log('ERROR: PVRTC1 requires square power of 2 textures');
}
}
else if (etcSupported)
{
formatString = 'ETC1';
format = BASIS_FORMAT.cTFETC1;
}
else
{
formatString = 'RGB565';
format = BASIS_FORMAT.cTFRGB565;
log('Decoding .basis data to 565');
}
elem('format').innerText = formatString;
if (!ktx2File.startTranscoding()) {
log('startTranscoding failed');
console.warn('startTranscoding failed');
basisFile.close();
basisFile.delete();
return;
}
dumpKTX2FileDesc(ktx2File);
const dstSize = ktx2File.getImageTranscodedSizeInBytes(0, 0, 0, format);
const dst = new Uint8Array(dstSize);
//log(dstSize);
if (!ktx2File.transcodeImage(dst, 0, 0, 0, format, 0, -1, -1)) {
log('ktx2File.transcodeImage failed');
console.warn('transcodeImage failed');
ktx2File.close();
ktx2File.delete();
return;
}
const elapsed = performance.now() - startTime;
ktx2File.close();
ktx2File.delete();
log('width: ' + width);
log('height: ' + height);
log('levels: ' + levels);
log('layers: ' + layers);
log('faces: ' + faces);
log('has_alpha: ' + has_alpha);
logTime('transcoding time', elapsed.toFixed(2));
alignedWidth = (width + 3) & ~3;
alignedHeight = (height + 3) & ~3;
displayWidth = alignedWidth;
displayHeight = alignedHeight;
var canvas = elem('canvas');
canvas.width = alignedWidth;
canvas.height = alignedHeight;
if (format === BASIS_FORMAT.cTFASTC_4x4)
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGBA_ASTC_4x4_KHR);
}
else if ((format === BASIS_FORMAT.cTFBC3) || (format === BASIS_FORMAT.cTFBC1) || (format == BASIS_FORMAT.cTFBC7))
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, DXT_FORMAT_MAP[format]);
}
else if (format === BASIS_FORMAT.cTFETC1)
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGB_ETC1_WEBGL);
}
else if (format === BASIS_FORMAT.cTFPVRTC1_4_RGB)
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGB_PVRTC_4BPPV1_IMG);
}
else if (format === BASIS_FORMAT.cTFPVRTC1_4_RGBA)
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGBA_PVRTC_4BPPV1_IMG);
}
else
{
canvas.width = width;
canvas.height = height;
displayWidth = width;
displayHeight = height;
// Create 565 texture.
var dstTex = new Uint16Array(width * height);
// Convert the array of bytes to an array of uint16's.
var pix = 0;
for (var y = 0; y < height; y++)
for (var x = 0; x < width; x++, pix++)
dstTex[pix] = dst[2 * pix + 0] | (dst[2 * pix + 1] << 8);
tex = renderer.createRgb565Texture(dstTex, width, height);
}
redraw();
}
function download_file(filename, body)
{
var element = document.createElement('a');
//element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
const blob = new Blob([body]);
const url = URL.createObjectURL(blob);
element.setAttribute('href', url);
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
var encodedKTX2File;
function PNGDataLoaded(data)
{
const { BasisFile, BasisEncoder, initializeBasis, encodeBasisTexture } = Module;
initializeBasis();
// Create a destination buffer to hold the compressed .basis file data. If this buffer isn't large enough compression will fail.
var ktx2FileData = new Uint8Array(1024*1024*10);
var num_output_bytes;
// Compress using the BasisEncoder class.
log('BasisEncoder::encode() started:');
const basisEncoder = new BasisEncoder();
const qualityLevel = parseInt(elem('EncodeQuality').value, 10);
const uastcFlag = elem('EncodeUASTC').checked;
basisEncoder.setCreateKTX2File(true);
basisEncoder.setKTX2UASTCSupercompression(true);
basisEncoder.setKTX2SRGBTransferFunc(true);
basisEncoder.setSliceSourceImage(0, new Uint8Array(data), 0, 0, true);
basisEncoder.setDebug(elem('Debug').checked);
basisEncoder.setComputeStats(elem('ComputeStats').checked);
basisEncoder.setPerceptual(elem('SRGB').checked);
basisEncoder.setMipSRGB(elem('SRGB').checked);
basisEncoder.setQualityLevel(qualityLevel);
basisEncoder.setUASTC(uastcFlag);
basisEncoder.setMipGen(elem('Mipmaps').checked);
if (!uastcFlag)
log('Encoding at ETC1S quality level ' + qualityLevel);
const startTime = performance.now();
num_output_bytes = basisEncoder.encode(ktx2FileData);
const elapsed = performance.now() - startTime;
logTime('encoding time', elapsed.toFixed(2));
var actualKTX2FileData = new Uint8Array(ktx2FileData.buffer, 0, num_output_bytes);
basisEncoder.delete();
if (num_output_bytes == 0)
{
log('encodeBasisTexture() failed!');
}
else
{
log('encodeBasisTexture() succeeded, output size ' + num_output_bytes);
encodedKTX2File = actualKTX2FileData;
//download("test.ktx2", actualKTX2FileData);
}
if (num_output_bytes != 0)
{
dataLoaded(actualKTX2FileData);
}
}
function runLoadFile() {
elem('logger').innerHTML = '';
loadArrayBuffer(elem('file').value, dataLoaded);
}
function runEncodePNGFile() {
elem('logger').innerHTML = '';
loadArrayBuffer(elem('pngfile').value, PNGDataLoaded);
}
function alphaBlend() { drawMode = 0; redraw(); }
function viewRGB() { drawMode = 1; redraw(); }
function viewAlpha() { drawMode = 2; redraw(); }
function downloadEncodedFile()
{
if (encodedKTX2File)
{
if (encodedKTX2File.length)
download_file("encoded_file.ktx2", encodedKTX2File);
}
}
</script>
</head>
<body>
<br>
<div style="font-size: 24pt; font-weight: bold">
Basis Universal KTX2 encoding and transcoding test
</div>
<br>This demo uses the Basis C++ transcoder (compiled to Javascript using Emscripten) to transcode a .ktx2 file to <b id='format'>FORMAT</b>
<br>It also supports encoding .PNG files to .KTX2.
<br>Thanks to Evan Parker for providing <a href="https://github.com/toji/webgl-texture-utils">webgl-texture-utils</a> and this test bed.
<br>
<br>
.ktx2 file:
<input id="file" type="text" size=30 value="assets/kodim23.ktx2"></input>
<input type="button" value="Transcode!" onclick="runLoadFile()"></input>
<br>
<br>
.png file:
<input id="pngfile" type="text" size=30 value="assets/kodim18_64x64.png"></input>
<input type="button" value="Encode!" onclick="runEncodePNGFile()"></input>
<br>
<input type="button" value="Download Encoded File" onclick="downloadEncodedFile()">
<br>
Debug:
<input type="checkbox" id="Debug">
<br>
Compute Stats:
<input type="checkbox" id="ComputeStats">
<br>
sRGB:
<input type="checkbox" id="SRGB">
<br>
Mipmaps:
<input type="checkbox" id="Mipmaps">
<br>
UASTC:
<input type="checkbox" id="EncodeUASTC">
<br>
ETC1S Quality:
<input type="range" min="1" max="255" value="10" class="slider" id="EncodeQuality">
<br>
<br>
<input type="button" value="Alpha blend" onclick="alphaBlend()"></input>
<input type="button" value="View RGB" onclick="viewRGB()"></input>
<input type="button" value="View Alpha" onclick="viewAlpha()"></input>
<div style="position:absolute; left: 525px; top:130px; font-size: 20pt; font-weight: bold; color: red">
<div id="no-compressed-tex" style="display: none; width: 768px; font-size: 20pt; font-weight: bold; color: red">
NOTE: Your browser does not support several compressed texture format, so using RGB565.
</div>
<canvas id='canvas'></canvas>
</div>
<br><br>
<div id='logger'></div>
</body>
<script>
BASIS({onRuntimeInitialized : () => {
elem('SRGB').checked = true;
var gl = elem('canvas').getContext('webgl');
astcSupported = !!gl.getExtension('WEBGL_compressed_texture_astc');
etcSupported = !!gl.getExtension('WEBGL_compressed_texture_etc1');
dxtSupported = !!gl.getExtension('WEBGL_compressed_texture_s3tc');
pvrtcSupported = !!(gl.getExtension('WEBGL_compressed_texture_pvrtc')) || !!(gl.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc'));
bc7Supported = !!gl.getExtension('EXT_texture_compression_bptc');
// HACK HACK - for testing uncompressed
//astcSupported = false;
//etcSupported = false;
//dxtSupported = false;
//bc7Supported = false;
//pvrtcSupported = false;
window.renderer = new Renderer(gl);
elem('file').addEventListener('keydown', function(e) {
if (e.keyCode == 13) {
runLoadFile();
}
}, false);
if (!(astcSupported || etcSupported || dxtSupported || pvrtcSupported))
{
// elem('nodxt').style.display = 'block';
}
runLoadFile();
}}).then(module => window.Module = module);
</script>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

+246
View File
@@ -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.<string, WebGLUniformLocation>}
* @private
*/
this.uniformLocations_ = {};
/**
* Cached attribute locations.
* @type {Object.<string, WebGLActiveInfo>}
* @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');
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+128
View File
@@ -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;
}
+596
View File
@@ -0,0 +1,596 @@
<html>
<head>
<script src="renderer.js"></script>
<script src="dxt-to-rgb565.js"></script>
<script src="../transcoder/build/basis_transcoder.js"></script>
<script type="text/javascript">
function log(s) {
var div = document.createElement('div');
div.innerHTML = s;
document.getElementById('logger').appendChild(div);
}
function logTime(desc, t) {
log(t + 'ms ' + desc);
}
function isDef(v) {
return typeof v != 'undefined';
}
function elem(id) {
return document.getElementById(id);
}
formatTable = function(rows) {
var colLengths = [];
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
for (var j = 0; j < row.length; j++) {
if (colLengths.length <= j) colLengths.push(0);
if (colLengths[j] < row[j].length) colLengths[j] = row[j].length;
}
}
function formatRow(row) {
var parts = [];
for (var i = 0; i < colLengths.length; i++) {
var s = row.length > i ? row[i] : '';
var padding = (new Array(1 + colLengths[i] - s.length)).join(' ');
if (s && s[0] >= '0' && s[0] <= '9') {
// Right-align numbers.
parts.push(padding + s);
} else {
parts.push(s + padding);
}
}
return parts.join(' | ');
}
var width = 0;
for (var i = 0; i < colLengths.length; i++) {
width += colLengths[i];
// Add another 3 for the separator.
if (i != 0) width += 3;
}
var lines = [];
lines.push(formatRow(rows[0]));
lines.push((new Array(width + 1)).join('-'));
for (var i = 1; i < rows.length; i++) {
lines.push(formatRow(rows[i]));
}
return lines.join('\n');
};
function loadArrayBuffer(uri, callback) {
log('Loading ' + uri + '...');
var xhr = new XMLHttpRequest();
xhr.responseType = "arraybuffer";
xhr.open('GET', uri, true);
xhr.onreadystatechange = function(e) {
if (xhr.readyState == 4 && xhr.status == 200) {
callback(xhr.response);
}
}
xhr.send(null);
}
// ASTC format, from:
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0;
// DXT formats, from:
// http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0;
COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
// BC7 format, from:
// https://www.khronos.org/registry/webgl/extensions/EXT_texture_compression_bptc/
COMPRESSED_RGBA_BPTC_UNORM = 0x8E8C;
// ETC format, from:
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc1/
COMPRESSED_RGB_ETC1_WEBGL = 0x8D64;
// PVRTC format, from:
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/
COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00;
COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02;
// Same as the Module.transcoder_texture_format enum
BASIS_FORMAT = {
cTFETC1: 0,
cTFETC2: 1,
cTFBC1: 2,
cTFBC3: 3,
cTFBC4: 4,
cTFBC5: 5,
cTFBC7: 6,
cTFPVRTC1_4_RGB: 8,
cTFPVRTC1_4_RGBA: 9,
cTFASTC_4x4: 10,
cTFATC_RGB: 11,
cTFATC_RGBA_INTERPOLATED_ALPHA: 12,
cTFRGBA32: 13,
cTFRGB565: 14,
cTFBGR565: 15,
cTFRGBA4444: 16,
cTFFXT1_RGB: 17,
cTFPVRTC2_4_RGB: 18,
cTFPVRTC2_4_RGBA: 19,
cTFETC2_EAC_R11: 20,
cTFETC2_EAC_RG11: 21
};
BASIS_FORMAT_NAMES = {};
for (var name in BASIS_FORMAT) {
BASIS_FORMAT_NAMES[BASIS_FORMAT[name]] = name;
}
DXT_FORMAT_MAP = {};
DXT_FORMAT_MAP[BASIS_FORMAT.cTFBC1] = COMPRESSED_RGB_S3TC_DXT1_EXT;
DXT_FORMAT_MAP[BASIS_FORMAT.cTFBC3] = COMPRESSED_RGBA_S3TC_DXT5_EXT;
DXT_FORMAT_MAP[BASIS_FORMAT.cTFBC7] = COMPRESSED_RGBA_BPTC_UNORM;
var astcSupported = false;
var etcSupported = false;
var dxtSupported = false;
var bc7Supported = false;
var pvrtcSupported = false;
var drawMode = 0;
var tex, width, height, images, levels, have_alpha, alignedWidth, alignedHeight, format, displayWidth, displayHeight;
function redraw()
{
if (!width)
return;
renderer.drawTexture(tex, displayWidth, displayHeight, drawMode);
}
function dumpBasisFileDesc(basisFile)
{
var basisFileDesc = basisFile.getFileDesc();
log('------');
log('getFileDesc():');
log('version: ' + basisFileDesc.version);
log('us per frame: ' + basisFileDesc.usPerFrame);
log('total images: ' + basisFileDesc.totalImages);
log('userdata0: ' + basisFileDesc.userdata0 + ' userdata1: ' + basisFileDesc.userdata1);
log('texFormat: ' + basisFileDesc.texFormat);
log('yFlipped: ' + basisFileDesc.yFlipped + ' hasAlphaSlices: ' + basisFileDesc.hasAlphaSlices);
if (basisFileDesc.texFormat == Module.basis_tex_format.cETC1S.value)
{
log('numEndpoints: ' + basisFileDesc.numEndpoints);
log('endpointPaletteOfs: ' + basisFileDesc.endpointPaletteOfs);
log('endpointPaletteLen: ' + basisFileDesc.endpointPaletteLen);
log('numSelectors: ' + basisFileDesc.numSelectors);
log('selectorPaletteOfs: ' + basisFileDesc.selectorPaletteOfs);
log('selectorPaletteLen: ' + basisFileDesc.selectorPaletteLen);
log('tablesOfs: ' + basisFileDesc.tablesOfs);
log('tablesLen: ' + basisFileDesc.tablesLen);
}
log('------');
log('getImageDesc() for all images:');
var image_index;
for (image_index = 0; image_index < basisFileDesc.totalImages; image_index++)
{
log('image: ' + image_index);
var basisImageDesc = basisFile.getImageDesc(image_index);
log('origWidth: ' + basisImageDesc.origWidth + ' origWidth: ' + basisImageDesc.origHeight);
log('numBlocksX: ' + basisImageDesc.numBlocksX + ' origWidth: ' + basisImageDesc.numBlocksY);
log('numLevels: ' + basisImageDesc.numLevels);
log('alphaFlag: ' + basisImageDesc.alphaFlag + ' iframeFlag: ' + basisImageDesc.iframeFlag);
log('getImageLevelDesc() for all mipmap levels:');
var level_index;
for (level_index = 0; level_index < basisImageDesc.numLevels; level_index++)
{
var basisImageLevelDesc = basisFile.getImageLevelDesc(image_index, level_index);
log('level: ' + level_index +
' rgb_file_offset: ' + basisImageLevelDesc.rgbFileOfs + ' rgb_file_len: ' + basisImageLevelDesc.rgbFileLen);
if (basisFileDesc.hasAlphaSlices)
log('alpha_file_offset: ' + basisImageLevelDesc.alphaFileOfs + ' alpha_file_len: ' + basisImageLevelDesc.alphaFileLen);
}
}
log('------');
}
function dataLoaded(data)
{
log('Done loading .basis file, decoded header:');
const { BasisFile, initializeBasis } = Module;
initializeBasis();
const startTime = performance.now();
const basisFile = new BasisFile(new Uint8Array(data));
width = basisFile.getImageWidth(0, 0);
height = basisFile.getImageHeight(0, 0);
images = basisFile.getNumImages();
levels = basisFile.getNumLevels(0);
has_alpha = basisFile.getHasAlpha();
dumpBasisFileDesc(basisFile);
if (!width || !height || !images || !levels) {
console.warn('Invalid .basis file');
basisFile.close();
basisFile.delete();
return;
}
// Note: If the file is UASTC, the preferred formats are ASTC/BC7.
// If the file is ETC1S and doesn't have alpha, the preferred formats are ETC1 and BC1. For alpha, the preferred formats are ETC2, BC3 or BC7.
var formatString = 'UNKNOWN';
if (astcSupported)
{
formatString = 'ASTC';
format = BASIS_FORMAT.cTFASTC_4x4;
}
else if (bc7Supported)
{
formatString = 'BC7';
format = BASIS_FORMAT.cTFBC7;
}
else if (dxtSupported)
{
if (has_alpha)
{
formatString = 'BC3';
format = BASIS_FORMAT.cTFBC3;
}
else
{
formatString = 'BC1';
format = BASIS_FORMAT.cTFBC1;
}
}
else if (pvrtcSupported)
{
if (has_alpha)
{
formatString = 'PVRTC1_RGBA';
format = BASIS_FORMAT.cTFPVRTC1_4_RGBA;
}
else
{
formatString = 'PVRTC1_RGB';
format = BASIS_FORMAT.cTFPVRTC1_4_RGB;
}
if (
((width & (width - 1)) != 0) || ((height & (height - 1)) != 0)
)
{
log('ERROR: PVRTC1 requires square power of 2 textures');
}
if (width != height)
{
log('ERROR: PVRTC1 requires square power of 2 textures');
}
}
else if (etcSupported)
{
formatString = 'ETC1';
format = BASIS_FORMAT.cTFETC1;
}
else
{
formatString = 'RGB565';
format = BASIS_FORMAT.cTFRGB565;
log('Decoding .basis data to 565');
}
elem('format').innerText = formatString;
log('format: ' + format);
if (!basisFile.startTranscoding()) {
log('startTranscoding failed');
console.warn('startTranscoding failed');
basisFile.close();
basisFile.delete();
return;
}
const isUncompressedFormat = Module.formatIsUncompressed(format);
const blockWidth = isUncompressedFormat ? 1 : 4, blockHeight = isUncompressedFormat ? 1 : 4;
const bytesPerBlockOrPixel = Module.getBytesPerBlockOrPixel(format);
log('isUncompressedFormat: ' + isUncompressedFormat + ' bytesPerBlockOrPixel: ' + bytesPerBlockOrPixel);
const dstSize = basisFile.getImageTranscodedSizeInBytes(0, 0, format);
const dst = new Uint8Array(dstSize);
// log('getImageTranscodedSizeInBytes() returned ' + dstSize);
// Use the low or high level transcoding API's. The high level API's require .basis files, while the low-level API's just work off blobs of memory and parameters.
if (elem('ContainerIndependentTranscoding').checked)
{
// Always transcode the first image and the first mipmap level
const image_index = 0;
const level_index = 0;
// Get the .basis file description
var basisFileDesc = basisFile.getFileDesc();
// Get the description of the file's first image (there could be multiple images, for texture arrays or videos)
var basisImageDesc = basisFile.getImageDesc(image_index);
// Get the description of this image's mipmap level
var basisImageLevelDesc = basisFile.getImageLevelDesc(image_index, level_index);
var status = false;
// If we're transcoding to ETC1S, use the LowLevelETC1SImageTranscoder class. Otherwise use the transcodeUASTCImage() function.
if (basisFileDesc.texFormat == Module.basis_tex_format.cETC1S.value)
{
// Create an instance of the LowLevelETC1SImageTranscoder class.
const etc1s_transcoder = new Module.LowLevelETC1SImageTranscoder();
// Create Uint8Array's pointing into the various bits of the .basis file holding the compressed data for the codebooks and the Huffman tables.
var selectorPalette = new Uint8Array(data, basisFileDesc.selectorPaletteOfs, basisFileDesc.selectorPaletteLen);
var endpointPalette = new Uint8Array(data, basisFileDesc.endpointPaletteOfs, basisFileDesc.endpointPaletteLen);
var tables = new Uint8Array(data, basisFileDesc.tablesOfs, basisFileDesc.tablesLen);
// Create a Uint8Array pointing to the image's compressed data.
// If it's an opaque .basis file, there will only be RGB data. For transparant .basis files, each RGB slice will be immediately followed by an alpha slice.
// Compressed ETC1S alpha data is guaranteed to immediately follow the RGB data (it's always at odd slices in the .basis file).
var compData = new Uint8Array(data, basisImageLevelDesc.rgbFileOfs, basisImageLevelDesc.rgbFileLen + basisImageLevelDesc.alphaFileLen);
// Decompress the palettes. This only has to be done once for each .basis file.
var status = etc1s_transcoder.decodePalettes(basisFileDesc.numEndpoints, endpointPalette, basisFileDesc.numSelectors, selectorPalette);
if (status)
{
// Decompress the Huffman tables. This only has to be done once for each .basis file.
status = etc1s_transcoder.decodeTables(tables);
if (status)
{
// Now transcode the image using the container independent transcode API. This API does not interpret any .basis file structures - only the compressed ETC1S data.
status = etc1s_transcoder.transcodeImage(
format,
dst, dstSize / bytesPerBlockOrPixel,
compData,
basisImageDesc.numBlocksX, basisImageDesc.numBlocksY, basisImageDesc.origWidth, basisImageDesc.origHeight, level_index,
0, basisImageLevelDesc.rgbFileLen, basisFileDesc.hasAlphaSlices ? basisImageLevelDesc.rgbFileLen : 0, basisImageLevelDesc.alphaFileLen,
0,
basisFileDesc.hasAlphaSlices,
basisFileDesc.isVideo,
0,
0);
if (!status)
log('transcodeImage() failed');
}
else
{
log('decodeTables() failed');
}
}
else
{
log('decodePalettes() failed');
}
etc1s_transcoder.delete();
if (!status)
{
log('etc1s_transcoder failed');
console.warn('etc1s_transcoder failed');
basisFile.close();
basisFile.delete();
return;
}
else
{
log('Successfully called etc1s_transcoder.transcodeImage()');
}
}
else
{
// Create a Uint8Array pointing to the image's compressed data.
var compData = new Uint8Array(data, basisImageLevelDesc.rgbFileOfs, basisImageLevelDesc.rgbFileLen);
// Transcode the UASTC texture data to the desired output format.
status = Module.transcodeUASTCImage(
format,
dst, dstSize / bytesPerBlockOrPixel,
compData,
basisImageDesc.numBlocksX, basisImageDesc.numBlocksY, basisImageDesc.origWidth, basisImageDesc.origHeight, level_index,
0, basisImageLevelDesc.rgbFileLen,
0,
basisFileDesc.hasAlphaSlices,
basisFileDesc.isVideo,
0,
0,
-1, -1);
if (!status)
{
log('transcodeUASTCImage() failed');
console.warn('transcodeUASTCImage() failed');
basisFile.close();
basisFile.delete();
return;
}
else
{
log('Successfully called transcodeUASTCImage()');
}
}
}
else
{
// Use the high-level transcode API, which requires a .basis file.
if (!basisFile.transcodeImage(dst, 0, 0, format, 0, 0)) {
log('basisFile.transcodeImage failed');
console.warn('transcodeImage failed');
basisFile.close();
basisFile.delete();
return;
}
}
const elapsed = performance.now() - startTime;
basisFile.close();
basisFile.delete();
log('width: ' + width);
log('height: ' + height);
log('images: ' + images);
log('first image mipmap levels: ' + levels);
log('has_alpha: ' + has_alpha);
logTime('transcoding time', elapsed.toFixed(2));
alignedWidth = (width + 3) & ~3;
alignedHeight = (height + 3) & ~3;
displayWidth = alignedWidth;
displayHeight = alignedHeight;
var canvas = elem('canvas');
canvas.width = alignedWidth;
canvas.height = alignedHeight;
if (format === BASIS_FORMAT.cTFASTC_4x4)
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGBA_ASTC_4x4_KHR);
}
else if ((format === BASIS_FORMAT.cTFBC3) || (format === BASIS_FORMAT.cTFBC1) || (format == BASIS_FORMAT.cTFBC7))
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, DXT_FORMAT_MAP[format]);
}
else if (format === BASIS_FORMAT.cTFETC1)
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGB_ETC1_WEBGL);
}
else if (format === BASIS_FORMAT.cTFPVRTC1_4_RGB)
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGB_PVRTC_4BPPV1_IMG);
}
else if (format === BASIS_FORMAT.cTFPVRTC1_4_RGBA)
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGBA_PVRTC_4BPPV1_IMG);
}
else
{
canvas.width = width;
canvas.height = height;
displayWidth = width;
displayHeight = height;
// Create 565 texture.
var dstTex = new Uint16Array(width * height);
// Convert the array of bytes to an array of uint16's.
var pix = 0;
for (var y = 0; y < height; y++)
for (var x = 0; x < width; x++, pix++)
dstTex[pix] = dst[2 * pix + 0] | (dst[2 * pix + 1] << 8);
tex = renderer.createRgb565Texture(dstTex, width, height);
}
redraw();
}
function runLoadFile() {
elem('logger').innerHTML = '';
loadArrayBuffer(elem('file').value, dataLoaded);
}
function alphaBlend() { drawMode = 0; redraw(); }
function viewRGB() { drawMode = 1; redraw(); }
function viewAlpha() { drawMode = 2; redraw(); }
</script>
</head>
<body>
<br>
<div style="font-size: 24pt; font-weight: bold">
.basis->Compressed texture transcoder test
</div>
<br>This demo uses the Basis C++ transcoder (compiled to Javascript using Emscripten) to transcode a .basis file to <b id='format'>FORMAT</b>
<br>Thanks to Evan Parker for providing <a href="https://github.com/toji/webgl-texture-utils">webgl-texture-utils</a> and this test bed.
<br>
<br>
.basis file:
<input id="file" type="text" size=30 value="assets/kodim03.basis"></input>
<input type="button" value="Run!" onclick="runLoadFile()"></input>
<br>
<br>
<input type="button" value="Alpha blend" onclick="alphaBlend()"></input>
<input type="button" value="View RGB" onclick="viewRGB()"></input>
<input type="button" value="View Alpha" onclick="viewAlpha()"></input>
<br>
<br>
Use Container Independent Transcoding API's:
<input type="checkbox" id="ContainerIndependentTranscoding">
<div style="position:absolute; left: 525px; top:130px; font-size: 20pt; font-weight: bold; color: red">
<div id="no-compressed-tex" style="display: none; width: 768px; font-size: 20pt; font-weight: bold; color: red">
NOTE: Your browser does not support several compressed texture format, so using RGB565.
</div>
<canvas id='canvas'></canvas>
</div>
<br><br>
<div id='logger'></div>
</body>
<script>
BASIS({onRuntimeInitialized : () => {
var gl = elem('canvas').getContext('webgl');
astcSupported = !!gl.getExtension('WEBGL_compressed_texture_astc');
etcSupported = !!gl.getExtension('WEBGL_compressed_texture_etc1');
dxtSupported = !!gl.getExtension('WEBGL_compressed_texture_s3tc');
pvrtcSupported = !!(gl.getExtension('WEBGL_compressed_texture_pvrtc')) || !!(gl.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc'));
bc7Supported = !!gl.getExtension('EXT_texture_compression_bptc');
// HACK HACK - for testing uncompressed
//astcSupported = false;
//etcSupported = false;
//dxtSupported = false;
//bc7Supported = false;
//pvrtcSupported = false;
window.renderer = new Renderer(gl);
elem('file').addEventListener('keydown', function(e) {
if (e.keyCode == 13) {
runLoadFile();
}
}, false);
if (!(astcSupported || etcSupported || dxtSupported || pvrtcSupported))
{
// elem('nodxt').style.display = 'block';
}
runLoadFile();
}}).then(module => window.Module = module);
</script>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

+246
View File
@@ -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.<string, WebGLUniformLocation>}
* @private
*/
this.uniformLocations_ = {};
/**
* Cached attribute locations.
* @type {Object.<string, WebGLActiveInfo>}
* @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');
@@ -0,0 +1,4 @@
build/*
!build/basis_loader.js
!build/basis_transcoder.js
!build/basis_transcoder.wasm
+52
View File
@@ -0,0 +1,52 @@
cmake_minimum_required(VERSION 3.0)
project(basisu_transcoder_js)
# KTX2 support (and Zstandard) in the transcoder is purely optional. Use "cmake -DKTX2=FALSE" to completely disable KTX2 support (which also disables Zstandard).
# If this is FALSE you'll only be able to transcode .basis files.
option(KTX2 "KTX2" TRUE)
# If KTX2 is enabled, Zstandard is needed to support decompressing supercompressed UASTC files.
# If you know you'll never be transcoding KTX2 files using this feature you can set KTX2_ZSTANDARD to 0.
option(KTX2_ZSTANDARD "KTX2_ZSTANDARD" TRUE)
message("KTX2=${KTX2}")
message("KTX2_ZSTANDARD=${KTX2_ZSTANDARD}")
if (EMSCRIPTEN)
set(CMAKE_CXX_STANDARD 11)
set(SRC_LIST
../../transcoder/basisu_transcoder.cpp
basis_wrappers.cpp
)
if (KTX2)
set(KTX2_DEFINITION BASISD_SUPPORT_KTX2=1)
else()
set(KTX2_DEFINITION BASISD_SUPPORT_KTX2=0)
endif()
if (KTX2_ZSTANDARD)
set(SRC_LIST ${SRC_LIST}
../../zstd/zstddeclib.c
)
set(ZSTD_DEFINITION BASISD_SUPPORT_KTX2_ZSTD=1)
else()
set(ZSTD_DEFINITION BASISD_SUPPORT_KTX2_ZSTD=0)
endif()
# message("KTX2_DEFINITION=${KTX2_DEFINITION})
# message("ZSTD_DEFINITION=${ZSTD_DEFINITION})
add_executable(basis_transcoder.js ${SRC_LIST})
target_compile_definitions(basis_transcoder.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=0 ${KTX2_DEFINITION} ${ZSTD_DEFINITION} )
target_compile_options(basis_transcoder.js PRIVATE -O3 -fno-strict-aliasing)
target_include_directories(basis_transcoder.js PRIVATE ../../transcoder)
set_target_properties(basis_transcoder.js PROPERTIES
OUTPUT_NAME "basis_transcoder"
SUFFIX ".js"
LINK_FLAGS "--bind -s ALLOW_MEMORY_GROWTH=1 -O3 -s ASSERTIONS=0 -s MALLOC=emmalloc -s MODULARIZE=1 -s EXPORT_NAME=BASIS ")
endif()
+7
View File
@@ -0,0 +1,7 @@
Prebuilt versions of `basis_transcoder.js` and `basis_transcoder.wasm` are included in the `build/` folder, and are sufficient for local demos. To build the transcoder yourself, first install emscripten ([tutorial](https://webassembly.org/getting-started/developers-guide/)) and cmake ([download](https://cmake.org/download/)). Then run:
```shell
cd webgl/transcoder/build/
emcmake cmake ../
make
```
File diff suppressed because it is too large Load Diff