Issue with using freezeActiveMeshes to improve game performance

Hello everyone, how are you? I hope you are all well! :blush:

I’m trying to improve the performance of the game I’m developing with Babylon (It’s using WebGL at the moment, not WebGPU), and I’m having some problems with freezeActiveMeshes(). The game uses Babylonjs 8.9.0.

I’m currently using the following techniques:

  1. All meshes are InstancedMeshes (from tiles to buildings). When I create a building, I create a new transformNode, then I get the meshes and create instances of them. This change had a good impact on performance, especially when I used it on tiles (around 2,700). However, I think I’ll use Thin Instances on tiles, since they are always on screen and only change the alpha value of the color (I haven’t seen if thin instances support alpha yet, but I’ll test it soon)

  2. All materials are frozen right after creation, since I don’t need to modify them. I created an alpha and a non-alpha version of each color (currently around 20 materials, but I intend to reduce this to just 2 materials with textures. All meshes share these materials; they are never cloned.

  3. All tiles have their world matrix frozen after creation. The world matrix of buildings is frozen as soon as the creation animations are executed.

  4. I also made several reductions in post-processing effects, such as reducing everything to 2 samples. I decreased the shadow resolution to 256 (with poisson sampling on). Currently the glowLayer is causing a negative impact of about 10/20 fps when active, but I have disabled it for now. My focus is to get as much FPS as possible so that, when I activate it, this drop is not so problematic.

  5. I enabled doNotSyncBoundingInfo for all meshes, since they are not needed in my game currently.

  6. I also set scene.autoClear = false and scene.blockMaterialDirtyMechanism = true

There are still some techniques that I want to apply, such as reducing the complexity of the meshes, reducing materials, changing tiles to thin instances, etc. I also tried switching to WebGPU but I didn’t see much difference and I still don’t have enough experience using it, although I want to test it again soon in non-compatibility mode.

However, I noticed that when the city starts to grow (about 50 occupied tiles, for example), my FPS drops from 144 to 85, even if the scene is still and nothing is being updated (no mouse movement, no animations, no code running on update observer, etc).

I performed performance checks in the browser and eliminated everything that could be running and causing the FPS to drop at those times. As you can see below, only the BabylonJS render function is running per frame, and even so, the FPS dropped.

So I decided to test scene.freezeActiveMeshes, since from what I read in several places, this could free up a lot of CPU load. And really, the result is surprising. I gained around 20/30 FPS (it stays around 110/115) even with the city having around 50 buildings or more.

However, when using freezeActiveMeshes I have had several unwanted visual artifacts. Even though I mark all meshes and instances as alwaysSelectAsActiveMesh, there are always times when a mesh simply does not appear, for example when it is created by hovering over a tile, and then, if I hover over the same tile, it appears.

So I modified it to use scene.unfreezeActiveMeshes() before creating new mesh instances, and then freeze them again. Basically, I create all the base meshes, mark them as alwaysSelectAsActiveMesh = true, and use freezeActiveMeshes. Then, when I need to create a building preview or a building, I call unfreezeActiveMeshes, create the nodes and instances, mark alwaysSelectAsActiveMesh = true, and then call freezeActiveMeshes.

It worked in a way, but now it creates some strange artifacts, like ghosting of the buildings in some situations, wireframe mode, etc.


I have not been able to identify what is causing what may be happening here. This seems like an excellent technique for reducing FPS drops, but I think I’m missing something about how to use it correctly.

Could someone with experience in these techniques please give me some guidance? What other techniques would make sense to use in a game like this?

I thank you in advance for your attention. Have a great day!

NOTE: You may have noticed that the FPS drops a lot when I move the mouse. This is because I perform some complex calculations when the mouse passes over a tile, and I haven’t optimized that part yet. But I did several checks to make sure that nothing that happens when moving the mouse is harming the Garbage Collector and causing problems when the scene is static. In fact, even right after the biggest city is loaded, the FPS rate drops a lot, even with the mouse outside the tiles and nothing being executed (just the BabylonJS rendering code).

1 Like

I’m not using freezeActiveMeshes so I can’t comment on that. But my method for creating mesh previews is dirt simple. I have 2 types of meshes, clones and thin instances. And its fast enough for both.

pseudocode

a) get master mesh
b) clone it and its skeleton, give it a unique id (which is used for disposal)
c) create material for it
d) set its animation
e) reduce visibility. DONE!

Use the gpu picker, its faster.

I have both mesh previews and the gpu picker live at Seedborn. Its a testbed, feel free to break it. Oh btw, I love your visual style! I can see that you are moving into the nitty gritty stuff. Keep it up, because its where things start to get really grueling. What I did with Seedborn was to develop every piece of code in the PG first, keep a copy, this ensures a rock-solid fallback, then port the codes to my dev, test, test and more tests! Iterate for a few hundred cycles and your confidence and speed will go up. tldr: develop in PG then port over. If it doesn’t work in PG, it won’t work on your dev.

Hope it helps, cheers!

3 Likes

Can only offer help for trouble shooting :frowning:

I would walk backwards: keep the freeze/unfreeze logic but, one-by-one, remove the other optimisations like doNotSync, freezeWorlMatrix, etc.

I am just guessing but the thing with appearing and disappearing meshes could sound like some culling issue. Maybe there is a bad combination of optimsations?

But at any rate, you can then at least pinpoint the error source.

Thinking about this some more: I wonder how these optimsations relate to instances. You do apply the mesh-based optimisation on the master mesh and then you call mesh.createInstance, don’t you? So maybe try cloning the meshes instead and see if that helps?

lol, well said and agreed :laughing:

1 Like

@phaselock @Joe_Kerr, thank you very much for answering me here. I’m back to the game code now, and I’ll try your advice. I hope to get back here soon with improvements or more info. Have a nice weekend!!

I did some testing here and discovered some interesting things:

  1. The wireframe issue (which was actually the edge renderer I use for meshes) is not a bug. It only occurs when Chrome restores a browser tab (for example, if I close the laptop, and open it again). It is not related to the use of the freezeActiveMesh feature

  2. The problem of showing a ghost building exists, but it only occurs once when I make the first mouse movement. In the video I recorded, it occurs several times for the exact same reason as the previous bug: when Chrome reactivates the tab that was hibernating, the game starts to behave strangely, but it doesn’t seem to be related to Babylon.

However, from what I’ve seen (I disabled practically everything, and left only freezeActiveMeshes()), what happens is that:

  1. I have some base meshes that I create and then use setEnabled(false) so that they are not visible.

  2. When creating a preview of a building, I obtain one of these meshes and create an instance.

  3. At that moment, for a tiny slice of time, even though my base mesh is invisible, it is shown for a fraction of time, as you can see in the video below:

I was sure that the problem is in the meshBase, because if I change its position, the ghost was created in the incorrect position. I tried a simple fix, which is to move this base mesh to a position outside the camera, like position.y = 1_000_000

This solves the slight flickering of the ghost base mesh, but it creates another problem: now the previews are not rendered correctly, only the edges are rendered, without the faces.

However, after some more tests, I believe I found the issue. The base mesh material has alpha. If I remove the alpha, the bug simply doesn’t occur. And I believe that removing the base mesh from the camera causes the problem I just reported, maybe also because of the alpha.

My guess is that when you create an instance of a mesh that has alpha, that object will flicker for a fraction of a second, something that doesn’t happen if the material doesn’t have alpha (I’m using StandardMaterial with diffuseColor, emissiveColor and alpha = 0.4):

for (const colorName in this.colors) {
    const material = new StandardMaterial(`${colorName}`, this.getScene())
    material.diffuseColor = this.colors[colorName]

    if (colorName === "white") {
        material.emissiveColor = this.colors[colorName].scale(this.whiteEmissiveScale)
    } else {
        material.emissiveColor = this.colors[colorName].scale(this.coloredEmissiveScale)
    }

    material.specularColor = new Color3(0.2, 0.2, 0.2)

    material.freeze()

    const alphaMaterialName = `${colorName}Alpha`
    this.materials[colorName] = material
    this.materials[alphaMaterialName] = material.clone(alphaMaterialName)
    this.materials[alphaMaterialName].alpha = 0.4
}

Anyway, I’ll soon try a simple workaround: as soon as the scene loads, I create an extra instance for each base mesh since the problem seems to occur only when the first instance is created.

As soon as I have more information, I’ll bring it here.

By the way, good news: the use of freezeActiveMeshes is awesome, the FPS is now at 144 even with the city with 50 buildings (I had forgotten to put this command in another place, and now it’s really good!)

Thank you very much for your help!

1 Like

I need to try this. I searched my project for freezeActiveMeshes in case I could help you and was surprised to find no reference, I wonder if I’ve tried it and run into visual artifacts too..

1 Like

Today I realized that the Vignette effect doesn’t seem to work very well with freezeActiveMeshes either. This isn’t really a big problem, since I believe the Vignette effect can be achieved by using a transparent image over the canvas.

Anyway, since I’m just doing the bare minimum to be able to record the trailer for now, I decided on another approach. I turned off freezeActiveMeshes, opened all the meshes in Blender, and optimized them as much as possible, both by reducing the number of polygons and by joining parts that were separated unnecessarily (for example, all parts that use the same color or that move together).

With this, I managed to keep close to 144fps, and a great reduction in draw calls.

I still plan to return to freeActiveMeshes in the future, as it seems like too good an optimization not to use, I just need to figure out how to work around these small visual glitches that occur (it seems that the vast majority of the problems occur with transparency, or because of something I’m using in the scene composition), but some workarounds should solve it (like using an image instead of a vignette, etc).

I also plan to reproduce the problems I found in a Playground if possible.

Another little tidbit. I don’t know if it’s just an observational bias, but I kept alwaysSelectAsActiveMesh = true on all meshes, even without using freezeActiveMeshes. This seems to have a positive impact on performance.

I believe, I’m not sure, that since the game is very light on the GPU, there’s no problem in increasing the draw calls a little while giving up Frustrum Clipping, since it’s the CPU that’s struggling the most to do its job (and by default, due to the game type, a huge quantity of meshes will be always inside the camera frustrum).

But I’m not sure if I’m right, I hope someone with more experience can clarify this doubt.

OK, my bad! For some reason I wrote:

this.scene.freeActiveMeshes()

instead of:

this.scene.freezeActiveMeshes()

When I created a building. No error occurred because this method actually exists in BabylonJS. Apparently this was what was causing the problem with Vignette.

However, this did not solve the problem of generating a ghost mesh when generating the first instance.

So I decided to test what I mentioned yesterday: as soon as I set up my scene, I create a cube and then an instance of it, and disable both. This solves the ghost building glitch :grin: :blush:

const cube = MeshBuilder.CreateBox("cube", { size: 1 }, this.scene)
const cubeInstance = cube.createInstance("cubeInstance")
cube.isVisible = false
cubeInstance.isVisible = false

We can see only a minor issue with shadows, but I’m sure this can also be solved.

So now it seems I have the best of both worlds: freezeActiveMeshes active, optimizations in the meshes, CPU and GPU.

There’s processing left over, and even the FPS drops when I move the mouse are no longer so annoying, which is enough to record the game trailer (the main problem I had was a huge FPS drop during recording for the trailer)

EDIT: the shadow issue was solved by delaying the freezeActiveMeshes execution to the next main thread loop (forcing it to be async):

setTimeout(() => {
    this.scene.getScene().freezeActiveMeshes()
}, 0)
3 Likes