Skip to content

Shadow map breaks under LÖVE 12 (any backend): near half of the light frustum missing, and the map read vertically mirrored #74

Description

@luisgonzaleznf

Summary

Under LÖVE 12 the sun pass stores a broken shadow map, producing large hard-edged dark regions detached from their casters. On iOS this is very visible: giant diagonal slabs across the overworld, and a lit parallelogram inside near-darkness in interiors.

This is not iOS-specific and not Metal-specific. It is a LÖVE 11 → 12 regression that iOS hit first because the iOS shell is the only place gen1recomp currently ships LÖVE 12. Desktop ships 11.5, which is why it never reproduced there — and why it is latent for desktop and Android the day those move to 12.

Controlled matrix

Same gamedir, same 430x932 portrait window, backend forced via t.renderers, everything else held still:

LÖVE backend ShadowMap map coverage shadow placement
11.5 OpenGL stock 1.5.1 full correct
12 nightly OpenGL stock 1.5.1 sparse wrong
12 nightly Metal stock 1.5.1 sparse wrong
12 nightly Metal z remap only full wrong
12 nightly Metal y probe only sparse correct
12 nightly OpenGL + Metal both full correct

The LÖVE 11.5 control ran with the same dpi-inflated canvases (px 4096 for a res 2048 rung) and stayed clean, so canvas dpiscale is not the cause of either artifact. It remains a real memory cost — see the PixelCanvas work in #48.

Cause

The sun pass bypasses transform_projection and builds its own clip-space matrices to LÖVE 11 conventions:

  • Mat4.ortho emits clip z in [-1, 1] — its own comment says "GL clip space (z in [-1, 1])"
  • fit() applies Mat4.scale(1, -1, 1) to compensate LÖVE 11's canvas-bound y-flip, commented "canvas coordinates run Y DOWN"

LÖVE 12 changed both, on every backend. love2d/love@7bfbd647 ("Make shader clip space consistent across APIs and projection matrices") states that projection matrices "no longer need to be flipped when rendering to a canvas versus the main screen", and closes with "custom projection matrices might need to be altered to account for the more consistent range".

The two symptoms map onto the two conventions, and the isolation runs above separate them cleanly:

  • Coverage: under LÖVE 12 the map comes back with the near half of the light frustum missing — large regions holding only the clear value, cut along the straight lines where the z = 0 plane crosses the terrain mesh, survivors packing a high byte >= 128. Remapping the ortho's clip z onto [0, 1] restores it.
  • Placement: the stale y-flip means the map is stored one way and read by uv math calibrated for the other, so every lookup samples it vertically mirrored — shadows of the far half stamped across the near field. Measuring the storage orientation restores it.

The camera pass never shows either wound: its projection is perspective, so everything visible sits past the z crossover at twice the near plane (near = dist * 0.05). The ortho here is linear, so it loses exactly half.

Environment

  • iPhone 15 Pro Max / iOS 27.0, gen1recomp v0.1.56 and v0.1.59 official iOS IPA (LÖVE 12 / Metal shell)
  • macOS, LÖVE 12.0 nightly (love-macos CI artifact), both Metal and OpenGL
  • macOS, LÖVE 11.5 (gen1recomp desktop build) as the control
  • DRAMATIC_SHAPE 1.5.1, VOXEL 35, DAYTIME DAY, Viridian City

Also observed

The water shader fails to compile under LÖVE 12 + Metal ('mediump' : overloaded functions must have the same parameter precision qualifiers, then missing #endif), so lakes draw flat. Absent under LÖVE 11.5 and under LÖVE 12 + OpenGL. Filing separately.

Fix incoming as a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions