Skip to content

Pixel-sized canvases: dpiscale 1 on every framebuffer allocation - #52

Open
mresnick67 wants to merge 2 commits into
DramaticShape:masterfrom
mresnick67:pr/dpiscale-pixel-buffers
Open

Pixel-sized canvases: dpiscale 1 on every framebuffer allocation#52
mresnick67 wants to merge 2 commits into
DramaticShape:masterfrom
mresnick67:pr/dpiscale-pixel-buffers

Conversation

@mresnick67

@mresnick67 mresnick67 commented Aug 1, 2026

Copy link
Copy Markdown

The bug

love.graphics.newCanvas(w, h) defaults the canvas's dpiscale to the display's, and every width/height handed to the mod's framebuffer allocations is already in pixels (getPixelDimensions, or sizes taken off such a canvas). On a highdpi phone — the engine's conf.lua turns highdpi on for both iOS and Android — each buffer therefore comes out density× larger per axis than asked for.

At a 3× display that means:

  • the "window-sized" scene canvas is 9× the screen's pixels (7866×3618 on an iPhone 16 Pro), fully rendered — geometry, sun shadows, water — then nearest-downsampled to a ninth of that at the composite blit;
  • the water's mirror copy, both tilt-shift buffers, both DOF buffers and the HUD frost all pay the same multiplier;
  • the shadow map asked for 1024 and stored 3072×3072 (with the main pass's sunTexel = 1/res assuming 1024).

TerrainAtlas and BattlePics already pass dpiscale = 1 for exactly this reason (their comments describe the trap); this PR brings the remaining nine allocation sites under the same rule — including 1.4.1's new AntiAlias fold target, which sizes its destination in display pixels on the same default, so on a phone the supersample multiplied on top of the density multiplier.

Why nothing visible changes

Every caller works in the canvas's nominal units, which are what getDimensions() reports either way — so no coordinate anywhere moves. On desktops the display dpiscale is 1 and the change is a no-op by construction. On phones the buffers land at the size the code always believed they were, and the screen can't show the discarded pixels — the composite was already throwing 8/9 of them away.

Effect

Tested on an iPhone 16 Pro (device) and the iOS Simulator: pixel-identical frames, and the device goes from visibly heating within minutes of free-roam voxel mode to staying comfortable — this one change is most of that difference. Android runs the same highdpi path (density ~2.6–3), so it should see the same win; I'd welcome a confirmation from someone with a device.

I have a couple of follow-ups sketched for phone-tier tuning (an internal render-scale option, mobile-flavored defaults) but kept this PR to the pure bug fix — happy to open an issue to discuss those separately.

🤖 Generated with Claude Code

Myles Resnick and others added 2 commits August 1, 2026 17:02
newCanvas defaults a canvas's dpiscale to the display's, and every
width/height handed to these eight allocations is already in PIXELS --
so on a highdpi phone (iOS and Android both run with highdpi on, see
the engine's conf.lua) each framebuffer came out density times larger
per axis than asked for. At a 3x display the window-sized scene canvas
is NINE times the screen's pixels, rendered in full -- geometry, sun
shadows, water, the lot -- and then downsampled to a ninth of that at
the composite. The mirror the water reads, both tilt-shift buffers,
both DOF buffers, the HUD frost and the shadow map (1024 asked,
3072x3072 stored) all paid the same multiplier.

TerrainAtlas and BattlePics already pass dpiscale = 1 for exactly this
reason; this brings the remaining allocations under the same rule.
Nothing about coordinates changes -- every caller works in the canvas's
nominal units, which are what getDimensions() reports either way -- so
desktops (dpiscale 1) are bit-identical, and phones render the pixels
their screens can actually show. On an iPhone 16 Pro this is the
difference between the mod visibly heating the device and not.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 1.4.1 AA pass folds its supersampled canvas down into a target sized
in display pixels, allocated on the same highdpi default as the rest --
so on a phone the fold's destination was itself density x too big per
axis, and the supersample multiplied on top of that. Same one-line rule
as the other eight sites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mresnick67

Copy link
Copy Markdown
Author

Pushed one more site: 1.4.1's new AntiAlias pass folds its supersampled canvas into a target sized in display pixels, allocated on the same highdpi default — so on a phone the fold's destination was itself density× too big per axis, with the supersample stacking on top. Same one-line rule as the rest; PR body updated to count nine sites.

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