Skip to content

Shadow cascade fade, the uniform cache's bits, and WindLight's water glint - #442

Merged
RyeMutt merged 3 commits into
developfrom
rye/render-fixes
Oct 11, 2026
Merged

RyeMutt merged 3 commits into
developfrom
rye/render-fixes

Conversation

@RyeMutt

@RyeMutt RyeMutt commented Oct 11, 2026

Copy link
Copy Markdown
Member

Three fixes to the renderer, one commit each.

The last shadow cascade fades its receivers to lit, not past it. sampleDirectionalShadow faded the last cascade out by adding the fade to the filtered shadow. A lit receiver near the far split came out twice as lit, then dropped back to lit past it: a hard line across the far view that moved with the draw distance and the camera. sunLightF clamps its output, so opaque surfaces hid the line. Water, alpha and PBR alpha read the shadow raw, and the water's sun glint showed it. The fade now mixes toward lit.

  • Opaque penumbrae in the far half of that cascade come out slightly darker than before.
  • sampleSpotShadow made the same addition and now mixes too. It also divided its one map by a cascade weight that is zero at three quarters of shadow_clip.z, which gave NaN at that distance. That weight is gone.

LLGLSLShader's uniform cache compares the bits it uploaded, and uniform4iv uploads all four ints. The cache that skips redundant glUniform* calls kept a float LLVector4 per location in a hash map. Ints and uints went through a float, so past 2^24 two different values compared equal and the change was dropped:

  • uniform1ui(FRAME_ID) after about 77 hours at 60 fps;
  • the CAS parameters, sent as float bits through uniform4uiv, whenever they moved by less than ~64 ulps.

The cache is now a flat array indexed by location. Each slot holds the upload's GL type and component count plus the raw bits, so a set skips only when both match exactly.

  • The fast setters now record what they upload.
  • link, mapUniforms and unload clear the cache.
  • uniform4iv called glUniform1iv, which is GL_INVALID_OPERATION on an ivec4. Nothing called it yet.
  • New GL test llglslshader_gl reads the values back from GL.

The water's sun glint, its glow and its ripples are WindLight's again. The sun's reflection on the water was a hard, narrow line, for three reasons:

  • distant water was flattened into a mirror;
  • the glint's roughness was the environment's blur multiplier;
  • its F0 was a magic-number sun colour, clamped at its ceiling.

It now follows WindLight's water, checked against the WindLight shaders in history:

  • Waves: the wave slopes are steepened as the view grazes the surface.
  • Glint: a normalized Phong lobe; its roughness is the new AlchemyRenderWaterGlintRoughness, whose default 0.35 is WindLight's exponent 128. Its brightness is dielectric Fresnel.
  • Glow: the glint writes its glow into the screen's alpha, which the legacy glow and the bloom both read. It is added to what is already there, and not written from below the surface or in probe captures.
  • Ripples: the refraction offset is WindLight's.

Two changes to the probes' reflection:

  • Normal scale: the water settings' normal scale now scales each wave layer. Before, equal values cancelled and the setting did nothing. At 2 the reflection is unchanged.
  • Below the horizon: a reflection that would point below the horizon is held on it.

Testing

  • The changed C++ files (llglslshader.cpp, llshadermgr.cpp, lldrawpoolwater.cpp, llglslshader_gl_test.cpp) compile from this branch with the Windows build's flags and warnings as errors.
  • llglslshader_gl passes on a full build of a branch stacked on this one. A deliberately broken copy of the test fails.
  • The changed shaders (waterF, underWaterF, shadowUtil) pass glslang in every define combination they are built with.
  • Not yet done on this branch alone: a full build, and an in-world check of the glint, the glow and the spot-shadow change.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 24 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c6cf7464-aaa2-4e52-b60a-70fa02685d06

📥 Commits

Reviewing files that changed from the base of the PR and between 2f73bc0 and 2c5aa45.


⛔ Files ignored due to path filters (3)
  • indra/newview/app_settings/shaders/class1/deferred/shadowUtil.glsl is excluded by !**/*.glsl
  • indra/newview/app_settings/shaders/class3/environment/underWaterF.glsl is excluded by !**/*.glsl
  • indra/newview/app_settings/shaders/class3/environment/waterF.glsl is excluded by !**/*.glsl

📒 Files selected for processing (2)
  • indra/llrender/tests/llglslshader_gl_test.cpp
  • indra/newview/app_settings/settings_alchemy.xml

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 45a0e788-fe73-4637-990a-a14e89708ed5


📥 Commits

Reviewing files that changed from the base of the PR and between 9290594 and 2f73bc0.



⛔ Files ignored due to path filters (3)
  • indra/newview/app_settings/shaders/class1/deferred/shadowUtil.glsl is excluded by !**/*.glsl
  • indra/newview/app_settings/shaders/class3/environment/underWaterF.glsl is excluded by !**/*.glsl
  • indra/newview/app_settings/shaders/class3/environment/waterF.glsl is excluded by !**/*.glsl


📒 Files selected for processing (8)
  • indra/llrender/CMakeLists.txt
  • indra/llrender/llglslshader.cpp
  • indra/llrender/llglslshader.h
  • indra/llrender/llshadermgr.cpp
  • indra/llrender/llshadermgr.h
  • indra/llrender/tests/llglslshader_gl_test.cpp
  • indra/newview/app_settings/settings_alchemy.xml
  • indra/newview/lldrawpoolwater.cpp


Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.




📝 Summary

Summary by CodeRabbit

  • New Features
    • Added a water glint roughness setting, adjustable from 0.05 to 1. Lower values create sharper-looking sun reflections; higher values create softer reflections.
  • Improvements
    • Updated above-water rendering to apply blending and alpha handling in supported shader modes. Other rendering scenarios retain their existing alpha behavior.
📝 Summary

Walkthrough

LLGLSLShader now caches uniform values by location using upload types and component bit patterns. Water rendering adds a configurable glint roughness uniform and conditionally enables blending and alpha writes during deferred rendering.

Changes

Uniform value caching

Layer / File(s) Summary
Typed uniform cache contract
indra/llrender/llglslshader.h, indra/llrender/llglslshader.cpp
UniformCache stores values by location and upload kind. Comparison helpers preserve integer values and float bit patterns.
Cache lifecycle and location mapping
indra/llrender/llglslshader.cpp
Program unload, uniform remapping, and linking clear cached values. Uniform mapping sizes the cache to the mapped locations, up to its limit.
Setter behavior and GL validation
indra/llrender/llglslshader.cpp, indra/llrender/CMakeLists.txt, indra/llrender/tests/llglslshader_gl_test.cpp
Scalar and vector setters compare cached values. Multi-element array writes invalidate the location, and fast setters remain unconditional. GL tests cover precision, setter combinations, cache resets, and array writes.

Water glint rendering

Layer / File(s) Summary
Glint roughness setting and uniform
indra/newview/app_settings/settings_alchemy.xml, indra/llrender/llshadermgr.h, indra/llrender/llshadermgr.cpp
Adds the persistent AlchemyRenderWaterGlintRoughness setting with a default of 0.35 and registers WATER_GLINT_ROUGHNESS.
Deferred water rendering
indra/newview/lldrawpoolwater.cpp
renderPostDeferred uploads the clamped roughness setting instead of WATER_SPECULAR. It enables separate color and alpha blending and writes alpha only under the specified above-water rendering conditions.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant LLDrawPoolWater
  participant LLGLSLShader
  participant OpenGL
  LLDrawPoolWater->>LLGLSLShader: Set WATER_GLINT_ROUGHNESS from the clamped setting
  LLGLSLShader->>OpenGL: Upload the uniform value
  LLDrawPoolWater->>OpenGL: Apply conditional blending and alpha writes
Loading

Suggested reviewers: martin-pitt



Merge Risk: ⚪ Minimal · up to 2f73b

No confirmed issue blocks merging. Complete the planned build and in-world rendering checks as normal validation.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 51 functions across 6 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title clearly summarizes the pull request's three main renderer changes: shadow cascade fading, uniform cache bit comparison, and WindLight water glint.
Description check Passed The description provides detailed context, implementation scope, and testing status for all three changes. It does not include the template's Related Issues section, checklist, or Additional Notes sec…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.


Full details: Docstring Coverage

Explanation

Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 51 functions across 6 files. (2 skipped: 2 unsupported.)




  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit checks the shader’s glow,
Bits stay true as values flow.
Water glints with roughness set,
Blend and alpha follow yet.
I twitch my nose; the tests all run,
Then hop beneath the setting sun.

Comment @coderabbitai help to get the list of available commands.

RyeMutt and others added 3 commits October 11, 2026 02:24
sampleDirectionalShadow faded the last cascade out toward its far split
by adding the fade to the filtered shadow, so a lit receiver there came
to twice lit at the split and dropped back to lit past it: a hard line
across the far view that moved with the draw distance and the camera's
angle. Opaque surfaces never showed the line, as sunLightF clamps the
shadow it writes to the light map at 1, but the water, alpha and PBR
alpha shaders read the shadow raw, and the water's sun glint drew the
line. The fade now mixes the filtered shadow toward lit. On opaque
surfaces lit and fully shadowed receivers fade as they did; a penumbra
in the far half of the cascade, which the clamped sum took to lit
sooner, now fades in proportion and is a little darker than it was.

sampleSpotShadow carried the same addition and mixes too. It also
weighted its one map as a cascade and divided by the weight, which is
zero three quarters of the way to the sun's third split, so a receiver
at that distance came out NaN. With one map there is nothing to weight,
and the division goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iv uploads all four ints

The cache that lets a uniform setter skip a value the program already
holds kept a float LLVector4 per location in a boost::unordered_map. An
int or uint went through a float on the way in, so past 2^24 two values
compared equal and the change was dropped: uniform1ui(FRAME_ID) after
about 77 hours at 60 fps, and the CAS parameters, uploaded as the bits of
floats through uniform4uiv, whenever they moved by less than about 64
ulps. Values of every type shared one slot, the fast setters uploaded
without telling it, and a bare link() kept values the relink had reset.
Every set paid a hash and a bucket walk.

The cache is a flat array indexed by uniform location, sized at
mapUniforms past the largest location a setter can reach. A slot holds
the kind of upload that wrote it (the GL component type and count) and
the uploaded components' bit patterns; a set matches only when both do,
so ints and uints compare as themselves and floats by their bits, and a
location written through one kind and set through another uploads. The
reserved-index setters and the named one share a location's slot. A set
costs a bounds compare, one 20-byte slot and an OR of XORs; there is no
hashing and no allocation. Arrays (count != 1) always upload and forget
their location's slot; matrices neither read nor write it. The fast
setters record what they upload, and link, mapUniforms and unload empty
the cache.

uniform4iv uploaded with glUniform1iv, which on an ivec4 uniform is
GL_INVALID_OPERATION and sets nothing; nothing in the tree called it
yet. It uploads with glUniform4iv, cached by its four ints.

llglslshader_gl reads back what the program holds: ints, ivec4s, uints
and uvec4s set past 2^24 and one ulp apart; set A, B, A again; a slot
empty after link against a value put there behind the cache; link and
remapping forgetting; a bool set through int and float; the named and
reserved setters sharing a slot; a fast set between two normal ones;
arrays forgetting. Taking an int through a float again fails it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sun's reflection on the water was a hard, narrow line, from three
faults stacked in its specular. The wave normal was pulled flat with the
distance, so far water was a mirror. Its roughness was the environment's
blur multiplier, around 0.04, which blurs the reflections and has nothing
to do with the sun. And its F0 was the pool's legacy sun colour, scaled
by magic numbers to around 110 at a low sun, with a metallic of 1: the
glint was clamped flat at its ceiling wherever it showed.

It is lit as WindLight's water lit it, from 2010 to the PBR viewer. The
wave normal's slopes are steepened as the view grazes the surface, by up
to ten times, so seen low the faces turned toward the sun fill more of
each pixel and the path breaks into a field of glints. The lobe is
WindLight's Phong about the mirror direction, normalized, its exponent
2 / alpha^2 - 2 from AlchemyRenderWaterGlintRoughness, whose default of
0.35 is WindLight's 128. A GGX lobe on the steepened slopes was tried:
its long tail lit every face within a few tens of degrees of the sun and
drew each crest across the path as a streak. Water is a dielectric: the
glint is the light's colour at the Fresnel reflectance of the angle
between the view and the light, worked from v.l alone, as the half vector
of a view and a setting sun at the horizon would not normalize. The
pool's magic numbers and the specular uniform go, and underWaterF's
unread specular with them.

The glint writes its glow, as WindLight's did: the lobe, by 0.1 rising to
0.3 with the light's elevation, unless shadowed, into the screen's alpha,
which the legacy glow and the bloom both read. The pool adds it to the
glow already there, so what glows beneath the surface keeps its own.
Only the class 3 shader writes it, as class 1's placeholder writes 1, and
not from below or in probe captures.

What is seen through the surface is displaced as WindLight displaced it:
by the steepened wave, shrinking with the square root of the distance
along the surface, and less where the surface mostly reflects. It was
displaced by six times the plain wave, as strongly at a grazing view as
looking straight down into it.

The probes' reflection stays a lookup by the wave normal's direction, as
the PBR viewer has it; WindLight displaced a mirrored render of the scene
on the screen, which the probes are not. Two things
in that lookup change. The water settings' normal scale holds one value
per wave layer, as WindLight used it; the PBR water multiplied the
composite normal's x, y and z by the three values and then normalized,
so equal values, the default among them, cancelled and the setting did
nothing. Each layer's slopes are scaled by its own value, against the
default of 2, which leaves the reflection as it was. And a wave tilted
across a low view reflected the view ray downward, so the probes returned
their lower faces, the ground seen from above, on water where the next
wave would hide anything below the horizon. The reflection is held on the
horizon, and the probes and screen space reflections take the normal that
reflects the view ray there; reflections above the horizon are untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RyeMutt
RyeMutt merged commit e6281d9 into develop Oct 11, 2026
15 checks passed
@RyeMutt
RyeMutt deleted the rye/render-fixes branch October 11, 2026 09:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant