BeanstalkBlue

Regarding BabylonJS main loop timing flaw, can I manually call update() and draw() loop?

Recommended Posts

I'm new to BabylonJS but one of the first things I notice is that almost all of the demos run slow (20-30 FPS is typical) on the same machine that is running three.js stuff fast.

It seems like update/draw is not being called as often as it could be, as according to the debug overlay update and draw frame time is only 10ms in these same demos (so potential FPS is 100). 

Additionally I think BabylonJS suffers from timing flaws that other JS engines do, see this article:

https://www.isaacsukin.com/news/2015/01/detailed-explanation-javascript-game-loops-and-timing

In three.js I am using Mainloop.js to control update/draw timing logic. I think I would like to do the same in babylon.js, unless I am missing something.

To expand further, physics should ideally be at a 30hz update rate in my game, but draw calls and other update logic should be at 60hz. Also update logic should be called at a very consistent 60hz, where draw can vary a bit from 40-60fps without real harm. (Draw lag spikes are permissible but update lag spikes should be dealt with by calling update twice to catch up and make game logic and especially physics have predictable behavior and equal time per step.)

 

Is there a way without forking Babylon to override the update/draw timing logic?

 

I notice the functions beginFrame() and endFrame() are public. Can I just call beginFrame, do my render calls, and then call endFrame? Will this break anything? What should "do my render calls" look like?

Again I would prefer to not modify babylon.js itself, and instead just override its behavior because I don't want to maintain a fork. 

Share this post


Link to post
Share on other sites

Hi and welcome! Firstly, BJS is a pretty mature library and the devs are quite active about adding missing features - so probably better to do forum searches and whatnot before you consider starting and maintaining a fork. ;)

As for main loop timing, yes, you can ignore the default way (engine.runRenderLoop) and do 

    engine.beginFrame()
    scene.render()
    engine.endFrame()

according to any timing you like. I'm not sure it's actually necessary for your purposes though, since the default behavior drives itself from requestAnimationFrame, which is what you'd probably want to do anyway.

Physics is a different matter. As far as I know BJS currently assumes that physics and rendering happen at the same rate - so it ticks the physics engine once per render, and then updates all necessary mesh positions without any interpolation.

To decouple them, I think your best be would be to ignore BJS's built-in physics handling and manage your own physics world separately. In this case you'd want to just tick your physics engine at whatever interval, and then add a scene.beforeRender() handler that moves necessary meshes to interpolated positions. (This is what I do in my project, since I'm not using one of BJS's default engines.)

Share this post


Link to post
Share on other sites

Does scene.render() do updating as well though?

A big part of the problem is that I do not want update and render to tick 1:1 at the exact same rate. Whenever there is a draw slowdown or update lag, update needs to tick an extra time (or several times) while the game catches up. E.g., if the user switches to another tab and back, or if there was a garbage collection.

If scene.render() doesn't do the updating, where/how do I do that?

For example in the Instances2 demo I see that animation of the meshes is done by registering a function with our animation logic into scene.registerBeforeRender(), and it seems like that just gets called when we call scene.render().

So is it safe to just make my own update() function and ignore registerBeforeRender()? Or are there other considerations to make sure updates aren't happening that I don't want.

E.g. How about shadow map updating for example? I don't want shadow maps to be generated every single update, for some lights.

 

Regarding physics decoupling, I noticed actually that there is a physics setTimeStep() function that might accomplish setting physics to run at a different update rate (though I'm not sure if that just "slows down time" in the physics sim).

Share this post


Link to post
Share on other sites

I read some of the article you reference.  I also did not like the "frame based time scale" for animation.  I have built my own animation system, primarily for highly integrated / choreographed animations & sounds, think speech.  I abandoned anything to do with  "frames", like skeleton frame animation in favor of skeleton pose interpolation.  Everything is measured in millis.

I also have a master time control (implemented as a scene after renderer), so all participants both know "when" this frame is & can adjust timescale to speed up / slow down.  The master time control also detects / manages tab switches.

Think there have also been changes recently to allow some time scaling in BJS animation too.  The standard animation system, I thought, did adjust if it found it was running late though, albeit coarse.

Have no extensive plans for physics nor knowledge. You may need to resort some kind of update loop for physics.  If you know Typescript, there may alternatives to "forking".  It is possible to sub-class both Scene & Engine.  As long as you stick to referencing / overriding public methods & properties, you can use everything else without being responsible for an entire fork.

Share this post


Link to post
Share on other sites
Quote

did not like the "frame based time scale" for animation

A lot of game logic can be greatly simplified by having a fixed time step for each frame. Animation of bones for example is something that is not really affected by this since generally you can just use whatever amount of time has passed to calculate current correct positions.

So perhaps you don't like this since animation can actually be simpler using other methods, but animation logic is a lot easier than physics/networking.

For things like physics and networking it isn't so simple. Using the current amount of time since last frame to do game logic can take you deep into calculus land with challenging integrals to solve for current positions given only the amount of time since last frame.

It is much easier to keep players over the network in sync and keep physics predictable by having a fixed time step size and running update an extra time when needed to catch up, if we fall behind briefly.

Share this post


Link to post
Share on other sites
3 hours ago, BeanstalkBlue said:

Does scene.render() do updating as well though?

AFAIK it updates everything BJS knows about - the physics engine if one is registered, animations, etc. And naturally it triggers frustum culling and matrix updates and all that stuff that you wouldn't want to decouple from rendering. If you need to advance a particular subsystem without rendering (like ticking the physics engine several times in a row), the necessary APIs are probably there but it would depend what you're trying to update.

 

4 hours ago, BeanstalkBlue said:

For example in the Instances2 demo I see that animation of the meshes is done by registering a function with our animation logic into scene.registerBeforeRender(), and it seems like that just gets called when we call scene.render().

So is it safe to just make my own update() function and ignore registerBeforeRender()? Or are there other considerations to make sure updates aren't happening that I don't want.

I'm not sure what you're asking. registerBeforeRender() just does what it says on the tin - you pass it a function, and that function will get called right before renders occur. Naturally you don't have to put logic there, you could call it right before you call render. Demos don't normally drive renders manually, so it's easier for them to use an event.

 

4 hours ago, BeanstalkBlue said:

E.g. How about shadow map updating for example? I don't want shadow maps to be generated every single update, for some lights.

I don't know specifically, but I strongly suspect this falls into the category of things that will by default get run every render, and you'll need to set a flag or something to disable them if you want to. (Naturally BJS will attempt to intelligently avoid doing unnecessary work though - so it would probably better to not worry about this until profiling shows that it's affecting performance.)

 

4 hours ago, BeanstalkBlue said:

Regarding physics decoupling, I noticed actually that there is a physics setTimeStep() function that might accomplish setting physics to run at a different update rate (though I'm not sure if that just "slows down time" in the physics sim).

Like I said before, Babylon ticks its physics engine once per render. setTimeStep sets the value Babylon passes to the engine's tick() function.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.