Can you sort by self time and after that also check with view sensitive enabled?
With sensitive enabled it is about 50-55 fps.

Please show me the bottom-up tab and sort by self time.
@luisherasme @PavelBoytchev Yo guys, while its good to discuss different optimization strategies, I think it would be best to move debugging conversations to the DMs. Javascript profiler instructions are rather off-topic, while also unnecessarily taking up a large part of the thread ![]()
Update:
I have improved the speed of the instanced skinned mesh by implementing an approach inspired by @DolphinIQ proposal. Before, I used to recalculate the skeleton for each instance individually. But now, I organize all the instances beforehand. This enables me to calculate the skeleton for a particular animation and time and assign it to all instances with the same animation and time. As a result, I no longer need to recalculate the skeleton for each instance, which saves a lot of time.
Demo on my phone:
Realistically speaking, the problem is matrix multiplication. You have a lot of bones and a lot of instances.
Sampling animation curves is an issue too.
Off the top of my head, I would recommend two paths:
-
Move curve sampling and matrix multiplications off the main core. Simplest path would be to move them to a separate worker thread I guess, but a better path would be to send animation matrix for each instance to the GPU and let sampling and matrix operations happen there.
That is, you have a matrix with one side being animation indices for a model, and second side being timing. You can optimize this to say a list of 2-3 animations instead. Up to you really. -
Bake animation states. Instead of doing matrix multiplications and animation curve sampling, pre-bake a table for each animation, where each row contains matrices for each bone. This way you only need to retrieve a relevant row from the table, no computation requited, only memory access.
It’s a fun problem to think about. I did work on a similar problem for fun a month or two ago. It was just a fun prototype for me, and I was leaning towards the first approach myself, since you end up with bone matrices baked into a texture on the GPU anyway, might as well move earlier animation stages to the GPU also.
One more thing to think about in these types of problems are memory access patterns. You want to keep data as cache-coherent as possible, and your code that loops over instances to be as tight as possible as well. Often it’s more optimal to take a complex piece of loop logic and break it into stages, then have multiple loops, one per stage, instead of having one larger loop.
PS:
To clarify the first point, about animation matrix or a list, that was to enable animation blending support.
- replace matrix.element with sharedArrayBuffer
- disable the matrixAutoUpdate property of bones
- start the animation in another thread
- call the updateMatrixWorld method on the bones
Works: provided that the object is added to the render and step 2 is completed.
Doesn’t work: if you perform the second step before adding the object to the render.
Thanks @DolphinIQ for this thread and the details, they were really useful as I was trying to throttle animations too.
I ended up with a simpler implementation, requiring no change in three.js.
You don’t really need to add the root bone to the scene : it can have no parent (no parent will use an identity matrix, similar to the scene).
After the root bone is completely removed from the scene hierarchy, you get to manually decide when the bones needs to updateWorldMatrix().
To avoid the call to skeleton.update(), I override the function with an empty function when I need it, restore it otherwise.
// Setup inside the unit class
this.rootBone.parent.remove(rootBone);
this.rootBone.updateMatrixWorld(true); //recompute all the world matrix for all the bones, relative to the skinnedMesh
this.skinnedMesh.bindMode = "detached";
this.skippedDelta = 0;
// Setup outside the unit class
const noopFunction = () => {};
// Update loop
if ( shouldUpdateAnimationDependingOnFrame ) {
unit.mixer.update( delta + unit.skippedDelta ); // Compensate for skipped frames
unit.rootBone.updateWorldMatrix();
unit.skeleton.update = THREE.Skeleton.prototype.update;
unit.skippedDelta = 0;
} else {
unit.skeleton.update = noopFunction;
unit.skippedDelta += delta;
}
Note my three.js version is rather old (r126) so your mileage may vary.
For those looking to move these matrix computations to the GPU (this was not desirable in my case), there’s an old example here similar to Baked Texture Animations in babylon.js. Both do not support blending between multiple animations.
First of all thanks to ya’ll for collecting and contributing so much info on anim optimization in three.
A lot of suggestions on further optimizations went into the direction of doing matrix calculations on the GPU, but in most of my apps the GPU already has a substantial workload. The most underused resource (in most three.js apps I’d assume) are my other CPU cores.
So I was wondering what it would take to put the heavy calc part of AnimationMixer.update() on a worker thread. Did anyone experiment in this direction yet?
Cheers
I don’t think the animation system works with transferable objects, so you’d take a hit copying stuff in/out of the workers…
But… it’s not outside the realm of possibility.
There are other simpler approaches to getting lots of animation tho, for instance VAT + instancing…
This would only work for me if I actually have that many instances of the same model in my scene though, right?
With a top-down rts that makes sense but for something like a mmo or just a game with lots of different types of enemies, the models might be different, potentially not even sharing the same Skeleton.
I found this comment by wizgrav very interesting InstancedSkinnedMesh by wizgrav · Pull Request #22667 · mrdoob/three.js · GitHub – I’d imagine something like that, with the heavy compute part outsourced to one or multiple workers (wasm or not) and then send back in time to the main thread. But of course you are right that you’d have to solve the syncing problem.
With WebGPU, all things are possible, so jot that down!


