Babylon Viewer HTML element broken in Safari (Intel)

Hello!

I am using the Babylon Viewer HTML element to show a model (glb). I am running macOS 26.5.2 (25F84) on an Intel Mac. Everything works well on Apple Silicon. On Intel it is a different story: It works well in Chrome, Firefox and Opera, but is completely broken in Safari (26.5). Here is a test project which only contains the viewer element: Babylon Test

This is how it looks like correctly:

This is how it looks like in Safari macOS:

There are no errors in the console. The same happens in the sandbox.

Three months ago there wasn’t any problem with Safari – so there is either a regression in Babylon or in Safari I guess? Are there any features I should enable/disable for Safari? Or are Intel Macs simply not supported anymore? ATM I am at a loss.

Thanks!

cc @ryantrem

This one may take some time as on one on the team has an Intel based Mac! :eyes:

Thanks for the clear report and for putting up a test page — that made this much easier to dig into.

I pulled your model.glb. It has 574,189 vertices and uses 32-bit (UNSIGNED_INT) indices. Babylon needs the uintIndices capability to render that — it comes from WebGL2, or from WebGL1 via the OES_element_index_uint extension. If neither is available, we silently truncate the index buffer to 16-bit, and every index above 65,535 wraps around and points at an unrelated vertex.

I decoded your index buffer to see how much of the model that would affect: 978,533 of 1,103,457 triangles (88.7%) reference at least one vertex above 65,535. So if this is what’s happening, ~89% of the mesh gets rewired into garbage triangles stretching between unrelated parts of the model, leaving a small fraction recognizable — which is what your screenshot looks like to me.

That would also fit the rest of the symptoms: no console errors (the truncation is silent), fine in Chrome/Firefox/Opera on the same machine, and fine on Apple Silicon.

So my working theory is that Safari 26.5 on Intel isn’t giving us a usable WebGL2 context, and we’re falling back to WebGL1.

Two things would confirm it:

1. Does a smaller model (under ~65k vertices) render correctly on the same page?

2. Paste this into the Safari console on your test page, once the model has finished loading:

(async () => {
  const out = {}, mk = t => { try { return document.createElement('canvas').getContext(t); } catch { return null; } };
  const gl2 = mk('webgl2'), gl1 = mk('webgl');
  out.webgl2Available = !!gl2;
  out.webgl2IsReal = !!(gl2 && gl2.deleteQuery);
  out.webgl1Available = !!gl1;
  out.OES_element_index_uint = !!(gl1 && gl1.getExtension('OES_element_index_uint'));

  const gl = gl2 || gl1;
  if (gl) {
    const dbg = gl.getExtension('WEBGL_debug_renderer_info');
    out.vendor = dbg ? gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL) : gl.getParameter(gl.VENDOR);
    out.renderer = dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER);
    out.glVersion = gl.getParameter(gl.VERSION);
  }

  const d = document.querySelector('babylon-viewer')?.viewerDetails;
  if (!d) {
    out.viewer = 'not ready — rerun once the model has loaded';
  } else {
    const e = d.scene.getEngine();
    out.babylonEngine = e.constructor.name;
    out.babylonGLVersion = e.webGLVersion;
    out.uintIndicesCap = e.getCaps().uintIndices;
    out.meshes = d.scene.meshes.filter(m => m.getTotalVertices() > 0).map(m => ({
      name: m.name,
      verts: m.getTotalVertices(),
      indices: m.getTotalIndices(),
      gpuIndexType: (() => { try { return m.geometry?.getIndexBuffer()?.is32Bits ? 'Uint32' : 'Uint16'; } catch { return '?'; } })()
    }));
  }
  console.log(JSON.stringify(out, null, 2));
})();

The value I care about most is uintIndicesCap — if that comes back false, this is confirmed.

A couple of other things worth checking while you’re in there:

  • Safari → Settings → Advanced → Feature Flags — is WebGL 2.0 enabled?
  • Which GPU does the machine have (Intel Iris/UHD, or a discrete AMD)? And which Mac model?

One note on “it worked three months ago”: your page loads @babylonjs/viewer from jsDelivr unpinned, so it auto-updates. If you pin an older version and it still breaks, that points at Safari rather than at Babylon.

Separately, and regardless of what turns out to be happening on your machine — Babylon dropping index data silently here isn’t great. I’ll look into getting a warning logged for that case.

Quick update — I simulated this locally against your model, and the theory only half holds up.

Confirmed: the parts that still look right in your render are exactly the triangles whose indices are all under 65,536. I isolated just those and got the hood top, the knees, and the shoes with the speckled ground patch — precisely the pieces that survive in your screenshot. So the 65,536 boundary is definitely involved.

But the broken part doesn’t match. If Babylon truncates the indices, every triangle still points at one of your model’s own vertices, so the mess stays inside the model’s silhouette — I get a dense, narrow spike-cone. Your spikes spread about twice as wide as the model itself. I projected all 574,189 vertices through the same camera from 24 different angles: the widest they can ever appear is about 0.32 of the frame height, and your artifact spans about 0.63. Index wrapping can’t produce that.

So this looks less like Babylon truncating indices, and more like the GPU handing back bad vertex data above index 65,535 — the kind of thing you see when a driver can’t cope with meshes over 65,536 vertices. That fits the rest of the picture too: Chrome and Firefox go through ANGLE, which works around this, and it’s Intel-specific.

Practically, that means uintIndicesCap will probably come back true. That’s still a useful answer rather than a dead end — it’s exactly what separates the two cases — so the snippet is still worth running, along with the small-model test (something under 65k vertices).