Skip to content

fix(react-ecs): stop dividing UI layout by devicePixelRatio - #1515

Closed
leanmendoza wants to merge 1 commit into
mainfrom
fix/remove-device-pixel-ratio-from-ui-layout
Closed

fix(react-ecs): stop dividing UI layout by devicePixelRatio#1515
leanmendoza wants to merge 1 commit into
mainfrom
fix/remove-device-pixel-ratio-from-ui-layout

Conversation

@leanmendoza

Copy link
Copy Markdown
Contributor

What

Removes devicePixelRatio from the two places where it participates in UI layout:

  • UiScaleSystemuiScaleFactor is now exactly the contain-fit of the design resolution inside the canvas.
  • scaleOnDim'Nvw' / 'Nvh' and string fontSize now resolve to N % of the canvas dimension, as they do in CSS. Note this path applies even when no virtual size is set.

The field stays on PBUiCanvasInformation and ScaleContext.ratio stays in the public API. Nothing about what renderers report changes, and no renderer change is required.

Why

devicePixelRatio answers "how many physical pixels does one canvas unit occupy?" — a density hint, for picking a 1x / 2x / 3x asset. Each renderer answers it differently:

Renderer How it computes the value What that gives
Bevy forwards the platform's real device pixel density — 2.0 on a Retina Mac, the OS display-scaling setting on Windows A true pixel density; the intended reading of the field.
Godot panel pixels ÷ its own normalized canvas Also answers the question, but against a canvas Godot defines itself. Self-consistent; its own definition.
Unity display resolution ÷ window size Unusual: the value moves when only the window moves, on an unchanged screen.

All three are defensible readings of a density hint. None of them is a layout unit — and dividing the contain-fit by it is what turns whichever one you get into one.

Measured with one scene and one build, an element declared width: 1600 against a 1600x720 design:

Target canvas dpr contain-fit the element covers
Bevy · Chrome on a Retina Mac 1512 × 823 2.0 0.945 50 %
Unity · windowed on a Mac 2244 × 1332 1.3476 1.4025 74 %
Godot · Galaxy A54 1600 × 738 1.4625 1.0000 68 %

The Unity row shows the divisor is not a density at all: the window was 2244 px wide on a 3024 px display, and 2244 / 3024 is the reported 1.3476. Scene UI ended up covering 74 % of its design — the same fraction of the monitor the window happened to occupy. Drag the window and the number moves.

The Godot row is the other end: because that client publishes a canvas normalized to 1600 wide, the contain-fit against a 1600x720 design is exactly 1.0000, so the entire 32 % shortfall is the divisor.

This also matches CSS and React Native, where the ratio is exposed but never enters layout (50vw is half the viewport, never divided by window.devicePixelRatio), and the protobuf, which already states: "the width of the canvas, in virtual pixels; this value does not change when the pixel ratio changes."

Impact

Every renderer is internally consistent — each reports the canvas in the same unit in which it interprets UiTransform px — so this fixes all of them at once with no client change.

Breaking for content calibrated against current behaviour, which will render devicePixelRatio times larger. Two things scope it:

  • Scenes that pass no virtual size keep uiScaleFactor === 1 and are unaffected.
  • It reduces the blast radius of the default-virtual-screen change in feat: remove dpr + ui default virtual screen + default screen inset area #1489 on mobile. With a 1600x720 default on the Godot mobile client, the contain-fit is 1.0000, so removing the divisor leaves uiScaleFactor at 1 — the "scenes not using any virtual screen at all" bucket stops being affected there.

Tests

  • virtual-scale-array.spec.tsx — the two tests that pinned the old behaviour now assert the new one, plus a regression guard that sweeps devicePixelRatio over [0, 1, 2, 3.5] and requires an identical layout. 0 is kept in the sweep because it used to be a divide-by-zero guard, and there must no longer be a division to guard.
  • label.spec.tsxscaleFontSize expectations updated ('10vw' on an 800-wide context is now 10 % of 800), plus a case asserting the result does not depend on ctx.ratio.

npx jest test/react-ecs — 120 passed. No change to the API report.

`uiScaleFactor` is now exactly the contain-fit of the design resolution
inside the canvas, and `scaleOnDim` resolves 'Nvw'/'Nvh' and string
`fontSize` to N% of the canvas dimension, as in CSS.

devicePixelRatio is a density hint for picking a 1x/2x/3x asset, and each
renderer computes it differently, so dividing by it made UI size inversely
proportional to whichever value the scene happened to get. The field stays
on PBUiCanvasInformation and `ScaleContext.ratio` stays in the public API.
No renderer change is required.

BREAKING CHANGE: scenes that set a virtual size and were calibrated against
the current behaviour will render devicePixelRatio times larger. Scenes that
pass no virtual size keep uiScaleFactor === 1 and are unaffected.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying js-sdk-toolchain with  Cloudflare Pages  Cloudflare Pages

Latest commit: 2995d67
Status: ✅  Deploy successful!
Preview URL: https://1bc39cdd.js-sdk-toolchain.pages.dev
Branch Preview URL: https://fix-remove-device-pixel-rati.js-sdk-toolchain.pages.dev

View logs

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Test this pull request

  • The @dcl/sdk package can be tested in scenes by running

    npm install "https://sdk-team-cdn.decentraland.org/@dcl/js-sdk-toolchain/branch/fix/remove-device-pixel-ratio-from-ui-layout/dcl-sdk-7.25.1-30843331887.commit-d3fd410.tgz"
  • The @dcl/js-runtime package can be tested in scenes by running

    npm install "https://sdk-team-cdn.decentraland.org/@dcl/js-sdk-toolchain/branch/fix/remove-device-pixel-ratio-from-ui-layout/@dcl/js-runtime/dcl-js-runtime-7.25.1-30843331887.commit-d3fd410.tgz"
  • To test with npx init

    export SDK_COMMANDS="https://sdk-team-cdn.decentraland.org/@dcl/js-sdk-toolchain/branch/fix/remove-device-pixel-ratio-from-ui-layout/dcl-sdk-commands-7.25.1-30843331887.commit-d3fd410.tgz"
    npx $SDK_COMMANDS init
  • The /changerealm command to test test in-world

    /changerealm https://sdk-team-cdn.decentraland.org/ipfs/fix/remove-device-pixel-ratio-from-ui-layout-e2e
    
  • You can preview this build entering:
    https://playground.decentraland.org/?sdk-branch=fix/remove-device-pixel-ratio-from-ui-layout

@leanmendoza leanmendoza closed this Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant