1

Calling renderer.render() in a requestAnimationFrame() loop will often fire faster than the <canvas> element can update its image, making any "true" FPS measurement inaccurate.

In three.js, how can I block each iteration long enough to let the <canvas> update its image?

For example, let's say you have a laggy three.js scene where you are also displaying the frame number in an HTML element on your page. If you record your screen at a high capture rate (~120Hz), you will notice that the frame number displayed in the HTML element will "skip" a frame or two occasionally, which artificially inflates FPS measurements. For example, between only two unique images in the canvas, the frame number would go from 1,340 to 1,343, essentially skipping 2 frames.

It appears that my requestAnimationFrame() loop is firing faster than the browser can repaint the <canvas> and all the other elements on the page. I have already tried all sorts of "hacks", including the following:

  1. 🤔 Connecting the <canvas> to a <video> element to take advantage of video.requestVideoFrameCallback(callback). This is the only one that stopped the skipped frames, but it introduced a much worse lag that outweighed the benefits.
  2. ❌ Doing this (using MessageChannel): https://webperf.tips/tip/measuring-paint-time (did nothing)
  3. ❌ Doing this: requestAnimationFrame(() => requestAnimationFrame(callback)) (did nothing)

So is there any way I can reliably block each iteration of requestAnimationFrame() to give the canvas (and all the elements on the page) enough time to repaint? This would allow me to get a "true" FPS measurement of the actual frames that were visible on screen.

Here is a JSFiddle demonstrating a laggy scene that prints out the FPS based on a requestAnimationFrame() loop. Just increase the number of instances for this InstancedMesh on this line to something that starts lagging your scene:

const mesh = new THREE.InstancedMesh(geometry, material, 100);

Next, record your screen at a high capture rate and rapidly rotate the camera around. You will notice the frame number skips a frame occasionally.

https://jsfiddle.net/ekypq94d

Here is a small recording of the frame number. Notice how frames 1022 and 1029 are skipped (Chrome was used to test):

enter image description here

2
  • The initial assessment is very surprising. Could you set up an example snippet where this would happen? The main process is supposed to wait for the renderer process is ready before starting a new event-loop iteration. Commented Oct 10 at 4:51
  • I just added a JSFiddle to the original post. In the JSFiddle, first increase the number of instances for the InstancedMesh to something that would begin lagging the scene (your machine may differ than mine). Next, begin recording the scene at a high capture rate (preferably 120Hz or greater). Finally, jerk the camera around rapidly as you record the scene for a few seconds and stop the recording. You can now manually step through each frame and see that frames get skipped occasionally. Note: I tested this using Chrome. Commented Oct 10 at 18:53

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.