Higher UI resolution with lower game resolution

Hello,
We are trying to render crips UI texts on mobile Hires devices like latest iPhones. Now we are using the engine.setHardwareScalingLevel(1 / window.devicePixelRatio); which makes UI perfect, but of course increasing (tripling) overal resolution of the game. For UI this is exactly what I want, but I want to keep UI texture (and final frame buffer) in high resolution, but rendering game scene in half of resolution to reduce performance impact of this change.Is there any way how to “grab” cameras render texture (without UI) and set something like renderScale = 0.5; to render game scene in half of canvas resolution yet still keeping UI texture in full res? To sum this up:

  • Canvas - native full resolution like 2556x1179 pixels
  • UI texture - native full resolution same as canvas, in order to have nice text rendering
  • Game/Scene texture - half of resolution, or third, in order to save perf

Or is there any other way to achieve nice text GUI rendering and lower resolution game rendering? Because if we do not change the hw scaling level - the texts are not very readable, as the game uses a lot of text and sometimes with a bit smaller font size.
Thanks for any advices :blush:

Hi. Maybe simple try gui ADT renderScale?

By default the ratio between rendering resolution and texture size is 1. But you can force it to different values with advancedTexture.renderScale. This could be useful if you want crisper texts for instance.

Thanks for the reply. I was trying this before I posted but it was not the result for mobiles I was looking for: SCALED IMAGES - Screenshots are from Iphone.

Maybe it can do what I want and I have some other option set up wrongly, but just changing renderScale is not helping. :frowning:

Besides using the renderScale, are you also scaling the entire GUI texture along with it?

No I don’t think so.

I create advancedTexture as this:

advancedTexture = AdvancedDynamicTexture.CreateFullscreenUI("UI", true);

advancedTexture.idealWidth = 3008;
advancedTexture.idealHeight  = 1390; // If both are set, the idealWidth will be used.

and then I have this for auto resizing:

window.addEventListener("resize", () => this.engine.resize());

But I don’t do any scaling or editing of size or anything like that. :thinking:

Instead of using idealWidth/idealHeight, can you try doing:

advancedTexture.renderScale = window.devicePixelRatio;
advancedTexture.rootContainer.scaleX = advancedTexture.rootContainer.scaleY = window.devicePixelRatio;

and see if the results look better?

I tried commenting idealWidth and idealHeight out and adding those lines. But then the game both on computer and mobile is very zoomed in.

I store my UI in .json files and load them up and parse them. They are created in Container - Rectangle in GUI editor on the resolution I set for the ideal size so everything is always perfectly position and resized in our given aspect ration we set. We want everything always be position in that ration and on the background is just solid color that is on 100% sizing.

It stopped resizing and is always in the 3008:1390 size and only crop the rest of UI out and not resize.

Aaaaah, ok, I understand your situation better now, thanks.
The thing is, both the renderScale and idealWidth/idealHeight influence on the sizing, but in different ways. Setting the renderScale multiplies the size of the GUI texture: Babylon.js/packages/dev/gui/src/2D/advancedDynamicTexture.ts at master · BabylonJS/Babylon.js (github.com), while idealWidth/idealHeight multiply the size of every element defined in pixels based on the size of the texture (which might have been multiplied by renderScale): Babylon.js/packages/dev/gui/src/2D/valueAndUnit.ts at master · BabylonJS/Babylon.js (github.com). So in this case you wouldn’t need to change the scale of the root container, because idealWidth/idealHeight already takes care of preserving the scaling. You already did something like this, correct, just changing the renderScale and setting idealWidth/idealHeight, which is the third image from your screenshots?

I wonder if we’re missing something on the texture side instead, so I’ll ping @Evgeni_Popov

hi. i think there solution with renderScale and setHardwareScalingLevel

Now with
var dpr = window.devicePixelRatio;
var HardwareScalingLevel = 1/dpr;
var canvasSize = window.size * dpr;
var guiTextureSize = canvasSize * dpr;
—>>>
dpr = 2,
window.size = 512x512;
HardwareScalingLevel = 0.5,
canvasSize = 1024x1024px,
guiTextureSize = 2048x2048px

1 Like

Maybe you could create a PassPostProcess and use a ratio of 0.5, to generate the scene in a texture half the size of the canvas.

Something like:

On my computer, the rendering canvas is 2556x2272 because I set the hardware scaling level to 0.5:

But the texture in which the scene is rendered is 1278x1136 only because I use a ratio of 0.5 when creating the pass post process:

You can set pass.samples = 4; if you want to enable anti-aliasing.

@carolhmj
Yes the third image is idealWidth and idealHeight set and added renderScale

@kvasss I’m not sure what you mean by canvasSize and guiTextureSize.
I looked at the demo you linked and do you mean that cavasSize is the advancedTexture.rootContainer.scaleX ?

Anyway I tried to do something like this:

engine.setHardwareScalingLevel(1 / window.devicePixelRatio);
...

advancedTexture = AdvancedDynamicTexture.CreateFullscreenUI("UI", true);

advancedTexture.renderScale = window.devicePixelRatio;
advancedTexture.rootContainer.scaleX = window.devicePixelRatio/(1/window.devicePixelRatio); //(x/(1/x) is x^2
advancedTexture.rootContainer.scaleY = window.devicePixelRatio/(1/window.devicePixelRatio); //(x/(1/x) is x^2

//advancedTexture.rootContainer.scaleX = this.advancedTexture.rootContainer.scaleY = window.devicePixelRatio;

//I commented out also this part 
//advancedTexture.idealWidth = this.planningScreenAdvancedTexture.idealWidth = 3008;
//advancedTexture.idealHeight = this.planningScreenAdvancedTexture.idealHeight = 1390; // If both are set, the idealWidth will be used.

So I tried different combination of these setting and nothing is working right. Sometimes the UI is only scaled down in middle screen, but then they are too big. But on phone it mostly just results in black screen {maybe zoomed in too much not sure) even when I can see something on the pc.
So maybe if you help me with how to set canvasSize and guiTexureSize you talk about I can test it out :innocent: thanks so much for trying to help.

@Evgeni_Popov
Does not the PassPostProcess work after the scene is already rendered in hi-resolution and used as input for the PPP, and only downscaled to 0.5 ration? Which means - no performance gain as it still needs to render to full-size first and only downscaled at the end? Or am I missing somethings?

I do not know the BabylonJS codebase much, but if I’m not mistaken - I need to somehow change the camera’s camera.outputRenderTarget to be smaller, let’s say by the ration 0.5 which would “force” rendering to render in lower resolution - which would ended on mobile devices in performance gains - but NOT affect UI texture which should render in 1.0 full size when used native high resolution of target device

  1. Render game scene to render target in 0.5 ratio (saves performance)
  2. Render UI to texture in 1.0 ratio
  3. Combine these two to final render target texture (visible in canvas) which is in 1.0 ratio

The first camera post-process contains the target texture used to render the scene. So, if you use a ratio of 0.5 when creating this post-process, the texture will be created with half the dimensions of the canvas.

This is shown in my Spector screenshot above: the texture used to render the sphere/ground is half the size of the canvas.

I understand what you mean. It will get the game to be lower resolution with higher resolution of UI. But I think (maybe you have some information about how this works in Babylon) post process will shrink it after the process power has been used on the higher resolution so it will not upgrade FPS.

In generic concepts - Camera renders scene to buffer - render-target/color-buffer/frame-buffer - this render-target is used as “input” for Post Process effect, which reads every pixel of this framebuffer and does some magic, like converting the RGB values of every pixel to black and white. But this means, the initial render is already done in hi-res and only “rescaled” using Post Process to second render target (buffer) which is shown on screen. So technically its two-step process (depending on how many Post Processing Passes are used). Is that correct? I’m saying this because I tested this in our use-case:

  1. When game renders using native 1:1 resolution, for example the iPhone 12 Pro its 2532x1170 (setHardwareScalingLevel(1/window.devicePixelRatio) where window.devicePixelRatio = 3 on iPhone), the performance is bad - 15FPS for sake of discussion
  2. When game renders using setHardwareScalingLevel(1) which should be one third of resolution (if I’m not mistaken) like 844x390 and perf is 60FPS
  3. When game renders native 1:1 resolution and your proposed post process pass used - no effect on performance - 15FPS

Are you sure the “pass” post process is created before any other post processes for that camera?

You should look at scene.activeCamera._postProcesses[0] in a browser console and make sure that it is the “pass” post process.