Emscripten's generated WebGPU shim implements the dynamic-offset path of
wgpuRenderPassEncoderSetBindGroup as
pass.setBindGroup(index, group, HEAPU32, ptr >>> 2, count);
where HEAPU32 is the view over the entire wasm linear memory. Browsers
validate the byte length of that whole backing buffer, not the slice
actually read, and refuse anything over 2 GB. This build lets the heap
grow to 4 GB because large federations need it, so on a big enough
session (66 models) every dynamic-offset draw — the axis gizmo, section
gizmo and overlay lines, all drawn every frame — throws
TypeError: GPURenderPassEncoder.setBindGroup: Argument 3 can't be an
ArrayBuffer or an ArrayBufferView larger than 2 GB
on every frame for the life of the page.
ifcviewer::setBindGroupDynamic copies the handful of offsets into a
small Uint32Array on web (HEAPU32.slice, not subarray, which would alias
the heap again) and forwards straight through natively. The five
dynamic-offset call sites route through it. A Playwright spec spies on
setBindGroup and asserts the largest buffer it is ever handed is the
offsets themselves (4 bytes), where the shim previously passed the full
268 MB heap 35 times in three seconds of idle rendering.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>