Loading of colored subsets causing first load blocking after going from r143 to r179

Hi,

We’re building a CAD-like shipbuilding viewer. A single ship model can have tens of thousands meshes and consists of logical parts (systems: ladders, pipes, doors, etc.). For per-system coloring, hiding, and transparency we currently do this (simplified):

  • We load one large master BufferGeometry (positions + normals + indexes).
  • . For each “system” we create a new BufferGeometry that:
    • Reuses the position and normal attributes by reference from the master geometry (no copying).
    • Has its own index (subset of triangles for that system).
  • Each subset geometry gets its own Mesh + Material (needed for different colors, opacity, visibility toggling).
  • We end up with a few hundred subset BufferGeometries per model.

Example (simplified a lot):


const geometry = new BufferGeometry();

geometry.setAttribute('position', master.geometry.attributes.position);

geometry.setAttribute('normal', master.geometry.attributes.normal);

geometry.setIndex(subsetIndices); // Uint32Array per system

const mesh = new Mesh(geometry, material);

// renderer, before first render
this.modelScene.add(subset.mesh); // foreach subset

This way of handling colored subsets is the backbone of a lot of logic in our codebase, and a refactor of this wouldn’t be very nice.

In r143 this loaded smoothly. After upgrading to r179 we now see long blocking time during the creation of these subset geometries. Once everything finishes, performance is 100% fine.

Do we have to bite the bullet and do a (very large) refactor, or is there an easier way of fixing this?

Thanks. :slightly_smiling_face:

If you run a Chrome performance profile, what are the heavy operations? Might be the geometry upload to GPU, might be computing the bounding boxes… probably good to narrow it down to 1-2 causes if possible.

Hi thanks for the reply,

The thing that is blocking is the computeBoundingSphere .

Hm, it’s not immediately obvious to me why r147 would be performing better than r179 here. But regardless I think you could consider reporting this on the GitHub repository as a performance regression. Personally I think the bounding box calculation should iterate only over vertices contained in the index, where it’s currently iterating the entire attribute each time, but that might require discussion.

If you need a quick fix in the meantime, assign geometry.boundingBox and geometry.boundingSphere manually after constructing each geometry, by iterating only vertices in the index. If assigned before first render, the renderer will not try to recompute them.

Aside – three.js generally assumes each BufferGeometry is a self-contained unit of upload to the GPU. So it is likely that your reuse of vertex attributes is consuming more GPU memory than necessary. That may or may not be an issue, I don’t think it’s the cause here if the framerate is OK after first render. I wouldn’t necessarily be in any rush to change your approach, if not.

One alternative that I think would be interesting to propose to three.js would be allowing each Mesh to have a .drawRange property, as BufferGeometry currently does, so that you could write all the indices to a single index, assign the same BufferGeometry to every mesh, and upload that geometry to the GPU only once. This could reduce any buffer binding overhead and VRAM cost.

if subsetIndices isn’t an attribute, setIndex has to loop through it to find max vertex id..

doing:

geometry.setIndex(new Uint32BufferAttribute(subsetIndices,1))

instead, skips the arrayNeedsUint32 check… and skips an additional attribute alloc.. and copy…

Most important is to find the cause of the issue, otherwise you may end up refactoring things that need no refactoring.

As a general advice upgrading should be done in steps of 10 (or less) releases r143 → r152 → r161 → r170 → r179. This is because important changes in the API are announced as messages for 10 releases and then the messages are removed. Additionally, upgrading in small steps could make it possible to identify where the issue occurs. It could be anywhere between r144 or r179. When you find the release when performance drops, check the changelog file for this release. Often it could point to changes that might be related to the issue.

This is what I ended up doing:

// foreach subset
subset.mesh.geometry.boundingSphere = new Sphere();

gross? yes, but it works like before.

We aren’t doing any raycasting or whatever so boundingSpheres end up being obsolete anyways.