Compressed Texture Extensions

Compressed Textures showed why GPU block compression matters and how Basis Universal 3,114 transcodes one file per device. The extension lists above show the other half of the story: which formats a device accepts is decided by its hardware, not by the browser. The GTX 1650, a desktop GPU, took the BC family only (s3tc, s3tc_srgb, rgtc, bptc); SwiftShader 2,546 , which decodes in software, also took ETC and ASTC, the phone formats. No renderer here offered WEBGL_compressed_texture_pvrtc, the PowerVR format of older iOS devices. Enabling matters too: gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS) stays empty until getExtension() has requested the extensions. A loader therefore enables the families it has files for, best first, and transcodes to the first that works:

Picking the compressed format family a device supportsJavaScript
const families = { astc: 'WEBGL_compressed_texture_astc', bc7: 'EXT_texture_compression_bptc',
  etc2: 'WEBGL_compressed_texture_etc', bc3: 'WEBGL_compressed_texture_s3tc' };   // best first
console.log('formats before:', gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS).length);
const enabled = Object.keys(families).filter((family) => gl.getExtension(families[family]));
const count = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS).length;
const best = enabled[0] ?? 'rgba8';                   // uncompressed if nothing matches
console.log(`enabled: ${enabled.join(', ')} | formats: ${count} | use ${best}`);
Output
formats before: 0
enabled: bc7, bc3 | formats: 8 | use bc7

That is the GTX 1650; SwiftShader printed enabled: astc, bc7, etc2, bc3 | formats: 46 | use astc. The KTX2 loaders of Three.js 30,815 and Babylon.js 89,603 make the same choice when they transcode Basis Universal textures (Compressed Textures).

Which compressed texture families this GPU accepts, best first, and the one a loader would transcode Basis Universal textures toHTMLLive
<!doctype html>
<style>
  body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
  .wrap { position: relative; max-width: 600px; }
  canvas { display: block; width: 100%; }
  .lbl { position: absolute; transform: translate(-50%, -50%); font: 11px monospace; text-align: center; color: #fff; }
  pre { max-width: 600px; margin: 4px 0; font: 11px monospace; }
</style>
<div class="wrap"><canvas id="c" width="600" height="220"></canvas></div>
<pre id="out"></pre>
<script>
const gl = document.getElementById('c').getContext('webgl2');
const before = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS).length;   // empty until extensions are requested
const families = [['astc', 'WEBGL_compressed_texture_astc', 'phones, Apple silicon'], ['bc7', 'EXT_texture_compression_bptc', 'desktop'],
  ['etc2', 'WEBGL_compressed_texture_etc', 'Android'], ['bc3', 'WEBGL_compressed_texture_s3tc', 'desktop'],
  ['pvrtc', 'WEBGL_compressed_texture_pvrtc', 'older iOS']];   // best first
const enabled = families.filter(([, ext]) => gl.getExtension(ext)).map(([family]) => family);
const count = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS).length;
const best = enabled[0] ?? 'rgba8';                             // uncompressed if nothing matches
// One tile per family: green if this device takes it, grey if not, a gold frame round the choice.
const wrap = document.querySelector('.wrap');
gl.clearColor(0.97, 0.96, 0.93, 1); gl.clear(gl.COLOR_BUFFER_BIT);
gl.enable(gl.SCISSOR_TEST);
families.forEach(([family, ext, where], i) => {
  const x = 10 + i * 118, ok = enabled.includes(family);
  if (family === best) { gl.scissor(x - 5, 45, 118, 150); gl.clearColor(0.88, 0.6, 0.06, 1); gl.clear(gl.COLOR_BUFFER_BIT); }
  gl.scissor(x, 50, 108, 140);
  gl.clearColor(...(ok ? [0.25, 0.49, 0.23] : [0.7, 0.68, 0.64]), 1); gl.clear(gl.COLOR_BUFFER_BIT);
  // a 4x4 "block" motif: the unit of block compression
  for (let k = 0; k < 16; k++) {
    gl.scissor(x + 22 + (k % 4) * 16, 110 + Math.floor(k / 4) * 16, 14, 14);
    gl.clearColor(...(ok ? [0.35 + (k % 3) * 0.1, 0.62, 0.35] : [0.8, 0.78, 0.74]), 1); gl.clear(gl.COLOR_BUFFER_BIT);
  }
  wrap.insertAdjacentHTML('beforeend', `<div class="lbl" style="left:${(x + 54) / 6}%;top:${(220 - 80) / 2.2}%"><b>${family}</b><br>${ok ? 'yes' : 'no'}</div>` +
    `<div class="lbl" style="left:${(x + 54) / 6}%;top:${(220 - 30) / 2.2}%;color:#333;font-size:9px">${where}</div>`);
});
document.getElementById('out').textContent =
  `COMPRESSED_TEXTURE_FORMATS: ${before} before getExtension(), ${count} after\nenabled: ${enabled.join(', ') || 'none'} | use ${best}`;
</script>