Skip to content

Pin dpiscale on the mod's own render targets (voxel mode silently dead on high-DPI iOS) - #40

Open
hernan0078 wants to merge 1 commit into
DramaticShape:masterfrom
hernan0078:mobile-dpi-canvas-size
Open

Pin dpiscale on the mod's own render targets (voxel mode silently dead on high-DPI iOS)#40
hernan0078 wants to merge 1 commit into
DramaticShape:masterfrom
hernan0078:mobile-dpi-canvas-size

Conversation

@hernan0078

Copy link
Copy Markdown

The symptom

On iPadOS the mod loads, registers both pipelines, compiles both shader
variants and reports Voxel3D.available() == true — and the diorama never
appears. The VOXEL row, the mod manager and the hotkey all say the mode is
on. The world stays flat, with no error anywhere a player could see it.

The cause

sceneSize() asking for love.graphics.getPixelDimensions() is correct — the
engine composites a pipeline's canvas with draw(canvas, 0, 0, 0, 1/dpiX, 1/dpiY), so the canvas has to report the pixel size for that blit to cover
the window.

But love.graphics.newCanvas defaults dpiscale to getDPIScale(), so the
texture it allocates is that size multiplied by the DPI scale again. On an
iPad reporting 2, asking for the window's 2752x2064 pixels allocated
5504x4128 — past the driver's maximum texture size:

Cannot create texture: pixel width of 5504 is too large for this system.

newCanvas threw → beginScene returned false → drawWorld returned nil —
and returning nil is the 2D fallback, so the engine quietly kept drawing the
flat world. Desktop never sees this: dpiscale is 1 there.

The fix

Every render target in this mod is already counted in framebuffer pixels: the
scene canvas is getPixelDimensions(), the shadow map is a resolution rung
(the very texel count sunTexel is derived from), the blur pair is the size
of the image it was handed. All three now go through a small
lib/PixelCanvas.lua that pins dpiscale = 1.

getWidth()/getHeight() always reported the requested size, so nothing
downstream moves — no geometry, no UV, no compositing scale. It also squares
the FX overlay, whose projected coordinates are already in canvas pixels and
were being scaled by the DPI factor a second time.

Two smaller wins that come with it: the scene no longer renders at 4x the
pixels it needs before being downsampled, and the shadow map's 2x2 filter taps
land on the texels ShadowMap.res says they do.

Verification

Reproduced and fixed on an iPad Pro 13-inch (M5) simulator, iPadOS 27, DPI
scale 2, on top of master (7f76caa, v1.3.1). Before the change the log shows
the texture failure and drawWorld canvas=nil on every frame; after it,
drawWorld canvas=ok, and terrain, cast shadows, depth occlusion and the
tilt-shift pass all render — checked across the 15 / 35 / 50 / 75 rungs and
FULL.

tests/dramatic_shape_test.lua is unchanged by this: the same single
pre-existing failure (a scripted trainer goes through pushBattle) before and
after, on master with and without the patch.

Note on Android

Android was almost certainly hitting the same double-scale — a density of
2.625 asked for a texture ~2.6x too large in each direction. It stayed
invisible there only because those drivers permit the larger texture; the
scene was still being rendered at ~7x the pixels it needed.

sceneSize() asks for love.graphics.getPixelDimensions(), which is right --
the engine composites a pipeline's canvas with draw(canvas, 0, 0, 0, 1/dpiX,
1/dpiY), so the canvas has to REPORT the pixel size for that blit to cover
the window.

But love.graphics.newCanvas defaults `dpiscale` to getDPIScale(), so the
texture it allocates is that size times the DPI scale again. On an iPad
reporting 2, asking for the window's 2752x2064 pixels allocated 5504x4128 --
past the driver's maximum texture size:

  Cannot create texture: pixel width of 5504 is too large for this system

newCanvas threw, beginScene returned false, drawWorld returned nil -- and
returning nil IS the 2D fallback, so the engine quietly kept drawing the flat
world. Every switch said the mode was on (the VOXEL row, the mod manager, the
hotkey) and nothing ever appeared, with no error anywhere the player could
see. Desktop never sees it: dpiscale is 1 there.

Every render target in this mod is already counted in framebuffer pixels --
the scene canvas is getPixelDimensions(), the shadow map is a resolution rung
whose texel count sunTexel is derived from, the blur pair is the size of the
image it was handed -- so all three go through a small lib/PixelCanvas.lua
that pins dpiscale = 1.

getWidth()/getHeight() always reported the REQUESTED size, so nothing
downstream moves: no geometry, no UV, no compositing scale. It also squares
the FX overlay, whose projected coordinates are already in canvas pixels and
were being scaled by the DPI factor a second time.

Verified on an iPad Pro 13-inch (M5) simulator running iPadOS 27 at DPI scale
2, where the diorama had been silently falling back to the flat world:
terrain, cast shadows, depth occlusion and the tilt-shift pass all render,
across the 15/35/50/75 rungs and FULL. The mod's own suite is unchanged --
same single pre-existing failure before and after.
@hernan0078

Copy link
Copy Markdown
Author

Worth separating this from #34, since the titles look alike — this is the same fix, applied to the canvases that one did not reach.

#34 pinned {dpiscale = 1} on the two sprite/atlas canvases (BattlePics.lua:114, TerrainAtlas.lua:278). In 1.3.1 the 3D pass's own render targets are still created without it:

  • Voxel3D.lua:543pcall(love.graphics.newCanvas, w, h)
  • ShadowMap.lua:183pcall(love.graphics.newCanvas, res, res)
  • TiltShift.lua:88 and :90 — same

newCanvas defaults dpiscale to getDPIScale(), so on an iPhone (scale 3) each of those allocates at 3x the size asked for, and the composite blit then covers a fraction of the window. The result is that 3D mode reports available, switches on, and draws an empty frame — no error anywhere a player or a log would show it.

This PR routes those four through a small helper that pins the scale, so it is your #34 technique extended to the remaining targets rather than a different approach.

Tested on a physical iPhone 17 Pro (iOS 27) — before/after captures to follow in a reply. Glad to test anything you would rather write yourself; I have the device set up and can turn a build around quickly.

@hernan0078

Copy link
Copy Markdown
Author
Captura de pantalla 2026-07-31 a la(s) 7 14 42 p m IMG_2468

Captures from a physical iPhone 17 Pro (iOS 27) with this PR applied —
the mod running on iOS hardware:

  1. Overworld battle in FULL 3D: voxel terrain, cast shadows, depth
    occlusion.
  2. Overworld in TILTED.
    On stock 1.3.1 neither of these renders on the device — the mode reports
    on and the world stays flat, which is what the four unpinned canvases
    above account for.

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