Babylon Sandbox vs. Standard Import - >35 FPS difference

What optimisation techniques is the Babylon.js Sandbox using to load in meshes/files?

I’ve looked through the Sandbox source code, and can’t see anything obvious.

Importing our model into the Sandbox we are seeing FPS in the region of 40-50:

However, with exactly the same model imported into our own scene with the following code:

BABYLON.SceneLoader.LoadAssetContainer("assets/3d/", "power_plant.glb", scene, function (container) {
    
    for (var m in container.meshes) {
      let mesh = container.meshes[m]
      mesh.freezeWorldMatrix();
      mesh.doNotSyncBoundingInfo = true;
      mesh.isPickable = false;
    }

    container.addAllToScene();
  })

We are getting awful performance in comparison. Generally scraping by in low-teens and high single digits:

Given the time taken in the meshes selection count, I had assumed it was Octree-related, but even adding in:

scene.createOrUpdateSelectionOctree(1500);

after the addAllToScene() call, it’s made no difference.

Our own application is Babylon.js wrapped inside Vue.js, which is the only difference I can possibly think of outside of code optimisation.

Any help/advice appreciated.

Thanks

Have also played with this playground:

https://playground.babylonjs.com/#3YFJ5R#0

If you comment out line 32: scene.createOrUpdateSelectionOctree(); there is no impact at all on the FPS/performance.

Well this seems like Vue is messing with the mesh properties.

You are not the only one:
Performance of BJS and VueJS - Questions - Babylon.js (babylonjs.com)
Very low FPS with Vue - Tutorials and tips - Babylon.js (babylonjs.com)

Thanks @Deltakosh - is there nothing else of note going on in the Sandbox? I used Vue & BabylonJS together on my Mayflower Digital Experience which has 500k+ vertices and didn’t hit any performance issues.

Hi!
Yes, this seems to be the same scenario. I had the same symptoms. Your render loop is running multiple times.

Learn more here:

1 Like

Thanks @roland.

The third method described is what I am using. Seems as though I’ll backtrack to the first method to expose my FPS, which is what I’m mostly using it for.

Since my old post was mentioned. I ended up moving my code out of Vue and just have methods which interact between vue and my code so that vue does not pick up or try to observe what is going on in Babylon.

https://sindarius.github.io/ you can hit the Load A Benchy on the top right and then play with the scrubber. So far I’ve been able to get it up to about 4mil voxels on an RTX 2080. Keeping Vue and Babylon disconnected was the main thing for me.

1 Like

I always create babylonjs scenes in a loosely coupled way with Vue, so I can test them easily in a non Vue container to determine what causes the poor performance. To be honest I had no performance issues after I realized, that exposing engine or scene is bad. You even not want to pass an array by reference between the scene and vue, always do a copy, shallow or deep, it depends on the situation.

1 Like

Thanks!

For what it’s worth, I actually built a custom events system for the Mayflower project, which I will also use here:

in the .scene.js:

let listeners = {}
function addEventListener (topic, fcn) {
  if (!(topic in listeners)) {
    listeners[topic] = []
  }
  listeners[topic].push(fcn)
}

function triggerEvent (topic, data) {
  if (topic in listeners) {
    listeners[topic].forEach(function (fcn) {
      fcn(data)
    })
  }
}

then from the accompanying .vue file:


import * as Scene from './myscene.scene.js'

...

Scene.addEventListener("<event-topic>", (data) => {
    // do stuff on event triggering with the emitted data from Babylon
})

I am using Vue bus (any messaging bus can be used) for the same reason, so I eliminated the possibility of sending anything by reference. :vulcan_salute:

Anyone interested into documenting it for our doc page?

What exactly? The messaging stuff?
EDIT: I am sure you were asking for the messaging stuff , there was no other discussed topic actually. If so, I will gladly make a contribution, however I think this has nothing to do with BabylonJS, but people are struggling with it and asking help here on the BJS forum, so why not? :vulcan_salute:

yeah I see that question about integrating with Vue quite a lot

I was wondering if we should add a comment about it in that doc: How to use BabylonJS with Vue | Babylon.js Documentation

Yep, already working on it. PR ETA 60 minutes.

1 Like

Love that answer ;D

1 Like

Hey @Deltakosh, I need to postpone this, because I’ve just realized, that I started to make the example project for Vue2 and I have no juice and calmness :smiley: now to start it over. Need some sleep :smiley:

However you can have a look at the repo with the example project, there is already a lot of stuff done:

See ya!

R.

For what it’s worth - I’m in the process of (slowly) writing a series of articles about the Mayflower project, one of which was about communicating between Vue and Babylon, so I’m happy to detail this all there.

Hi @Deltakosh !
after several unexpected issues I finally migrated the repo to Vue3. :vulcan_salute:

Here is the demo app (one can watch the console how the messages are passed back and forth)
https://babylonjs.nascor.tech/scene-director/
and the repo:

I will write about this technique an article a bit later. :sunglasses:
R.

1 Like

Hi guys!

The problem

If you are exposing BabylonJS objects and you are manipulating them directly with Vue, you will sooner or later end up with very low FPS caused by multiple redraws of the scene. The reason is that you mess up something with Vue’s reflectivity and something is being called recurrently.

The solution

Do not expose the BabylonJS objects and send always a copy of your objects in your methods or just simply use JSON for your data.

Choosing JSON?

So do you have to JSON.stringify and JSON.parse every piece of data you are passing between Vue and BabylonJS? Yes, but we can write a class which will help us to do so with minimal effort.

Having everything in JSON opens up new possibilites, so we can leverage a messaging bus for easy data passing between the two frameworks.

The idea behind messaging

We don’t want our Vue code to know about BabylonJS implementation details, we want methods, we can call, which will ensure the required tasks to be done. Let’s jump to the example project, it will be easier to follow how the data is passed and received.

The Marble example

This examples uses Mitt bus (GitHub - developit/mitt: 🥊 Tiny 200 byte functional event emitter / pubsub.), but you can use any messaging bus. In Vue2 you can use new Vue() to create a bus.

image

assets - you all know
bus - the Mitt bus wrappers
components - our Vue component which displays the BabylonJS scene
director - our layer between Vue and BabylonJS
scenes - our BabylonJS scene
utils - some utility methods

Bus
Now that we can use messaging to communicate between Vue and BabylonJS, how about to have this communication async so for example the method called by Vue can await a method, which runs on BabylonJS code, so simply we create an async wrapper around the synchronous bus.

Let’s introduce an interface for our message bus. I will show you only the AsyncBus implementation. The synchronous bus is pretty much the same. This interface must be implemented by our bus.

as seen in AsyncBus.ts
image

AsyncBus is just a facade and uses the Mitt bus under the hood, but adds asynchronousity to our messaging.

The Scene Director

The Scene Director is a simple method-call-to-message converter, so your Vue code calls code on the SceneDirector which creates message(s) and sends it(them) using our AsyncBus and as far as our BabylonJS scene is interested in a message, (it is subscribed to process a particular message, basically at low level this is calling Mitt.$on(messageType, callback), it gets executed. When the execution finished the BabylonJS scene have to notiy the Scene Director, that it has finished execution. The Scene Director awaits for a specific message type SceneDirectorEventBusMessages.SceneDirectorCommandFinished with some information about the executed command. Don’t worry there are helpers methods and the usage is very easy.

Let’s jump to Vue!

Vue page

This example uses App.vue for the whole UI, but you should not put everything here and it is a good idea to have a router at hand and of course use pages/layouts/views/components for better modularity of your project.

First of all you have to import our SceneDirector class and create an instance so you can call it’s methods.

image

The example application comes with three methods. As you can see, all methods are async. I marked some void, because I just don’t want to await methods returning void for now. However the getMeshnames method has a return type of string[] and I am interested in the result, so I must use await here.

Scene Director methods
Ok, so let’s se our method implementation in the Scene Director.

All we do here is calling a helper method called asyncCommand

where we need to specify the message type and if we have something to send, the payload.

Message types
We have to specify, what messages are we going to send throught our bus, so we have this:

image

One can have these values hardcoded directly in the SceneDirector, but we didn’t start to code yesterday, so we know, that it is not right! :smiley: So we have two nice enums for this reason. :vulcan_salute:

There are two types of messages, just for better readibility, you can put them under one enum if you like so. SceneDirectorEventBusMessages, these are sent by Vue towards BabylonJS and obviosly the second one is moving from BabylonJS towards Vue.

It is a good idea not to create a message type for every single action, for example you are not going to create a message LookLeft and a LookRight, but you will create a message LookAt and call it with a parameter, however in your SceneDirector you can have two separate methods, so Vue just calls LookLeft or LookRight and the SceneDirector send a message LookAt with a parameter { rot: - Math.PI / 2 } or { rot: Math.PI / 2 } which will set the cameras alpha for example.

BabylonJS scene

You simply register your message subscriptions by modifying this method:


So it maps message types to functions.

Let’s have a look at the functions:
addMarble adds a new marble (maybe atoms should be a better name, just look at the page screenshot below)
image
As seen on the screenshot above, every mapped method receives a command. The payload stuff has to be clear for all of you, if not, you can access the payload sent by the SceneDirector here, in our case the name of the marble.

addMarbleByName just does this:

The very important thing here is to call this.commandFinished(sceneDirectorCommand) after you method has finished. If you started an animation and want to wait for it, no problem, just call this.commandFinished(sceneDirectorCommand) in your animation end callback.

If you want to send a message towards Vue, you can use

where this.emitCommand is just a helper method

image

and don’t forget to register your message in SceneDirector (MySceneDirector in our example)

Unregistering events is a must also :slight_smile: Just take your time :slight_smile:

Vue ref
In App.vue we can use some data from the SceneDirector


what is a simple ref:
image
and whenever a MarbleSelected message arrives we just set the ref’s value
image

I am a fan of loosely connected architecture, so I would rather use a callback instead of ref so I don’t have to reference any Vue object in SceneDirector. Just imagine you could change Vue (don’t do it) for any other framework and you don’t have to rewrite any BJS related method. The Vue ref is used here just as an example of how to use refs.

The example app

The app creates 40 marbles on startup. You can add a marble by entering it’s name to the text input and hit Add marble. Remove marbles will remove some of the marbles by each click. The last button will query the scene for all the meshe names on the scene and will print it out to the console.The methods are described above in the Vue page section.

So what?
So, if you are interested in this, you should take a look at the source code, how things works (hopefully) :smiley: Open the developer console and watch how the messaging works (again hopefully), try to add a new message, etc.

WebWorkers
So we have a message drive scene?! You can easily move your BabylonJS scene to a WebWorker!! Or you can control your scene by external messages, for example a light sensors can deliver messages for controlling light.intensity on BabylonJS lights…

Guys I put this together very quickly, there are things I would like to have done another way, I know it’s far from perfect so constructive criticism is very welcome!

:vulcan_salute:

Thanks!

@Deltakosh Yo! I will add this to the Vue docs as well later

Demo app open the console to view the message flow
https://babylonjs.nascor.tech/scene-director/

R.

EDIT:

Example of a getMeshNames message flow from the SceneDirector to MarbleScene and back to SceneDirector and finaly gets console.logged in App.vue

4 Likes

Man! this is excellent!! thanks a million!

2 Likes