Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions doc/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,10 @@ The deferred rendering pipeline is orchestrated by `LLPipeline` (`indra/newview/
2. **Shadows** (`renderGeomShadow()`) — 4 sun shadow cascades + 2 spot light shadow maps
3. **Deferred Lighting** (`renderDeferredLighting()`) — Sun, point lights (up to 16 batched), spot lights, with reflection probe influence
4. **Post-Deferred** (`renderGeomPostDeferred()`) — Alpha, water, atmospheric haze
5. **Post-Processing** — Luminance/exposure, bloom, DoF, tonemapping, color grading, AA, screen effects
6. **Finalize** (`renderFinalize()`) — Final blit with vignette, film grain, dithering, chromatic aberration
5. **Post-Processing** — Luminance/exposure, depth of field, bloom, then one pass doing tonemapping, color grading and the bloom composite together, then AA and CAS
6. **Finalize** (`renderFinalize()`) — Final blit with lens distortion, vignette, film grain, dithering, CVD compensation

Depth of field runs **before** bloom and before the tonemapper, on linear HDR. Gathering over display-space values gathers over already-compressed highlights, and bloom-after-defocus is the optical order, so a defocused highlight blooms as a soft disc rather than a sharp core on a blurred background.

**GBuffer layout** (MRT attachments on `deferredScreen`):
- frag_data[0]: Base color (GL_RGBA)
Expand All @@ -101,18 +103,22 @@ The deferred rendering pipeline is orchestrated by `LLPipeline` (`indra/newview/
- `mMainRT` — Full resolution for main scene
- `mAuxillaryRT` — 512×512 for reflection probes and dynamic texture bakes
- `mHeroProbeRT` — High-res hero probe rendering
- Additional targets: `mSceneMap` (SSR input), `mLuminanceMap`/`mExposureMap` (auto-exposure), `mPostPingMap`/`mPostPongMap` (post-process ping-pong), `mFXAAMap`, `mSMAABlendBuffer`, `mGlow[3]` (bloom pyramid), `mWaterDis` (refraction), `mSpotShadow[2]`, `mPbrBrdfLut`
- Additional targets: `mSceneMap` (SSR input), `mLuminanceMap`/`mExposureMap` (auto-exposure), `postPingMap`/`postPongMap` (post-process ping-pong), `mFXAAMap`, `mSMAABlendBuffer`, `bloomMip[BLOOM_MAX_MIPS]` (HDR bloom pyramid, up to 7 levels, live count in `bloomMipCount`), `dofSharp`/`dofBlur` (depth of field, allocated only when `RenderDepthOfField` is on), `crossFilter[3]` (cross-screen filter scratch and accumulator, allocated lazily on the first frame the effect is enabled and released when it is switched off — `crossFilterHeight` guards the state), `mWaterDis` (refraction), `mSpotShadow[2]`, `mPbrBrdfLut`
- `mGlow[3]` is the **legacy non-HDR glow chain**, not the bloom pyramid, and lives outside the pack. So does `mLensDirtMap`, the generated lens dirt plate — an `LLRenderTarget` allocated the first frame the effect is enabled and released when it is switched off.

**Post-processing chain:**
- **Auto-exposure:** Progressive histogram (`gLuminanceProgram`, `gExposureProgram`) with history fade
- **Bloom:** Bright-area extraction → 3-level glow pyramid (`mGlow[3]`) with warmth correction
- **Depth of Field:** Circle-of-confusion via `gDeferredCoFProgram`, combine via `gDeferredDoFCombineProgram`. Settings: `CameraFNumber`, `CameraFocalLength`, `CameraMaxCoF`
- **Bloom:** Bright-area extraction → downsample/upsample pyramid over `bloomMip[]` with warmth correction and optional halation carried in alpha. The additive composite is **not** a pass of its own: it is folded into `colorCorrect`'s `BLOOM_COMPOSITE` permutation. `compositeBloomHDR` is a standalone equivalent that nothing currently calls
- **Cross-screen (star) filter:** `gCrossFilterProgram` streaks every thresholded highlight, seeded from `bloomMip[0]` and accumulated through `crossFilter[0..2]`. One strictly one-sided three-pass chain per arm, at strides that tile the reachable offsets exactly once — the tiling is load-bearing and the shader explains why. Composited in `colorCorrect` alongside the pyramid, so it inherits bloom strength
- **Lens dirt:** `gLensDirtGenProgram` draws the grime plate into `mLensDirtMap` — dust motes, wipe smudges, stray fibres, fine grit and optional scratches, built from hashes and distance fields rather than loaded from an image. Not a per-frame pass: it runs when a generation parameter moves or the window resizes, guarded by the cached parameter set in `mLensDirtParams`, which is also what makes a failed allocation stop retrying. Generated at the frame's own resolution, so nothing has to fit a square plate to a wide window. A rebuild is held off while `mLensDirtSliderHeld` is raised — the Lightbox raises it on a generation slider's mouse-down and lowers it on mouse-up, so a drag costs one plate on release rather than one per frame. Everything that is not a drag (typed values, resets, applying a Look, undo, window resizes) rebuilds immediately, which is what a settle timer would have delayed for no reason. `colorCorrect` multiplies the accumulated bloom and flare terms by it
- **Depth of Field:** three passes — circle of confusion (`gDeferredCoFProgram`, writing sharp linear colour plus signed CoF in alpha to `dofSharp`), a gather blur at `CameraDoFResScale` into `dofBlur`, and a combine (`gDeferredDoFCombineProgram`) back over `mRT->screen` under `setColorMask(true, false)` so the legacy prim-glow alpha tag survives. The gather has four compile-time variants over two axes, `FRONT_BLUR` × `DOF_SHAPED`: `gDeferredPostProgram`, `gDeferredPostProgramNoNear`, `gDeferredPostProgramShaped`, `gDeferredPostProgramNoNearShaped`. Optics: `CameraFNumber`, `CameraFocalLength`, `CameraFieldOfView`, `CameraMaxCoF`, `CameraDoFResScale`
- **Bokeh shaping** (inside the `DOF_SHAPED` gather): polygonal aperture (`RenderBokehApertureBlades`, `Rotation`, `Curvature`), anamorphic squeeze, cat's-eye optical vignetting, defocus fringing, and the lens aberrations — spherical (`RenderBokehSphericalAberration`), field stretch for swirl and coma (`RenderBokehFieldStretch`, `FieldFalloff`) and comatic asymmetry. The CPU picks a shaped variant only when one of them is actually doing something
- **Screen Space Reflections:** Class 3+ feature, iterative ray marching
- **Tonemapping:** ACES, Reinhard, Filmic, AGX — selectable via `AlchemyRenderTonemapType`
- **Color Grading:** 3D LUT-based (`gDeferredPostGammaCorrectCGLutProgram`, `mCGLut`)
- **Anti-aliasing:** FXAA (1-pass, `gFXAAProgram[4]`) or SMAA (3-pass edge detect → blend weights → neighborhood blend, `gSMAAEdgeDetectProgram[4]`/`gSMAABlendWeightsProgram[4]`/`gSMAANeighborhoodBlendProgram[4]`). CAS (Contrast Adaptive Sharpening) via `gCASProgram`
- **Final blit effects** (`blitWithEffectsF.glsl` in `shaders/class1/alchemy/`): Vignette (configurable shape/softness/color), film grain (luma/color/coarse/photon styles), TPDF dithering, CVD compensation/preview
- **Chromatic aberration** (`colorCorrectF.glsl` in `shaders/class1/alchemy/`): Per-channel offset with amount, falloff, angle, anisotropy controls
- **Final blit effects** (`blitWithEffectsF.glsl` in `shaders/class1/alchemy/`): geometric lens distortion (Brown-Conrady radial `k1`/`k2` and tangential `p1`/`p2`, anamorphic squeeze, decentring, with the auto-fit rescale solved on the CPU and out-of-frame samples masked to black), vignette (configurable shape/softness/color), film grain (luma/color/coarse/photon styles), TPDF dithering, CVD compensation/preview. Distortion warps the world view only — overlays drawn after the blit do not follow it
- **Effects inside `colorCorrectF.glsl`** (`shaders/class1/alchemy/`), which runs in every variant including the no-post ones and so gates them itself: chromatic aberration (per-channel offset with amount, falloff, angle, anisotropy), the five-component lens flare, the bloom and cross-filter composite, and lens dirt — the generated grime plate above, multiplied by the accumulated flare and bloom terms, so it only lights up where something already is

Post-processing settings are exposed in the Lightbox floater (`ALFloaterLightBox`); see `doc/LIGHTBOX.md` for how to add UI sections for new effects.

Expand All @@ -126,9 +132,9 @@ GLSL shaders live in `indra/newview/app_settings/shaders/` organized by quality
- **`class1/`** — Base shaders (all hardware)
- **`class2/`** — Mid-tier features
- **`class3/`** — Advanced features (SSR, high-quality lighting)
- Categories: `deferred/`, `objects/`, `environment/`, `alchemy/` (Alchemy post-processing), `windlight/`, `avatar/`, `interface/`
- Categories: `deferred/`, `objects/`, `environment/`, `alchemy/` (Alchemy post-processing), `effects/` (glow, the bloom pyramid, the cross-screen filter), `windlight/`, `avatar/`, `interface/`

Key shader groups: GBuffer write (`gDeferredDiffuseProgram`, `gDeferredPBROpaqueProgram`, `gDeferredBumpProgram`, `gDeferredMaterialProgram[]`, etc.), deferred lighting (`gDeferredSunProgram`, `gDeferredLightProgram`, `gDeferredMultiLightProgram[16]`, `gDeferredSpotLightProgram`), shadows (`gDeferredShadowProgram` and variants), environment (`gDeferredWLSkyProgram`, `gWaterProgram`, `gHazeProgram`), post-processing (tonemap, FXAA, SMAA, CAS, bloom, DoF programs).
Key shader groups: GBuffer write (`gDeferredDiffuseProgram`, `gDeferredPBROpaqueProgram`, `gDeferredBumpProgram`, `gDeferredMaterialProgram[]`, etc.), deferred lighting (`gDeferredSunProgram`, `gDeferredLightProgram`, `gDeferredMultiLightProgram[16]`, `gDeferredSpotLightProgram`), shadows (`gDeferredShadowProgram` and variants), environment (`gDeferredWLSkyProgram`, `gWaterProgram`, `gHazeProgram`), post-processing (tonemap, FXAA, SMAA, CAS, the bloom pyramid programs, `gCrossFilterProgram`, and the four-variant DoF gather set).

### Draw Pool & Spatial System

Expand Down
53 changes: 48 additions & 5 deletions doc/LIGHTBOX.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,35 @@ The floater's C++ provides:
- `LightBox.CommitSplitToneGraph` — the split-tone band graph's handle, which
writes `RenderSplitToneBalance`.
- `LightBox.PickWhiteBalance` — arms the eyedropper.
- `LightBox.OpenLUTFolder` — reveals the user's colour-LUT folder, creating it
on first use.
- The Looks bar and tonemapper-row greying (effect-specific, already done).

**Asset-picker rows are a third kind of dropdown**, distinct from the enum
recipe below. They bind a `combo_box allow_text_entry="true"` to a *string*
setting naming a file, and are filled from C++ by
`populateAssetCombo(combo_name, dir_name, extensions, setting_name)`: bundled
entries from `app_settings/<dir>` first, then the user's own from
`user_settings/<dir>` behind a separator, matching the order the renderer
itself resolves names in. Four things are not optional. The XUI carries one
literal `<combo_box.item value="" label="None"/>` and nothing else. `postBuild`
must call the populate helper, because the list does not exist until it does.
The helper's closing `selectByValue` is load-bearing rather than cosmetic —
with `allow_text_entry` nothing else restores the saved value when the floater
opens. And the extension whitelist must list only what the loader can actually
decode, or the picker offers files that silently fail. Pair it with an
`openUserAssetFolder(dir_name)` button so the folder is discoverable.

The colour LUT is currently the only picker — the lens dirt plate that shared
these helpers is generated now. Both stay parameterised by directory anyway,
because the shape is the shared part and collapsing them back to a constant
only has to be undone for the next asset. Before reaching for a picker at all,
ask whether the asset could be generated instead: a texture a shader can draw
into a render target once needs no file, no packaging entry, no extension
whitelist and no fitting to the window, and it can be put on sliders. See
`generateLensDirt` in pipeline.cpp for the shape — gate, cached parameter set,
allocate, draw, release when the effect goes off.

The last four are examples of the per-control cost: a graph or a tool needs
something to interpret its input, so it gets one callback and one `setup*`
call in `postBuild`. Both graphs follow the same shape — `setupX` connects to
Expand Down Expand Up @@ -811,13 +838,20 @@ only; apply per row, not on the parent panel. Reference patterns:
The snapshot floater's "No post-processing" box has to mean it, so a new
*print* effect must check it wherever its strength is uploaded — there are
`clean_plate` gates in **two** places, because post-grade is two passes.
Effects in the final blit (vignette, grain, CVD, the preview modes) gate in
`renderFinalize`; effects applied *inside* the colorCorrect program
(chromatic aberration, lens flare) gate in `colorCorrect`, because they run
in every variant including the no-post ones — those two leaked through the
first time for exactly that reason. The one deliberate exception is dither,
Effects in the final blit (lens distortion, vignette, grain, CVD, the preview
modes) gate in `renderFinalize`; effects applied *inside* the colorCorrect
program (chromatic aberration, lens flare, lens dirt, the cross-filter
composite) gate in `colorCorrect`, because they run in every variant
including the no-post ones — the first two leaked through the first time for
exactly that reason. The one deliberate exception is dither,
which is a quantisation aid rather than a look and which an 8-bit PNG wants
either way.
- **A generation pass is a third case, and gating it is a mistake.**
`generateLensDirt` deliberately ignores `gSnapshotNoPost`: the flag is true
for the single frame a no-post snapshot is taken, so releasing the plate for
it would buy a full regeneration on the very next frame — a hitch every time
someone takes one. What has to be gated is the *use* of the plate, in
`colorCorrect`, not its production.

Two things that only show up on screen, both of which did:

Expand Down Expand Up @@ -856,6 +890,15 @@ keys, `Persist=0` keys, structural buffer-shape knobs, or debug toggles.
A startup `LL_WARNS("Presets")` fires for whitelist names that stop existing,
so renames get caught.

`audit_bundled_looks()` runs from the `LLPresetsManager` constructor and checks
the bundled Looks two ways: any whitelisted key a Look is missing, and any key
whose stored `Comment` no longer matches the live setting's. The second is a
maintenance rule worth stating plainly — **rewording a setting's `Comment` in
`settings_alchemy.xml` obliges you to re-save the three bundled Looks**, which
each carry their own copy that nothing reads. Five keys had already drifted
that way before the check existed, silently, because presence was all anything
verified.

Bundled starter Looks live in `app_settings/looks/` as full whitelist
snapshots ({Comment, Persist, Type, Value} per key, URI-escaped filenames).
**Add your keys to all three at their defaults**, or applying a bundled Look
Expand Down
54 changes: 54 additions & 0 deletions indra/llrender/llshadermgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1991,6 +1991,60 @@ void LLShaderMgr::initAttribsAndUniforms()
mReservedUniforms.push_back("uRefWipeMode");
mReservedUniforms.push_back("uRefWipePos");

// Geometric lens distortion
mReservedUniforms.push_back("uLensDistortAmount");
mReservedUniforms.push_back("uLensDistortK");
mReservedUniforms.push_back("uLensDistortScale");
mReservedUniforms.push_back("uLensDistortSqueeze");
mReservedUniforms.push_back("uLensDistortCenter");
mReservedUniforms.push_back("uLensDistortTangential");

// Bokeh
mReservedUniforms.push_back("uBokehHighlightThreshold");
mReservedUniforms.push_back("uBokehHighlightGain");
mReservedUniforms.push_back("uBokehHighlightClamp");
mReservedUniforms.push_back("uBokehBlades");
mReservedUniforms.push_back("uBokehApertureRotation");
mReservedUniforms.push_back("uBokehApertureCurvature");
mReservedUniforms.push_back("uBokehApertureConst");
mReservedUniforms.push_back("uBokehAnamorphic");
mReservedUniforms.push_back("uBokehCatEye");
mReservedUniforms.push_back("uBokehFringeAmount");
mReservedUniforms.push_back("uBokehFringeNearTint");
mReservedUniforms.push_back("uBokehFringeFarTint");

// Lens dirt
mReservedUniforms.push_back("uLensDirtMap");
mReservedUniforms.push_back("uLensDirtStrength");
mReservedUniforms.push_back("uLensDirtBloomResponse");
mReservedUniforms.push_back("uLensDirtFlareResponse");

// Lens dirt generation
mReservedUniforms.push_back("uDirtResolution");
mReservedUniforms.push_back("uDirtSeed");
mReservedUniforms.push_back("uDirtGrime");
mReservedUniforms.push_back("uDirtMoteScale");
mReservedUniforms.push_back("uDirtSmudge");
mReservedUniforms.push_back("uDirtScratches");
mReservedUniforms.push_back("uDirtToe");
mReservedUniforms.push_back("uDirtGain");

// Cross-screen filter
mReservedUniforms.push_back("uCrossTexel");
mReservedUniforms.push_back("uCrossDir");
mReservedUniforms.push_back("uCrossLength");
mReservedUniforms.push_back("uCrossFalloff");
mReservedUniforms.push_back("uCrossChromatic");
mReservedUniforms.push_back("uCrossPassScale");
mReservedUniforms.push_back("uCrossStrength");

mReservedUniforms.push_back("uBokehSpherical");
mReservedUniforms.push_back("uBokehFieldStretch");
mReservedUniforms.push_back("uBokehFieldFalloff");
mReservedUniforms.push_back("uBokehComaAsymmetry");

mReservedUniforms.push_back("crossFilterMap");

// Text Shadow
mReservedUniforms.push_back("textShadowMode");

Expand Down
Loading
Loading