Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9a24e31
Rebuild the lightbox floater as a declarative tabs-of-accordions shell
taylnos Jul 19, 2026
35e4445
Fix accordion section clipping and grey inactive tonemapper params
taylnos Jul 19, 2026
6c99be8
Size the Advanced tone tab to its real content and unclip the swatch
taylnos Jul 19, 2026
24a66ef
Treat missing alpha as opaque when a color swatch reads a Color3
taylnos Jul 19, 2026
15daecf
Keep Color3 swatches opaque and stop persisting a stale alpha
taylnos Jul 19, 2026
7741ccc
Complete the Look tab: grade suite, white balance, split toning, curves
taylnos Jul 19, 2026
6eaebfd
Fold the split toning midtone rows into the main section
taylnos Jul 19, 2026
771ccce
Build out the Lens tab: DoF, bloom/glow, flare, CA, vignette, grain
taylnos Jul 19, 2026
92614d2
Polish the Lens tab from first review feedback
taylnos Jul 19, 2026
c5768fc
Drop the bypass post-processing toggle from the topbar
taylnos Jul 19, 2026
befd321
Gate the bloom/glow fork on RenderHDREnabled for now
taylnos Jul 19, 2026
f74f2bc
Build out the Scene tab: AA, reflections, shadows, performance, preview
taylnos Jul 19, 2026
a251788
Streamline the Scene tab from review feedback
taylnos Jul 19, 2026
a635f94
Expose the SSAO effect vector by its real semantics
taylnos Jul 19, 2026
8cf4051
Clamp shadow bias to non-positive and offset to non-negative ranges
taylnos Jul 19, 2026
e4f027f
Give fine-value sliders an explicit value-text width
taylnos Jul 19, 2026
e3ab088
Add Looks: shareable aesthetic preset bundles for the Lightbox
taylnos Jul 19, 2026
48b0cba
Give the Looks Save As dialog a clean name field
taylnos Jul 19, 2026
c08cd54
Surface that RenderColorGrade gates the whole grading suite
taylnos Jul 19, 2026
f562a9c
Document how to add Lightbox sections
taylnos Jul 19, 2026
e7cc0e8
Add Lightbox v2: colour wheels, curve editor, scopes, undo/redo, refe…
taylnos Aug 11, 2026
48f03e4
Show up to four scopes at once, each pane assigned by right-click
taylnos Aug 11, 2026
2548c0f
Open the Scopes window from the Lightbox's own top bar
taylnos Aug 11, 2026
5568a94
Put the colour grading skips on the headers of the sections they skip
taylnos Aug 11, 2026
a4002db
Fold Basic's advanced tail in, and lift the grading master onto its h…
taylnos Aug 11, 2026
8768d9a
Bring the Lightbox guide back in line with what v2 actually built
taylnos Aug 11, 2026
229a682
Add a Sky tab: hold the day cycle still, and reach the client-side sk…
taylnos Aug 12, 2026
71863f7
Drop the render order note from the Color Grading section
taylnos Aug 12, 2026
9db6bc8
Fix the scope resize readback and the day scrub's sky capture
taylnos Aug 12, 2026
66e9597
Cap grouped undo steps, list only loadable LUTs, and reach the LUT fo…
taylnos Aug 12, 2026
10f7250
Merge branch 'develop' into feature/lightbox-v2
taylnos Aug 12, 2026
a07840a
Forward the snapshot layer type and settle the rest of the PR review
taylnos Aug 12, 2026
a113ac9
Make the depth of field focus point actually follow the pointer
taylnos Aug 12, 2026
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
2 changes: 2 additions & 0 deletions doc/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ The deferred rendering pipeline is orchestrated by `LLPipeline` (`indra/newview/
- **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

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

### Shader System

Shader management has two layers:
Expand Down
941 changes: 941 additions & 0 deletions doc/LIGHTBOX.md

Large diffs are not rendered by default.

195 changes: 195 additions & 0 deletions indra/llrender/llrender2dutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,161 @@ void gl_line_2d(S32 x1, S32 y1, S32 x2, S32 y2, const LLColor4 &color )
gGL.end();
}

void gl_polyline_2d(const std::vector<LLVector2>& points, const LLColor4& color, F32 width, bool closed)
{
const size_t count = points.size();
if (count < 2)
{
return;
}

const F32 half = llmax(width, 0.1f) * 0.5f;
// One pixel of falloff either side. Wider reads as a blurred line rather
// than a smooth one; narrower leaves the stair steps visible.
constexpr F32 FEATHER = 1.0f;
// A mitre grows as 1/cos(theta/2), so a hairpin would send the corner off
// the widget. Past this the join is simply blunted; on a curve sampled
// densely enough to be smooth the clamp never engages.
constexpr F32 MITRE_LIMIT = 4.0f;

const size_t segments = closed ? count : count - 1;

// Per-vertex mitre normals, so consecutive segments share their corner
// vertices exactly and the ribbon has no notches at the joins.
std::vector<LLVector2> normals(count);
for (size_t i = 0; i < count; ++i)
{
const bool has_prev = closed || i > 0;
const bool has_next = closed || i + 1 < count;

LLVector2 n_prev(0.f, 0.f);
LLVector2 n_next(0.f, 0.f);

if (has_prev)
{
const LLVector2& p = points[(i + count - 1) % count];
LLVector2 d = points[i] - p;
if (d.lengthSquared() > 0.f)
{
d.normalize();
n_prev.set(-d.mV[VY], d.mV[VX]);
}
}
if (has_next)
{
LLVector2 d = points[(i + 1) % count] - points[i];
if (d.lengthSquared() > 0.f)
{
d.normalize();
n_next.set(-d.mV[VY], d.mV[VX]);
}
}

LLVector2 n = n_prev + n_next;
if (n.lengthSquared() <= 0.f)
{
// An end point (one neighbour) or a doubled-back segment; fall
// back to whichever side actually has a direction.
n = has_next ? n_next : n_prev;
}
if (n.lengthSquared() <= 0.f)
{
normals[i].set(0.f, 0.f);
continue;
}
n.normalize();

// Lengthen the mitre so the ribbon keeps a constant apparent width
// through the turn rather than pinching.
const LLVector2& reference = (n_next.lengthSquared() > 0.f) ? n_next : n_prev;
const F32 cos_half = n * reference;
const F32 scale = (cos_half > 1.f / MITRE_LIMIT) ? (1.f / cos_half) : MITRE_LIMIT;
normals[i] = n * scale;
}

gGL.getTextureSlot(0)->unbind();

const LLColor4 edge(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.f);

// TRIANGLES rather than a strip: LLRender auto-flushes this mode on a
// multiple of three, so a long polyline cannot overrun the immediate-mode
// vertex buffer and lose its tail the way an unsplittable strip would.
gGL.begin(LLRender::TRIANGLES);
for (size_t s = 0; s < segments; ++s)
{
const size_t i0 = s;
const size_t i1 = (s + 1) % count;
const LLVector2& p0 = points[i0];
const LLVector2& p1 = points[i1];
if ((p1 - p0).lengthSquared() <= 0.f)
{
continue;
}
const LLVector2& n0 = normals[i0];
const LLVector2& n1 = normals[i1];

// Three bands per segment: the opaque core, and a fading skirt on
// each side. The skirts are what actually anti-alias the edge.
const F32 offsets[4] = { -(half + FEATHER), -half, half, half + FEATHER };
const LLColor4* colors[4] = { &edge, &color, &color, &edge };

for (S32 band = 0; band < 3; ++band)
{
const LLVector2 a0 = p0 + n0 * offsets[band];
const LLVector2 a1 = p1 + n1 * offsets[band];
const LLVector2 b0 = p0 + n0 * offsets[band + 1];
const LLVector2 b1 = p1 + n1 * offsets[band + 1];
const LLColor4& ca = *colors[band];
const LLColor4& cb = *colors[band + 1];

gGL.color4fv(ca.mV); gGL.vertex2f(a0.mV[VX], a0.mV[VY]);
gGL.color4fv(ca.mV); gGL.vertex2f(a1.mV[VX], a1.mV[VY]);
gGL.color4fv(cb.mV); gGL.vertex2f(b1.mV[VX], b1.mV[VY]);

gGL.color4fv(ca.mV); gGL.vertex2f(a0.mV[VX], a0.mV[VY]);
gGL.color4fv(cb.mV); gGL.vertex2f(b1.mV[VX], b1.mV[VY]);
gGL.color4fv(cb.mV); gGL.vertex2f(b0.mV[VX], b0.mV[VY]);
}
}
gGL.end();
gGL.flush();
}

void gl_polyfill_2d(const std::vector<LLVector2>& points, F32 baseline_y, const LLColor4& color)
{
const size_t count = points.size();
if (count < 2)
{
return;
}

gGL.getTextureSlot(0)->unbind();

// TRIANGLES, not a strip, for the reason gl_polyline_2d gives: LLRender
// auto-flushes this mode on a multiple of three, so a densely sampled curve
// cannot overrun the immediate-mode buffer and lose its tail.
gGL.begin(LLRender::TRIANGLES);
gGL.color4fv(color.mV);
for (size_t i = 0; i + 1 < count; ++i)
{
const LLVector2& p0 = points[i];
const LLVector2& p1 = points[i + 1];

// Degenerate where the curve touches the baseline, which is the common
// case for a weight band outside its range. Harmless, and cheaper to
// emit than to test for.
gGL.vertex2f(p0.mV[VX], baseline_y);
gGL.vertex2f(p1.mV[VX], baseline_y);
gGL.vertex2f(p1.mV[VX], p1.mV[VY]);

gGL.vertex2f(p0.mV[VX], baseline_y);
gGL.vertex2f(p1.mV[VX], p1.mV[VY]);
gGL.vertex2f(p0.mV[VX], p0.mV[VY]);
}
gGL.end();
gGL.flush();
}

void gl_triangle_2d(S32 x1, S32 y1, S32 x2, S32 y2, S32 x3, S32 y3, const LLColor4& color, bool filled)
{
gGL.getTextureSlot(0)->unbind();
Expand Down Expand Up @@ -1066,6 +1221,46 @@ void gl_washer_segment_2d(F32 outer_radius, F32 inner_radius, F32 start_radians,
gGL.end();
}

void gl_washer_angular_2d(F32 outer_radius, F32 inner_radius,
const std::vector<LLColor4>& colors, F32 inner_fade)
{
const size_t steps = colors.size();
if (steps < 3)
{
return;
}

gGL.getTextureSlot(0)->unbind();

// TRIANGLES rather than a strip, for the same reason gl_polyline_2d does:
// LLRender auto-flushes this mode on a multiple of three, so a finely
// stepped ring cannot overrun the immediate-mode buffer and lose its tail.
gGL.begin(LLRender::TRIANGLES);
for (size_t i = 0; i < steps; ++i)
{
const size_t j = (i + 1) % steps;
const F32 a0 = F_TWO_PI * (F32)i / (F32)steps;
const F32 a1 = F_TWO_PI * (F32)j / (F32)steps;

const F32 c0 = cosf(a0), s0 = sinf(a0);
const F32 c1 = cosf(a1), s1 = sinf(a1);

LLColor4 in0(colors[i]); in0.mV[VALPHA] *= inner_fade;
LLColor4 in1(colors[j]); in1.mV[VALPHA] *= inner_fade;

// Outer i -> outer j -> inner j, then outer i -> inner j -> inner i.
gGL.color4fv(colors[i].mV); gGL.vertex2f(outer_radius * c0, outer_radius * s0);
gGL.color4fv(colors[j].mV); gGL.vertex2f(outer_radius * c1, outer_radius * s1);
gGL.color4fv(in1.mV); gGL.vertex2f(inner_radius * c1, inner_radius * s1);

gGL.color4fv(colors[i].mV); gGL.vertex2f(outer_radius * c0, outer_radius * s0);
gGL.color4fv(in1.mV); gGL.vertex2f(inner_radius * c1, inner_radius * s1);
gGL.color4fv(in0.mV); gGL.vertex2f(inner_radius * c0, inner_radius * s0);
}
gGL.end();
gGL.flush();
}

void gl_rect_2d_simple_tex( S32 width, S32 height )
{
gGL.begin( LLRender::TRIANGLES );
Expand Down
47 changes: 46 additions & 1 deletion indra/llrender/llrender2dutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@
#include "llrect.h"
#include "llsingleton.h"
#include "llglslshader.h"
#include "v2math.h"

#include <vector>

class LLColor4;
class LLVector3;
class LLVector2;
class LLUIImage;
class LLUUID;

Expand All @@ -48,6 +50,34 @@ void gl_state_for_2d(S32 width, S32 height);

void gl_line_2d(S32 x1, S32 y1, S32 x2, S32 y2);
void gl_line_2d(S32 x1, S32 y1, S32 x2, S32 y2, const LLColor4 &color );

// Anti-aliased polyline through `points`, drawn as a ribbon of triangles with
// a one-pixel alpha falloff along each edge.
//
// GL_LINE_SMOOTH is not used, and deliberately: it appears nowhere in this
// tree, core profiles routinely ignore it, and LLRender::setLineWidth already
// clamps to mAliasedLineRange -- which is [1,1] on most core drivers -- so
// neither smoothing nor width can be relied on from the fixed pipeline. Doing
// the coverage by hand costs a few triangles per segment and looks the same
// everywhere.
//
// Joins are mitred, so a continuous curve has no notches at its vertices. The
// mitre is clamped, so a hairpin turn is blunted rather than shooting off to
// infinity. Fewer than two points draws nothing; coincident points are skipped.
void gl_polyline_2d(const std::vector<LLVector2>& points, const LLColor4& color,
F32 width = 1.f, bool closed = false);

// The area between `points` and the horizontal line y = `baseline_y`, flat
// filled. The companion to gl_polyline_2d for a graph whose meaning is how much
// is under the curve rather than where the curve runs -- a weight band, an
// occupancy plot -- where an outline alone reads as three crossing lines.
//
// The edge is left aliased: pass a translucent colour, as such a fill wants,
// and the polyline's feathered skirt would double up against the fill it sits
// on and draw a darker seam along the top. Outline it with gl_polyline_2d if a
// crisp edge is wanted; then the two feathers sit on the same path.
void gl_polyfill_2d(const std::vector<LLVector2>& points, F32 baseline_y,
const LLColor4& color);
void gl_triangle_2d(S32 x1, S32 y1, S32 x2, S32 y2, S32 x3, S32 y3, const LLColor4& color, bool filled);
void gl_rect_2d_simple( S32 width, S32 height );

Expand All @@ -71,6 +101,21 @@ void gl_corners_2d(S32 left, S32 top, S32 right, S32 bottom, S32 length, F32 max
void gl_washer_2d(F32 outer_radius, F32 inner_radius, S32 steps, const LLColor4& inner_color, const LLColor4& outer_color);
void gl_washer_segment_2d(F32 outer_radius, F32 inner_radius, F32 start_radians, F32 end_radians, S32 steps, const LLColor4& inner_color, const LLColor4& outer_color);

// A washer whose colour varies AROUND the sweep rather than across it: one
// entry in `colors` per step, wrapping back to the first. The washers above
// take a single inner and outer colour held constant along the arc, which can
// only ever make a radial gradient -- this is the angular one, and it is what
// a hue ring needs.
//
// `inner_fade` scales each colour's alpha at the inner edge, so a ring can
// fall off toward its centre instead of ending in a hard step.
//
// Like its siblings and unlike gl_circle_2d, this draws around the CURRENT
// origin and does not push the UI matrix -- wrap it in gGL.pushUIMatrix() and
// translateUI() yourself. Fewer than three colours draws nothing.
void gl_washer_angular_2d(F32 outer_radius, F32 inner_radius,
const std::vector<LLColor4>& colors, F32 inner_fade = 1.f);

void gl_draw_image(S32 x, S32 y, LLTexture* image, const LLColor4& color = UI_VERTEX_COLOR, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f));
void gl_draw_scaled_target(S32 x, S32 y, S32 width, S32 height, LLRenderTarget* target, const LLColor4& color = UI_VERTEX_COLOR, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f));
void gl_draw_scaled_image(S32 x, S32 y, S32 width, S32 height, LLTexture* image, const LLColor4& color = UI_VERTEX_COLOR, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f));
Expand Down
17 changes: 17 additions & 0 deletions indra/llrender/llshadermgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1936,6 +1936,11 @@ void LLShaderMgr::initAttribsAndUniforms()
// Previews
mReservedUniforms.push_back("uPreviewMode");

// Reference still
mReservedUniforms.push_back("uReferenceStill");
mReservedUniforms.push_back("uRefWipeMode");
mReservedUniforms.push_back("uRefWipePos");

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

Expand All @@ -1961,6 +1966,18 @@ void LLShaderMgr::initAttribsAndUniforms()
LL_ERRS() << "Duplicate reserved uniform name found: " << mReservedUniforms[i] << LL_ENDL;
}
dupe_check.insert(mReservedUniforms[i]);

// An array uniform belongs here under its bare name. LLGLSLShader::mapUniform
// chops the "[0]" off whatever GL reports before matching against this table,
// so a subscript here can never match anything -- and nothing complains. The
// location is never recorded, every upload to it silently does nothing, and the
// shader reads the array as all zeroes, which surfaces as a rendering fault a
// long way from the cause. Fatal for the same reason as the two checks above.
if (mReservedUniforms[i].find('[') != std::string::npos)
{
LL_ERRS() << "Reserved uniform '" << mReservedUniforms[i] << "' carries a subscript; "
<< "array uniforms are declared here by their bare name" << LL_ENDL;
}
}
}

5 changes: 5 additions & 0 deletions indra/llrender/llshadermgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,11 @@ class LLShaderMgr
// Previews
PREVIEW_MODE, // "uPreviewMode"

// Reference still — grab a frame, wipe the live image against it
REFERENCE_STILL, // "uReferenceStill"
REFERENCE_WIPE_MODE, // "uRefWipeMode"
REFERENCE_WIPE_POS, // "uRefWipePos"

// End Alchemy Effects Stack
TEXT_SHADOW_MODE, // "textShadowMode"

Expand Down
Loading
Loading