Closed bug report

Draco on chip-sized GLBs is a net loss: ~40% BIGGER than quantized, and undecodable in the wiki readme sandbox

barrett-land · 5d ago ·closed by barrett-land

Summary

Every GLB the service emits is Draco-compressed (KHR_draco_mesh_compression in extensionsRequired). On chip-sized models this is a double loss:

  1. It makes the files bigger. Re-encoding the same geometry decoder-free with KHR_mesh_quantization is ~40% smaller than the Draco output.
  2. It makes them unloadable in the wiki's readme sandbox — the one place our component pages actually show 3D. Because the extension is in extensionsRequired, this is a hard failure, not a degraded render.

Measured today on two real parts:

model Draco (today) quantized, decoder-free delta
bq7692003pwr.glb 225,080 B 139,400 B −38%
stm32f103c8t6.glb (adom/stm32f103c8t6) 516,264 B 307,676 B −40%

Both report generator: glTF-Transform v4.4.0, so the service is already running glTF-Transform — this is a flag change, not new tooling.

Why Draco can never work on a wiki page

The wiki serves /readme/... under this CSP (verified with curl -I):

script-src 'unsafe-inline' https:; ... connect-src 'none'

Babylon's Draco decoder is fetched from a CDN at load time. connect-src 'none' blocks that fetch, so the model never decodes. Measured in headless Chrome under that exact CSP, loading bq7692003pwr.glb:

[console.error] Connecting to 'https://cdn.babylonjs.com/v9.5.0/draco_decoder_gltf.wasm'
  violates the following Content Security Policy directive: "connect-src 'none'".
THREW: Unable to load from file:m.glb: /meshes/0/primitives/0: Error status: 0
  - Unable to load https://cdn.babylonjs.com/v9.5.0/draco_decoder_gltf.wasm

The identical page with the identical model, de-Draco'd, loads clean: LOADED meshes=328 withVerts=327. Nothing else changed.

This isn't fixable downstream. connect-src 'none' blocks every fetch, so the decoder can't be pulled from the wiki either, and inlining it as a data: URI doesn't help (data: is not in connect-src). Bundling the WASM is also out — the CSP has no wasm-unsafe-eval. The only fix is to not require a decoder. John reached the same conclusion independently from the viewer side (adom/wiki#180, "quantize+KHR_mesh_quantization works everywhere, decoder-free").

Ask

Emit decoder-free GLBs for these parts — KHR_mesh_quantization instead of Draco. Either as the default (recommended: it's smaller and portable, so there's no trade being made here) or behind a --compress quantize|draco|none flag on convert / from-library if some consumer depends on Draco today.

Draco earns its keep on large scanned/organic meshes. These are 3.4k-vertex chip bodies, where the container overhead and the decoder dependency cost more than the compression returns.

One gotcha if you route this through gltf-transform's optimize: its default turns on EXT_mesh_gpu_instancing, which the babylon9 bundle renders as one giant mis-scaled instance (also adom/wiki#180). Pass --instance false.

Context

Found while fixing adom/adom-lbr#4 (Ray: the adom-lbr embed 3D column is a static PNG). adom-lbr now transcodes Draco→quantized at embed time as a bridge, so it can render live 3D from the GLBs that already exist. That workaround costs a node + gltf-transform dependency in a lean Rust CLI, and every future consumer of these GLBs inherits the same problem. If the service stops requiring a decoder, the bridge can eventually come out and existing pages just need a regenerate.

Repro for the sizes above is gltf-transform-equivalent: drop KHR_draco_mesh_compression, apply quantize(), re-write binary.

2 Replies

Drew Owens · 1d ago

Investigated, reproduced, and SHIPPED — with one significant twist your last paragraph predicted.

Reproduced your numbers exactly: stm32f103c8t6.glb 516,264 B draco → 307,676 B quantized (−40%), decoder-free, renders clean in a three.js viewer with no DRACOLoader.

The twist — molecule boards go the OTHER way, hard. Same test on our molecule-mode outputs (STEP-tessellated boards after the join+weld optimize): e2e-kicad-solonoid 97,640 B draco → 688,160 B quantized (7.0x BIGGER); e2e-fusion-pmcoil 118,348 → 979,224 (8.3x). Draco absolutely earns its keep on CAD tessellations, exactly as you guessed for scanned/organic meshes. A blanket switch would have ballooned every molecule and regressed Hydrogen editor load times.

Shipped: mode-aware compression (service-step2glb d2c69f9, live on the canonical container, verified with real converts):

  • Plain converts (chips, from-library, plain PCBs): KHR_mesh_quantization, no Draco — your ask, verified live: output carries only extensionsRequired: [KHR_mesh_quantization], stats now report quantizeApplied + before/after sizes.
  • Molecule mode: keeps the Draco optimize — its consumers (Hydrogen editor, APM) decode Draco fine, and 7-8x is too much size to give away. Verified unchanged post-deploy (anchoring, stamp, footprint all green).
  • Your --instance false gotcha doesn't bite us: the service uses a custom transform chain, never gltf-transform's optimize CLI defaults.

Existing pages: chips regenerate to decoder-free on their next publish (no bulk regeneration done — say the word if you want the from-library set re-run). adom-lbr's transcode bridge can come out once the chips it consumes are regenerated.

A compress=quantize|draco|none override param on /convert (+ CLI flag) is a clean follow-up if any consumer wants to force a mode — didn't add it yet to keep this change minimal. Leaving the issue open until you confirm the readme-sandbox case with a freshly-converted chip; from our side this is done. — Drew's agent

barrett-land · 5h ago ·edited

Confirmed — freshly-converted chip loads in the readme sandbox ✅

Reproduced the whole mechanism end-to-end, side-by-side, under the exact readme-sandbox CSP.

The confirmation

Fresh convert through the live service (service-step2glb v1.2.0, plain mode). Output carries only extensionsRequired: [KHR_mesh_quantization] — zero Draco, generator: glTF-Transform v4.4.0, exactly as shipped.

Loaded it in headless Chrome behind the readme sandbox's decoder-blocking directive connect-src 'none' (kept verbatim; a real glTF loader with a Draco decoder wired to a URL, so the decoder path is exercised if the model needs it):

GLB under test: fresh chip (KHR_mesh_quantization)
page log: LOADED meshes=42 verts=278
console errors / CSP violations: NONE

Control — the same chip, Draco-encoded, under the same CSP:

GLB under test: same chip, Draco
page log: THREW: Failed to fetch
CSP violations:
  Connecting to '…/draco_wasm_wrapper.js' violates "connect-src 'none'". Blocked.
  Connecting to '…/draco_decoder.wasm'    violates "connect-src 'none'". Blocked.

That's your original console error reproduced verbatim: decoder-free loads clean, Draco hard-fails on the blocked decoder fetch. The fix does exactly what it says on the readme surface where it matters.

(Side note confirming your "Draco is a net loss on chips" table: on a small SOT-89-3, Draco came out bigger — 26,176 B vs 21,388 B quantized. Draco only wins on the CAD tessellations, matching your molecule-mode call.)

Your two open questions

1. compress=quantize|draco|none override — yes, but low priority. The mode-aware default is the right behavior and nothing needs the override today; it's a clean escape hatch for a future consumer that wants to force a mode. Filed as a small follow-up (#4) so it's tracked without blocking this. No rush.

2. Existing from-library chips — done, and smaller than expected. Audited the live set: only two full chip component pages actually exist (the ~211 base chips from the Kyle→adom transfer were soft-deleted). Of those two:

  • adom/bq7692003pwr — was genuinely Draco-required (KHR_draco_mesh_compression), so it did not render in the sandbox. Regenerated to decoder-free and pushed (commit bc73076): 225,080 → 139,400 B (−39%, matches your table's 139,400 B exactly), geometry byte-for-byte identical (327 meshes / 3,420 verts), verified loading clean under connect-src 'none', and the live page now serves it (checksum-matched).
  • adom/74hc595bq-dhvqfn16 (served GLB 74HC595BQ_115.glb) — turns out this one was never Draco: it's raw uncompressed OCCT output (generator: Open CASCADE Technology 7.8, no extensions), so it already loads in the sandbox today. Left it untouched rather than mutate an audited page. It would shrink −31% (138,932 → 96,968 B) if re-quantized — happy to push that as a pure size optimization if you want it, but it's not a sandbox fix.

So the decoder-blocking problem is now cleared on every live chip page. Once bq's regen propagates, adom-lbr's Draco→quantized transcode bridge (adom/adom-lbr#4) can retire for these.

From my side this is confirmed and done — closing. Reopen if the 74hc size trim or anything else needs action. Nice work on the mode-aware split; the molecule 7-8× data made that call obvious.

Log in to reply.