HiDPI + fractional scaling: performance pitfalls and best practices?

Hi everyone,

I’ve been investigating how Three.js behaves on HiDPI monitors when fractional UI scaling (like 125%, 150%, 175%) is enabled from some operating system settings (MacOS and Linux Mint Cinnamon in my tests).
I noticed that the common pattern used in the official examples:

renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);

can sometimes create a render buffer much larger than the physical screen resolution.

This leads to higher GPU usage and lower FPS. I wrote a detailed post here: Three.js and HiDPI

I’m wondering:

  1. Do you use any strategies to cap or adjust resolution (e.g. limiting pixel count, custom resize functions)?

  2. Would it make sense to add a note in https://threejs.org/manual/?q=hidpi#en/responsive#handling-hd-dpi-displays about this pitfall and possible workarounds? I’d gladly make a PR

Hmm.. my understanding is that the actual physical display size is width and height * devicePixelRatio. It’s just that mobile devices has low power GPUs driving much higher res displays. SO.. if you want truly crystal clear images on a plane for instance, you do want to use that full size display buffer, but if you’re pushing lots of triangles, (fillrate bound) you might default to a clamped/lower devicePixelRatio like 1.5 or smth.
Fractional ratios of course give you a slightly blurred image, but on a high dpi display that doesn’t always look bad.

edit: Just read your blog post.. looks like you cover all that pretty well, including the relationship to browser scale factor. I agree, it couldn’t hurt to add a pitfalls section to the threejs docs…

2 Likes

it couldn’t hurt to add a pitfalls section to the threejs docs…

That’s exactly what I would have liked to read in the doc.
I know it’s a very niche issue, since it only affects certain operating system configurations, but that’s also what makes it so difficult to debug.

That said, some of my colleagues use 4K monitors with fractional scaling, and I’ve always found it tricky to pin down the root cause of the performance drops.

1 Like

Effective dpi/ppi would be a great opt-in for a performanceMonitor. For compatibility reasons, getters/setters often use the reported system value. :nerd_face: We outsourced an audit at AABB Canvas Disposal for an undisclosed project. What if pixels aren’t square? What if RGB channels are unequal? We are the ones who push for progress!

This is an interesting write up - I had assumed that devicePixelRatio * dimension would always get you to physical pixel size of the screen but it seems that’s not the case. I’m on a Macbook with OS 15.6.1 and after changing some system display settings I’m unable to get the render buffer size to exceed the physical pixel units size. The “devicePixelRatio” is always either 1 or 2 (when connected to an external monitor) but it’s alway the case that devicePixelRatio * innerWidth is <= the real monitor pixel width, so there’s no performance issue.

It seems like this is specifically an artifact of how your linux distribution handles UI scaling? As in it’s always rounding the reported “pixel ratio” up to an integer even though the resolution has only been scaled by a fraction. I’m wondering how widespread this issue is or if it’s limited to non-standard OSs?

One maybe somewhat related issue I’ve noticed is that zooming the page in and out (cmd + +/-) can majorly impact performance because this changes the DPR and triggers a resize but three.js does not set the DPR in any of the resize callbacks. This leads to cases where the DPR was set to 2.0 on page load but once scaling, the window width is reported as 7200 so three.js starts rendering to a 14400 canvas. If the renderer correctly adjusted the DPR to 0.5 on resize, as well, then it would render to the appropriate 3600-pixel-width physical resolution.

3 Likes

Hi @gkjohnson with my Macbook Pro M4 14" with OSX 15 the issue shows up only when using an external 4K monitor.


Spreadsheet link

I guess Apple doesn’t allow to pick “scaled resolutions” (column D in the sheet) bigger than “as 1800x1169” for readability reasons. However I suspect that Apple and some Linux DE are using the same scaling approach so the issue can show up on Apple devices as well (EG: Mac Mini/Studio connected to an external 4K display).

Regarding zoom: yes, that is a quick way to make ThreeJS do intense supersampling… Unfortunately there is no API, as far as I know, to get the real monitor physical size so the only solution that I found is to clamp the pixel count to a value below 4K.

1 Like

OK, I opened this PR: Manual: add a section about limiting internal resolution to avoid performance issues by davcri · Pull Request #31942 · mrdoob/three.js · GitHub

Also I noticed that the examples zoom issue is probably related to the missing call to setPixelRatio in the examples resize logic: Examples rendering resolution grows when lowering browser zoom below 100% · Issue #31943 · mrdoob/three.js · GitHub