Billboard UI with zoom based scaling (including retina screens)

Hi there,

My app was using the fullscreen mode of the GUI to display annotations in a 3D scene but linkWithMesh was causing heavy performance degradation on Safari and Firefox (all fine on Chrome). After a quick test I realised the billboard (texture) mode of the GUI was doing much better. So I tried to get a billboard annotation going that would adapt its scale to the zoom to get a bit of the fullscreen UI experience (consistent display size). The basics are simple but getting it right with retina displays, conserving aspect ratio, etc. has been a fight. I thought this might be useful to others so here goes a PG containing the gist of what I do:

For DPR:

const DPR = window.devicePixelRatio
engine.setHardwareScalingLevel(1 / DPR) // or set adaptToDeviceRatio to true in new Engine() call
// increase renderScale to avoid blur annotations (more antialiasing)
ui.renderScale = 2
// renderScale seems to screw the aspect ratio of the UI, fix it with 3 lines below
const sceneSize = Math.min(sceneWidth, sceneHeight)
ui.rootContainer.scaleX = sceneWidth / sceneSize
ui.rootContainer.scaleY = sceneHeight / sceneSize

The rest of the code in the playground is to resize the UI controls when the camera stops moving and zoom has changed (arc rotate camera).

One issue I’m still not able to fix is if you drop to WebGL 1 (or use Safari basically), renderScale = 2 seems to not be supported. Any idea why?

Edit: Another issue I just realised – the rescaling is super slow on Firefox (probably their canvas engine which is notoriously slow) and is blocking the interaction. This is particularly obvious on a scene with 10ish annotations, 2-3s to rescale. Any thoughts on getting around this?

Cross linking a post that was super useful to get this working: Proper way to support HDPI/Retina display at full resolution and scaled properly? - #6 by Deltakosh



No idea… @msDestiny14 thoughts?

Hi @msDestiny14, just checking if you had any idea why renderScale=2 seems unsupported in WebGL1? I’m really stuck with this one and no practical workaround.

Hi there! Looks like I missed this originally. Sorry about that… :frowning:

Can you post the repro the with lag so I can take a closer look. Initially I don’t see a reason why it would work on one over the other and I’m not quite seeing any issues on my end running on firefox.

No problem @msDestiny14 :slight_smile:

The main issue i’m facing that @DarraghBurke was pinging you for is the “renderScale=2” does not work with WebGL 1, the repro PG is at If you open in Safari or drop manually to WebGL 1, you’ll see the annotation turn into a giant black square.

I can try to create a repro for the slow Firefox part but that’s less of an issue for me. I’ll probably need to add 8-10 annotations. Maybe we can focus on the first issue for now?

1 Like

Yep. I’m seeing the problem, will put this on my task list tomorrow in the morning. :+1:

1 Like

Amazing! Thanks a bunch :grinning_face_with_smiling_eyes:

@msDestiny14 I also wanted to get back to you on the firefox lag. Here is a repro: where you can play with nbOfBillboards to see it more clearly depending on the computing power you have at hand. There is a clear difference between Chrome and Firefox on the time it takes for the labels to be resized. You can count the seconds between “camera stopped” being logged in the console and the annotations actually resizing.

There are dozens of reports on bugzilla on the slow canvas of firefox. I’m guessing it could be related but you might know better. More than improving this performance which I think is in the hands of the browser devs, any idea how I could get around the scene freezing while that’s being done?

Thanks again.

1 Like

Ok after a tiny bit of deliberation here is what I have learned :smiley:

The main issue with webgl1 is that does not support no power of 2 textures (I just learned this myself :open_mouth:) For more in depth understanding, by using renderScale, the user ask the system to set the texture to 2xscreen resolution but screen resolution will probably never be a power of 2. Instead to work around this just set the texture size manually…

const ui = BABYLON.GUI.AdvancedDynamicTexture.CreateForMesh(billboard, 680 * 2, 340 * 2, false);

As for the lag on the other browsers, I’m still not really seeing it. Maybe a single frame drop? BUT one solution you can try is setting the limitDeviceRation for engine options to 2 during the engine construction.

1 Like

Wow, that’s quite the discovery. I wonder how you figured this one out, kudos. :wink:

Thanks for the super clear explanation and workaround. I’ve posted an updated PG that uses the method you suggested, works like a charm – Only weird thing is the resolution of the texture was not multiplied by a factor of 2 to get the same visual size. It was 6 for webgl 2 and 8 for webgl 1, go figure :man_shrugging:. It works and that’s good enough for me at this point. Oh, and i also had to keep a square resolution to avoid deformations of the content.

TLDR of the changes for anyone who wonders:

const textureScale = engine.webGLVersion === 2 ? 6 : 8
const ui = BABYLON.GUI.AdvancedDynamicTexture.CreateForMesh(billboard, 680 * textureScale, 680 * textureScale, false)
// ui.rootContainer.scaleX = sceneWidth / sceneSize
// ui.rootContainer.scaleY = sceneHeight / sceneSize
// ui.renderScale = 2

I’ll look into the stuff you sent for the lag issue, haven’t had the time yet.

1 Like

@msDestiny14 I’ve posted an updated PG that mixes the new technique you suggested and the 25 billboards.

Here is the experience I’ve having:

  1. Chrome / Edge runs smooth
  2. Firefox takes about 10s to resize the annotations after zooming in or out
  3. Safari reloads the page on its own and complains it is using significant memory (mostly happens after zoom in/out but can also happen without doing anything)

Do you face similar issues?

The thing is that annotations are user generated in my app so there could potentially be dozens of them, that seem to equal dozens of 4096x4096 canvases in memory (the stuff safari complains about).

Somehow Safari didn’t complain previously (maybe it was small in memory and rendered at larger resolution with renderScale?). Somehow Chrome and Edge don’t bat an eyelid. As it comes to Firefox don’t get me started on their canvas engine :sob:. Btw, ff has a “meta” bug related to the canvas performance at 1560450 - (slow-canvas2d-software) [meta] Some apps are slower with software-only canvas2d in case that’s of interest.

Bottom line, any idea how i might mitigate the high memory due to large canvas textures? Is there any way to “freeze” them to save on calculations? Is there any way the rescaling can be done in a way that doesn’t block the interaction on the page?

Sorry for having so many questions on this :thinking:

Edit with new idea for webgl1: can we detect screen resolution and set renderScale to a number that will have the final rendering resolution be a power of 2?

a dozen 4096 squared texture is a lot !!! this would kill a lot of hardware as each time you need to rerender them you would need to transfer at least in a frame about 4096 * 4096 * 4 * 12 which is about a Gb of memory to the gpu to reset the texture content

1 Like

That’s a great point @sebavan. I’ve done a round of shaving off the extra pixels and am now around 2048*512. Even at 25 billboards, it’s running quite ok on all browsers. Updated PG:

Basically I’ve introduced a max scale so I can predictably know the maximum size of the annotations even if the user zooms out a lot (it’s better UX anyway, you don’t want 10ish huge annotations on a tiny scene seen from far). This has allowed me to reduce the size of the billboards while still ensuring good resolution. Then I used the plane builder to create non square planes and textures. Since my annotations are long but not high, I might as well remove the unnecessary height.

It starts to be good. Even the scale updates on Firefox are now bearable. :slight_smile:

1 Like