Hi everyone,
In our project, we display a large number of meshes and their associated textures. We’ve noticed that sometimes our app fails to load on smartphone browsers due to high memory requirements. I am wondering if someone has advices, tips, pointers on how to reduce memory consumption in BabylonJS apps.
I’m particularly interested in understanding whether lights and advanced material properties contribute significantly to memory consumption.
Thank you,
David
The more shader variation, the more memory it will use but not in the proportion of highres textures. Standard tricks are to use lower texture resolution, use texture compression. use instancing instead of individual meshes (where possible).
Hey!
I was solving this issue nowadays. Our app was crashing on non-Pro iPhones and mid-level Android devices. The FPS was around 14-16 on an iPhone 10 (the model we choose as the bottom line device the app has to run on). After I did some optimizations on the model/code the app runs on nearly all devices we could put our hands on (except some really old androids) and the FPS is in range of incredible 45-50 FPS on the iPhone 10.
DO THIS FIRST!
Change the powerPreference
to default
. BabylonJS sets it to high-performance
by default. Especially iOS is quite unforgiving when set to high-performance
and Safari immediatelly kills your app if it detects high battery usage, so do not force high-performace
mode by default but go with default
which gives the browser the permission to set it’s own rules how to handle the webgl stuff in your app.
createEngine(canvas: HTMLCanvasElement): Engine {
const engine = new Engine(
canvas,
true,
{
powerPreference: 'default',
},
false
)
return engine
}
Just this one step pushed the app compatibility towards the Low profile devices and the app was not crashing so often as it used to.
Profiles
First of all I decided to create 3 groups of devices we gonna support or let’s call them profiles instead of groups.
The Low profile includes the older non Pro iPhones and their Android equivalents.
The Mid profile includes Pro iPhones and their Android equivalents.
The High profile includes Desktop and high range phones like the iPhone 15 Pro.
Packing
I realized that the first bottleneck seems to be always the textures when it comes to memory consumption as @cedric suggested. Put all your textures into the glb (I was loading some textures dynamically by code) and PACK the glb file. This already helped a lot to reduce memory footprint.
This is how I pack the glbs: GLTF/GLB texture and compression support in babylon.js - #2 by roland The FPS was poor but it was running on even more devices!
Summoning @labris for more elaborated information. He is the king of packing on the forum
Texture resolution
We create our assets always for the High profile devices.
We use Blender which has Python scripting support so you can do pretty nice things before you export the glb. Basically we have different resulting glbs for different device profiles. Downscaled textures to 2k or 1k. The level of packing is also set differently for each of the profiles. I do not include lightmaps in the low profile glbs for example. So you are basically trading in graphics fidelity.
Start to test with 4k packed textures and go lower if the app is crashing until you find which texture resolution works for you.
Go low poly, use instances
Remove as many geometry as you can. You can use the packer to do lossy packing of the geometry too but it can create glitches. Sometimes it is not perfect just good enough but it will run even on your fridge
Another thing already mentioned by @cedric is instances. Use them wherever you can.
HTML layers vs FPS
If your app uses HTML UI, you render something above the webgl canvas, the GPU needs to compose the UI layers together with the webgl canvas. The more HTML layers the more GPU cycles your app needs. Our app uses React. We had like 40+ HTML layers on some screens. After rewiriting the UI we ended up just by 2! One is the webgl canvas and the second one is the HTML layer. You can achieve this by using React portals or VUE teleport. I’m not familiar with Angular or Svelte but I bet they have something similar.
Another thing you have to keep in mind is to optimize your UI components rehydrations. A wrong component design can lead to a lot of unnecessary rehydrations and redraws of the UI layers which triggers GPU composition every time.
This change ramped up the FPS quite a lot!
Use any tool available to debug
Connect your phone to your computer and use desktop browser tools to inspect what is happeninng on your phone. Try different browsers, different tools…
I used Safari to debug issues on iOS a lot. If you gonna connect your phone to you computer don’t forget to use a datacable not a simple charging cable. Enable the Web inspector in Safari on your phone. Turn off Wifi/Bluetooth on your phone before starting to debug.
All about performance profiling in Chrome:
https://developer.chrome.com/docs/devtools/performance/overview
FPS in general
Use everything you can:
https://doc.babylonjs.com/features/featuresDeepDive/scene/optimize_your_scene
On demand rendering
Power consumption is another concern for a mobile app developer. If your scene becomes static, when nothing moves/changes on the scene you stop the rendering loop.
A simple event handler on the canvas can trigger starting the render loop again. For example when the users clicks on something, moves the mouse, etc… You can start the render loop manually when you know that something needs to move on your scene. For example a navigation agent was added and started to move towards it’s destination point. When your agent reaches the destination you can stop the rendering loop again. Etc… babylonjs has a lot of observables to use in scenarios like this.
Misc
You can crash Safari on iOS by loading too much assets at a time. I had a hardtime to load 8 textures at once. 8 subsequent calls to new Texture(url)
crashed the app on iOS. If you need to load textures this way do it in batches of 3-4 for example.
This approach applies to desktop first apps as well so keep optimizing!!
Great, you taught us optimization methods in great detail. Too many people need this
I agree with @brightMoon
You should copy and paste you post in a documentation page. I’ll be happy to review a PR
I second that!
@roland HUGE thank you for this info. Super valuable! Your post saved hours of my work.
I’ll be AFK for a week. I’ll gladly contribute this to the docs when I return from my vacation.
You’re welcome!
I envy you so much for taking a vacation. This program optimization should have appeared in the documentation a long time ago, so we can save more time
It couldn’t come earlier because all the information comes from my own experience, experiments, 50+ test playgrounds, trials and errors I made in the last 4 weeks. 16+ hours daily including weekends
Great, you are the strongest
Thanks but I am not. There was a strict deadline I had agreed on and didn’t expect so many obstructions to overcome. You really don’t have anything to envy sometimes we coders have to work like crazy night and day and sometimes we can enjoy the loose project plans:crossed_fingers:
Maybe it would be cool to put all my testing PGs together and create a testing app which could if not exactly but closely identify why is the BabylonJS app crashing on a given platform.
good, I am looking forward to it
Should we revert the default power pref in Babylon ?
As MDN says:
"high-performance"
, which provides a hint to prioritize performance over power consumption.You are encouraged to only specify this value if absolutely necessary,
since it may significantly decrease battery life on portable devices. It may also result in increased
GPUDevice
loss — the system will sometimes elect to switch to a lower-power adapter to save power.
I don’t know what impact would reverting the power pref to default have in existing apps, mainly in desktop apps. My experience is that on mobile devices I couldn’t really move on with the high-performance
setting unless the app was running on a high end Android or Apple Pro device.
What I definitelly know we need to create a Mobile first approach section in the docs and put all of this + future knowledge about developing babylon.js apps on mobiles.
Another consideration;
From what I know, the power preference can not be changed in the same context.
Will it be possible to implement the feature for Babylonjs to recreate the context and change the preference afterward? How long would the recreation take? Will it flicker?
If possible, I suggest that maybe make default scene optimizer to handle them to resolve:
We could do some tests what really happens when the Android/iOS device “reloads” the app due to the high-perfromance
power preference setting. If it’s a full page reload, we are done. If we can somehow catch the event and react to it we could lower the power preference setting.
I think the “reload” and crash afterwards occurs due to something like a low-level unhandled exception which we simply can’t handle in babylon.js.
Just imagine lowering the power pref could suddenly degrade the performance of desktop apps just because it will always favor the interated GPU when not on AC even though before the change the apps were running maybe 10x faster because regardless the device was powered from battery or not, it always ran on the discrete GPU - I’m pretty sure we could make some developers a bit angry