From 2cfa6eed928f7b8d9ef6dddc8caeae81cb4edc14 Mon Sep 17 00:00:00 2001 From: Rye Date: Wed, 21 Oct 2026 08:38:28 -0400 Subject: [PATCH 01/11] Draw the sky from ALSky, not from viewer objects and pools The sky was two fake viewer objects (LLVOSky, LLVOWLSky) with pcodes, drawables and faces, pushed into every view's cull and drawn through two pools, one of them empty. Since the composite (Phase 3) only the stars still drew through a pool. ALSky (newview/alsky.h) now holds the sky's state, owned by LLPipeline as the water's surface is: - the sun and moon (LLHeavenBody, trimmed to its direction and turn as the sky sets them, its disk and its quad; their colour, the bloom's tint, and whether each is up are the current sky's, read where they are drawn) and their quads, taken at the start of the frame rather than in every view the pool ran in. Their half-extent per unit of disk radius, scale and distance is ALSkyLayers::BODY_FACTOR, the one bodyCorners builds them with, which the lens flare's probe (LLPipeline::generateLensFlareState) reads too; - the EEP textures, fetched from the ids the sky sets on the next update, at the priority they are drawn at; the bloom's only under the latched sun bloom. A rainbow or halo id the sky leaves null is LLSettingsSky's default, since every view that draws the dome samples both maps; - the star field, laid out on the General work queue (requestStars) and landed on the main thread (landStars), which keeps only the field that answers the last request: a RenderStarCount change, which takes effect without a restart, draws the last field until its own lands. The star buffer is made from the field when next drawn, and where it cannot be made it is not asked for again until a new field lands or the context changes. The cells, the brightness cull (none on the float attachment, where every star in view is drawn), and the meteors; - the per-frame uniforms (bindDome, bindComposite, useHDRISky); - the fog colour the tracker's beacons read, and for now the CPU-built legacy environment cube, moved as it was. It runs at three fixed points, the same in every view: the stars right after renderGeomDeferred's pool loop, the composite at the sky's pass, and the dome where the haze binds for water. Nothing after the opaque pools drew into the G-buffer (glow, alpha and water draw after the lighting), so the stars land where they did. ALSky::update takes the frame's sky at the start of display, before anything draws: the mirrors draw it, and the previews and impostor bakes read its environment cube. Right after LLEnvironment::update has moved the sky on, followEnvironment takes the bodies and the textures again for the main view; the mirrors, drawn before it, draw the sky as the frame began, as they take its environment. Each view takes what it draws of the sky once, at its G-buffer's clear. LLPipeline::beginSkyView has ALSky::beginView take the view's layers (ALSkyLayers::layersFor) from the kind of view, the current sky and the latched inputs, none where drawsSky says the view draws no sky, and clears the stars' attachment only where they have the stars. The stars' draw, the meteors' tick and the composite read the same layers (viewLayers). The composite's draw binds its program alone, not the G-buffer through bindDeferredShader: bindComposite binds the stars' attachment among its maps, only where the layers have them, records each map it binds, and unbindComposite lets go of those alone. It leaves the meteors out where none is on the screen. hazeOnWater asks drawsSky. gSky is folded in (D1): its callers move to gPipeline.mSky, and LLSky goes with the init and cleanup calls that existed only because the sky was an in-world object (llstartup, llworld, the agent's region hack). Its destroyGL/restoreGL are now the pipeline's releaseAllGLResources, restoreGL and cleanup. setWind, setCloudDensity and setMoonDirectionCFR fed nothing and go, and with setWind the region's average wind LLAppViewer::idle took for it; setSunDirectionCFR keeps its one effect, the sun bump maps are lit by. LLFace's moon ray and the sun and moon beacons take their directions from LLEnvironment. Deleted: LLVOSky, LLVOWLSky, LLDrawPoolSky, LLDrawPoolWLSky, POOL_SKY, POOL_WL_SKY, LL_VO_SKY, LL_VO_WL_SKY and their special cases, the cull pushes, the pool members and their lookups, the drawable's sky case, the pipeline's unread LLCullResult mSky, and the bump pool's uncalled bindCubeMap/unbindCubeMap. RENDER_TYPE_SKY and RENDER_TYPE_WL_SKY stay, beside RENDER_TYPE_CLOUDS outside the pool range; nothing persists pool or render-type numbers (the menu names its types by string). LLAtmospherics' constructor read two settings, SkyAmbientScale and SkyNightColorShift, into members nothing reads; ALSky constructs it with the pipeline, before settings load, so those reads go, and the two settings with them (settings.xml and their Portuguese comments). LLGLSLShader::bindTexture given a null texture or render target, and bindDepthTexture given a null target, bind nothing and return the unit all the same, let go of what an earlier bind left there, so the draw reads none of it; ALTextureSlot::bindFast given a null texture, or one with no GL image, lets its slot go likewise. llglslshader_gl_test binds each null over a unit holding a target and finds the unit let go, and the disable a reader pairs with the bind finding nothing to release under the debug checks. quadCoord, the CPU mirror of skyQuadCoord that only alskylayers_test calls, moves out of ALSkyLayers into it. alskydome_gl_test takes the sky tests' shared helpers (alskyraster_gl.h, which RAINBOW_SKY joins) in place of its own copies, and holds the haze on water against itself with its weight floor at zero: over a made-up scene whose dome weight runs from zero through the floor, the two are one picture where the weight is zero or at the floor and past it, and under the floor differ by no more than the floor allows. Co-Authored-By: Claude Opus 5.5 --- indra/alrender/alskylayers.cpp | 25 - indra/alrender/alskylayers.h | 27 +- indra/alrender/alstarfield.h | 6 +- .../alrender/tests/alskycomposite_gl_test.cpp | 9 +- indra/alrender/tests/alskydome_gl_test.cpp | 563 ++++---- indra/alrender/tests/alskylayers_gl_test.cpp | 4 +- indra/alrender/tests/alskylayers_test.cpp | 30 +- indra/alrender/tests/alskyraster_gl.h | 13 +- indra/alrender/tests/alstarfield_gl_test.cpp | 10 +- indra/alrender/tests/alstarfield_test.cpp | 4 +- indra/llrender/altextureslot.cpp | 9 +- indra/llrender/altextureslot.h | 2 +- indra/llrender/llglslshader.cpp | 17 +- indra/llrender/llglslshader.h | 2 + indra/llrender/tests/llglslshader_gl_test.cpp | 69 +- indra/newview/CMakeLists.txt | 12 +- indra/newview/alsky.cpp | 1242 +++++++++++++++++ indra/newview/alsky.h | 312 +++++ indra/newview/app_settings/settings.xml | 26 - .../class1/deferred/skyCompositeF.glsl | 5 +- .../shaders/class1/deferred/starsF.glsl | 2 +- .../shaders/class1/deferred/starsV.glsl | 4 +- indra/newview/llagent.cpp | 8 - indra/newview/llappviewer.cpp | 8 - indra/newview/lldrawable.cpp | 7 +- indra/newview/lldrawpool.cpp | 8 - indra/newview/lldrawpool.h | 8 +- indra/newview/lldrawpoolalpha.cpp | 1 - indra/newview/lldrawpoolavatar.cpp | 1 - indra/newview/lldrawpoolbump.cpp | 82 +- indra/newview/lldrawpoolbump.h | 2 - indra/newview/lldrawpoolsimple.cpp | 1 - indra/newview/lldrawpoolsky.cpp | 57 - indra/newview/lldrawpoolsky.h | 68 - indra/newview/lldrawpoolterrain.cpp | 1 - indra/newview/lldrawpooltree.cpp | 1 - indra/newview/lldrawpoolwlsky.cpp | 563 -------- indra/newview/lldrawpoolwlsky.h | 85 -- indra/newview/llenvironment.cpp | 1 - indra/newview/llface.cpp | 6 +- indra/newview/llflexibleobject.cpp | 1 - indra/newview/llfloatergodtools.cpp | 1 - indra/newview/llfloaterpreference.cpp | 1 - indra/newview/llglsandbox.cpp | 4 +- indra/newview/lllegacyatmospherics.cpp | 5 +- indra/newview/lllegacyatmospherics.h | 2 - indra/newview/llsettingsvo.cpp | 16 +- indra/newview/llsky.cpp | 333 ----- indra/newview/llsky.h | 96 -- indra/newview/llstartup.cpp | 14 - indra/newview/llsurfacepatch.cpp | 1 - indra/newview/lltoolgun.cpp | 5 +- indra/newview/lltoolmorph.cpp | 1 - indra/newview/lltracker.cpp | 7 +- indra/newview/llviewercontrol.cpp | 4 +- indra/newview/llviewerdisplay.cpp | 24 +- indra/newview/llviewerjointmesh.cpp | 1 - indra/newview/llviewermessage.cpp | 1 - indra/newview/llviewerobject.cpp | 8 - indra/newview/llviewerobject.h | 2 - indra/newview/llviewerobjectlist.cpp | 1 - indra/newview/llviewershadermgr.cpp | 7 +- indra/newview/llviewerwindow.cpp | 8 - indra/newview/llvoavatar.cpp | 1 - indra/newview/llvograss.cpp | 1 - indra/newview/llvopartgroup.cpp | 1 - indra/newview/llvosky.cpp | 1039 -------------- indra/newview/llvosky.h | 354 ----- indra/newview/llvosurfacepatch.cpp | 1 - indra/newview/llvovolume.cpp | 1 - indra/newview/llvowlsky.cpp | 390 ------ indra/newview/llvowlsky.h | 101 -- indra/newview/llworld.cpp | 2 - indra/newview/pipeline.cpp | 146 +- indra/newview/pipeline.h | 21 +- .../default/xui/pt/settings_comments.xml | 4 - 76 files changed, 2051 insertions(+), 3855 deletions(-) create mode 100644 indra/newview/alsky.cpp create mode 100644 indra/newview/alsky.h delete mode 100644 indra/newview/lldrawpoolsky.cpp delete mode 100644 indra/newview/lldrawpoolsky.h delete mode 100644 indra/newview/lldrawpoolwlsky.cpp delete mode 100644 indra/newview/lldrawpoolwlsky.h delete mode 100644 indra/newview/llsky.cpp delete mode 100644 indra/newview/llsky.h delete mode 100644 indra/newview/llvosky.cpp delete mode 100644 indra/newview/llvosky.h delete mode 100644 indra/newview/llvowlsky.cpp delete mode 100644 indra/newview/llvowlsky.h diff --git a/indra/alrender/alskylayers.cpp b/indra/alrender/alskylayers.cpp index 1cc7e67e0a3..b2d1f5513a8 100644 --- a/indra/alrender/alskylayers.cpp +++ b/indra/alrender/alskylayers.cpp @@ -43,12 +43,6 @@ namespace ALSkyLayers { -namespace -{ - // A body's half-extent per unit of its disk radius and its distance. - constexpr F32 BODY_FACTOR = 0.1f; -} // namespace - S32 layersFor(const Frame& frame) { if (frame.mHdri) @@ -148,25 +142,6 @@ std::array rayQuad(const Corners& corners, const LLVector3& offset return { ALFarPlane::skyFrame(centre), ALFarPlane::skyFrame(half_u), ALFarPlane::skyFrame(half_v) }; } -bool quadCoord(const std::array& quad, const LLVector3& dir, F32& u, F32& v) -{ - const LLVector3 normal = quad[1] % quad[2]; - const F32 facing = dir * normal; - if (fabsf(facing) < 1e-12f) - { - return false; - } - const F32 t = (quad[0] * normal) / facing; - if (t <= 0.f) - { - return false; - } - const LLVector3 offset = dir * t - quad[0]; - u = (offset * quad[1]) * 0.5f + 0.5f; - v = (offset * quad[2]) * 0.5f + 0.5f; - return true; -} - U32 packMeteors(const std::vector& meteors, const LLMatrix4a& mvp, F32 width, F32 height, F32 width_px, std::vector& out) { diff --git a/indra/alrender/alskylayers.h b/indra/alrender/alskylayers.h index ed45badb4d6..81d19b4053e 100644 --- a/indra/alrender/alskylayers.h +++ b/indra/alrender/alskylayers.h @@ -43,7 +43,7 @@ class LLMatrix4a; class LLQuaternion; /// The sky's layers as the composite takes them (deferred/skyCompositeF.glsl and the layers' files): no GL and no -/// settings, so LLVOSky, LLVOWLSky, the pass that binds the composite and the GL tests share one definition. +/// settings, so ALSky, the pass that binds the composite and the GL tests share one definition. namespace ALSkyLayers { @@ -80,12 +80,12 @@ struct Frame bool mProbe = false; ///< a reflection probe's face or a mirror's (gCubeSnapshot) bool mMirror = false; ///< a mirror's (LLHeroProbeManager::isMirrorPass) bool mRadiance = true; ///< not an irradiance pass of the probes (LLReflectionMapManager::isRadiancePass) - bool mHdri = false; ///< an HDRI sky in place of WindLight's (LLDrawPoolWLSky::useHDRISky) + bool mHdri = false; ///< an HDRI sky in place of WindLight's (ALSky::useHDRISky) bool mSun = false; ///< the sun is up and has a texture bool mBloom = false; ///< the sun bloom is drawn (AlchemyRenderSunBloom, latched) and has a texture bool mMoon = false; ///< the moon is up and has a texture F32 mStarAlpha = 0.f; ///< the star brightness over 500 - bool mMeteors = false; ///< meteors are on (RenderMeteorsEnabled) and some fly + bool mMeteors = false; ///< meteors are on (RenderMeteorsEnabled), so a view of the night ticks them F32 mAurora = 0.f; ///< the aurora's intensity after the moon's wash bool mClouds = false; ///< clouds are shown (RENDER_TYPE_CLOUDS) and have a noise texture }; @@ -98,7 +98,7 @@ S32 layersFor(const Frame& frame); /// The attachment of the G-buffer the stars are drawn into and the composite reads them from: the emissive with the /// emissive buffer (frag_data[3]), the albedo without it (frag_data[0]). A view whose layers leave the stars out -/// does not read it. +/// neither clears nor reads it. constexpr U32 starAttachment(bool emissive) { return emissive ? 3 : 0; } /// A sun's or moon's quad about the camera, in the agent's frame: corners 0 to 3 at (-right, +up), (-right, -up), @@ -106,14 +106,16 @@ constexpr U32 starAttachment(bool emissive) { return emissive ? 3 : 0; } /// and up axes square to each other. using Corners = std::array; +/// A body's half-extent per unit of its disk radius, its scale and its distance. +constexpr F32 BODY_FACTOR = 0.1f; + /// The quad of a body whose direction is @a rotation's turn of +x, centred @a distance from the camera along it, its -/// half-extents @a scale times @a disk_radius times the distance (times HEAVENLY_BODY_FACTOR, 0.1, as LLVOSky built -/// it): wider by up to 30% and taller by up to 20% as the body nears the horizon. +/// half-extents @a scale times @a disk_radius times a tenth of the distance: wider by up to 30% and taller by up to 20% +/// as the body nears the horizon. Corners bodyCorners(const LLQuaternion& rotation, F32 distance, F32 scale, F32 disk_radius); /// The sun bloom's square about the sun's quad @a sun: centred on it, a half-extent every way of four times the disc's -/// half-width, along the disc's axes. The square the sun's halo was drawn in before WindLight -/// (LLVOSky::updateSunHaloGeometry at the open-source import). +/// half-width, along the disc's axes. Corners bloomCorners(const Corners& sun); /// The moon's opacity as it nears the horizon, from @a moon_z, the agent's z of its direction: four times its square, @@ -130,12 +132,7 @@ constexpr F32 SUN_DROP = 50.f; /// [-1, 1]. std::array rayQuad(const Corners& corners, const LLVector3& offset = LLVector3::zero); -/// The texture coordinates, unclipped, where @a dir, a direction from the camera in the dome's frame, meets the plane -/// of @a quad (rayQuad's); false, and the coordinates unset, where it runs parallel to the plane or away from it. A CPU -/// mirror of skyQuadCoord, for the tests. -bool quadCoord(const std::array& quad, const LLVector3& dir, F32& u, F32& v); - -/// The most meteors LLVOWLSky keeps, and the composite's array of them. +/// The most meteors ALSky keeps, and the composite's array of them. constexpr U32 MAX_METEORS = 64; /// The vec4s a meteor takes in the composite's skyMeteorData. deferred/skyMeteorsF.glsl's SKY_METEOR_VECTORS must equal @@ -145,7 +142,7 @@ constexpr U32 METEOR_VECTORS = 3; /// The width the meteors draw at, in pixels each side of the streak for a meteor of width 1. constexpr F32 METEOR_WIDTH_PIXELS = 1.5f; -/// A meteor as LLVOWLSky ticks it for a frame, about the camera in the agent's frame: the streak's midpoint and half +/// A meteor as ALSky ticks it for a frame, about the camera in the agent's frame: the streak's midpoint and half /// of it toward the head, its colour times its envelope (sRGB) and its width class as eight bits each, the width a /// quarter in alpha, and its width class as it is. struct Meteor diff --git a/indra/alrender/alstarfield.h b/indra/alrender/alstarfield.h index ecd1b6632bd..d6d398da40d 100644 --- a/indra/alrender/alstarfield.h +++ b/indra/alrender/alstarfield.h @@ -37,10 +37,10 @@ class LLMatrix4a; -/// The star field, worked from a star count and the dome's radius alone: no GL and no settings, so LLVOWLSky's buffer -/// and the GL tests that draw it share one definition. +/// The star field, worked from a star count and the dome's radius alone: no GL and no settings, so ALSky's buffer and +/// the GL tests that draw it share one definition. /// -/// The stars' frame is the agent's turned slowly about z (LLDrawPoolWLSky::renderStarsDeferred): x and y on the +/// The stars' frame is the agent's turned slowly about z (ALSky::renderStars): x and y on the /// horizon, z up. Every star lies on the upper hemisphere. namespace ALStarField { diff --git a/indra/alrender/tests/alskycomposite_gl_test.cpp b/indra/alrender/tests/alskycomposite_gl_test.cpp index 6127bf22b14..17927f9f56c 100644 --- a/indra/alrender/tests/alskycomposite_gl_test.cpp +++ b/indra/alrender/tests/alskycomposite_gl_test.cpp @@ -231,13 +231,6 @@ void main() } )"; - // A rainbow and a halo, which the dome draws in bands below the horizon too. - const Sky RAINBOW_SKY = { "midday, rainbow and halo", { 0.2447f, 0.4487f, 0.7599f }, { 0.4954f, 0.4954f, 0.6399f }, - 0.7f, 0.19f, 0.0001f, 1605.f, { 5.f, 0.001f, -0.4799f }, 0.27f, - { 0.7342f, 0.7815f, 0.8999f }, { 0.35f, 0.35f, 0.45f }, 35.f, 200.f, 1.f, 0.6f, 800.f, - 0.5f, { 0.41f, 0.41f, 0.41f }, { 1.6869f, 0.5269f, 0.88f }, { 0.07f, 0.4f, 0.125f }, - 0.4199f, 0.27f }; - // The moonlit night under a cloud deck dense enough to be opaque over most of the sky, with gaps. const Sky CLOUD_DECK_SKY = { "night, moonlit, a cloud deck", { 0.12f, 0.18f, 0.38f }, { 0.18f, 0.22f, 0.36f }, 0.5f, 0.12f, 0.00018f, 1200.f, { 5.f, 0.001f, -0.4799f }, 0.45f, { 0.21f, 0.245f, 0.35f }, @@ -535,7 +528,7 @@ void main() MOON_DISK_RADIUS); } - // The stars into the bound G-buffer, as LLDrawPoolWLSky::renderStarsDeferred draws them: every star, into + // The stars into the bound G-buffer, as ALSky::renderStars draws them: every star, into // the one attachment, the moon's quad masking those behind it. void drawStars(const Scene& scene, bool emissive, LLRenderTarget& target) { diff --git a/indra/alrender/tests/alskydome_gl_test.cpp b/indra/alrender/tests/alskydome_gl_test.cpp index 9bc9eee5a9c..ab5fa81831a 100644 --- a/indra/alrender/tests/alskydome_gl_test.cpp +++ b/indra/alrender/tests/alskydome_gl_test.cpp @@ -29,111 +29,29 @@ #include "linden_common.h" -#include "alfarplane.h" -#include "alskydome.h" - -#include "llgl.h" -#include "llglheaders.h" -#include "llglslshader.h" -#include "llglstates.h" -#include "llimage.h" -#include "llimagegl.h" -#include "llmatrix4a.h" -#include "llrender.h" -#include "llrendertarget.h" -#include "llshadermgr.h" -#include "llstrider.h" -#include "llvertexbuffer.h" - #include "alskyraster_gl.h" -#include "../../llrender/tests/llheadlessgl_fixture.h" - -#include - -#include -#include -#include -#include #include -#include -#include -#include namespace { + using namespace al_sky_test; + constexpr S32 W = 192; constexpr S32 H = 128; - constexpr F32 TAN_HALF_FOV = 0.6f; - constexpr F32 NEAR_CLIP = 0.5f; - constexpr F32 FAR_CLIP = 1024.f; - - // LLSettingsSky's DOME_RADIUS, and its DOME_OFFSET of it: where LLEnvironment::getCamHeight puts the camera. - constexpr F32 DOME_RADIUS = 15000.f; - constexpr F32 CAMERA_HEIGHT = 0.96f * 15000.f; - - // Where the camera is in the agent's frame. renderDome takes the dome to it, so nothing should depend on it. - const LLVector3 CAMERA(118.f, -43.f, 27.f); - - // A sky's settings, as the Environment block and the sky's loose uniforms take them. - struct Sky - { - const char* mName; - LLVector3 mBlueDensity; - LLVector3 mBlueHorizon; - F32 mHazeDensity; - F32 mHazeHorizon; - F32 mDensityMultiplier; - F32 mMaxY; - LLVector3 mGlow; - F32 mCloudShadow; - LLVector3 mLight; ///< sky_light: the sun's colour, or the moon's at 0.7 - LLVector3 mAmbient; ///< sky_ambient - F32 mElevation; ///< the light's, in degrees - F32 mAzimuth; ///< the light's, in degrees clockwise from north - F32 mGlowFactor; ///< sun_moon_glow_factor: 1 with the sun up, the moon's brightness / 4 with it down - F32 mMoisture; - F32 mDroplet; - F32 mIce; - }; // The default sky at midday, the same with a rainbow's moisture and a halo's ice, a sunset under thicker haze, and // a night lit by the moon, whose glow factor below 1 takes the glow away. - const Sky SKIES[] = { - { "midday", { 0.2447f, 0.4487f, 0.7599f }, { 0.4954f, 0.4954f, 0.6399f }, 0.7f, 0.19f, 0.0001f, 1605.f, - { 5.f, 0.001f, -0.4799f }, 0.27f, { 0.7342f, 0.7815f, 0.8999f }, { 0.35f, 0.35f, 0.45f }, 60.f, 80.f, 1.f, - 0.f, 800.f, 0.f }, - { "midday, rainbow and halo", { 0.2447f, 0.4487f, 0.7599f }, { 0.4954f, 0.4954f, 0.6399f }, 0.7f, 0.19f, 0.0001f, - 1605.f, { 5.f, 0.001f, -0.4799f }, 0.27f, { 0.7342f, 0.7815f, 0.8999f }, { 0.35f, 0.35f, 0.45f }, 35.f, 200.f, - 1.f, 0.6f, 800.f, 0.5f }, - { "sunset", { 0.18f, 0.3f, 0.52f }, { 0.62f, 0.42f, 0.38f }, 1.4f, 0.32f, 0.00025f, 906.f, - { 2.2f, 0.001f, -0.62f }, 0.32f, { 1.25f, 0.62f, 0.31f }, { 0.16f, 0.12f, 0.14f }, 3.f, 260.f, 1.f, 0.f, 800.f, - 0.f }, - { "night, moonlit", { 0.12f, 0.18f, 0.38f }, { 0.18f, 0.22f, 0.36f }, 0.5f, 0.12f, 0.00018f, 1200.f, - { 5.f, 0.001f, -0.4799f }, 0.2f, { 0.21f, 0.245f, 0.35f }, { 0.04f, 0.045f, 0.08f }, 35.f, 140.f, 0.125f, 0.f, - 800.f, 0.f }, - }; + const Sky* const DOME_SKIES[] = { &SKIES[0], &RAINBOW_SKY, &SKIES[1], &SKIES[2] }; - // A view: the camera's bearing clockwise from north, and its pitch above the horizon, in degrees. - struct View - { - F32 mBearing; - F32 mPitch; - }; + // The views' vertical field of view, in degrees: a tangent of 0.6 at its half. + const F32 FOV = 2.f * atanf(0.6f) * RAD_TO_DEG; // Toward the light and away from it at the horizon, up into the cap, and down onto water. std::array views(const Sky& sky) { - return { View{ sky.mAzimuth, 0.f }, View{ sky.mAzimuth + 180.f, -4.f }, View{ sky.mAzimuth + 60.f, 35.f }, - View{ sky.mAzimuth - 30.f, -22.f } }; - } - - // The light in the dome's frame, (north, up, east). - LLVector3 lightNorm(const Sky& sky) - { - const F32 el = sky.mElevation * DEG_TO_RAD; - const F32 az = sky.mAzimuth * DEG_TO_RAD; - return LLVector3(cosf(az) * cosf(el), sinf(el), sinf(az) * cosf(el)); + return { View{ sky.mAzimuth, 0.f, FOV }, View{ sky.mAzimuth + 180.f, -4.f, FOV }, + View{ sky.mAzimuth + 60.f, 35.f, FOV }, View{ sky.mAzimuth - 30.f, -22.f, FOV } }; } // The analytic pass: a screen triangle, each pixel's view ray in eye space turned into the dome's frame. @@ -160,6 +78,78 @@ namespace " frag_color = vec4(windlightDome(test_eye_to_sky * normalize(eye)), 1.0);\n" "}\n"; + // The haze pass's screen triangle (deferred/hazeV.glsl) without the atmospherics' varyings. + const char* kHazeVertex = R"(/*[EXTRA_CODE_HERE]*/ + +in vec3 position; + +out vec2 vary_fragcoord; + +void main() +{ + gl_Position = vec4(position.xy, 0.0, 1.0); + vary_fragcoord = position.xy * 0.5 + 0.5; +} +)"; + + // What the haze pass reads of the scene and of WindLight's in-scatter, made up so that the dome's weight runs + // across the screen: none over the left quarter, then the square of the distance from it, a little less in green and + // blue. The water lies 1 km out along each pixel's ray, and WindLight's in-scatter grows with the weight. + const char* kHazeScene = R"(/*[EXTRA_CODE_HERE]*/ + +in vec2 vary_fragcoord; + +uniform vec2 test_tan_half; + +float getDepth(vec2 tc) +{ + return 0.5; +} + +bool isFarDepth(float d) +{ + return false; +} + +vec4 getNorm(vec2 tc) +{ + return vec4(0.0, 0.0, 1.0, 0.0); +} + +vec4 getPositionWithDepth(vec2 tc, float depth) +{ + return vec4(normalize(vec3((tc * 2.0 - 1.0) * test_tan_half, -1.0)) * 1000.0, 1.0); +} + +void calcAtmosphericVarsLinear(vec3 inPositionEye, vec3 norm, vec3 light_dir, out vec3 sunlit, out vec3 amblit, + out vec3 additive, out vec3 atten) +{ + float x = max(vary_fragcoord.x - 0.25, 0.0); + vec3 weight = vec3(1.0, 0.9, 0.8) * (x * x); + atten = vec3(1.0) - weight; + additive = vec3(0.5, 0.6, 0.7) * weight; + sunlit = vec3(0.0); + amblit = vec3(0.0); +} + +vec3 getAdditiveColor() +{ + return vec3(0.0); +} + +vec3 getAtmosAttenuation() +{ + return vec3(1.0); +} +)"; + + // The weight the haze scene gives the dome at column @a x, in its strongest channel. + F32 hazeWeight(S32 x) + { + const F32 from = llmax(((F32)x + 0.5f) / (F32)W - 0.25f, 0.f); + return from * from; + } + struct alskydome_data { alskydome_data() @@ -188,59 +178,21 @@ namespace gGL.loadIdentity(); } - // A smooth texture, so a lookup's place shows in what it reads. - static LLPointer gradient(U32 seed) - { - constexpr S32 SIZE = 64; - LLPointer raw = new LLImageRaw(SIZE, SIZE, 4); - U8* data = raw->getData(); - for (S32 y = 0; y < SIZE; ++y) - { - for (S32 x = 0; x < SIZE; ++x) - { - const F32 u = (F32)x / (SIZE - 1); - const F32 v = (F32)y / (SIZE - 1); - U8* px = data + (y * SIZE + x) * 4; - px[0] = (U8)std::lround(255.f * (0.5f + 0.5f * sinf(6.f * v + (F32)seed))); - px[1] = (U8)std::lround(255.f * (0.5f + 0.5f * cosf(5.f * v + 2.f * u))); - px[2] = (U8)std::lround(255.f * v); - px[3] = 255; - } - } - return new LLImageGL(raw, false); - } - // The sky program as LLViewerShaderMgr built it, without the emissive buffer, so it writes the dome's colour - // to the albedo attachment as it is; the analytic pass; the dome's mesh as LLVOWLSky fills it; and a float - // target the size of the view. + // to the albedo attachment as it is; the analytic pass; the dome's mesh; and a float target the size of the + // view. void setUp() { mTree.populate(); - std::map defines; - for (const char* object : { "deferred/textureUtilV.glsl", "objects/nonindexedTextureV.glsl" }) - { - CAPTURE(object); - S32 level = 1; - REQUIRE(mShaders.loadShaderFile(object, level, GL_VERTEX_SHADER, &defines) != 0); - } - for (const char* object : { "deferred/globalF.glsl", "environment/srgbF.glsl" }) - { - CAPTURE(object); - S32 level = 1; - REQUIRE(mShaders.loadShaderFile(object, level, GL_FRAGMENT_SHADER, &defines) != 0); - } - - mSky.mName = "Deferred Windlight Sky Shader"; - mSky.mFeatures.hasSrgb = true; - mSky.mShaderFiles.clear(); - mSky.mShaderFiles.push_back(std::make_pair("skyraster/deferred/skyV.glsl", GL_VERTEX_SHADER)); - mSky.mShaderFiles.push_back(std::make_pair("skyraster/deferred/skyF.glsl", GL_FRAGMENT_SHADER)); - mSky.mShaderFiles.push_back(std::make_pair("windlight/windlightDomeF.glsl", GL_FRAGMENT_SHADER)); - mSky.mShaderLevel = 1; - UNSCOPED_INFO("the sky program builds"); - REQUIRE(mSky.createShader()); + loadSharedObjects(mShaders); + buildProgram(mSky, "Deferred Windlight Sky Shader", + { { "skyraster/deferred/skyV.glsl", GL_VERTEX_SHADER }, + { "skyraster/deferred/skyF.glsl", GL_FRAGMENT_SHADER }, + { "windlight/windlightDomeF.glsl", GL_FRAGMENT_SHADER } }, + false); { + std::map defines; S32 level = 1; const GLuint dome = mShaders.loadShaderFile("windlight/windlightDomeF.glsl", level, GL_FRAGMENT_SHADER, &defines); UNSCOPED_INFO("windlightDomeF compiles"); @@ -263,173 +215,40 @@ namespace REQUIRE(mRay.mapUniforms()); } - const U32 verts = ALSkyDome::vertexCount(ALSkyDome::STACKS); - const U32 indices = ALSkyDome::indexCount(ALSkyDome::STACKS); - mDome = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0); - REQUIRE(mDome->allocateBuffer(verts, indices)); - LLStrider vertices; - LLStrider tex_coords; - LLStrider index; - REQUIRE(mDome->getVertexStrider(vertices)); - REQUIRE(mDome->getTexCoord0Strider(tex_coords)); - REQUIRE(mDome->getIndexStrider(index)); - ALSkyDome::buildStrips(0, ALSkyDome::STACKS, DOME_RADIUS, vertices, tex_coords, index); - mDome->unmapBuffer(); - - mTriangle = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX); - REQUIRE(mTriangle->allocateBuffer(3, 0)); - LLStrider vert; - mTriangle->getVertexStrider(vert); - vert[0].set(-1.f, 1.f, 0.f); - vert[1].set(-1.f, -3.f, 0.f); - vert[2].set(3.f, 1.f, 0.f); - mTriangle->unmapBuffer(); - - mRainbow = gradient(1); - mHalo = gradient(4); + mDome = domeMesh(); + mTriangle = screenTriangle(); + mRainbow = gradient(1); + mHalo = gradient(4); REQUIRE(mFull.allocate(W, H, GL_RGBA32F)); glGenBuffers(1, &mEnvironment); } - // The Environment block as LLEnvironment packs it from @a sky (LLSettingsVOSky::applySpecial's terms), at the - // offsets the linked program gives each member, and bound where every program reads it. - void bindEnvironment(const Sky& sky) + // The sky program over the mesh into the float target, every band drawn on the far plane in order, so the + // later covers the earlier where they overlap. Read back. + std::vector raster(const Sky& sky, const View& view) { - const GLuint program = mSky.mProgramObject; - const GLuint block = glGetUniformBlockIndex(program, "Environment"); - REQUIRE(block != GL_INVALID_INDEX); - GLint size = 0; - glGetActiveUniformBlockiv(program, block, GL_UNIFORM_BLOCK_DATA_SIZE, &size); - REQUIRE(size > 0); - std::vector data((size_t)size, 0); - const auto put = [&](const char* name, const F32* value, S32 count) - { - GLuint index = GL_INVALID_INDEX; - glGetUniformIndices(program, 1, &name, &index); - if (index == GL_INVALID_INDEX) - { - return; - } - GLint offset = -1; - glGetActiveUniformsiv(program, 1, &index, GL_UNIFORM_OFFSET, &offset); - REQUIRE(offset >= 0); - REQUIRE((size_t)offset + count * sizeof(F32) <= data.size()); - std::memcpy(data.data() + offset, value, count * sizeof(F32)); - }; - const auto put3 = [&](const char* name, const LLVector3& v) { put(name, v.mV, 3); }; - const auto put1 = [&](const char* name, F32 v) { put(name, &v, 1); }; - - LLVector3 extinction; - LLVector3 blue_weight; - LLVector3 haze_weight; - LLVector3 light_atten; - LLVector3 ambient_below; - for (S32 c = 0; c < 3; ++c) - { - extinction.mV[c] = llmax(fabsf(sky.mBlueDensity.mV[c]) + fabsf(sky.mHazeDensity), 1e-6f); - blue_weight.mV[c] = sky.mBlueDensity.mV[c] / extinction.mV[c]; - haze_weight.mV[c] = sky.mHazeDensity / extinction.mV[c]; - light_atten.mV[c] = (sky.mBlueDensity.mV[c] + sky.mHazeDensity * 0.25f) * (sky.mDensityMultiplier * sky.mMaxY); - ambient_below.mV[c] = sky.mAmbient.mV[c] + llmax(0.f, 1.f - sky.mAmbient.mV[c]) * sky.mCloudShadow * 0.5f; - } - put3("blue_density", sky.mBlueDensity); - put1("haze_density", sky.mHazeDensity); - put3("blue_horizon", sky.mBlueHorizon); - put1("haze_horizon", sky.mHazeHorizon); - put3("glow", sky.mGlow); - put1("max_y", sky.mMaxY); - put1("cloud_shadow", sky.mCloudShadow); - put3("haze_extinction", extinction); - put3("blue_weight", blue_weight); - put3("haze_weight", haze_weight); - put3("sky_light", sky.mLight); - put3("sky_ambient", sky.mAmbient); - put3("sky_light_atten", light_atten); - put3("sky_ambient_below", ambient_below); - - glBindBuffer(GL_UNIFORM_BUFFER, mEnvironment); - glBufferData(GL_UNIFORM_BUFFER, size, data.data(), GL_STATIC_DRAW); - glBindBuffer(GL_UNIFORM_BUFFER, 0); - glBindBufferBase(GL_UNIFORM_BUFFER, LLGLSLShader::UB_ENVIRONMENT, mEnvironment); - } - - // The dome's loose uniforms and maps, as LLDrawPoolWLSky::bindDome binds them, on the bound @a program. - void bindDome(LLGLSLShader& program, const Sky& sky) - { - const LLVector3 light = lightNorm(sky); - program.uniform3fv(LLShaderMgr::LIGHTNORM, 1, light.mV); - program.uniform1f(LLShaderMgr::DENSITY_MULTIPLIER, sky.mDensityMultiplier); - program.uniform1f(LLShaderMgr::SUN_MOON_GLOW_FACTOR, sky.mGlowFactor); - program.uniform1f(LLShaderMgr::MOISTURE_LEVEL, sky.mMoisture); - program.uniform1f(LLShaderMgr::DROPLET_RADIUS, sky.mDroplet); - program.uniform1f(LLShaderMgr::ICE_LEVEL, sky.mIce); - program.uniform3f(LLShaderMgr::WL_CAMPOSLOCAL, 0.f, CAMERA_HEIGHT, 0.f); - program.uniform1f(LLShaderMgr::SKY_DOME_RADIUS, DOME_RADIUS); - for (const auto& [uniform, map] : { std::make_pair(LLShaderMgr::RAINBOW_MAP, mRainbow.get()), - std::make_pair(LLShaderMgr::HALO_MAP, mHalo.get()) }) - { - const S32 channel = program.enableTexture(uniform); - if (channel > -1) - { - gGL.getTextureSlot(channel)->bindSampled(map, ALSamplers::AnisoWrap); - } - } - } - - // The camera looking along @a view from CAMERA: the agent's frame (east, north, up) turned to the eye's. - static void loadView(const View& view) - { - gGL.matrixMode(LLRender::MM_PROJECTION); - LLMatrix4a proj; - proj.clear(); - F32* p = proj.getF32ptr(); - p[0] = 1.f / (TAN_HALF_FOV * (F32)W / (F32)H); - p[5] = 1.f / TAN_HALF_FOV; - p[10] = (FAR_CLIP + NEAR_CLIP) / (NEAR_CLIP - FAR_CLIP); - p[11] = -1.f; - p[14] = 2.f * FAR_CLIP * NEAR_CLIP / (NEAR_CLIP - FAR_CLIP); - gGL.loadMatrix(proj); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.loadIdentity(); - gGL.rotatef(-view.mPitch, 1.f, 0.f, 0.f); - gGL.rotatef(-90.f, 1.f, 0.f, 0.f); - gGL.rotatef(view.mBearing, 0.f, 0.f, 1.f); - gGL.translatef(-CAMERA.mV[VX], -CAMERA.mV[VY], -CAMERA.mV[VZ]); - } - - // The sky program over the mesh into @a target, as LLDrawPoolWLSky::renderDome drew it: the dome taken to the - // camera, turned from its frame to the agent's and lowered by the camera's height, every band drawn on the far - // plane in order, so the later covers the earlier where they overlap. Read back. - std::vector raster(LLRenderTarget& target, const Sky& sky, const View& view) - { - target.bindTarget(); + mFull.bindTarget(); gGL.setClearColor(LLColor4(-1.f, -1.f, -1.f, 0.f)); - target.clear(GL_COLOR_BUFFER_BIT); + mFull.clear(GL_COLOR_BUFFER_BIT); gGL.setClearColor(LLColor4::transparent); - loadView(view); + loadView(view, W, H); + bindEnvironment(mSky, mEnvironment, sky, ALFarPlane::eyeToSkyFrame(gGL.getModelviewMatrix())); mSky.bind(); - bindDome(mSky, sky); - gGL.pushMatrix(); - gGL.translatef(CAMERA.mV[VX], CAMERA.mV[VY], CAMERA.mV[VZ]); - gGL.rotatef(120.f, 1.f / F_SQRT3, 1.f / F_SQRT3, 1.f / F_SQRT3); - gGL.scalef(0.333f, 0.333f, 0.333f); - gGL.translatef(0.f, -CAMERA_HEIGHT, 0.f); + bindDome(mSky, sky, mRainbow, mHalo); { - LLGLDisable blend(GL_BLEND); - LLGLDisable cull(GL_CULL_FACE); - LLGLDepthTest no_depth(GL_FALSE, GL_FALSE); - mDome->setBuffer(); - mDome->drawRange(LLRender::TRIANGLE_STRIP, 0, mDome->getNumVerts() - 1, mDome->getNumIndices(), 0); + LLGLDisable blend(GL_BLEND); + LLGLDisable cull(GL_CULL_FACE); + LLGLDepthTest no_depth(GL_FALSE, GL_FALSE); + drawDome(mDome); } - gGL.popMatrix(); mSky.unbind(); - std::vector out(size_t(target.getWidth()) * target.getHeight() * 4); - glReadPixels(0, 0, target.getWidth(), target.getHeight(), GL_RGBA, GL_FLOAT, out.data()); - target.flush(); + std::vector out(size_t(W) * H * 4); + glReadPixels(0, 0, W, H, GL_RGBA, GL_FLOAT, out.data()); + mFull.flush(); return out; } @@ -442,12 +261,13 @@ namespace mFull.clear(GL_COLOR_BUFFER_BIT); gGL.setClearColor(LLColor4::transparent); - loadView(view); + loadView(view, W, H); mEyeToSky = ALFarPlane::eyeToSkyFrame(gGL.getModelviewMatrix()); + bindEnvironment(mRay, mEnvironment, sky, mEyeToSky); mRay.bind(); - bindDome(mRay, sky); - glUniform2f(glGetUniformLocation(mRay.mProgramObject, "test_tan_half"), TAN_HALF_FOV * (F32)W / (F32)H, - TAN_HALF_FOV); + bindDome(mRay, sky, mRainbow, mHalo); + const F32 tan_half = tanf(view.mFov * 0.5f * DEG_TO_RAD); + glUniform2f(glGetUniformLocation(mRay.mProgramObject, "test_tan_half"), tan_half * (F32)W / (F32)H, tan_half); glUniformMatrix3fv(glGetUniformLocation(mRay.mProgramObject, "test_eye_to_sky"), 1, GL_FALSE, mEyeToSky.data()); { LLGLDisable blend(GL_BLEND); @@ -463,39 +283,24 @@ namespace return out; } - // Pixel (x, y)'s view ray in the dome's frame, as the ray pass works it. - LLVector3 domeRay(S32 x, S32 y) const - { - const F32 nx = ((F32)x + 0.5f) / (F32)W * 2.f - 1.f; - const F32 ny = ((F32)y + 0.5f) / (F32)H * 2.f - 1.f; - LLVector3 eye(nx * TAN_HALF_FOV * (F32)W / (F32)H, ny * TAN_HALF_FOV, -1.f); - eye.normVec(); - LLVector3 dir; - for (S32 r = 0; r < 3; ++r) - { - dir.mV[r] = mEyeToSky[0 * 3 + r] * eye.mV[0] + mEyeToSky[1 * 3 + r] * eye.mV[1] + mEyeToSky[2 * 3 + r] * eye.mV[2]; - } - return dir; - } - static LLVector3 colour(const std::vector& image, S32 x, S32 y) { const F32* p = &image[(size_t(y) * W + x) * 4]; return LLVector3(p[0], p[1], p[2]); } - al_sky_test::SkyShaderTree mTree; - ll_test::SharedGLScope mGL; - ll_test::TestShaderMgr mShaders; - LLGLSLShader mSky; - LLGLSLShader mRay; - LLRenderTarget mFull; + SkyShaderTree mTree; + ll_test::SharedGLScope mGL; + ll_test::TestShaderMgr mShaders; + LLGLSLShader mSky; + LLGLSLShader mRay; + LLRenderTarget mFull; LLPointer mDome; LLPointer mTriangle; - LLPointer mRainbow; - LLPointer mHalo; - GLuint mEnvironment = 0; - std::array mEyeToSky{}; + LLPointer mRainbow; + LLPointer mHalo; + GLuint mEnvironment = 0; + std::array mEyeToSky{}; }; // How far windlightDome may lie from the raster dome, relative to the brighter of the two (or to RELATIVE_FLOOR, @@ -525,6 +330,13 @@ namespace return worst; } + // The haze's weight floor, below which it leaves the dome out (class3/deferred/hazeF.glsl's HAZE_DOME_MIN_WEIGHT), + // and what it allows there: 0.94% of the in-scatter, or 2e-5 times WindLight's HDR scale. + constexpr F32 HAZE_FLOOR = 1.f / 256.f; + constexpr F32 HAZE_RELATIVE = 0.0094f; + constexpr F32 HAZE_ABSOLUTE = 2e-5f; + constexpr F32 SKY_HDR_SCALE = 1.25f; + // A map whose every texel is infinite, so that any read of it shows, even one weighed by zero. LLPointer infiniteMap() { @@ -545,14 +357,14 @@ TEST_CASE_METHOD(alskydome_data, "alskydome_gl: windlightDome is the raster dome setUp(); S32 cap_pixels = 0; S32 skirt_pixels = 0; - for (const Sky& sky : SKIES) + for (const Sky* entry : DOME_SKIES) { - bindEnvironment(sky); + const Sky& sky = *entry; const bool bands = sky.mMoisture > 0.f || sky.mIce > 0.f; for (const View& view : views(sky)) { CAPTURE(sky.mName, view.mBearing, view.mPitch); - const std::vector mesh = raster(mFull, sky, view); + const std::vector mesh = raster(sky, view); const std::vector ray = analytic(sky, view); F32 cap = 0.f; @@ -569,7 +381,7 @@ TEST_CASE_METHOD(alskydome_data, "alskydome_gl: windlightDome is the raster dome FAIL("the mesh leaves a pixel uncovered"); } const F32 diff = relativeDifference(colour(ray, x, y), m); - const F32 rise = domeRay(x, y).mV[VY]; + const F32 rise = domeRay(view, mEyeToSky, W, H, x, y).mV[VY]; if (rise >= 0.f) { cap = llmax(cap, diff); @@ -602,14 +414,13 @@ TEST_CASE_METHOD(alskydome_data, "alskydome_gl: windlightDome reads the rainbow' const LLPointer halo = mHalo; const LLPointer poison = infiniteMap(); // The rainbow and halo sky, with each of its two strengths and with neither. - const Sky& both = SKIES[1]; + const Sky& both = RAINBOW_SKY; for (const auto& [moisture, ice] : { std::make_pair(0.f, 0.f), std::make_pair(both.mMoisture, 0.f), std::make_pair(0.f, both.mIce), std::make_pair(both.mMoisture, both.mIce) }) { Sky sky = both; sky.mMoisture = moisture; sky.mIce = ice; - bindEnvironment(sky); const View view = views(sky)[1]; mRainbow = rainbow; mHalo = halo; @@ -645,3 +456,109 @@ TEST_CASE_METHOD(alskydome_data, "alskydome_gl: windlightDome reads the rainbow' mRainbow = rainbow; mHalo = halo; } + +TEST_CASE_METHOD(alskydome_data, "alskydome_gl: the haze on water leaves the dome out only where its weight cannot show", + "[alskydome_gl]") +{ + setUp(); + // The haze on water as the viewer builds it, and the same with its weight floor at zero, so that it converges on + // the dome at every pixel, over the made-up scene. + std::error_code ec; + const std::string haze = LLFile::getContents(std::string(AL_TEST_SHADER_DIR) + "/class3/deferred/hazeF.glsl", ec); + REQUIRE(!ec); + const std::string floor_test = ">= HAZE_DOME_MIN_WEIGHT"; + const size_t at = haze.find(floor_test); + UNSCOPED_INFO("the haze tests the dome's weight against its floor"); + REQUIRE(at != std::string::npos); + std::string everywhere = haze; + everywhere.replace(at, floor_test.size(), ">= 0.0"); + REQUIRE(mTree.write("skytest/hazeF.glsl", haze)); + REQUIRE(mTree.write("skytest/hazeEverywhereF.glsl", everywhere)); + REQUIRE(mTree.write("skytest/hazeV.glsl", kHazeVertex)); + REQUIRE(mTree.write("skytest/hazeSceneF.glsl", kHazeScene)); + LLGLSLShader programs[2]; + for (S32 floored = 0; floored < 2; ++floored) + { + buildProgram(programs[floored], floored ? "Haze On Water Shader (test)" : "Haze On Water Shader, no floor (test)", + { { "skytest/hazeV.glsl", GL_VERTEX_SHADER }, + { floored ? "skytest/hazeF.glsl" : "skytest/hazeEverywhereF.glsl", GL_FRAGMENT_SHADER }, + { "skytest/hazeSceneF.glsl", GL_FRAGMENT_SHADER }, + { "windlight/atmosphericsF.glsl", GL_FRAGMENT_SHADER }, + { "windlight/windlightDomeF.glsl", GL_FRAGMENT_SHADER } }, + false, { { "HAZE_ON_WATER", "1" } }); + } + + // Down onto the water toward a low sun, where the dome behind it glows. + const Sky& sky = SKIES[1]; + const View view{ sky.mAzimuth, -10.f, FOV }; + std::vector pictures[2]; + for (S32 floored = 0; floored < 2; ++floored) + { + LLGLSLShader& program = programs[floored]; + mFull.bindTarget(); + loadView(view, W, H); + bindEnvironment(program, mEnvironment, sky, ALFarPlane::eyeToSkyFrame(gGL.getModelviewMatrix())); + program.bind(); + bindDome(program, sky, mRainbow, mHalo); + program.uniform1f(LLShaderMgr::SKY_HDR_SCALE, SKY_HDR_SCALE); + const F32 tan_half = tanf(view.mFov * 0.5f * DEG_TO_RAD); + glUniform2f(glGetUniformLocation(program.mProgramObject, "test_tan_half"), tan_half * (F32)W / (F32)H, tan_half); + glUniform4f(glGetUniformLocation(program.mProgramObject, "waterPlane"), 0.f, 0.f, 0.f, 1.f); + glUniform1f(glGetUniformLocation(program.mProgramObject, "waterGroundFade"), 0.f); + { + LLGLDisable blend(GL_BLEND); + LLGLDepthTest no_depth(GL_FALSE, GL_FALSE); + mTriangle->setBuffer(); + mTriangle->drawArrays(LLRender::TRIANGLES, 0, 3); + } + program.unbind(); + pictures[floored].resize(size_t(W) * H * 4); + glReadPixels(0, 0, W, H, GL_RGBA, GL_FLOAT, pictures[floored].data()); + mFull.flush(); + } + + // Where the weight is zero, or at the floor and past it, the two are one picture; under the floor they differ by + // what the floor allows. + S32 none = 0; + S32 under = 0; + S32 over = 0; + S32 apart = 0; + F32 most = 0.f; + for (S32 y = 0; y < H; ++y) + { + for (S32 x = 0; x < W; ++x) + { + const F32 weight = hazeWeight(x); + const bool skipped = weight > 0.f && weight < HAZE_FLOOR; + none += weight == 0.f; + under += skipped; + over += weight >= HAZE_FLOOR; + for (S32 c = 0; c < 3; ++c) + { + const size_t i = (size_t(y) * W + x) * 4 + c; + const F32 floored = pictures[1][i]; + const F32 converged = pictures[0][i]; + const F32 diff = fabsf(floored - converged); + if (skipped) + { + most = llmax(most, diff / llmax(converged, 1e-6f)); + apart += diff > llmax(HAZE_RELATIVE * converged, HAZE_ABSOLUTE * SKY_HDR_SCALE); + } + else + { + apart += floored != converged; + } + } + } + } + CAPTURE(none, under, over, most); + UNSCOPED_INFO("the scene runs the weight from zero through the floor"); + CHECK(none > 0); + CHECK(under > 0); + CHECK(over > 0); + CHECK(apart == 0); + for (LLGLSLShader& program : programs) + { + program.unload(); + } +} diff --git a/indra/alrender/tests/alskylayers_gl_test.cpp b/indra/alrender/tests/alskylayers_gl_test.cpp index b55a3b54504..3c91faa5323 100644 --- a/indra/alrender/tests/alskylayers_gl_test.cpp +++ b/indra/alrender/tests/alskylayers_gl_test.cpp @@ -267,7 +267,7 @@ void main() { 35.f, 240.f, 0.1f, 90.f, LLColor4U(255, 255, 255, 0), 1.0f }, }; - // The meteors as LLVOWLSky ticks them: at the dome's radius about the camera, each a streak of its half-length + // The meteors as ALSky ticks them: at the dome's radius about the camera, each a streak of its half-length // along a tangent at its heading, its width a quarter in its colour's alpha. The one end on has its half along // its own direction. std::vector meteors() @@ -487,7 +487,7 @@ void main() return out; } - // The sun's quad, as LLVOSky makes it. + // The sun's quad, as ALSky makes it. static ALSkyLayers::Corners sunCorners(const BodyCase& sun) { return ALSkyLayers::bodyCorners(bodyRotation(sun.mElevation, sun.mBearing), BODY_DISTANCE, sun.mScale, diff --git a/indra/alrender/tests/alskylayers_test.cpp b/indra/alrender/tests/alskylayers_test.cpp index 9d8d2a2fd00..cb9da9432a3 100644 --- a/indra/alrender/tests/alskylayers_test.cpp +++ b/indra/alrender/tests/alskylayers_test.cpp @@ -45,7 +45,7 @@ namespace { - // LLVOSky's HEAVENLY_BODY_DIST, and the sun's disk radius. + // ALSky's HEAVENLY_BODY_DIST, and the sun's disk radius. constexpr F32 BODY_DISTANCE = 1004.f; constexpr F32 SUN_DISK_RADIUS = 0.5f; @@ -60,6 +60,28 @@ namespace { return ALFarPlane::skyFrame(agent); } + + // The texture coordinates, unclipped, where @a dir, a direction from the camera in the dome's frame, meets the + // plane of @a quad (ALSkyLayers::rayQuad's), as deferred/skyBodiesF.glsl's skyQuadCoord works them out; false, and + // the coordinates unset, where it runs parallel to the plane or away from it. + bool quadCoord(const std::array& quad, const LLVector3& dir, F32& u, F32& v) + { + const LLVector3 normal = quad[1] % quad[2]; + const F32 facing = dir * normal; + if (fabsf(facing) < 1e-12f) + { + return false; + } + const F32 t = (quad[0] * normal) / facing; + if (t <= 0.f) + { + return false; + } + const LLVector3 offset = dir * t - quad[0]; + u = (offset * quad[1]) * 0.5f + 0.5f; + v = (offset * quad[2]) * 0.5f + 0.5f; + return true; + } } TEST_CASE("alskylayers: a body's quad is centred along it and square to it and grows toward the horizon", "[alskylayers]") @@ -121,7 +143,7 @@ TEST_CASE("alskylayers: a ray meets a quad at the texture coordinates of the poi const LLVector3 point = (i < 4 ? c[i] : (c[0] + c[3]) * 0.5f) + offset; F32 u = -1.f; F32 v = -1.f; - REQUIRE(ALSkyLayers::quadCoord(quad, dome(point / point.length()), u, v)); + REQUIRE(quadCoord(quad, dome(point / point.length()), u, v)); CHECK(fabsf(u - expected[i][0]) < 1e-4f); CHECK(fabsf(v - expected[i][1]) < 1e-4f); } @@ -129,9 +151,9 @@ TEST_CASE("alskylayers: a ray meets a quad at the texture coordinates of the poi // A ray away from the quad, or along its plane, misses it. F32 u = 0.f; F32 v = 0.f; - CHECK_FALSE(ALSkyLayers::quadCoord(quad, dome(-dir), u, v)); + CHECK_FALSE(quadCoord(quad, dome(-dir), u, v)); const LLVector3 along = (c[2] - c[0]) / (c[2] - c[0]).length(); - CHECK_FALSE(ALSkyLayers::quadCoord(quad, dome(along), u, v)); + CHECK_FALSE(quadCoord(quad, dome(along), u, v)); } // The dome's frame is the agent's (east, north, up) turned to (north, up, east). diff --git a/indra/alrender/tests/alskyraster_gl.h b/indra/alrender/tests/alskyraster_gl.h index e69518b975e..635a08a2061 100644 --- a/indra/alrender/tests/alskyraster_gl.h +++ b/indra/alrender/tests/alskyraster_gl.h @@ -64,7 +64,7 @@ namespace al_sky_test constexpr F32 DOME_RADIUS = 15000.f; constexpr F32 CAMERA_HEIGHT = 0.96f * 15000.f; - // LLVOSky's HEAVENLY_BODY_DIST (HORIZON_DIST less 20 m), and the sun's and moon's disk radii. + // ALSky's HEAVENLY_BODY_DIST (HORIZON_DIST less 20 m), and the sun's and moon's disk radii. constexpr F32 BODY_DISTANCE = 1024.f - 20.f; constexpr F32 SUN_DISK_RADIUS = 0.5f; constexpr F32 MOON_DISK_RADIUS = SUN_DISK_RADIUS * 0.9f; @@ -121,6 +121,15 @@ namespace al_sky_test 800.f, 0.f, { 0.22f, 0.23f, 0.3f }, { 0.81f, 0.13f, 0.76f }, { 0.09f, 0.55f, 0.11f }, 0.35f, 0.2f }, }; + // The clouded midday with a rainbow's moisture and a halo's ice, which the dome draws in bands below the horizon + // too. + inline const Sky RAINBOW_SKY = { "midday, rainbow and halo", { 0.2447f, 0.4487f, 0.7599f }, + { 0.4954f, 0.4954f, 0.6399f }, 0.7f, 0.19f, 0.0001f, 1605.f, + { 5.f, 0.001f, -0.4799f }, 0.27f, { 0.7342f, 0.7815f, 0.8999f }, + { 0.35f, 0.35f, 0.45f }, 35.f, 200.f, 1.f, 0.6f, 800.f, 0.5f, + { 0.41f, 0.41f, 0.41f }, { 1.6869f, 0.5269f, 0.88f }, { 0.07f, 0.4f, 0.125f }, + 0.4199f, 0.27f }; + // A view: the camera's bearing clockwise from north, its pitch above the horizon and its vertical field of view, in // degrees. struct View @@ -427,7 +436,7 @@ namespace al_sky_test } } - // The dome's loose uniforms and maps, as LLDrawPoolWLSky::bindDome binds them, on the bound @a program. + // The dome's loose uniforms and maps, as ALSky::bindDome binds them, on the bound @a program. inline void bindDome(LLGLSLShader& program, const Sky& sky, LLImageGL* rainbow, LLImageGL* halo) { const LLVector3 light = lightNorm(sky); diff --git a/indra/alrender/tests/alstarfield_gl_test.cpp b/indra/alrender/tests/alstarfield_gl_test.cpp index e056aaa5115..f351be4d427 100644 --- a/indra/alrender/tests/alstarfield_gl_test.cpp +++ b/indra/alrender/tests/alstarfield_gl_test.cpp @@ -2,7 +2,7 @@ * @file alstarfield_gl_test.cpp * @brief The stars two ways on the hidden window, from the same fixed-seed field: as they were drawn before their * rework (a quad of six vertices a star, every term worked per fragment, into all four attachments), and as - * LLDrawPoolWLSky draws them now (a point a star, a quad for each spiked one, the cells in view, the stars bright + * ALSky draws them now (a point a star, a quad for each spiked one, the cells in view, the stars bright * enough to show, into the one attachment the sky's colour is in). The two must leave the same picture. * * $LicenseInfo:firstyear=2026&license=viewerlgpl$ @@ -76,7 +76,7 @@ namespace const F32 NO_COLOUR[4] = { 0.f, 0.f, 0.f, 1.f }; const F32 SKY_FLAGS[4] = { 0.f, 0.f, 0.f, 0.f }; - // Half a step of 8 bits: what LLDrawPoolWLSky's brightness cull lets a left-out star add, at most. + // Half a step of 8 bits: what ALSky's brightness cull lets a left-out star add, at most. constexpr F32 HALF_QUANTUM = 0.5f / 255.f; // A frame of the night sky: the camera's bearing clockwise from north, its pitch above the horizon and its @@ -445,7 +445,7 @@ void main() } // The field at the default count; the old buffer, six vertices a star with their corner offsets, as - // LLVOWLSky::updateStarGeometry filled it, and the buffer it fills now; both paths' programs; and a G-buffer + // LLVOWLSky::updateStarGeometry filled it, and the buffer ALSky fills now; both paths' programs; and a G-buffer // for each, the emissive one with four attachments and the albedo's with three. void setUp() { @@ -538,7 +538,7 @@ void main() gGL.translatef(-CAMERA.mV[VX], -CAMERA.mV[VY], -CAMERA.mV[VZ]); } - // The star uniforms on the bound @a program, as LLDrawPoolWLSky::renderStarsDeferred sets them, in the stars' + // The star uniforms on the bound @a program, as ALSky::renderStars sets them, in the stars' // frame; the moon's mask only where @a mask_moon. void bindUniforms(LLGLSLShader& program, const Scene& scene, bool mask_moon = true) { @@ -629,7 +629,7 @@ void main() return picture; } - // The stars drawn as LLDrawPoolWLSky::renderStarsDeferred draws them, into the G-buffer of @a emissive's path; + // The stars drawn as ALSky::renderStars draws them, into the G-buffer of @a emissive's path; // every point, the frustum's cull aside, where @a everything, and every star however faint where // @a every_star. Picture drawStars(const Scene& scene, bool emissive, bool everything = false, bool every_star = false) diff --git a/indra/alrender/tests/alstarfield_test.cpp b/indra/alrender/tests/alstarfield_test.cpp index fff2ea5119f..1757d63f80e 100644 --- a/indra/alrender/tests/alstarfield_test.cpp +++ b/indra/alrender/tests/alstarfield_test.cpp @@ -115,7 +115,7 @@ TEST_CASE("alstarfield: a count lays out the same field every time", "[alstarfie TEST_CASE("alstarfield: the default field is the one the fixed seed laid out before", "[alstarfield]") { - // The first stars of the default field, as LLVOWLSky::initStars laid them out from the fixed seed: a change of + // The first stars of the default field, as ALSky has them laid out from the fixed seed: a change of // seed, draw order or tier would move every image of the night sky the sky's A/B captures compare. struct Pinned { @@ -490,7 +490,7 @@ namespace }; // The stars' frame to clip space for @a view, built as LLRender builds it for the star draw: the view from a camera - // in the agent's frame, then the stars taken to the camera and turned (LLDrawPoolWLSky::renderStarsDeferred). + // in the agent's frame, then the stars taken to the camera and turned (ALSky::renderStars). LLMatrix4a starMVP(const View& view) { const LLVector3 camera(118.f, -43.f, 27.f); diff --git a/indra/llrender/altextureslot.cpp b/indra/llrender/altextureslot.cpp index adf6b0118d4..bade9f4393e 100644 --- a/indra/llrender/altextureslot.cpp +++ b/indra/llrender/altextureslot.cpp @@ -165,7 +165,14 @@ void ALTextureSlot::bindFast(LLTexture* texture, ALSampler key) { // getGLTexture() is virtual and this is the per-draw path, so it is resolved once and the // sampler is chosen from the result rather than re-fetching. - LLImageGL* gl_tex = texture->getGLTexture(); + LLImageGL* gl_tex = texture ? texture->getGLTexture() : nullptr; + if (!gl_tex) + { + // Nothing to bind: the slot lets go of what it holds, so the draw reads nothing an earlier + // bind left here. + unbindFast(); + return; + } bindFastImpl(texture, gl_tex, gGL.getSampler(key)); } diff --git a/indra/llrender/altextureslot.h b/indra/llrender/altextureslot.h index c1a71d23c21..18c83857e11 100644 --- a/indra/llrender/altextureslot.h +++ b/indra/llrender/altextureslot.h @@ -142,9 +142,9 @@ class ALTextureSlot // bind implementation for inner loops // makes the following assumptions: // - No need for gGL.flush() - // - texture is not null // - gl_tex->getTexName() is not zero // - This texture is not being bound redundantly + // A null texture, or one with no GL image, binds nothing and lets the slot go (unbindFast). // The CALLER names how it wants to sample; the resource contributes nothing. void bindFast(LLTexture* texture, ALSampler key); diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index 6a51560c315..55742e78921 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -2032,7 +2032,15 @@ S32 LLGLSLShader::bindTexture(S32 uniform, LLRenderTarget* texture, ALSampler ke if (uniform > -1) { - texture->bindTexture(index, uniform, key); + if (texture) + { + texture->bindTexture(index, uniform, key); + } + else + { + // No target binds nothing, and the unit is let go of what an earlier bind left there. + gGL.getTextureSlot(uniform)->unbind(); + } } return uniform; @@ -2055,7 +2063,12 @@ S32 LLGLSLShader::bindDepthTexture(S32 uniform, LLRenderTarget* texture, ALSampl uniform = getTextureChannel(uniform); - if (uniform > -1) + if (uniform > -1 && !texture) + { + // No target binds nothing, and the unit is let go of what an earlier bind left there. + gGL.getTextureSlot(uniform)->unbind(); + } + else if (uniform > -1) { // Clamp by default, and a shadow-compare sampler is never wanted here -- this is a // plain depth fetch. A repeat wrap on depth returns the opposite edge of the screen diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h index b5076820f05..9e545b270bc 100644 --- a/indra/llrender/llglslshader.h +++ b/indra/llrender/llglslshader.h @@ -469,6 +469,8 @@ class LLGLSLShader // bindTexture returns the texture unit we've bound the texture to. // You can reuse the return value to unbind a texture when required. + // A null texture or target binds nothing there, and the unit is let go of what it held; the + // unit is returned all the same. bindDepthTexture likewise. // // The sampler is NAMED BY THE CALLER, and there is no overload that infers it. How a pass // reads an image is a property of the pass: two materials can reference one texture and diff --git a/indra/llrender/tests/llglslshader_gl_test.cpp b/indra/llrender/tests/llglslshader_gl_test.cpp index 536ea464fc2..21ff1e26e58 100644 --- a/indra/llrender/tests/llglslshader_gl_test.cpp +++ b/indra/llrender/tests/llglslshader_gl_test.cpp @@ -7,7 +7,7 @@ * work group size, its dispatches direct, by thread count and indirect, the image units its reserved names * get, arrays included, the fixed storage binding points a buffer bound once serves every program at, and a * compaction drawn indirectly. The units every kind of sampler gets, integer textures read through reserved - * samplers, and what the debug validator says of images and stencil reads. And who owns a shader object: a program its own, deleted once it links, + * samplers, a bind given no texture, and what the debug validator says of images and stencil reads. And who owns a shader object: a program its own, deleted once it links, * the manager the shared ones. * * $LicenseInfo:firstyear=2026&license=viewerlgpl$ @@ -2074,6 +2074,73 @@ namespace CHECK(glGetError() == GLenum(GL_NO_ERROR)); } + // A program given no texture binds none. bindTexture with a null texture or render target, bindDepthTexture with a + // null target, and the slot's bindFast with a null texture each let the unit go, as the slot holding a texture + // another bind left there: its record says it holds nothing, GL's binding is no longer that texture, and the unit + // is returned as a bind returns it. The disable a reader pairs with its bind then finds nothing to release, under + // the debug checks too. + TEST_CASE_METHOD(llglslshader_data, "llglslshader_gl: bindTexture given no texture lets its unit go", "[llglslshader_gl]") + { + setUpShaders(); + constexpr U32 W = 4; + constexpr U32 H = 4; + REQUIRE(mTargets[0].allocate(W, H, GL_RGBA8)); + REQUIRE(buildProgram(mDraw, "null bind (test)", + { { "test/fullscreenV.glsl", GL_VERTEX_SHADER }, { "test/sampleF.glsl", GL_FRAGMENT_SHADER } })); + const S32 unit = mDraw.getTextureChannel(TEST_SOURCE_MAP); + UNSCOPED_INFO("the sampler has a unit"); + REQUIRE(unit >= 0); + mDraw.bind(); + + // The name GL holds on the unit's 2D target, read with the unit made active and the active one put back. + const auto bound_2d = [unit]() + { + glActiveTexture(GL_TEXTURE0 + unit); + GLint name = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &name); + glActiveTexture(GL_TEXTURE0 + gGL.getCurrentTexUnitIndex()); + return (U32)name; + }; + const auto hold_target = [&]() + { + UNSCOPED_INFO("the target is bound at the unit"); + REQUIRE(mDraw.bindTexture(TEST_SOURCE_MAP, &mTargets[0], ALSamplers::PointClamp) == unit); + UNSCOPED_INFO("and GL holds it there"); + REQUIRE(bound_2d() == mTargets[0].getTexture()); + }; + const auto check_let_go = [&](const char* what, S32 returned) + { + INFO(what); + CHECK(returned == unit); + const ALTextureSlot* slot = gGL.getTextureSlot(unit); + CHECK(slot->getCurrType() == ALTextureSlot::TT_NONE); + CHECK(slot->getCurrTexture() == 0); + CHECK(bound_2d() != mTargets[0].getTexture()); + }; + + hold_target(); + check_let_go("a null texture", + mDraw.bindTexture(TEST_SOURCE_MAP, static_cast(nullptr), ALSamplers::PointClamp)); + + hold_target(); + check_let_go("a null render target", mDraw.bindTexture(TEST_SOURCE_MAP, static_cast(nullptr))); + + hold_target(); + check_let_go("a null depth target", mDraw.bindDepthTexture(TEST_SOURCE_MAP, nullptr)); + + hold_target(); + gGL.getTextureSlot(unit)->bindFast(nullptr, ALSamplers::PointClamp); + check_let_go("a null texture through the slot", unit); + + // A unit let go and then released, under the debug checks, whose expected target it does not hold. + gDebugGL = true; + S32 released = -1; + CHECK_NOTHROW(released = mDraw.disableTexture(TEST_SOURCE_MAP, ALTextureSlot::TT_CUBE_MAP)); + gDebugGL = false; + CHECK(released == unit); + CHECK(glGetError() == GLenum(GL_NO_ERROR)); + } + // A shared object is the shader manager's, not the programs' that attach it. Two programs built through // createShader attach the same one, as every program with a feature attaches its object, and call into it. Once // linked a program holds no object, and its own are deleted: the count of live objects is what it was before the diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index af06c3d3653..4d4cbe60d6c 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -250,6 +250,7 @@ if(AL_BUILD_VIEWER) alscripttypes.cpp alscriptweightspane.cpp alscriptworkspace.cpp + alsky.cpp alterrainsurfacemaps.cpp altextureboost.cpp alwatersurface.cpp @@ -392,11 +393,9 @@ if(AL_BUILD_VIEWER) lldrawpoolmaterials.cpp lldrawpoolpbropaque.cpp lldrawpoolsimple.cpp - lldrawpoolsky.cpp lldrawpoolterrain.cpp lldrawpooltree.cpp lldrawpoolwater.cpp - lldrawpoolwlsky.cpp lldrawpoolwaterexclusion.cpp lldynamictexture.cpp lleasymessagelogentry.cpp @@ -824,7 +823,6 @@ if(AL_BUILD_VIEWER) llsidepaneltaskinfo.cpp llsidetraypanelcontainer.cpp llskinningutil.cpp - llsky.cpp llslurl.cpp llsnapshotlivepreview.cpp llspatialpartition.cpp @@ -969,11 +967,9 @@ if(AL_BUILD_VIEWER) llvoicewebrtc.cpp llvoinventorylistener.cpp llvopartgroup.cpp - llvosky.cpp llvosurfacepatch.cpp llvotree.cpp llvovolume.cpp - llvowlsky.cpp llwearableitemslist.cpp llwearablelist.cpp llweb.cpp @@ -1133,6 +1129,7 @@ if(AL_BUILD_VIEWER) alscripttypes.h alscriptweightspane.h alscriptworkspace.h + alsky.h alterrainsurfacemaps.h alwatersurface.h altextureboost.h @@ -1276,11 +1273,9 @@ if(AL_BUILD_VIEWER) lldrawpoolmaterials.h lldrawpoolpbropaque.h lldrawpoolsimple.h - lldrawpoolsky.h lldrawpoolterrain.h lldrawpooltree.h lldrawpoolwater.h - lldrawpoolwlsky.h lldrawpoolwaterexclusion.h lldynamictexture.h lleasymessagelogentry.h @@ -1699,7 +1694,6 @@ if(AL_BUILD_VIEWER) llsidepaneltaskinfo.h llsidetraypanelcontainer.h llskinningutil.h - llsky.h llslurl.h llsnapshotlivepreview.h llsnapshotmodel.h @@ -1847,11 +1841,9 @@ if(AL_BUILD_VIEWER) llvoicewebrtc.h llvoinventorylistener.h llvopartgroup.h - llvosky.h llvosurfacepatch.h llvotree.h llvovolume.h - llvowlsky.h llwearableitemslist.h llwearablelist.h llweb.h diff --git a/indra/newview/alsky.cpp b/indra/newview/alsky.cpp new file mode 100644 index 00000000000..17954964c3b --- /dev/null +++ b/indra/newview/alsky.cpp @@ -0,0 +1,1242 @@ +/** + * @file alsky.cpp + * @brief The sky: the sun and the moon, the sky's textures, the stars and the meteors, and what the passes that draw + * it are given. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "alsky.h" + +#include "llagent.h" +#include "llappviewer.h" // gFrameIntervalSeconds +#include "llcubemap.h" +#include "llenvironment.h" +#include "llglslshader.h" +#include "llglstates.h" +#include "llimage.h" +#include "llrender.h" +#include "llrendertarget.h" +#include "llsettingssky.h" +#include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewershadermgr.h" +#include "llviewertexture.h" +#include "llvoavatarself.h" +#include "pipeline.h" +#include "workqueue.h" + +extern bool gCubeSnapshot; +extern LLPointer gEXRImage; + +namespace +{ + // The bodies' disks, as fractions of ALSkyLayers::BODY_FACTOR's metre. + constexpr F32 SUN_DISK_RADIUS = 0.5f; + constexpr F32 MOON_DISK_RADIUS = SUN_DISK_RADIUS * 0.9f; + + // The legacy environment cube: a face's edge, how long a finished cube holds before the next may start, and how + // far the sky must move to start one. + constexpr S32 CUBE_RESOLUTION = 64; + constexpr S32 CUBE_FACES = 6; + constexpr F64 CUBE_UPDATE_SECONDS = 0.25; + constexpr F32 UPDATE_MIN_DELTA_THRESHOLD = 0.0005f; + + // The global generator, for the meteors. + F32 globalRandom() { return ll_frand(); } + + // The most meteors in flight: the user-facing RenderMeteorMaxCount setting is clamped to the composite's array. + constexpr U32 kMeteorHardMax = ALSkyLayers::MAX_METEORS; + + // Pick a random unit vector perpendicular to `n`. Uses a deterministic + // orthogonal basis built from `n` and a fixed "up" swap for robustness + // at the poles, then rotates around `n` by a uniform angle. + template LLVector3 randomTangentTo(const LLVector3& n, Rand& draw) + { + const LLVector3 fallback = (fabsf(n.mV[2]) < 0.9f) + ? LLVector3(0.f, 0.f, 1.f) + : LLVector3(1.f, 0.f, 0.f); + LLVector3 a = (n % fallback); // cross + a.normVec(); + LLVector3 b = (n % a); + b.normVec(); + const F32 rot = F_TWO_PI * draw(); + return a * cosf(rot) + b * sinf(rot); + } + + // A texture pair the sky blends between, as the sun's and the clouds' draws took one: the first, and the next + // only while they differ and both are there, with how far the sky has blended toward it. + struct TexturePair + { + LLViewerTexture* mFirst = nullptr; + LLViewerTexture* mNext = nullptr; + F32 mBlend = 0.f; + }; + + TexturePair texturePair(LLViewerTexture* first, LLViewerTexture* next, F32 blend) + { + if (first && (!next || first == next)) + { + return { first, nullptr, 0.f }; + } + if (next && !first) + { + return { next, nullptr, 0.f }; + } + if (first && next) + { + return { first, next, blend }; + } + return {}; + } + + // @a texture for @a id: null for none, fetched again where it is not the one held. + void fetch(LLPointer& texture, const LLUUID& id) + { + if (id.isNull()) + { + texture = nullptr; + } + else if (texture.isNull() || texture->getID() != id) + { + texture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); + } + } +} + +ALSky::ALSky() + : mSun(SUN_DISK_RADIUS) + , mMoon(MOON_DISK_RADIUS) + , mBumpSunDir(0.f, 0.f, 1.f) +{ +} + +ALSky::~ALSky() = default; + +void ALSky::takeSky() +{ + const LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + if (!psky) + { + return; + } + + updateDirections(); + + // The bodies' quads about the camera: the stars' moon mask and the composite read them. + mSun.setCorners(ALSkyLayers::bodyCorners(mSun.getRotation(), HEAVENLY_BODY_DIST, mSunScale, mSun.getDiskRadius())); + mMoon.setCorners( + ALSkyLayers::bodyCorners(mMoon.getRotation(), HEAVENLY_BODY_DIST, mMoonScale, mMoon.getDiskRadius())); + + fetchTextures(); +} + +void ALSky::update() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; + + LLEnvironment& environment = LLEnvironment::instance(); + const LLSettingsSky::ptr_t psky = environment.getCurrentSky(); + if (!psky) + { + return; + } + + if (!mReady) + { + // The directions and the lighting worked once before anything is drawn. + psky->update(); + + requestStars(); + // RenderStarCount is for tuning, so it takes effect without a restart, once its field lands. + mStarCountConnection = gSavedSettings.getControl("RenderStarCount")->getSignal()->connect( + [this](LLControlVariable*, const LLSD&, const LLSD&) { requestStars(); }); + + setFogRatio(gSavedSettings.getF32("RenderFogRatio")); + mReady = true; + } + + takeSky(); + + if (gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SKY) && !gGLManager.mIsDisabled) + { + updateCubeMap(); + } +} + +void ALSky::followEnvironment() +{ + if (!mReady) + { + return; + } + + LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; + takeSky(); +} + +void ALSky::cleanup() +{ + mStarCountConnection.disconnect(); + // A field still being laid out lands on nothing. + ++mStarRequest; + mStars.clear(); + mStarLayout = {}; + mStarsBuildFailed = false; + mStarsVerts = nullptr; + mCubeMap = nullptr; + for (auto* textures : { &mSunTexture, &mMoonTexture, &mCloudNoiseTexture, &mBloomTexture }) + { + textures->fill(nullptr); + } + mRainbowMap = nullptr; + mHaloMap = nullptr; + mMeteors.clear(); + mReady = false; +} + +void ALSky::releaseGL() +{ + // The next context may make the buffer this one could not. + mStarsBuildFailed = false; + mStarsVerts = nullptr; + if (mCubeMap) + { + mCubeMap->destroyGL(); + } +} + +void ALSky::restoreGL() +{ + if (!mShiny[0].mDirs.empty()) + { + initCubeMap(); + } + forceSkyUpdate(); +} + +void ALSky::updateDirections() +{ + const LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + mSun.setDirection(psky->getSunDirection()); + mMoon.setDirection(psky->getMoonDirection()); + mSun.setRotation(psky->getSunRotation()); + mMoon.setRotation(psky->getMoonRotation()); +} + +void ALSky::setSunDirectionCFR(const LLVector3& sun_dir_cfr) +{ + // Push the sun "South" as it approaches directly overhead so that we can always see bump mapping + // on the upward facing faces of cubes. + // Same as dot product with the up direction + clamp. + F32 sunDot = llmax(0.f, sun_dir_cfr.mV[2]); + sunDot *= sunDot; + + // Create normalized vector that has the sunDir pushed south about an hour and change. + LLVector3 adjustedDir = (sun_dir_cfr + LLVector3(0.f, -0.70711f, 0.70711f)) * 0.5f; + + // Blend between normal sun dir and adjusted sun dir based on how close we are + // to having the sun overhead. + mBumpSunDir = adjustedDir * sunDot + sun_dir_cfr * (1.0f - sunDot); + mBumpSunDir.normalize(); +} + +void ALSky::setSunTextures(const LLUUID& sun_texture, const LLUUID& sun_texture_next) +{ + mSunTextureId = { sun_texture, sun_texture_next }; +} + +void ALSky::setMoonTextures(const LLUUID& moon_texture, const LLUUID& moon_texture_next) +{ + mMoonTextureId = { moon_texture, moon_texture_next }; +} + +void ALSky::setCloudNoiseTextures(const LLUUID& cloud_noise_texture, const LLUUID& cloud_noise_texture_next) +{ + mCloudNoiseTextureId = { cloud_noise_texture, cloud_noise_texture_next }; +} + +void ALSky::setBloomTextures(const LLUUID& bloom_texture, const LLUUID& bloom_texture_next) +{ + const LLUUID fallback = bloom_texture.isNull() ? LLSettingsSky::GetDefaultBloomTextureId() : bloom_texture; + mBloomTextureId = { fallback, bloom_texture_next.isNull() ? fallback : bloom_texture_next }; +} + +void ALSky::fetchTextures() +{ + const LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + // The latched input, so the bloom's textures come and go with the bloom the programs draw. + const bool bloom = gPipeline.shaderInputs().mLocal.mSunBloom; + for (size_t i = 0; i < 2; ++i) + { + fetch(mSunTexture[i], mSunTextureId[i]); + fetch(mMoonTexture[i], mMoonTextureId[i]); + fetch(mCloudNoiseTexture[i], mCloudNoiseTextureId[i]); + fetch(mBloomTexture[i], bloom ? mBloomTextureId[i] : LLUUID::null); + } + // A sky need not name the dome's two maps, which every view that draws the dome samples: one it leaves out is the + // default's. + const LLUUID rainbow = psky->getRainbowTextureId(); + const LLUUID halo = psky->getHaloTextureId(); + fetch(mRainbowMap, rainbow.isNull() ? LLSettingsSky::GetDefaultRainbowTextureId() : rainbow); + fetch(mHaloMap, halo.isNull() ? LLSettingsSky::GetDefaultHaloTextureId() : halo); + + // The sun's, the moon's and the bloom's at full size, as the drawn bodies are always wanted. + for (auto* textures : { &mSunTexture, &mMoonTexture, &mBloomTexture }) + { + for (const LLPointer& texture : *textures) + { + if (texture) + { + texture->addTextureStats((F32)MAX_IMAGE_AREA); + } + } + } +} + +// +// The stars +// + +void ALSky::requestStars() +{ + // RenderStarCount scales every tier alike, so the mix of bright primaries, mid stars and faint dust stays the same + // when the total is dialled down for performance. + const LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + if (!psky) + { + return; + } + const U32 request = ++mStarRequest; + const U32 count = (U32)llmax(gSavedSettings.getS32("RenderStarCount"), 0); + const F32 dome_radius = psky->getDomeRadius(); + + // Tens of milliseconds at the default count, and more toward ALStarField::MAX_COUNT: off the main thread, from the + // count and the radius alone. + auto lay_out = [count, dome_radius]() + { + LL_PROFILE_ZONE_NAMED("sky stars lay out"); + StarField field; + try + { + field.mStars = ALStarField::generate(count, dome_radius); + field.mLayout = ALStarField::layOut(field.mStars); + } + catch (const std::exception& e) + { + LL_WARNS("Sky") << "The star field could not be laid out, so no stars are drawn: " << e.what() << LL_ENDL; + field = {}; + } + return field; + }; + + LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop"); + LL::WorkQueue::ptr_t general_queue = LL::WorkQueue::getInstance("General"); + if (!main_queue || !general_queue || + !main_queue->postTo(general_queue, std::move(lay_out), + [request](StarField field) { gPipeline.mSky.landStars(request, std::move(field)); })) + { + LL_WARNS("Sky") << "No queue to lay the star field out on, so no stars are drawn" << LL_ENDL; + } +} + +void ALSky::landStars(U32 request, StarField&& field) +{ + if (request != mStarRequest) + { + return; + } + mStars = std::move(field.mStars); + mStarLayout = std::move(field.mLayout); + // The buffer is made from the new field when next drawn, and tried again where the last could not be made. + mStarsVerts = nullptr; + mStarsBuildFailed = false; +} + +bool ALSky::buildStars() +{ + LL_PROFILE_ZONE_SCOPED; + + // The field is fixed once it lands; the buffer is let go with the context and when a new field lands, and made + // again here. + if (mStarsVerts.notNull()) + { + return true; + } + + // No field yet, and RenderStarCount = 0, draw no stars and leave no buffer. Nor does a buffer that could not be + // made, until a new field lands or the context changes. + const U32 num_verts = mStarLayout.vertices(); + if (num_verts == 0 || mStarsBuildFailed) + { + return false; + } + + mStarsVerts = new LLVertexBuffer(STAR_VERTEX_DATA_MASK); + if (!mStarsVerts->allocateBuffer(num_verts, 0)) + { + LL_WARNS() << "Failed to allocate Vertex Buffer for Sky to " << num_verts << " vertices" << LL_ENDL; + mStarsVerts = nullptr; + mStarsBuildFailed = true; + return false; + } + + LLStrider verticesp; + if (!mStarsVerts->getVertexStrider(verticesp)) + { + LL_ERRS() << "Failed updating star geometry." << LL_ENDL; + } + + // One vertex a star, drawn as a point the vertex shader sizes; six for each spiked star, which it spreads into a + // quad. + ALStarField::fillVertices(mStars, mStarLayout, verticesp); + + mStarsVerts->unmapBuffer(); + return true; +} + +void ALSky::drawStarPoints(const ALStarField::Frustum& frustum, U32 faintest) +{ + if (mStarsVerts.isNull() || mStarLayout.mPoints == 0) + { + return; + } + + { + LL_PROFILE_ZONE_NAMED("sky stars cull"); + ALStarField::pointDraws(mStarLayout, frustum, faintest, mStarDraws); + } + if (!mStarDraws.mFirst.empty()) + { + mStarsVerts->setBuffer(); + mStarsVerts->drawMultiArrays(LLRender::POINTS, mStarDraws.mFirst.data(), mStarDraws.mCount.data(), + (U32)mStarDraws.mFirst.size()); + } +} + +void ALSky::drawStarQuads(U32 faintest) +{ + const U32 quads = ALStarField::quadsDrawn(mStarLayout, faintest); + if (mStarsVerts.notNull() && quads > 0) + { + mStarsVerts->setBuffer(); + mStarsVerts->drawArrays(LLRender::TRIANGLES, 0, quads * ALStarField::QUAD_VERTICES); + } +} + +void ALSky::beginView(bool draws_sky) +{ + mView = {}; + mViewLayers = 0; + if (!draws_sky) + { + return; + } + + const LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + + mView.mProbe = gCubeSnapshot; + mView.mMirror = gPipeline.mHeroProbeManager.isMirrorPass(); + mView.mRadiance = gPipeline.mReflectionMapManager.isRadiancePass(); + mView.mHdri = useHDRISky(); + + // The sun, and its bloom while the latched input draws it (its textures are fetched only then); the moon, its first + // texture alone. + mView.mSun = psky->getIsSunUp() && texturePair(mSunTexture[0], mSunTexture[1], 0.f).mFirst; + mView.mBloom = + gPipeline.shaderInputs().mLocal.mSunBloom && texturePair(mBloomTexture[0], mBloomTexture[1], 0.f).mFirst; + mView.mMoon = psky->getIsMoonUp() && mMoonTexture[0]; + + // The night: the stars' brightness gates the meteors and the aurora, and the moon washes the aurora out. + static LLCachedControl meteors_enabled(gSavedSettings, "RenderMeteorsEnabled", true); + static LLCachedControl aurora_intensity(gSavedSettings, "RenderAuroraIntensity", 0.6f); + mView.mStarAlpha = psky->getStarBrightness() / 500.0f; + mView.mMeteors = meteors_enabled; + const F32 moon_wash = psky->getIsMoonUp() ? (F32)psky->getMoonBrightness() : 0.0f; + mView.mAurora = aurora_intensity > 0.f ? aurora_intensity * llmax(0.0f, 1.0f - moon_wash * 0.85f) : 0.f; + + // The clouds, which take their first noise texture to draw. + mView.mClouds = gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_CLOUDS) && mCloudNoiseTexture[0]; + + mViewLayers = ALSkyLayers::layersFor(mView); +} + +void ALSky::renderStars() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + + if (!(mViewLayers & ALSkyLayers::STARS) || !buildStars()) + { + return; + } + + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + const F32 star_alpha = mView.mStarAlpha; + + // The G-buffer the stars draw into, and their attachment of it. The fragment stage writes that output alone, so a + // target without it takes no stars. + const bool emissive = gPipeline.shaderInputs().mGlobal.mEmissive; + const U32 attachment = ALSkyLayers::starAttachment(emissive); + LLRenderTarget* gbuffer = LLRenderTarget::getCurrentBoundTarget(); + if (!gbuffer || attachment >= gbuffer->getNumTextures()) + { + llassert(false); + return; + } + + // The stars store display-encoded values into their attachment raw, which the composite reads back as it is: the + // deferred pass's encode would transform them twice on the albedo path. + LLGLDisable srgb(GL_FRAMEBUFFER_SRGB); + LLGLDisable cull(GL_CULL_FACE); + LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_LEQUAL); + LLGLEnable blend(GL_BLEND); + + // Pre-multiplied additive blend: the shader multiplies the star's RGB by + // its shape/alpha before output, so the framebuffer operation is a simple + // screen-space add rather than an alpha-blend. + gGL.setSceneBlendType(LLRender::BT_ADD); + + const LLVector3& origin = LLViewerCamera::getInstance()->getOrigin(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + gGL.translatef(origin.mV[0], origin.mV[1], origin.mV[2]); + // Subtle rotation so fixed patterns drift over long time scales. + const F32 star_rotation = (F32)fmod(gFrameTimeSeconds * 0.01, 360.0); + gGL.rotatef(star_rotation, 0.f, 0.f, 1.f); + + // The stars' frame is the agent's turned by the rotation above, and the vertex shader works in it, so agent + // directions are turned back by the rotation's transpose. + LLMatrix4a to_stars = LLMatrix4a::rotation(star_rotation * DEG_TO_RAD, LLVector4a(0.f, 0.f, 1.f)); + to_stars.transpose(); + const auto in_stars = [&to_stars](const LLVector3& v) + { + LLVector4a out; + to_stars.rotate(LLVector4a(v.mV[VX], v.mV[VY], v.mV[VZ]), out); + return LLVector3(out.getF32ptr()); + }; + + // Moon glare inputs, in the stars' frame so the glare stays on the moon however far the stars have turned. + const LLVector3 moon_dir = in_stars(psky->getMoonDirection()); + const F32 moon_brightness = psky->getIsMoonUp() ? (F32)psky->getMoonBrightness() : 0.0f; + + // Wrapped time for scintillation noise. 86400s is long enough that a + // typical session never hits the wrap boundary (where the non-periodic + // noise would show a one-frame jump), and short enough that 32-bit float + // precision stays good for the scaled time term in the shader. + const F32 star_time = (F32)fmod(LLFrameTimer::getUptimeSeconds() * 0.5, 86400.0); + + // The composite lays the moon over the stars and fades it low in the sky, so the stars leave out what its quad and + // texture cover (starsV.glsl). Its corners lie around the camera in agent space. + LLViewerTexture* moon_tex = mMoonTexture[0]; + const bool mask_moon = psky->getIsMoonUp() && moon_tex; + LLVector3 moon_centre; + LLVector3 moon_u; + LLVector3 moon_v; + if (mask_moon) + { + // Corners 0..3 are (-right, +up), (-right, -up), (+right, +up), (+right, -up) (ALSkyLayers::bodyCorners). + const ALSkyLayers::Corners& corner = mMoon.corners(); + const LLVector3 centre = (corner[0] + corner[3]) * 0.5f; + const F32 dist = centre.length(); + LLVector3 half_u = (corner[2] - corner[0]) * (0.5f / dist); + LLVector3 half_v = (corner[0] - corner[1]) * (0.5f / dist); + half_u /= half_u.lengthSquared(); + half_v /= half_v.lengthSquared(); + + moon_centre = in_stars(centre / dist); + moon_u = in_stars(half_u); + moon_v = in_stars(half_v); + } + + // Both star programs take the same inputs. + const auto bind_stars = [&](LLGLSLShader& program) + { + program.bind(); + program.uniform1f(LLShaderMgr::CUSTOM_ALPHA, star_alpha); + // The viewport the stars draw into, which the vertex stage sizes them in and the points find their centres + // by. + program.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, (GLfloat)LLRenderTarget::sCurResX, + (GLfloat)LLRenderTarget::sCurResY); + program.uniform3fv(LLShaderMgr::DEFERRED_MOON_DIR, 1, moon_dir.mV); + program.uniform1f(LLShaderMgr::MOON_BRIGHTNESS, moon_brightness); + program.uniform1f(LLShaderMgr::WATER_TIME, star_time); + if (mask_moon) + { + program.uniform3fv(LLShaderMgr::MOON_QUAD_CENTER, 1, moon_centre.mV); + program.uniform3fv(LLShaderMgr::MOON_QUAD_U, 1, moon_u.mV); + program.uniform3fv(LLShaderMgr::MOON_QUAD_V, 1, moon_v.mV); + program.bindTexture(LLShaderMgr::DIFFUSE_MAP, moon_tex, ALSamplers::AnisoClamp); + } + else + { + program.uniform3f(LLShaderMgr::MOON_QUAD_CENTER, 0.f, 0.f, 0.f); + } + }; + const auto unbind_stars = [&](LLGLSLShader& program) + { + if (mask_moon) + { + program.disableTexture(LLShaderMgr::DIFFUSE_MAP); + } + program.unbind(); + }; + + // On an 8-bit attachment, the faintest star that can still add anything a pixel keeps this frame, for the star + // brightness, the moon's wash and the attachment's gain, and every fainter one is left out: what a star adds + // under half a step rounds away. The float one, the emissive under HDR, rounds nothing away and the exposure, the + // tonemapper and the grade may show any of it, so every star in view is drawn there. + const F32 moon_power = moon_brightness * llmax(0.f, moon_dir.mV[VZ]); + const U32 faintest = + emissive && LLPipeline::hdrEnabled() ? 0 : ALStarField::faintestVisible(star_alpha, moon_power, emissive); + + // The stars add into one attachment of the G-buffer, the emissive with the emissive buffer and the albedo without + // it (starsF.glsl), and leave the rest unread and unwritten. + gbuffer->setDrawAttachment(attachment); + + { + LL_PROFILE_GPU_ZONE("sky stars"); + + // The spiked stars, few and bright, as quads (ALStarField::SPIKE_INTENSITY). + bind_stars(gDeferredStarQuadProgram); + drawStarQuads(faintest); + unbind_stars(gDeferredStarQuadProgram); + + // Every other star as a point, sized by its vertex shader, from the cells the view takes in: its frustum in + // the stars' frame, through the matrices that draw them. + LLMatrix4a mvp; + mvp.setMul(gGL.getModelviewMatrix(), gGL.getProjectionMatrix()); + const ALStarField::Frustum frustum = + ALStarField::pointFrustum(mvp, LLRenderTarget::sCurResX, LLRenderTarget::sCurResY); + + LLGLEnable point_size(GL_PROGRAM_POINT_SIZE); + bind_stars(gDeferredStarProgram); + drawStarPoints(frustum, faintest); + unbind_stars(gDeferredStarProgram); + } + + gbuffer->setDrawBuffers(); + + gGL.popMatrix(); + gGL.setSceneBlendType(LLRender::BT_ALPHA); +} + +// +// The meteors +// + +void ALSky::tickMeteors(F32 dt_seconds) +{ + LL_PROFILE_ZONE_SCOPED; + + if (dt_seconds <= 0.0f) return; + + // Age existing meteors and drop any that have run their course. + for (MeteorState& m : mMeteors) + { + m.age += dt_seconds; + } + mMeteors.erase( + std::remove_if(mMeteors.begin(), mMeteors.end(), + [](const MeteorState& m) { return m.age >= m.lifetime; }), + mMeteors.end()); + + static LLCachedControl max_count_setting(gSavedSettings, "RenderMeteorMaxCount", 16); + static LLCachedControl spawn_secs_setting(gSavedSettings, "RenderMeteorSpawnInterval", 4.0f); + const U32 max_count = (U32)llclamp((S32)max_count_setting, 0, (S32)kMeteorHardMax); + const F32 spawn_secs = llmax(0.05f, (F32)spawn_secs_setting); + + // Poisson-ish spawn: per-frame probability = dt / mean_interval. At 60 FPS + // with a 4s mean, this produces a new meteor roughly every 240 frames. + if (mMeteors.size() < max_count + && ll_frand() < dt_seconds / spawn_secs) + { + const F32 dome_radius = LLEnvironment::instance().getCurrentSky()->getDomeRadius(); + + // Start point anywhere on the upper hemisphere, but prefer a bit above + // the horizon so meteors don't immediately pop out of view. + LLVector3 start_dir = ALStarField::uniformUpperHemisphere(globalRandom); + // Lift horizon-hugging starts slightly so short streaks stay visible. + if (start_dir.mV[2] < 0.1f) + { + start_dir.mV[2] = 0.1f; + start_dir.normVec(); + } + + // Travel direction is a random tangent in the sky tangent plane at + // start_dir. This gives great-circle-like paths. + LLVector3 travel_dir = randomTangentTo(start_dir, globalRandom); + + // Classify each new meteor into a visual archetype. The weighted mix + // makes showers feel alive at high spawn rates — most meteors are + // faint and quick, but the occasional fireball with exotic chemistry + // colors draws the eye. The cumulative thresholds below are the + // probability cutoffs for each class. + const F32 roll = ll_frand(); + F32 width_scale; + F32 path_mult = 1.0f; // multiplier on both path and trail length + F32 life_mult = 1.0f; // multiplier on lifetime (shorter = faster) + F32 peak; + LLColor3 color; + + if (roll < 0.55f) + { + // Common: faint, thin, cool red-orange. These dominate by count + // (matching real meteor distributions) and give the eye a quiet + // background of activity. + width_scale = 0.55f + 0.25f * ll_frand(); + peak = 0.35f + 0.25f * ll_frand(); + life_mult = 0.75f + 0.25f * ll_frand(); + const F32 tk = 2500.0f + 1500.0f * ll_frand(); + color = ALStarField::blackBodyColor(tk); + } + else if (roll < 0.85f) + { + // Standard: yellow-white, the "average" visible meteor. + width_scale = 0.85f + 0.35f * ll_frand(); + peak = 0.6f + 0.3f * ll_frand(); + const F32 tk = 4500.0f + 2500.0f * ll_frand(); + color = ALStarField::blackBodyColor(tk); + } + else if (roll < 0.96f) + { + // Fast: hot blue-white, travels farther in the same time so + // apparent angular velocity is higher. + width_scale = 0.7f + 0.3f * ll_frand(); + peak = 0.8f + 0.2f * ll_frand(); + path_mult = 1.25f + 0.35f * ll_frand(); + life_mult = 0.6f + 0.25f * ll_frand(); + const F32 tk = 8000.0f + 7000.0f * ll_frand(); + color = ALStarField::blackBodyColor(tk); + } + else + { + // Fireball: thick, bright, persistent. Some roll to an exotic + // chemistry color from ablation (Mg, Na, N, Cu) rather than the + // usual blackbody curve. + width_scale = 1.8f + 1.4f * ll_frand(); + peak = 0.9f + 0.1f * ll_frand(); + path_mult = 1.3f + 0.4f * ll_frand(); + life_mult = 1.1f + 0.4f * ll_frand(); + + const F32 chem = ll_frand(); + if (chem < 0.22f) color = LLColor3(0.35f, 1.00f, 0.45f); // magnesium green + else if (chem < 0.42f) color = LLColor3(1.00f, 0.55f, 0.20f); // sodium orange + else if (chem < 0.58f) color = LLColor3(1.00f, 0.35f, 0.30f); // nitrogen red + else if (chem < 0.72f) color = LLColor3(0.40f, 0.55f, 1.00f); // copper/Mg blue + else + { + // Hot white bolide, no exotic chemistry. + const F32 tk = 9000.0f + 6000.0f * ll_frand(); + color = ALStarField::blackBodyColor(tk); + } + } + + MeteorState m; + m.origin_world = start_dir * dome_radius; + m.direction_world = travel_dir; + // Base lifetime 1.1..2.5s. Fast class scales this down, fireballs up, + // so the full range across classes is roughly 0.7..3.7s. + m.lifetime = (1.1f + 1.4f * ll_frand()) * life_mult; + const F32 path_angle = (0.21f + 0.31f * ll_frand()) * path_mult; // radians + const F32 trail_angle = path_angle * (0.35f + 0.25f * ll_frand()); + m.path_length_world = dome_radius * path_angle; + m.trail_length_world = dome_radius * trail_angle; + m.color = color; + m.peak_intensity = peak; + m.width_scale = width_scale; + m.age = 0.0f; + + mMeteors.push_back(m); + } +} + +void ALSky::meteors(std::vector& out) const +{ + out.clear(); + for (const MeteorState& m : mMeteors) + { + if (out.size() >= kMeteorHardMax) break; + + // Envelope: quick fade in, steady, slower fade out. These ratios read + // as a meteor "blooming" and then trailing off. + const F32 frac = m.age / llmax(m.lifetime, 1e-4f); + F32 alpha = 1.0f; + if (frac < 0.10f) alpha = frac / 0.10f; + else if (frac > 0.80f) alpha = (1.0f - frac) / 0.20f; + alpha = llclamp(alpha, 0.0f, 1.0f) * m.peak_intensity; + if (alpha <= 0.0f) continue; + + // Head progresses linearly along the travel direction. Tail is a fixed + // length behind the head regardless of age — the trail doesn't grow, + // it just moves with the head. + const LLVector3 head = m.origin_world + m.direction_world * (m.path_length_world * frac); + const LLVector3 tail = head - m.direction_world * m.trail_length_world; + + // Pack per-frame envelope alpha into the RGB channels (in sRGB space — a + // mild nonlinearity vs. linear-space premultiply, but the envelope is + // smooth enough that the difference isn't visible). This frees the + // color's alpha slot to carry the per-meteor width_scale, which the + // fragment shader needs to normalize its cross-streak Gaussian so + // thicker meteors are actually thicker (not just bigger empty quads). + LLColor4 color4; + color4.mV[VRED] = m.color.mV[VRED] * alpha; + color4.mV[VGREEN] = m.color.mV[VGREEN] * alpha; + color4.mV[VBLUE] = m.color.mV[VBLUE] * alpha; + // Width_scale range is [0, 4]; pack as /4 so it fits in 8-bit unsigned. + color4.mV[VALPHA] = llclamp(m.width_scale * 0.25f, 0.0f, 1.0f); + + ALSkyLayers::Meteor meteor; + meteor.mMidpoint = (head + tail) * 0.5f; + meteor.mHalf = (head - tail) * 0.5f; + meteor.mColour = LLColor4U(color4); + meteor.mWidth = m.width_scale; + out.push_back(meteor); + } +} + +// +// The passes' inputs +// + +// static +bool ALSky::useHDRISky() +{ + static LLCachedControl hdri_split(gSavedSettings, "RenderHDRISplitScreen", 1.f); + static LLCachedControl irradiance_only(gSavedSettings, "RenderHDRIIrradianceOnly", false); + + return gCubeSnapshot && (!irradiance_only || !gPipeline.mReflectionMapManager.isRadiancePass()) ? gEXRImage.notNull() : // always use HDRI for reflection probes when available + gEXRImage.notNull() ? hdri_split > 0.f : // fallback to EEP sky when split screen is zero + false; // no HDRI available, always use EEP sky +} + +void ALSky::bindDome(LLGLSLShader& shader) const +{ + LLEnvironment& environment = LLEnvironment::instance(); + const LLSettingsSky::ptr_t psky = environment.getCurrentSky(); + + shader.bindTexture(LLShaderMgr::RAINBOW_MAP, mRainbowMap.get(), ALSamplers::AnisoWrap); + shader.bindTexture(LLShaderMgr::HALO_MAP, mHaloMap.get(), ALSamplers::AnisoWrap); + + F32 moisture_level = (float)psky->getSkyMoistureLevel(); + F32 droplet_radius = (float)psky->getSkyDropletRadius(); + F32 ice_level = (float)psky->getSkyIceLevel(); + + // hobble halos and rainbows when there's no light source to generate them + if (!psky->getIsSunUp() && !psky->getIsMoonUp()) + { + moisture_level = 0.0f; + ice_level = 0.0f; + } + + shader.uniform1f(LLShaderMgr::MOISTURE_LEVEL, moisture_level); + shader.uniform1f(LLShaderMgr::DROPLET_RADIUS, droplet_radius); + shader.uniform1f(LLShaderMgr::ICE_LEVEL, ice_level); + + shader.uniform1f(LLShaderMgr::SUN_MOON_GLOW_FACTOR, psky->getSunMoonGlowFactor()); + shader.uniform1f(LLShaderMgr::DENSITY_MULTIPLIER, psky->getDensityMultiplier()); + shader.uniform3fv(LLShaderMgr::LIGHTNORM, 1, environment.getClampedLightNorm().mV); + + // The dome's geometry: the camera's place in its frame, and the radius of the sphere the dome lies on. + shader.uniform3f(LLShaderMgr::WL_CAMPOSLOCAL, 0.f, environment.getCamHeight(), 0.f); + shader.uniform1f(LLShaderMgr::SKY_DOME_RADIUS, psky->getDomeRadius()); +} + +void ALSky::bindComposite(LLGLSLShader& shader) +{ + LLEnvironment& environment = LLEnvironment::instance(); + const LLSettingsSky::ptr_t psky = environment.getCurrentSky(); + + // Every map bound below is recorded, so that unbindComposite lets go of these and no other unit. + mCompositeBound.clear(); + const auto bind = [this, &shader](U32 map, LLViewerTexture* texture, ALSampler sampler) + { + if (shader.bindTexture(map, texture, sampler) > -1) + { + mCompositeBound.push_back(map); + } + }; + + // The dome's two maps, which bindDome binds wherever the program reads them. + bindDome(shader); + mCompositeBound.push_back(LLShaderMgr::RAINBOW_MAP); + mCompositeBound.push_back(LLShaderMgr::HALO_MAP); + + if (mViewLayers & ALSkyLayers::HDRI) + { + static LLCachedControl hdri_exposure(gSavedSettings, "RenderHDRIExposure", 0.0f); + static LLCachedControl hdri_rotation(gSavedSettings, "RenderHDRIRotation", 0.f); + static LLCachedControl hdri_split(gSavedSettings, "RenderHDRISplitScreen", 1.f); + + const S32 channel = shader.enableTexture(LLShaderMgr::SKY_HDRI_MAP); + if (channel > -1 && gGL.getTextureSlot(channel)->bindSampled(gEXRImage, ALSamplers::TrilinearClamp)) + { + mCompositeBound.push_back(LLShaderMgr::SKY_HDRI_MAP); + } + LLMatrix3 rot; + rot.setRot(0.f, hdri_rotation * DEG_TO_RAD, 0.f); + shader.uniformMatrix3fv(LLShaderMgr::SKY_HDRI_ROTATION, 1, GL_FALSE, (F32*)rot.mMatrix); + shader.uniform1f(LLShaderMgr::SKY_HDRI_SCALE, powf(2.f, hdri_exposure)); + shader.uniform1f(LLShaderMgr::HDRI_SPLIT_SCREEN, gCubeSnapshot ? 1.f : (F32)hdri_split); + shader.uniform1i(LLShaderMgr::SKY_LAYERS, mViewLayers); + return; + } + + // The textures each layer the view draws takes (beginView), and how far the sky has blended toward the next. + const F32 blend_factor = (F32)psky->getBlendFactor(); + const TexturePair sun = texturePair(mSunTexture[0], mSunTexture[1], blend_factor); + const TexturePair bloom = texturePair(mBloomTexture[0], mBloomTexture[1], blend_factor); + LLViewerTexture* moon_texture = mMoonTexture[0]; + const F32 cloud_blend = psky->getCloudScrollRate().isExactlyZero() ? 0.f : blend_factor; + const TexturePair clouds = texturePair(mCloudNoiseTexture[0], mCloudNoiseTexture[1], cloud_blend); + + // Each frame's interval, clamped after a long one so a meteor does not snap to the end of its life. Zero while the + // world is paused, which freezes the meteors and the aurora with it. + F32 dt = (F32)gFrameIntervalSeconds.value(); + if (dt > 0.1f) + { + dt = 0.1f; + } + + // The meteors fly in the views that draw them, through the matrices that drew their quads: the camera's, taken to + // its origin. The composite leaves them out where none crosses the view. + S32 layers = mViewLayers; + mMeteorData.clear(); + U32 meteor_count = 0; + if (layers & ALSkyLayers::METEORS) + { + tickMeteors(dt); + meteors(mMeteorsInFlight); + + const LLVector3& origin = LLViewerCamera::getInstance()->getOrigin(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + gGL.loadMatrix(LLViewerCamera::getCurrent().getModelview()); + gGL.translatef(origin.mV[VX], origin.mV[VY], origin.mV[VZ]); + LLMatrix4a mvp; + mvp.setMul(gGL.getModelviewMatrix(), LLViewerCamera::getCurrent().getProjection()); + gGL.popMatrix(); + + const LLRenderTarget& screen = gPipeline.mRT->deferredScreen; + meteor_count = ALSkyLayers::packMeteors(mMeteorsInFlight, mvp, (F32)screen.getWidth(), (F32)screen.getHeight(), + ALSkyLayers::METEOR_WIDTH_PIXELS, mMeteorData); + if (meteor_count == 0) + { + layers &= ~ALSkyLayers::METEORS; + } + } + + shader.uniform1i(LLShaderMgr::SKY_LAYERS, layers); + + if (layers & ALSkyLayers::STARS) + { + // The stars' attachment of the G-buffer they were drawn into, as the composite reads it: the emissive as it + // is, the sRGB albedo decoded. Nothing else of the G-buffer is bound. + const bool emissive = gPipeline.shaderInputs().mGlobal.mEmissive; + const U32 map = emissive ? LLShaderMgr::DEFERRED_EMISSIVE : LLShaderMgr::DEFERRED_DIFFUSE; + const S32 channel = shader.enableTexture(map); + if (channel > -1) + { + gPipeline.mRT->deferredScreen.bindTexture(ALSkyLayers::starAttachment(emissive), channel, + emissive ? ALSamplers::PointClamp + : ALSamplers::PointClamp | ALSampler::SRGBDecode); + mCompositeBound.push_back(map); + } + } + + if (layers & ALSkyLayers::SUN) + { + // The disc lowered as its vertex stage lowered it. + const std::array quad = + ALSkyLayers::rayQuad(mSun.corners(), LLVector3(0.f, 0.f, -ALSkyLayers::SUN_DROP)); + shader.uniform3fv(LLShaderMgr::SKY_SUN_QUAD, 3, quad[0].mV); + bind(LLShaderMgr::SKY_SUN_MAP, sun.mFirst, ALSamplers::AnisoClamp); + if (sun.mNext) + { + bind(LLShaderMgr::SKY_SUN_MAP_NEXT, sun.mNext, ALSamplers::AnisoClamp); + } + shader.uniform1f(LLShaderMgr::SKY_SUN_BLEND, sun.mBlend); + } + + if (layers & ALSkyLayers::BLOOM) + { + // The halo's square about the disc, lowered with it, tinted by the sun's colour as a vertex colour held to 1. + const std::array quad = ALSkyLayers::rayQuad(ALSkyLayers::bloomCorners(mSun.corners()), + LLVector3(0.f, 0.f, -ALSkyLayers::SUN_DROP)); + shader.uniform3fv(LLShaderMgr::SKY_BLOOM_QUAD, 3, quad[0].mV); + bind(LLShaderMgr::SKY_BLOOM_MAP, bloom.mFirst, ALSamplers::AnisoClamp); + if (bloom.mNext) + { + bind(LLShaderMgr::SKY_BLOOM_MAP_NEXT, bloom.mNext, ALSamplers::AnisoClamp); + } + shader.uniform1f(LLShaderMgr::SKY_BLOOM_BLEND, bloom.mBlend); + LLColor3 colour = psky->getSunDiffuse(); + colour.clamp(); + shader.uniform3fv(LLShaderMgr::SKY_BLOOM_COLOR, 1, colour.mV); + } + + if (layers & ALSkyLayers::MOON) + { + const std::array quad = ALSkyLayers::rayQuad(mMoon.corners()); + shader.uniform3fv(LLShaderMgr::SKY_MOON_QUAD, 3, quad[0].mV); + bind(LLShaderMgr::SKY_MOON_MAP, moon_texture, ALSamplers::AnisoClamp); + shader.uniform1f(LLShaderMgr::SKY_MOON_FADE, ALSkyLayers::moonFade(psky->getMoonDirection().mV[VZ])); + shader.uniform1f(LLShaderMgr::MOON_BRIGHTNESS, (F32)psky->getMoonBrightness()); + } + + if (layers & ALSkyLayers::METEORS) + { + shader.uniform4fv(LLShaderMgr::SKY_METEOR_DATA, (U32)mMeteorData.size(), mMeteorData[0].mV); + shader.uniform1i(LLShaderMgr::SKY_METEOR_COUNT, (S32)meteor_count); + } + + if (layers & ALSkyLayers::AURORA) + { + // Accumulated in F64: an F32 near the wrap holds the time only to 8 ms, so adding a frame's + // dt to it rounds each step to whole multiples of that, and the aurora moves unevenly. + mAuroraTime = fmod(mAuroraTime + dt, 86400.0); + shader.uniform1f(LLShaderMgr::AURORA_INTENSITY, mView.mAurora); + shader.uniform1f(LLShaderMgr::AURORA_TIME, (F32)mAuroraTime); + } + + if (layers & ALSkyLayers::CLOUDS) + { + bind(LLShaderMgr::CLOUD_NOISE_MAP, clouds.mFirst, ALSamplers::AnisoWrap); + if (clouds.mNext) + { + bind(LLShaderMgr::CLOUD_NOISE_MAP_NEXT, clouds.mNext, ALSamplers::AnisoWrap); + } + shader.uniform1f(LLShaderMgr::SKY_CLOUD_BLEND, clouds.mBlend); + shader.uniform1f(LLShaderMgr::CLOUD_VARIANCE, (F32)psky->getCloudVariance()); + } +} + +void ALSky::unbindComposite(LLGLSLShader& shader) +{ + for (U32 map : mCompositeBound) + { + shader.disableTexture(map); + } + mCompositeBound.clear(); +} + +// +// The legacy environment cube +// + +bool ALSky::bindEnvironmentCube(S32 channel) const +{ + if (LLPipeline::sReflectionProbesEnabled || channel < 0 || mCubeMap.isNull()) + { + return false; + } + mCubeMap->enable(channel); + mCubeMap->bind(); + return true; +} + +void ALSky::cacheEnvironment(AtmosphericsVars& vars) const +{ + const LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + // NOTE: Also see: LLAtmospherics::updateFog() + // invariants across whole sky tex process... + vars.blue_density = psky->getBlueDensity(); + vars.blue_horizon = psky->getBlueHorizon(); + vars.haze_density = psky->getHazeDensity(); + vars.haze_horizon = psky->getHazeHorizon(); + vars.density_multiplier = psky->getDensityMultiplier(); + vars.distance_multiplier = psky->getDistanceMultiplier(); + vars.max_y = psky->getMaxY(); + vars.sun_norm = LLEnvironment::instance().getClampedSunNorm(); + vars.sunlight = psky->getIsSunUp() ? psky->getSunlightColor() : psky->getMoonlightColor(); + vars.ambient = psky->getAmbientColor(); + vars.glow = psky->getGlow(); + vars.cloud_shadow = psky->getCloudShadow(); + vars.dome_radius = psky->getDomeRadius(); + vars.dome_offset = psky->getDomeOffset(); + vars.light_atten = psky->getLightAttenuation(vars.max_y); + vars.light_transmittance = psky->getLightTransmittance(vars.max_y); + vars.total_density = psky->getTotalDensity(); + vars.gamma = psky->getGamma(); +} + +void ALSky::initSkyTextureDirs(S32 side, S32 tile) +{ + ShinyFace& face = mShiny[side]; + if (face.mDirs.empty()) + { + face.mDirs.resize(CUBE_RESOLUTION * CUBE_RESOLUTION); + face.mColours.resize(CUBE_RESOLUTION * CUBE_RESOLUTION); + face.mImage = new LLImageRaw(CUBE_RESOLUTION, CUBE_RESOLUTION, 4); + } + + F32 coeff[3] = {0, 0, 0}; + const S32 curr_coef = side >> 1; // 0/1 = X axis, 2/3 = Y, 4/5 = Z + const S32 side_dir = (((side & 1) << 1) - 1); // even = -1, odd = 1 + const S32 x_coef = (curr_coef + 1) % 3; + const S32 y_coef = (x_coef + 1) % 3; + + coeff[curr_coef] = (F32)side_dir; + + const F32 inv_res = 1.f / CUBE_RESOLUTION; + for (S32 y = 0; y < CUBE_RESOLUTION; ++y) + { + for (S32 x = 0; x < CUBE_RESOLUTION; ++x) + { + coeff[x_coef] = F32((x<<1) + 1) * inv_res - 1.f; + coeff[y_coef] = F32((y<<1) + 1) * inv_res - 1.f; + LLVector3 dir(coeff[0], coeff[1], coeff[2]); + dir.normalize(); + face.mDirs[x * CUBE_RESOLUTION + y] = dir; + } + } +} + +void ALSky::createSkyTexture(AtmosphericsVars& vars, S32 side, S32 tile) +{ + const LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + ShinyFace& face = mShiny[side]; + for (size_t i = 0; i < face.mDirs.size(); ++i) + { + face.mColours[i] = mAtmospherics.calcSkyColorInDir(psky, vars, face.mDirs[i], true, false); + } +} + +void ALSky::initCubeMap() +{ + std::vector> images; + for (ShinyFace& face : mShiny) + { + { + LLImageDataLock lock(face.mImage); + U32* pixels = (U32*)face.mImage->getData(); + for (size_t i = 0; i < face.mColours.size(); ++i) + { + pixels[i] = LLColor4U(face.mColours[i]).asRGBA(); + } + } + images.push_back(face.mImage); + } + + if (!mCubeMap && gSavedSettings.getBOOL("RenderWater")) + { + mCubeMap = new LLCubeMap(); + } + + if (mCubeMap) + { + mCubeMap->init(images); + } + + gGL.getTextureSlot(0)->unbind(); +} + +void ALSky::forceSkyUpdate() +{ + mForceUpdate = true; + mLastAtmosphericsVars = {}; + mCubeMapUpdateStage = -1; +} + +void ALSky::updateCubeMap() +{ + if (mShiny[0].mDirs.empty()) + { + // Every face at once, the first time. + cacheEnvironment(mAtmosphericsVars); + for (S32 side = 0; side < CUBE_FACES; ++side) + { + initSkyTextureDirs(side, 0); + createSkyTexture(mAtmosphericsVars, side, 0); + } + initCubeMap(); + } + + if (!mCubeMap || LLPipeline::sReflectionProbesEnabled) + { + mCubeMapUpdateStage = CUBE_FACES; + mForceUpdate = false; + return; + } + + if (mCubeMapUpdateStage < 0) + { + cacheEnvironment(mAtmosphericsVars); + + const bool same_atmospherics = + approximatelyEqual(mLastAtmosphericsVars, mAtmosphericsVars, UPDATE_MIN_DELTA_THRESHOLD); + const bool need_update = mForceUpdate || !same_atmospherics; + if (need_update && + (mForceUpdate || LLFrameTimer::getTotalSeconds() - mCubeMapUpdated >= CUBE_UPDATE_SECONDS)) + { + // start updating cube map sides + mAtmospherics.updateFog(LLViewerCamera::getInstance()->getFar(), + LLEnvironment::instance().getClampedLightDirection()); + mCubeMapUpdateStage = 0; + mForceUpdate = false; + } + } + else if (mCubeMapUpdateStage == CUBE_FACES) + { + LL_PROFILE_ZONE_NAMED("updateSky - forced"); + initCubeMap(); + mLastAtmosphericsVars = mAtmosphericsVars; + mForceUpdate = false; + mCubeMapUpdated = LLFrameTimer::getTotalSeconds(); + mCubeMapUpdateStage = -1; + } + else + { + // CPU hungry part, createSkyTexture() is math heavy: a face a frame. + LL_PROFILE_ZONE_NAMED("updateSky - create"); + createSkyTexture(mAtmosphericsVars, mCubeMapUpdateStage, 0); + mCubeMapUpdateStage++; + } +} + +// +// The beacons +// + +void ALSky::addSunMoonBeacons() +{ + if (!gAgentAvatarp || !mReady) return; + + static LLUICachedControl show_sun_beacon("sunbeacon", false); + static LLUICachedControl show_moon_beacon("moonbeacon", false); + + const LLEnvironment& environment = LLEnvironment::instance(); + if (show_sun_beacon) + { + renderSunMoonBeacons(gAgentAvatarp->getPositionAgent(), environment.getSunDirection(), + LLColor4(1.f, 0.5f, 0.f, 0.5f)); + } + if (show_moon_beacon) + { + renderSunMoonBeacons(gAgentAvatarp->getPositionAgent(), environment.getMoonDirection(), + LLColor4(1.f, 0.f, 0.8f, 0.5f)); + } +} diff --git a/indra/newview/alsky.h b/indra/newview/alsky.h new file mode 100644 index 00000000000..da1643466bf --- /dev/null +++ b/indra/newview/alsky.h @@ -0,0 +1,312 @@ +/** + * @file alsky.h + * @brief The sky: the sun and the moon, the sky's textures, the stars and the meteors, and what the passes that draw + * it are given. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#pragma once + +#include "alskylayers.h" +#include "alstarfield.h" +#include "lllegacyatmospherics.h" + +#include "llpointer.h" +#include "llquaternion.h" +#include "lluuid.h" +#include "llvertexbuffer.h" +#include "v3color.h" +#include "v3math.h" +#include "v4color.h" + +#include + +#include +#include + +class LLCubeMap; +class LLGLSLShader; +class LLImageRaw; +class LLViewerFetchedTexture; +class LLViewerTexture; + +/// How far from the camera the sun's and the moon's quads lie. +const F32 HEAVENLY_BODY_DIST = HORIZON_DIST - 20.f; + +/// The sun or the moon as the sky draws it: its direction and turn as the sky sets them, its disk, and its quad about +/// the camera (ALSkyLayers::bodyCorners) as the frame draws it. Its colour and whether it is up are the current sky's +/// (LLSettingsSky). +class LLHeavenBody +{ +public: + explicit LLHeavenBody(F32 disk_radius) : mDiskRadius(disk_radius) {} + + void setDirection(const LLVector3& direction) { mDirection = direction; } + + const LLQuaternion& getRotation() const { return mRotation; } + void setRotation(const LLQuaternion& rotation) { mRotation = rotation; } + + F32 getDiskRadius() const { return mDiskRadius; } + + const ALSkyLayers::Corners& corners() const { return mCorners; } + void setCorners(const ALSkyLayers::Corners& corners) { mCorners = corners; } + +private: + LLVector3 mDirection; + LLQuaternion mRotation; + F32 mDiskRadius; + ALSkyLayers::Corners mCorners{}; +}; + +/// The sky, drawn by no object and no pool: the sun and the moon, the sky's textures and their fetch, the star field's +/// buffer and the meteors, the fog colour the tracker's beacons take, and the legacy environment cube the shiny and +/// probe-less reflections read where reflection probes are off. Its pure parts are alrender's (ALStarField, ALSkyDome, +/// ALSkyLayers). +/// +/// Owned by LLPipeline, as the water's surface is. It runs at three fixed points, the same in every view: the stars +/// after renderGeomDeferred's pool loop (renderStars), the composite at the sky's pass (bindComposite), and the +/// dome wherever the haze program is bound for the water (bindDome). It holds nothing until its first update, once the +/// world draws. +class ALSky +{ +public: + /// A star's centre, and its colour in the position's fourth word, where the texture index goes (ALStarField). + static constexpr U32 STAR_VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXTURE_INDEX; + + ALSky(); + ~ALSky(); + + ALSky(const ALSky&) = delete; + ALSky& operator=(const ALSky&) = delete; + + /// Takes the frame's sky: the bodies' directions, colour and quads and whether each is up, the textures the current + /// sky names fetched at the priority they are drawn at, and the environment cube where it is wanted. Once a main + /// frame, before anything draws; the first lays the star field out. + void update(); + + /// Whether an update has run, so there is a sky to draw. + bool ready() const { return mReady; } + + /// The bodies and the textures taken again once the frame's environment update (LLEnvironment::update) has moved + /// the sky on, before the main view draws them. The views drawn before it, the mirrors, draw the sky as the frame + /// began with it, as they take its environment: their faces' environment updates do not move the sky on. Nothing + /// before the first update. + void followEnvironment(); + + /// Lets go of everything at shutdown, ahead of the texture list. + void cleanup(); + + /// The GL objects of its own, released with the context (destroyGL): the stars' buffer and the cube's, made again + /// when next drawn. + void releaseGL(); + + /// After the context returns: the cube is built again on the next update. + void restoreGL(); + + /// The environment cube built again on the next update, whatever has changed: after its programs are loaded. + void forceSkyUpdate(); + + /// The sun's direction, CFR (+x at, +z up, +y right), as LLSettingsVOSky::updateSettings and the mouselook tool set + /// it: the sun bump maps are lit by (getBumpSunDir). The bodies take the current sky's own directions. + void setSunDirectionCFR(const LLVector3& sun_direction); + + // The textures the current sky names, each the one drawn and the one the sky blends toward. They are fetched on the + // next update. + void setSunTextures(const LLUUID& sun_texture, const LLUUID& sun_texture_next); + void setMoonTextures(const LLUUID& moon_texture, const LLUUID& moon_texture_next); + void setCloudNoiseTextures(const LLUUID& cloud_noise_texture, const LLUUID& cloud_noise_texture_next); + /// Only while the sun bloom is on (ALShaderInputs::Local::mSunBloom) are the bloom's fetched. + void setBloomTextures(const LLUUID& bloom_texture, const LLUUID& bloom_texture_next); + + void setSunScale(F32 sun_scale) { mSunScale = sun_scale; } + void setMoonScale(F32 moon_scale) { mMoonScale = moon_scale; } + + const LLHeavenBody& getSun() const { return mSun; } + const LLHeavenBody& getMoon() const { return mMoon; } + + /// The sun's direction pushed south as it nears the zenith, so bump maps always show on cubes' upward faces. + const LLVector3& getBumpSunDir() const { return mBumpSunDir; } + + /// Fog distance as a fraction of the cull distance. + void setFogRatio(F32 fog_ratio) { mAtmospherics.setFogRatio(fog_ratio); } + F32 getFogRatio() const { return mAtmospherics.getFogRatio(); } + /// The sky's colour near the horizon, which the tracker's beacons fade into. + LLColor4 getSkyFogColor() const { return mAtmospherics.getFogColor(); } + + /// Begins a view, at its G-buffer's clear: what it draws of the sky (ALSkyLayers::layersFor), taken once, from the + /// kind of view, the current sky and the latched inputs, nothing where @a draws_sky is false + /// (LLPipeline::drawsSky). The stars' draw, the clear of their attachment, the meteors' tick and the composite all + /// take it from here. + void beginView(bool draws_sky); + + /// The layers the view begun last draws (ALSkyLayers::Layer), 0 in a view that draws no sky. + S32 viewLayers() const { return mViewLayers; } + + /// The stars into their attachment of the bound G-buffer (ALSkyLayers::starAttachment), the one layer of the sky + /// drawn into it. They lie on the far plane and write no depth, so the depth test passes only where the world drew + /// nothing. Only in a view whose layers have them. + void renderStars(); + + /// What windlight/windlightDomeF.glsl reads besides the Environment block, on the bound @a shader: the rainbow and + /// halo maps and their levels, the glow, density and light, and the dome's geometry. + void bindDome(LLGLSLShader& shader) const; + + /// The sky's composite's inputs on the bound @a shader, the sky's pass (deferred/skyCompositeF.glsl): the dome's + /// (bindDome), the layers this view draws (viewLayers) and what each takes, about the view's camera, the stars' + /// attachment among them. Ticks the meteors and the aurora's clock in a view that draws them. + void bindComposite(LLGLSLShader& shader); + + /// The textures bindComposite bound on @a shader, let go, and those alone: a unit whose map this view's layers left + /// unbound holds what an earlier pass bound there. + void unbindComposite(LLGLSLShader& shader); + + /// Whether the sky this view draws is the HDRI rather than WindLight's: always in a capture that has one (unless + /// only the irradiance takes it), and in the main view while its split screen shows any of it. + static bool useHDRISky(); + + /// The legacy environment cube, bound on texture @a channel for a program's environmentMap, where reflection probes + /// are off and there is one. False, with nothing bound, otherwise. + bool bindEnvironmentCube(S32 channel) const; + + /// The debug beacons toward the sun and the moon from the agent, as the menu shows them. + void addSunMoonBeacons(); + +private: + // The bodies' directions and turns as the current sky has them. + void updateDirections(); + // The textures the sky names, fetched where they changed; the bloom's only while the sun bloom is drawn. + void fetchTextures(); + // The bodies' directions, turns and quads, and the textures, from the current sky. + void takeSky(); + + // A star field as the General queue lays it out for the buffer: plain data, handed over whole. + struct StarField + { + std::vector mStars; + ALStarField::Layout mLayout; + }; + // The star field RenderStarCount asks for, laid out on the General queue and landed on the main thread by + // landStars, which keeps it where it answers the last request and drops it otherwise: a field asked for before + // the count changed again, or before cleanup. + void requestStars(); + void landStars(U32 request, StarField&& field); + // The buffer made from the field where there is none. + bool buildStars(); + // The stars drawn as points, those of the cells in view through @a frustum, and the spiked stars as quads + // (ALStarField::Layout), each with its own program bound; only those at @a faintest or brighter + // (ALStarField::faintestVisible). + void drawStarPoints(const ALStarField::Frustum& frustum, U32 faintest); + void drawStarQuads(U32 faintest); + + // Meteors: short-lived streaks simulated on the CPU, ticked once a frame in a view that draws them. + void tickMeteors(F32 dt_seconds); + // The meteors in flight as the composite takes them (ALSkyLayers::packMeteors): each one's streak about the camera + // as its envelope has carried it this far, its colour times that envelope, and its width class. + void meteors(std::vector& out) const; + + // The legacy environment cube: built on the CPU a face a frame, through LLAtmospherics, where probes are off. + void initCubeMap(); + void updateCubeMap(); + void cacheEnvironment(AtmosphericsVars& vars) const; + void initSkyTextureDirs(S32 side, S32 tile); + void createSkyTexture(AtmosphericsVars& vars, S32 side, S32 tile); + + static void renderSunMoonBeacons(const LLVector3& pos_agent, const LLVector3& direction, LLColor4 color); + + bool mReady = false; + LLHeavenBody mSun; + LLHeavenBody mMoon; + F32 mSunScale = 1.f; + F32 mMoonScale = 1.f; + LLVector3 mBumpSunDir; + + // The ids the sky names, and the textures fetched for them. + std::array mSunTextureId; + std::array mMoonTextureId; + std::array mCloudNoiseTextureId; + std::array mBloomTextureId; + std::array, 2> mSunTexture; + std::array, 2> mMoonTexture; + std::array, 2> mCloudNoiseTexture; + std::array, 2> mBloomTexture; + LLPointer mRainbowMap; + LLPointer mHaloMap; + + // The star field last landed, in the order the buffer takes it, and where each kind lies in it: none until the + // first lands, and the last one while a new count's is laid out. + std::vector mStars; + ALStarField::Layout mStarLayout; + ALStarField::Draws mStarDraws; // the frame's runs of points, kept for their storage + LLPointer mStarsVerts; + // The buffer could not be made, and is not asked for again until a new field lands or the context changes. + bool mStarsBuildFailed = false; + // The request a landing field must answer to (requestStars), moved on by each request and by cleanup. + U32 mStarRequest = 0; + // Asks for the star field again when RenderStarCount changes, which is meant to take effect without a restart. + boost::signals2::scoped_connection mStarCountConnection; + + struct MeteorState + { + LLVector3 origin_world; // starting head position + LLVector3 direction_world; // unit direction of travel + F32 path_length_world; // distance head travels across its lifetime + F32 trail_length_world; // length of the glowing trail behind the head + F32 age; // seconds since spawn + F32 lifetime; // total seconds to live + LLColor3 color; // sRGB color + F32 peak_intensity; // 0..1 scale on the envelope peak + F32 width_scale; // per-meteor thickness multiplier (0..4, packed to color.a) + }; + std::vector mMeteors; + // The meteors in flight and their streaks on the screen, kept for their storage. + std::vector mMeteorsInFlight; + std::vector mMeteorData; + // The aurora's clock, and the stars' twinkle's. + F64 mAuroraTime = 0.0; + // The maps the last bindComposite bound, which unbindComposite lets go. + std::vector mCompositeBound; + + // What decides the layers the view begun last draws, and those layers (beginView). + ALSkyLayers::Frame mView; + S32 mViewLayers = 0; + + LLAtmospherics mAtmospherics; + + // The legacy environment cube, and the shiny faces it is made from. + struct ShinyFace + { + LLPointer mImage; + std::vector mColours; + std::vector mDirs; + }; + std::array mShiny; + LLPointer mCubeMap; + AtmosphericsVars mAtmosphericsVars; + AtmosphericsVars mLastAtmosphericsVars; + S32 mCubeMapUpdateStage = -1; // -1 idle; 0-5 a face a frame; 6 finalizing + bool mForceUpdate = true; + F64 mCubeMapUpdated = 0.0; // LLFrameTimer::getTotalSeconds() at the last finished cube +}; diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 205ac73ffe4..a336995e688 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -11784,17 +11784,6 @@ Value - SkyAmbientScale - - Comment - Controls strength of ambient, or non-directional light from the sun and moon (fraction or multiple of default ambient level) - Persist - 1 - Type - F32 - Value - 0.300000011921 - SkyMoonDefaultPosition Comment @@ -11810,21 +11799,6 @@ -0.1 - SkyNightColorShift - - Comment - Controls moonlight color (base color applied to moon as light source) - Persist - 1 - Type - Color3 - Value - - 0.67 - 0.67 - 1.0 - - SkyOverrideSimSunPosition Comment diff --git a/indra/newview/app_settings/shaders/class1/deferred/skyCompositeF.glsl b/indra/newview/app_settings/shaders/class1/deferred/skyCompositeF.glsl index 2a59756b811..12747933b61 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/skyCompositeF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/skyCompositeF.glsl @@ -54,8 +54,9 @@ uniform mat3 skyHdriRotation; uniform float skyHdriScale; uniform float hdri_split_screen; -// The stars' attachment of the G-buffer (LLDrawPoolWLSky::renderStarsDeferred), cleared to black where nothing else -// writes it: the emissive buffer, or the sRGB albedo, read decoded. +// The stars' attachment of the G-buffer (ALSky::renderStars, ALSkyLayers::starAttachment): the emissive buffer, or the +// sRGB albedo, read decoded. It is cleared to black, where nothing else writes it, only in a view that draws the stars, +// and read only there. #if defined(HAS_EMISSIVE) uniform sampler2D emissiveRect; #else diff --git a/indra/newview/app_settings/shaders/class1/deferred/starsF.glsl b/indra/newview/app_settings/shaders/class1/deferred/starsF.glsl index ee9a7e51104..e3466f43e67 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/starsF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/starsF.glsl @@ -79,7 +79,7 @@ void main() vec3 premult = vary_star_color * shape; // The stars draw into one attachment and leave the rest as the sky wrote them, its flags among them - // (LLDrawPoolWLSky::renderStarsDeferred narrows the G-buffer's draw buffers to it). + // (ALSky::renderStars narrows the G-buffer's draw buffers to it). #if defined(HAS_EMISSIVE) // With a dedicated emissive buffer the stars live there (bloom source). frag_data[3] = vec4(premult, shape); diff --git a/indra/newview/app_settings/shaders/class1/deferred/starsV.glsl b/indra/newview/app_settings/shaders/class1/deferred/starsV.glsl index 1f7ff89cd44..9664406282c 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/starsV.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/starsV.glsl @@ -32,7 +32,7 @@ // One vertex a star, drawn as a point; or, for the stars with diffraction spikes (STAR_QUADS), six, drawn as a quad: GL // drops a point whose centre leaves the screen, which would pop a spiked star's arms at the edge -// (LLDrawPoolWLSky::renderStarsDeferred, ALStarField). +// (ALSky::renderStars, ALStarField). in vec3 position; // the star's centre, in the stars' frame in int texture_index; // its colour, in the position's fourth word: rgb = sRGB black-body colour, a = intensity @@ -51,7 +51,7 @@ uniform vec3 moon_dir; // moon direction in the stars' frame (z up) uniform float moon_brightness; // moon illumination 0..1 (from LLSettingsSky) // The moon's quad in the stars' frame, on the plane a unit away along moonQuadCenter (zero when no moon is drawn), and -// its texture (LLDrawPoolWLSky::renderStarsDeferred). +// its texture (ALSky::renderStars). uniform vec3 moonQuadCenter; uniform vec3 moonQuadU; uniform vec3 moonQuadV; diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 1dd7e2dba16..a4f9c7fc78e 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -69,7 +69,6 @@ #include "llrendersphere.h" #include "llscriptruntimeperms.h" #include "llsdutil.h" -#include "llsky.h" #include "llslurl.h" #include "llsmoothstep.h" #include "llstartup.h" @@ -1106,13 +1105,6 @@ void LLAgent::setRegion(LLViewerRegion *regionp) // Update all of the regions. LLWorld::getInstance()->updateAgentOffset(agent_offset_global); - // Hack to keep sky in the agent's region, otherwise it may get deleted - DJS 08/02/02 - // *TODO: possibly refactor into gSky->setAgentRegion(regionp)? -Brad - if (gSky.mVOSkyp) - { - gSky.mVOSkyp->setRegion(regionp); - } - if (regionp->capabilitiesReceived()) { regionp->requestSimulatorFeatures(); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 3f890f97611..62e64ac220c 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -231,7 +231,6 @@ #include "llbutton.h" #include "llstatusbar.h" #include "llsurface.h" -#include "llvosky.h" #include "llvotree.h" #include "llvoavatar.h" #include "llfolderview.h" @@ -254,7 +253,6 @@ #include "lldeferredsounds.h" #include "pipeline.h" #include "llgesturemgr.h" -#include "llsky.h" #include "llvlcomposition.h" #include "llvlmanager.h" #include "llviewercamera.h" @@ -5884,18 +5882,12 @@ void LLAppViewer::idle() // Update wind vector LLVector3 wind_position_region; - static LLVector3 average_wind; LLViewerRegion *regionp; regionp = LLWorld::getInstance()->resolveRegionGlobal(wind_position_region, gAgent.getPositionGlobal()); // puts agent's local coords into wind_position if (regionp) { gWindVec = regionp->mWind.getVelocity(wind_position_region); - - // Compute average wind and use to drive motion of water - - average_wind = regionp->mWind.getAverage(); - gSky.setWind(average_wind); } else { diff --git a/indra/newview/lldrawable.cpp b/indra/newview/lldrawable.cpp index f0583f06111..1e1527a7a5a 100644 --- a/indra/newview/lldrawable.cpp +++ b/indra/newview/lldrawable.cpp @@ -38,7 +38,6 @@ #include "llface.h" #include "lllightconstants.h" #include "llmatrix4a.h" -#include "llsky.h" #include "llsurfacepatch.h" #include "llviewercamera.h" #include "llviewerregion.h" @@ -506,8 +505,7 @@ void LLDrawable::makeActive() U32 pcode = mVObjp->getPCode(); if (pcode == LLViewerObject::LL_VO_SURFACE_PATCH || pcode == LLViewerObject::LL_VO_PART_GROUP || - pcode == LLViewerObject::LL_VO_HUD_PART_GROUP || - pcode == LLViewerObject::LL_VO_SKY) + pcode == LLViewerObject::LL_VO_HUD_PART_GROUP) { LL_ERRS() << "Static viewer object has active drawable!" << LL_ENDL; } @@ -1032,8 +1030,7 @@ void LLDrawable::shiftPos(const LLVector4a &shift_vector) bool rebuild = (!volume && getRenderType() != LLPipeline::RENDER_TYPE_TREE && - getRenderType() != LLPipeline::RENDER_TYPE_TERRAIN && - getRenderType() != LLPipeline::RENDER_TYPE_SKY); + getRenderType() != LLPipeline::RENDER_TYPE_TERRAIN); if (rebuild) { diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp index b66fb5f6bd5..0a2356f29f8 100644 --- a/indra/newview/lldrawpool.cpp +++ b/indra/newview/lldrawpool.cpp @@ -37,7 +37,6 @@ #include "lldrawpoolmaterials.h" #include "lldrawpoolpbropaque.h" #include "lldrawpoolsimple.h" -#include "lldrawpoolsky.h" #include "lldrawpooltree.h" #include "lldrawpoolterrain.h" #include "lldrawpoolwater.h" @@ -47,7 +46,6 @@ #include "pipeline.h" #include "llspatialpartition.h" #include "llviewercamera.h" -#include "lldrawpoolwlsky.h" #include "llglslshader.h" #include "llglcommonfunc.h" #include "llimagegl.h" @@ -102,9 +100,6 @@ LLDrawPool *LLDrawPool::createPool(const U32 type, LLViewerTexture *tex0) case POOL_TERRAIN: poolp = new LLDrawPoolTerrain(tex0); break; - case POOL_SKY: - poolp = new LLDrawPoolSky(); - break; case POOL_WATER: poolp = new LLDrawPoolWater(); break; @@ -114,9 +109,6 @@ LLDrawPool *LLDrawPool::createPool(const U32 type, LLViewerTexture *tex0) case POOL_MATERIALS: poolp = new LLDrawPoolMaterials(); break; - case POOL_WL_SKY: - poolp = new LLDrawPoolWLSky(); - break; case POOL_GLTF_PBR: poolp = new LLDrawPoolGLTFPBR(); break; diff --git a/indra/newview/lldrawpool.h b/indra/newview/lldrawpool.h index ed579c996f1..b6fd3f54c55 100644 --- a/indra/newview/lldrawpool.h +++ b/indra/newview/lldrawpool.h @@ -57,8 +57,7 @@ class LLDrawPool // before grass, so grass should be the first alpha masked pool. Other ordering should be done // based on fill rate and likelihood to occlude future passes (faster, large occluders first). // - POOL_SKY = 1, - POOL_WATEREXCLUSION, + POOL_WATEREXCLUSION = 1, POOL_SIMPLE, POOL_FULLBRIGHT, POOL_BUMP, @@ -72,7 +71,6 @@ class LLDrawPool POOL_FULLBRIGHT_ALPHA_MASK, POOL_AVATAR, POOL_CONTROL_AV, // Animesh - POOL_WL_SKY, // after every opaque pool, so the depth test spares it all they cover POOL_GLOW, POOL_ALPHA_PRE_WATER, POOL_VOIDWATER, @@ -140,7 +138,7 @@ class LLDrawPool // the pass renders at the wrong gamma. // // Converted: diffuse, diffusealphamask (incl. grass), bump, tree, terrain, materials, -// avatar skin, PBR. The remaining raw writers -- the WL sky family, the avatar +// avatar skin, PBR. The remaining raw writers -- the stars (ALSky::renderStars), the avatar // impostor/rigid passes, and the parcel-owner overlay -- opt OUT of the hoisted encode // locally and keep the AnisoWrap default on their samples. // @@ -403,7 +401,7 @@ class LLRenderPass : public LLDrawPool // AnisoWrap, plus the SRGBDecode bit when the program's mLinearDiffuse (the // LINEAR_DIFFUSE permutation) says the pass shades in linear. The matching re-encode // on store comes from the deferred pass's hoisted GL_FRAMEBUFFER_SRGB - // (renderGeomDeferred); raw pass-through writers -- the WL sky family, the avatar + // (renderGeomDeferred); raw pass-through writers -- the stars (ALSky::renderStars), the avatar // impostor/rigid passes -- opt out of that encode locally and keep undecoded samples, // letting the lighting pass's decoded read close the loop instead. static void bindIndexedTextures(const LLDrawInfo& params, const LLGLSLShader* shader); diff --git a/indra/newview/lldrawpoolalpha.cpp b/indra/newview/lldrawpoolalpha.cpp index a03b75021b5..888a0213934 100644 --- a/indra/newview/lldrawpoolalpha.cpp +++ b/indra/newview/lldrawpoolalpha.cpp @@ -34,7 +34,6 @@ #include "llrender.h" #include "llcubemap.h" -#include "llsky.h" #include "lldrawable.h" #include "llface.h" #include "llviewercamera.h" diff --git a/indra/newview/lldrawpoolavatar.cpp b/indra/newview/lldrawpoolavatar.cpp index 1f1102ad5fc..e679626352f 100644 --- a/indra/newview/lldrawpoolavatar.cpp +++ b/indra/newview/lldrawpoolavatar.cpp @@ -39,7 +39,6 @@ #include "lldrawpoolbump.h" #include "llface.h" #include "llmeshrepository.h" -#include "llsky.h" #include "llviewercamera.h" #include "llviewerregion.h" #include "noise.h" diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp index e07d0637a70..9f61eae8489 100644 --- a/indra/newview/lldrawpoolbump.cpp +++ b/indra/newview/lldrawpoolbump.cpp @@ -40,7 +40,6 @@ #include "llcubemap.h" #include "lldrawable.h" #include "llface.h" -#include "llsky.h" #include "llstartup.h" #include "lltextureentry.h" #include "llviewercamera.h" @@ -209,74 +208,6 @@ S32 LLDrawPoolBump::numBumpPasses() } -//static -void LLDrawPoolBump::bindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& diffuse_channel, S32& cube_channel) -{ - LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; - if( cube_map && !LLPipeline::sReflectionProbesEnabled ) - { - if (shader ) - { - const LLMatrix4a& modelview = LLViewerCamera::getCurrent().getModelview(); - const LLMatrix4 mat = modelview.toMatrix4(); - LLVector3 vec = LLVector3(gShinyOrigin) * mat; - LLVector4 vec4(vec, gShinyOrigin.mV[3]); - shader->uniform4fv(LLViewerShaderMgr::SHINY_ORIGIN, 1, vec4.mV); - if (shader_level > 1) - { - cube_map->setMatrix(1, modelview); - // Make sure that texture coord generation happens for tex unit 1, as that's the one we use for - // the cube map in the one pass shiny shaders - cube_channel = shader->enableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP); - cube_map->enableTexture(cube_channel); - diffuse_channel = shader->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - } - else - { - cube_map->setMatrix(0, modelview); - cube_channel = shader->enableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP); - diffuse_channel = -1; - cube_map->enable(cube_channel); - } - gGL.getTextureSlot(cube_channel)->bind(cube_map, ALSamplers::AnisoClamp); - } - else - { - cube_channel = 0; - diffuse_channel = -1; - gGL.getTextureSlot(0)->unbind(); - cube_map->enable(0); - cube_map->setMatrix(0, LLViewerCamera::getCurrent().getModelview()); - gGL.getTextureSlot(0)->bind(cube_map, ALSamplers::AnisoClamp); - } - } -} - -//static -void LLDrawPoolBump::unbindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& diffuse_channel, S32& cube_channel) -{ - LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; - if( cube_map && !LLPipeline::sReflectionProbesEnabled) - { - if (shader_level > 1) - { - shader->disableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP, ALTextureSlot::TT_CUBE_MAP); - - if (LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT) > 0) - { - if (diffuse_channel != 0) - { - shader->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - } - } - } - // Moved below shader->disableTexture call to avoid false alarms from auto-re-enable of textures on stage 0 - // MAINT-755 - cube_map->disable(); - cube_map->restoreMatrix(); - } -} - void LLDrawPoolBump::beginFullbrightShiny() { LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; @@ -303,18 +234,14 @@ void LLDrawPoolBump::beginFullbrightShiny() gGL.getTextureSlot(channel)->bind(&gPipeline.mExposureMap); } - LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; - - if (cube_map && !LLPipeline::sReflectionProbesEnabled) + if (!LLPipeline::sReflectionProbesEnabled) { // Make sure that texture coord generation happens for tex unit 1, as that's the one we use for // the cube map in the one pass shiny shaders gGL.getTextureSlot(1)->unbind(); cube_channel = shader->enableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP); - cube_map->enableTexture(cube_channel); diffuse_channel = shader->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - - gGL.getTextureSlot(cube_channel)->bind(cube_map, ALSamplers::AnisoClamp); + gPipeline.mSky.bindEnvironmentCube(cube_channel); } { @@ -379,10 +306,9 @@ void LLDrawPoolBump::endFullbrightShiny() { LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; - if( cube_map && !LLPipeline::sReflectionProbesEnabled ) + if (!LLPipeline::sReflectionProbesEnabled) { - cube_map->disable(); + shader->disableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP, ALTextureSlot::TT_CUBE_MAP); if (shader->mFeatures.hasReflectionProbes) { gPipeline.unbindReflectionProbes(*shader); diff --git a/indra/newview/lldrawpoolbump.h b/indra/newview/lldrawpoolbump.h index 15e05d0bd67..1934a566ca3 100644 --- a/indra/newview/lldrawpoolbump.h +++ b/indra/newview/lldrawpoolbump.h @@ -67,8 +67,6 @@ protected : void renderBump(U32 pass = LLRenderPass::PASS_BUMP); void endBump(U32 pass = LLRenderPass::PASS_BUMP); - static void bindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& diffuse_channel, S32& cube_channel); - static void unbindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& diffuse_channel, S32& cube_channel); virtual S32 getNumDeferredPasses() override; /*virtual*/ void renderDeferred(S32 pass) override; diff --git a/indra/newview/lldrawpoolsimple.cpp b/indra/newview/lldrawpoolsimple.cpp index 85a4b1a7aea..724c218cbd7 100644 --- a/indra/newview/lldrawpoolsimple.cpp +++ b/indra/newview/lldrawpoolsimple.cpp @@ -31,7 +31,6 @@ #include "llviewercamera.h" #include "lldrawable.h" #include "llface.h" -#include "llsky.h" #include "pipeline.h" #include "llspatialpartition.h" #include "llviewershadermgr.h" diff --git a/indra/newview/lldrawpoolsky.cpp b/indra/newview/lldrawpoolsky.cpp deleted file mode 100644 index 1e83426714b..00000000000 --- a/indra/newview/lldrawpoolsky.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @file lldrawpoolsky.cpp - * @brief LLDrawPoolSky class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldrawpoolsky.h" - -// DEPRECATED - -LLDrawPoolSky::LLDrawPoolSky() -: LLFacePool(POOL_SKY), - mSkyTex(NULL), - mShader(NULL) -{ -} - -void LLDrawPoolSky::prerender() -{ -} - -void LLDrawPoolSky::render(S32 pass) -{ - -} - -void LLDrawPoolSky::renderSkyFace(U8 index) -{ - -} - -void LLDrawPoolSky::endRenderPass( S32 pass ) -{ -} - diff --git a/indra/newview/lldrawpoolsky.h b/indra/newview/lldrawpoolsky.h deleted file mode 100644 index 574afd05587..00000000000 --- a/indra/newview/lldrawpoolsky.h +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @file lldrawpoolsky.h - * @brief LLDrawPoolSky class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLDRAWPOOLSKY_H -#define LL_LLDRAWPOOLSKY_H - -#include "lldrawpool.h" - -class LLSkyTex; -class LLGLSLShader; - -class LLDrawPoolSky : public LLFacePool -{ -private: - LLSkyTex *mSkyTex; - LLGLSLShader *mShader; - -public: - enum - { - VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | - LLVertexBuffer::MAP_TEXCOORD0 - }; - - virtual U32 getVertexDataMask() { return VERTEX_DATA_MASK; } - - LLDrawPoolSky(); - - /*virtual*/ S32 getNumPostDeferredPasses() { return getNumPasses(); } - /*virtual*/ void beginPostDeferredPass(S32 pass) { beginRenderPass(pass); } - /*virtual*/ void endPostDeferredPass(S32 pass) { endRenderPass(pass); } - /*virtual*/ void renderPostDeferred(S32 pass) { render(pass); } - - /*virtual*/ void prerender(); - /*virtual*/ void render(S32 pass = 0); - /*virtual*/ void endRenderPass(S32 pass); - void setSkyTex(LLSkyTex* const st) { mSkyTex = st; } - - void renderSkyFace(U8 index); - void renderHeavenlyBody(U8 hb, LLFace* face); - void renderSunHalo(LLFace* face); - -}; - -#endif // LL_LLDRAWPOOLSKY_H diff --git a/indra/newview/lldrawpoolterrain.cpp b/indra/newview/lldrawpoolterrain.cpp index cd6ce03f819..ef3a90bfc4e 100644 --- a/indra/newview/lldrawpoolterrain.cpp +++ b/indra/newview/lldrawpoolterrain.cpp @@ -34,7 +34,6 @@ #include "llviewercontrol.h" #include "lldrawable.h" #include "llface.h" -#include "llsky.h" #include "llsurface.h" #include "llsurfacepatch.h" #include "llviewerregion.h" diff --git a/indra/newview/lldrawpooltree.cpp b/indra/newview/lldrawpooltree.cpp index 7902018dd68..fa476d1db18 100644 --- a/indra/newview/lldrawpooltree.cpp +++ b/indra/newview/lldrawpooltree.cpp @@ -30,7 +30,6 @@ #include "lldrawable.h" #include "llface.h" -#include "llsky.h" #include "llvotree.h" #include "pipeline.h" #include "llviewercamera.h" diff --git a/indra/newview/lldrawpoolwlsky.cpp b/indra/newview/lldrawpoolwlsky.cpp deleted file mode 100644 index 3949e4ed92e..00000000000 --- a/indra/newview/lldrawpoolwlsky.cpp +++ /dev/null @@ -1,563 +0,0 @@ -/** - * @file lldrawpoolwlsky.cpp - * @brief LLDrawPoolWLSky class implementation - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * Alchemy Viewer Source Code - * Copyright © 2026, Rye - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldrawpoolwlsky.h" - -#include "llerror.h" -#include "llface.h" -#include "llimage.h" -#include "llrender.h" -#include "llenvironment.h" -#include "llglslshader.h" -#include "llgl.h" - -#include "llviewerregion.h" -#include "llviewershadermgr.h" -#include "llviewercamera.h" -#include "pipeline.h" -#include "llsky.h" -#include "llvowlsky.h" -#include "llsettingsvo.h" -#include "llviewercontrol.h" -#include "llappviewer.h" // gFrameIntervalSeconds - -#include "llglstates.h" - -#include "alskylayers.h" - -extern bool gCubeSnapshot; - -static float sStarTime; - -LLDrawPoolWLSky::LLDrawPoolWLSky(void) : - LLDrawPool(POOL_WL_SKY) -{ -} - -LLDrawPoolWLSky::~LLDrawPoolWLSky() -{ -} - -LLViewerTexture *LLDrawPoolWLSky::getDebugTexture() -{ - return NULL; -} - -extern LLPointer gEXRImage; - -namespace -{ - // A texture pair the sky blends between, as the sun's and the clouds' draws took one: the first, and the next - // only while they differ and both are there, with how far the sky has blended toward it. - struct TexturePair - { - LLViewerTexture* mFirst = nullptr; - LLViewerTexture* mNext = nullptr; - F32 mBlend = 0.f; - }; - - TexturePair texturePair(LLViewerTexture* first, LLViewerTexture* next, F32 blend) - { - if (first && (!next || first == next)) - { - return { first, nullptr, 0.f }; - } - if (next && !first) - { - return { next, nullptr, 0.f }; - } - if (first && next) - { - return { first, next, blend }; - } - return {}; - } - - // A body's quad as LLVOSky keeps it. - ALSkyLayers::Corners corners(const LLHeavenBody& body) - { - const LLVector3* c = body.corners(); - return { c[0], c[1], c[2], c[3] }; - } - - // The maps the last bindComposite bound, which unbindComposite lets go. - std::vector sCompositeBound; -} - -// static -bool LLDrawPoolWLSky::useHDRISky() -{ - static LLCachedControl hdri_split(gSavedSettings, "RenderHDRISplitScreen", 1.f); - static LLCachedControl irradiance_only(gSavedSettings, "RenderHDRIIrradianceOnly", false); - - return gCubeSnapshot && (!irradiance_only || !gPipeline.mReflectionMapManager.isRadiancePass()) ? gEXRImage.notNull() : // always use HDRI for reflection probes when available - gEXRImage.notNull() ? hdri_split > 0.f : // fallback to EEP sky when split screen is zero - false; // no HDRI available, always use EEP sky - -} - -// static -void LLDrawPoolWLSky::bindDome(LLGLSLShader& shader) -{ - LLEnvironment& environment = LLEnvironment::instance(); - const LLSettingsSky::ptr_t psky = environment.getCurrentSky(); - - shader.bindTexture(LLShaderMgr::RAINBOW_MAP, gSky.mVOSkyp->getRainbowTex(), ALSamplers::AnisoWrap); - shader.bindTexture(LLShaderMgr::HALO_MAP, gSky.mVOSkyp->getHaloTex(), ALSamplers::AnisoWrap); - - F32 moisture_level = (float)psky->getSkyMoistureLevel(); - F32 droplet_radius = (float)psky->getSkyDropletRadius(); - F32 ice_level = (float)psky->getSkyIceLevel(); - - // hobble halos and rainbows when there's no light source to generate them - if (!psky->getIsSunUp() && !psky->getIsMoonUp()) - { - moisture_level = 0.0f; - ice_level = 0.0f; - } - - shader.uniform1f(LLShaderMgr::MOISTURE_LEVEL, moisture_level); - shader.uniform1f(LLShaderMgr::DROPLET_RADIUS, droplet_radius); - shader.uniform1f(LLShaderMgr::ICE_LEVEL, ice_level); - - shader.uniform1f(LLShaderMgr::SUN_MOON_GLOW_FACTOR, psky->getSunMoonGlowFactor()); - shader.uniform1f(LLShaderMgr::DENSITY_MULTIPLIER, psky->getDensityMultiplier()); - shader.uniform3fv(LLShaderMgr::LIGHTNORM, 1, environment.getClampedLightNorm().mV); - - // The dome's geometry: the camera's place in its frame, and the radius of the sphere the dome lies on. - shader.uniform3f(LLShaderMgr::WL_CAMPOSLOCAL, 0.f, environment.getCamHeight(), 0.f); - shader.uniform1f(LLShaderMgr::SKY_DOME_RADIUS, psky->getDomeRadius()); -} - -// static -void LLDrawPoolWLSky::bindComposite(LLGLSLShader& shader) -{ - LLEnvironment& environment = LLEnvironment::instance(); - const LLSettingsSky::ptr_t psky = environment.getCurrentSky(); - LLVOSky* sky = gSky.mVOSkyp; - - // Every map bound below is recorded, so that unbindComposite lets go of these and no other unit. - sCompositeBound.clear(); - const auto bind = [&shader](U32 map, LLViewerTexture* texture, ALSampler sampler) - { - if (shader.bindTexture(map, texture, sampler) > -1) - { - sCompositeBound.push_back(map); - } - }; - - // The dome's two maps, which bindDome binds wherever the program reads them. - bindDome(shader); - sCompositeBound.push_back(LLShaderMgr::RAINBOW_MAP); - sCompositeBound.push_back(LLShaderMgr::HALO_MAP); - - ALSkyLayers::Frame frame; - frame.mProbe = gCubeSnapshot; - frame.mMirror = gPipeline.mHeroProbeManager.isMirrorPass(); - frame.mRadiance = gPipeline.mReflectionMapManager.isRadiancePass(); - frame.mHdri = useHDRISky(); - - if (frame.mHdri) - { - static LLCachedControl hdri_exposure(gSavedSettings, "RenderHDRIExposure", 0.0f); - static LLCachedControl hdri_rotation(gSavedSettings, "RenderHDRIRotation", 0.f); - static LLCachedControl hdri_split(gSavedSettings, "RenderHDRISplitScreen", 1.f); - - const S32 channel = shader.enableTexture(LLShaderMgr::SKY_HDRI_MAP); - if (channel > -1 && gGL.getTextureSlot(channel)->bindSampled(gEXRImage, ALSamplers::TrilinearClamp)) - { - sCompositeBound.push_back(LLShaderMgr::SKY_HDRI_MAP); - } - LLMatrix3 rot; - rot.setRot(0.f, hdri_rotation * DEG_TO_RAD, 0.f); - shader.uniformMatrix3fv(LLShaderMgr::SKY_HDRI_ROTATION, 1, GL_FALSE, (F32*)rot.mMatrix); - shader.uniform1f(LLShaderMgr::SKY_HDRI_SCALE, powf(2.f, hdri_exposure)); - shader.uniform1f(LLShaderMgr::HDRI_SPLIT_SCREEN, gCubeSnapshot ? 1.f : (F32)hdri_split); - shader.uniform1i(LLShaderMgr::SKY_LAYERS, ALSkyLayers::layersFor(frame)); - return; - } - - const F32 blend_factor = (F32)psky->getBlendFactor(); - - // The sun, and its bloom while the latched input draws it (its textures are fetched only then). - LLFace* sun_face = sky->mFace[LLVOSky::FACE_SUN]; - const TexturePair sun = sun_face ? texturePair(sun_face->getTexture(LLRender::DIFFUSE_MAP), - sun_face->getTexture(LLRender::ALTERNATE_DIFFUSE_MAP), blend_factor) - : TexturePair(); - frame.mSun = sky->getSun().getDraw() && sun.mFirst; - const TexturePair bloom = texturePair(sky->getBloomTex(), sky->getBloomTexNext(), blend_factor); - frame.mBloom = gPipeline.shaderInputs().mLocal.mSunBloom && bloom.mFirst; - - // The moon, its first texture alone. - LLFace* moon_face = sky->mFace[LLVOSky::FACE_MOON]; - LLViewerTexture* moon_texture = moon_face ? moon_face->getTexture(LLRender::DIFFUSE_MAP) : nullptr; - frame.mMoon = sky->getMoon().getDraw() && moon_texture; - - // The night: the stars' brightness gates the meteors and the aurora, and the moon washes the aurora out. - static LLCachedControl meteors_enabled(gSavedSettings, "RenderMeteorsEnabled", true); - static LLCachedControl aurora_intensity(gSavedSettings, "RenderAuroraIntensity", 0.6f); - frame.mStarAlpha = psky->getStarBrightness() / 500.0f; - const F32 moon_wash = psky->getIsMoonUp() ? (F32)psky->getMoonBrightness() : 0.0f; - frame.mAurora = aurora_intensity > 0.f ? aurora_intensity * llmax(0.0f, 1.0f - moon_wash * 0.85f) : 0.f; - - // The clouds, which take their first noise texture to draw. - const F32 cloud_blend = psky->getCloudScrollRate().isExactlyZero() ? 0.f : blend_factor; - const TexturePair clouds = texturePair(sky->getCloudNoiseTex(), sky->getCloudNoiseTexNext(), cloud_blend); - frame.mClouds = gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_CLOUDS) && sky->getCloudNoiseTex(); - - // Each frame's interval, clamped after a long one so a meteor does not snap to the end of its life. Zero while the - // world is paused, which freezes the meteors and the aurora with it. - F32 dt = (F32)gFrameIntervalSeconds.value(); - if (dt > 0.1f) - { - dt = 0.1f; - } - - // The meteors fly in the views that draw them, through the matrices that drew their quads: the camera's, taken to - // its origin. - static std::vector in_flight; - static std::vector meteor_data; - meteor_data.clear(); - U32 meteor_count = 0; - if (meteors_enabled && frame.mStarAlpha >= 0.001f && !frame.mProbe && gSky.mVOWLSkyp.notNull()) - { - gSky.mVOWLSkyp->tickMeteors(dt); - gSky.mVOWLSkyp->meteors(in_flight); - - const LLVector3& origin = LLViewerCamera::getInstance()->getOrigin(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - gGL.loadMatrix(LLViewerCamera::getCurrent().getModelview()); - gGL.translatef(origin.mV[VX], origin.mV[VY], origin.mV[VZ]); - LLMatrix4a mvp; - mvp.setMul(gGL.getModelviewMatrix(), LLViewerCamera::getCurrent().getProjection()); - gGL.popMatrix(); - - const LLRenderTarget& screen = gPipeline.mRT->deferredScreen; - meteor_count = ALSkyLayers::packMeteors(in_flight, mvp, (F32)screen.getWidth(), (F32)screen.getHeight(), - ALSkyLayers::METEOR_WIDTH_PIXELS, meteor_data); - frame.mMeteors = meteor_count > 0; - } - - const S32 layers = ALSkyLayers::layersFor(frame); - shader.uniform1i(LLShaderMgr::SKY_LAYERS, layers); - - if (layers & ALSkyLayers::SUN) - { - // The disc lowered as its vertex stage lowered it. - const std::array quad = - ALSkyLayers::rayQuad(corners(sky->getSun()), LLVector3(0.f, 0.f, -ALSkyLayers::SUN_DROP)); - shader.uniform3fv(LLShaderMgr::SKY_SUN_QUAD, 3, quad[0].mV); - bind(LLShaderMgr::SKY_SUN_MAP, sun.mFirst, ALSamplers::AnisoClamp); - if (sun.mNext) - { - bind(LLShaderMgr::SKY_SUN_MAP_NEXT, sun.mNext, ALSamplers::AnisoClamp); - } - shader.uniform1f(LLShaderMgr::SKY_SUN_BLEND, sun.mBlend); - } - - if (layers & ALSkyLayers::BLOOM) - { - // The halo's square about the disc, lowered with it, tinted by the sun's colour as a vertex colour held to 1. - const std::array quad = ALSkyLayers::rayQuad(ALSkyLayers::bloomCorners(corners(sky->getSun())), - LLVector3(0.f, 0.f, -ALSkyLayers::SUN_DROP)); - shader.uniform3fv(LLShaderMgr::SKY_BLOOM_QUAD, 3, quad[0].mV); - bind(LLShaderMgr::SKY_BLOOM_MAP, bloom.mFirst, ALSamplers::AnisoClamp); - if (bloom.mNext) - { - bind(LLShaderMgr::SKY_BLOOM_MAP_NEXT, bloom.mNext, ALSamplers::AnisoClamp); - } - shader.uniform1f(LLShaderMgr::SKY_BLOOM_BLEND, bloom.mBlend); - LLColor3 colour = psky->getSunDiffuse(); - colour.clamp(); - shader.uniform3fv(LLShaderMgr::SKY_BLOOM_COLOR, 1, colour.mV); - } - - if (layers & ALSkyLayers::MOON) - { - const std::array quad = ALSkyLayers::rayQuad(corners(sky->getMoon())); - shader.uniform3fv(LLShaderMgr::SKY_MOON_QUAD, 3, quad[0].mV); - bind(LLShaderMgr::SKY_MOON_MAP, moon_texture, ALSamplers::AnisoClamp); - shader.uniform1f(LLShaderMgr::SKY_MOON_FADE, ALSkyLayers::moonFade(psky->getMoonDirection().mV[VZ])); - shader.uniform1f(LLShaderMgr::MOON_BRIGHTNESS, (F32)psky->getMoonBrightness()); - } - - if (layers & ALSkyLayers::METEORS) - { - shader.uniform4fv(LLShaderMgr::SKY_METEOR_DATA, (U32)meteor_data.size(), meteor_data[0].mV); - shader.uniform1i(LLShaderMgr::SKY_METEOR_COUNT, (S32)meteor_count); - } - - if (layers & ALSkyLayers::AURORA) - { - // Accumulated in F64: an F32 near the wrap holds the time only to 8 ms, so adding a frame's - // dt to it rounds each step to whole multiples of that, and the aurora moves unevenly. - static F64 s_aurora_time = 0.0; - s_aurora_time = fmod(s_aurora_time + dt, 86400.0); - shader.uniform1f(LLShaderMgr::AURORA_INTENSITY, frame.mAurora); - shader.uniform1f(LLShaderMgr::AURORA_TIME, (F32)s_aurora_time); - } - - if (layers & ALSkyLayers::CLOUDS) - { - bind(LLShaderMgr::CLOUD_NOISE_MAP, clouds.mFirst, ALSamplers::AnisoWrap); - if (clouds.mNext) - { - bind(LLShaderMgr::CLOUD_NOISE_MAP_NEXT, clouds.mNext, ALSamplers::AnisoWrap); - } - shader.uniform1f(LLShaderMgr::SKY_CLOUD_BLEND, clouds.mBlend); - shader.uniform1f(LLShaderMgr::CLOUD_VARIANCE, (F32)psky->getCloudVariance()); - } -} - -// static -void LLDrawPoolWLSky::unbindComposite(LLGLSLShader& shader) -{ - for (U32 map : sCompositeBound) - { - shader.disableTexture(map); - } - sCompositeBound.clear(); -} - -void LLDrawPoolWLSky::renderStarsDeferred(const LLVector3& camPosLocal) const -{ - if (!gSky.mVOSkyp || useHDRISky()) - { - return; - } - - LLGLEnable blend(GL_BLEND); - - // Pre-multiplied additive blend: the shader multiplies the star's RGB by - // its shape/alpha before output, so the framebuffer operation is a simple - // screen-space add rather than an alpha-blend. - gGL.setSceneBlendType(LLRender::BT_ADD); - - F32 star_alpha = LLEnvironment::instance().getCurrentSky()->getStarBrightness() / 500.0f; - - // If start_brightness is not set, exit - if(star_alpha < 0.001f) - { - LL_DEBUGS("SKY") << "star_brightness below threshold." << LL_ENDL; - return; - } - - // The G-buffer the stars draw into, and their attachment of it: the emissive with the emissive buffer, the albedo - // without it. The fragment stage writes that output alone, so a target without it takes no stars. - const bool emissive = gPipeline.shaderInputs().mGlobal.mEmissive; - const U32 attachment = emissive ? 3 : 0; - LLRenderTarget* gbuffer = LLRenderTarget::getCurrentBoundTarget(); - if (!gbuffer || attachment >= gbuffer->getNumTextures()) - { - llassert(false); - return; - } - - gGL.pushMatrix(); - gGL.translatef(camPosLocal.mV[0], camPosLocal.mV[1], camPosLocal.mV[2]); - // Subtle rotation so fixed patterns drift over long time scales. - const F32 star_rotation = (F32)fmod(gFrameTimeSeconds * 0.01, 360.0); - gGL.rotatef(star_rotation, 0.f, 0.f, 1.f); - - // The stars' frame is the agent's turned by the rotation above, and the vertex shader works in it, so agent - // directions are turned back by the rotation's transpose. - LLMatrix4a to_stars = LLMatrix4a::rotation(star_rotation * DEG_TO_RAD, LLVector4a(0.f, 0.f, 1.f)); - to_stars.transpose(); - const auto in_stars = [&to_stars](const LLVector3& v) - { - LLVector4a out; - to_stars.rotate(LLVector4a(v.mV[VX], v.mV[VY], v.mV[VZ]), out); - return LLVector3(out.getF32ptr()); - }; - - // Moon glare inputs, in the stars' frame so the glare stays on the moon however far the stars have turned. - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - const LLVector3 moon_dir = in_stars(psky->getMoonDirection()); - const F32 moon_brightness = psky->getIsMoonUp() ? (F32)psky->getMoonBrightness() : 0.0f; - - // Wrapped time for scintillation noise. 86400s is long enough that a - // typical session never hits the wrap boundary (where the non-periodic - // noise would show a one-frame jump), and short enough that 32-bit float - // precision stays good for the scaled time term in the shader. - sStarTime = (F32)fmod(LLFrameTimer::getUptimeSeconds() * 0.5, 86400.0); - - // The composite lays the moon over the stars and fades it low in the sky, so the stars leave out what its quad and - // texture cover (starsV.glsl). Its corners lie around the camera in agent space. - LLFace* moon_face = gSky.mVOSkyp->mFace[LLVOSky::FACE_MOON]; - LLViewerTexture* moon_tex = moon_face ? moon_face->getTexture(LLRender::DIFFUSE_MAP) : nullptr; - const LLHeavenBody& moon = gSky.mVOSkyp->getMoon(); - const bool mask_moon = moon.getDraw() && moon_tex; - LLVector3 moon_centre; - LLVector3 moon_u; - LLVector3 moon_v; - if (mask_moon) - { - // Corners 0..3 are (-right, +up), (-right, -up), (+right, +up), (+right, -up) (ALSkyLayers::bodyCorners). - const LLVector3 centre = (moon.corner(0) + moon.corner(3)) * 0.5f; - const F32 dist = centre.length(); - LLVector3 half_u = (moon.corner(2) - moon.corner(0)) * (0.5f / dist); - LLVector3 half_v = (moon.corner(0) - moon.corner(1)) * (0.5f / dist); - half_u /= half_u.lengthSquared(); - half_v /= half_v.lengthSquared(); - - moon_centre = in_stars(centre / dist); - moon_u = in_stars(half_u); - moon_v = in_stars(half_v); - } - - // Both star programs take the same inputs. - const auto bind_stars = [&](LLGLSLShader& program) - { - program.bind(); - program.uniform1f(LLShaderMgr::CUSTOM_ALPHA, star_alpha); - // The viewport the stars draw into, which the vertex stage sizes them in and the points find their centres - // by. - program.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, (GLfloat)LLRenderTarget::sCurResX, - (GLfloat)LLRenderTarget::sCurResY); - program.uniform3fv(LLShaderMgr::DEFERRED_MOON_DIR, 1, moon_dir.mV); - program.uniform1f(LLShaderMgr::MOON_BRIGHTNESS, moon_brightness); - program.uniform1f(LLShaderMgr::WATER_TIME, sStarTime); - if (mask_moon) - { - program.uniform3fv(LLShaderMgr::MOON_QUAD_CENTER, 1, moon_centre.mV); - program.uniform3fv(LLShaderMgr::MOON_QUAD_U, 1, moon_u.mV); - program.uniform3fv(LLShaderMgr::MOON_QUAD_V, 1, moon_v.mV); - program.bindTexture(LLShaderMgr::DIFFUSE_MAP, moon_tex, ALSamplers::AnisoClamp); - } - else - { - program.uniform3f(LLShaderMgr::MOON_QUAD_CENTER, 0.f, 0.f, 0.f); - } - }; - const auto unbind_stars = [&](LLGLSLShader& program) - { - if (mask_moon) - { - program.disableTexture(LLShaderMgr::DIFFUSE_MAP); - } - program.unbind(); - }; - - // On an 8-bit attachment, the faintest star that can still add anything a pixel keeps this frame, for the star - // brightness, the moon's wash and the attachment's gain, and every fainter one is left out: what a star adds - // under half a step rounds away. The float one, the emissive under HDR, rounds nothing away and the exposure, the - // tonemapper and the grade may show any of it, so every star in view is drawn there. - const F32 moon_power = moon_brightness * llmax(0.f, moon_dir.mV[VZ]); - const U32 faintest = - emissive && LLPipeline::hdrEnabled() ? 0 : ALStarField::faintestVisible(star_alpha, moon_power, emissive); - - // The stars add into one attachment of the G-buffer, the emissive with the emissive buffer and the albedo without - // it (starsF.glsl), and leave the rest unread and unwritten. - gbuffer->setDrawAttachment(attachment); - - { - LL_PROFILE_GPU_ZONE("sky stars"); - - // The spiked stars, few and bright, as quads (ALStarField::SPIKE_INTENSITY). - bind_stars(gDeferredStarQuadProgram); - gSky.mVOWLSkyp->drawStarQuads(faintest); - unbind_stars(gDeferredStarQuadProgram); - - // Every other star as a point, sized by its vertex shader, from the cells the view takes in: its frustum in - // the stars' frame, through the matrices that draw them. - LLMatrix4a mvp; - mvp.setMul(gGL.getModelviewMatrix(), gGL.getProjectionMatrix()); - const ALStarField::Frustum frustum = - ALStarField::pointFrustum(mvp, LLRenderTarget::sCurResX, LLRenderTarget::sCurResY); - - LLGLEnable point_size(GL_PROGRAM_POINT_SIZE); - bind_stars(gDeferredStarProgram); - gSky.mVOWLSkyp->drawStarPoints(frustum, faintest); - unbind_stars(gDeferredStarProgram); - } - - gbuffer->setDrawBuffers(); - - gGL.popMatrix(); -} - -void LLDrawPoolWLSky::renderDeferred(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - if (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SKY) || gSky.mVOSkyp.isNull()) - { - return; - } - - // Opt out of the deferred pass's hoisted GL_FRAMEBUFFER_SRGB (renderGeomDeferred): the - // sky writers store display-encoded values into the albedo attachment raw and the - // lighting pass's decoded read round-trips them -- the pass-through convention. An - // encode here would double-transform the non-emissive sky path. Inert for the - // HAS_EMISSIVE path, which writes the sky to the float emissive attachment instead. - LLGLDisable srgb(GL_FRAMEBUFFER_SRGB); - - // The sun's and moon's quads and whether each is up, which the stars' moon mask and the composite read. - gSky.mVOSkyp->updateGeometry(gSky.mVOSkyp->mDrawable); - - LLVector3 const & origin = LLViewerCamera::getInstance()->getOrigin(); - - if (gPipeline.canUseWindLightShaders() && !gCubeSnapshot) - { - // The stars are the one layer the G-buffer takes: into their attachment, cleared to black, which the sky's - // composite adds to every pixel the world leaves at the far plane, where it works the other layers out - // (deferred/skyCompositeF.glsl). They lie on the far plane and write no depth, and the pool draws after all - // the opaque geometry, so the depth test passes only where the world drew nothing. - LLGLDisable cull(GL_CULL_FACE); - LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_LEQUAL); - - renderStarsDeferred(origin); - - // Reset blend type after the stars' BT_ADD - gGL.setSceneBlendType(LLRender::BT_ALPHA); - } -} - - - -LLViewerTexture* LLDrawPoolWLSky::getTexture() -{ - return NULL; -} - -void LLDrawPoolWLSky::resetDrawOrders() -{ -} - -//static -void LLDrawPoolWLSky::cleanupGL() -{ -} - -//static -void LLDrawPoolWLSky::restoreGL() -{ -} diff --git a/indra/newview/lldrawpoolwlsky.h b/indra/newview/lldrawpoolwlsky.h deleted file mode 100644 index 6dc6712f603..00000000000 --- a/indra/newview/lldrawpoolwlsky.h +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @file lldrawpoolwlsky.h - * @brief LLDrawPoolWLSky class definition - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * Alchemy Viewer Source Code - * Copyright © 2026, Rye - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_DRAWPOOLWLSKY_H -#define LL_DRAWPOOLWLSKY_H - -#include "lldrawpool.h" - -class LLGLSLShader; - -class LLDrawPoolWLSky : public LLDrawPool { -public: - - // A star's centre, and its colour in the position's fourth word, where the texture index goes (ALStarField). - static const U32 STAR_VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | - LLVertexBuffer::MAP_TEXTURE_INDEX; - LLDrawPoolWLSky(void); - /*virtual*/ ~LLDrawPoolWLSky(); - - /*virtual*/ bool isDead() { return false; } - - /*virtual*/ S32 getNumDeferredPasses() { return 1; } - /*virtual*/ void renderDeferred(S32 pass); - - /*virtual*/ LLViewerTexture *getDebugTexture(); - /*virtual*/ bool verify() const { return true; } // Verify that all data in the draw pool is correct! - /*virtual*/ S32 getShaderLevel() const { return mShaderLevel; } - - //static LLDrawPool* createPool(const U32 type, LLViewerTexture *tex0 = NULL); - - /*virtual*/ LLViewerTexture* getTexture(); - /*virtual*/ bool isFacePool() { return false; } - /*virtual*/ void resetDrawOrders(); - - static void cleanupGL(); - static void restoreGL(); - - /// What windlight/windlightDomeF.glsl reads besides the Environment block, on the bound @a shader: the rainbow - /// and halo maps and their levels, the glow, density and light, and the dome's geometry. - static void bindDome(LLGLSLShader& shader); - - /// Whether the sky this view draws is the HDRI rather than WindLight's: always in a capture that has one (unless - /// only the irradiance takes it), and in the main view while its split screen shows any of it. - static bool useHDRISky(); - - /// The sky's composite's inputs on the bound @a shader, the soften pass's sky variant (deferred/skyCompositeF.glsl): - /// the dome's (bindDome), the layers this view draws (ALSkyLayers::layersFor) and what each takes, about the view's - /// camera. Ticks the meteors and the aurora's clock in a view that draws them, as their draws did. - static void bindComposite(LLGLSLShader& shader); - - /// The textures bindComposite bound on @a shader, let go, and those alone: a unit whose map this view's layers left - /// unbound holds what an earlier pass bound there. - static void unbindComposite(LLGLSLShader& shader); -private: - // The stars into their attachment of the bound G-buffer, the one layer of the sky drawn into it. - void renderStarsDeferred(const LLVector3& camPosLocal) const; -}; - -#endif // LL_DRAWPOOLWLSKY_H diff --git a/indra/newview/llenvironment.cpp b/indra/newview/llenvironment.cpp index f1bfb227672..e34d5b1d275 100644 --- a/indra/newview/llenvironment.cpp +++ b/indra/newview/llenvironment.cpp @@ -39,7 +39,6 @@ #include "alfarplane.h" #include "llviewercamera.h" #include "pipeline.h" -#include "llsky.h" #include "llviewershadermgr.h" diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp index 9ad17398ae3..f564e288e92 100644 --- a/indra/newview/llface.cpp +++ b/indra/newview/llface.cpp @@ -40,10 +40,10 @@ #include "lldrawpoolavatar.h" #include "lldrawpoolbump.h" +#include "llenvironment.h" #include "llgl.h" #include "llrender.h" #include "lllightconstants.h" -#include "llsky.h" #include "llviewercamera.h" #include "llviewertexturelist.h" #include "llvopartgroup.h" @@ -1490,8 +1490,8 @@ bool LLFace::getGeometryVolume(const LLVolume& volume, // Use the nudged south when coming from above sun angle, such // that emboss mapping always shows up on the upward faces of cubes when // it's noon (since a lot of builders build with the sun forced to noon). - LLVector3 sun_ray = gSky.mVOSkyp->mBumpSunDir; - LLVector3 moon_ray = gSky.mVOSkyp->getMoon().getDirection(); + LLVector3 sun_ray = gPipeline.mSky.getBumpSunDir(); + LLVector3 moon_ray = LLEnvironment::instance().getMoonDirection(); LLVector3& primary_light_ray = (sun_ray.mV[VZ] > 0) ? sun_ray : moon_ray; bump_s_primary_light_ray.load3((offset_multiple * s_scale * primary_light_ray).mV); diff --git a/indra/newview/llflexibleobject.cpp b/indra/newview/llflexibleobject.cpp index 1f894fbfe68..4b84281be59 100644 --- a/indra/newview/llflexibleobject.cpp +++ b/indra/newview/llflexibleobject.cpp @@ -34,7 +34,6 @@ #include "llrendersphere.h" #include "llviewerobject.h" #include "llagent.h" -#include "llsky.h" #include "llviewercamera.h" #include "llviewertexturelist.h" #include "llviewercontrol.h" diff --git a/indra/newview/llfloatergodtools.cpp b/indra/newview/llfloatergodtools.cpp index 612a35f9ec8..528e2d1f4c8 100644 --- a/indra/newview/llfloatergodtools.cpp +++ b/indra/newview/llfloatergodtools.cpp @@ -52,7 +52,6 @@ #include "llmenugl.h" #include "llresmgr.h" #include "llselectmgr.h" -#include "llsky.h" #include "llspinctrl.h" #include "llstatusbar.h" #include "lltabcontainer.h" diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index be76c70d94e..7582fe8282b 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -75,7 +75,6 @@ #include "llpanelvoicedevicesettings.h" #include "llradiogroup.h" #include "llsearchcombobox.h" -#include "llsky.h" #include "llscrolllistctrl.h" #include "llscrolllistitem.h" #include "llsliderctrl.h" diff --git a/indra/newview/llglsandbox.cpp b/indra/newview/llglsandbox.cpp index 6aac3d216f5..22cd9659a09 100644 --- a/indra/newview/llglsandbox.cpp +++ b/indra/newview/llglsandbox.cpp @@ -44,7 +44,6 @@ #include "llviewercamera.h" #include "llvoavatarself.h" -#include "llsky.h" #include "llagent.h" #include "lltoolmgr.h" #include "llselectmgr.h" @@ -847,7 +846,8 @@ void LLViewerObjectList::renderObjectBeacons() } } -void LLSky::renderSunMoonBeacons(const LLVector3& pos_agent, const LLVector3& direction, LLColor4 color) +// static +void ALSky::renderSunMoonBeacons(const LLVector3& pos_agent, const LLVector3& direction, LLColor4 color) { LLGLSUIDefault gls_ui; gUIProgram.bind(); diff --git a/indra/newview/lllegacyatmospherics.cpp b/indra/newview/lllegacyatmospherics.cpp index c4ec2f4786c..812bcf0274f 100644 --- a/indra/newview/lllegacyatmospherics.cpp +++ b/indra/newview/lllegacyatmospherics.cpp @@ -37,7 +37,6 @@ #include "lldrawable.h" #include "llface.h" #include "llglheaders.h" -#include "llsky.h" #include "llviewercamera.h" #include "llviewertexturelist.h" #include "llviewerobjectlist.h" @@ -181,8 +180,6 @@ LLAtmospherics::LLAtmospherics() { /// WL PARAMS mInitialized = false; - mAmbientScale = gSavedSettings.getF32("SkyAmbientScale"); - mNightColorShift = gSavedSettings.getColor3("SkyNightColorShift"); mFogColor.mV[VRED] = mFogColor.mV[VGREEN] = mFogColor.mV[VBLUE] = 0.5f; mFogColor.mV[VALPHA] = 0.0f; mFogRatio = 1.2f; @@ -423,7 +420,7 @@ void LLAtmospherics::updateFog(const F32 distance, const LLVector3& tosun_in) LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - // NOTE: This is very similar to LLVOSky::cacheEnvironment() + // NOTE: This is very similar to ALSky::cacheEnvironment() // Differences: // vars.sun_norm // vars.sunlight diff --git a/indra/newview/lllegacyatmospherics.h b/indra/newview/lllegacyatmospherics.h index f8cc7384cbd..a8194e2cfbd 100644 --- a/indra/newview/lllegacyatmospherics.h +++ b/indra/newview/lllegacyatmospherics.h @@ -270,8 +270,6 @@ class LLAtmospherics bool mInitialized; LLVector3 mLastLightingDirection; LLColor3 mLastTotalAmbient; - F32 mAmbientScale; - LLColor3 mNightColorShift; F32 mInterpVal; LLColor4 mFogColor; LLColor4 mGLFogCol; diff --git a/indra/newview/llsettingsvo.cpp b/indra/newview/llsettingsvo.cpp index e3ba15367e7..1f5a4bfe963 100644 --- a/indra/newview/llsettingsvo.cpp +++ b/indra/newview/llsettingsvo.cpp @@ -49,7 +49,6 @@ #include "llviewerinventory.h" #include "llenvironment.h" -#include "llsky.h" #include "llpermissions.h" @@ -674,14 +673,15 @@ void LLSettingsVOSky::updateSettings() F32 sun_dynamic_range = llmax(sdr(), 0.0001f); mSceneLightStrength = 2.0f * (0.75f + sun_dynamic_range * dp); - gSky.setSunAndMoonDirectionsCFR(sun_direction, moon_direction); - gSky.setSunTextures(getSunTextureId(), getNextSunTextureId()); - gSky.setMoonTextures(getMoonTextureId(), getNextMoonTextureId()); - gSky.setCloudNoiseTextures(getCloudNoiseTextureId(), getNextCloudNoiseTextureId()); - gSky.setBloomTextures(getBloomTextureId(), getNextBloomTextureId()); + ALSky& sky = gPipeline.mSky; + sky.setSunDirectionCFR(sun_direction); + sky.setSunTextures(getSunTextureId(), getNextSunTextureId()); + sky.setMoonTextures(getMoonTextureId(), getNextMoonTextureId()); + sky.setCloudNoiseTextures(getCloudNoiseTextureId(), getNextCloudNoiseTextureId()); + sky.setBloomTextures(getBloomTextureId(), getNextBloomTextureId()); - gSky.setSunScale(getSunScale()); - gSky.setMoonScale(getMoonScale()); + sky.setSunScale(getSunScale()); + sky.setMoonScale(getMoonScale()); } // A sky colour as an irradiance pass takes it under RenderDesaturateIrradiance: grey at its diff --git a/indra/newview/llsky.cpp b/indra/newview/llsky.cpp deleted file mode 100644 index 82caa144333..00000000000 --- a/indra/newview/llsky.cpp +++ /dev/null @@ -1,333 +0,0 @@ -/** - * @file llsky.cpp - * @brief IndraWorld sky class - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// Ideas: -// -haze should be controlled by global query from sims -// -need secondary optical effects on sun (flare) -// -stars should be brought down from sims -// -star intensity should be driven by global ambient level from sims, -// so that eclipses, etc can be easily done. -// - -#include "llviewerprecompiledheaders.h" - -#include "llsky.h" - -// linden library includes -#include "llerror.h" -#include "llmath.h" -#include "math.h" -#include "v4color.h" - -#include "llviewerobjectlist.h" -#include "llviewerobject.h" -#include "llviewercamera.h" -#include "pipeline.h" -#include "lldrawpool.h" - -#include "llvosky.h" -#include "llcubemap.h" -#include "llviewercontrol.h" -#include "llenvironment.h" -#include "llvoavatarself.h" -#include "llvowlsky.h" - -F32 azimuth_from_vector(const LLVector3 &v); -F32 elevation_from_vector(const LLVector3 &v); - -LLSky gSky; - -// ---------------- LLSky ---------------- -////////////////////////////////////////////////////////////////////// -// Construction/Destruction -////////////////////////////////////////////////////////////////////// - -LLSky::LLSky() -{ - // Set initial clear color to black - // Set fog color - mFogColor.mV[VRED] = mFogColor.mV[VGREEN] = mFogColor.mV[VBLUE] = 0.5f; - mFogColor.mV[VALPHA] = 0.0f; - - mLightingGeneration = 0; - mUpdatedThisFrame = true; -} - - -LLSky::~LLSky() -{ -} - -void LLSky::cleanup() -{ - mVOSkyp = NULL; - mVOWLSkyp = NULL; -} - -void LLSky::destroyGL() -{ - if (!mVOSkyp.isNull() && mVOSkyp->getCubeMap()) - { - mVOSkyp->cleanupGL(); - } - if (mVOWLSkyp.notNull()) - { - mVOWLSkyp->cleanupGL(); - } -} - -void LLSky::restoreGL() -{ - if (mVOSkyp) - { - mVOSkyp->restoreGL(); - } - if (mVOWLSkyp) - { - mVOWLSkyp->restoreGL(); - } -} - -void LLSky::resetVertexBuffers() -{ - if (gSky.mVOSkyp.notNull()) - { - gPipeline.resetVertexBuffers(gSky.mVOSkyp->mDrawable); - gPipeline.markRebuild(gSky.mVOSkyp->mDrawable, LLDrawable::REBUILD_ALL); - } - if (gSky.mVOWLSkyp.notNull()) - { - gSky.mVOWLSkyp->resetVertexBuffers(); - gPipeline.resetVertexBuffers(gSky.mVOWLSkyp->mDrawable); - gPipeline.markRebuild(gSky.mVOWLSkyp->mDrawable, LLDrawable::REBUILD_ALL); - } -} - -void LLSky::setSunScale(F32 sun_scale) -{ - if(mVOSkyp.notNull()) - { - mVOSkyp->setSunScale(sun_scale); - } -} - -void LLSky::setMoonScale(F32 moon_scale) -{ - if(mVOSkyp.notNull()) - { - mVOSkyp->setMoonScale(moon_scale); - } -} - -void LLSky::setSunTextures(const LLUUID& sun_texture, const LLUUID& sun_texture_next) -{ - if(mVOSkyp.notNull()) { - mVOSkyp->setSunTextures(sun_texture, sun_texture_next); - } -} - -void LLSky::setMoonTextures(const LLUUID& moon_texture, const LLUUID& moon_texture_next) -{ - if(mVOSkyp.notNull()) { - mVOSkyp->setMoonTextures(moon_texture, moon_texture_next); - } -} - -void LLSky::setCloudNoiseTextures(const LLUUID& cloud_noise_texture, const LLUUID& cloud_noise_texture_next) -{ - if(mVOSkyp.notNull()) { - mVOSkyp->setCloudNoiseTextures(cloud_noise_texture, cloud_noise_texture_next); - } -} - -void LLSky::setBloomTextures(const LLUUID& bloom_texture, const LLUUID& bloom_texture_next) -{ - if(mVOSkyp.notNull()) { - mVOSkyp->setBloomTextures(bloom_texture, bloom_texture_next); - } -} - -void LLSky::setSunAndMoonDirectionsCFR(const LLVector3 &sun_direction, const LLVector3 &moon_direction) -{ - if(mVOSkyp.notNull()) { - mVOSkyp->setSunAndMoonDirectionsCFR(sun_direction, moon_direction); - } -} - -void LLSky::setSunDirectionCFR(const LLVector3 &sun_direction) -{ - if(mVOSkyp.notNull()) { - mVOSkyp->setSunDirectionCFR(sun_direction); - } -} - -void LLSky::setMoonDirectionCFR(const LLVector3 &moon_direction) -{ - if(mVOSkyp.notNull()) { - mVOSkyp->setMoonDirectionCFR(moon_direction); - } -} - -////////////////////////////////////////////////////////////////////// -// Public Methods -////////////////////////////////////////////////////////////////////// - -void LLSky::init() -{ - mVOWLSkyp = static_cast(gObjectList.createObjectViewer(LLViewerObject::LL_VO_WL_SKY, NULL)); - mVOWLSkyp->init(); - gPipeline.createObject(mVOWLSkyp.get()); - - mVOSkyp = (LLVOSky *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_SKY, NULL); - mVOSkyp->init(); - gPipeline.createObject(mVOSkyp.get()); - - gSky.setFogRatio(gSavedSettings.getF32("RenderFogRatio")); - - mUpdatedThisFrame = true; -} - - -void LLSky::setCloudDensityAtAgent(F32 cloud_density) -{ - if (mVOSkyp) - { - mVOSkyp->setCloudDensity(cloud_density); - } -} - - -void LLSky::setWind(const LLVector3& average_wind) -{ - if (mVOSkyp) - { - mVOSkyp->setWind(average_wind); - } -} - -void LLSky::addSunMoonBeacons() -{ - if (!gAgentAvatarp || !mVOSkyp) return; - - static LLUICachedControl show_sun_beacon("sunbeacon", false); - static LLUICachedControl show_moon_beacon("moonbeacon", false); - - if (show_sun_beacon) - { - renderSunMoonBeacons(gAgentAvatarp->getPositionAgent(), mVOSkyp->getSun().getDirection(), LLColor4(1.f, 0.5f, 0.f, 0.5f)); - } - if (show_moon_beacon) - { - renderSunMoonBeacons(gAgentAvatarp->getPositionAgent(), mVOSkyp->getMoon().getDirection(), LLColor4(1.f, 0.f, 0.8f, 0.5f)); - } -} - -////////////////////////////////////////////////////////////////////// -// Private Methods -////////////////////////////////////////////////////////////////////// - - -LLColor4 LLSky::getSkyFogColor() const -{ - if (mVOSkyp) - { - return mVOSkyp->getSkyFogColor(); - } - - return LLColor4(1.f, 1.f, 1.f, 1.f); -} - -void LLSky::updateFog(const F32 distance) -{ - if (mVOSkyp) - { - mVOSkyp->updateFog(distance); - } -} - -void LLSky::updateCull() -{ - // *TODO: do culling for wl sky properly -Brad -} - -void LLSky::updateSky() -{ - if (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SKY)) - { - return; - } - if (mVOSkyp) - { - mVOSkyp->updateSky(); - } -} - - -void LLSky::setFogRatio(const F32 fog_ratio) -{ - if (mVOSkyp) - { - mVOSkyp->setFogRatio(fog_ratio); - } -} - - -F32 LLSky::getFogRatio() const -{ - if (mVOSkyp) - { - return mVOSkyp->getFogRatio(); - } - else - { - return 0.f; - } -} - - -// Returns angle (DEGREES) between the horizontal plane and "v", -// where the angle is negative when v.mV[VZ] < 0.0f -F32 elevation_from_vector(const LLVector3 &v) -{ - F32 elevation = 0.0f; - F32 xy_component = (F32) sqrt(v.mV[VX] * v.mV[VX] + v.mV[VY] * v.mV[VY]); - if (xy_component != 0.0f) - { - elevation = RAD_TO_DEG * (F32) atan(v.mV[VZ]/xy_component); - } - else - { - if (v.mV[VZ] > 0.f) - { - elevation = 90.f; - } - else - { - elevation = -90.f; - } - } - return elevation; -} diff --git a/indra/newview/llsky.h b/indra/newview/llsky.h deleted file mode 100644 index d06f1813573..00000000000 --- a/indra/newview/llsky.h +++ /dev/null @@ -1,96 +0,0 @@ -/** - * @file llsky.h - * @brief It's, uh, the sky! - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSKY_H -#define LL_LLSKY_H - -#include "llmath.h" -#include "v3math.h" -#include "v4math.h" -#include "v4color.h" -#include "v4coloru.h" -#include "llvosky.h" - -class LLViewerCamera; - -class LLVOWLSky; - - -class LLSky -{ -public: - LLSky(); - ~LLSky(); - - void init(); - void cleanup(); - - // These directions should be in CFR coord sys (+x at, +z up, +y right) - void setSunAndMoonDirectionsCFR(const LLVector3 &sun_direction, const LLVector3 &moon_direction); - void setSunDirectionCFR(const LLVector3 &sun_direction); - void setMoonDirectionCFR(const LLVector3 &moon_direction); - - void setSunTextures(const LLUUID& sun_texture, const LLUUID& sun_texture_next); - void setMoonTextures(const LLUUID& moon_texture, const LLUUID& moon_texture_next); - void setCloudNoiseTextures(const LLUUID& cloud_noise_texture, const LLUUID& cloud_noise_texture_next); - void setBloomTextures(const LLUUID& bloom_texture, const LLUUID& bloom_texture_next); - - void setSunScale(F32 sun_scale); - void setMoonScale(F32 moon_scale); - - LLColor4 getSkyFogColor() const; - - void setCloudDensityAtAgent(F32 cloud_density); - void setWind(const LLVector3& wind); - - void updateFog(const F32 distance); - void updateCull(); - void updateSky(); - - S32 mLightingGeneration; - bool mUpdatedThisFrame; - - void setFogRatio(const F32 fog_ratio); // Fog distance as fraction of cull distance. - F32 getFogRatio() const; - LLColor4U getFadeColor() const; - - void destroyGL(); - void restoreGL(); - void resetVertexBuffers(); - - void addSunMoonBeacons(); - void renderSunMoonBeacons(const LLVector3& pos_agent, const LLVector3& direction, LLColor4 color); - -public: - LLPointer mVOSkyp; // Pointer to the LLVOSky object (only one, ever!) - LLPointer mVOWLSkyp; - -protected: - LLColor4 mFogColor; -}; - -extern LLSky gSky; -#endif diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 8bc836e02f3..8bd031643fd 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -224,7 +224,6 @@ #include "llsecapi.h" #include "alobjectproperties.h" #include "llselectmgr.h" -#include "llsky.h" #include "llstatview.h" #include "llstatusbar.h" // sendMoneyBalanceRequest(), owns L$ balance #include "llbuycurrencyhtml.h" @@ -1791,7 +1790,6 @@ bool idle_startup() // to hapen with caps granted gTextureList.doPrefetchImages(); - // will init images, should be done with caps, but before gSky.init() LLEnvironment::getInstance()->initSingleton(); do_startup_frame(); @@ -1903,17 +1901,6 @@ bool idle_startup() gAgentCamera.resetCamera(); do_startup_frame(); - // Initialize global class data needed for surfaces (i.e. textures) - LL_DEBUGS("AppInit") << "Initializing sky..." << LL_ENDL; - // Initialize all of the viewer object classes for the first time (doing things like texture fetches. - LLGLState::checkStates(); - - gSky.init(); - - LLGLState::checkStates(); - - do_startup_frame(); - LL_DEBUGS("AppInit") << "Decoding images..." << LL_ENDL; // For all images pre-loaded into viewer cache, init // priorities and fetching using decodeAllImages. @@ -3499,7 +3486,6 @@ void reset_login() gAgentWearables.cleanup(); gAgentCamera.cleanup(); gAgent.cleanup(); - gSky.cleanup(); // mVOSkyp is an inworld object. LLWorld::getInstance()->resetClass(); LLAppearanceMgr::getInstance()->cleanup(); diff --git a/indra/newview/llsurfacepatch.cpp b/indra/newview/llsurfacepatch.cpp index b3146898f5f..e777fbaa3b6 100644 --- a/indra/newview/llsurfacepatch.cpp +++ b/indra/newview/llsurfacepatch.cpp @@ -32,7 +32,6 @@ #include "llsurface.h" #include "pipeline.h" #include "llagent.h" -#include "llsky.h" // For getting composition values #include "llviewerregion.h" diff --git a/indra/newview/lltoolgun.cpp b/indra/newview/lltoolgun.cpp index e19254e9536..60174de577b 100644 --- a/indra/newview/lltoolgun.cpp +++ b/indra/newview/lltoolgun.cpp @@ -32,13 +32,13 @@ #include "llagent.h" #include "llagentcamera.h" #include "llviewercontrol.h" -#include "llsky.h" #include "llappviewer.h" #include "llresmgr.h" #include "llfontgl.h" #include "llui.h" #include "llviewertexturelist.h" #include "llviewercamera.h" +#include "pipeline.h" // [RLVa:KB] - Checked: 2014-02-24 (RLVa-1.4.10) #include "llfocusmgr.h" // [/RLVa:KB] @@ -131,7 +131,7 @@ bool LLToolGun::handleHover(S32 x, S32 y, MASK mask) if (mouse_sun) { const LLVector3& sunpos = LLViewerCamera::getInstance()->getAtAxis(); - gSky.setSunDirectionCFR(sunpos); + gPipeline.mSky.setSunDirectionCFR(sunpos); gSavedSettings.setVector3("SkySunDefaultPosition", sunpos); } @@ -139,7 +139,6 @@ bool LLToolGun::handleHover(S32 x, S32 y, MASK mask) if (mouse_moon) { const LLVector3& moonpos = LLViewerCamera::getInstance()->getAtAxis(); - gSky.setMoonDirectionCFR(moonpos); gSavedSettings.setVector3("SkyMoonDefaultPosition", moonpos); } diff --git a/indra/newview/lltoolmorph.cpp b/indra/newview/lltoolmorph.cpp index e5b72f35eb5..89f6bc395b1 100644 --- a/indra/newview/lltoolmorph.cpp +++ b/indra/newview/lltoolmorph.cpp @@ -47,7 +47,6 @@ #include "llmorphview.h" #include "llresmgr.h" #include "llselectmgr.h" -#include "llsky.h" #include "lltexlayer.h" #include "lltoolmgr.h" #include "llui.h" diff --git a/indra/newview/lltracker.cpp b/indra/newview/lltracker.cpp index 212b94bed98..edc5dbdbda9 100644 --- a/indra/newview/lltracker.cpp +++ b/indra/newview/lltracker.cpp @@ -53,7 +53,6 @@ #include "llinventoryobserver.h" #include "lllandmarklist.h" #include "llprogressview.h" -#include "llsky.h" #include "llui.h" #include "llviewercamera.h" #include "llviewerinventory.h" @@ -61,6 +60,7 @@ #include "llworld.h" #include "llworldmapview.h" #include "llviewercontrol.h" +#include "pipeline.h" const F32 DESTINATION_REACHED_RADIUS = 3.0f; @@ -630,8 +630,9 @@ void LLTracker::renderBeacon(LLVector3d pos_global, color_frac = 1.f - 0.6f*(dist/LLViewerCamera::getInstance()->getFar()); } - LLColor4 fogged_color = color_frac * color + (1 - color_frac)*gSky.getSkyFogColor(); - LLColor4 fogged_color_under = color_frac * color_under + (1 - color_frac) * gSky.getSkyFogColor(); + const LLColor4 fog_color = gPipeline.mSky.getSkyFogColor(); + LLColor4 fogged_color = color_frac * color + (1 - color_frac) * fog_color; + LLColor4 fogged_color_under = color_frac * color_under + (1 - color_frac) * fog_color; F32 FADE_DIST = 3.f; fogged_color.mV[3] = llmax(0.2f, llmin(0.5f,(dist-FADE_DIST)/FADE_DIST)); diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 3b9191503f7..56f48ee3af2 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -53,7 +53,6 @@ #include "llviewershadermgr.h" #include "llhudtext.h" -#include "llsky.h" #include "llvieweraudio.h" #include "llviewermenu.h" #include "llviewertexturelist.h" @@ -74,7 +73,6 @@ #include "llerrorcontrol.h" #include "llappviewer.h" #include "llmemory.h" -#include "llvowlsky.h" #include "alsamplerstate.h" #include "llrender.h" #include "llnavigationbar.h" @@ -430,7 +428,7 @@ const F32 MIN_USER_FOG_RATIO = 0.5f; static bool handleFogRatioChanged(const LLSD& newvalue) { F32 fog_ratio = llmax(MIN_USER_FOG_RATIO, llmin((F32) newvalue.asReal(), MAX_USER_FOG_RATIO)); - gSky.setFogRatio(fog_ratio); + gPipeline.mSky.setFogRatio(fog_ratio); return true; } diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index b87d5e4ec5e..5a1b3a83881 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -63,7 +63,6 @@ #include "llscenemonitor.h" #include "llsdjson.h" #include "llselectmgr.h" -#include "llsky.h" #include "llspatialpartition.h" #include "llstartup.h" #include "llstartup.h" @@ -781,6 +780,15 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) LLVOAvatar::allocateImpostors(); } + // The sky as the frame begins with it, before anything draws: the mirrors below draw it, and the previews and + // impostor bakes read its environment cube. + if (!gDisconnected && !LLApp::isExiting()) + { + LLAppViewer::instance()->pingMainloopTimeout("Display:Sky"); + LL_PROFILE_ZONE_NAMED_CATEGORY_ENVIRONMENT("update sky"); + gPipeline.mSky.update(); + } + // do render-to-texture stuff here if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_DYNAMIC_TEXTURES)) { @@ -835,6 +843,8 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Env Update"); // update all the sky/atmospheric/water settings LLEnvironment::instance().update(LLViewerCamera::getInstance()); + // The sky's bodies and textures as the update has moved it on, for the main view. + gPipeline.mSky.followEnvironment(); } // *TODO: merge these two methods @@ -1001,12 +1011,6 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) LLPipeline::sUseOcclusion = occlusion; - { - LLAppViewer::instance()->pingMainloopTimeout("Display:Sky"); - LL_PROFILE_ZONE_NAMED_CATEGORY_ENVIRONMENT("update sky"); - gSky.updateSky(); - } - if(gUseWireframe) { gGL.setClearColor(LLColor4(0.5f, 0.5f, 0.5f, 0.f)); @@ -1071,7 +1075,7 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) gGL.setClearColor(LLColor4::magenta); } gPipeline.mRT->deferredScreen.clear(); - gPipeline.clearStarAttachment(); + gPipeline.beginSkyView(); gGL.setColorMask(true, false); @@ -1357,7 +1361,7 @@ void display_cube_face() gGL.setClearColor(LLColor4::magenta); } gPipeline.mRT->deferredScreen.clear(); - gPipeline.clearStarAttachment(); + gPipeline.beginSkyView(); LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; @@ -1869,7 +1873,7 @@ void render_ui_3d() // Render debugging beacons. gObjectList.renderObjectBeacons(); gObjectList.resetObjectBeacons(); - gSky.addSunMoonBeacons(); + gPipeline.mSky.addSunMoonBeacons(); } else { diff --git a/indra/newview/llviewerjointmesh.cpp b/indra/newview/llviewerjointmesh.cpp index 2aef96cb5c7..a5c7f1b9f17 100644 --- a/indra/newview/llviewerjointmesh.cpp +++ b/indra/newview/llviewerjointmesh.cpp @@ -44,7 +44,6 @@ #include "llviewertexturelist.h" #include "llviewerjointmesh.h" #include "llvoavatar.h" -#include "llsky.h" #include "pipeline.h" #include "llviewershadermgr.h" #include "llmath.h" diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 5fab34f6905..5d513c2b5ee 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -91,7 +91,6 @@ #include "llscriptruntimeperms.h" #include "llselectmgr.h" #include "llstartup.h" -#include "llsky.h" #include "llslurl.h" #include "llstatenums.h" #include "llstatusbar.h" diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 7c86bd66ef0..e29cb60d4c4 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -86,7 +86,6 @@ #include "llvoavatar.h" #include "llvoavatarself.h" #include "llvograss.h" -#include "llvosky.h" #include "llvolume.h" #include "llvolumemessage.h" #include "llvopartgroup.h" @@ -97,7 +96,6 @@ #include "llui.h" #include "pipeline.h" #include "llviewernetwork.h" -#include "llvowlsky.h" #include "llmanip.h" #include "lltrans.h" #include "llsdutil.h" @@ -163,8 +161,6 @@ LLViewerObject *LLViewerObject::createObject(const LLUUID &id, const LLPCode pco if (gNonInteractive && pcode != LL_PCODE_LEGACY_AVATAR && pcode != LL_VO_SURFACE_PATCH - && pcode != LL_VO_WL_SKY - && pcode != LL_VO_SKY && pcode != LL_VO_PART_GROUP ) { @@ -230,14 +226,10 @@ LLViewerObject *LLViewerObject::createObject(const LLUUID &id, const LLPCode pco res = NULL; break; case LL_VO_SURFACE_PATCH: res = new LLVOSurfacePatch(id, pcode, regionp); break; - case LL_VO_SKY: - res = new LLVOSky(id, pcode, regionp); break; case LL_VO_PART_GROUP: res = new LLVOPartGroup(id, pcode, regionp); break; case LL_VO_HUD_PART_GROUP: res = new LLVOHUDPartGroup(id, pcode, regionp); break; - case LL_VO_WL_SKY: - res = new LLVOWLSky(id, pcode, regionp); break; default: LL_WARNS() << "Unknown object pcode " << (S32)pcode << LL_ENDL; res = NULL; break; diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h index ef3d8653230..636579863d3 100644 --- a/indra/newview/llviewerobject.h +++ b/indra/newview/llviewerobject.h @@ -778,9 +778,7 @@ class LLViewerObject { LL_VO_CLOUDS = LL_PCODE_APP | 0x20, // no longer used LL_VO_SURFACE_PATCH = LL_PCODE_APP | 0x30, - LL_VO_WL_SKY = LL_PCODE_APP | 0x40, LL_VO_SQUARE_TORUS = LL_PCODE_APP | 0x50, - LL_VO_SKY = LL_PCODE_APP | 0x60, LL_VO_PART_GROUP = LL_PCODE_APP | 0xa0, LL_VO_TRIANGLE_TORUS = LL_PCODE_APP | 0xb0, LL_VO_HUD_PART_GROUP = LL_PCODE_APP | 0xc0, diff --git a/indra/newview/llviewerobjectlist.cpp b/indra/newview/llviewerobjectlist.cpp index 9929a0dc828..fee6d58157c 100644 --- a/indra/newview/llviewerobjectlist.cpp +++ b/indra/newview/llviewerobjectlist.cpp @@ -51,7 +51,6 @@ #include "llflexibleobject.h" #include "llviewertextureanim.h" #include "xform.h" -#include "llsky.h" #include "llviewercamera.h" #include "llselectmgr.h" #include "llresmgr.h" diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index 21e8bb4294f..4cf6070bed4 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -46,7 +46,6 @@ #include "llenvironment.h" #include "llerrorcontrol.h" #include "llworld.h" -#include "llsky.h" #include "pipeline.h" @@ -726,10 +725,10 @@ bool LLViewerShaderMgr::setShaders(const ALShaderInputs& inputs, const ALRenderA shader->unload(); } - // The fallback sky cube map is drawn with these programs, and again by the sky once it is told to. - if (LLGLSLShader::sLinked > 0 && gSky.mVOSkyp.notNull()) + // The environment cube is drawn with these programs, and again by the sky once it is told to. + if (LLGLSLShader::sLinked > 0) { - gSky.mVOSkyp->forceSkyUpdate(); + gPipeline.mSky.forceSkyUpdate(); } if (!mFailedPrograms.empty()) diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 43da1727f72..f8eb85e5100 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -71,7 +71,6 @@ #include "llfontgl.h" #include "llmousehandler.h" #include "llrect.h" -#include "llsky.h" #include "llstring.h" #include "llui.h" #include "lluuid.h" @@ -2755,9 +2754,6 @@ void LLViewerWindow::shutdownGL() SUBSYSTEM_CLEANUP(LLFontManager); stop_glerror(); - gSky.cleanup(); - stop_glerror(); - LL_INFOS() << "Cleaning up pipeline" << LL_ENDL; gPipeline.cleanup(); stop_glerror(); @@ -6598,9 +6594,6 @@ void LLViewerWindow::stopGL() LLAppViewer::getTextureCache()->pause(); LLAppViewer::getTextureFetch()->pause(); - gSky.destroyGL(); - stop_glerror(); - LLManipTranslate::destroyGL() ; stop_glerror(); @@ -6666,7 +6659,6 @@ void LLViewerWindow::restoreGL(const std::string& progress_message) //LLFontGL::destroyDefaultFonts(); initFonts(); - gSky.restoreGL(); gPipeline.restoreGL(); LLManipTranslate::restoreGL(); diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 731c2c9beef..b12e2d0bcbc 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -103,7 +103,6 @@ #include "llworld.h" #include "pipeline.h" #include "llviewershadermgr.h" -#include "llsky.h" #include "llanimstatelabels.h" #include "lltrans.h" #include "llappearancemgr.h" diff --git a/indra/newview/llvograss.cpp b/indra/newview/llvograss.cpp index 46bc8331655..b689144e0f8 100644 --- a/indra/newview/llvograss.cpp +++ b/indra/newview/llvograss.cpp @@ -35,7 +35,6 @@ #include "lldrawable.h" #include "lldrawpoolalpha.h" #include "llface.h" -#include "llsky.h" #include "llsurface.h" #include "llsurfacepatch.h" #include "llviewercamera.h" diff --git a/indra/newview/llvopartgroup.cpp b/indra/newview/llvopartgroup.cpp index afed882ba33..0bb5aee8e39 100644 --- a/indra/newview/llvopartgroup.cpp +++ b/indra/newview/llvopartgroup.cpp @@ -36,7 +36,6 @@ #include "llagentcamera.h" #include "lldrawable.h" #include "llface.h" -#include "llsky.h" #include "llviewercamera.h" #include "llviewerpartsim.h" #include "llviewerregion.h" diff --git a/indra/newview/llvosky.cpp b/indra/newview/llvosky.cpp deleted file mode 100644 index 35d3d07f3f6..00000000000 --- a/indra/newview/llvosky.cpp +++ /dev/null @@ -1,1039 +0,0 @@ -/** - * @file llvosky.cpp - * @brief LLVOSky class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llvosky.h" - -#include "alskylayers.h" -#include "llfeaturemanager.h" -#include "llviewercontrol.h" -#include "llframetimer.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "lldrawable.h" -#include "llface.h" -#include "llcubemap.h" -#include "lldrawpoolsky.h" -#include "llglheaders.h" -#include "llsky.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llworld.h" -#include "pipeline.h" -#include "lldrawpoolwlsky.h" -#include "v3colorutil.h" - -#include "llsettingssky.h" -#include "llenvironment.h" - -#include "lltrace.h" - -#undef min -#undef max - -namespace -{ - const S32 NUM_TILES_X = 8; - const S32 NUM_TILES_Y = 4; - const S32 NUM_TILES = NUM_TILES_X * NUM_TILES_Y; - const S32 NUM_CUBEMAP_FACES = 6; // See SKYTEX_RESOLUTION for face dimensions - const S32 TOTAL_TILES = NUM_CUBEMAP_FACES * NUM_TILES; - const S32 MAX_TILES = TOTAL_TILES + 1; - -// Heavenly body constants - const F32 SUN_DISK_RADIUS = 0.5f; - const F32 MOON_DISK_RADIUS = SUN_DISK_RADIUS * 0.9f; - const F32 SUN_INTENSITY = 1e5; - - F32Seconds UPDATE_EXPRY(0.25f); - - const F32 UPDATE_MIN_DELTA_THRESHOLD = 0.0005f; -} -/*************************************** - SkyTex -***************************************/ - -S32 LLSkyTex::sCurrent = 0; - - -LLSkyTex::LLSkyTex() : - mSkyData(NULL), - mSkyDirs(NULL), - mIsShiny(false) -{ -} - -void LLSkyTex::init(bool isShiny) -{ - mIsShiny = isShiny; - try - { - mSkyData = new LLColor4[(U32)(SKYTEX_RESOLUTION * SKYTEX_RESOLUTION)]; - mSkyDirs = new LLVector3[(U32)(SKYTEX_RESOLUTION * SKYTEX_RESOLUTION)]; - } - catch (std::bad_alloc&) - { - LLError::LLUserWarningMsg::showOutOfMemory(); - LL_ERRS() << "Failed to allocate memory for sky texture data" << LL_ENDL; - } - - for (S32 i = 0; i < 2; ++i) - { - mTexture[i] = LLViewerTextureManager::getLocalTexture(false); - mImageRaw[i] = new LLImageRaw((U16)SKYTEX_RESOLUTION, (U16)SKYTEX_RESOLUTION, (S8)SKYTEX_COMPONENTS); - - initEmpty(i); - } -} - -void LLSkyTex::cleanupGL() -{ - mTexture[0] = NULL; - mTexture[1] = NULL; -} - -void LLSkyTex::restoreGL() -{ - for (S32 i = 0; i < 2; i++) - { - mTexture[i] = LLViewerTextureManager::getLocalTexture(false); - } -} - -LLSkyTex::~LLSkyTex() -{ - delete[] mSkyData; - mSkyData = NULL; - - delete[] mSkyDirs; - mSkyDirs = NULL; -} - -S32 LLSkyTex::getResolution() -{ - return (S32)SKYTEX_RESOLUTION; -} - -S32 LLSkyTex::getCurrent() -{ - return sCurrent; -} - -S32 LLSkyTex::stepCurrent() { - sCurrent++; - sCurrent &= 1; - return sCurrent; -} - -S32 LLSkyTex::getNext() -{ - return ((sCurrent+1) & 1); -} - -S32 LLSkyTex::getWhich(const bool curr) -{ - int tex = curr ? sCurrent : getNext(); - return tex; -} - -void LLSkyTex::initEmpty(const S32 tex) -{ - LLImageDataLock lock(mImageRaw[tex]); - U8* data = mImageRaw[tex]->getData(); - for (S32 i = 0; i < SKYTEX_RESOLUTION; ++i) - { - for (S32 j = 0; j < SKYTEX_RESOLUTION; ++j) - { - const S32 basic_offset = (i * (S32)SKYTEX_RESOLUTION + j); - S32 offset = basic_offset * (S32)SKYTEX_COMPONENTS; - data[offset] = 0; - data[offset+1] = 0; - data[offset+2] = 0; - data[offset+3] = 255; - - mSkyData[basic_offset].setToBlack(); - } - } - - createGLImage(tex); -} - -void LLSkyTex::create() -{ - LLImageDataSharedLock lock(mImageRaw[sCurrent]); - const U8* data = mImageRaw[sCurrent]->getData(); - for (S32 i = 0; i < SKYTEX_RESOLUTION; ++i) - { - for (S32 j = 0; j < SKYTEX_RESOLUTION; ++j) - { - const S32 basic_offset = (i * (S32)SKYTEX_RESOLUTION + j); - S32 offset = basic_offset * (S32)SKYTEX_COMPONENTS; - U32* pix = (U32*)(data + offset); - LLColor4U temp = LLColor4U(mSkyData[basic_offset]); - *pix = temp.asRGBA(); - } - } - createGLImage(sCurrent); -} - -void LLSkyTex::createGLImage(S32 which) -{ - mTexture[which]->setExplicitFormat(GL_RGBA8, GL_RGBA); - mTexture[which]->createGLTexture(0, mImageRaw[which], 0, true, LLGLTexture::LOCAL); -} - -void LLSkyTex::bindTexture(bool curr) -{ - int tex = getWhich(curr); - gGL.getTextureSlot(0)->bindSampled(mTexture[tex], ALSamplers::AnisoClamp, true); -} - -LLImageRaw* LLSkyTex::getImageRaw(bool curr) -{ - int tex = getWhich(curr); - return mImageRaw[tex]; -} - -/*************************************** - LLHeavenBody -***************************************/ - -F32 LLHeavenBody::sInterpVal = 0; - -LLHeavenBody::LLHeavenBody(const F32 rad) -: mDirectionCached(LLVector3(0,0,0)), - mDirection(LLVector3(0,0,0)), - mIntensity(0.f), - mDiskRadius(rad), - mDraw(false), - mHorizonVisibility(1.f), - mVisibility(1.f), - mVisible(false) -{ - mColor.setToBlack(); - mColorCached.setToBlack(); -} - -const LLQuaternion& LLHeavenBody::getRotation() const -{ - return mRotation; -} - -void LLHeavenBody::setRotation(const LLQuaternion& rot) -{ - mRotation = rot; -} - -const LLVector3& LLHeavenBody::getDirection() const -{ - return mDirection; -} - -void LLHeavenBody::setDirection(const LLVector3 &direction) -{ - mDirection = direction; -} - -void LLHeavenBody::setAngularVelocity(const LLVector3 &ang_vel) -{ - mAngularVelocity = ang_vel; -} - -const LLVector3& LLHeavenBody::getAngularVelocity() const -{ - return mAngularVelocity; -} - -const LLVector3& LLHeavenBody::getDirectionCached() const -{ - return mDirectionCached; -} - -void LLHeavenBody::renewDirection() -{ - mDirectionCached = mDirection; -} - -const LLColor3& LLHeavenBody::getColorCached() const -{ - return mColorCached; -} - -void LLHeavenBody::setColorCached(const LLColor3& c) -{ - mColorCached = c; -} - -const LLColor3& LLHeavenBody::getColor() const -{ - return mColor; -} - -void LLHeavenBody::setColor(const LLColor3& c) -{ - mColor = c; -} - -void LLHeavenBody::renewColor() -{ - mColorCached = mColor; -} - -F32 LLHeavenBody::interpVal() -{ - return sInterpVal; -} - -void LLHeavenBody::setInterpVal(const F32 v) -{ - sInterpVal = v; -} - -LLColor3 LLHeavenBody::getInterpColor() const -{ - return sInterpVal * mColor + (1 - sInterpVal) * mColorCached; -} - -const F32& LLHeavenBody::getVisibility() const -{ - return mVisibility; -} - -void LLHeavenBody::setVisibility(const F32 c) -{ - mVisibility = c; -} - -bool LLHeavenBody::isVisible() const -{ - return mVisible; -} - -void LLHeavenBody::setVisible(const bool v) -{ - mVisible = v; -} - -const F32& LLHeavenBody::getIntensity() const -{ - return mIntensity; -} - -void LLHeavenBody::setIntensity(const F32 c) -{ - mIntensity = c; -} - -void LLHeavenBody::setDiskRadius(const F32 radius) -{ - mDiskRadius = radius; -} - -F32 LLHeavenBody::getDiskRadius() const -{ - return mDiskRadius; -} - -void LLHeavenBody::setDraw(const bool draw) -{ - mDraw = draw; -} - -bool LLHeavenBody::getDraw() const -{ - return mDraw; -} - -const LLVector3& LLHeavenBody::corner(const S32 n) const -{ - return mQuadCorner[n]; -} - -LLVector3& LLHeavenBody::corner(const S32 n) -{ - return mQuadCorner[n]; -} - -const LLVector3* LLHeavenBody::corners() const -{ - return mQuadCorner; -} - -/*************************************** - Sky -***************************************/ - -const S32 SKYTEX_TILE_RES_X = (S32)SKYTEX_RESOLUTION / NUM_TILES_X; -const S32 SKYTEX_TILE_RES_Y = (S32)SKYTEX_RESOLUTION / NUM_TILES_Y; - -LLVOSky::LLVOSky(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) -: LLStaticViewerObject(id, pcode, regionp, true), - mSun(SUN_DISK_RADIUS), mMoon(MOON_DISK_RADIUS), - mBrightnessScale(1.f), - mBrightnessScaleNew(0.f), - mBrightnessScaleGuess(1.f), - mWeatherChange(false), - mCloudDensity(0.2f), - mWind(0.f), - mForceUpdate(false), - mNeedUpdate(true), - mCubeMapUpdateStage(-1), - mWorldScale(1.f), - mBumpSunDir(0.f, 0.f, 1.f) -{ - /// WL PARAMS - - mInitialized = false; - mbCanSelect = false; - mUpdateTimer.reset(); - - mForceUpdateThrottle.setTimerExpirySec(UPDATE_EXPRY); - mForceUpdateThrottle.reset(); - - for (S32 i = 0; i < NUM_CUBEMAP_FACES; i++) - { - mSkyTex[i].init(false); - mShinyTex[i].init(true); - } - for (S32 i=0; iupdate(); - - updateDirections(psky); - - cacheEnvironment(psky,m_atmosphericsVars); - - // Initialize the cached normalized direction vectors - for (S32 side = 0; side < NUM_CUBEMAP_FACES; ++side) - { - for (S32 tile = 0; tile < NUM_TILES; ++tile) - { - initSkyTextureDirs(side, tile); - createSkyTexture(psky, m_atmosphericsVars, side, tile); - } - mSkyTex[side].create(); - mShinyTex[side].create(); - } - - initCubeMap(); - - mInitialized = true; - - mHeavenlyBodyUpdated = false ; - - mRainbowMap = LLViewerTextureManager::getFetchedTexture(psky->getRainbowTextureId(), FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - mHaloMap = LLViewerTextureManager::getFetchedTexture(psky->getHaloTextureId(), FTT_DEFAULT, true, LLGLTexture::BOOST_UI); -} - - -void LLVOSky::cacheEnvironment(LLSettingsSky::ptr_t psky,AtmosphericsVars& atmosphericsVars) -{ - // NOTE: Also see: LLAtmospherics::updateFog() - // invariants across whole sky tex process... - atmosphericsVars.blue_density = psky->getBlueDensity(); - atmosphericsVars.blue_horizon = psky->getBlueHorizon(); - atmosphericsVars.haze_density = psky->getHazeDensity(); - atmosphericsVars.haze_horizon = psky->getHazeHorizon(); - atmosphericsVars.density_multiplier = psky->getDensityMultiplier(); - atmosphericsVars.distance_multiplier = psky->getDistanceMultiplier(); - atmosphericsVars.max_y = psky->getMaxY(); - atmosphericsVars.sun_norm = LLEnvironment::instance().getClampedSunNorm(); - atmosphericsVars.sunlight = psky->getIsSunUp() ? psky->getSunlightColor() : psky->getMoonlightColor(); - atmosphericsVars.ambient = psky->getAmbientColor(); - atmosphericsVars.glow = psky->getGlow(); - atmosphericsVars.cloud_shadow = psky->getCloudShadow(); - atmosphericsVars.dome_radius = psky->getDomeRadius(); - atmosphericsVars.dome_offset = psky->getDomeOffset(); - atmosphericsVars.light_atten = psky->getLightAttenuation(atmosphericsVars.max_y); - atmosphericsVars.light_transmittance = psky->getLightTransmittance(atmosphericsVars.max_y); - atmosphericsVars.total_density = psky->getTotalDensity(); - atmosphericsVars.gamma = psky->getGamma(); -} - -void LLVOSky::calc() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - cacheEnvironment(psky,m_atmosphericsVars); - - mSun.setColor(psky->getSunDiffuse()); - mMoon.setColor(LLColor3(1.0f, 1.0f, 1.0f)); - - mSun.renewDirection(); - mSun.renewColor(); - mMoon.renewDirection(); - mMoon.renewColor(); -} - -void LLVOSky::initCubeMap() -{ - std::vector > images; - for (S32 side = 0; side < NUM_CUBEMAP_FACES; side++) - { - images.push_back(mShinyTex[side].getImageRaw()); - } - - if (!mCubeMap && gSavedSettings.getBOOL("RenderWater")) - { - mCubeMap = new LLCubeMap(); - } - - if (mCubeMap) - { - mCubeMap->init(images); - } - - gGL.getTextureSlot(0)->unbind(); -} - - -void LLVOSky::cleanupGL() -{ - S32 i; - for (i = 0; i < NUM_CUBEMAP_FACES; i++) - { - mSkyTex[i].cleanupGL(); - } - if (getCubeMap()) - { - getCubeMap()->destroyGL(); - } -} - -void LLVOSky::restoreGL() -{ - S32 i; - for (i = 0; i < NUM_CUBEMAP_FACES; i++) - { - mSkyTex[i].restoreGL(); - } - - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - if (psky) - { - setSunTextures(psky->getSunTextureId(), psky->getNextSunTextureId()); - setMoonTextures(psky->getMoonTextureId(), psky->getNextMoonTextureId()); - } - - updateDirections(psky); - - if (gSavedSettings.getBOOL("RenderWater")) - { - initCubeMap(); - } - - forceSkyUpdate(); - - if (mDrawable) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); - } - -} - -void LLVOSky::initSkyTextureDirs(const S32 side, const S32 tile) -{ - S32 tile_x = tile % NUM_TILES_X; - S32 tile_y = tile / NUM_TILES_X; - - S32 tile_x_pos = tile_x * SKYTEX_TILE_RES_X; - S32 tile_y_pos = tile_y * SKYTEX_TILE_RES_Y; - - F32 coeff[3] = {0, 0, 0}; - const S32 curr_coef = side >> 1; // 0/1 = Z axis, 2/3 = Y, 4/5 = X - const S32 side_dir = (((side & 1) << 1) - 1); // even = -1, odd = 1 - const S32 x_coef = (curr_coef + 1) % 3; - const S32 y_coef = (x_coef + 1) % 3; - - coeff[curr_coef] = (F32)side_dir; - - F32 inv_res = 1.f/SKYTEX_RESOLUTION; - S32 x, y; - for (y = tile_y_pos; y < (tile_y_pos + SKYTEX_TILE_RES_Y); ++y) - { - for (x = tile_x_pos; x < (tile_x_pos + SKYTEX_TILE_RES_X); ++x) - { - coeff[x_coef] = F32((x<<1) + 1) * inv_res - 1.f; - coeff[y_coef] = F32((y<<1) + 1) * inv_res - 1.f; - LLVector3 dir(coeff[0], coeff[1], coeff[2]); - dir.normalize(); - mSkyTex[side].setDir(dir, x, y); - mShinyTex[side].setDir(dir, x, y); - } - } -} - -void LLVOSky::createSkyTexture(const LLSettingsSky::ptr_t &psky, AtmosphericsVars& vars, const S32 side, const S32 tile) -{ - const bool low_end = !gPipeline.canUseWindLightShaders(); - - S32 tile_x = tile % NUM_TILES_X; - S32 tile_y = tile / NUM_TILES_X; - - S32 tile_x_pos = tile_x * SKYTEX_TILE_RES_X; - S32 tile_y_pos = tile_y * SKYTEX_TILE_RES_Y; - - S32 x, y; - for (y = tile_y_pos; y < (tile_y_pos + SKYTEX_TILE_RES_Y); ++y) - { - for (x = tile_x_pos; x < (tile_x_pos + SKYTEX_TILE_RES_X); ++x) - { - mSkyTex [side].setPixel(m_legacyAtmospherics.calcSkyColorInDir(psky, vars, mSkyTex [side].getDir(x, y), false, low_end), x, y); - mShinyTex[side].setPixel(m_legacyAtmospherics.calcSkyColorInDir(psky, vars, mShinyTex[side].getDir(x, y), true , low_end), x, y); - } - } -} - -void LLVOSky::updateDirections(LLSettingsSky::ptr_t psky) -{ - mSun.setDirection(psky->getSunDirection()); - mMoon.setDirection(psky->getMoonDirection()); - mSun.setRotation(psky->getSunRotation()); - mMoon.setRotation(psky->getMoonRotation()); - mSun.renewDirection(); - mMoon.renewDirection(); -} - -void LLVOSky::idleUpdate(LLAgent &agent, const F64 &time) -{ -} - -void LLVOSky::forceSkyUpdate() -{ - mForceUpdate = true; - m_lastAtmosphericsVars = {}; - mCubeMapUpdateStage = -1; -} - -bool LLVOSky::updateSky() -{ - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - if (mDead || !(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SKY))) - { - // It's dead. Don't update it. - return true; - } - - if (gGLManager.mIsDisabled) - { - return true; - } - - LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; - - static S32 next_frame = 0; - - mNeedUpdate = mForceUpdate; - - ++next_frame; - next_frame = next_frame % MAX_TILES; - - mInterpVal = (!mInitialized) ? 1 : (F32)next_frame / MAX_TILES; - LLHeavenBody::setInterpVal( mInterpVal ); - updateDirections(psky); - - if (!mCubeMap || LLPipeline::sReflectionProbesEnabled) - { - mCubeMapUpdateStage = NUM_CUBEMAP_FACES; - mForceUpdate = false; - return true; - } - - if (mCubeMapUpdateStage < 0) - { - calc(); - - bool same_atmospherics = approximatelyEqual(m_lastAtmosphericsVars, m_atmosphericsVars, UPDATE_MIN_DELTA_THRESHOLD); - - mNeedUpdate = mNeedUpdate || !same_atmospherics; - - if (mNeedUpdate && (mForceUpdateThrottle.hasExpired() || mForceUpdate)) - { - // start updating cube map sides - updateFog(LLViewerCamera::getInstance()->getFar()); - mCubeMapUpdateStage = 0; - mForceUpdate = false; - } - } - else if (mCubeMapUpdateStage == NUM_CUBEMAP_FACES && !LLPipeline::sReflectionProbesEnabled) - { - LL_PROFILE_ZONE_NAMED("updateSky - forced"); - LLSkyTex::stepCurrent(); - - bool is_alm_wl_sky = gPipeline.canUseWindLightShaders(); - - int tex = mSkyTex[0].getWhich(true); - - for (int side = 0; side < NUM_CUBEMAP_FACES; side++) - { - LLImageRaw* raw1 = nullptr; - LLImageRaw* raw2 = nullptr; - - if (!is_alm_wl_sky) - { - raw1 = mSkyTex[side].getImageRaw(true); - raw2 = mSkyTex[side].getImageRaw(false); - raw2->copy(raw1); - mSkyTex[side].createGLImage(tex); - } - - raw1 = mShinyTex[side].getImageRaw(true); - raw2 = mShinyTex[side].getImageRaw(false); - raw2->copy(raw1); - mShinyTex[side].createGLImage(tex); - } - next_frame = 0; - - // update the sky texture - if (!is_alm_wl_sky) - { - for (S32 i = 0; i < NUM_CUBEMAP_FACES; ++i) - { - mSkyTex[i].create(); - } - } - - for (S32 i = 0; i < NUM_CUBEMAP_FACES; ++i) - { - mShinyTex[i].create(); - } - - // update the environment map - initCubeMap(); - - m_lastAtmosphericsVars = m_atmosphericsVars; - - mNeedUpdate = false; - mForceUpdate = false; - - mForceUpdateThrottle.setTimerExpirySec(UPDATE_EXPRY); - - if (mDrawable.notNull() && mDrawable->getFace(0) && !mDrawable->getFace(0)->getVertexBuffer()) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); - } - mCubeMapUpdateStage = -1; - } - // run 0 to 5 faces, each face in own frame - else if (mCubeMapUpdateStage >= 0 && mCubeMapUpdateStage < NUM_CUBEMAP_FACES && !LLPipeline::sReflectionProbesEnabled) - { - LL_PROFILE_ZONE_NAMED("updateSky - create"); - S32 side = mCubeMapUpdateStage; - // CPU hungry part, createSkyTexture() is math heavy - // Prior to EEP it was mostly per tile, but since EPP it is per face. - // This still can be optimized further - // (i.e. potentially can be made per tile again, can be moved to thread - // instead of executing per face, or may be can be moved to shaders) - for (S32 tile = 0; tile < NUM_TILES; tile++) - { - createSkyTexture(psky, m_atmosphericsVars, side, tile); - } - mCubeMapUpdateStage++; - } - - return true; -} - -void LLVOSky::updateTextures() -{ - if (mSunTexturep[0]) - { - mSunTexturep[0]->addTextureStats( (F32)MAX_IMAGE_AREA ); - } - - if (mSunTexturep[1]) - { - mSunTexturep[1]->addTextureStats( (F32)MAX_IMAGE_AREA ); -} - - if (mMoonTexturep[0]) -{ - mMoonTexturep[0]->addTextureStats( (F32)MAX_IMAGE_AREA ); -} - - if (mMoonTexturep[1]) -{ - mMoonTexturep[1]->addTextureStats( (F32)MAX_IMAGE_AREA ); -} - - fetchBloomTextures(); - for (const LLPointer& bloom : mBloomTexturep) - { - if (bloom) - { - bloom->addTextureStats((F32)MAX_IMAGE_AREA); - } - } -} - -LLDrawable *LLVOSky::createDrawable(LLPipeline *pipeline) -{ - pipeline->allocDrawable(this); - mDrawable->setLit(false); - - LLDrawPoolSky *poolp = (LLDrawPoolSky*) gPipeline.getPool(LLDrawPool::POOL_SKY); - poolp->setSkyTex(mSkyTex); - mDrawable->setRenderType(LLPipeline::RENDER_TYPE_SKY); - - mFace[FACE_SUN] = mDrawable->addFace(poolp, nullptr); - mFace[FACE_MOON] = mDrawable->addFace(poolp, nullptr); - mFace[FACE_BLOOM] = mDrawable->addFace(poolp, nullptr); - - mFace[FACE_SUN]->setMediaAllowed(false); - mFace[FACE_MOON]->setMediaAllowed(false); - mFace[FACE_BLOOM]->setMediaAllowed(false); - - return mDrawable; -} - -void LLVOSky::setSunScale(F32 sun_scale) -{ - mSunScale = sun_scale; -} - -void LLVOSky::setMoonScale(F32 moon_scale) -{ - mMoonScale = moon_scale; -} - -void LLVOSky::setSunTextures(const LLUUID& sun_texture, const LLUUID& sun_texture_next) -{ - // We test the UUIDs here because we explicitly do not want the default image returned by getFetchedTexture in that case... - mSunTexturep[0] = sun_texture.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(sun_texture, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - mSunTexturep[1] = sun_texture_next.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(sun_texture_next, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - - bool can_use_wl = gPipeline.canUseWindLightShaders(); - - if (mFace[FACE_SUN]) - { - LLViewerTexture* current_tex0 = mFace[FACE_SUN]->getTexture(LLRender::DIFFUSE_MAP); - LLViewerTexture* current_tex1 = mFace[FACE_SUN]->getTexture(LLRender::ALTERNATE_DIFFUSE_MAP); - - if (current_tex0 && (mSunTexturep[0] != current_tex0) && current_tex0->isViewerMediaTexture()) - { - static_cast(current_tex0)->removeMediaFromFace(mFace[FACE_SUN]); - } - - if (current_tex1 && (mSunTexturep[1] != current_tex1) && current_tex1->isViewerMediaTexture()) - { - static_cast(current_tex1)->removeMediaFromFace(mFace[FACE_SUN]); - } - - mFace[FACE_SUN]->setTexture(LLRender::DIFFUSE_MAP, mSunTexturep[0]); - - if (can_use_wl) - { - mFace[FACE_SUN]->setTexture(LLRender::ALTERNATE_DIFFUSE_MAP, mSunTexturep[1]); - } - } -} - -void LLVOSky::setMoonTextures(const LLUUID& moon_texture, const LLUUID& moon_texture_next) -{ - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - bool can_use_wl = gPipeline.canUseWindLightShaders(); - - mMoonTexturep[0] = moon_texture.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(moon_texture, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - mMoonTexturep[1] = moon_texture_next.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(moon_texture_next, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - - if (mFace[FACE_MOON]) - { - mFace[FACE_MOON]->setTexture(LLRender::DIFFUSE_MAP, mMoonTexturep[0]); - - if (mMoonTexturep[1] && can_use_wl) - { - mFace[FACE_MOON]->setTexture(LLRender::ALTERNATE_DIFFUSE_MAP, mMoonTexturep[1]); - } - } -} - -void LLVOSky::setCloudNoiseTextures(const LLUUID& cloud_noise_texture, const LLUUID& cloud_noise_texture_next) -{ - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - mCloudNoiseTexturep[0] = cloud_noise_texture.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(cloud_noise_texture, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - mCloudNoiseTexturep[1] = cloud_noise_texture_next.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(cloud_noise_texture_next, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); -} - -void LLVOSky::setBloomTextures(const LLUUID& bloom_texture, const LLUUID& bloom_texture_next) -{ - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - mBloomTextureId[0] = bloom_texture.isNull() ? psky->GetDefaultBloomTextureId() : bloom_texture; - mBloomTextureId[1] = bloom_texture_next.isNull() ? (bloom_texture.isNull() ? psky->GetDefaultBloomTextureId() : bloom_texture) : bloom_texture_next; - - fetchBloomTextures(); -} - -void LLVOSky::fetchBloomTextures() -{ - // The latched input, so the textures come and go with the bloom the programs draw (updateTextures asks again). - const bool bloom = gPipeline.shaderInputs().mLocal.mSunBloom; - for (S32 i = 0; i < 2; ++i) - { - const LLUUID& id = bloom ? mBloomTextureId[i] : LLUUID::null; - if (id.isNull()) - { - mBloomTexturep[i] = nullptr; - } - else if (mBloomTexturep[i].isNull() || mBloomTexturep[i]->getID() != id) - { - mBloomTexturep[i] = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - } - } -} - -bool LLVOSky::updateGeometry(LLDrawable *drawable) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE; - - mCameraPosAgent = drawable->getPositionAgent(); - - mEarthCenter.mV[0] = mCameraPosAgent.mV[0]; - mEarthCenter.mV[1] = mCameraPosAgent.mV[1]; - - updateHeavenlyBody(mSunScale, mSun); - updateHeavenlyBody(mMoonScale, mMoon); - - mSun.setDraw(LLEnvironment::getInstance()->getIsSunUp()); - mMoon.setDraw(LLEnvironment::getInstance()->getIsMoonUp()); - - LLPipeline::sCompiles++; - return true; -} - -void LLVOSky::updateHeavenlyBody(F32 scale, LLHeavenBody& hb) -{ - mHeavenlyBodyUpdated = true ; - - const ALSkyLayers::Corners corners = - ALSkyLayers::bodyCorners(hb.getRotation(), HEAVENLY_BODY_DIST, scale, hb.getDiskRadius()); - for (S32 vtx = 0; vtx < 4; ++vtx) - { - hb.corner(vtx) = corners[vtx]; - } - - hb.setVisible(true); -} - -void LLVOSky::updateFog(const F32 distance) -{ - LLEnvironment& environment = LLEnvironment::instance(); - if (environment.getCurrentSky() != nullptr) - { - // updateFog wants +z up; getClampedLightNorm() is OGL order, where [2] is east - m_legacyAtmospherics.updateFog(distance, environment.getClampedLightDirection()); - } - } - -void LLVOSky::setSunAndMoonDirectionsCFR(const LLVector3 &sun_dir_cfr, const LLVector3 &moon_dir_cfr) -{ - mSun.setDirection(sun_dir_cfr); - mMoon.setDirection(moon_dir_cfr); - - // Push the sun "South" as it approaches directly overhead so that we can always see bump mapping - // on the upward facing faces of cubes. - // Same as dot product with the up direction + clamp. - F32 sunDot = llmax(0.f, sun_dir_cfr.mV[2]); - sunDot *= sunDot; - - // Create normalized vector that has the sunDir pushed south about an hour and change. - LLVector3 adjustedDir = (sun_dir_cfr + LLVector3(0.f, -0.70711f, 0.70711f)) * 0.5f; - - // Blend between normal sun dir and adjusted sun dir based on how close we are - // to having the sun overhead. - mBumpSunDir = adjustedDir * sunDot + sun_dir_cfr * (1.0f - sunDot); - mBumpSunDir.normalize(); - - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - updateDirections(psky); -} - -void LLVOSky::setSunDirectionCFR(const LLVector3 &sun_dir_cfr) -{ - mSun.setDirection(sun_dir_cfr); - - // Push the sun "South" as it approaches directly overhead so that we can always see bump mapping - // on the upward facing faces of cubes. - // Same as dot product with the up direction + clamp. - F32 sunDot = llmax(0.f, sun_dir_cfr.mV[2]); - sunDot *= sunDot; - - // Create normalized vector that has the sunDir pushed south about an hour and change. - LLVector3 adjustedDir = (sun_dir_cfr + LLVector3(0.f, -0.70711f, 0.70711f)) * 0.5f; - - // Blend between normal sun dir and adjusted sun dir based on how close we are - // to having the sun overhead. - mBumpSunDir = adjustedDir * sunDot + sun_dir_cfr * (1.0f - sunDot); - mBumpSunDir.normalize(); - - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - updateDirections(psky); -} - -void LLVOSky::setMoonDirectionCFR(const LLVector3 &moon_dir_cfr) -{ - mMoon.setDirection(moon_dir_cfr); - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - updateDirections(psky); -} diff --git a/indra/newview/llvosky.h b/indra/newview/llvosky.h deleted file mode 100644 index 72dd481730e..00000000000 --- a/indra/newview/llvosky.h +++ /dev/null @@ -1,354 +0,0 @@ -/** - * @file llvosky.h - * @brief LLVOSky class header file - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVOSKY_H -#define LL_LLVOSKY_H - -#include "stdtypes.h" -#include "v3color.h" -#include "v4coloru.h" -#include "llquaternion.h" -#include "llviewertexture.h" -#include "llviewerobject.h" -#include "llframetimer.h" -#include "v3colorutil.h" -#include "llsettingssky.h" -#include "lllegacyatmospherics.h" - -const F32 HEAVENLY_BODY_DIST = HORIZON_DIST - 20.f; -const F32 HEAVENLY_BODY_FACTOR = 0.1f; -const F32 HEAVENLY_BODY_SCALE = HEAVENLY_BODY_DIST * HEAVENLY_BODY_FACTOR; - -const F32 SKYTEX_COMPONENTS = 4; -const F32 SKYTEX_RESOLUTION = 64; - -class LLFace; -class LLHaze; - -class LLSkyTex -{ - friend class LLVOSky; -private: - LLPointer mTexture[2]; - LLPointer mImageRaw[2]; - LLColor4 *mSkyData; - LLVector3 *mSkyDirs; // Cache of sky direction vectors - static S32 sCurrent; - -public: - void bindTexture(bool curr = true); - -protected: - LLSkyTex(); - void init(bool isShiny); - void cleanupGL(); - void restoreGL(); - - ~LLSkyTex(); - - - static S32 getResolution(); - static S32 getCurrent(); - static S32 stepCurrent(); - static S32 getNext(); - static S32 getWhich(const bool curr); - - void initEmpty(const S32 tex); - - void create(); - - void setDir(const LLVector3 &dir, const S32 i, const S32 j) - { - S32 offset = (S32)(i * SKYTEX_RESOLUTION + j); - mSkyDirs[offset] = dir; - } - - const LLVector3 &getDir(const S32 i, const S32 j) const - { - S32 offset = (S32)(i * SKYTEX_RESOLUTION + j); - return mSkyDirs[offset]; - } - - void setPixel(const LLColor4 &col, const S32 i, const S32 j) - { - S32 offset = (S32)(i * SKYTEX_RESOLUTION + j); - mSkyData[offset] = col; - } - - void setPixel(const LLColor4U &col, const S32 i, const S32 j) - { - LLImageDataSharedLock lock(mImageRaw[sCurrent]); - S32 offset = (S32)((i * SKYTEX_RESOLUTION + j) * SKYTEX_COMPONENTS); - U32* pix = (U32*) &(mImageRaw[sCurrent]->getData()[offset]); - *pix = col.asRGBA(); - } - - LLColor4U getPixel(const S32 i, const S32 j) - { - LLColor4U col; - LLImageDataSharedLock lock(mImageRaw[sCurrent]); - S32 offset = (S32)((i * SKYTEX_RESOLUTION + j) * SKYTEX_COMPONENTS); - U32* pix = (U32*) &(mImageRaw[sCurrent]->getData()[offset]); - col.fromRGBA( *pix ); - return col; - } - - LLImageRaw* getImageRaw(bool curr=true); - void createGLImage(S32 which); - - bool mIsShiny; -}; - -/// TODO Move into the stars draw pool (and rename them appropriately). -class LLHeavenBody -{ -protected: - LLVector3 mDirectionCached; // hack for events that shouldn't happen every frame - - LLColor3 mColor; - LLColor3 mColorCached; - F32 mIntensity; - LLVector3 mDirection; // direction of the local heavenly body - LLQuaternion mRotation; - LLVector3 mAngularVelocity; // velocity of the local heavenly body - - F32 mDiskRadius; - bool mDraw; // false - do not draw. - F32 mHorizonVisibility; // number [0, 1] due to how horizon - F32 mVisibility; // same but due to other objects being in throng. - bool mVisible; - static F32 sInterpVal; - LLVector3 mQuadCorner[4]; - LLVector3 mO; - -public: - LLHeavenBody(const F32 rad); - ~LLHeavenBody() {} - - const LLQuaternion& getRotation() const; - void setRotation(const LLQuaternion& rot); - - const LLVector3& getDirection() const; - void setDirection(const LLVector3 &direction); - void setAngularVelocity(const LLVector3 &ang_vel); - const LLVector3& getAngularVelocity() const; - - const LLVector3& getDirectionCached() const; - void renewDirection(); - - const LLColor3& getColorCached() const; - void setColorCached(const LLColor3& c); - const LLColor3& getColor() const; - void setColor(const LLColor3& c); - - void renewColor(); - - static F32 interpVal(); - static void setInterpVal(const F32 v); - - LLColor3 getInterpColor() const; - - const F32& getVisibility() const; - void setVisibility(const F32 c = 1); - - bool isVisible() const; - void setVisible(const bool v); - - const F32& getIntensity() const; - void setIntensity(const F32 c); - - void setDiskRadius(const F32 radius); - F32 getDiskRadius() const; - - void setDraw(const bool draw); - bool getDraw() const; - - const LLVector3& corner(const S32 n) const; - LLVector3& corner(const S32 n); - const LLVector3* corners() const; -}; - -class LLCubeMap; - -class LLVOSky : public LLStaticViewerObject -{ -public: - enum - { - FACE_SUN, - FACE_MOON, - FACE_BLOOM, - FACE_COUNT - }; - - LLVOSky(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); - - // Initialize/delete data that's only inited once per class. - void init(); - void initCubeMap(); - - void cleanupGL(); - void restoreGL(); - - void calc(); - void cacheEnvironment(LLSettingsSky::ptr_t psky, AtmosphericsVars& atmosphericsVars); - - /*virtual*/ void idleUpdate(LLAgent &agent, const F64 &time); - bool updateSky(); - - // Graphical stuff for objects - maybe broken out into render class - // later? - /*virtual*/ void updateTextures(); - /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); - /*virtual*/ bool updateGeometry(LLDrawable *drawable); - - const LLHeavenBody& getSun() const { return mSun; } - const LLHeavenBody& getMoon() const { return mMoon; } - - bool isSameFace(S32 idx, const LLFace* face) const { return mFace[idx] == face; } - - // directions provided should already be in CFR coord sys (+x at, +z up, +y right) - void setSunAndMoonDirectionsCFR(const LLVector3 &sun_dir, const LLVector3 &moon_dir); - void setSunDirectionCFR(const LLVector3 &sun_direction); - void setMoonDirectionCFR(const LLVector3 &moon_direction); - - // The body's quad about the camera (ALSkyLayers::bodyCorners), at @a scale times its disk. - void updateHeavenlyBody(F32 scale, LLHeavenBody& hb); - - F32 getWorldScale() const { return mWorldScale; } - void setWorldScale(const F32 s) { mWorldScale = s; } - void updateFog(const F32 distance); - - void setFogRatio(const F32 fog_ratio) { m_legacyAtmospherics.setFogRatio(fog_ratio); } - F32 getFogRatio() const { return m_legacyAtmospherics.getFogRatio(); } - - LLColor4 getSkyFogColor() const { return m_legacyAtmospherics.getFogColor(); } - LLColor4 getGLFogColor() const { return m_legacyAtmospherics.getGLFogColor(); } - - void setCloudDensity(F32 cloud_density) { mCloudDensity = cloud_density; } - void setWind ( const LLVector3& wind ) { mWind = wind.length(); } - - const LLVector3 &getCameraPosAgent() const { return mCameraPosAgent; } - LLVector3 getEarthCenter() const { return mEarthCenter; } - - LLCubeMap *getCubeMap() const { return mCubeMap; } - - LLViewerTexture* getSunTex() const { return mSunTexturep[0]; } - LLViewerTexture* getMoonTex() const { return mMoonTexturep[0]; } - LLViewerTexture* getBloomTex() const { return mBloomTexturep[0]; } - LLViewerTexture* getCloudNoiseTex() const { return mCloudNoiseTexturep[0]; } - - LLViewerTexture* getRainbowTex() const { return mRainbowMap; } - LLViewerTexture* getHaloTex() const { return mHaloMap; } - - LLViewerTexture* getSunTexNext() const { return mSunTexturep[1]; } - LLViewerTexture* getMoonTexNext() const { return mMoonTexturep[1]; } - LLViewerTexture* getBloomTexNext() const { return mBloomTexturep[1]; } - LLViewerTexture* getCloudNoiseTexNext() const { return mCloudNoiseTexturep[1]; } - - void setSunTextures(const LLUUID& sun_texture, const LLUUID& sun_texture_next); - void setMoonTextures(const LLUUID& moon_texture, const LLUUID& moon_texture_next); - void setCloudNoiseTextures(const LLUUID& cloud_noise_texture, const LLUUID& cloud_noise_texture_next); - // Only while the sun bloom is on (ALShaderInputs::Local::mSunBloom) are the textures fetched. - void setBloomTextures(const LLUUID& bloom_texture, const LLUUID& bloom_texture_next); - - void setSunScale(F32 sun_scale); - void setMoonScale(F32 sun_scale); - - void forceSkyUpdate(void); - -public: - LLFace *mFace[FACE_COUNT]; - LLVector3 mBumpSunDir; - - F32 getInterpVal() const { return mInterpVal; } - -protected: - ~LLVOSky(); - - void updateDirections(LLSettingsSky::ptr_t psky); - - // The bloom textures fetched, or let go, as the sun bloom is on or off. - void fetchBloomTextures(); - - void initSkyTextureDirs(const S32 side, const S32 tile); - void createSkyTexture(const LLSettingsSky::ptr_t &psky, AtmosphericsVars& vars, const S32 side, const S32 tile); - - LLPointer mSunTexturep[2]; - LLPointer mMoonTexturep[2]; - LLPointer mCloudNoiseTexturep[2]; - LLPointer mBloomTexturep[2]; - LLUUID mBloomTextureId[2]; - LLPointer mRainbowMap; - LLPointer mHaloMap; - - F32 mSunScale = 1.0f; - F32 mMoonScale = 1.0f; - - static S32 sResolution; - static S32 sTileResX; - static S32 sTileResY; - LLSkyTex mSkyTex[6]; - LLSkyTex mShinyTex[6]; - LLHeavenBody mSun; - LLHeavenBody mMoon; - LLVector3 mSunDefaultPosition; - LLVector3 mSunAngVel; - F32 mAtmHeight; - LLVector3 mEarthCenter; - LLVector3 mCameraPosAgent; - F32 mBrightnessScale; - LLColor3 mBrightestPoint; - F32 mBrightnessScaleNew; - LLColor3 mBrightestPointNew; - F32 mBrightnessScaleGuess; - LLColor3 mBrightestPointGuess; - bool mWeatherChange; - F32 mCloudDensity; - F32 mWind; - - bool mInitialized; - bool mForceUpdate; - bool mNeedUpdate; // flag to force update of cubemap - S32 mCubeMapUpdateStage; // state of cubemap uodate: -1 idle; 0-5 per-face updates; 6 finalizing - - F32 mAmbientScale; - LLColor3 mNightColorShift; - F32 mInterpVal; - F32 mWorldScale; - - LLPointer mCubeMap; // Cube map for the environment - - LLFrameTimer mUpdateTimer; - LLTimer mForceUpdateThrottle; - bool mHeavenlyBodyUpdated ; - - AtmosphericsVars m_atmosphericsVars; - AtmosphericsVars m_lastAtmosphericsVars; - LLAtmospherics m_legacyAtmospherics; -}; - -#endif diff --git a/indra/newview/llvosurfacepatch.cpp b/indra/newview/llvosurfacepatch.cpp index ff1fad99930..22b9e57f484 100644 --- a/indra/newview/llvosurfacepatch.cpp +++ b/indra/newview/llvosurfacepatch.cpp @@ -33,7 +33,6 @@ #include "lldrawable.h" #include "llface.h" #include "llprimitive.h" -#include "llsky.h" #include "llsurfacepatch.h" #include "llsurface.h" #include "llviewerobjectlist.h" diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index a7fd2366d75..26350923f10 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -59,7 +59,6 @@ #include "llflexibleobject.h" #include "llskinningutil.h" #include "alsimdkernels.h" -#include "llsky.h" #include "lltexturefetch.h" #include "llvector4a.h" #include "llviewercamera.h" diff --git a/indra/newview/llvowlsky.cpp b/indra/newview/llvowlsky.cpp deleted file mode 100644 index b15257f175e..00000000000 --- a/indra/newview/llvowlsky.cpp +++ /dev/null @@ -1,390 +0,0 @@ -/** - * @file llvowlsky.cpp - * @brief LLVOWLSky class implementation - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * Alchemy Viewer Source Code - * Copyright © 2026, Rye - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "pipeline.h" - -#include "llvowlsky.h" -#include "llsky.h" -#include "lldrawpoolwlsky.h" -#include "llface.h" -#include "llviewercontrol.h" -#include "llenvironment.h" -#include "llsettingssky.h" - -#include "alstarfield.h" - -namespace -{ - // The global generator, for the meteors. - F32 globalRandom() { return ll_frand(); } - - // The most meteors in flight: the user-facing RenderMeteorMaxCount setting is clamped to the composite's array. - constexpr U32 kMeteorHardMax = ALSkyLayers::MAX_METEORS; - - // Pick a random unit vector perpendicular to `n`. Uses a deterministic - // orthogonal basis built from `n` and a fixed "up" swap for robustness - // at the poles, then rotates around `n` by a uniform angle. - template LLVector3 randomTangentTo(const LLVector3& n, Rand& draw) - { - const LLVector3 fallback = (fabsf(n.mV[2]) < 0.9f) - ? LLVector3(0.f, 0.f, 1.f) - : LLVector3(1.f, 0.f, 0.f); - LLVector3 a = (n % fallback); // cross - a.normVec(); - LLVector3 b = (n % a); - b.normVec(); - const F32 rot = F_TWO_PI * draw(); - return a * cosf(rot) + b * sinf(rot); - } -} - -LLVOWLSky::LLVOWLSky(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) - : LLStaticViewerObject(id, pcode, regionp, true) -{ - initStars(); - - // Re-generate star data and force the VB to be rebuilt when the user - // changes RenderStarCount. The setting is intended for perf tuning, so - // taking effect immediately (without a restart) is the expected behavior. - mStarCountConnection = gSavedSettings.getControl("RenderStarCount") - ->getSignal() - ->connect([this](LLControlVariable*, const LLSD&, const LLSD&) - { - initStars(); - resetVertexBuffers(); - }); -} - -void LLVOWLSky::idleUpdate(LLAgent &agent, const F64 &time) -{ - -} - -bool LLVOWLSky::isActive(void) const -{ - return false; -} - -LLDrawable * LLVOWLSky::createDrawable(LLPipeline * pipeline) -{ - pipeline->allocDrawable(this); - - //LLDrawPoolWLSky *poolp = static_cast( - gPipeline.getPool(LLDrawPool::POOL_WL_SKY); - - mDrawable->setRenderType(LLPipeline::RENDER_TYPE_WL_SKY); - - return mDrawable; -} - -void LLVOWLSky::resetVertexBuffers() -{ - mStarsVerts = nullptr; - - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); -} - -void LLVOWLSky::cleanupGL() -{ - mStarsVerts = nullptr; - - LLDrawPoolWLSky::cleanupGL(); -} - -void LLVOWLSky::restoreGL() -{ - LLDrawPoolWLSky::restoreGL(); - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); -} - -bool LLVOWLSky::updateGeometry(LLDrawable * drawable) -{ - LL_PROFILE_ZONE_SCOPED; - - updateStarGeometry(drawable); - - LLPipeline::sCompiles++; - - return true; -} - -void LLVOWLSky::drawStarPoints(const ALStarField::Frustum& frustum, U32 faintest) -{ - if (mStarsVerts.isNull() || mStarLayout.mPoints == 0) - { - return; - } - - { - LL_PROFILE_ZONE_NAMED("sky stars cull"); - ALStarField::pointDraws(mStarLayout, frustum, faintest, mStarDraws); - } - if (!mStarDraws.mFirst.empty()) - { - mStarsVerts->setBuffer(); - mStarsVerts->drawMultiArrays(LLRender::POINTS, mStarDraws.mFirst.data(), mStarDraws.mCount.data(), - (U32)mStarDraws.mFirst.size()); - } -} - -void LLVOWLSky::drawStarQuads(U32 faintest) -{ - const U32 quads = ALStarField::quadsDrawn(mStarLayout, faintest); - if (mStarsVerts.notNull() && quads > 0) - { - mStarsVerts->setBuffer(); - mStarsVerts->drawArrays(LLRender::TRIANGLES, 0, quads * ALStarField::QUAD_VERTICES); - } -} - -void LLVOWLSky::initStars() -{ - // RenderStarCount scales every tier alike, so the mix of bright primaries, mid stars and faint dust stays the same - // when the total is dialled down for performance. - const S32 requested = gSavedSettings.getS32("RenderStarCount"); - mStars = ALStarField::generate((U32)llmax(requested, 0), - LLEnvironment::instance().getCurrentSky()->getDomeRadius()); - mStarLayout = ALStarField::layOut(mStars); -} - -bool LLVOWLSky::updateStarGeometry(LLDrawable *drawable) -{ - LL_PROFILE_ZONE_SCOPED; - - // Star data is static after initStars(); skip re-upload if the buffer - // already exists. It's released on cleanupGL/resetVertexBuffers, so we'll - // rebuild it after a context loss. - if (mStarsVerts.notNull()) - { - return true; - } - - const U32 num_verts = mStarLayout.vertices(); - - // RenderStarCount = 0 disables stars entirely. Leave mStarsVerts null so - // the star draws short-circuit. - if (num_verts == 0) - { - return true; - } - - mStarsVerts = new LLVertexBuffer(LLDrawPoolWLSky::STAR_VERTEX_DATA_MASK); - if (!mStarsVerts->allocateBuffer(num_verts, 0)) - { - LL_WARNS() << "Failed to allocate Vertex Buffer for Sky to " << num_verts << " vertices" << LL_ENDL; - mStarsVerts = nullptr; - return false; - } - - LLStrider verticesp; - if (!mStarsVerts->getVertexStrider(verticesp)) - { - LL_ERRS() << "Failed updating star geometry." << LL_ENDL; - } - - // One vertex a star, drawn as a point the vertex shader sizes; six for each spiked star, which it spreads into a - // quad. - ALStarField::fillVertices(mStars, mStarLayout, verticesp); - - mStarsVerts->unmapBuffer(); - return true; -} - -void LLVOWLSky::tickMeteors(F32 dt_seconds) -{ - LL_PROFILE_ZONE_SCOPED; - - if (dt_seconds <= 0.0f) return; - - // Age existing meteors and drop any that have run their course. - for (MeteorState& m : mMeteors) - { - m.age += dt_seconds; - } - mMeteors.erase( - std::remove_if(mMeteors.begin(), mMeteors.end(), - [](const MeteorState& m) { return m.age >= m.lifetime; }), - mMeteors.end()); - - static LLCachedControl max_count_setting(gSavedSettings, "RenderMeteorMaxCount", 16); - static LLCachedControl spawn_secs_setting(gSavedSettings, "RenderMeteorSpawnInterval", 4.0f); - const U32 max_count = (U32)llclamp((S32)max_count_setting, 0, (S32)kMeteorHardMax); - const F32 spawn_secs = llmax(0.05f, (F32)spawn_secs_setting); - - // Poisson-ish spawn: per-frame probability = dt / mean_interval. At 60 FPS - // with a 4s mean, this produces a new meteor roughly every 240 frames. - if (mMeteors.size() < max_count - && ll_frand() < dt_seconds / spawn_secs) - { - const F32 dome_radius = LLEnvironment::instance().getCurrentSky()->getDomeRadius(); - - // Start point anywhere on the upper hemisphere, but prefer a bit above - // the horizon so meteors don't immediately pop out of view. - LLVector3 start_dir = ALStarField::uniformUpperHemisphere(globalRandom); - // Lift horizon-hugging starts slightly so short streaks stay visible. - if (start_dir.mV[2] < 0.1f) - { - start_dir.mV[2] = 0.1f; - start_dir.normVec(); - } - - // Travel direction is a random tangent in the sky tangent plane at - // start_dir. This gives great-circle-like paths. - LLVector3 travel_dir = randomTangentTo(start_dir, globalRandom); - - // Classify each new meteor into a visual archetype. The weighted mix - // makes showers feel alive at high spawn rates — most meteors are - // faint and quick, but the occasional fireball with exotic chemistry - // colors draws the eye. The cumulative thresholds below are the - // probability cutoffs for each class. - const F32 roll = ll_frand(); - F32 width_scale; - F32 path_mult = 1.0f; // multiplier on both path and trail length - F32 life_mult = 1.0f; // multiplier on lifetime (shorter = faster) - F32 peak; - LLColor3 color; - - if (roll < 0.55f) - { - // Common: faint, thin, cool red-orange. These dominate by count - // (matching real meteor distributions) and give the eye a quiet - // background of activity. - width_scale = 0.55f + 0.25f * ll_frand(); - peak = 0.35f + 0.25f * ll_frand(); - life_mult = 0.75f + 0.25f * ll_frand(); - const F32 tk = 2500.0f + 1500.0f * ll_frand(); - color = ALStarField::blackBodyColor(tk); - } - else if (roll < 0.85f) - { - // Standard: yellow-white, the "average" visible meteor. - width_scale = 0.85f + 0.35f * ll_frand(); - peak = 0.6f + 0.3f * ll_frand(); - const F32 tk = 4500.0f + 2500.0f * ll_frand(); - color = ALStarField::blackBodyColor(tk); - } - else if (roll < 0.96f) - { - // Fast: hot blue-white, travels farther in the same time so - // apparent angular velocity is higher. - width_scale = 0.7f + 0.3f * ll_frand(); - peak = 0.8f + 0.2f * ll_frand(); - path_mult = 1.25f + 0.35f * ll_frand(); - life_mult = 0.6f + 0.25f * ll_frand(); - const F32 tk = 8000.0f + 7000.0f * ll_frand(); - color = ALStarField::blackBodyColor(tk); - } - else - { - // Fireball: thick, bright, persistent. Some roll to an exotic - // chemistry color from ablation (Mg, Na, N, Cu) rather than the - // usual blackbody curve. - width_scale = 1.8f + 1.4f * ll_frand(); - peak = 0.9f + 0.1f * ll_frand(); - path_mult = 1.3f + 0.4f * ll_frand(); - life_mult = 1.1f + 0.4f * ll_frand(); - - const F32 chem = ll_frand(); - if (chem < 0.22f) color = LLColor3(0.35f, 1.00f, 0.45f); // magnesium green - else if (chem < 0.42f) color = LLColor3(1.00f, 0.55f, 0.20f); // sodium orange - else if (chem < 0.58f) color = LLColor3(1.00f, 0.35f, 0.30f); // nitrogen red - else if (chem < 0.72f) color = LLColor3(0.40f, 0.55f, 1.00f); // copper/Mg blue - else - { - // Hot white bolide, no exotic chemistry. - const F32 tk = 9000.0f + 6000.0f * ll_frand(); - color = ALStarField::blackBodyColor(tk); - } - } - - MeteorState m; - m.origin_world = start_dir * dome_radius; - m.direction_world = travel_dir; - // Base lifetime 1.1..2.5s. Fast class scales this down, fireballs up, - // so the full range across classes is roughly 0.7..3.7s. - m.lifetime = (1.1f + 1.4f * ll_frand()) * life_mult; - const F32 path_angle = (0.21f + 0.31f * ll_frand()) * path_mult; // radians - const F32 trail_angle = path_angle * (0.35f + 0.25f * ll_frand()); - m.path_length_world = dome_radius * path_angle; - m.trail_length_world = dome_radius * trail_angle; - m.color = color; - m.peak_intensity = peak; - m.width_scale = width_scale; - m.age = 0.0f; - - mMeteors.push_back(m); - } -} - -void LLVOWLSky::meteors(std::vector& out) const -{ - out.clear(); - for (const MeteorState& m : mMeteors) - { - if (out.size() >= kMeteorHardMax) break; - - // Envelope: quick fade in, steady, slower fade out. These ratios read - // as a meteor "blooming" and then trailing off. - const F32 frac = m.age / llmax(m.lifetime, 1e-4f); - F32 alpha = 1.0f; - if (frac < 0.10f) alpha = frac / 0.10f; - else if (frac > 0.80f) alpha = (1.0f - frac) / 0.20f; - alpha = llclamp(alpha, 0.0f, 1.0f) * m.peak_intensity; - if (alpha <= 0.0f) continue; - - // Head progresses linearly along the travel direction. Tail is a fixed - // length behind the head regardless of age — the trail doesn't grow, - // it just moves with the head. - const LLVector3 head = m.origin_world + m.direction_world * (m.path_length_world * frac); - const LLVector3 tail = head - m.direction_world * m.trail_length_world; - - // Pack per-frame envelope alpha into the RGB channels (in sRGB space — a - // mild nonlinearity vs. linear-space premultiply, but the envelope is - // smooth enough that the difference isn't visible). This frees the - // color's alpha slot to carry the per-meteor width_scale, which the - // fragment shader needs to normalize its cross-streak Gaussian so - // thicker meteors are actually thicker (not just bigger empty quads). - LLColor4 color4; - color4.mV[VRED] = m.color.mV[VRED] * alpha; - color4.mV[VGREEN] = m.color.mV[VGREEN] * alpha; - color4.mV[VBLUE] = m.color.mV[VBLUE] * alpha; - // Width_scale range is [0, 4]; pack as /4 so it fits in 8-bit unsigned. - color4.mV[VALPHA] = llclamp(m.width_scale * 0.25f, 0.0f, 1.0f); - - ALSkyLayers::Meteor meteor; - meteor.mMidpoint = (head + tail) * 0.5f; - meteor.mHalf = (head - tail) * 0.5f; - meteor.mColour = LLColor4U(color4); - meteor.mWidth = m.width_scale; - out.push_back(meteor); - } -} - diff --git a/indra/newview/llvowlsky.h b/indra/newview/llvowlsky.h deleted file mode 100644 index 4d531826a5f..00000000000 --- a/indra/newview/llvowlsky.h +++ /dev/null @@ -1,101 +0,0 @@ -/** - * @file llvowlsky.h - * @brief LLVOWLSky class definition - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * Alchemy Viewer Source Code - * Copyright © 2026, Rye - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_VOWLSKY_H -#define LL_VOWLSKY_H - -#include "llviewerobject.h" - -#include "alskylayers.h" -#include "alstarfield.h" - -#include - -class LLVOWLSky : public LLStaticViewerObject { -public: - LLVOWLSky(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); - - /*virtual*/ void idleUpdate(LLAgent &agent, const F64 &time); - /*virtual*/ bool isActive(void) const; - /*virtual*/ LLDrawable * createDrawable(LLPipeline *pipeline); - /*virtual*/ bool updateGeometry(LLDrawable *drawable); - - // The stars drawn as points, those of the cells in view through @a frustum, and the spiked stars as quads - // (ALStarField::Layout), each with its own program bound; only those at @a faintest or brighter - // (ALStarField::faintestVisible). - void drawStarPoints(const ALStarField::Frustum& frustum, U32 faintest); - void drawStarQuads(U32 faintest); - void resetVertexBuffers(void); - - // Meteors: CPU-side simulation of short-lived streaks. Tick once per frame in a view that draws them, then hand - // their streaks to the sky's composite (LLDrawPoolWLSky::bindComposite). - void tickMeteors(F32 dt_seconds); - - // The meteors in flight, as the sky's composite takes them (ALSkyLayers::packMeteors): each one's streak about the - // camera as its envelope has carried it this far, its colour times that envelope, and its width class. - void meteors(std::vector& out) const; - - void cleanupGL(); - void restoreGL(); - -private: - - // helper function for initializing the stars. - void initStars(); - - // helper function for updating the stars geometry. - bool updateStarGeometry(LLDrawable *drawable); - -private: - LLPointer mStarsVerts; - - // The star field RenderStarCount asked for, in the order the buffer takes it, and where each kind lies in it. - std::vector mStars; - ALStarField::Layout mStarLayout; - ALStarField::Draws mStarDraws; // the frame's runs of points, kept for their storage - - struct MeteorState - { - LLVector3 origin_world; // starting head position - LLVector3 direction_world; // unit direction of travel - F32 path_length_world; // distance head travels across its lifetime - F32 trail_length_world; // length of the glowing trail behind the head - F32 age; // seconds since spawn - F32 lifetime; // total seconds to live - LLColor3 color; // sRGB color - F32 peak_intensity; // 0..1 scale on the envelope peak - F32 width_scale; // per-meteor thickness multiplier (0..4, packed to color.a) - }; - std::vector mMeteors; - - // Connection that re-runs star generation when RenderStarCount changes. - boost::signals2::scoped_connection mStarCountConnection; -}; - -#endif // LL_VOWLSKY_H diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp index 23428aba239..abe7c1861d7 100644 --- a/indra/newview/llworld.cpp +++ b/indra/newview/llworld.cpp @@ -39,7 +39,6 @@ #include "llglheaders.h" #include "llhttpnode.h" #include "llregionhandle.h" -#include "llsky.h" #include "llsurface.h" #include "lltrans.h" #include "llviewercamera.h" @@ -105,7 +104,6 @@ LLWorld::LLWorld() : void LLWorld::resetClass() { gObjectList.destroy(); - gSky.cleanup(); // references an object for(region_list_t::iterator region_it = mRegionList.begin(); region_it != mRegionList.end(); ) { LLViewerRegion* region_to_delete = *region_it++; diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index a1a4deaa93b..ce4c045f982 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -85,7 +85,6 @@ #include "lldrawpoolbump.h" #include "lldrawpooltree.h" #include "lldrawpoolwater.h" -#include "lldrawpoolwlsky.h" #include "llface.h" #include "llfeaturemanager.h" #include "llfetchedgltfmaterial.h" @@ -100,7 +99,6 @@ #include "llpipelinelistener.h" #include "llresmgr.h" #include "llselectmgr.h" -#include "llsky.h" #include "lltracker.h" #include "lltool.h" #include "lltoolmgr.h" @@ -114,8 +112,6 @@ #include "llviewerwindow.h" // For getSpinAxis #include "llvoavatarself.h" #include "llvocache.h" -#include "llvosky.h" -#include "llvowlsky.h" #include "llvotree.h" #include "llvovolume.h" #include "llvosurfacepatch.h" @@ -732,8 +728,6 @@ void LLPipeline::cleanup() mAlphaPoolPreWater = nullptr; delete mAlphaPoolPostWater; mAlphaPoolPostWater = nullptr; - delete mSkyPool; - mSkyPool = NULL; delete mTerrainPool; mTerrainPool = NULL; delete mWaterPool; @@ -746,13 +740,11 @@ void LLPipeline::cleanup() mGlowPool = NULL; delete mBumpPool; mBumpPool = NULL; - // don't delete wl sky pool it was handled above in the for loop - //delete mWLSkyPool; - mWLSkyPool = NULL; delete mWaterExclusionPool; mWaterExclusionPool = nullptr; releaseAllGLResources(); + mSky.cleanup(); mFaceSelectImagep = NULL; @@ -1549,6 +1541,7 @@ void LLPipeline::releaseAllGLResources() releaseRenderTargets(); releaseDepthDependents(); + mSky.releaseGL(); LLVOAvatar::resetImpostors(); gBumpImageList.destroyGL(); @@ -2461,6 +2454,7 @@ void LLPipeline::restoreGL() // The new context's programs and resources, from the inputs as they are now. loadRenderInputs(); + mSky.restoreGL(); for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); iter != LLWorld::getInstance()->getRegionList().end(); ++iter) @@ -2635,18 +2629,10 @@ LLDrawPool *LLPipeline::findPool(const U32 type, LLViewerTexture *tex0) case LLDrawPool::POOL_CONTROL_AV: break; // Do nothing - case LLDrawPool::POOL_SKY: - poolp = mSkyPool; - break; - case LLDrawPool::POOL_WATER: poolp = mWaterPool; break; - case LLDrawPool::POOL_WL_SKY: - poolp = mWLSkyPool; - break; - case LLDrawPool::POOL_GLTF_PBR: poolp = mPBROpaquePool; break; @@ -3597,31 +3583,6 @@ void LLPipeline::updateCull(LLCamera& camera, LLCullResult& result) vo_part->cull(camera, sUseOcclusion > 0); } } - - // Neither sky is part of a cutout's subject, and both come in here rather - // than through a partition the cutout's choke points see. - const bool cutout = cullingToCutoutSubject(); - - if (!cutout && - hasRenderType(LLPipeline::RENDER_TYPE_SKY) && - gSky.mVOSkyp.notNull() && - gSky.mVOSkyp->mDrawable.notNull()) - { - gSky.mVOSkyp->mDrawable->setVisible(camera); - sCull->pushDrawable(gSky.mVOSkyp->mDrawable); - gSky.updateCull(); - stop_glerror(); - } - - if (!cutout && - hasRenderType(LLPipeline::RENDER_TYPE_WL_SKY) && - gPipeline.canUseWindLightShaders() && - gSky.mVOWLSkyp.notNull() && - gSky.mVOWLSkyp->mDrawable.notNull()) - { - gSky.mVOWLSkyp->mDrawable->setVisible(camera); - sCull->pushDrawable(gSky.mVOWLSkyp->mDrawable); - } } void LLPipeline::markNotCulled(LLSpatialGroup* group, LLCamera& camera) @@ -4228,7 +4189,6 @@ void LLPipeline::stateSort(LLCamera& camera, LLCullResult &result) LLPipeline::RENDER_TYPE_CONTROL_AV, LLPipeline::RENDER_TYPE_TERRAIN, LLPipeline::RENDER_TYPE_TREE, - LLPipeline::RENDER_TYPE_SKY, LLPipeline::RENDER_TYPE_VOIDWATER, LLPipeline::RENDER_TYPE_WATER, LLPipeline::END_RENDER_TYPES)) @@ -5156,8 +5116,8 @@ void LLPipeline::renderGeomDeferred(LLCamera& camera, bool do_occlusion) // pool -- each real FRAMEBUFFER_SRGB toggle costs a gGL.flush() plus a GL state // change, and the per-pool scopes toggled it off and on again at every pool // boundary. The writers that still store display-encoded values raw opt OUT - // locally: the WL sky family (LLDrawPoolWLSky::renderDeferred) and the avatar pool's - // impostor/rigid passes (LLDrawPoolAvatar::renderDeferred). + // locally: the stars (ALSky::renderStars) and the avatar pool's impostor/rigid + // passes (LLDrawPoolAvatar::renderDeferred). LLGLEnable srgb(GL_FRAMEBUFFER_SRGB); for (pool_set_t::iterator iter = mPools.begin(); iter != mPools.end(); ++iter) @@ -5253,6 +5213,14 @@ void LLPipeline::renderGeomDeferred(LLCamera& camera, bool do_occlusion) gGL.matrixMode(LLRender::MM_MODELVIEW); gGL.loadMatrix(LLViewerCamera::getCurrent().getModelview()); + // The stars, the one layer of the sky the G-buffer takes, after every opaque pool, so the depth test spares + // all they cover. No pool after the opaque ones draws into the G-buffer (glow, alpha and water draw after the + // lighting), so the sky's pixels hold here what they held when a pool drew the stars there. + if (drawsSky()) + { + mSky.renderStars(); + } + gGL.setColorMask(true, false); } // Tracy ZoneScoped @@ -6392,18 +6360,6 @@ void LLPipeline::addToQuickLookup( LLDrawPool* new_poolp ) case LLDrawPool::POOL_CONTROL_AV: break; // Do nothing - case LLDrawPool::POOL_SKY: - if( mSkyPool ) - { - llassert(0); - LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate Sky pool" << LL_ENDL; - } - else - { - mSkyPool = new_poolp; - } - break; - case LLDrawPool::POOL_WATER: if( mWaterPool ) { @@ -6416,18 +6372,6 @@ void LLPipeline::addToQuickLookup( LLDrawPool* new_poolp ) } break; - case LLDrawPool::POOL_WL_SKY: - if( mWLSkyPool ) - { - llassert(0); - LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate WLSky Pool" << LL_ENDL; - } - else - { - mWLSkyPool = new_poolp; - } - break; - case LLDrawPool::POOL_GLTF_PBR: if( mPBROpaquePool ) { @@ -6509,11 +6453,6 @@ void LLPipeline::removeFromQuickLookup( LLDrawPool* poolp ) mFullbrightPool = NULL; break; - case LLDrawPool::POOL_WL_SKY: - llassert(mWLSkyPool == poolp); - mWLSkyPool = NULL; - break; - case LLDrawPool::POOL_GLOW: llassert(mGlowPool == poolp); mGlowPool = NULL; @@ -6551,11 +6490,6 @@ void LLPipeline::removeFromQuickLookup( LLDrawPool* poolp ) case LLDrawPool::POOL_CONTROL_AV: break; // Do nothing - case LLDrawPool::POOL_SKY: - llassert( poolp == mSkyPool ); - mSkyPool = NULL; - break; - case LLDrawPool::POOL_WATER: llassert( poolp == mWaterPool ); mWaterPool = NULL; @@ -8740,21 +8674,17 @@ void LLPipeline::generateLensFlareState(LLRenderTarget* src) } // Probe radius as a fraction of screen height: the body's own angular - // radius (updateHeavenlyBodyGeometry: HEAVENLY_BODY_FACTOR x disk radius + // radius (ALSkyLayers::bodyCorners: ALSkyLayers::BODY_FACTOR x disk radius // x sky scale, at unit distance) through the current vertical FOV, times // the setting. It follows zoom and the sky's sun scale, so an occluder // narrower than the disc dims the flare instead of cutting it. LLSettingsSky::ptr_t psky = environment.getCurrentSky(); - F32 disk_radius = 0.5f; - if (gSky.mVOSkyp.notNull()) - { - disk_radius = (sun_up ? gSky.mVOSkyp->getSun() : gSky.mVOSkyp->getMoon()).getDiskRadius(); - } + const F32 disk_radius = (sun_up ? mSky.getSun() : mSky.getMoon()).getDiskRadius(); const F32 body_scale = llmax(sun_up ? psky->getSunScale() : psky->getMoonScale(), 0.01f); // The drawn quad grows towards the horizon (1.3x wide and 1.2x tall at - // dir.z = 0, llvosky.cpp); a circular probe takes the mean of the two. + // dir.z = 0, ALSkyLayers::bodyCorners); a circular probe takes the mean of the two. const F32 enlargement = 1.f + (1.f - light_dir.mV[VZ]) * 0.25f; - const F32 half_tan = HEAVENLY_BODY_FACTOR * disk_radius * body_scale * enlargement; + const F32 half_tan = ALSkyLayers::BODY_FACTOR * disk_radius * body_scale * enlargement; const LLViewerCamera* camera = LLViewerCamera::getInstance(); const F32 fov_y = llclamp(camera->getView(), 0.01f, F_PI - 0.01f); // A tiled snapshot zooms the projection without touching the FOV, and the @@ -11681,12 +11611,7 @@ void LLPipeline::bindDeferredShader(LLGLSLShader& shader, LLRenderTarget* light_ channel = shader.enableTexture(LLShaderMgr::ENVIRONMENT_MAP); if (channel > -1) { - LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; - if (cube_map) - { - cube_map->enable(channel); - cube_map->bind(); - } + mSky.bindEnvironmentCube(channel); const F32* m = LLViewerCamera::getCurrent().getModelview().getF32ptr(); @@ -11981,8 +11906,8 @@ void LLPipeline::renderDeferredLighting() if (drawsSky()) { LLGLSLShader& sky_shader = gDeferredSkyCompositeProgram; - bindDeferredShader(sky_shader); - LLDrawPoolWLSky::bindComposite(sky_shader); + sky_shader.bind(); + mSky.bindComposite(sky_shader); { LL_PROFILE_GPU_ZONE("sky composite"); LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_EQUAL); @@ -11990,8 +11915,8 @@ void LLPipeline::renderDeferredLighting() mScreenTriangleVB->setBuffer(); mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); } - LLDrawPoolWLSky::unbindComposite(sky_shader); - unbindDeferredShader(sky_shader); + mSky.unbindComposite(sky_shader); + sky_shader.unbind(); } } @@ -12381,13 +12306,19 @@ void LLPipeline::renderDeferredLighting() bool LLPipeline::drawsSky() const { - // As the sky's draws were gated: its two objects pushed to the cull, and the pool drawing. return !cullingToCutoutSubject() && canUseWindLightShaders() && hasRenderType(RENDER_TYPE_SKY) && - hasRenderType(RENDER_TYPE_WL_SKY) && gSky.mVOSkyp.notNull() && gSky.mVOWLSkyp.notNull(); + hasRenderType(RENDER_TYPE_WL_SKY) && mSky.ready(); } -void LLPipeline::clearStarAttachment() +void LLPipeline::beginSkyView() { + // What the view draws of the sky, taken once for its draws: the stars', the composite's and this clear. + mSky.beginView(drawsSky()); + if (!(mSky.viewLayers() & ALSkyLayers::STARS)) + { + return; + } + LLRenderTarget* gbuffer = LLRenderTarget::getCurrentBoundTarget(); const U32 attachment = ALSkyLayers::starAttachment(shaderInputs().mGlobal.mEmissive); if (!gbuffer || attachment >= gbuffer->getNumTextures()) @@ -12407,8 +12338,7 @@ bool LLPipeline::hazeOnWater() const // The haze converges only the water surface seen from above it (hazeF.glsl), on the dome the sky draws. An HDRI // sky is colour already shaded, which the haze cannot take the same way. if (gCubeSnapshot || sImpostorRender || mRT != &mMainRT || !RenderDeferredAtmospheric || sUnderWaterRender || - !canUseWindLightShaders() || !hasRenderType(RENDER_TYPE_SKY) || !hasRenderType(RENDER_TYPE_WL_SKY) || - gSky.mVOSkyp.isNull() || LLDrawPoolWLSky::useHDRISky()) + !drawsSky() || ALSky::useHDRISky()) { return false; } @@ -12465,7 +12395,7 @@ void LLPipeline::doAtmospherics() if (on_water) { - LLDrawPoolWLSky::bindDome(haze_shader); + mSky.bindDome(haze_shader); // From high above the water, the water fades into the sky behind it, wholly by the height it is no // longer drawn from. const F32 height = LLViewerCamera::getInstance()->getOrigin().mV[VZ] - environment.getWaterHeight(); @@ -12796,15 +12726,7 @@ void LLPipeline::unbindDeferredShader(LLGLSLShader &shader) if (!LLPipeline::sReflectionProbesEnabled) { - S32 channel = shader.disableTexture(LLShaderMgr::ENVIRONMENT_MAP, ALTextureSlot::TT_CUBE_MAP); - if (channel > -1) - { - LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; - if (cube_map) - { - cube_map->disable(); - } - } + shader.disableTexture(LLShaderMgr::ENVIRONMENT_MAP, ALTextureSlot::TT_CUBE_MAP); } unbindReflectionProbes(shader); diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h index be70f6fcdc5..e5f116ef57c 100644 --- a/indra/newview/pipeline.h +++ b/indra/newview/pipeline.h @@ -47,6 +47,7 @@ #include "alrenderinputrequests.h" #include "alrenderresourceplan.h" #include "alshaderinputs.h" +#include "alsky.h" #include "alspotshadowcache.h" #include "altransienttargetpool.h" #include "altransienttargets.h" @@ -626,10 +627,11 @@ class LLPipeline // cutout's frame, which leaves the background empty. bool drawsSky() const; - // Clear the bound G-buffer's attachment the stars add into to black, the emissive with the emissive buffer and the - // albedo without it: the sky's composite adds it to every pixel the world leaves at the far plane, where nothing - // else writes it. Right after the G-buffer's own clear. - void clearStarAttachment(); + // Begin a view's sky, right after its G-buffer's clear: take what the view draws of it, once (ALSky::beginView), + // and where that has the stars, clear the bound G-buffer's attachment they add into to black + // (ALSkyLayers::starAttachment). The sky's composite adds it to every pixel the world leaves at the far plane, where + // nothing else writes it, in that view alone. + void beginSkyView(); // apply water haze based on contents of color and depth buffer // should be called just before rendering pre-water alpha objects @@ -826,8 +828,6 @@ class LLPipeline enum LLRenderTypeMask { // Following are pool types (some are also object types) - RENDER_TYPE_SKY = LLDrawPool::POOL_SKY, - RENDER_TYPE_WL_SKY = LLDrawPool::POOL_WL_SKY, RENDER_TYPE_TERRAIN = LLDrawPool::POOL_TERRAIN, RENDER_TYPE_SIMPLE = LLDrawPool::POOL_SIMPLE, RENDER_TYPE_GRASS = LLDrawPool::POOL_GRASS, @@ -916,6 +916,9 @@ class LLPipeline RENDER_TYPE_PARTICLES, RENDER_TYPE_CLOUDS, RENDER_TYPE_HUD_PARTICLES, + // The sky's, drawn by ALSky rather than a pool: the sky, and WindLight's sky beside an HDRI + RENDER_TYPE_SKY, + RENDER_TYPE_WL_SKY, NUM_RENDER_TYPES, END_RENDER_TYPES = NUM_RENDER_TYPES }; @@ -1183,7 +1186,6 @@ class LLPipeline S32 mScenePixelY = 0; bool mScenePixelPending = false; - LLCullResult mSky; LLCullResult mReflectedObjects; LLCullResult mRefractedObjects; @@ -1277,6 +1279,9 @@ class LLPipeline // The water's layout around the main camera and its map of cell heights, laid out at the start of each main frame. ALWaterSurface mWaterSurface; + // The sky: its bodies, textures, stars and environment cube, taken at the start of each main frame. + ALSky mSky; + // The farthest depth under each texel of the main view's depth, built where a GPU cull asks for it. ALDepthPyramid mDepthPyramid; @@ -1523,7 +1528,6 @@ class LLPipeline std::map mTreePools; LLDrawPoolAlpha* mAlphaPoolPreWater = nullptr; LLDrawPoolAlpha* mAlphaPoolPostWater = nullptr; - LLDrawPool* mSkyPool = nullptr; LLDrawPool* mTerrainPool = nullptr; LLDrawPool* mWaterPool = nullptr; LLRenderPass* mSimplePool = nullptr; @@ -1534,7 +1538,6 @@ class LLPipeline LLDrawPool* mGlowPool = nullptr; LLDrawPool* mBumpPool = nullptr; LLDrawPool* mMaterialsPool = nullptr; - LLDrawPool* mWLSkyPool = nullptr; LLDrawPool* mPBROpaquePool = nullptr; LLDrawPool* mPBRAlphaMaskPool = nullptr; LLDrawPool* mWaterExclusionPool = nullptr; diff --git a/indra/newview/skins/default/xui/pt/settings_comments.xml b/indra/newview/skins/default/xui/pt/settings_comments.xml index ca6c31bab9f..1e02aaa847e 100644 --- a/indra/newview/skins/default/xui/pt/settings_comments.xml +++ b/indra/newview/skins/default/xui/pt/settings_comments.xml @@ -2423,12 +2423,8 @@ Nome do arquivo de configuração de cor do skin do cliente (por instalação). SkipBenchmark Se verdadeiro, desativa a execução do benchmark de GPU na inicialização (padrão para classe 1). - SkyAmbientScale - Controla a intensidade da luz ambiente, ou não direcional, do sol e da lua (fração ou múltiplo do nível de ambiente padrão). SkyMoonDefaultPosition Posição padrão do sol no céu (direção em coordenadas do mundo). - SkyNightColorShift - Controla a cor do luar (cor base aplicada à lua como fonte de luz). SkyOverrideSimSunPosition Substituir a posição do sol do simulador pela posição local definida. SkySunDefaultPosition From 50daef6661a3b1858efbe03df841c88bef5d57ca Mon Sep 17 00:00:00 2001 From: Rye Date: Wed, 21 Oct 2026 09:00:24 -0400 Subject: [PATCH 02/11] Bake the probes-off environment cube from windlightDome on the GPU Where reflection probes are off (the low feature class, every Intel legacy session, RenderReflectionsEnabled off) the shiny and probe-less reflections read an environment cube. The CPU built it through LLAtmospherics a face a frame. Now ALSky bakes it from windlightDome whenever the sky changes, at most every quarter second as before, then mips it (D2): - ALSkyCube (alrender): each GL face's directions in the agent's frame the readers look it up in, and their turn into the dome's frame (toDome, ALFarPlane::skyFrame); the cube's storage (RGBA8, 64 a face as before, every level, LEVELS worked from SIZE), counted whole as an image's (LLImageGLMemory::OTHER_TEXTURE), every face and level mid grey as the readers take it (NEUTRAL) until its first bake; and the bake. The six faces draw side by side in one pass over a strip, then are copied into their faces and mipped (LLImageGL::generateMipmaps), so nothing is bound over the program's own texture units between them. - deferred/skyCubeF.glsl: windlightDome along each texel's direction, halved as the haze takes it, the scale the readers double (applyLegacyEnv). Built only without probes (ALShaderInputs). - ALRenderResourcePlan::Id::EnvironmentCube: a persistent texture of the pipeline's own, wanted only without probes. The reconcile makes and frees it; made neutral, it is bound from the frame it is made in, wherever the plan wants it, and where it is not the unit is let go. RenderWater no longer decides whether it exists. - The bake runs when windlightDome's inputs change: its Environment block terms, its loose uniforms, and the rainbow and halo maps as far as they have loaded. A term counts as changed only past ALSkyCube::TERM_TOLERANCE, 0.0005, of the larger of its two values (termsAlike), the comparison the CPU cube made of its atmospherics (approximatelyEqual): a sky that has drifted less differs from its bake by under a step of its eight bits. The fog colour the tracker's beacons read follows the same change, with probes on too, where it was never updated. - Both run from ALSky::followEnvironment, once LLEnvironment::update has packed the Environment block the main view takes, whether or not the sky is drawn: the cube lights the reflections, and the fog colour the beacons. A bake that cannot run, its program not built or its strip not made, leaves the sky untaken, and is tried again at the same interval until one does. LLAtmospherics keeps the fog colour alone: the sky texture's path, LLHaze and the atmospherics' comparisons go, and with them the members of AtmosphericsVars nothing reads (the haze below the clouds, the clouds' colours and density, the distance multiplier, the cloud shadow and the light transmittance). LLHeavenBody keeps its disk and its quad alone: the quads turn by the current sky's rotations, and nothing read its copies of the directions and turns. ALSkyDome, the dome's mesh, which nothing in the viewer draws, moves out of alrender into the sky tests' support (alskyraster_gl.h), for the raster sky they draw. alskycube_test pins each texel's direction against GL's face selection table, and holds termsAlike to the tolerance either side of it. alskycube_gl_test bakes with the viewer's program and samples every texel against windlightDome (0.56/255 at most, the rounding to eight bits), checks each face's last level against the mean of its first, checks that a cube never baked is complete and neutral at every level, read back and through the trilinear lookup the readers take, and that its storage is counted whole from its making to its deletion, and reports the bake against the old CPU cube, kept in the test. That cube differs on purpose. It held WindLight's colour along the opposite direction of each lookup (calcSkyColorInDir negates every axis), at a quarter saturation, and below -0.02 a dim fog-grey ground. Midday: above its horizon 14.6/255 on average (83 at most), below it 72.5/255 (115). Sunset: 17.9 (242, at the sun's glow) and 22.6 (139). Moonlit night: 2.2 (16) and 8.9 (19). Turning the mirror round changes little (15.7 for 14.6 at midday above the horizon), and full saturation with the haze in place of the ground leaves 13.4 above and 57.1 below: most of it is the old CPU model itself, which has no below-cloud blend, rainbow or halo, and dims the sun again by the haze's transmittance along the ray. Co-Authored-By: Claude Opus 5.5 --- indra/alrender/CMakeLists.txt | 22 +- indra/alrender/alrenderresourceplan.cpp | 12 +- indra/alrender/alrenderresourceplan.h | 1 + indra/alrender/alskycube.cpp | 174 ++++ indra/alrender/alskycube.h | 100 +++ indra/alrender/alskydome.cpp | 127 --- indra/alrender/alskydome.h | 63 -- .../tests/alrenderresourceplan_test.cpp | 27 +- indra/alrender/tests/alskycube_gl_test.cpp | 809 ++++++++++++++++++ indra/alrender/tests/alskycube_test.cpp | 165 ++++ indra/alrender/tests/alskyraster_gl.h | 102 ++- indra/llrender/llshadermgr.cpp | 1 + indra/llrender/llshadermgr.h | 1 + indra/newview/alsky.cpp | 325 ++++--- indra/newview/alsky.h | 120 +-- .../shaders/class1/deferred/skyCubeF.glsl | 48 ++ indra/newview/lllegacyatmospherics.cpp | 414 +-------- indra/newview/lllegacyatmospherics.h | 193 +---- indra/newview/llviewerdisplay.cpp | 6 +- indra/newview/llviewershadermgr.cpp | 17 + indra/newview/llviewershadermgr.h | 1 + indra/newview/pipeline.cpp | 6 + 22 files changed, 1703 insertions(+), 1031 deletions(-) create mode 100644 indra/alrender/alskycube.cpp create mode 100644 indra/alrender/alskycube.h delete mode 100644 indra/alrender/alskydome.cpp delete mode 100644 indra/alrender/alskydome.h create mode 100644 indra/alrender/tests/alskycube_gl_test.cpp create mode 100644 indra/alrender/tests/alskycube_test.cpp create mode 100644 indra/newview/app_settings/shaders/class1/deferred/skyCubeF.glsl diff --git a/indra/alrender/CMakeLists.txt b/indra/alrender/CMakeLists.txt index 42e9fdfdefb..6d45462641a 100644 --- a/indra/alrender/CMakeLists.txt +++ b/indra/alrender/CMakeLists.txt @@ -5,10 +5,11 @@ # change, the far plane, the water's field, wave heights and stencil bits, # the depth pyramid, the light map's blur, the depth under the HUD, # which placed reflection probes swallow automatic ones, snapshot cutouts, -# whether a spot light's shadow map is drawn again, the sky dome's mesh and -# what the sky's other layers are worked out from along the view ray; and -# the pool that lends the frame's short-lived render targets by description. -# Whatever reads settings or the scene hands these their inputs as values. +# whether a spot light's shadow map is drawn again, what the sky's layers +# are worked out from along the view ray and the environment cube baked +# from the dome; and the pool that lends the frame's +# short-lived render targets by description. Whatever reads settings or the +# scene hands these their inputs as values. set( alrender_SOURCES @@ -21,7 +22,7 @@ set( alrenderinputrequests.cpp alrenderresourceplan.cpp alshaderinputs.cpp - alskydome.cpp + alskycube.cpp alskylayers.cpp alspotshadowcache.cpp alstarfield.cpp @@ -46,7 +47,7 @@ set( alrenderresourceinputs.h alrenderresourceplan.h alshaderinputs.h - alskydome.h + alskycube.h alskylayers.h alspotshadowcache.h alstarfield.h @@ -85,8 +86,8 @@ if(AL_BUILD_TESTS) # that ask for it again, the transient target pool's policy, what the programs are built from, the far plane # and the constants its shaders share, the water's field and the wave # heights a normal map's slopes integrate to, snapshot cutouts, the spot - # shadows' cache, how a display pass ends its frame, the star field and the - # sky's layers. + # shadows' cache, how a display pass ends its frame, the star field, the + # sky's layers and the environment cube's directions. al_add_test_binary( alrender_tests PROJECT alrender @@ -98,6 +99,7 @@ if(AL_BUILD_TESTS) tests/alrenderinputrequests_test.cpp tests/alrenderresourceplan_test.cpp tests/alshaderinputs_test.cpp + tests/alskycube_test.cpp tests/alskylayers_test.cpp tests/alspotshadowcache_test.cpp tests/alstarfield_test.cpp @@ -117,7 +119,8 @@ if(AL_BUILD_TESTS) # the light map's blur by one dispatch against its two fragment passes, # WindLight's dome along the view ray against the dome's mesh, the sky's # other layers along it against the programs that drew them and the whole - # sky's composite against the sky they drew, the stars + # sky's composite against the sky they drew, the environment cube against + # the dome it is baked from and the cube the CPU built, the stars # against the picture they left before their rework, FXAA on an RGB10_A2 # source, the reflection probes' taps in one pass over them against their # list, the transient target pool's GL objects, shared depth and poison, @@ -131,6 +134,7 @@ if(AL_BUILD_TESTS) tests/allightmapblur_gl_test.cpp tests/alondemandtarget_gl_test.cpp tests/alskycomposite_gl_test.cpp + tests/alskycube_gl_test.cpp tests/alskydome_gl_test.cpp tests/alskylayers_gl_test.cpp tests/alspotshadowcache_gl_test.cpp diff --git a/indra/alrender/alrenderresourceplan.cpp b/indra/alrender/alrenderresourceplan.cpp index 526dad45bab..bfc622d105e 100644 --- a/indra/alrender/alrenderresourceplan.cpp +++ b/indra/alrender/alrenderresourceplan.cpp @@ -69,10 +69,10 @@ namespace "shadow.spot0", "shadow.spot1", "smaa.edges", "smaa.blend", "ssr.scene", "main.light.blurred", "glow0", "glow1", "glow2", "downres", "preview", "bake", "brdf.lut", "exposure.meter", "exposure.histogram", "exposure", "exposure.last", "lens.flare.a", "lens.flare.b", "noise", "noise.true", "smaa.area", "smaa.search", - "smaa.sample", "tone.curve", + "smaa.sample", "tone.curve", "sky.cube", }; - static_assert(OWN_NAMES[(size_t)Id::ToneCurve - PACK_IDS] == "tone.curve", "a name for every id after the packs"); + static_assert(OWN_NAMES[(size_t)Id::EnvironmentCube - PACK_IDS] == "sky.cube", "a name for every id after the packs"); } // static @@ -345,6 +345,14 @@ ALRenderResourcePlan ALRenderResourcePlan::make(const ALRenderResourceInputs& in plan.at(Id::TrueNoise).mDesc = ALRenderTargetDesc{ .color = { GL_RGB16F } }; plan.at(Id::ToneCurve).mDesc = ALRenderTargetDesc{ .color = { GL_RGBA16 } }; + // Where there are no reflection probes, the shiny and probe-less reflections read the sky from a small cube the + // sky bakes, with every level. + if (!in.mProbes) + { + plan.at(Id::EnvironmentCube).mDesc = + ALRenderTargetDesc{ .color = { GL_RGBA8 }, .mips = ALRenderTargetDesc::Mips::Manual }; + } + // SMAA's area and search tables, and the debug image it can blend, for as long as SMAA is what antialiases, as its // edges and blend weights are. if (in.mFSAAType == 2) diff --git a/indra/alrender/alrenderresourceplan.h b/indra/alrender/alrenderresourceplan.h index 8a5e1d3f789..70aa74282ad 100644 --- a/indra/alrender/alrenderresourceplan.h +++ b/indra/alrender/alrenderresourceplan.h @@ -129,6 +129,7 @@ class ALRenderResourcePlan SMAASearch, SMAASample, ToneCurve, + EnvironmentCube, ///< the sky's cube for the shiny and probe-less reflections, without probes (ALSkyCube) COUNT }; diff --git a/indra/alrender/alskycube.cpp b/indra/alrender/alskycube.cpp new file mode 100644 index 00000000000..b0ef0e087a2 --- /dev/null +++ b/indra/alrender/alskycube.cpp @@ -0,0 +1,174 @@ +/** + * @file alskycube.cpp + * @brief WindLight's dome baked into a small cube, the environment map probe-less reflections read. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "alskycube.h" + +#include "alfarplane.h" +#include "llgl.h" +#include "llglheaders.h" +#include "llglslshader.h" +#include "llglstates.h" +#include "llimagegl.h" +#include "llrender.h" +#include "llrendertarget.h" +#include "llshadermgr.h" +#include "llvertexbuffer.h" + +#include +#include + +namespace ALSkyCube +{ + +namespace +{ + // GL's cube lookup (the GL specification's table of cube map face selection): for the major axis' face, s runs + // along sc and t along tc. Each face's right and up are those axes in the agent's frame. + const std::array FACES = { { + { LLVector3(1.f, 0.f, 0.f), LLVector3(0.f, 0.f, -1.f), LLVector3(0.f, -1.f, 0.f) }, // +x: sc = -z, tc = -y + { LLVector3(-1.f, 0.f, 0.f), LLVector3(0.f, 0.f, 1.f), LLVector3(0.f, -1.f, 0.f) }, // -x: sc = +z, tc = -y + { LLVector3(0.f, 1.f, 0.f), LLVector3(1.f, 0.f, 0.f), LLVector3(0.f, 0.f, 1.f) }, // +y: sc = +x, tc = +z + { LLVector3(0.f, -1.f, 0.f), LLVector3(1.f, 0.f, 0.f), LLVector3(0.f, 0.f, -1.f) }, // -y: sc = +x, tc = -z + { LLVector3(0.f, 0.f, 1.f), LLVector3(1.f, 0.f, 0.f), LLVector3(0.f, -1.f, 0.f) }, // +z: sc = +x, tc = -y + { LLVector3(0.f, 0.f, -1.f), LLVector3(-1.f, 0.f, 0.f), LLVector3(0.f, -1.f, 0.f) }, // -z: sc = -x, tc = -y + } }; +} + +Face face(U32 face) +{ + return FACES[face]; +} + +LLVector3 texelDirection(U32 face, U32 x, U32 y, U32 size) +{ + const Face& basis = FACES[face]; + const F32 s = (F32)(2 * x + 1) / (F32)size - 1.f; + const F32 t = (F32)(2 * y + 1) / (F32)size - 1.f; + LLVector3 dir = basis.mForward + basis.mRight * s + basis.mUp * t; + dir.normalize(); + return dir; +} + +LLVector3 toDome(const LLVector3& agent) +{ + return ALFarPlane::skyFrame(agent); +} + +bool termsAlike(std::span a, std::span b) +{ + if (a.size() != b.size()) + { + return false; + } + for (size_t i = 0; i < a.size(); ++i) + { + const F32 apart = fabsf(a[i] - b[i]); + if (!(apart < F_APPROXIMATELY_ZERO || apart < llmax(fabsf(a[i]), fabsf(b[i])) * TERM_TOLERANCE)) + { + return false; + } + } + return true; +} + +U32 allocate() +{ + U32 name = 0; + LLImageGL::generateTextures(1, &name); + if (!name) + { + return 0; + } + + // Allocated whole, every face and level at once: glTexStorage2D takes the cube's own target. + gGL.getTextureSlot(0)->bindManual(ALTextureSlot::TT_CUBE_MAP, name); + glTexStorage2D(GL_TEXTURE_CUBE_MAP, LEVELS, GL_RGBA8, SIZE, SIZE); + // Recorded once for the whole cube: the count of 6 covers its faces, each with every level. + LLImageGLMemory::alloc_tex_image(SIZE, SIZE, GL_RGBA8, 6, LEVELS, LLImageGLMemory::OTHER_TEXTURE); + + // Every face and level neutral, each written whole, so the cube holds nothing undefined before its first bake. + std::vector texels(size_t(SIZE) * SIZE * 4); + for (size_t i = 0; i < texels.size(); i += 4) + { + texels[i + 0] = NEUTRAL; + texels[i + 1] = NEUTRAL; + texels[i + 2] = NEUTRAL; + texels[i + 3] = 255; + } + for (U32 level = 0; level < LEVELS; ++level) + { + const S32 size = (S32)(SIZE >> level); + for (U32 f = 0; f < 6; ++f) + { + LLImageGL::setManualSubImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + f, (S32)level, size, size, GL_RGBA, + GL_UNSIGNED_BYTE, texels.data()); + } + } + gGL.getTextureSlot(0)->unbind(); + stop_glerror(); + return name; +} + +void bake(LLGLSLShader& program, U32 cube, LLRenderTarget& scratch, LLVertexBuffer& triangle) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; + LL_PROFILE_GPU_ZONE("sky cube"); + + // Every face's directions, in the dome's frame the program works in, three to a face. + std::array directions; + for (U32 f = 0; f < 6; ++f) + { + directions[f * 3 + 0] = toDome(FACES[f].mForward); + directions[f * 3 + 1] = toDome(FACES[f].mRight); + directions[f * 3 + 2] = toDome(FACES[f].mUp); + } + + LLGLDisable blend(GL_BLEND); + LLGLDisable cull(GL_CULL_FACE); + LLGLDisable scissor(GL_SCISSOR_TEST); + LLGLDisable srgb(GL_FRAMEBUFFER_SRGB); + LLGLDepthTest depth(GL_FALSE, GL_FALSE); + + // The six faces side by side in one draw (the program picks the face from where the pixel lies across the strip), + // so nothing is bound between them over the units the program samples through. + scratch.bindTarget(); + program.uniform3fv(LLShaderMgr::SKY_CUBE_FACE, (U32)directions.size(), directions[0].mV); + triangle.setBuffer(); + triangle.drawArrays(LLRender::TRIANGLES, 0, 3); + + // Each face's square of the strip into the face, read from the target still bound. + gGL.getTextureSlot(0)->bindManual(ALTextureSlot::TT_CUBE_MAP, cube); + for (U32 f = 0; f < 6; ++f) + { + glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + f, 0, 0, 0, f * SIZE, 0, SIZE, SIZE); + } + LLImageGL::generateMipmaps(GL_TEXTURE_CUBE_MAP); + gGL.getTextureSlot(0)->unbind(); + scratch.flush(); + stop_glerror(); +} + +} // namespace ALSkyCube diff --git a/indra/alrender/alskycube.h b/indra/alrender/alskycube.h new file mode 100644 index 00000000000..9b88717db39 --- /dev/null +++ b/indra/alrender/alskycube.h @@ -0,0 +1,100 @@ +/** + * @file alskycube.h + * @brief WindLight's dome baked into a small cube, the environment map probe-less reflections read. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#pragma once + +#include "stdtypes.h" +#include "v3math.h" + +#include +#include + +class LLGLSLShader; +class LLRenderTarget; +class LLVertexBuffer; + +/// The environment cube the shiny and probe-less reflections read where reflection probes are off (environmentMap, as +/// LLPipeline::bindDeferredShader and the bump pool's shiny pass bind it): WindLight's dome along each texel's direction, +/// drawn on the GPU (deferred/skyCubeF.glsl) when the sky changes, then mipped. Its readers look it up in the agent's +/// frame (east, north, up), through env_mat, the eye's turn back into it. No viewer state, so the sky and the GL tests +/// bake it alike. +namespace ALSkyCube +{ + +/// A face's edge, in texels: a power of two, which halves to 1. +constexpr U32 SIZE = 64; +static_assert(std::has_single_bit(SIZE), "a face's edge halves level by level to 1"); + +/// Its levels, from SIZE down to 1. +constexpr U32 LEVELS = (U32)std::bit_width(SIZE); + +/// The width of the strip the six faces are drawn into side by side, in GL's face order, before each is copied into +/// its face. +constexpr U32 STRIP_WIDTH = 6 * SIZE; + +/// What each colour channel of every face and level holds until the cube is first baked, and for as long as the bake +/// cannot run: mid grey as the readers take it, who double what the cube holds (applyLegacyEnv). Its alpha is 1. +constexpr U8 NEUTRAL = 64; + +/// The directions GL's cube lookup selects face @a face (GL_TEXTURE_CUBE_MAP_POSITIVE_X + face) for, in the agent's +/// frame: the texel at s and t, each running -1 to 1 across the face from the edge of its first texel to the far edge +/// of its last, lies along mForward + s mRight + t mUp. +struct Face +{ + LLVector3 mForward; + LLVector3 mRight; + LLVector3 mUp; +}; +Face face(U32 face); + +/// The direction through the centre of texel (@a x, @a y) of face @a face, @a size a side, normalized, in the agent's +/// frame. +LLVector3 texelDirection(U32 face, U32 x, U32 y, U32 size = SIZE); + +/// A direction in the agent's frame (east, north, up) in the dome's (north, up, east): ALFarPlane::skyFrame, as +/// LLEnvironment::toLightNorm turns the light into it. +LLVector3 toDome(const LLVector3& agent); + +/// How far apart two values of a term the bake is worked from may lie, as a fraction of the larger, and still bake +/// alike: a sky that has drifted less than this from the one last baked differs from its bake by less than eight bits +/// keep, so a day cycle bakes again only once it has drifted further. +constexpr F32 TERM_TOLERANCE = 0.0005f; + +/// Whether the terms of two skies, @a a and @a b, bake alike: as many of each, and every pair within TERM_TOLERANCE of +/// the larger of the two, or within F_APPROXIMATELY_ZERO of each other. +bool termsAlike(std::span a, std::span b); + +/// A new cube: RGBA8, SIZE a face with every level, every face and level NEUTRAL, so that it reads as something +/// defined from the first draw it is bound for, its storage counted as a texture's. Returns its name, or 0 where it +/// could not be made; texture unit 0 is left with nothing bound. +U32 allocate(); + +/// Bakes @a program, bound and given what it reads, into @a cube: @a triangle, a screen triangle in clip space, drawn +/// once over @a scratch, an RGBA8 target STRIP_WIDTH by SIZE, with every face's directions in the dome's frame as the +/// program's skyCubeFace (forward, right and up for each face in turn), each face's square of the strip then copied +/// into its first level, and every other level made from it. Draws with no blend, depth test, scissor or culling, +/// leaves the bound target as it found it, and texture unit 0 with nothing bound. +void bake(LLGLSLShader& program, U32 cube, LLRenderTarget& scratch, LLVertexBuffer& triangle); + +} // namespace ALSkyCube diff --git a/indra/alrender/alskydome.cpp b/indra/alrender/alskydome.cpp deleted file mode 100644 index 413ca28edb8..00000000000 --- a/indra/alrender/alskydome.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/** - * @file alskydome.cpp - * @brief The WindLight sky dome's mesh, in the dome's frame. - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2007, Linden Research, Inc. - * - * Alchemy Viewer Source Code - * Copyright (C) 2026, Rye - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "alskydome.h" - -#include "llmath.h" -#include "v2math.h" -#include "v3math.h" - -#include - -namespace ALSkyDome -{ - -F32 ringAngle(U32 ring) -{ - // Calc: PI/8 * 1-((1-t^4)*(1-t^4)) { 0& vertices, - LLStrider& tex_coords, LLStrider& indices) -{ - llassert(end_stack <= STACKS); - - for (U32 i = begin_stack + 1; i <= end_stack + 1; ++i) - { - const F32 phi0 = ringAngle(i); - - for (U32 j = 0; j < SLICES; ++j) - { - const F32 theta = F_TWO_PI * (float(j) / float(SLICES)); - - // standard transformation from spherical to - // rectangular coordinates - const F32 x0 = sin(phi0) * cos(theta); - const F32 y0 = cos(phi0); - const F32 z0 = sin(phi0) * sin(theta); - - if (i == STACKS - 2) - { - *vertices++ = LLVector3(x0 * radius, y0 * radius - 1024.f * 2.f, z0 * radius); - } - else if (i == STACKS - 1) - { - *vertices++ = LLVector3(0, y0 * radius - 1024.f * 2.f, 0); - } - else - { - *vertices++ = LLVector3(x0 * radius, y0 * radius, z0 * radius); - } - - // generate planar uv coordinates - // note: x and z are transposed in order for things to animate - // correctly in the global coordinate system where +x is east and - // +y is north - *tex_coords++ = LLVector2((-z0 + 1.f) / 2.f, (-x0 + 1.f) / 2.f); - } - } - - //build triangle strip... - *indices++ = 0; - - S32 k = 0; - for (U32 i = 1; i <= end_stack - begin_stack; ++i) - { - *indices++ = (U16)(i * SLICES + (U32)k); - - k = (k + 1) % (S32)SLICES; - for (U32 j = 0; j < SLICES; ++j) - { - *indices++ = (U16)((i - 1) * SLICES + (U32)k); - *indices++ = (U16)(i * SLICES + (U32)k); - - k = (k + 1) % (S32)SLICES; - } - - if ((--k) < 0) - { - k = (S32)SLICES - 1; - } - - *indices++ = (U16)(i * SLICES + (U32)k); - } -} - -} // namespace ALSkyDome diff --git a/indra/alrender/alskydome.h b/indra/alrender/alskydome.h deleted file mode 100644 index 3b5d41722d4..00000000000 --- a/indra/alrender/alskydome.h +++ /dev/null @@ -1,63 +0,0 @@ -/** - * @file alskydome.h - * @brief The WindLight sky dome's mesh, in the dome's frame. - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2007, Linden Research, Inc. - * - * Alchemy Viewer Source Code - * Copyright (C) 2026, Rye - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#pragma once - -#include "llstrider.h" -#include "stdtypes.h" - -class LLVector2; -class LLVector3; - -/// The WindLight sky dome's mesh, in the dome's frame (north, up, east): rings of a sphere about the origin, from the -/// zenith down past the camera's level, which the sky program shaded from the camera's point inside the sphere -/// (windlight/windlightDomeF.glsl's windlightDomeAt). The last rings are a skirt: the third from last lowered 2048 m, -/// the next collapsed onto the axis below it, and the last two back out on the sphere, so the strips overlap below the -/// camera's level and the later band drawn covers the earlier. Triangle strips of SLICES vertices a ring. The sky's -/// layers were drawn over it before its composite worked them out along the view ray; the GL tests draw it, with the -/// programs that drew them (tests/skyraster), beside the composite. -namespace ALSkyDome -{ - /// The stacks the dome has, and the vertices a ring has. Any fewer and the facets show. - constexpr U32 STACKS = 18; - constexpr U32 SLICES = 2 * STACKS; - - /// The vertices and indices buildStrips writes for @a stacks stacks. - constexpr U32 vertexCount(U32 stacks) { return SLICES * (stacks + 1); } - constexpr U32 indexCount(U32 stacks) { return 1 + stacks * (2 + 2 * SLICES); } - - /// The polar angle of ring @a ring, from the zenith: PI/8 (1 - (1 - t^4)^2) at t = ring / STACKS, which gathers the - /// rings toward the horizon. - F32 ringAngle(U32 ring); - - /// Stacks [@a begin_stack, @a end_stack) of a dome of @a radius: rings begin_stack + 1 to end_stack + 1, their - /// positions, their planar texture coordinates ((1 - east) / 2, (1 - north) / 2) and one triangle strip over them. - void buildStrips(U32 begin_stack, U32 end_stack, F32 radius, LLStrider& vertices, - LLStrider& tex_coords, LLStrider& indices); -} // namespace ALSkyDome diff --git a/indra/alrender/tests/alrenderresourceplan_test.cpp b/indra/alrender/tests/alrenderresourceplan_test.cpp index 1623a48bf46..ec711805274 100644 --- a/indra/alrender/tests/alrenderresourceplan_test.cpp +++ b/indra/alrender/tests/alrenderresourceplan_test.cpp @@ -298,7 +298,8 @@ namespace { "RenderReflectionProbeResolution", no_change, [](Inputs& in) { in.mProbeResolution = 256; }, [](const Plan& before) { return wanted(before, Plan::idsOf(Pack::Probe)); } }, { "RenderReflectionsEnabled", no_change, [](Inputs& in) { in.mProbes = false; }, - [](const Plan& before) { return wanted(before, Plan::idsOf(Pack::Probe) | Plan::idsOf(Pack::Hero)); } }, + [](const Plan& before) + { return wanted(before, Plan::idsOf(Pack::Probe) | Plan::idsOf(Pack::Hero)) | of({ Id::EnvironmentCube }); } }, { "RenderScreenSpaceReflections", no_change, [](Inputs& in) { in.mSSR = true; }, [](const Plan&) { return of({ Id::SSRScene }); } }, { "RenderScreenSpaceReflections without probes", [](Inputs& in) { in.mProbes = false; }, @@ -1442,6 +1443,7 @@ namespace CHECK(Plan::name(Id::SMAAEdges) == "smaa.edges"); CHECK(Plan::name(Id::LensFlareB) == "lens.flare.b"); CHECK(Plan::name(Id::ToneCurve) == "tone.curve"); + CHECK(Plan::name(Id::EnvironmentCube) == "sky.cube"); std::set seen; for (size_t i = 0; i < Plan::ID_COUNT; ++i) @@ -1452,4 +1454,27 @@ namespace CHECK(seen.insert(name).second); } } + + TEST_CASE("alrenderresourceplan: the sky's environment cube is wanted only without probes", "[alrenderresourceplan]") + { + CHECK_FALSE(Plan::make(defaults()).wants(Id::EnvironmentCube)); + + // Probes off, whatever else the inputs say: the low feature class, every Intel legacy session and the setting. + for (const bool hdr : { true, false }) + { + INFO("HDR " << hdr); + Inputs in = defaults(); + in.mProbes = false; + in.mHDR = hdr; + const Plan plan = Plan::make(in); + REQUIRE(plan.wants(Id::EnvironmentCube)); + CHECK(plan.desc(Id::EnvironmentCube)->color[0] == GL_RGBA8); + CHECK(plan.desc(Id::EnvironmentCube)->mips == ALRenderTargetDesc::Mips::Manual); + } + + // A texture of the pipeline's own, which the reconcile keeps rather than the transient pool lending it. + CHECK(Plan::isTexture(Id::EnvironmentCube)); + CHECK_FALSE(Plan::isTransient(Id::EnvironmentCube)); + CHECK_FALSE(Plan::followsRenderSize(Id::EnvironmentCube)); + } } diff --git a/indra/alrender/tests/alskycube_gl_test.cpp b/indra/alrender/tests/alskycube_gl_test.cpp new file mode 100644 index 00000000000..2977a8609f4 --- /dev/null +++ b/indra/alrender/tests/alskycube_gl_test.cpp @@ -0,0 +1,809 @@ +/** + * @file alskycube_gl_test.cpp + * @brief The environment cube probe-less reflections read, on the hidden window: baked by ALSkyCube from the viewer's + * own program (deferred/skyCubeF.glsl), against windlightDome along each texel's direction, with every level + * made; neutral at every level before its first bake; its storage counted whole while it lives; and against the + * cube the CPU built before it (LLVOSky's createSkyTexture through LLAtmospherics, kept here), whose difference + * it reports. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "alskycube.h" + +#include "alskyraster_gl.h" + +#include "llcubemap.h" +#include "v3color.h" +#include "v3colorutil.h" +#include "v4color.h" +#include "v4coloru.h" + +#include + +#include + +using namespace al_sky_test; + +namespace +{ + // The equirectangular view of every direction the old and new cubes are compared over: bearing across, clockwise + // from north, and elevation up, from the nadir to the zenith. + constexpr S32 PANORAMA_W = 256; + constexpr S32 PANORAMA_H = 128; + + // What eight bits keep of a value: half a step either way. + constexpr F32 HALF_STEP = 0.5f / 255.f; + + // The cube's first level against what windlightDome gives: the texels are rounded to eight bits, and the bake and + // the lookup work a texel's direction out each their own way, which near the sun's glow, where the dome is steep, + // tips a few texels across a rounding edge (measured: 0.56/255 at most, 0.25/255 on average, every sky). + constexpr F32 BAKED = 1.5f * HALF_STEP; + + // A face's coarsest level against the mean of its first: each level rounded to eight bits as it is made. + constexpr F32 MIPPED = 4.f / 255.f; + + // The pass that reads a cube along each pixel's direction (test_dirs, in the agent's frame) at test_lod + // (test_mode 0), or works out what it should hold there (test_mode 1): windlightDome halved, in [0, 1], taking the + // direction into the dome's frame (north, up, east) through test_to_dome, ALFarPlane::skyFrame as a matrix. + const char* kLookupVertex = R"(in vec3 position; + +void main() +{ + gl_Position = vec4(position.xy, 0.0, 1.0); +} +)"; + + const char* kLookupFragment = R"(/*[EXTRA_CODE_HERE]*/ + +out vec4 frag_color; + +uniform int test_mode; +uniform float test_lod; +uniform samplerCube test_cube; +uniform sampler2D test_dirs; +uniform mat3 test_to_dome; + +vec3 windlightDome(vec3 dir); + +void main() +{ + vec3 dir = texelFetch(test_dirs, ivec2(gl_FragCoord.xy), 0).xyz; + if (test_mode == 0) + { + frag_color = vec4(textureLod(test_cube, dir, test_lod).rgb, 1.0); + } + else + { + frag_color = vec4(clamp(windlightDome(test_to_dome * dir) * 0.5, vec3(0.0), vec3(1.0)), 1.0); + } +} +)"; + + // The cube the CPU built before the bake, a face a frame, as LLVOSky::createSkyTexture made it through + // LLAtmospherics::calcSkyColorInDir's shiny path, and LLAtmospherics::updateFog's colour, which that path took + // below the horizon. Kept here, in its arithmetic, so the bake's difference from it can be measured. + namespace legacy + { + // LLFastLn: the table the fog's gamma took its powers from. + class FastLn + { + public: + FastLn() + { + mTable[0] = 0; + for (S32 i = 1; i < 257; i++) + { + mTable[i] = log((F32)i); + } + } + + F32 ln(F32 x) const + { + const F32 OO_255 = 0.003921568627450980392156862745098f; + const F32 LN_255 = 5.5412635451584261462455391880218f; + + if (x < OO_255) + { + return log(x); + } + else if (x < 1) + { + x *= 255.f; + S32 index = llfloor(x); + F32 t = x - index; + F32 low = mTable[index]; + F32 high = mTable[index + 1]; + return low + t * (high - low) - LN_255; + } + else if (x <= 255) + { + S32 index = llfloor(x); + F32 t = x - index; + F32 low = mTable[index]; + F32 high = mTable[index + 1]; + return low + t * (high - low); + } + return log(x); + } + + F32 pow(F32 x, F32 y) const { return (F32)ll_fast_exp(y * ln(x)); } + + private: + F32 mTable[257]; + }; + + // AtmosphericsVars, as LLVOSky::cacheEnvironment took them from the sky. + struct Vars + { + LLColor3 blue_density; + LLColor3 blue_horizon; + F32 haze_density = 0.f; + F32 haze_horizon = 0.f; + F32 density_multiplier = 0.f; + F32 max_y = 0.f; + LLVector4 sun_norm; + LLColor3 sunlight; + LLColor3 ambient; + LLColor3 glow; + F32 cloud_shadow = 0.f; + F32 dome_radius = 0.f; + F32 dome_offset = 0.f; + LLColor3 light_atten; + LLColor3 total_density; + }; + + Vars vars(const Sky& sky) + { + Vars v; + v.blue_density = LLColor3(sky.mBlueDensity.mV); + v.blue_horizon = LLColor3(sky.mBlueHorizon.mV); + v.haze_density = sky.mHazeDensity; + v.haze_horizon = sky.mHazeHorizon; + v.density_multiplier = sky.mDensityMultiplier; + v.max_y = sky.mMaxY; + const LLVector3 light = lightNorm(sky); + v.sun_norm = LLVector4(light.mV[0], light.mV[1], light.mV[2], 0.f); + v.sunlight = LLColor3(sky.mLight.mV); + v.ambient = LLColor3(sky.mAmbient.mV); + v.glow = LLColor3(sky.mGlow.mV); + v.cloud_shadow = sky.mCloudShadow; + v.dome_radius = DOME_RADIUS; + v.dome_offset = CAMERA_HEIGHT / DOME_RADIUS; + // LLSettingsSky::getLightAttenuation(max_y) and getTotalDensity. + v.light_atten = (v.blue_density + smear(v.haze_density * 0.25f)) * v.density_multiplier * v.max_y; + v.total_density = v.blue_density + smear(v.haze_density); + return v; + } + + // LLAtmospherics::calcSkyColorWLVert's haze colour above the cloud, for Pn in WindLight's frame. + LLColor3 hazeColor(const Vars& v, LLVector3 Pn) + { + // project the direction ray onto the sky dome. + F32 phi = acos(Pn[1]); + F32 sinA = sin(F_PI - phi); + if (fabsf(sinA) < 0.01f) + { //avoid division by zero + sinA = 0.01f; + } + + F32 Plen = v.dome_radius * sin(F_PI + phi + asin(v.dome_offset * sinA)) / sinA; + + Pn *= Plen; + + // Set altitude + if (Pn[1] > 0.f) + { + Pn *= (v.max_y / Pn[1]); + } + else + { + Pn *= (-32000.f / Pn[1]); + } + + Plen = Pn.length(); + Pn /= Plen; + + LLColor3 sunlight = v.sunlight; + LLColor3 ambient = v.ambient; + LLColor3 glow = v.glow; + + LLColor3 light_atten = v.light_atten; + // LLSettingsSky::getLightTransmittanceFast. + LLColor3 light_transmittance = componentExp(v.total_density * -(v.density_multiplier * Plen)); + + LLColor3 temp2(0.f, 0.f, 0.f); + LLColor3 temp1 = v.total_density; + + LLColor3 blue_weight = componentDiv(v.blue_density, temp1); + LLColor3 blue_factor = v.blue_horizon * blue_weight; + LLColor3 haze_weight = componentDiv(smear(v.haze_density), temp1); + LLColor3 haze_factor = v.haze_horizon * haze_weight; + + temp2.mV[1] = llmax(F_APPROXIMATELY_ZERO, llmax(0.f, Pn[1]) * 1.0f + v.sun_norm.mV[1]); + temp2.mV[1] = 1.f / temp2.mV[1]; + componentMultBy(sunlight, componentExp((light_atten * -1.f) * temp2.mV[1])); + componentMultBy(sunlight, light_transmittance); + + temp2.mV[2] = Plen * v.density_multiplier; + temp1 = componentExp((temp1 * -1.f) * temp2.mV[2]); + + temp2.mV[0] = Pn * LLVector3(v.sun_norm); + temp2.mV[0] = 1.f - temp2.mV[0]; + temp2.mV[0] = llmax(temp2.mV[0], .001f); + temp2.mV[0] *= glow.mV[0]; + temp2.mV[0] = pow(temp2.mV[0], glow.mV[2]); + temp2.mV[0] += .25f; + + LLColor3 haze = (blue_factor * (sunlight + ambient) + componentMult(haze_factor, sunlight * temp2.mV[0] + ambient)); + componentMultBy(haze, LLColor3::white - temp1); + return haze; + } + + // WindLight's frame from a direction in the agent's, as calcSkyColorInDir took it: every axis negated. + LLVector3 windlightFrame(const LLVector3& dir, bool mirrored) + { + const LLVector3 dome(dir[1], dir[2], dir[0]); + return mirrored ? -dome : dome; + } + + // LLAtmospherics::updateFog's colour, toward the light @a tosun_in (the agent's frame) a little above the + // horizon, taking calcSkyColorInDir's plain path, whose LLSettingsSky::gammaCorrect left the input doubled and + // clamped. + LLColor3 fogColor(const Vars& v, const LLVector3& tosun_in) + { + static const FastLn fast_ln; + LLVector3 tosun = tosun_in; + const F32 tosun_z = tosun.mV[VZ]; + tosun.mV[VZ] = 0.f; + tosun.normalize(); + LLVector3 perp_tosun(-tosun.mV[VY], tosun.mV[VX], 0.f); + LLVector3 tosun_45 = tosun + perp_tosun; + tosun_45.normalize(); + + const F32 delta = 0.06f; + tosun.mV[VZ] = delta; + perp_tosun.mV[VZ] = delta; + tosun_45.mV[VZ] = delta; + tosun.normalize(); + perp_tosun.normalize(); + tosun_45.normalize(); + + LLColor3 sum; + for (const LLVector3& dir : { tosun, perp_tosun, tosun_45 }) + { + sum += componentSaturate(hazeColor(v, windlightFrame(dir, true)) * 4.f); + } + const F32 m = color_max(sum); + LLColor3 sky_fog_color = m > 1.f ? sum * (1.f / m) : sum; + + const F32 full_off = -0.25f; + const F32 full_on = 0.00f; + F32 on = (tosun_z - full_off) / (full_on - full_off); + on = llclamp(on, 0.01f, 1.f); + sky_fog_color *= 0.5f * on; + + for (S32 i = 0; i < 3; i++) + { + sky_fog_color.mV[i] = llmax(0.0001f, sky_fog_color.mV[i]); + } + const F32 gamma_inv = 1.f / 1.2f; + for (S32 i = 0; i < 3; ++i) + { + sky_fog_color.mV[i] = fast_ln.pow(sky_fog_color.mV[i], gamma_inv); + } + return sky_fog_color; + } + + // calcSkyColorInDir's shiny path for @a dir in the agent's frame, the fog colour below the horizon; or with + // @a mirrored false, the sky along dir itself, and with @a desaturated false, its haze colour as it is. + LLColor4 shiny(const Vars& v, const LLColor3& fog, const LLVector3& dir, bool mirrored, bool desaturated) + { + const F32 sky_saturation = 0.25f; + const F32 land_saturation = 0.1f; + + if (desaturated && dir.mV[VZ] < -0.02f) + { + LLColor4 col; + LLColor3 desat_fog = fog; + F32 brightness = desat_fog.brightness(); + if (brightness < 0.15f) + { + brightness = 0.15f; + desat_fog = smear(0.15f); + } + F32 greyscale_sat = brightness * (1.0f - land_saturation); + desat_fog = desat_fog * land_saturation + smear(greyscale_sat); + col = LLColor4(desat_fog * 0.5f, 0.f); + float x = 1.0f - fabsf(-0.1f - dir.mV[VZ]); + x *= x; + col.mV[0] *= x * x; + col.mV[1] *= powf(x, 2.5f); + col.mV[2] *= x * x * x; + return col; + } + + const LLColor3 haze = hazeColor(v, windlightFrame(dir, mirrored)); + if (!desaturated) + { + return LLColor4(haze, 0.f); + } + const F32 brightness = haze.brightness(); + const F32 greyscale_sat = brightness * (1.0f - sky_saturation); + return LLColor4(haze * sky_saturation + smear(greyscale_sat), 0.0f); + } + + // The cube as LLVOSky::initCubeMap made it: each side's texels from LLVOSky::initSkyTextureDirs' directions, + // row x and column y, eight bits, through LLCubeMap's flips onto GL's faces. + LLPointer cube(const Sky& sky, bool mirrored, bool desaturated) + { + constexpr S32 RES = 64; + const Vars v = vars(sky); + // The light in the agent's frame, as getClampedLightDirection gives it to the fog. + LLVector3 tosun(v.sun_norm.mV[2], v.sun_norm.mV[0], v.sun_norm.mV[1]); + if (tosun.mV[VZ] < -0.1f) + { + tosun.mV[VZ] = -0.1f; + } + const LLColor3 fog = fogColor(v, tosun); + + std::vector> images; + for (S32 side = 0; side < 6; ++side) + { + LLPointer raw = new LLImageRaw(RES, RES, 4); + U8* data = raw->getData(); + F32 coeff[3] = { 0, 0, 0 }; + const S32 curr_coef = side >> 1; + const S32 side_dir = (((side & 1) << 1) - 1); + const S32 x_coef = (curr_coef + 1) % 3; + const S32 y_coef = (x_coef + 1) % 3; + coeff[curr_coef] = (F32)side_dir; + for (S32 x = 0; x < RES; ++x) + { + for (S32 y = 0; y < RES; ++y) + { + coeff[x_coef] = F32((x << 1) + 1) / RES - 1.f; + coeff[y_coef] = F32((y << 1) + 1) / RES - 1.f; + LLVector3 dir(coeff[0], coeff[1], coeff[2]); + dir.normalize(); + const LLColor4U texel = shiny(v, fog, dir, mirrored, desaturated); + std::memcpy(data + (x * RES + y) * 4, texel.mV, 4); + } + } + images.push_back(raw); + } + LLPointer cube = new LLCubeMap(); + cube->init(images); + gGL.getTextureSlot(0)->unbind(); + return cube; + } + } // namespace legacy + + // Absolute differences between two pictures over the pixels @a where keeps: the largest and the mean channel's. + struct Difference + { + F32 mMost = 0.f; + F64 mSum = 0.0; + S32 mCount = 0; + + F32 mean() const { return mCount ? (F32)(mSum / mCount) : 0.f; } + std::string summary() const + { + std::ostringstream out; + out.precision(3); + out << std::fixed << mMost * 255.f << "/255 at most, " << mean() * 255.f << "/255 on average"; + return out.str(); + } + }; + + template + Difference compare(const std::vector& a, const std::vector& b, Keep&& keep) + { + Difference d; + for (size_t px = 0; px * 4 < a.size(); ++px) + { + if (!keep(px)) + { + continue; + } + for (S32 c = 0; c < 3; ++c) + { + const F32 delta = fabsf(a[px * 4 + c] - b[px * 4 + c]); + d.mMost = llmax(d.mMost, delta); + d.mSum += delta; + ++d.mCount; + } + } + return d; + } + + struct alskycube_gl_data + { + alskycube_gl_data() + : mShaders(mTree.prefix()) + { + } + + ~alskycube_gl_data() + { + LLGLSLShader::unbind(); + mBake.unload(); + mLookup.unload(); + mShaders.clearShaderObjects(); + mStrip.release(); + mPicture.release(); + mTriangle = nullptr; + mRainbow = nullptr; + mHalo = nullptr; + for (U32 texture : { mCube, mStripDirs, mPanoramaDirs }) + { + if (texture) + { + LLImageGL::deleteTextures(1, &texture); + } + } + if (mEnvironment) + { + glDeleteBuffers(1, &mEnvironment); + } + } + + // The viewer's bake program and the test's lookup, the strip the bake draws into, the pictures, and each + // pixel's direction for the strip (every texel's centre, face by face) and for the panorama. + void setUp() + { + mTree.populate(); + REQUIRE(mTree.write("skytest/skyCubeLookupV.glsl", kLookupVertex)); + REQUIRE(mTree.write("skytest/skyCubeLookupF.glsl", kLookupFragment)); + loadSharedObjects(mShaders); + + buildProgram(mBake, "Sky Environment Cube Program", + { { "deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER }, + { "deferred/skyCubeF.glsl", GL_FRAGMENT_SHADER }, + { "windlight/windlightDomeF.glsl", GL_FRAGMENT_SHADER } }, + false); + buildProgram(mLookup, "The environment cube along each pixel's direction (test)", + { { "skytest/skyCubeLookupV.glsl", GL_VERTEX_SHADER }, + { "skytest/skyCubeLookupF.glsl", GL_FRAGMENT_SHADER }, + { "windlight/windlightDomeF.glsl", GL_FRAGMENT_SHADER } }, + false); + + REQUIRE(mStrip.allocate(ALSkyCube::STRIP_WIDTH, ALSkyCube::SIZE, GL_RGBA8)); + REQUIRE(mPicture.allocate(llmax((S32)ALSkyCube::STRIP_WIDTH, PANORAMA_W), llmax((S32)ALSkyCube::SIZE, PANORAMA_H), + GL_RGBA32F)); + mTriangle = screenTriangle(); + mRainbow = gradient(1); + mHalo = gradient(4); + glGenBuffers(1, &mEnvironment); + + std::vector strip(size_t(ALSkyCube::STRIP_WIDTH) * ALSkyCube::SIZE); + for (U32 face = 0; face < 6; ++face) + { + for (U32 y = 0; y < ALSkyCube::SIZE; ++y) + { + for (U32 x = 0; x < ALSkyCube::SIZE; ++x) + { + strip[y * ALSkyCube::STRIP_WIDTH + face * ALSkyCube::SIZE + x] = + ALSkyCube::texelDirection(face, x, y); + } + } + } + mStripDirs = directions(strip, ALSkyCube::STRIP_WIDTH, ALSkyCube::SIZE); + + std::vector panorama(size_t(PANORAMA_W) * PANORAMA_H); + for (S32 y = 0; y < PANORAMA_H; ++y) + { + for (S32 x = 0; x < PANORAMA_W; ++x) + { + const F32 bearing = ((F32)x + 0.5f) / PANORAMA_W * 360.f; + const F32 elevation = ((F32)y + 0.5f) / PANORAMA_H * 180.f - 90.f; + panorama[y * PANORAMA_W + x] = agentDirection(elevation, bearing); + } + } + mPanoramaDirs = directions(panorama, PANORAMA_W, PANORAMA_H); + } + + // @a dirs as an RGB32F texture of @a width by @a height, read texel by texel. + static U32 directions(const std::vector& dirs, S32 width, S32 height) + { + U32 name = 0; + LLImageGL::generateTextures(1, &name); + gGL.getTextureSlot(0)->bindManual(ALTextureSlot::TT_TEXTURE, name); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, width, height, 0, GL_RGB, GL_FLOAT, dirs.data()); + gGL.getTextureSlot(0)->unbind(); + return name; + } + + // The cube baked from @a sky, as ALSky bakes it: the Environment block and the dome's uniforms on the bound + // program, ALSkyCube's bake over the strip. + void bake(const Sky& sky) + { + if (!mCube) + { + mCube = ALSkyCube::allocate(); + REQUIRE(mCube != 0); + } + bindEnvironment(mBake, mEnvironment, sky, IDENTITY); + mBake.bind(); + bindDome(mBake, sky, mRainbow, mHalo); + ALSkyCube::bake(mBake, mCube, mStrip, *mTriangle); + mBake.disableTexture(LLShaderMgr::RAINBOW_MAP); + mBake.disableTexture(LLShaderMgr::HALO_MAP); + mBake.unbind(); + } + + // The lookup over @a dirs (@a width by @a height): @a cube at @a lod, or with @a cube 0, what it should hold + // for @a sky. Read back. + std::vector lookup(const Sky& sky, U32 cube, F32 lod, U32 dirs, S32 width, S32 height) + { + mPicture.bindTarget(); + glViewport(0, 0, width, height); + bindEnvironment(mLookup, mEnvironment, sky, IDENTITY); + mLookup.bind(); + bindDome(mLookup, sky, mRainbow, mHalo); + constexpr S32 DIRS_UNIT = 10; + constexpr S32 CUBE_UNIT = 11; + gGL.getTextureSlot(DIRS_UNIT)->bindManual(ALTextureSlot::TT_TEXTURE, dirs); + if (cube) + { + gGL.getTextureSlot(CUBE_UNIT)->bindManual(ALTextureSlot::TT_CUBE_MAP, cube, + gGL.getSampler(ALSamplers::TrilinearClamp)); + } + const GLuint object = mLookup.mProgramObject; + glUniform1i(glGetUniformLocation(object, "test_dirs"), DIRS_UNIT); + glUniform1i(glGetUniformLocation(object, "test_cube"), CUBE_UNIT); + glUniform1i(glGetUniformLocation(object, "test_mode"), cube ? 0 : 1); + glUniform1f(glGetUniformLocation(object, "test_lod"), lod); + // ALFarPlane::skyFrame as a column-major matrix: its columns are the agent's axes in the dome's frame. + F32 to_dome[9] = {}; + for (S32 axis = 0; axis < 3; ++axis) + { + LLVector3 agent; + agent.mV[axis] = 1.f; + const LLVector3 dome = ALFarPlane::skyFrame(agent); + for (S32 row = 0; row < 3; ++row) + { + to_dome[axis * 3 + row] = dome.mV[row]; + } + } + glUniformMatrix3fv(glGetUniformLocation(object, "test_to_dome"), 1, GL_FALSE, to_dome); + { + LLGLDisable blend(GL_BLEND); + LLGLDepthTest no_depth(GL_FALSE, GL_FALSE); + mTriangle->setBuffer(); + mTriangle->drawArrays(LLRender::TRIANGLES, 0, 3); + } + gGL.getTextureSlot(DIRS_UNIT)->unbind(); + gGL.getTextureSlot(CUBE_UNIT)->unbind(); + mLookup.disableTexture(LLShaderMgr::RAINBOW_MAP); + mLookup.disableTexture(LLShaderMgr::HALO_MAP); + mLookup.unbind(); + + std::vector out(size_t(width) * height * 4); + glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, out.data()); + mPicture.flush(); + return out; + } + + // Face @a face's level @a level of @a cube, read back. + static std::vector level(U32 cube, U32 face, U32 level) + { + const U32 size = ALSkyCube::SIZE >> level; + std::vector out(size_t(size) * size * 4); + gGL.getTextureSlot(0)->bindManual(ALTextureSlot::TT_CUBE_MAP, cube); + glGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, level, GL_RGBA, GL_FLOAT, out.data()); + gGL.getTextureSlot(0)->unbind(); + return out; + } + + static constexpr std::array IDENTITY = { 1.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 1.f }; + + SkyShaderTree mTree; + ll_test::SharedGLScope mGL; + ll_test::TestShaderMgr mShaders; + LLGLSLShader mBake; + LLGLSLShader mLookup; + LLRenderTarget mStrip; + LLRenderTarget mPicture; + LLPointer mTriangle; + LLPointer mRainbow; + LLPointer mHalo; + GLuint mEnvironment = 0; + U32 mCube = 0; + U32 mStripDirs = 0; + U32 mPanoramaDirs = 0; + }; +} + +TEST_CASE_METHOD(alskycube_gl_data, "alskycube_gl: each texel holds windlightDome along its direction, halved", + "[alskycube_gl]") +{ + setUp(); + for (const Sky& sky : SKIES) + { + CAPTURE(sky.mName); + bake(sky); + const std::vector baked = lookup(sky, mCube, 0.f, mStripDirs, ALSkyCube::STRIP_WIDTH, ALSkyCube::SIZE); + const std::vector expected = lookup(sky, 0, 0.f, mStripDirs, ALSkyCube::STRIP_WIDTH, ALSkyCube::SIZE); + const Difference difference = compare(baked, expected, [](size_t) { return true; }); + const std::string summary = difference.summary(); + CAPTURE(summary); + CHECK(difference.mCount == (S32)(ALSkyCube::STRIP_WIDTH * ALSkyCube::SIZE * 3)); + CHECK(difference.mMost <= BAKED); + + // The faces differ: the sky is not one colour, so a face read in the wrong place would show. + F32 lowest = 1.f; + F32 highest = 0.f; + for (size_t px = 0; px * 4 < expected.size(); ++px) + { + lowest = llmin(lowest, expected[px * 4 + 2]); + highest = llmax(highest, expected[px * 4 + 2]); + } + CHECK(highest - lowest > 0.05f); + } +} + +TEST_CASE_METHOD(alskycube_gl_data, "alskycube_gl: every level is made from the first", "[alskycube_gl]") +{ + setUp(); + F32 brightest = 0.f; + for (const Sky& sky : SKIES) + { + CAPTURE(sky.mName); + bake(sky); + for (U32 face = 0; face < 6; ++face) + { + CAPTURE(face); + const std::vector first = level(mCube, face, 0); + const std::vector last = level(mCube, face, ALSkyCube::LEVELS - 1); + REQUIRE(last.size() == 4); + for (S32 c = 0; c < 3; ++c) + { + F64 sum = 0.0; + for (size_t px = 0; px * 4 < first.size(); ++px) + { + sum += first[px * 4 + c]; + } + const F32 mean = (F32)(sum / (first.size() / 4)); + CAPTURE(c, mean, last[c]); + CHECK(fabsf(last[c] - mean) <= MIPPED); + brightest = llmax(brightest, mean); + } + } + } + // Some face bright enough that a level left unmade would show. + CHECK(brightest > 0.1f); +} + +// A cube the readers bind before its first bake, or where the bake cannot run, reads as something: complete, with +// every face and level NEUTRAL, read back level by level and through the trilinear sampler the readers' lookup takes, +// at each level. The storage GL makes holds undefined contents, black on most drivers. +TEST_CASE_METHOD(alskycube_gl_data, "alskycube_gl: a cube never baked is complete and neutral at every level", + "[alskycube_gl]") +{ + setUp(); + mCube = ALSkyCube::allocate(); + REQUIRE(mCube != 0); + + gGL.getTextureSlot(0)->bindManual(ALTextureSlot::TT_CUBE_MAP, mCube); + GLint immutable = GL_FALSE; + GLint levels = 0; + glGetTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_IMMUTABLE_FORMAT, &immutable); + glGetTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_IMMUTABLE_LEVELS, &levels); + gGL.getTextureSlot(0)->unbind(); + CHECK(immutable == GL_TRUE); + CHECK(levels == (GLint)ALSkyCube::LEVELS); + + const F32 neutral = ALSkyCube::NEUTRAL / 255.f; + const auto neutral_everywhere = [neutral](const std::vector& texels, bool with_alpha) + { + F32 most = 0.f; + for (size_t px = 0; px * 4 < texels.size(); ++px) + { + for (S32 c = 0; c < 3; ++c) + { + most = llmax(most, fabsf(texels[px * 4 + c] - neutral)); + } + if (with_alpha) + { + most = llmax(most, fabsf(texels[px * 4 + 3] - 1.f)); + } + } + return most; + }; + + for (U32 face = 0; face < 6; ++face) + { + for (U32 l = 0; l < ALSkyCube::LEVELS; ++l) + { + CAPTURE(face, l); + const std::vector texels = level(mCube, face, l); + CHECK(texels.size() == size_t(ALSkyCube::SIZE >> l) * (ALSkyCube::SIZE >> l) * 4); + CHECK(neutral_everywhere(texels, true) <= HALF_STEP); + } + } + + for (U32 l = 0; l < ALSkyCube::LEVELS; ++l) + { + CAPTURE(l); + const std::vector read = lookup(SKIES[0], mCube, (F32)l, mStripDirs, ALSkyCube::STRIP_WIDTH, ALSkyCube::SIZE); + CHECK(neutral_everywhere(read, false) <= HALF_STEP); + } +} + +// The cube's storage is counted as an image's from its making to its deletion: six faces, each with every level. +TEST_CASE_METHOD(alskycube_gl_data, "alskycube_gl: the cube's storage is counted whole while it lives", + "[alskycube_gl]") +{ + const U64 before = LLImageGL::getTextureBytesAllocated(LLImageGLMemory::OTHER_TEXTURE); + mCube = ALSkyCube::allocate(); + REQUIRE(mCube != 0); + const U64 whole = + (U64)LLImageGL::dataFormatVRAMBytes(GL_RGBA8, ALSkyCube::SIZE, ALSkyCube::SIZE, ALSkyCube::LEVELS) * 6; + CHECK(LLImageGL::getTextureBytesAllocated(LLImageGLMemory::OTHER_TEXTURE) - before == whole); + + LLImageGL::deleteTextures(1, &mCube); + mCube = 0; + CHECK(LLImageGL::getTextureBytesAllocated(LLImageGLMemory::OTHER_TEXTURE) == before); +} + +TEST_CASE_METHOD(alskycube_gl_data, "alskycube_gl: the baked cube against the one the CPU built", "[alskycube_gl]") +{ + setUp(); + for (const Sky& sky : SKIES) + { + CAPTURE(sky.mName); + bake(sky); + const std::vector baked = lookup(sky, mCube, 0.f, mPanoramaDirs, PANORAMA_W, PANORAMA_H); + + // Above the old cube's horizon, below it, and the sliver between, by the panorama's rows. + const auto elevation = [](size_t px) { return ((F32)(px / PANORAMA_W) + 0.5f) / PANORAMA_H * 180.f - 90.f; }; + const auto above = [&](size_t px) { return sinf(elevation(px) * DEG_TO_RAD) >= -0.02f; }; + const auto below = [&](size_t px) { return sinf(elevation(px) * DEG_TO_RAD) < -0.02f; }; + + // The old cube as it was; its sky along each direction itself rather than the opposite one; and that sky's + // haze colour as it is rather than at a quarter of its saturation, and in place of the fog below the horizon. + const struct + { + const char* mName; + bool mMirrored; + bool mDesaturated; + } OLD[] = { { "the old cube", true, true }, + { "the old cube's sky along each direction itself", false, true }, + { "that sky's haze colour as it is, below the horizon too", false, false } }; + for (const auto& old : OLD) + { + LLPointer cube = legacy::cube(sky, old.mMirrored, old.mDesaturated); + REQUIRE(cube->getGLName() != 0); + const std::vector picture = lookup(sky, cube->getGLName(), 0.f, mPanoramaDirs, PANORAMA_W, PANORAMA_H); + const Difference up = compare(baked, picture, above); + const Difference down = compare(baked, picture, below); + WARN(sky.mName << ", the bake against " << old.mName << ": above its horizon " << up.summary() + << "; below it " << down.summary()); + CHECK(up.mCount > 0); + CHECK(down.mCount > 0); + cube = nullptr; + } + } +} diff --git a/indra/alrender/tests/alskycube_test.cpp b/indra/alrender/tests/alskycube_test.cpp new file mode 100644 index 00000000000..5aaddd7a267 --- /dev/null +++ b/indra/alrender/tests/alskycube_test.cpp @@ -0,0 +1,165 @@ +/** + * @file alskycube_test.cpp + * @brief The environment cube's directions: each texel's against the face and texel GL's cube lookup selects for it, + * and the turn into the dome's frame. And when two skies bake alike. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include + +#include "../alskycube.h" + +#include "llmath.h" + +#include +#include + +namespace +{ + // GL's cube map face selection, as the GL specification tabulates it: the major axis of @a r picks the face (+x, + // -x, +y, -y, +z, -z), and sc and tc over its magnitude, taken from [-1, 1] to [0, 1], are s and t across it. + void select(const LLVector3& r, U32& face, F32& s, F32& t) + { + const F32 ax = fabsf(r.mV[VX]); + const F32 ay = fabsf(r.mV[VY]); + const F32 az = fabsf(r.mV[VZ]); + F32 sc = 0.f; + F32 tc = 0.f; + F32 ma = 0.f; + if (ax >= ay && ax >= az) + { + face = r.mV[VX] > 0.f ? 0 : 1; + sc = r.mV[VX] > 0.f ? -r.mV[VZ] : r.mV[VZ]; + tc = -r.mV[VY]; + ma = ax; + } + else if (ay >= az) + { + face = r.mV[VY] > 0.f ? 2 : 3; + sc = r.mV[VX]; + tc = r.mV[VY] > 0.f ? r.mV[VZ] : -r.mV[VZ]; + ma = ay; + } + else + { + face = r.mV[VZ] > 0.f ? 4 : 5; + sc = r.mV[VZ] > 0.f ? r.mV[VX] : -r.mV[VX]; + tc = -r.mV[VY]; + ma = az; + } + s = 0.5f * (sc / ma + 1.f); + t = 0.5f * (tc / ma + 1.f); + } +} + +TEST_CASE("alskycube: each texel looks along the direction GL's lookup selects it for", "[alskycube]") +{ + for (U32 face = 0; face < 6; ++face) + { + for (U32 y = 0; y < ALSkyCube::SIZE; ++y) + { + for (U32 x = 0; x < ALSkyCube::SIZE; ++x) + { + const LLVector3 dir = ALSkyCube::texelDirection(face, x, y); + CAPTURE(face, x, y, dir.mV[VX], dir.mV[VY], dir.mV[VZ]); + REQUIRE(fabsf(dir.length() - 1.f) < 1e-5f); + + U32 selected = 6; + F32 s = -1.f; + F32 t = -1.f; + select(dir, selected, s, t); + REQUIRE(selected == face); + // The texel's centre. + REQUIRE(fabsf(s * ALSkyCube::SIZE - ((F32)x + 0.5f)) < 1e-3f); + REQUIRE(fabsf(t * ALSkyCube::SIZE - ((F32)y + 0.5f)) < 1e-3f); + } + } + } +} + +TEST_CASE("alskycube: a face's directions are its forward, right and up", "[alskycube]") +{ + for (U32 face = 0; face < 6; ++face) + { + CAPTURE(face); + const ALSkyCube::Face basis = ALSkyCube::face(face); + // Square to one another and of unit length, the forward along the face's own axis and sign. + CHECK(fabsf(basis.mForward * basis.mRight) < 1e-6f); + CHECK(fabsf(basis.mForward * basis.mUp) < 1e-6f); + CHECK(fabsf(basis.mRight * basis.mUp) < 1e-6f); + CHECK(fabsf(basis.mForward.mV[face / 2] - ((face & 1) ? -1.f : 1.f)) < 1e-6f); + + // The face's centre and corners as the first and last texels lie toward them. + const LLVector3 first = ALSkyCube::texelDirection(face, 0, 0); + const LLVector3 last = ALSkyCube::texelDirection(face, ALSkyCube::SIZE - 1, ALSkyCube::SIZE - 1); + CHECK(first * basis.mRight < 0.f); + CHECK(first * basis.mUp < 0.f); + CHECK(last * basis.mRight > 0.f); + CHECK(last * basis.mUp > 0.f); + } +} + +TEST_CASE("alskycube: the agent's frame turns into the dome's as the light does", "[alskycube]") +{ + // LLEnvironment::toLightNorm: (east, north, up) to (north, up, east). + const LLVector3 agent(0.2f, -0.7f, 0.3f); + const LLVector3 dome = ALSkyCube::toDome(agent); + CHECK(dome.mV[0] == agent.mV[VY]); + CHECK(dome.mV[1] == agent.mV[VZ]); + CHECK(dome.mV[2] == agent.mV[VX]); + + // Up in the agent's frame is up in the dome's, where windlightDome takes y as the height. + const LLVector3 up = ALSkyCube::toDome(LLVector3(0.f, 0.f, 1.f)); + CHECK(up.mV[1] == 1.f); +} + +TEST_CASE("alskycube: terms within the tolerance bake alike and terms past it do not", "[alskycube]") +{ + constexpr F32 T = ALSkyCube::TERM_TOLERANCE; + // A sky's terms: a colour channel, a density, a term at zero, and the dome's radius. + const std::array last = { 0.25f, 1.5f, 0.f, 15000.f }; + CHECK(ALSkyCube::termsAlike(last, last)); + + // Each moved by under the tolerance of the larger, the zero by under F_APPROXIMATELY_ZERO, as a day cycle moves + // them between two bakes. + const std::array drifted = { 0.25f * (1.f + 0.4f * T), 1.5f * (1.f - 0.4f * T), 0.5f * F_APPROXIMATELY_ZERO, + 15000.f * (1.f + 0.9f * T) }; + CHECK(ALSkyCube::termsAlike(last, drifted)); + CHECK(ALSkyCube::termsAlike(drifted, last)); + + // Any one moved past it. + const std::array moved = { 0.25f * (1.f + 2.f * T), 1.5f * (1.f - 2.f * T), 2.f * F_APPROXIMATELY_ZERO, + 15000.f * (1.f + 2.f * T) }; + for (size_t i = 0; i < last.size(); ++i) + { + CAPTURE(i); + std::array one = last; + one[i] = moved[i]; + CHECK_FALSE(ALSkyCube::termsAlike(last, one)); + CHECK_FALSE(ALSkyCube::termsAlike(one, last)); + } + + // Skies of different lengths are not alike. + const std::array fewer = { last[0], last[1], last[2] }; + CHECK_FALSE(ALSkyCube::termsAlike(last, fewer)); +} diff --git a/indra/alrender/tests/alskyraster_gl.h b/indra/alrender/tests/alskyraster_gl.h index 635a08a2061..a498e989af5 100644 --- a/indra/alrender/tests/alskyraster_gl.h +++ b/indra/alrender/tests/alskyraster_gl.h @@ -27,7 +27,6 @@ #pragma once #include "alfarplane.h" -#include "alskydome.h" #include "alskylayers.h" #include "llgl.h" @@ -36,6 +35,7 @@ #include "llglstates.h" #include "llimage.h" #include "llimagegl.h" +#include "llmath.h" #include "llmatrix4a.h" #include "llquaternion.h" #include "llrender.h" @@ -537,6 +537,106 @@ namespace al_sky_test return new LLImageGL(raw, true); } + // WindLight's sky dome as a mesh, in the dome's frame (north, up, east), which the old sky program shaded from the + // camera's point inside the sphere (windlight/windlightDomeF.glsl's windlightDomeAt): rings of a sphere about the + // origin, from the zenith down past the camera's level. The last rings are a skirt: the third from last lowered + // 2048 m, the next collapsed onto the axis below it, and the last two back out on the sphere, so the strips overlap + // below the camera's level and the later band drawn covers the earlier. Triangle strips of SLICES vertices a ring. + namespace ALSkyDome + { + // The stacks the dome has, and the vertices a ring has. Any fewer and the facets show. + constexpr U32 STACKS = 18; + constexpr U32 SLICES = 2 * STACKS; + + // The vertices and indices buildStrips writes for @a stacks stacks. + constexpr U32 vertexCount(U32 stacks) { return SLICES * (stacks + 1); } + constexpr U32 indexCount(U32 stacks) { return 1 + stacks * (2 + 2 * SLICES); } + + // The polar angle of ring @a ring, from the zenith: PI/8 (1 - (1 - t^4)^2) at t = ring / STACKS, which gathers + // the rings toward the horizon. + inline F32 ringAngle(U32 ring) + { + F32 t = float(ring) * (1.f / STACKS); + + // ^4 the parameter of the tesselation to bias things toward 0 (the dome's apex) + t *= t; + t *= t; + + // invert and square the parameter of the tesselation to bias things toward 1 (the horizon) + t = 1.f - t; + t = t * t; + t = 1.f - t; + + return (F_PI / 8.f) * t; + } + + // Stacks [@a begin_stack, @a end_stack) of a dome of @a radius: rings begin_stack + 1 to end_stack + 1, their + // positions, their planar texture coordinates ((1 - east) / 2, (1 - north) / 2) and one triangle strip over + // them. + inline void buildStrips(U32 begin_stack, U32 end_stack, F32 radius, LLStrider& vertices, + LLStrider& tex_coords, LLStrider& indices) + { + llassert(end_stack <= STACKS); + + for (U32 i = begin_stack + 1; i <= end_stack + 1; ++i) + { + const F32 phi0 = ringAngle(i); + + for (U32 j = 0; j < SLICES; ++j) + { + const F32 theta = F_TWO_PI * (float(j) / float(SLICES)); + + // standard transformation from spherical to rectangular coordinates + const F32 x0 = sin(phi0) * cos(theta); + const F32 y0 = cos(phi0); + const F32 z0 = sin(phi0) * sin(theta); + + if (i == STACKS - 2) + { + *vertices++ = LLVector3(x0 * radius, y0 * radius - 1024.f * 2.f, z0 * radius); + } + else if (i == STACKS - 1) + { + *vertices++ = LLVector3(0, y0 * radius - 1024.f * 2.f, 0); + } + else + { + *vertices++ = LLVector3(x0 * radius, y0 * radius, z0 * radius); + } + + // planar uv coordinates, x and z transposed so that they animate in the global frame, where +x is + // east and +y north + *tex_coords++ = LLVector2((-z0 + 1.f) / 2.f, (-x0 + 1.f) / 2.f); + } + } + + // the triangle strip + *indices++ = 0; + + S32 k = 0; + for (U32 i = 1; i <= end_stack - begin_stack; ++i) + { + *indices++ = (U16)(i * SLICES + (U32)k); + + k = (k + 1) % (S32)SLICES; + for (U32 j = 0; j < SLICES; ++j) + { + *indices++ = (U16)((i - 1) * SLICES + (U32)k); + *indices++ = (U16)(i * SLICES + (U32)k); + + k = (k + 1) % (S32)SLICES; + } + + if ((--k) < 0) + { + k = (S32)SLICES - 1; + } + + *indices++ = (U16)(i * SLICES + (U32)k); + } + } + } // namespace ALSkyDome + // The dome's mesh, as LLVOWLSky filled it. inline LLPointer domeMesh() { diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp index 43af105b2da..2fee60fc468 100644 --- a/indra/llrender/llshadermgr.cpp +++ b/indra/llrender/llshadermgr.cpp @@ -2228,6 +2228,7 @@ void LLShaderMgr::initAttribsAndUniforms() mReservedUniforms.push_back("skyHdriMap"); mReservedUniforms.push_back("skyHdriRotation"); mReservedUniforms.push_back("skyHdriScale"); + mReservedUniforms.push_back("skyCubeFace"); // Selection outlines, hover glow and wireframe (ALSelectionOutline) mReservedUniforms.push_back("selection_id"); diff --git a/indra/llrender/llshadermgr.h b/indra/llrender/llshadermgr.h index 1f15237eb75..3c83e9be5e5 100644 --- a/indra/llrender/llshadermgr.h +++ b/indra/llrender/llshadermgr.h @@ -665,6 +665,7 @@ class LLShaderMgr SKY_HDRI_MAP, // "skyHdriMap" the HDRI sky, wrapped about the dome's sphere SKY_HDRI_ROTATION, // "skyHdriRotation" the HDRI's turn about the vertical (RenderHDRIRotation) SKY_HDRI_SCALE, // "skyHdriScale" the HDRI's exposure (RenderHDRIExposure) + SKY_CUBE_FACE, // "skyCubeFace" vec3[18], each environment cube face's forward, right and up (ALSkyCube::bake) // Selection outlines, hover glow and wireframe (ALSelectionOutline) SELECTION_ID, // "selection_id" the object's id, 1 to 65535 diff --git a/indra/newview/alsky.cpp b/indra/newview/alsky.cpp index 17954964c3b..a76f87402da 100644 --- a/indra/newview/alsky.cpp +++ b/indra/newview/alsky.cpp @@ -32,9 +32,10 @@ #include "alsky.h" +#include "alskycube.h" + #include "llagent.h" #include "llappviewer.h" // gFrameIntervalSeconds -#include "llcubemap.h" #include "llenvironment.h" #include "llglslshader.h" #include "llglstates.h" @@ -59,12 +60,8 @@ namespace constexpr F32 SUN_DISK_RADIUS = 0.5f; constexpr F32 MOON_DISK_RADIUS = SUN_DISK_RADIUS * 0.9f; - // The legacy environment cube: a face's edge, how long a finished cube holds before the next may start, and how - // far the sky must move to start one. - constexpr S32 CUBE_RESOLUTION = 64; - constexpr S32 CUBE_FACES = 6; - constexpr F64 CUBE_UPDATE_SECONDS = 0.25; - constexpr F32 UPDATE_MIN_DELTA_THRESHOLD = 0.0005f; + // How long the fog colour and the environment cube hold before a changed sky is taken again. + constexpr F64 ENVIRONMENT_UPDATE_SECONDS = 0.25; // The global generator, for the meteors. F32 globalRandom() { return ll_frand(); } @@ -145,12 +142,12 @@ void ALSky::takeSky() return; } - updateDirections(); - - // The bodies' quads about the camera: the stars' moon mask and the composite read them. - mSun.setCorners(ALSkyLayers::bodyCorners(mSun.getRotation(), HEAVENLY_BODY_DIST, mSunScale, mSun.getDiskRadius())); + // The bodies' quads about the camera, turned as the sky turns them: the stars' moon mask and the composite read + // them. + mSun.setCorners( + ALSkyLayers::bodyCorners(psky->getSunRotation(), HEAVENLY_BODY_DIST, mSunScale, mSun.getDiskRadius())); mMoon.setCorners( - ALSkyLayers::bodyCorners(mMoon.getRotation(), HEAVENLY_BODY_DIST, mMoonScale, mMoon.getDiskRadius())); + ALSkyLayers::bodyCorners(psky->getMoonRotation(), HEAVENLY_BODY_DIST, mMoonScale, mMoon.getDiskRadius())); fetchTextures(); } @@ -181,11 +178,6 @@ void ALSky::update() } takeSky(); - - if (gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SKY) && !gGLManager.mIsDisabled) - { - updateCubeMap(); - } } void ALSky::followEnvironment() @@ -197,6 +189,12 @@ void ALSky::followEnvironment() LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; takeSky(); + + // Whether or not the sky is drawn: the cube lights the reflections, and the fog colour the beacons. + if (!gGLManager.mIsDisabled) + { + updateEnvironment(); + } } void ALSky::cleanup() @@ -208,7 +206,7 @@ void ALSky::cleanup() mStarLayout = {}; mStarsBuildFailed = false; mStarsVerts = nullptr; - mCubeMap = nullptr; + mCubeStrip.release(); for (auto* textures : { &mSunTexture, &mMoonTexture, &mCloudNoiseTexture, &mBloomTexture }) { textures->fill(nullptr); @@ -224,28 +222,18 @@ void ALSky::releaseGL() // The next context may make the buffer this one could not. mStarsBuildFailed = false; mStarsVerts = nullptr; - if (mCubeMap) - { - mCubeMap->destroyGL(); - } + mCubeStrip.release(); } void ALSky::restoreGL() { - if (!mShiny[0].mDirs.empty()) - { - initCubeMap(); - } forceSkyUpdate(); } -void ALSky::updateDirections() +void ALSky::forceSkyUpdate() { - const LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - mSun.setDirection(psky->getSunDirection()); - mMoon.setDirection(psky->getMoonDirection()); - mSun.setRotation(psky->getSunRotation()); - mMoon.setRotation(psky->getMoonRotation()); + mEnvironmentDirty = true; + mEnvironmentRetry = false; } void ALSky::setSunDirectionCFR(const LLVector3& sun_dir_cfr) @@ -833,36 +821,48 @@ bool ALSky::useHDRISky() false; // no HDRI available, always use EEP sky } -void ALSky::bindDome(LLGLSLShader& shader) const +ALSky::DomeUniforms ALSky::domeUniforms() const { LLEnvironment& environment = LLEnvironment::instance(); const LLSettingsSky::ptr_t psky = environment.getCurrentSky(); - shader.bindTexture(LLShaderMgr::RAINBOW_MAP, mRainbowMap.get(), ALSamplers::AnisoWrap); - shader.bindTexture(LLShaderMgr::HALO_MAP, mHaloMap.get(), ALSamplers::AnisoWrap); - - F32 moisture_level = (float)psky->getSkyMoistureLevel(); - F32 droplet_radius = (float)psky->getSkyDropletRadius(); - F32 ice_level = (float)psky->getSkyIceLevel(); + DomeUniforms dome; + dome.mMoisture = (F32)psky->getSkyMoistureLevel(); + dome.mDroplet = (F32)psky->getSkyDropletRadius(); + dome.mIce = (F32)psky->getSkyIceLevel(); // hobble halos and rainbows when there's no light source to generate them if (!psky->getIsSunUp() && !psky->getIsMoonUp()) { - moisture_level = 0.0f; - ice_level = 0.0f; + dome.mMoisture = 0.0f; + dome.mIce = 0.0f; } - shader.uniform1f(LLShaderMgr::MOISTURE_LEVEL, moisture_level); - shader.uniform1f(LLShaderMgr::DROPLET_RADIUS, droplet_radius); - shader.uniform1f(LLShaderMgr::ICE_LEVEL, ice_level); + dome.mGlowFactor = psky->getSunMoonGlowFactor(); + dome.mDensity = psky->getDensityMultiplier(); + dome.mLightNorm = environment.getClampedLightNorm(); + dome.mCamHeight = environment.getCamHeight(); + dome.mDomeRadius = psky->getDomeRadius(); + return dome; +} - shader.uniform1f(LLShaderMgr::SUN_MOON_GLOW_FACTOR, psky->getSunMoonGlowFactor()); - shader.uniform1f(LLShaderMgr::DENSITY_MULTIPLIER, psky->getDensityMultiplier()); - shader.uniform3fv(LLShaderMgr::LIGHTNORM, 1, environment.getClampedLightNorm().mV); +void ALSky::bindDome(LLGLSLShader& shader) const +{ + shader.bindTexture(LLShaderMgr::RAINBOW_MAP, mRainbowMap.get(), ALSamplers::AnisoWrap); + shader.bindTexture(LLShaderMgr::HALO_MAP, mHaloMap.get(), ALSamplers::AnisoWrap); + + const DomeUniforms dome = domeUniforms(); + shader.uniform1f(LLShaderMgr::MOISTURE_LEVEL, dome.mMoisture); + shader.uniform1f(LLShaderMgr::DROPLET_RADIUS, dome.mDroplet); + shader.uniform1f(LLShaderMgr::ICE_LEVEL, dome.mIce); + + shader.uniform1f(LLShaderMgr::SUN_MOON_GLOW_FACTOR, dome.mGlowFactor); + shader.uniform1f(LLShaderMgr::DENSITY_MULTIPLIER, dome.mDensity); + shader.uniform3fv(LLShaderMgr::LIGHTNORM, 1, dome.mLightNorm.mV); // The dome's geometry: the camera's place in its frame, and the radius of the sphere the dome lies on. - shader.uniform3f(LLShaderMgr::WL_CAMPOSLOCAL, 0.f, environment.getCamHeight(), 0.f); - shader.uniform1f(LLShaderMgr::SKY_DOME_RADIUS, psky->getDomeRadius()); + shader.uniform3f(LLShaderMgr::WL_CAMPOSLOCAL, 0.f, dome.mCamHeight, 0.f); + shader.uniform1f(LLShaderMgr::SKY_DOME_RADIUS, dome.mDomeRadius); } void ALSky::bindComposite(LLGLSLShader& shader) @@ -1044,177 +1044,142 @@ void ALSky::unbindComposite(LLGLSLShader& shader) } // -// The legacy environment cube +// The environment cube // +void ALSky::allocateEnvironmentCube() +{ + // Neutral until the bake (ALSkyCube::allocate), so it is bound from the frame it is made in. + mCube = ALSkyCube::allocate(); + forceSkyUpdate(); +} + bool ALSky::bindEnvironmentCube(S32 channel) const { - if (LLPipeline::sReflectionProbesEnabled || channel < 0 || mCubeMap.isNull()) + if (channel < 0) + { + return false; + } + ALTextureSlot* slot = gGL.getTextureSlot(channel); + if (!mCube || !gPipeline.resourcePlan().wants(ALRenderResourcePlan::Id::EnvironmentCube)) { + // The unit holds nothing, which the reader's disable then finds and leaves. + slot->unbind(); return false; } - mCubeMap->enable(channel); - mCubeMap->bind(); + slot->bindManual(ALTextureSlot::TT_CUBE_MAP, mCube, gGL.getSampler(ALSamplers::AnisoClamp)); return true; } -void ALSky::cacheEnvironment(AtmosphericsVars& vars) const +bool ALSky::EnvironmentKey::alike(const EnvironmentKey& other) const { - const LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - // NOTE: Also see: LLAtmospherics::updateFog() - // invariants across whole sky tex process... - vars.blue_density = psky->getBlueDensity(); - vars.blue_horizon = psky->getBlueHorizon(); - vars.haze_density = psky->getHazeDensity(); - vars.haze_horizon = psky->getHazeHorizon(); - vars.density_multiplier = psky->getDensityMultiplier(); - vars.distance_multiplier = psky->getDistanceMultiplier(); - vars.max_y = psky->getMaxY(); - vars.sun_norm = LLEnvironment::instance().getClampedSunNorm(); - vars.sunlight = psky->getIsSunUp() ? psky->getSunlightColor() : psky->getMoonlightColor(); - vars.ambient = psky->getAmbientColor(); - vars.glow = psky->getGlow(); - vars.cloud_shadow = psky->getCloudShadow(); - vars.dome_radius = psky->getDomeRadius(); - vars.dome_offset = psky->getDomeOffset(); - vars.light_atten = psky->getLightAttenuation(vars.max_y); - vars.light_transmittance = psky->getLightTransmittance(vars.max_y); - vars.total_density = psky->getTotalDensity(); - vars.gamma = psky->getGamma(); + return mMaps == other.mMaps && ALSkyCube::termsAlike(mTerms, other.mTerms); } -void ALSky::initSkyTextureDirs(S32 side, S32 tile) +ALSky::EnvironmentKey ALSky::environmentKey() const { - ShinyFace& face = mShiny[side]; - if (face.mDirs.empty()) - { - face.mDirs.resize(CUBE_RESOLUTION * CUBE_RESOLUTION); - face.mColours.resize(CUBE_RESOLUTION * CUBE_RESOLUTION); - face.mImage = new LLImageRaw(CUBE_RESOLUTION, CUBE_RESOLUTION, 4); - } + // The members windlightDome reads of the Environment block (LLSettingsVOSky::applySpecial and + // LLEnvironment::packAtmosphericTerms work them from the sky), and of its loose uniforms. + const LLEnvironment::EnvironmentUBOData& block = LLEnvironment::instance().mEnvUBOData; + const DomeUniforms dome = domeUniforms(); - F32 coeff[3] = {0, 0, 0}; - const S32 curr_coef = side >> 1; // 0/1 = X axis, 2/3 = Y, 4/5 = Z - const S32 side_dir = (((side & 1) << 1) - 1); // even = -1, odd = 1 - const S32 x_coef = (curr_coef + 1) % 3; - const S32 y_coef = (x_coef + 1) % 3; - - coeff[curr_coef] = (F32)side_dir; - - const F32 inv_res = 1.f / CUBE_RESOLUTION; - for (S32 y = 0; y < CUBE_RESOLUTION; ++y) + EnvironmentKey key; + size_t next = 0; + const auto take = [&](const F32* values, size_t count) { - for (S32 x = 0; x < CUBE_RESOLUTION; ++x) + for (size_t i = 0; i < count; ++i) { - coeff[x_coef] = F32((x<<1) + 1) * inv_res - 1.f; - coeff[y_coef] = F32((y<<1) + 1) * inv_res - 1.f; - LLVector3 dir(coeff[0], coeff[1], coeff[2]); - dir.normalize(); - face.mDirs[x * CUBE_RESOLUTION + y] = dir; + key.mTerms[next++] = values[i]; } + }; + take(block.blue_horizon, 3); + take(&block.haze_horizon, 1); + take(block.glow, 3); + take(&block.max_y, 1); + take(&block.cloud_shadow, 1); + take(block.haze_extinction, 3); + take(block.blue_weight, 3); + take(block.haze_weight, 3); + take(block.sky_light, 3); + take(block.sky_ambient, 3); + take(block.sky_light_atten, 3); + take(block.sky_ambient_below, 3); + take(&dome.mMoisture, 1); + take(&dome.mDroplet, 1); + take(&dome.mIce, 1); + take(&dome.mGlowFactor, 1); + take(&dome.mDensity, 1); + take(dome.mLightNorm.mV, 3); + take(&dome.mCamHeight, 1); + take(&dome.mDomeRadius, 1); + llassert(next == key.mTerms.size()); + + // A map still loading is baked as far as it has come, and again as it comes in. + for (const auto& [slot, map] : { std::pair{ 0, mRainbowMap.get() }, std::pair{ 2, mHaloMap.get() } }) + { + key.mMaps[slot] = map ? (S32)map->getTexName() : 0; + key.mMaps[slot + 1] = map ? map->getDiscardLevel() : -1; } + return key; } -void ALSky::createSkyTexture(AtmosphericsVars& vars, S32 side, S32 tile) +void ALSky::updateEnvironment() { - const LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - ShinyFace& face = mShiny[side]; - for (size_t i = 0; i < face.mDirs.size(); ++i) + if (!mCube) { - face.mColours[i] = mAtmospherics.calcSkyColorInDir(psky, vars, face.mDirs[i], true, false); + // The plan has none, with reflection probes on. + mCubeStrip.release(); } -} -void ALSky::initCubeMap() -{ - std::vector> images; - for (ShinyFace& face : mShiny) + // At once where something asked, and otherwise where the sky has moved, at most every so often; a bake that did not + // happen is tried again as often, until one does. + const EnvironmentKey key = environmentKey(); + const F64 now = LLFrameTimer::getTotalSeconds(); + const bool due = now - mEnvironmentUpdated >= ENVIRONMENT_UPDATE_SECONDS; + if (mEnvironmentDirty ? (mEnvironmentRetry && !due) : (key.alike(mEnvironmentKey) || !due)) { - { - LLImageDataLock lock(face.mImage); - U32* pixels = (U32*)face.mImage->getData(); - for (size_t i = 0; i < face.mColours.size(); ++i) - { - pixels[i] = LLColor4U(face.mColours[i]).asRGBA(); - } - } - images.push_back(face.mImage); + return; } - if (!mCubeMap && gSavedSettings.getBOOL("RenderWater")) - { - mCubeMap = new LLCubeMap(); - } + LL_PROFILE_ZONE_NAMED_CATEGORY_ENVIRONMENT("sky environment"); - if (mCubeMap) + // The fog colour the tracker's beacons fade into. + mAtmospherics.updateFog(LLViewerCamera::getInstance()->getFar(), LLEnvironment::instance().getClampedLightDirection()); + + // The sky is taken once the cube holds it, or where there is no cube to bake. + mEnvironmentUpdated = now; + mEnvironmentRetry = mCube && !bakeEnvironmentCube(); + if (!mEnvironmentRetry) { - mCubeMap->init(images); + mEnvironmentKey = key; + mEnvironmentDirty = false; } - - gGL.getTextureSlot(0)->unbind(); -} - -void ALSky::forceSkyUpdate() -{ - mForceUpdate = true; - mLastAtmosphericsVars = {}; - mCubeMapUpdateStage = -1; } -void ALSky::updateCubeMap() +bool ALSky::bakeEnvironmentCube() { - if (mShiny[0].mDirs.empty()) + LLGLSLShader& program = gDeferredSkyCubeProgram; + if (!program.isComplete()) { - // Every face at once, the first time. - cacheEnvironment(mAtmosphericsVars); - for (S32 side = 0; side < CUBE_FACES; ++side) - { - initSkyTextureDirs(side, 0); - createSkyTexture(mAtmosphericsVars, side, 0); - } - initCubeMap(); + LL_WARNS_ONCE("Sky") << "The environment cube's program did not build: reflections without probes read it " + "neutral grey" << LL_ENDL; + return false; } - if (!mCubeMap || LLPipeline::sReflectionProbesEnabled) + const ALRenderTargetDesc strip{ .width = ALSkyCube::STRIP_WIDTH, .height = ALSkyCube::SIZE, .color = { GL_RGBA8 } }; + if (gPipeline.mScreenTriangleVB.isNull() || mCubeStrip.ensure(strip) == LLRenderTarget::EnsureResult::Failed) { - mCubeMapUpdateStage = CUBE_FACES; - mForceUpdate = false; - return; + return false; } - if (mCubeMapUpdateStage < 0) - { - cacheEnvironment(mAtmosphericsVars); - - const bool same_atmospherics = - approximatelyEqual(mLastAtmosphericsVars, mAtmosphericsVars, UPDATE_MIN_DELTA_THRESHOLD); - const bool need_update = mForceUpdate || !same_atmospherics; - if (need_update && - (mForceUpdate || LLFrameTimer::getTotalSeconds() - mCubeMapUpdated >= CUBE_UPDATE_SECONDS)) - { - // start updating cube map sides - mAtmospherics.updateFog(LLViewerCamera::getInstance()->getFar(), - LLEnvironment::instance().getClampedLightDirection()); - mCubeMapUpdateStage = 0; - mForceUpdate = false; - } - } - else if (mCubeMapUpdateStage == CUBE_FACES) - { - LL_PROFILE_ZONE_NAMED("updateSky - forced"); - initCubeMap(); - mLastAtmosphericsVars = mAtmosphericsVars; - mForceUpdate = false; - mCubeMapUpdated = LLFrameTimer::getTotalSeconds(); - mCubeMapUpdateStage = -1; - } - else - { - // CPU hungry part, createSkyTexture() is math heavy: a face a frame. - LL_PROFILE_ZONE_NAMED("updateSky - create"); - createSkyTexture(mAtmosphericsVars, mCubeMapUpdateStage, 0); - mCubeMapUpdateStage++; - } + program.bind(); + LLEnvironment::instance().bindEnvironmentUBO(); + bindDome(program); + ALSkyCube::bake(program, mCube, mCubeStrip, *gPipeline.mScreenTriangleVB); + program.disableTexture(LLShaderMgr::RAINBOW_MAP); + program.disableTexture(LLShaderMgr::HALO_MAP); + program.unbind(); + return true; } // diff --git a/indra/newview/alsky.h b/indra/newview/alsky.h index da1643466bf..3898a92dae6 100644 --- a/indra/newview/alsky.h +++ b/indra/newview/alsky.h @@ -36,6 +36,7 @@ #include "llpointer.h" #include "llquaternion.h" +#include "llrendertarget.h" #include "lluuid.h" #include "llvertexbuffer.h" #include "v3color.h" @@ -47,44 +48,34 @@ #include #include -class LLCubeMap; class LLGLSLShader; -class LLImageRaw; class LLViewerFetchedTexture; class LLViewerTexture; /// How far from the camera the sun's and the moon's quads lie. const F32 HEAVENLY_BODY_DIST = HORIZON_DIST - 20.f; -/// The sun or the moon as the sky draws it: its direction and turn as the sky sets them, its disk, and its quad about -/// the camera (ALSkyLayers::bodyCorners) as the frame draws it. Its colour and whether it is up are the current sky's -/// (LLSettingsSky). +/// The sun or the moon as the sky draws it: its disk, and its quad about the camera (ALSkyLayers::bodyCorners) as the +/// frame draws it. Its direction, its turn, its colour and whether it is up are the current sky's (LLSettingsSky). class LLHeavenBody { public: explicit LLHeavenBody(F32 disk_radius) : mDiskRadius(disk_radius) {} - void setDirection(const LLVector3& direction) { mDirection = direction; } - - const LLQuaternion& getRotation() const { return mRotation; } - void setRotation(const LLQuaternion& rotation) { mRotation = rotation; } - F32 getDiskRadius() const { return mDiskRadius; } const ALSkyLayers::Corners& corners() const { return mCorners; } void setCorners(const ALSkyLayers::Corners& corners) { mCorners = corners; } private: - LLVector3 mDirection; - LLQuaternion mRotation; F32 mDiskRadius; ALSkyLayers::Corners mCorners{}; }; /// The sky, drawn by no object and no pool: the sun and the moon, the sky's textures and their fetch, the star field's -/// buffer and the meteors, the fog colour the tracker's beacons take, and the legacy environment cube the shiny and -/// probe-less reflections read where reflection probes are off. Its pure parts are alrender's (ALStarField, ALSkyDome, -/// ALSkyLayers). +/// buffer and the meteors, the fog colour the tracker's beacons take, and the environment cube the shiny and probe-less +/// reflections read where reflection probes are off. Its pure parts are alrender's (ALStarField, ALSkyLayers, +/// ALSkyCube). /// /// Owned by LLPipeline, as the water's surface is. It runs at three fixed points, the same in every view: the stars /// after renderGeomDeferred's pool loop (renderStars), the composite at the sky's pass (bindComposite), and the @@ -102,9 +93,9 @@ class ALSky ALSky(const ALSky&) = delete; ALSky& operator=(const ALSky&) = delete; - /// Takes the frame's sky: the bodies' directions, colour and quads and whether each is up, the textures the current - /// sky names fetched at the priority they are drawn at, and the environment cube where it is wanted. Once a main - /// frame, before anything draws; the first lays the star field out. + /// Takes the frame's sky: the bodies' quads, and the textures the current sky names fetched at the priority they are + /// drawn at. Once a main frame, with the context current, before anything draws; the first asks for the star field, + /// which is laid out off the main thread, and the stars draw once it lands. void update(); /// Whether an update has run, so there is a sky to draw. @@ -112,23 +103,32 @@ class ALSky /// The bodies and the textures taken again once the frame's environment update (LLEnvironment::update) has moved /// the sky on, before the main view draws them. The views drawn before it, the mirrors, draw the sky as the frame - /// began with it, as they take its environment: their faces' environment updates do not move the sky on. Nothing - /// before the first update. + /// began with it, as they take its environment: their faces' environment updates do not move the sky on. And where + /// the sky has changed, the fog colour and the environment cube, from the Environment block as the main view takes + /// it rather than as the last capture left it, which an irradiance pass greys. Nothing before the first update. void followEnvironment(); /// Lets go of everything at shutdown, ahead of the texture list. void cleanup(); - /// The GL objects of its own, released with the context (destroyGL): the stars' buffer and the cube's, made again - /// when next drawn. + /// The GL objects of its own, released with the context (destroyGL): the stars' buffer and the cube's strip, made + /// again when next drawn. The cube is the pipeline's to release (ALRenderResourcePlan::Id::EnvironmentCube). void releaseGL(); - /// After the context returns: the cube is built again on the next update. + /// After the context returns: the cube is baked again when the environment is next taken (followEnvironment). void restoreGL(); - /// The environment cube built again on the next update, whatever has changed: after its programs are loaded. + /// The environment cube baked again when the environment is next taken (followEnvironment), whatever has changed: + /// after its program is loaded. void forceSkyUpdate(); + /// The environment cube's GL name, which the pipeline's reconcile makes (allocateEnvironmentCube) and frees as its + /// plan says (ALRenderResourcePlan::Id::EnvironmentCube): only where reflection probes are off. + U32* environmentCubeTexture() { return &mCube; } + + /// A new environment cube (ALSkyCube::allocate), baked when the environment is next taken (followEnvironment). + void allocateEnvironmentCube(); + /// The sun's direction, CFR (+x at, +z up, +y right), as LLSettingsVOSky::updateSettings and the mouselook tool set /// it: the sun bump maps are lit by (getBumpSunDir). The bodies take the current sky's own directions. void setSunDirectionCFR(const LLVector3& sun_direction); @@ -187,19 +187,18 @@ class ALSky /// only the irradiance takes it), and in the main view while its split screen shows any of it. static bool useHDRISky(); - /// The legacy environment cube, bound on texture @a channel for a program's environmentMap, where reflection probes - /// are off and there is one. False, with nothing bound, otherwise. + /// The environment cube, bound on texture @a channel for a program's environmentMap, where the plan wants one + /// (reflection probes off): neutral until it is first baked (ALSkyCube::NEUTRAL). False otherwise, with the unit let + /// go, so the reader's disable finds nothing there. bool bindEnvironmentCube(S32 channel) const; /// The debug beacons toward the sun and the moon from the agent, as the menu shows them. void addSunMoonBeacons(); private: - // The bodies' directions and turns as the current sky has them. - void updateDirections(); // The textures the sky names, fetched where they changed; the bloom's only while the sun bloom is drawn. void fetchTextures(); - // The bodies' directions, turns and quads, and the textures, from the current sky. + // The bodies' quads and the textures, from the current sky. void takeSky(); // A star field as the General queue lays it out for the buffer: plain data, handed over whole. @@ -227,12 +226,40 @@ class ALSky // as its envelope has carried it this far, its colour times that envelope, and its width class. void meteors(std::vector& out) const; - // The legacy environment cube: built on the CPU a face a frame, through LLAtmospherics, where probes are off. - void initCubeMap(); - void updateCubeMap(); - void cacheEnvironment(AtmosphericsVars& vars) const; - void initSkyTextureDirs(S32 side, S32 tile); - void createSkyTexture(AtmosphericsVars& vars, S32 side, S32 tile); + // windlight/windlightDomeF.glsl's loose uniforms, as bindDome sets them: the rainbow's and the halo's levels, which + // a sky with neither the sun nor the moon up has none of, the glow, the density and the light, and the camera's + // place in the dome's frame and the dome's radius. + struct DomeUniforms + { + F32 mMoisture = 0.f; + F32 mDroplet = 0.f; + F32 mIce = 0.f; + F32 mGlowFactor = 0.f; + F32 mDensity = 0.f; + LLVector4 mLightNorm; + F32 mCamHeight = 0.f; + F32 mDomeRadius = 0.f; + }; + DomeUniforms domeUniforms() const; + + // What the environment cube and the fog colour are worked from: windlightDome's terms of the Environment block, its + // loose uniforms, and the rainbow and halo maps as far as they have loaded. + struct EnvironmentKey + { + std::array mTerms{}; + std::array mMaps{}; + + // Whether the two bake alike: the same maps, as far loaded, and terms within ALSkyCube::TERM_TOLERANCE of each + // other (ALSkyCube::termsAlike). + bool alike(const EnvironmentKey& other) const; + }; + EnvironmentKey environmentKey() const; + + // Where the sky has changed since it was last taken, at most every so often, or where something asked: the fog + // colour, and the environment cube baked where there is one. The sky is taken only once the cube holds it. + void updateEnvironment(); + // False where the cube could not be baked: its program did not build, or its strip could not be made. + bool bakeEnvironmentCube(); static void renderSunMoonBeacons(const LLVector3& pos_agent, const LLVector3& direction, LLColor4 color); @@ -295,18 +322,13 @@ class ALSky LLAtmospherics mAtmospherics; - // The legacy environment cube, and the shiny faces it is made from. - struct ShinyFace - { - LLPointer mImage; - std::vector mColours; - std::vector mDirs; - }; - std::array mShiny; - LLPointer mCubeMap; - AtmosphericsVars mAtmosphericsVars; - AtmosphericsVars mLastAtmosphericsVars; - S32 mCubeMapUpdateStage = -1; // -1 idle; 0-5 a face a frame; 6 finalizing - bool mForceUpdate = true; - F64 mCubeMapUpdated = 0.0; // LLFrameTimer::getTotalSeconds() at the last finished cube + // The environment cube and the strip its faces are drawn into; and the sky it was last taken from, when it was last + // tried, whether it must be taken again whatever has changed, and whether that is because the last bake did not + // happen, which is tried again when next due rather than at once. + U32 mCube = 0; + LLRenderTarget mCubeStrip; + EnvironmentKey mEnvironmentKey; + F64 mEnvironmentUpdated = 0.0; // LLFrameTimer::getTotalSeconds() + bool mEnvironmentDirty = true; + bool mEnvironmentRetry = false; }; diff --git a/indra/newview/app_settings/shaders/class1/deferred/skyCubeF.glsl b/indra/newview/app_settings/shaders/class1/deferred/skyCubeF.glsl new file mode 100644 index 00000000000..dbcf887f5de --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/skyCubeF.glsl @@ -0,0 +1,48 @@ +/** + * @file class1/deferred/skyCubeF.glsl + * @brief WindLight's dome baked into the environment cube the shiny and probe-less reflections read where reflection + * probes are off: the six faces side by side across one strip (ALSkyCube::bake). + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +out vec4 frag_color; + +// Across the strip, 0 to 1 each way. +in vec2 vary_fragcoord; + +// Each face's forward, right and up in the dome's frame (north, up, east), in GL's face order: the texel at s and t, +// each -1 to 1 across its face, looks along forward + s right + t up (ALSkyCube::face). +uniform vec3 skyCubeFace[18]; + +vec3 windlightDome(vec3 dir); + +void main() +{ + float across = vary_fragcoord.x * 6.0; + int face = min(int(across), 5); + vec2 st = vec2(fract(across), vary_fragcoord.y) * 2.0 - 1.0; + + vec3 dir = normalize(skyCubeFace[face * 3] + st.x * skyCubeFace[face * 3 + 1] + st.y * skyCubeFace[face * 3 + 2]); + + // Half the dome, as the haze takes it for far water: WindLight's colour before the sky program doubles it, which + // the readers double again where they take it as the legacy environment (applyLegacyEnv). + frag_color = vec4(windlightDome(dir) * 0.5, 1.0); +} diff --git a/indra/newview/lllegacyatmospherics.cpp b/indra/newview/lllegacyatmospherics.cpp index 812bcf0274f..ada2ac5cc28 100644 --- a/indra/newview/lllegacyatmospherics.cpp +++ b/indra/newview/lllegacyatmospherics.cpp @@ -110,20 +110,6 @@ static LLFastLn gFastLn; // Functions used a lot. -inline F32 LLHaze::calcPhase(const F32 cos_theta) const -{ - const F32 g2 = mG * mG; - const F32 den = 1 + g2 - 2 * mG * cos_theta; - return (1 - g2) * gFastLn.pow(den, -1.5); -} - -inline void color_pow(LLColor3 &col, const F32 e) -{ - col.mV[0] = gFastLn.pow(col.mV[0], e); - col.mV[1] = gFastLn.pow(col.mV[1], e); - col.mV[2] = gFastLn.pow(col.mV[2], e); -} - inline LLColor3 color_norm(const LLColor3 &col) { const F32 m = color_max(col); @@ -151,40 +137,15 @@ inline void color_gamma_correct(LLColor3 &col) } } -static LLColor3 calc_air_sca_sea_level() -{ - static LLColor3 WAVE_LEN(675, 520, 445); - static LLColor3 refr_ind = refr_ind_calc(WAVE_LEN); - static LLColor3 n21 = refr_ind * refr_ind - LLColor3(1, 1, 1); - static LLColor3 n4 = n21 * n21; - static LLColor3 wl2 = WAVE_LEN * WAVE_LEN * 1e-6f; - static LLColor3 wl4 = wl2 * wl2; - static LLColor3 mult_const = fsigma * 2.0f/ 3.0f * 1e24f * (F_PI * F_PI) * n4; - static F32 dens_div_N = F32( ATM_SEA_LEVEL_NDENS / Ndens2); - return dens_div_N * mult_const.divide(wl4); -} - -// static constants. -LLColor3 const LLHaze::sAirScaSeaLevel = calc_air_sca_sea_level(); -F32 const LLHaze::sAirScaIntense = color_intens(LLHaze::sAirScaSeaLevel); -F32 const LLHaze::sAirScaAvg = LLHaze::sAirScaIntense / 3.f; - /*************************************** Atmospherics ***************************************/ LLAtmospherics::LLAtmospherics() -: mCloudDensity(0.2f), - mWind(0.f), - mWorldScale(1.f) { - /// WL PARAMS - mInitialized = false; mFogColor.mV[VRED] = mFogColor.mV[VGREEN] = mFogColor.mV[VBLUE] = 0.5f; mFogColor.mV[VALPHA] = 0.0f; mFogRatio = 1.2f; - mHazeConcentration = 0.f; - mInterpVal = 0.f; } @@ -192,64 +153,15 @@ LLAtmospherics::~LLAtmospherics() { } -void LLAtmospherics::init() -{ - const F32 haze_int = color_intens(mHaze.calcSigSca(0)); - mHazeConcentration = haze_int / (color_intens(mHaze.calcAirSca(0)) + haze_int); - mInitialized = true; -} - -// This cubemap is used as "environmentMap" in indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl -LLColor4 LLAtmospherics::calcSkyColorInDir(const LLSettingsSky::ptr_t &psky, AtmosphericsVars& vars, const LLVector3 &dir, bool isShiny, bool low_end) +LLColor4 LLAtmospherics::calcSkyColorInDir(const LLSettingsSky::ptr_t &psky, AtmosphericsVars& vars, const LLVector3 &dir) { - const F32 sky_saturation = 0.25f; - const F32 land_saturation = 0.1f; - - if (isShiny && dir.mV[VZ] < -0.02f) - { - LLColor4 col; - LLColor3 desat_fog = LLColor3(mFogColor); - F32 brightness = desat_fog.brightness();// NOTE: Linear brightness! - // So that shiny somewhat shows up at night. - if (brightness < 0.15f) - { - brightness = 0.15f; - desat_fog = smear(0.15f); - } - F32 greyscale_sat = brightness * (1.0f - land_saturation); - desat_fog = desat_fog * land_saturation + smear(greyscale_sat); - if (low_end) - { - col = LLColor4(desat_fog, 0.f); - } - else - { - col = LLColor4(desat_fog * 0.5f, 0.f); - } - float x = 1.0f-fabsf(-0.1f-dir.mV[VZ]); - x *= x; - col.mV[0] *= x*x; - col.mV[1] *= powf(x, 2.5f); - col.mV[2] *= x*x*x; - return col; - } - // undo OGL_TO_CFR_ROTATION and negate vertical direction. LLVector3 Pn = LLVector3(-dir[1] , -dir[2], -dir[0]); //calculates hazeColor calcSkyColorWLVert(psky, Pn, vars); - if (isShiny) - { - F32 brightness = vars.hazeColor.brightness(); - F32 greyscale_sat = brightness * (1.0f - sky_saturation); - LLColor3 sky_color = vars.hazeColor * sky_saturation + smear(greyscale_sat); - //sky_color *= (0.5f + 0.5f * brightness); // SL-12574 EEP sky is being attenuated too much - return LLColor4(sky_color, 0.0f); - } - - LLColor3 sky_color = low_end ? vars.hazeColor * 2.0f : psky->gammaCorrect(vars.hazeColor * 2.0f, vars.gamma); + LLColor3 sky_color = psky->gammaCorrect(vars.hazeColor * 2.0f, vars.gamma); return LLColor4(sky_color, 0.0f); } @@ -299,7 +211,6 @@ void LLAtmospherics::calcSkyColorWLVert(const LLSettingsSky::ptr_t &psky, LLVect LLColor3 ambient = vars.ambient; LLColor3 glow = vars.glow; - F32 cloud_shadow = vars.cloud_shadow; // Sunlight attenuation effect (hue and brightness) due to atmosphere // this is used later for sunlight modulation at various altitudes @@ -351,27 +262,8 @@ void LLAtmospherics::calcSkyColorWLVert(const LLSettingsSky::ptr_t &psky, LLVect // Haze color above cloud vars.hazeColor = (blue_factor * (sunlight + ambient) + componentMult(haze_factor, sunlight * temp2.mV[0] + ambient)); - // Increase ambient when there are more clouds - LLColor3 tmpAmbient = ambient + (LLColor3::white - ambient) * cloud_shadow * 0.5f; - - // Dim sunlight by cloud shadow percentage - sunlight *= (1.f - cloud_shadow); - - // Haze color below cloud - vars.hazeColorBelowCloud = (blue_factor * (sunlight + tmpAmbient) + componentMult(haze_factor, sunlight * temp2.mV[0] + tmpAmbient)); - // Final atmosphere additive componentMultBy(vars.hazeColor, LLColor3::white - temp1); - -/* - // SL-12574 - - // Attenuate cloud color by atmosphere - temp1 = componentSqrt(temp1); //less atmos opacity (more transparency) below clouds - - // At horizon, blend high altitude sky color towards the darker color below the clouds - vars.hazeColor += componentMult(vars.hazeColorBelowCloud - vars.hazeColor, LLColor3::white - componentSqrt(temp1)); -*/ } void LLAtmospherics::updateFog(const F32 distance, const LLVector3& tosun_in) @@ -420,28 +312,20 @@ void LLAtmospherics::updateFog(const F32 distance, const LLVector3& tosun_in) LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - // NOTE: This is very similar to ALSky::cacheEnvironment() - // Differences: - // vars.sun_norm - // vars.sunlight - // invariants across whole sky tex process... vars.blue_density = psky->getBlueDensity(); vars.blue_horizon = psky->getBlueHorizon(); vars.haze_density = psky->getHazeDensity(); vars.haze_horizon = psky->getHazeHorizon(); vars.density_multiplier = psky->getDensityMultiplier(); - vars.distance_multiplier = psky->getDistanceMultiplier(); vars.max_y = psky->getMaxY(); // calcSkyColorWLVert wants OGL order ([1] is elevation), not CFR (y, x, z) vars.sun_norm = LLEnvironment::instance().getClampedLightNorm(); vars.sunlight = psky->getSunlightColor(); vars.ambient = psky->getAmbientColor(); vars.glow = psky->getGlow(); - vars.cloud_shadow = psky->getCloudShadow(); vars.dome_radius = psky->getDomeRadius(); vars.dome_offset = psky->getDomeOffset(); vars.light_atten = psky->getLightAttenuation(vars.max_y); - vars.light_transmittance = psky->getLightTransmittance(vars.max_y); vars.total_density = psky->getTotalDensity(); vars.gamma = psky->getGamma(); @@ -499,297 +383,3 @@ void LLAtmospherics::updateFog(const F32 distance, const LLVector3& tosun_in) stop_glerror(); } - -// Functions used a lot. -F32 color_norm_pow(LLColor3& col, F32 e, bool postmultiply) -{ - F32 mv = color_max(col); - if (0 == mv) - { - return 0; - } - - col *= 1.f / mv; - color_pow(col, e); - if (postmultiply) - { - col *= mv; - } - return mv; -} - -// Returns angle (RADIANs) between the horizontal projection of "v" and the x_axis. -// Range of output is 0.0f to 2pi //359.99999...f -// Returns 0.0f when "v" = +/- z_axis. -F32 azimuth(const LLVector3 &v) -{ - F32 azimuth = 0.0f; - if (v.mV[VX] == 0.0f) - { - if (v.mV[VY] > 0.0f) - { - azimuth = F_PI * 0.5f; - } - else if (v.mV[VY] < 0.0f) - { - azimuth = F_PI * 1.5f;// 270.f; - } - } - else - { - azimuth = (F32) atan(v.mV[VY] / v.mV[VX]); - if (v.mV[VX] < 0.0f) - { - azimuth += F_PI; - } - else if (v.mV[VY] < 0.0f) - { - azimuth += F_PI * 2; - } - } - return azimuth; -} - -bool operator==(const AtmosphericsVars& a, const AtmosphericsVars& b) -{ - if (a.hazeColor != b.hazeColor) - { - return false; - } - - if (a.hazeColorBelowCloud != b.hazeColorBelowCloud) - { - return false; - } - - if (a.cloudColorSun != b.cloudColorSun) - { - return false; - } - - if (a.cloudColorAmbient != b.cloudColorAmbient) - { - return false; - } - - if (a.cloudDensity != b.cloudDensity) - { - return false; - } - - if (a.density_multiplier != b.density_multiplier) - { - return false; - } - - if (a.haze_horizon != b.haze_horizon) - { - return false; - } - - if (a.haze_density != b.haze_density) - { - return false; - } - - if (a.blue_horizon != b.blue_horizon) - { - return false; - } - - if (a.blue_density != b.blue_density) - { - return false; - } - - if (a.dome_offset != b.dome_offset) - { - return false; - } - - if (a.dome_radius != b.dome_radius) - { - return false; - } - - if (a.cloud_shadow != b.cloud_shadow) - { - return false; - } - - if (a.glow != b.glow) - { - return false; - } - - if (a.ambient != b.ambient) - { - return false; - } - - if (a.sunlight != b.sunlight) - { - return false; - } - - if (a.sun_norm != b.sun_norm) - { - return false; - } - - if (a.gamma != b.gamma) - { - return false; - } - - if (a.max_y != b.max_y) - { - return false; - } - - if (a.distance_multiplier != b.distance_multiplier) - { - return false; - } - - // light_atten, light_transmittance, total_density - // are ignored as they always change when the values above do - // they're just shared calc across the sky map generation to save cycles - - return true; -} - -bool approximatelyEqual(const F32 &a, const F32 &b, const F32 &fraction_treshold) -{ - F32 diff = fabs(a - b); - if (diff < F_APPROXIMATELY_ZERO || diff < llmax(fabs(a), fabs(b)) * fraction_treshold) - { - return true; - } - return false; -} - -bool approximatelyEqual(const LLColor3 &a, const LLColor3 &b, const F32 &fraction_treshold) -{ - return approximatelyEqual(a.mV[0], b.mV[0], fraction_treshold) - && approximatelyEqual(a.mV[1], b.mV[1], fraction_treshold) - && approximatelyEqual(a.mV[2], b.mV[2], fraction_treshold); -} - -bool approximatelyEqual(const LLVector4 &a, const LLVector4 &b, const F32 &fraction_treshold) -{ - return approximatelyEqual(a.mV[0], b.mV[0], fraction_treshold) - && approximatelyEqual(a.mV[1], b.mV[1], fraction_treshold) - && approximatelyEqual(a.mV[2], b.mV[2], fraction_treshold) - && approximatelyEqual(a.mV[3], b.mV[3], fraction_treshold); -} - -bool approximatelyEqual(const AtmosphericsVars& a, const AtmosphericsVars& b, const F32 fraction_treshold) -{ - if (!approximatelyEqual(a.hazeColor, b.hazeColor, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.hazeColorBelowCloud, b.hazeColorBelowCloud, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.cloudColorSun, b.cloudColorSun, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.cloudColorAmbient, b.cloudColorAmbient, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.cloudDensity, b.cloudDensity, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.density_multiplier, b.density_multiplier, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.haze_horizon, b.haze_horizon, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.haze_density, b.haze_density, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.blue_horizon, b.blue_horizon, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.blue_density, b.blue_density, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.dome_offset, b.dome_offset, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.dome_radius, b.dome_radius, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.cloud_shadow, b.cloud_shadow, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.glow, b.glow, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.ambient, b.ambient, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.sunlight, b.sunlight, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.sun_norm, b.sun_norm, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.gamma, b.gamma, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.max_y, b.max_y, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.distance_multiplier, b.distance_multiplier, fraction_treshold)) - { - return false; - } - - // light_atten, light_transmittance, total_density - // are ignored as they always change when the values above do - // they're just shared calc across the sky map generation to save cycles - - return true; -} - diff --git a/indra/newview/lllegacyatmospherics.h b/indra/newview/lllegacyatmospherics.h index a8194e2cfbd..b8bd030153c 100644 --- a/indra/newview/lllegacyatmospherics.h +++ b/indra/newview/lllegacyatmospherics.h @@ -1,6 +1,6 @@ /** * @file lllegacyatmospherics.h - * @brief LLVOSky class header file + * @brief The fog colour the tracker's beacons fade into: WindLight's sky near the horizon, worked on the CPU. * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code @@ -29,164 +29,18 @@ #include "stdtypes.h" #include "v3color.h" -#include "v4coloru.h" -#include "llviewertexture.h" -#include "llviewerobject.h" -#include "llframetimer.h" -#include "v3colorutil.h" +#include "v3math.h" +#include "v4color.h" +#include "v4math.h" #include "llsettingssky.h" -////////////////////////////////// -// -// Lots of constants -// -// Will clean these up at some point... -// - const F32 HORIZON_DIST = 1024.0f; -const F32 ATM_EXP_FALLOFF = 0.000126f; -const F32 ATM_SEA_LEVEL_NDENS = 2.55e25f; -const F32 ATM_HEIGHT = 100000.f; - -// constants used in calculation of scattering coeff of clear air -const F32 sigma = 0.035f; -const F32 fsigma = (6.f + 3.f * sigma) / (6.f-7.f*sigma); -const F64 Ndens = 2.55e25; -const F64 Ndens2 = Ndens*Ndens; - -class LLFace; -class LLHaze; - -LL_FORCE_INLINE LLColor3 refr_ind_calc(const LLColor3 &wave_length) -{ - LLColor3 refr_ind; - for (S32 i = 0; i < 3; ++i) - { - const F32 wl2 = wave_length.mV[i] * wave_length.mV[i] * 1e-6f; - refr_ind.mV[i] = 6.43e3f + ( 2.95e6f / ( 146.0f - 1.f/wl2 ) ) + ( 2.55e4f / ( 41.0f - 1.f/wl2 ) ); - refr_ind.mV[i] *= 1.0e-8f; - refr_ind.mV[i] += 1.f; - } - return refr_ind; -} - - -class LLHaze -{ -public: - LLHaze() : mG(0), mFalloff(1), mAbsCoef(0.f) {mSigSca.setToBlack();} - LLHaze(const F32 g, const LLColor3& sca, const F32 fo = 2.f) : - mG(g), mSigSca(0.25f/F_PI * sca), mFalloff(fo), mAbsCoef(0.f) - { - mAbsCoef = color_intens(mSigSca) / sAirScaIntense; - } - - LLHaze(const F32 g, const F32 sca, const F32 fo = 2.f) : mG(g), - mSigSca(0.25f/F_PI * LLColor3(sca, sca, sca)), mFalloff(fo) - { - mAbsCoef = 0.01f * sca / sAirScaAvg; - } - -/* Proportion of light that is scattered into 'path' from 'in' over distance dt. */ -/* assumes that vectors 'path' and 'in' are normalized. Scattering coef / 2pi */ - - LL_FORCE_INLINE LLColor3 calcAirSca(const F32 h) - { - return calcFalloff(h) * sAirScaSeaLevel; - } - - LL_FORCE_INLINE void calcAirSca(const F32 h, LLColor3 &result) - { - result = sAirScaSeaLevel; - result *= calcFalloff(h); - } - - F32 getG() const { return mG; } - - void setG(const F32 g) - { - mG = g; - } - - const LLColor3& getSigSca() const // sea level - { - return mSigSca; - } - - void setSigSca(const LLColor3& s) - { - mSigSca = s; - mAbsCoef = 0.01f * color_intens(mSigSca) / sAirScaIntense; - } - - void setSigSca(const F32 s0, const F32 s1, const F32 s2) - { - mSigSca = sAirScaAvg * LLColor3 (s0, s1, s2); - mAbsCoef = 0.01f * (s0 + s1 + s2) / 3; - } - - F32 getFalloff() const - { - return mFalloff; - } - - void setFalloff(const F32 fo) - { - mFalloff = fo; - } - - F32 getAbsCoef() const - { - return mAbsCoef; - } - - inline static F32 calcFalloff(const F32 h) - { - return (h <= 0) ? 1.0f : (F32)ll_fast_exp(-ATM_EXP_FALLOFF * h); - } - - inline LLColor3 calcSigSca(const F32 h) const - { - return calcFalloff(h * mFalloff) * mSigSca; - } - - inline void calcSigSca(const F32 h, LLColor3 &result) const - { - result = mSigSca; - result *= calcFalloff(h * mFalloff); - } - - LLColor3 calcSigExt(const F32 h) const - { - return calcFalloff(h * mFalloff) * (1 + mAbsCoef) * mSigSca; - } - - F32 calcPhase(const F32 cos_theta) const; - -private: - static LLColor3 const sAirScaSeaLevel; - static F32 const sAirScaIntense; - static F32 const sAirScaAvg; - -protected: - F32 mG; - LLColor3 mSigSca; - F32 mFalloff; // 1 - slow, >1 - faster - F32 mAbsCoef; -}; - - -class LLCubeMap; class AtmosphericsVars { public: AtmosphericsVars() : hazeColor(0,0,0) - , hazeColorBelowCloud(0,0,0) - , cloudColorSun(0,0,0) - , cloudColorAmbient(0,0,0) - , cloudDensity(0.0f) , blue_density() , blue_horizon() , haze_density(0.0f) @@ -198,83 +52,54 @@ class AtmosphericsVars , sunlight() , ambient() , glow() - , cloud_shadow(1.0f) , dome_radius(1.0f) , dome_offset(1.0f) , light_atten() - , light_transmittance() { } - friend bool operator==(const AtmosphericsVars& a, const AtmosphericsVars& b); - // returns true if values are within treshold of each other. - friend bool approximatelyEqual(const AtmosphericsVars& a, const AtmosphericsVars& b, const F32 fraction_treshold); - LLColor3 hazeColor; - LLColor3 hazeColorBelowCloud; - LLColor3 cloudColorSun; - LLColor3 cloudColorAmbient; - F32 cloudDensity; LLColor3 blue_density; LLColor3 blue_horizon; F32 haze_density; F32 haze_horizon; F32 density_multiplier; - F32 distance_multiplier; F32 max_y; F32 gamma; LLVector4 sun_norm; LLColor3 sunlight; LLColor3 ambient; LLColor3 glow; - F32 cloud_shadow; F32 dome_radius; F32 dome_offset; LLColor3 light_atten; - LLColor3 light_transmittance; LLColor3 total_density; }; +/// The sky's colour a little above the horizon, toward the light, across from it and between, which the tracker's +/// beacons fade into; and the colour the GL fog took, the water's below its surface. class LLAtmospherics { public: LLAtmospherics(); ~LLAtmospherics(); - void init(); + /// Works both colours out from the current sky, toward @a tosun (+z up), while the render debug feature Fog is on. void updateFog(const F32 distance, const LLVector3& tosun); - const LLHaze& getHaze() const { return mHaze; } - LLHaze& getHaze() { return mHaze; } - F32 getHazeConcentration() const { return mHazeConcentration; } - void setHaze(const LLHaze& h) { mHaze = h; } void setFogRatio(const F32 fog_ratio) { mFogRatio = fog_ratio; } F32 getFogRatio() const { return mFogRatio; } LLColor4 getFogColor() const { return mFogColor; } LLColor4 getGLFogColor() const { return mGLFogCol; } - void setCloudDensity(F32 cloud_density) { mCloudDensity = cloud_density; } - void setWind ( const LLVector3& wind ) { mWind = wind.length(); } - - LLColor4 calcSkyColorInDir(const LLSettingsSky::ptr_t &psky, AtmosphericsVars& vars, const LLVector3& dir, bool isShiny = false, const bool low_end = false); - -protected: - +private: + LLColor4 calcSkyColorInDir(const LLSettingsSky::ptr_t &psky, AtmosphericsVars& vars, const LLVector3& dir); void calcSkyColorWLVert(const LLSettingsSky::ptr_t &psky, LLVector3 & Pn, AtmosphericsVars& vars); - LLHaze mHaze; - F32 mHazeConcentration; - F32 mCloudDensity; - F32 mWind; - bool mInitialized; - LLVector3 mLastLightingDirection; - LLColor3 mLastTotalAmbient; - F32 mInterpVal; LLColor4 mFogColor; LLColor4 mGLFogCol; F32 mFogRatio; - F32 mWorldScale; }; #endif diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index 5a1b3a83881..1212534371a 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -780,8 +780,7 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) LLVOAvatar::allocateImpostors(); } - // The sky as the frame begins with it, before anything draws: the mirrors below draw it, and the previews and - // impostor bakes read its environment cube. + // The sky as the frame begins with it, before anything draws: the mirrors below draw it. if (!gDisconnected && !LLApp::isExiting()) { LLAppViewer::instance()->pingMainloopTimeout("Display:Sky"); @@ -843,7 +842,8 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Env Update"); // update all the sky/atmospheric/water settings LLEnvironment::instance().update(LLViewerCamera::getInstance()); - // The sky's bodies and textures as the update has moved it on, for the main view. + // The sky's bodies and textures as the update has moved it on, for the main view, and its fog colour and + // environment cube from the Environment block the main view takes. gPipeline.mSky.followEnvironment(); } diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index 4cf6070bed4..6ccfec627ed 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -223,6 +223,7 @@ LLGLSLShader gCASProgram; LLGLSLShader gDeferredPostNoDoFProgram; LLGLSLShader gDeferredStarProgram; LLGLSLShader gDeferredStarQuadProgram; +LLGLSLShader gDeferredSkyCubeProgram; LLGLSLShader gDeferredFullbrightShinyProgram; LLGLSLShader gHUDFullbrightShinyProgram; LLGLSLShader gNormalMapGenProgram; @@ -1382,6 +1383,7 @@ void LLViewerShaderMgr::loadShadersDeferred() gCASProgram.unload(); gDeferredStarProgram.unload(); gDeferredStarQuadProgram.unload(); + gDeferredSkyCubeProgram.unload(); gDeferredFullbrightShinyProgram.unload(); gHUDFullbrightShinyProgram.unload(); @@ -3169,6 +3171,21 @@ void LLViewerShaderMgr::loadShadersDeferred() built(gDeferredStarQuadProgram.createShader(), gDeferredStarQuadProgram); } + if (!mInputs.mGlobal.mProbes) + { + // WindLight's dome baked into the environment cube the shiny and probe-less reflections read (ALSkyCube), where + // reflection probes are off. + gDeferredSkyCubeProgram.mName = "Sky Environment Cube Program"; + gDeferredSkyCubeProgram.mShaderFiles.clear(); + gDeferredSkyCubeProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); + gDeferredSkyCubeProgram.mShaderFiles.push_back(make_pair("deferred/skyCubeF.glsl", GL_FRAGMENT_SHADER)); + gDeferredSkyCubeProgram.mShaderFiles.push_back(make_pair("windlight/windlightDomeF.glsl", GL_FRAGMENT_SHADER)); + gDeferredSkyCubeProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredSkyCubeProgram.mShaderGroup = LLGLSLShader::SG_SKY; + + built(gDeferredSkyCubeProgram.createShader(), gDeferredSkyCubeProgram); + } + { gNormalMapGenProgram.mName = "Normal Map Generation Program"; gNormalMapGenProgram.mShaderFiles.clear(); diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h index 28e5e70616f..97c5d76c648 100644 --- a/indra/newview/llviewershadermgr.h +++ b/indra/newview/llviewershadermgr.h @@ -286,6 +286,7 @@ extern LLGLSLShader gDeferredEmissiveIndexedProgram; // multi-material i extern LLGLSLShader gDeferredAvatarAlphaProgram; extern LLGLSLShader gDeferredStarProgram; extern LLGLSLShader gDeferredStarQuadProgram; +extern LLGLSLShader gDeferredSkyCubeProgram; extern LLGLSLShader gDeferredFullbrightShinyProgram; extern LLGLSLShader gHUDFullbrightShinyProgram; extern LLGLSLShader gNormalMapGenProgram; diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index ce4c045f982..8c1e7395fc9 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -959,6 +959,7 @@ U32* LLPipeline::resourceTexture(ALRenderResourcePlan::Id id) case Id::SMAASearch: return &mSMAASearchMap; case Id::SMAASample: return &mSMAASampleMap; case Id::ToneCurve: return &mToneCurveLut; + case Id::EnvironmentCube: return mSky.environmentCubeTexture(); default: return nullptr; } @@ -1868,6 +1869,11 @@ void LLPipeline::createResourceTexture(ALRenderResourcePlan::Id id) switch (id) { + case Id::EnvironmentCube: + // Allocated neutral, and baked when the sky next takes the frame's environment. + mSky.allocateEnvironmentCube(); + break; + case Id::Noise: { std::vector noise(noise_res * noise_res); From 5f008b9680ed306340c06722356e0eb830d3d7ff Mon Sep 17 00:00:00 2001 From: Rye Date: Wed, 21 Oct 2026 09:03:21 -0400 Subject: [PATCH 03/11] Describe the sky as ALSky draws it in ARCHITECTURE.md The sky is no longer a viewer object, a pool or a cull push: say what holds and draws it, beside the water's paragraph, and drop LLVOWLSky, POOL_WL_SKY and the sky pushes from the cutout's choke points. The paragraph says when ALSky takes the sky, at the start of display before the mirrors draw it and again once the environment update has moved it on, before the main view; that the environment cube is neutral grey until its first bake; that the star field is laid out on the General queue; and that each view takes what it draws of the sky once, at its G-buffer's clear, which clears the stars' attachment only where the view draws them. Co-Authored-By: Claude Opus 5.5 --- doc/ARCHITECTURE.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/ARCHITECTURE.md b/doc/ARCHITECTURE.md index 180f579b6b6..c7226d0d7e2 100644 --- a/doc/ARCHITECTURE.md +++ b/doc/ARCHITECTURE.md @@ -81,10 +81,12 @@ Every parser takes its input as hostile: an LLSD capability response picks its o `LLViewerObject` (`indra/newview/llviewerobject.h`) is the base for all world objects, deriving from `LLPrimitive`. Key subclasses: - **`LLVOVolume`** (`llvovolume.h`) — Most common type: prims, meshes, sculpts. Contains volume geometry with multiple faces, materials, texture animation, media-on-surface, flex. A sculpt samples its map at its detail's mesh; where the samples of a map sampled more than two pixels apart have no area (an oblong map with a collapsed line every few, at a coarser detail), `LLVolume::sculpt` samples the map averaged down by two along that axis, until they have or none is left, before it builds the sphere - **`LLVOAvatar`** (`llvoavatar.h`) — Avatar with skeletal system, mesh deformation, animation blending, appearance layering, impostor rendering. `LLVOAvatarSelf` is the player's own avatar -- **`LLVOGrass`**, **`LLVOTree`**, **`LLVOSurfacePatch`** (terrain), **`LLVOPartGroup`** (particles), **`LLVOWLSky`** (sky dome) +- **`LLVOGrass`**, **`LLVOTree`**, **`LLVOSurfacePatch`** (terrain), **`LLVOPartGroup`** (particles) Water is not an object. A region's water height is `LLSurface`'s; `ALWaterSurface` (`alwatersurface.h`, owned by `LLPipeline`) lays the water out around the main camera at the start of each frame's display, before the mirrors, the shadow fit and the probes read it, from `ALWaterField` (`alwaterfield.h`, pure arithmetic): a square of region-sized cells over the loaded regions, each at its region's height and the empty ones at the agent region's, and rings of quads doubling in size out to the rim. A 32x32 cell map holds the heights, and the water is drawn instanced with no vertex buffer, placed by `environment/waterSurface.glsl` in parts (`ALWaterField::Draw`): the cells, the rings at three triangles a quad, ring 0's four sides fanned onto the square's cell corners, and, with waves, the cells beside the patches fanned onto the patches' corners. Every vertex two parts share is worked from the same exact multiples and lies on no edge it is not a corner of, so the field has no T-junction for a crack to open along. With `AlchemyRenderWaterWaves` on a GL 4.0 context, the main view draws the cells within the tessellation reach instead as 32 m tessellation patches (probe captures and mirrors, whose hero probe is a cube of faces, draw the field flat) (`gWaterWavesProgram`: `waterPatchV`, `waterTC`, `waterTE`; built, with the depth pyramid's program, only while the waves are wanted, and their failing leaves the water flat), their edges past the reach left whole so they meet the cells beside them corner to corner, displaced by heights integrated from the normal maps that shade the surface: `ALWaterHeightBake` (pocketfft, Frankot-Chellappa) bakes each map on the General queue and `ALWaterSurface` keeps the heights as RG16F textures. The waves' bound, A, widens the shadow fit and the field's slab; the plane stays the plane for every height consumer. The patches are drawn before the flat field and front to back along the view (`waterPatchOrder`), so the depth test turns away water a nearer crest hides before it is shaded, and the control stage drops a patch whose box is out of the frustum or, in the main view where the session computes (`AlchemyRenderWaterOcclusionCull`), wholly behind the scene's depth, through `ALDepthPyramid` built from the depth the water pass copied. +The sky is not an object either. `ALSky` (`alsky.h`, owned by `LLPipeline`) takes the frame's sky at the start of each frame's display, before the mirrors draw it: the sun and moon and their quads, and the sky's textures at the priority they are drawn at. It takes them again once the frame's environment update has moved the sky on, before the main view (`followEnvironment`), and with them, where reflection probes are off, the environment cube the shiny and probe-less reflections read, baked from `windlightDome` when the sky changes (`ALSkyCube`) and neutral grey until its first bake. It holds the star field, laid out on the General queue and drawn once it lands, its buffer and the meteors. Each view takes what it draws of the sky once, at its G-buffer's clear (`ALSky::beginView`, from `LLPipeline::beginSkyView`, which clears the stars' attachment only where the view draws them); ALSky then draws the stars after `renderGeomDeferred`'s pool loop, binds the sky's pass (`gDeferredSkyCompositeProgram`) and the dome for the haze on water; nothing of it is culled or drawn through a pool. + `LLViewerObjectList` (`llviewerobjectlist.h`) is the global registry tracking all objects by UUID. ### Rendering Pipeline @@ -157,7 +159,7 @@ A free target goes only to an acquisition of exactly its description and shared Post-processing settings are exposed in the Lightbox floater (`ALFloaterLightBox`); see `doc/LIGHTBOX.md` for how to add UI sections for new effects. **Snapshot cutouts.** The snapshot floater's Cutout mode (and File > Take Snapshot to Disk while it is on) draws one avatar with what it wears, or one linkset, and fills the rest with a colour whose alpha may be 0. The request is `ALSnapshotCutout`, built only by `ALSnapshotCutout::fromSettings()`; the subject is armed in `ALRenderIsolation`, whose `Scope` covers the frames of one `rawSnapshot`: -- **Cull:** `LLPipeline::cullingToCutoutSubject()` is true for the main camera's pass only, so shadows, probes and impostor bakes keep the whole world and the subject stays lit where it stands. The choke points are the two sky pushes in `updateCull`, the render-by-group branch of `markNotCulled`, `markVisible` and a `stateSort(LLDrawable*)` backstop; what they turn away is still marked visible, for object-cache eviction, LOD and texture priority. A static linkset is made active on arming, since only a bridge of its own separates its geometry from its neighbours'. The water is drawn from its field rather than culled, so the water pool runs no pass there (`LLDrawPoolWater::getNumPostDeferredPasses`). +- **Cull:** `LLPipeline::cullingToCutoutSubject()` is true for the main camera's pass only, so shadows, probes and impostor bakes keep the whole world and the subject stays lit where it stands. The choke points are the render-by-group branch of `markNotCulled`, `markVisible` and a `stateSort(LLDrawable*)` backstop; what they turn away is still marked visible, for object-cache eviction, LOD and texture priority. A static linkset is made active on arming, since only a bridge of its own separates its geometry from its neighbours'. The water is drawn from its field rather than culled, so the water pool runs no pass there (`LLDrawPoolWater::getNumPostDeferredPasses`). - **Coverage:** kept in `mRT->screen`'s alpha as an impostor bake keeps it — `seedCutoutCoverageFromDepth` once the opaque forward pools (fullbright, shiny) have drawn and before the first blended one, then the alpha pool's blend (`alphaWritesCoverage()`), which the haze pass leaves alone — and written into the finished frame's alpha by `copyCutoutCoverageToAlpha`, since nothing in the post chain carries usable alpha. `colorCorrect` divides coverage out before grading and multiplies it back after (`uCutoutPremultiplied`). - **Post:** a cutout frame drops the lens and print effects (`suppressesLensAndPrintEffects()`), measures nothing (no metering, SSR copy, scopes or pixel probe) and runs nothing that spreads past a pixel (DoF, bloom, glow, AA, CAS); `render_ui` draws nothing over it. Edges come from rendering offscreen at a multiple of the size (the screen targets' size, with the resolution multiplier, held under the floater's largest snapshot) and averaging down while premultiplied in `ALCutoutImage::resolve`; `ALCutoutImage::layOverFill` then composites over the fill and keeps alpha only where the format can (`LLSnapshotModel::keepsAlpha`). The preview keeps the resolved cutout, so a new fill or format is laid down again without a render (`ALSnapshotCutout::drawsSameAs`). @@ -191,7 +193,7 @@ Key shader groups: GBuffer write (`gDeferredDiffuseProgram`, `gDeferredPBROpaque ### Draw Pool & Spatial System -**Draw pools** (`indra/newview/lldrawpool.h` and `lldrawpool*.h`) group geometry by material type for batched rendering. Each pool corresponds to a render pass. Key pools: `POOL_SIMPLE`, `POOL_BUMP`, `POOL_MATERIALS` (legacy), `POOL_GLTF_PBR` (PBR opaque), `POOL_GLTF_PBR_ALPHA_MASK`, `POOL_TERRAIN`, `POOL_AVATAR`, `POOL_CONTROL_AV` (animesh), `POOL_ALPHA_PRE_WATER`/`POOL_ALPHA_POST_WATER`, `POOL_WATER` (no faces: it draws the water field), `POOL_WL_SKY`, `POOL_GLOW`. Many pools have `_RIGGED` pass variants for skinned geometry. +**Draw pools** (`indra/newview/lldrawpool.h` and `lldrawpool*.h`) group geometry by material type for batched rendering. Each pool corresponds to a render pass. Key pools: `POOL_SIMPLE`, `POOL_BUMP`, `POOL_MATERIALS` (legacy), `POOL_GLTF_PBR` (PBR opaque), `POOL_GLTF_PBR_ALPHA_MASK`, `POOL_TERRAIN`, `POOL_AVATAR`, `POOL_CONTROL_AV` (animesh), `POOL_ALPHA_PRE_WATER`/`POOL_ALPHA_POST_WATER`, `POOL_WATER` (no faces: it draws the water field), `POOL_GLOW`. Many pools have `_RIGGED` pass variants for skinned geometry. **Spatial partitioning** uses an octree. `LLSpatialPartition` (`indra/newview/llspatialpartition.h`) is the base, with specialized partitions: `LLVolumePartition`, `LLTerrainPartition`, `LLTreePartition`, `LLGrassPartition`, `LLParticlePartition`. `LLSpatialGroup` is an octree node grouping drawable geometry. `LLDrawInfo` encapsulates a single draw call (vertex buffer, index range, textures, material, blend mode, skin info). `LLCullResult` stores per-frame visible geometry organized by render type. The cull (`LLViewerOctreeCull`) visits each node's children nearest the camera first (`LLOctreeTraveler::traverseNearFirst`, by the node's child map), and the render map keeps the order groups were found in, so opaque geometry reaches the GPU roughly front to back; culls whose results keep no order (the object cache's, selection) and the sun's shadow cameras, which stand at the origin, keep the children's own order. `LLPipeline::updateCull` tests each region's partitions' roots together first, against the camera's frustum without its far plane, which is no stricter than any partition's own cull of its root, and culls none of a region whose content lies outside it; the object cache's cull, which picks cached objects behind the camera too, runs either way. No node that can still split holds more than `OctreeMaxNodeCapacity` elements: an element too big for a node stays below its own size's node only while that node is full, and passes on to a child of its own once the node it stays in is full too, so that a group does not gather more faces than a rebuild draws (`MAX_FACE_COUNT`, warned of once where it does). Only a node within twice `OctreeMinimumNodeSize`, whose children would sit at its centre, takes whatever comes. From 53b3c362cbc3ee944c9f1b1f5f290008f6c6b5d1 Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 26 Oct 2026 14:16:36 -0400 Subject: [PATCH 04/11] Keep only the beacons' fog colour in LLAtmospherics LLAtmospherics worked out three things on each sky update: the fog colour the tracker's beacons fade into, a fog distance from the fog ratio, and a GL fog colour, the water's below its surface. Only the first has a reader. The fog distance went into a local, and nothing has asked for the GL fog colour or the ratio since before the sky moved to ALSky (develop's LLSky::getFogRatio and LLVOSky::getGLFogColor had no caller either). So the ratio goes (ALSky::setFogRatio/getFogRatio, its first-update read of RenderFogRatio and the setting's listener), and so do the GL fog colour and its underwater branch with the two settings only it read, WaterGLFogDepthScale and WaterGLFogDepthFloor. updateFog takes the direction to the light alone, and the file drops the includes only those paths needed. RenderFogRatio goes too, from settings.xml and its Portuguese comment, the three feature tables and the feature manager's skipped list. The test spinner floater's control spinner takes RenderDebugNormalScale in its place, a setting only the normals' debug display reads, its range 0 to 1 at two decimals. Co-Authored-By: Claude Opus 5.5 --- indra/newview/alsky.cpp | 3 +- indra/newview/alsky.h | 3 - indra/newview/app_settings/settings.xml | 33 ----------- indra/newview/featuretable.txt | 1 - indra/newview/featuretable_linux.txt | 1 - indra/newview/featuretable_mac.txt | 1 - indra/newview/llfeaturemanager.cpp | 1 - indra/newview/lllegacyatmospherics.cpp | 58 +------------------ indra/newview/lllegacyatmospherics.h | 12 +--- indra/newview/llviewercontrol.cpp | 11 ---- .../default/xui/en/floater_test_spinner.xml | 8 +-- .../default/xui/pt/settings_comments.xml | 6 -- 12 files changed, 9 insertions(+), 129 deletions(-) diff --git a/indra/newview/alsky.cpp b/indra/newview/alsky.cpp index a76f87402da..e70407fb1df 100644 --- a/indra/newview/alsky.cpp +++ b/indra/newview/alsky.cpp @@ -173,7 +173,6 @@ void ALSky::update() mStarCountConnection = gSavedSettings.getControl("RenderStarCount")->getSignal()->connect( [this](LLControlVariable*, const LLSD&, const LLSD&) { requestStars(); }); - setFogRatio(gSavedSettings.getF32("RenderFogRatio")); mReady = true; } @@ -1144,7 +1143,7 @@ void ALSky::updateEnvironment() LL_PROFILE_ZONE_NAMED_CATEGORY_ENVIRONMENT("sky environment"); // The fog colour the tracker's beacons fade into. - mAtmospherics.updateFog(LLViewerCamera::getInstance()->getFar(), LLEnvironment::instance().getClampedLightDirection()); + mAtmospherics.updateFog(LLEnvironment::instance().getClampedLightDirection()); // The sky is taken once the cube holds it, or where there is no cube to bake. mEnvironmentUpdated = now; diff --git a/indra/newview/alsky.h b/indra/newview/alsky.h index 3898a92dae6..bd3cee43421 100644 --- a/indra/newview/alsky.h +++ b/indra/newview/alsky.h @@ -150,9 +150,6 @@ class ALSky /// The sun's direction pushed south as it nears the zenith, so bump maps always show on cubes' upward faces. const LLVector3& getBumpSunDir() const { return mBumpSunDir; } - /// Fog distance as a fraction of the cull distance. - void setFogRatio(F32 fog_ratio) { mAtmospherics.setFogRatio(fog_ratio); } - F32 getFogRatio() const { return mAtmospherics.getFogRatio(); } /// The sky's colour near the horizon, which the tracker's beacons fade into. LLColor4 getSkyFogColor() const { return mAtmospherics.getFogColor(); } diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index a336995e688..7a220e57216 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -9035,17 +9035,6 @@ Value 1.0 - RenderFogRatio - - Comment - DEPRECATED - Distance from camera where fog reaches maximum density (fraction or multiple of far clip distance) - Persist - 1 - Type - F32 - Value - 4.0 - RenderGamma Comment @@ -13987,28 +13976,6 @@ Value 0.02 - WaterGLFogDepthFloor - - Comment - Controls how dark water gl fog can get - Persist - 1 - Type - F32 - Value - 0.25 - - WaterGLFogDepthScale - - Comment - Controls how quickly gl fog gets dark under water - Persist - 1 - Type - F32 - Value - 50.0 - FlashCount Comment diff --git a/indra/newview/featuretable.txt b/indra/newview/featuretable.txt index a632d46a231..1e26c34c3b9 100644 --- a/indra/newview/featuretable.txt +++ b/indra/newview/featuretable.txt @@ -35,7 +35,6 @@ RenderAvatarMaxComplexity 1 350000 RenderAutoMuteSurfaceAreaLimit 1 1000.0 RenderFarClip 1 256 RenderFlexTimeFactor 1 1.0 -RenderFogRatio 1 4.0 RenderGamma 1 0 RenderGlowResolutionPow 1 9 RenderMaxPartCount 1 8192 diff --git a/indra/newview/featuretable_linux.txt b/indra/newview/featuretable_linux.txt index 3527d5d03ce..0adbdb8c37d 100644 --- a/indra/newview/featuretable_linux.txt +++ b/indra/newview/featuretable_linux.txt @@ -35,7 +35,6 @@ RenderAvatarMaxComplexity 1 350000 RenderAutoMuteSurfaceAreaLimit 1 1000.0 RenderFarClip 1 256 RenderFlexTimeFactor 1 1.0 -RenderFogRatio 1 4.0 RenderGamma 1 0 RenderGlowResolutionPow 1 9 RenderMaxPartCount 1 8192 diff --git a/indra/newview/featuretable_mac.txt b/indra/newview/featuretable_mac.txt index 82408bc50b1..6334f12b987 100644 --- a/indra/newview/featuretable_mac.txt +++ b/indra/newview/featuretable_mac.txt @@ -35,7 +35,6 @@ RenderAvatarMaxComplexity 1 350000 RenderAutoMuteSurfaceAreaLimit 1 1000.0 RenderFarClip 1 256 RenderFlexTimeFactor 1 1.0 -RenderFogRatio 1 4.0 RenderGamma 1 0 RenderGlowResolutionPow 1 9 RenderMaxPartCount 1 8192 diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp index 9b039b30bc7..b4f29c79118 100644 --- a/indra/newview/llfeaturemanager.cpp +++ b/indra/newview/llfeaturemanager.cpp @@ -266,7 +266,6 @@ bool LLFeatureManager::loadFeatureTables() // correctly mSkippedFeatures.insert("RenderGamma"); mSkippedFeatures.insert("RenderVBOEnable"); - mSkippedFeatures.insert("RenderFogRatio"); // first table is install with app std::string app_path = gDirUtilp->getAppRODataDir(); diff --git a/indra/newview/lllegacyatmospherics.cpp b/indra/newview/lllegacyatmospherics.cpp index ada2ac5cc28..2c1141041ef 100644 --- a/indra/newview/lllegacyatmospherics.cpp +++ b/indra/newview/lllegacyatmospherics.cpp @@ -28,26 +28,11 @@ #include "lllegacyatmospherics.h" -#include "llfeaturemanager.h" -#include "llviewercontrol.h" -#include "llframetimer.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "lldrawable.h" -#include "llface.h" -#include "llglheaders.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llworld.h" #include "pipeline.h" #include "v3colorutil.h" #include "llsettingssky.h" #include "llenvironment.h" -#include "lldrawpoolwater.h" class LLFastLn { @@ -145,7 +130,6 @@ LLAtmospherics::LLAtmospherics() { mFogColor.mV[VRED] = mFogColor.mV[VGREEN] = mFogColor.mV[VBLUE] = 0.5f; mFogColor.mV[VALPHA] = 0.0f; - mFogRatio = 1.2f; } @@ -266,7 +250,7 @@ void LLAtmospherics::calcSkyColorWLVert(const LLSettingsSky::ptr_t &psky, LLVect componentMultBy(vars.hazeColor, LLColor3::white - temp1); } -void LLAtmospherics::updateFog(const F32 distance, const LLVector3& tosun_in) +void LLAtmospherics::updateFog(const LLVector3& tosun_in) { LLVector3 tosun = tosun_in; @@ -275,20 +259,9 @@ void LLAtmospherics::updateFog(const F32 distance, const LLVector3& tosun_in) return; } - LLColor4 target_fog(0.f, 0.2f, 0.5f, 0.f); - - const F32 water_height = gAgent.getRegion() ? gAgent.getRegion()->getWaterHeight() : 0.f; - // LLWorld::getInstance()->getWaterHeight(); - F32 camera_height = gAgentCamera.getCameraPositionAgent().mV[2]; - - F32 near_clip_height = LLViewerCamera::getInstance()->getAtAxis().mV[VZ] * LLViewerCamera::getInstance()->getNear(); - camera_height += near_clip_height; - - F32 fog_distance = 0.f; LLColor3 res_color[3]; LLColor3 sky_fog_color = LLColor3::white; - LLColor3 render_fog_color = LLColor3::white; const F32 tosun_z = tosun.mV[VZ]; tosun.mV[VZ] = 0.f; @@ -351,35 +324,6 @@ void LLAtmospherics::updateFog(const F32 distance, const LLVector3& tosun_in) color_gamma_correct(sky_fog_color); - render_fog_color = sky_fog_color; - - fog_distance = mFogRatio * distance; - - if (camera_height > water_height) - { - LLColor4 fog(render_fog_color); - mGLFogCol = fog; - } - else - { - LLSettingsWater::ptr_t pwater = LLEnvironment::instance().getCurrentWater(); - F32 depth = water_height - camera_height; - LLColor4 water_fog_color(pwater->getWaterFogColor()); - - // adjust the color based on depth. We're doing linear approximations - float depth_scale = gSavedSettings.getF32("WaterGLFogDepthScale"); - float depth_modifier = 1.0f - llmin(llmax(depth / depth_scale, 0.01f), - gSavedSettings.getF32("WaterGLFogDepthFloor")); - - LLColor4 fogCol = water_fog_color * depth_modifier; - fogCol.setAlpha(1); - - // set the gl fog color - mGLFogCol = fogCol; - } - mFogColor = sky_fog_color; mFogColor.setAlpha(1); - - stop_glerror(); } diff --git a/indra/newview/lllegacyatmospherics.h b/indra/newview/lllegacyatmospherics.h index b8bd030153c..43e2dec6181 100644 --- a/indra/newview/lllegacyatmospherics.h +++ b/indra/newview/lllegacyatmospherics.h @@ -77,29 +77,23 @@ class AtmosphericsVars }; /// The sky's colour a little above the horizon, toward the light, across from it and between, which the tracker's -/// beacons fade into; and the colour the GL fog took, the water's below its surface. +/// beacons fade into. class LLAtmospherics { public: LLAtmospherics(); ~LLAtmospherics(); - /// Works both colours out from the current sky, toward @a tosun (+z up), while the render debug feature Fog is on. - void updateFog(const F32 distance, const LLVector3& tosun); + /// Works the colour out from the current sky, toward @a tosun (+z up), while the render debug feature Fog is on. + void updateFog(const LLVector3& tosun); - void setFogRatio(const F32 fog_ratio) { mFogRatio = fog_ratio; } - - F32 getFogRatio() const { return mFogRatio; } LLColor4 getFogColor() const { return mFogColor; } - LLColor4 getGLFogColor() const { return mGLFogCol; } private: LLColor4 calcSkyColorInDir(const LLSettingsSky::ptr_t &psky, AtmosphericsVars& vars, const LLVector3& dir); void calcSkyColorWLVert(const LLSettingsSky::ptr_t &psky, LLVector3 & Pn, AtmosphericsVars& vars); LLColor4 mFogColor; - LLColor4 mGLFogCol; - F32 mFogRatio; }; #endif diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 56f48ee3af2..e55de679db6 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -422,16 +422,6 @@ static bool handleGammaChanged(const LLSD& newvalue) return true; } -const F32 MAX_USER_FOG_RATIO = 10.f; -const F32 MIN_USER_FOG_RATIO = 0.5f; - -static bool handleFogRatioChanged(const LLSD& newvalue) -{ - F32 fog_ratio = llmax(MIN_USER_FOG_RATIO, llmin((F32) newvalue.asReal(), MAX_USER_FOG_RATIO)); - gPipeline.mSky.setFogRatio(fog_ratio); - return true; -} - static bool handleMaxPartCountChanged(const LLSD& newvalue) { LLViewerPartSim::setMaxPartCount(newvalue.asInteger()); @@ -1026,7 +1016,6 @@ void settings_setup_listeners() setting_setup_signal_listener(gSavedSettings, "RenderTreeLODFactor", handleTreeLODChanged); setting_setup_signal_listener(gSavedSettings, "RenderFlexTimeFactor", handleFlexLODChanged); setting_setup_signal_listener(gSavedSettings, "RenderGamma", handleGammaChanged); - setting_setup_signal_listener(gSavedSettings, "RenderFogRatio", handleFogRatioChanged); setting_setup_signal_listener(gSavedSettings, "RenderMaxPartCount", handleMaxPartCountChanged); setting_setup_signal_listener(gSavedSettings, "RenderDynamicLOD", handleRenderDynamicLODChanged); setting_setup_signal_listener(gSavedSettings, "RenderVSyncEnable", handleVSyncChanged); diff --git a/indra/newview/skins/default/xui/en/floater_test_spinner.xml b/indra/newview/skins/default/xui/en/floater_test_spinner.xml index 5fa0b20d4f8..d11e1f14bda 100644 --- a/indra/newview/skins/default/xui/en/floater_test_spinner.xml +++ b/indra/newview/skins/default/xui/en/floater_test_spinner.xml @@ -63,14 +63,14 @@ top_delta="0" width="200" /> diff --git a/indra/newview/skins/default/xui/pt/settings_comments.xml b/indra/newview/skins/default/xui/pt/settings_comments.xml index 1e02aaa847e..40da2a4c79e 100644 --- a/indra/newview/skins/default/xui/pt/settings_comments.xml +++ b/indra/newview/skins/default/xui/pt/settings_comments.xml @@ -1869,8 +1869,6 @@ Clipe de distância de renderização. RenderFlexTimeFactor Controla o nível de detalhe de objetos flexíveis (multiplicador para o tempo gasto processando objetos flex). - RenderFogRatio - OBSOLETO - Distância da câmera onde a névoa atinge densidade máxima (fração ou múltiplo da distância de far clip). RenderGLContextCoreProfile Não usar um contexto OpenGL de perfil de compatibilidade. Requer reinício. Apenas Windows. RenderGLMultiThreadedMedia @@ -2871,10 +2869,6 @@ Controla se o temporizador watchdog de threads é ativado. O valor é S32. Defina -1 para usar o padrão embutido. WaterGLFogDensityScale Mapeia a densidade da névoa de água do shader para a densidade da névoa do gl. - WaterGLFogDepthFloor - Controla quão escura a névoa de água do gl pode ficar. - WaterGLFogDepthScale - Controla quão rapidamente a névoa do gl escurece debaixo d'água. WearFolderLimit Limita o número de itens na pasta que podem ser substituídos/adicionados ao visual atual. WearablesEveryoneCopy From 95362a10418f1391fce23a800bbfc03e72cb3b95 Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 26 Oct 2026 14:17:48 -0400 Subject: [PATCH 05/11] Size the lens flare's occlusion probe from the body's drawn quad The flare's state pass probes how much of the sun or the moon the world hides over the body's own angular radius. It worked that radius out by hand from what ALSkyLayers::bodyCorners builds the quad from: the factor, the disk radius, the sky's scale and the quad's growth toward the horizon, 1 + (1 - z) / 4, the mean of its 1.3 and 1.2. It now reads the quad the sky draws (LLHeavenBody::angularHalfSize, the mean of the corners' half-width and half-height over their distance), so retuning the disc moves the probe with it. The two agree wherever a body is up and its scale is at least 0.01, the floor the old sum held it to. They differ only while neither body is up, when the pass takes the moon and the old sum the direction the lighting falls back to, level, where the moon's quad lies lower and larger; and at a scale under 0.01, where the probe now shrinks with the quad. Co-Authored-By: Claude Opus 5.5 --- indra/newview/alsky.cpp | 17 +++++++++++++++-- indra/newview/alsky.h | 4 ++++ indra/newview/pipeline.cpp | 16 +++++----------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/indra/newview/alsky.cpp b/indra/newview/alsky.cpp index e70407fb1df..5e26642728d 100644 --- a/indra/newview/alsky.cpp +++ b/indra/newview/alsky.cpp @@ -125,6 +125,19 @@ namespace } } +F32 LLHeavenBody::angularHalfSize() const +{ + // Corners 0..3 are (-right, +up), (-right, -up), (+right, +up), (+right, -up) (ALSkyLayers::bodyCorners). + const F32 dist = ((mCorners[0] + mCorners[3]) * 0.5f).length(); + if (dist <= 0.f) + { + return 0.f; + } + const F32 half_width = (mCorners[2] - mCorners[0]).length() * 0.5f; + const F32 half_height = (mCorners[0] - mCorners[1]).length() * 0.5f; + return 0.5f * (half_width + half_height) / dist; +} + ALSky::ALSky() : mSun(SUN_DISK_RADIUS) , mMoon(MOON_DISK_RADIUS) @@ -142,8 +155,8 @@ void ALSky::takeSky() return; } - // The bodies' quads about the camera, turned as the sky turns them: the stars' moon mask and the composite read - // them. + // The bodies' quads about the camera, turned as the sky turns them: the stars' moon mask, the composite and the + // lens flare's occlusion probe read them. mSun.setCorners( ALSkyLayers::bodyCorners(psky->getSunRotation(), HEAVENLY_BODY_DIST, mSunScale, mSun.getDiskRadius())); mMoon.setCorners( diff --git a/indra/newview/alsky.h b/indra/newview/alsky.h index bd3cee43421..0eb632f4b0e 100644 --- a/indra/newview/alsky.h +++ b/indra/newview/alsky.h @@ -67,6 +67,10 @@ class LLHeavenBody const ALSkyLayers::Corners& corners() const { return mCorners; } void setCorners(const ALSkyLayers::Corners& corners) { mCorners = corners; } + /// The tangent of the quad's half-size seen from the camera: the mean of its half-width's and half-height's, which + /// grow apart toward the horizon (ALSkyLayers::bodyCorners). 0 before the quad is first taken. + F32 angularHalfSize() const; + private: F32 mDiskRadius; ALSkyLayers::Corners mCorners{}; diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 8c1e7395fc9..0ac7cb5d408 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -8680,17 +8680,11 @@ void LLPipeline::generateLensFlareState(LLRenderTarget* src) } // Probe radius as a fraction of screen height: the body's own angular - // radius (ALSkyLayers::bodyCorners: ALSkyLayers::BODY_FACTOR x disk radius - // x sky scale, at unit distance) through the current vertical FOV, times - // the setting. It follows zoom and the sky's sun scale, so an occluder - // narrower than the disc dims the flare instead of cutting it. - LLSettingsSky::ptr_t psky = environment.getCurrentSky(); - const F32 disk_radius = (sun_up ? mSky.getSun() : mSky.getMoon()).getDiskRadius(); - const F32 body_scale = llmax(sun_up ? psky->getSunScale() : psky->getMoonScale(), 0.01f); - // The drawn quad grows towards the horizon (1.3x wide and 1.2x tall at - // dir.z = 0, ALSkyLayers::bodyCorners); a circular probe takes the mean of the two. - const F32 enlargement = 1.f + (1.f - light_dir.mV[VZ]) * 0.25f; - const F32 half_tan = ALSkyLayers::BODY_FACTOR * disk_radius * body_scale * enlargement; + // radius through the current vertical FOV, times the setting. The radius is + // the quad the sky draws the body in (LLHeavenBody::angularHalfSize), so it + // follows zoom, the sky's scale and the quad's growth toward the horizon, + // and an occluder narrower than the disc dims the flare instead of cutting it. + const F32 half_tan = (sun_up ? mSky.getSun() : mSky.getMoon()).angularHalfSize(); const LLViewerCamera* camera = LLViewerCamera::getInstance(); const F32 fov_y = llclamp(camera->getView(), 0.01f, F_PI - 0.01f); // A tiled snapshot zooms the projection without touching the FOV, and the From 1f9aca0b75652eb8e85995de517c4919e820de8c Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 26 Oct 2026 13:45:00 -0400 Subject: [PATCH 06/11] Benchmark the star points by prefix against whole cells and a threshold ALStarField::pointDraws gives glMultiDrawArrays one run a cell in view, the prefix of it bright enough to show, so at dusk a view draws over a hundred short runs. Drawing every point of the cells in view instead, the runs that meet merged, and leaving the faint stars out in the vertex stage would make the runs few. alstarfield_gl_bench measures the two at the default count, 1920x1080 into R11G11B10F, through a level 60-degree view and a 100-degree one 60 degrees up, at faintest 0 (night) and 20, 40, 80 and 140 (dusk), on NVIDIA: - the CPU's time is the same either way, 1.2-1.4 us and 2.2-2.3 us: glMultiDrawArrays is one call whatever its runs; - at night the two draw the same runs, 5.2 against 5.1 us and 14.2 against 14.2 us of GPU; - at dusk the prefixes are cheaper on the GPU, the more so the fainter the stars left out: 3.5 against 3.7 us at faintest 20 and 0.8 against 1.4 us at 140 in the level view (121 runs against 13), 8.8 against 8.8 us and 1.8 against 3.5 us in the wide one (298 runs against 16). A vertex left out still runs, and a run costs less than the vertices. So pointDraws keeps its prefixes, and the benchmark stays as the reason. Co-Authored-By: Claude Opus 5.5 --- indra/alrender/CMakeLists.txt | 1 + indra/alrender/tests/alstarfield_gl_bench.cpp | 280 ++++++++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 indra/alrender/tests/alstarfield_gl_bench.cpp diff --git a/indra/alrender/CMakeLists.txt b/indra/alrender/CMakeLists.txt index 6d45462641a..71a522c5edb 100644 --- a/indra/alrender/CMakeLists.txt +++ b/indra/alrender/CMakeLists.txt @@ -146,6 +146,7 @@ if(AL_BUILD_TESTS) BENCH_SOURCES tests/alcompute_gl_bench.cpp tests/alondemandtarget_bench.cpp + tests/alstarfield_gl_bench.cpp tests/alwatertargets_bench.cpp tests/reflectionprobe_gl_bench.cpp LIBRARIES alrender diff --git a/indra/alrender/tests/alstarfield_gl_bench.cpp b/indra/alrender/tests/alstarfield_gl_bench.cpp new file mode 100644 index 00000000000..7904fdcbafe --- /dev/null +++ b/indra/alrender/tests/alstarfield_gl_bench.cpp @@ -0,0 +1,280 @@ +/** + * @file alstarfield_gl_bench.cpp + * @brief Two ways to draw the points of the stars a frame keeps, on the hidden window: a prefix of each cell in view, + * one run of glMultiDrawArrays a cell (ALStarField::pointDraws), or every point of the cells in view, the runs + * that meet merged, with the vertex stage leaving out the stars under the frame's faintest. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +// One sample is one frame's point draw at the default count, through a 60-degree 16:9 view 20 degrees up or a +// 100-degree one 60 degrees up, at 1920x1080 into an R11G11B10F attachment, as ALSky::renderStars draws it on the +// emissive path: the CPU's time to work the runs out and to submit them once, and the GPU's to draw them once, from a +// GL_TIME_ELAPSED query around REPEATS draws of them. Each row is the median of 31 samples after a warm-up, with the +// samples' spread. + +#include "linden_common.h" + +#include "../alstarfield.h" +#include "alskyraster_gl.h" + +#include "altestbenchmark.h" + +#include + +#include +#include +#include +#include +#include + +namespace +{ + using Clock = std::chrono::steady_clock; + + constexpr S32 W = 1920; + constexpr S32 H = 1080; + constexpr S32 SAMPLES = 31; + constexpr S32 WARMUP = 5; + constexpr S32 REPEATS = 8; + + F64 ms_since(Clock::time_point start) + { + return std::chrono::duration(Clock::now() - start).count(); + } + + // The median and the samples' spread, in microseconds. + std::string summary(std::vector samples) + { + std::sort(samples.begin(), samples.end()); + return fmt::format("{:.1f} us ({:.1f}-{:.1f})", samples[samples.size() / 2] * 1000.0, + samples.front() * 1000.0, samples.back() * 1000.0); + } + + // starsV.glsl with every star under the uniform faintest_byte left out first thing, outside the clip volume. + std::string thresholdVertex(std::string source) + { + const size_t main_at = source.find("void main()"); + REQUIRE(main_at != std::string::npos); + const size_t open = source.find('{', main_at); + REQUIRE(open != std::string::npos); + source.insert(open + 1, R"( + if (float(uint(texture_index) >> 24u) < faintest_byte) + { + gl_Position = vec4(2.0, 2.0, 2.0, 1.0); + gl_PointSize = 1.0; + vary_point = vec3(0.0, 0.0, 1.0); + vary_star_color = vec3(0.0); + vary_spike = 0.0; + return; + } +)"); + source.insert(main_at, "uniform float faintest_byte;\n\n"); + return source; + } + + struct alstarfield_gl_bench_data + { + alstarfield_gl_bench_data() + { + mTree.populate(); + std::error_code ec; + const std::string vertex = LLFile::getContents(std::string(AL_TEST_SHADER_DIR) + "/class1/deferred/starsV.glsl", ec); + REQUIRE(!ec); + REQUIRE(mTree.write("deferred/starsThresholdV.glsl", thresholdVertex(vertex))); + mShaders = std::make_unique(mTree.prefix()); + al_sky_test::loadSharedObjects(*mShaders); + + build(mPrefix, "deferred/starsV.glsl"); + build(mThreshold, "deferred/starsThresholdV.glsl"); + mFaintestLocation = glGetUniformLocation(mThreshold.mProgramObject, "faintest_byte"); + REQUIRE(mFaintestLocation >= 0); + + mStars = ALStarField::generate(ALStarField::DEFAULT_COUNT, al_sky_test::DOME_RADIUS); + mLayout = ALStarField::layOut(mStars); + mBuffer = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXTURE_INDEX); + REQUIRE(mBuffer->allocateBuffer(mLayout.vertices(), 0)); + LLStrider vertices; + REQUIRE(mBuffer->getVertexStrider(vertices)); + ALStarField::fillVertices(mStars, mLayout, vertices); + mBuffer->unmapBuffer(); + + REQUIRE(mTarget.allocate(W, H, GL_RGBA8)); + REQUIRE(mTarget.addColorAttachment(GL_RGBA8)); + REQUIRE(mTarget.addColorAttachment(GL_RGB10_A2)); + REQUIRE(mTarget.addColorAttachment(GL_R11F_G11F_B10F)); + glGenQueries(1, &mQuery); + } + + ~alstarfield_gl_bench_data() + { + LLGLSLShader::unbind(); + mPrefix.unload(); + mThreshold.unload(); + mShaders->clearShaderObjects(); + mTarget.release(); + mBuffer = nullptr; + glDeleteQueries(1, &mQuery); + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.loadIdentity(); + } + + void build(LLGLSLShader& program, const char* vertex) + { + program.mName = "Deferred Star Program (bench)"; + program.mShaderFiles.clear(); + program.mShaderFiles.push_back(std::make_pair(vertex, GL_VERTEX_SHADER)); + program.mShaderFiles.push_back(std::make_pair("deferred/valueNoiseUtil.glsl", GL_VERTEX_SHADER)); + program.mShaderFiles.push_back(std::make_pair("deferred/starsF.glsl", GL_FRAGMENT_SHADER)); + program.mShaderLevel = 1; + program.mShaderGroup = LLGLSLShader::SG_SKY; + program.addConstant(LLGLSLShader::SHADER_CONST_STAR_DEPTH); + program.addPermutation("HAS_EMISSIVE", "1"); + UNSCOPED_INFO("the star program builds from " << vertex); + REQUIRE(program.createShader()); + } + + // The run's draws, through the bound program: the ranges, REPEATS times. + void draw(const ALStarField::Draws& draws) + { + mBuffer->setBuffer(); + for (S32 r = 0; r < REPEATS; ++r) + { + mBuffer->drawMultiArrays(LLRender::POINTS, draws.mFirst.data(), draws.mCount.data(), + (U32)draws.mFirst.size()); + } + } + + // One way's samples through @a view at @a faintest: by prefix where @a prefix, or whole cells left to the + // vertex stage. + void sample(const al_sky_test::View& view, bool prefix, U32 faintest, std::vector& cpu, + std::vector& gpu, size_t& runs, U32& points) + { + mTarget.bindTarget(); + mTarget.clear(); + al_sky_test::loadView(view, W, H); + LLGLEnable blend(GL_BLEND); + LLGLDisable cull(GL_CULL_FACE); + LLGLDepthTest no_depth(GL_FALSE, GL_FALSE); + LLGLEnable point_size(GL_PROGRAM_POINT_SIZE); + gGL.setSceneBlendType(LLRender::BT_ADD); + mTarget.setDrawAttachment(3); + gGL.pushMatrix(); + gGL.translatef(al_sky_test::CAMERA.mV[VX], al_sky_test::CAMERA.mV[VY], al_sky_test::CAMERA.mV[VZ]); + gGL.rotatef(17.f, 0.f, 0.f, 1.f); + + LLGLSLShader& program = prefix ? mPrefix : mThreshold; + program.bind(); + program.uniform1f(LLShaderMgr::CUSTOM_ALPHA, 0.5f); + program.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, (F32)W, (F32)H); + program.uniform1f(LLShaderMgr::WATER_TIME, 1234.5f); + program.uniform3f(LLShaderMgr::DEFERRED_MOON_DIR, 0.f, 0.f, -1.f); + program.uniform1f(LLShaderMgr::MOON_BRIGHTNESS, 0.f); + program.uniform3f(LLShaderMgr::MOON_QUAD_CENTER, 0.f, 0.f, 0.f); + if (!prefix) + { + glUniform1f(mFaintestLocation, (F32)faintest); + } + + LLMatrix4a mvp; + mvp.setMul(gGL.getModelviewMatrix(), gGL.getProjectionMatrix()); + const ALStarField::Frustum frustum = ALStarField::pointFrustum(mvp, W, H); + for (S32 s = -WARMUP; s < SAMPLES; ++s) + { + glFinish(); + const Clock::time_point start = Clock::now(); + ALStarField::pointDraws(mLayout, frustum, prefix ? faintest : 0, mDraws); + const F64 cull_ms = ms_since(start); + const Clock::time_point submit = Clock::now(); + glBeginQuery(GL_TIME_ELAPSED, mQuery); + draw(mDraws); + glEndQuery(GL_TIME_ELAPSED); + const F64 submit_ms = ms_since(submit); + GLuint64 ns = 0; + glGetQueryObjectui64v(mQuery, GL_QUERY_RESULT, &ns); + if (s >= 0) + { + cpu.push_back(cull_ms + submit_ms / REPEATS); + gpu.push_back((F64)ns / 1.0e6 / REPEATS); + } + } + runs = mDraws.mFirst.size(); + points = 0; + for (S32 count : mDraws.mCount) + { + points += (U32)count; + } + + program.unbind(); + gGL.popMatrix(); + mTarget.setDrawBuffers(); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + mTarget.flush(); + } + + void report(const char* name, const al_sky_test::View& view, U32 faintest) + { + std::vector cpu[2]; + std::vector gpu[2]; + size_t runs[2] = {}; + U32 points[2] = {}; + for (S32 way = 0; way < 2; ++way) + { + sample(view, way == 0, faintest, cpu[way], gpu[way], runs[way], points[way]); + } + WARN(fmt::format("{}, faintest {}:\n by prefix: cpu {}, gpu {}, {} runs, {} points\n" + " whole cells: cpu {}, gpu {}, {} runs, {} points", + name, faintest, summary(cpu[0]), summary(gpu[0]), runs[0], points[0], summary(cpu[1]), + summary(gpu[1]), runs[1], points[1])); + } + + ll_test::SharedGLScope mGL; + al_sky_test::SkyShaderTree mTree; + std::unique_ptr mShaders; + LLGLSLShader mPrefix; + LLGLSLShader mThreshold; + GLint mFaintestLocation = -1; + std::vector mStars; + ALStarField::Layout mLayout; + ALStarField::Draws mDraws; + LLPointer mBuffer; + LLRenderTarget mTarget; + GLuint mQuery = 0; + }; + + TEST_CASE_METHOD(alstarfield_gl_bench_data, "alstarfield_gl_bench: the points by prefix against whole cells and a threshold", + "[alstarfield_gl_bench][!benchmark]") + { + ll_test::skipUnlessOptimised(); + // Night keeps every star; at dusk a star brightness of 0.03, 0.01 and 0.003 keeps those at about these bytes + // and brighter on an 8-bit attachment (ALStarField::faintestVisible). + for (const F32 alpha : { 0.03f, 0.01f, 0.003f }) + { + WARN(fmt::format("star brightness {}: faintest {}", alpha, ALStarField::faintestVisible(alpha, 0.f, true))); + } + for (const U32 faintest : { 0u, 20u, 40u, 80u, 140u }) + { + report("level, 60 degrees", { 30.f, 20.f, 60.f }, faintest); + report("up, 100 degrees", { 75.f, 60.f, 100.f }, faintest); + } + } +} From 92310f08de78ee8d917cc3177ab0a0538ab1c4d0 Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 26 Oct 2026 14:10:40 -0400 Subject: [PATCH 07/11] Say what the clouds' and the aurora's fades do The comments carried over from the raster clouds and aurora told how each fade came to be: what WindLight drew, a rate the aurora's fade once had, a hard edge a user once saw. They now say what each fade does and, for the clouds, why SL-10751's fade is not the one used. Co-Authored-By: Claude Opus 5.5 --- .../shaders/class1/deferred/skyAuroraF.glsl | 11 +++++------ .../shaders/class1/deferred/skyCloudsF.glsl | 9 ++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/indra/newview/app_settings/shaders/class1/deferred/skyAuroraF.glsl b/indra/newview/app_settings/shaders/class1/deferred/skyAuroraF.glsl index 36b3527c17d..4a25761df9b 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/skyAuroraF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/skyAuroraF.glsl @@ -91,10 +91,9 @@ vec3 skyAurora(vec3 dir) n += 0.14 * valueNoise3D(p * 4.5 + vec3(drift * 1.4, t * 0.22)); n += 0.08 * valueNoise3D(p * 9.5 + vec3(drift * 1.9, t * 0.33)); - // Soft smoothstep instead of clamp+pow so the brightest curtain peaks - // don't plateau at 1.0 — that plateau becomes a visible "ceiling" where - // the elevation fade then scales it down, producing the hard top edge - // the user was seeing. + // A soft smoothstep rather than clamp+pow, so the brightest curtain peaks + // don't plateau at 1.0: a plateau becomes a visible "ceiling" where the + // elevation fade then scales it down, a hard top edge. float curtain_mask = pow(smoothstep(0.05, 0.92, n), 1.6); // ----- Elevation envelope -------------------------------------------- @@ -109,8 +108,8 @@ vec3 skyAurora(vec3 dir) float elev_env = horizon_fade * zenith_fade; // ----- Body envelope -------------------------------------------------- - // Below-base fade rate softened further (was 7) so the green trails - // gradually toward the horizon with no visible edge. + // A soft fade below the base, so the green trails gradually toward the + // horizon with no visible edge. float body_top = 0.30; float body_env = exp(min(d_above, 0.0) * 3.5) * (1.0 - smoothstep(body_top * 0.15, body_top, d_above)); diff --git a/indra/newview/app_settings/shaders/class1/deferred/skyCloudsF.glsl b/indra/newview/app_settings/shaders/class1/deferred/skyCloudsF.glsl index 0c045b50458..ac3e4a6c943 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/skyCloudsF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/skyCloudsF.glsl @@ -102,11 +102,10 @@ vec4 skyClouds(vec3 dir) // The position relative to the camera, raised as windlightDome raises it. vec3 rel_pos = windlightDomeRaise(hit); - // WindLight drew clouds at full opacity above the horizon. SL-10751's (rel_pos.y + 512) / max_y fade was - // meant for the dome's underside, but rel_pos.y tops out near 650, so with max_y above ~1160 it capped - // every cloud (0.72 at the zenith for the default 1605). Below the horizon SL-11589 zeroes them (WindLight - // drew them hanging under it); this only softens that cut over the last two degrees above the horizon, - // where the haze has already taken the clouds most of the way to the horizon's colour. + // The clouds are at full opacity above the horizon and none below it (SL-11589), the cut softened over the last + // two degrees above it, where the haze has already taken them most of the way to the horizon's colour. SL-10751's + // fade, (rel_pos.y + 512) / max_y, is not used: rel_pos.y tops out near 650, so with max_y above ~1160 it would + // cap every cloud (0.72 at the zenith for the default 1605). float altitude_blend_factor = smoothstep(0.0, 0.035, rel_pos.y / length(rel_pos)); // Set altitude. max_y is held at 1 m or more, as windlightDomeAt and calcAtmosphericVars hold it: the From 4044bcaa7a5b6e07418259ca79131deb7845e907 Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 26 Oct 2026 14:11:52 -0400 Subject: [PATCH 08/11] Name the water surface's GL test cases without commas A comma in a Catch2 test spec means "or", so a name with one cannot be run by its name alone. Seven of alwatersurface_gl_test's cases had one; they read the same without it. Co-Authored-By: Claude Opus 5.5 --- indra/alrender/tests/alwatersurface_gl_test.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/indra/alrender/tests/alwatersurface_gl_test.cpp b/indra/alrender/tests/alwatersurface_gl_test.cpp index 1f0e987d476..1ff9fd41ac6 100644 --- a/indra/alrender/tests/alwatersurface_gl_test.cpp +++ b/indra/alrender/tests/alwatersurface_gl_test.cpp @@ -1419,7 +1419,7 @@ namespace // From above, an exclusion prim over the water removes the water behind it; one under the water removes nothing, // since the water hides it. - TEST_CASE_METHOD(alwatersurface_data, "alwatersurface_gl: from above, an exclusion prim over the water removes the water behind it", "[alwatersurface_gl]") + TEST_CASE_METHOD(alwatersurface_data, "alwatersurface_gl: from above an exclusion prim over the water removes the water behind it", "[alwatersurface_gl]") { for (LLRenderTarget::eDepthFormat format : formats()) { @@ -1443,7 +1443,7 @@ namespace } // From below, the reverse: the prim under the water, on the camera's side, removes it; the one above it does not. - TEST_CASE_METHOD(alwatersurface_data, "alwatersurface_gl: from below, a prim under the water removes it and one above does not", "[alwatersurface_gl]") + TEST_CASE_METHOD(alwatersurface_data, "alwatersurface_gl: from below a prim under the water removes it and one above does not", "[alwatersurface_gl]") { for (LLRenderTarget::eDepthFormat format : formats()) { @@ -1511,7 +1511,7 @@ namespace // From below, the surface is drawn twice, once where the exclude bit is clear and once where it is set, and each // pixel takes exactly one of them. - TEST_CASE_METHOD(alwatersurface_data, "alwatersurface_gl: from below, each pixel takes exactly one of the surface's two draws", "[alwatersurface_gl]") + TEST_CASE_METHOD(alwatersurface_data, "alwatersurface_gl: from below each pixel takes exactly one of the surface's two draws", "[alwatersurface_gl]") { for (LLRenderTarget::eDepthFormat format : formats()) { @@ -1599,7 +1599,7 @@ namespace // The haze pass's two draws, one where the surface bit is clear and one where it is set, shade every pixel once, // the second exactly where the water is. - TEST_CASE_METHOD(alwatersurface_data, "alwatersurface_gl: the haze's two draws shade every pixel once, the converging one exactly on the water", "[alwatersurface_gl]") + TEST_CASE_METHOD(alwatersurface_data, "alwatersurface_gl: the haze's two draws shade every pixel once and the converging one exactly the water", "[alwatersurface_gl]") { for (LLRenderTarget::eDepthFormat format : formats()) { @@ -1686,7 +1686,7 @@ namespace } // Each cell lies at its own region's water height, and the rings at the agent region's. - TEST_CASE_METHOD(alwatersurface_data, "alwatersurface_gl: each cell lies at its own region's water height, and the rings at the agent region's", "[alwatersurface_gl]") + TEST_CASE_METHOD(alwatersurface_data, "alwatersurface_gl: each cell lies at its own region's water height and the rings at the agent region's", "[alwatersurface_gl]") { setUpField(); { @@ -2241,7 +2241,7 @@ namespace // Whatever order the waves' patches are drawn in, each is drawn once and in the same place, and the order a view // asks for runs from its near end: every order the flags give tessellates the same triangles, and the slow axis run // from its high end draws the patches there first. - TEST_CASE_METHOD(alwatersurface_data, "alwatersurface_gl: every patch order draws the same triangles, starting from the view's near end", "[alwatersurface_gl]") + TEST_CASE_METHOD(alwatersurface_data, "alwatersurface_gl: every patch order draws the same triangles starting from the view's near end", "[alwatersurface_gl]") { setUpWater(LLRenderTarget::DEPTH_FMT_24); mScene.flush(); @@ -2411,7 +2411,7 @@ namespace // With waves the field leaves the patches' cells to them and fans the cells beside them onto their corners: drawn // alone, it covers every pixel inside the rim once but those over the patches' cells, which it leaves; with the // patches drawn too, flat, every pixel inside the rim is covered once. - TEST_CASE_METHOD(alwatersurface_data, "alwatersurface_gl: with waves, the field and the patches cover every pixel inside the rim once", "[alwatersurface_gl]") + TEST_CASE_METHOD(alwatersurface_data, "alwatersurface_gl: with waves the field and the patches cover every pixel inside the rim once", "[alwatersurface_gl]") { setUpField(REACH); const ALWaterField::PatchRect rect = ALWaterField::patchRect(mLayout); From 50df8edbf47f15943509a639febc22391cc3bc59 Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 26 Oct 2026 19:08:38 -0400 Subject: [PATCH 09/11] Delete the shader constants nothing reads LLGLSLShader::addConstant defined LL_SHADER_CONST_CLOUD_MOON_DEPTH and LL_SHADER_CONST_STAR_DEPTH into a program's permutations (SL-14113), and no shader reads either. The raster cloud and moon programs that took the first are gone, and the star program's second only added a dead key to its permutations. The enum, the two tables and addConstant go, with the star program's call and the GL tests' copies of it. Co-Authored-By: Claude Opus 5.5 --- .../alrender/tests/alskycomposite_gl_test.cpp | 1 - indra/alrender/tests/alstarfield_gl_bench.cpp | 1 - indra/alrender/tests/alstarfield_gl_test.cpp | 1 - indra/llrender/llglslshader.cpp | 19 ------------------- indra/llrender/llglslshader.h | 10 ---------- indra/newview/llviewershadermgr.cpp | 1 - 6 files changed, 33 deletions(-) diff --git a/indra/alrender/tests/alskycomposite_gl_test.cpp b/indra/alrender/tests/alskycomposite_gl_test.cpp index 17927f9f56c..d5b70d84ac7 100644 --- a/indra/alrender/tests/alskycomposite_gl_test.cpp +++ b/indra/alrender/tests/alskycomposite_gl_test.cpp @@ -419,7 +419,6 @@ void main() { LLGLSLShader& program = quads ? mStarQuads[path] : mStarPoints[path]; program.mShaderGroup = LLGLSLShader::SG_SKY; - program.addConstant(LLGLSLShader::SHADER_CONST_STAR_DEPTH); buildProgram(program, quads ? "Deferred Star Quad Program" : "Deferred Star Program", { { "deferred/starsV.glsl", GL_VERTEX_SHADER }, { "deferred/valueNoiseUtil.glsl", GL_VERTEX_SHADER }, diff --git a/indra/alrender/tests/alstarfield_gl_bench.cpp b/indra/alrender/tests/alstarfield_gl_bench.cpp index 7904fdcbafe..e97d2e4fd4e 100644 --- a/indra/alrender/tests/alstarfield_gl_bench.cpp +++ b/indra/alrender/tests/alstarfield_gl_bench.cpp @@ -147,7 +147,6 @@ namespace program.mShaderFiles.push_back(std::make_pair("deferred/starsF.glsl", GL_FRAGMENT_SHADER)); program.mShaderLevel = 1; program.mShaderGroup = LLGLSLShader::SG_SKY; - program.addConstant(LLGLSLShader::SHADER_CONST_STAR_DEPTH); program.addPermutation("HAS_EMISSIVE", "1"); UNSCOPED_INFO("the star program builds from " << vertex); REQUIRE(program.createShader()); diff --git a/indra/alrender/tests/alstarfield_gl_test.cpp b/indra/alrender/tests/alstarfield_gl_test.cpp index f351be4d427..d113b28c6e2 100644 --- a/indra/alrender/tests/alstarfield_gl_test.cpp +++ b/indra/alrender/tests/alstarfield_gl_test.cpp @@ -409,7 +409,6 @@ void main() program.mShaderFiles.push_back(std::make_pair("deferred/starsF.glsl", GL_FRAGMENT_SHADER)); program.mShaderLevel = 1; program.mShaderGroup = LLGLSLShader::SG_SKY; - program.addConstant(LLGLSLShader::SHADER_CONST_STAR_DEPTH); if (quads) { program.addPermutation("STAR_QUADS", "1"); diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index 55742e78921..b2634729036 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -168,20 +168,6 @@ namespace LLGLSLShader gUIProgram; LLGLSLShader gSolidColorProgram; -// NOTE: Keep gShaderConsts* and LLGLSLShader::ShaderConsts_e in sync! -const std::string gShaderConstsKey[LLGLSLShader::NUM_SHADER_CONSTS] = -{ - "LL_SHADER_CONST_CLOUD_MOON_DEPTH" - , "LL_SHADER_CONST_STAR_DEPTH" -}; - -// NOTE: Keep gShaderConsts* and LLGLSLShader::ShaderConsts_e in sync! -const std::string gShaderConstsVal[LLGLSLShader::NUM_SHADER_CONSTS] = -{ - "0.99998" // SHADER_CONST_CLOUD_MOON_DEPTH // SL-14113 - , "0.99999" // SHADER_CONST_STAR_DEPTH // SL-14113 -}; - namespace { // The bits a component is uploaded as, which is what the uniform value cache compares: an int or a uint as @@ -1416,11 +1402,6 @@ void LLGLSLShader::addPermutation(std::string name, std::string value) mPermutationsAdded = true; } -void LLGLSLShader::addConstant(const LLGLSLShader::eShaderConsts shader_const) -{ - addPermutation(gShaderConstsKey[shader_const], gShaderConstsVal[shader_const]); -} - void LLGLSLShader::removePermutation(std::string name) { mDefines.erase(name); diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h index 9e545b270bc..8fca2a1fd62 100644 --- a/indra/llrender/llglslshader.h +++ b/indra/llrender/llglslshader.h @@ -147,14 +147,6 @@ class LLShaderUniforms class LLGLSLShader { public: - // NOTE: Keep gShaderConsts and LLGLSLShader::ShaderConsts_e in sync! - enum eShaderConsts - { - SHADER_CONST_CLOUD_MOON_DEPTH - , SHADER_CONST_STAR_DEPTH - , NUM_SHADER_CONSTS - }; - // enum primarily used to control application sky settings uniforms typedef enum { @@ -455,8 +447,6 @@ class LLGLSLShader } void removePermutation(std::string name); - void addConstant(const LLGLSLShader::eShaderConsts shader_const); - // Texture channel for the specified uniform, or -1 if the uniform is not used as a // texture. enableTexture only resolves it; disableTexture also releases the slot, and its // `mode` is the caller's expected target, checked against what was actually bound there diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index 6ccfec627ed..d571825f39a 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -3148,7 +3148,6 @@ void LLViewerShaderMgr::loadShadersDeferred() gDeferredStarProgram.mShaderFiles.push_back(make_pair("deferred/starsF.glsl", GL_FRAGMENT_SHADER)); gDeferredStarProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; gDeferredStarProgram.mShaderGroup = LLGLSLShader::SG_SKY; - gDeferredStarProgram.addConstant( LLGLSLShader::SHADER_CONST_STAR_DEPTH ); // SL-14113 add_common_permutations(&gDeferredStarProgram); From 7a040199709da4a9084ae3a45f89e61ea3effe97 Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 26 Oct 2026 19:08:41 -0400 Subject: [PATCH 10/11] Delete the unbound highlight program and the sky decode of unlit pixels gDeferredHighlightProgram (deferred/highlightF.glsl) was built and unloaded but never bound: the highlights draw through gHighlightProgram and its normal and specular twins, forward. It was the last program to flag a fragment in front of the far plane SKIP_ATMOS but one: class 1's material program (deferred/materialF.glsl), which writes a flat magenta into the albedo with no normal. The soften pass showed such a pixel through skyColor, the sky's old decode: with the emissive buffer it read the emissive, which that program leaves black, and scaled it by sky_hdr_scale. It now shows the albedo as it is, linear through the sRGB target's decode, and softenLightF neither declares sky_hdr_scale nor keeps skyColor. Co-Authored-By: Claude Opus 5.5 --- .../shaders/class1/deferred/highlightF.glsl | 42 ------------------- .../shaders/class3/deferred/softenLightF.glsl | 22 ++-------- indra/newview/llviewershadermgr.cpp | 14 ------- indra/newview/llviewershadermgr.h | 2 - 4 files changed, 4 insertions(+), 76 deletions(-) delete mode 100644 indra/newview/app_settings/shaders/class1/deferred/highlightF.glsl diff --git a/indra/newview/app_settings/shaders/class1/deferred/highlightF.glsl b/indra/newview/app_settings/shaders/class1/deferred/highlightF.glsl deleted file mode 100644 index ca8c4caf164..00000000000 --- a/indra/newview/app_settings/shaders/class1/deferred/highlightF.glsl +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @file highlightF.glsl - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2007, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -out vec4 frag_data[4]; - -uniform vec4 color; -uniform sampler2D diffuseMap; - -in vec2 vary_texcoord0; - -void main() -{ - frag_data[0] = color*texture(diffuseMap, vary_texcoord0.xy); - frag_data[1] = vec4(0.0); - frag_data[2] = vec4(0.0, 1.0, 0.0, GBUFFER_FLAG_SKIP_ATMOS); - -#if defined(HAS_EMISSIVE) - frag_data[3] = vec4(0); -#endif -} diff --git a/indra/newview/app_settings/shaders/class3/deferred/softenLightF.glsl b/indra/newview/app_settings/shaders/class3/deferred/softenLightF.glsl index 3b617eaee3f..ad89630f62a 100644 --- a/indra/newview/app_settings/shaders/class3/deferred/softenLightF.glsl +++ b/indra/newview/app_settings/shaders/class3/deferred/softenLightF.glsl @@ -87,8 +87,6 @@ uniform vec4 waterPlane; uniform int cube_snapshot; -uniform float sky_hdr_scale; - void calcHalfVectors(vec3 lv, vec3 n, vec3 v, out vec3 h, out vec3 l, out float nh, out float nl, out float nv, out float vh, out float lightDist); float blinnPhongLobe(float nh, float glossiness); void calcDiffuseSpecular(vec3 baseColor, float metallic, out vec3 diffuseColor, out vec3 specularColor); @@ -150,20 +148,6 @@ float ssaoVisibility(float ambocc) #endif } -// The colour a fragment in front of the far plane flagged to skip the atmospherics wrote (the debug highlight, -// highlightF.glsl), which nothing lights, scaled for fake HDR. -vec3 skyColor(GBufferInfo gb) -{ -#if defined(HAS_EMISSIVE) - vec3 color = srgb_to_linear(gb.emissive.rgb); -#else - // the sRGB albedo target holds the colour display encoded, and this pass reads it with hardware - // decode, so it is linear already - vec3 color = gb.albedo.rgb; -#endif - return color * sky_hdr_scale; -} - void writeColor(vec3 color) { float final_scale = 1; @@ -267,8 +251,10 @@ void main() } else if (GET_GBUFFER_FLAG(gb.gbufferFlag, GBUFFER_FLAG_SKIP_ATMOS)) { - // unlit in front of the far plane, where the sky's own draw does not reach: a debug highlight writes SKIP_ATMOS - color = skyColor(gb); + // Unlit, as it is: class 1's material program (deferred/materialF.glsl) writes its magenta into the albedo, + // with no normal to light, flagged to skip the atmospherics. The sRGB albedo target is read with hardware + // decode, so the colour is linear already. + color = gb.albedo.rgb; } else { diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index d571825f39a..764af98fd11 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -134,8 +134,6 @@ LLGLSLShader gSelectionGlowReachProgram; LLGLSLShader gSelectionGlowRowProgram; LLGLSLShader gSelectionBackdropMaskProgram; -LLGLSLShader gDeferredHighlightProgram; - LLGLSLShader gPathfindingProgram; LLGLSLShader gPathfindingNoNormalsProgram; @@ -1387,8 +1385,6 @@ void LLViewerShaderMgr::loadShadersDeferred() gDeferredFullbrightShinyProgram.unload(); gHUDFullbrightShinyProgram.unload(); - gDeferredHighlightProgram.unload(); - gNormalMapGenProgram.unload(); gDeferredGenBrdfLutProgram.unload(); gDepthPyramidProgram.unload(); @@ -1423,16 +1419,6 @@ void LLViewerShaderMgr::loadShadersDeferred() return; } - { - gDeferredHighlightProgram.mName = "Deferred Highlight Shader"; - gDeferredHighlightProgram.mShaderFiles.clear(); - gDeferredHighlightProgram.mShaderFiles.push_back(make_pair("interface/highlightV.glsl", GL_VERTEX_SHADER)); - gDeferredHighlightProgram.mShaderFiles.push_back(make_pair("deferred/highlightF.glsl", GL_FRAGMENT_SHADER)); - gDeferredHighlightProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - add_common_permutations(&gDeferredHighlightProgram); - built(gDeferredHighlightProgram.createShader(), gDeferredHighlightProgram); - } - { gDeferredDiffuseProgram.mName = "Deferred Diffuse Shader"; gDeferredDiffuseProgram.mFeatures.hasSrgb = true; diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h index 97c5d76c648..a6041c15cf4 100644 --- a/indra/newview/llviewershadermgr.h +++ b/indra/newview/llviewershadermgr.h @@ -204,8 +204,6 @@ extern LLGLSLShader gSelectionGlowReachProgram; extern LLGLSLShader gSelectionGlowRowProgram; extern LLGLSLShader gSelectionBackdropMaskProgram; -extern LLGLSLShader gDeferredHighlightProgram; - extern LLGLSLShader gPathfindingProgram; extern LLGLSLShader gPathfindingNoNormalsProgram; From 3cc20b4971ed77078261fd4663da4581509147fa Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 27 Oct 2026 11:00:09 -0400 Subject: [PATCH 11/11] Leave a meteor seen end on out of the packed streaks A meteor end on to the camera has its two ends apart on the screen by rounding alone. packMeteors left a streak out only where that came to exactly nothing, so whether it packed one hung on how the compiler fused the arithmetic: the sanitized Clang build packed four of the layers test's meteors where every other build packed five. A streak shorter than a hundredth of a pixel each way is now left out, as the CPU test's end on meteor already was. Such a streak covers no pixel's centre but by chance, and the old program's sliver for this one drew nothing. The CPU test adds an end on meteor whose head rounding has moved, which packMeteors packed before. Co-Authored-By: Claude Opus 5.5 --- indra/alrender/alskylayers.cpp | 10 ++++++++++ indra/alrender/alskylayers.h | 3 ++- indra/alrender/tests/alskylayers_gl_test.cpp | 8 ++++---- indra/alrender/tests/alskylayers_test.cpp | 6 +++++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/indra/alrender/alskylayers.cpp b/indra/alrender/alskylayers.cpp index b2d1f5513a8..05d5b33d8cd 100644 --- a/indra/alrender/alskylayers.cpp +++ b/indra/alrender/alskylayers.cpp @@ -171,6 +171,16 @@ U32 packMeteors(const std::vector& meteors, const LLMatrix4a& mvp, F32 w const F32 mid_y = clip_mid[VY] / clip_mid[VW]; const F32 streak_x = clip_head[VX] / clip_head[VW] - mid_x; const F32 streak_y = clip_head[VY] / clip_head[VW] - mid_y; + + // A streak shorter on the screen than a hundredth of a pixel each way is one seen end on, its ends apart by + // rounding alone, and its quad covers no pixel's centre but by chance. + const F32 streak_px_x = streak_x * width * 0.5f; + const F32 streak_px_y = streak_y * height * 0.5f; + if (streak_px_x * streak_px_x + streak_px_y * streak_px_y < 1e-4f) + { + continue; + } + F32 perp_x = -streak_y; F32 perp_y = streak_x; const F32 perp_len = llmax(sqrtf(perp_x * perp_x + perp_y * perp_y), 1e-5f); diff --git a/indra/alrender/alskylayers.h b/indra/alrender/alskylayers.h index 81d19b4053e..7664a05c69a 100644 --- a/indra/alrender/alskylayers.h +++ b/indra/alrender/alskylayers.h @@ -162,7 +162,8 @@ struct Meteor /// /// Each streak is a quad on the screen of @a width_px pixels a side for each unit of width, through @a mvp (the /// agent's frame about the camera to clip space) onto a @a width by @a height viewport. A streak with an end at or -/// behind the camera, one wholly off the screen, and one with no area are left out. Returns how many are in @a out. +/// behind the camera, one seen end on (shorter on the screen than a hundredth of a pixel each way), one wholly off the +/// screen, and one with no area are left out. Returns how many are in @a out. U32 packMeteors(const std::vector& meteors, const LLMatrix4a& mvp, F32 width, F32 height, F32 width_px, std::vector& out); diff --git a/indra/alrender/tests/alskylayers_gl_test.cpp b/indra/alrender/tests/alskylayers_gl_test.cpp index c48214f754b..ae7414065ab 100644 --- a/indra/alrender/tests/alskylayers_gl_test.cpp +++ b/indra/alrender/tests/alskylayers_gl_test.cpp @@ -992,9 +992,9 @@ TEST_CASE_METHOD(alskylayers_gl_data, "alskylayers_gl: the aurora along the view TEST_CASE_METHOD(alskylayers_gl_data, "alskylayers_gl: the meteors at each pixel are the meteors' quads", "[alskylayers_gl]") { // Every corner of a meteor's quad lies at the depth of its midpoint, so the quad interpolated its coordinates - // linearly on the screen, as the loop over the packed streaks solves them. The meteor behind the camera is left out - // of the loop, and drew nothing before; the one end on to the camera is a sliver both ways. The fireball passes 1, - // which the 8-bit albedo clamped (D6). + // linearly on the screen, as the loop over the packed streaks solves them. The meteors behind the camera and end on + // to it are left out of the loop: the one drew nothing before, and the other a sliver that covers no pixel. The + // fireball passes 1, which the 8-bit albedo clamped (D6). setUp(); S32 clamped_pixels = 0; for (S32 path = 0; path < 2; ++path) @@ -1003,7 +1003,7 @@ TEST_CASE_METHOD(alskylayers_gl_data, "alskylayers_gl: the meteors at each pixel CAPTURE(emissive); U32 packed = 0; const std::vector ray = rayMeteors(emissive, packed); - CHECK(packed == 5); + CHECK(packed == 4); check(ray, oldMeteors(emissive, false), oldMeteors(emissive, true), emissive, METEOR_MATHS, clamped_pixels, true); } diff --git a/indra/alrender/tests/alskylayers_test.cpp b/indra/alrender/tests/alskylayers_test.cpp index a5347f24c15..21c1f6269ee 100644 --- a/indra/alrender/tests/alskylayers_test.cpp +++ b/indra/alrender/tests/alskylayers_test.cpp @@ -233,8 +233,12 @@ TEST_CASE("alskylayers: a meteor's streak is packed as its quad on the screen", ALSkyLayers::Meteor end_on = ahead; end_on.mHalf = ahead.mMidpoint * 0.1f; + // One end on whose ends lie apart by what rounding moves a point, a few millionths of a pixel on the screen. + ALSkyLayers::Meteor rounded = end_on; + rounded.mHalf.mV[VX] += 1e-5f; + std::vector out; - const U32 count = ALSkyLayers::packMeteors({ behind, ahead, aside, end_on }, mvp, WIDTH, HEIGHT, 1.5f, out); + const U32 count = ALSkyLayers::packMeteors({ behind, ahead, aside, end_on, rounded }, mvp, WIDTH, HEIGHT, 1.5f, out); REQUIRE(count == 1); REQUIRE(out.size() == ALSkyLayers::METEOR_VECTORS);