fix android water shading

This commit is contained in:
DramaticShape
2026-08-01 00:13:01 -04:00
parent 98f7419b72
commit 22b58e27a4
3 changed files with 70 additions and 24 deletions
+38 -22
View File
@@ -149,30 +149,46 @@
### Fixed
- **On Android the water stayed flat, as if the row were off.** Two GLSL ES
defaults the desktop never exercises, both in the water shader:
- **On Android the water stayed flat, as if the row were off -- and once it
did draw, it came up in blocks with the haze showing through the holes.**
Three separate faults, every one of them invisible on desktop GL, run down
on a Galaxy Z Fold 7 with the driver's own compiler errors in logcat:
Fragment floats default to **mediump** on GLSL ES, and the vertex stage's
default is highp -- and the water shader is the mod's first to declare the
same uniform, the frame's `vp` matrix, in BOTH stages, one on each default.
GLSL ES refuses to link a uniform whose precision the stages disagree on,
so the whole shader failed to build and the pass fell back -- quietly, by
design -- to the flat water the mode always drew. The pixel stage now lifts
its float default to highp (guarded, so a GPU without fragment highp still
compiles and falls back flat), which settles the link and is also simply
needed: the march works in world coordinates that run to a few thousand,
where fp16 has no fraction left, and the world-position varying is
qualified highp for the same reason the wireframe's has been all along.
Samplers default to **lowp** no matter what floats are set to, so the depth
read is lifted too -- eight bits of depth is a march with nothing to land
on.
**The shader would not build.** Fragment floats default to **mediump** on
GLSL ES while the vertex stage's default is highp, and the water shader is
the mod's first to declare the same uniform -- the frame's `vp` matrix --
in BOTH stages, one on each default; GLSL ES refuses to link that, and the
pass fell back, quietly and by design, to the flat water the mode always
drew. The pixel stage now lifts its float default to highp (guarded, so a
GPU without fragment highp still compiles and falls back flat), which
settles the link and is also simply needed: the march works in world
coordinates that run to a few thousand, where fp16 has no fraction left.
The world-position varying is qualified highp for the same reason the
wireframe's always was, and the depth sampler too -- samplers default to
**lowp** whatever the floats are set to, and eight bits of depth is a
march with nothing to land on. One wrinkle inside the fix: LOVE's header
forward-declares `effect()` under ITS default, and Samsung's Xclipse
compiler treats a definition whose parameter precisions have drifted from
the prototype's as an illegal overload -- so effect()'s own float
parameters stay pinned to mediump, matching the declaration, and the
maths above them runs highp regardless.
And the readable depth canvas -- the one hardware requirement the rest of
the mode does not already have -- now tries four formats before giving up:
depth24, depth24 riding a stencil (a pairing some mobile drivers will
texture when they refuse the bare format), depth32f, and depth16 as the
floor every GLES3 device can read. Refused all four, the reflections are
lost and nothing else, exactly as before.
**The depth test read the wrong texels.** The shader's own depth test
normalised LOVE's pixel coordinate by the `screen` uniform, which counts
canvas UNITS -- and on a highdpi phone (Android's density here is 2.625)
a canvas holds that many PIXELS per unit, so the lookup ran to 2.6,
clamped, and read edge texels across two thirds of the frame. Water
discarded itself in blocks wherever the mis-read depth landed in front,
and the haze backdrop showed through the holes. The coordinate is now
normalised by `love_ScreenSize.xy` -- the bound canvas's own pixel size,
measured in the same units on every display.
**And the readable depth canvas** -- the one hardware requirement the
rest of the mode does not already have -- now tries four formats before
giving up: depth24, depth24 riding a stencil (a pairing some mobile
drivers will texture when they refuse the bare format), depth32f, and
depth16 as the floor every GLES3 device can read. Refused all four, the
reflections are lost and nothing else, exactly as before.
### Known
+21 -2
View File
@@ -849,12 +849,31 @@ vec2 waveUV(vec2 tc, vec2 col) {
return org + (mod(col, 8.0) + 0.5) * texel;
}
vec4 effect(vec4 color, Image tex, vec2 tc, vec2 sc) {
// The float parameters are pinned to mediump BECAUSE the stage default is
// not: LOVE's own header forward-declares effect() under its default, and
// at least one mobile compiler (Samsung's Xclipse, in so many words) holds
// that a definition whose parameter precisions differ from its prototype's
// is a second function of the same name, and refuses the pair. The params
// can afford it -- the colour is a colour, and tc/sc arrived through
// LOVE's mediump plumbing whatever this signature says -- and the maths
// below runs on the stage default the moment the values touch a local.
vec4 effect(mediump vec4 color, Image tex, mediump vec2 tc, mediump vec2 sc) {
// THE DEPTH TEST, done here because the buffer that would have done it is
// detached for the length of this pass so it can be READ (see the header).
// Same comparison, same buffer, same result: a building in front of a pond
// still hides it.
vec2 uv = sc / screen;
//
// Normalised by LOVE's own screen size, not by the `screen` uniform: `sc`
// arrives in canvas PIXELS, and on a highdpi surface (Android's density
// is routinely 2.625) a canvas holds that many pixels per canvas UNIT,
// which is what `screen` counts. Divided by units, uv runs to 2.6 and
// clamps, and the test reads edge texels for two thirds of the frame --
// discarding water in blocks and letting the haze backdrop through, which
// on a phone looked like lakes with pieces missing. love_ScreenSize.xy is
// the bound canvas's own pixel size, the same units sc is measured in, on
// every display. (`screen` stays in units: skyPos reads it against cell
// and skyEdge, which are unit-measured with it.)
vec2 uv = sc / love_ScreenSize.xy;
if (gl_FragCoord.z > Texel(depthTex, uv).r) discard;
// THE COLUMN THIS FRAGMENT IS LOOKING AT. Every water pixel is a bar of
+11
View File
@@ -1796,6 +1796,17 @@ T.check(plain:find("LOVE_HIGHP_OR_MEDIUMP vec3 vBent", 1, true) ~= nil,
.. "rather than left to the fragment default")
T.check(plain:find("LOVE_HIGHP_OR_MEDIUMP Image depthTex", 1, true) ~= nil,
"and the depth sampler is lifted off lowp, which is eight bits of depth")
T.check(plain:find(
"effect(mediump vec4 color, Image tex, mediump vec2 tc, mediump vec2 sc)",
1, true) ~= nil,
"effect()'s own floats stay pinned to LOVE's prototype precision -- the "
.. "Xclipse compiler reads a definition that drifted from the forward "
.. "declaration as an illegal overload and refuses the whole shader")
T.check(plain:find("sc / love_ScreenSize.xy", 1, true) ~= nil,
"the depth test normalises the pixel coord by the canvas's own pixel "
.. "size -- `screen` counts canvas UNITS, and on a highdpi phone the two "
.. "differ by the density, which clamped the lookup and cut the water "
.. "into blocks")
-- ------- the lift itself
--