Skip to content
Snippets Groups Projects
llreflectionmapmanager.cpp 46.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • /**
     * @file llreflectionmapmanager.cpp
     * @brief LLReflectionMapManager class implementation
     *
     * $LicenseInfo:firstyear=2022&license=viewerlgpl$
     * Second Life Viewer Source Code
     * Copyright (C) 2022, 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 "llreflectionmapmanager.h"
    
    #include "llspatialpartition.h"
    #include "llviewerregion.h"
    #include "pipeline.h"
    #include "llviewershadermgr.h"
    
    #include "llviewercontrol.h"
    
    #include "llviewermenufile.h"
    #include "llnotificationsutil.h"
    
    
    #if LL_WINDOWS
    #pragma warning (push)
    #pragma warning (disable : 4702) // compiler complains unreachable code
    #endif
    #define TINYEXR_USE_MINIZ 0
    #include "zlib.h"
    #define TINYEXR_IMPLEMENTATION
    #include "tinyexr/tinyexr.h"
    #if LL_WINDOWS
    #pragma warning (pop)
    #endif
    
    
    LLPointer<LLImageGL> gEXRImage;
    
    void load_exr(const std::string& filename)
    {
        // reset reflection maps when previewing a new HDRI
        gPipeline.mReflectionMapManager.reset();
        gPipeline.mReflectionMapManager.initReflectionMaps();
    
    
        float* out; // width * height * RGBA
        int width;
        int height;
        const char* err = NULL; // or nullptr in C++11
    
        int ret =  LoadEXRWithLayer(&out, &width, &height, filename.c_str(), /* layername */ nullptr, &err);
        if (ret == TINYEXR_SUCCESS) 
        {
    
            U32 texName = 0;
            LLImageGL::generateTextures(1, &texName);
    
            gEXRImage = new LLImageGL(texName, 4, GL_TEXTURE_2D, GL_RGB16F, GL_RGB16F, GL_FLOAT, LLTexUnit::TAM_CLAMP);
            gEXRImage->setHasMipMaps(TRUE);
            gEXRImage->setUseMipMaps(TRUE);
            gEXRImage->setFilteringOption(LLTexUnit::TFO_TRILINEAR);
    
            gGL.getTexUnit(0)->bind(gEXRImage);
    
    
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width, height, 0, GL_RGBA, GL_FLOAT, out);
            free(out); // release memory of image data
    
    
            glGenerateMipmap(GL_TEXTURE_2D);
    
            gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
    
        }
    
            LLSD notif_args;
            notif_args["WHAT"] = filename;
    
            notif_args["REASON"] = "Unknown";
            if (err) 
            {
                notif_args["REASON"] = std::string(err);
                FreeEXRErrorMessage(err); // release memory of error message.
            }
    
            LLNotificationsUtil::add("CannotLoad", notif_args);
        }
    }
    
    void hdri_preview()
    {
        LLFilePickerReplyThread::startPicker(
            [](const std::vector<std::string>& filenames, LLFilePicker::ELoadFilter load_filter, LLFilePicker::ESaveFilter save_filter)
            {
                if (LLAppViewer::instance()->quitRequested())
                {
                    return;
                }
                if (filenames.size() > 0)
                {
                    load_exr(filenames[0]);
                }
            },
            LLFilePicker::FFLOAD_HDRI,
            true);
    }
    
    
    extern BOOL gCubeSnapshot;
    extern BOOL gTeleportDisplay;
    
    
    // get the next highest power of two of v (or v if v is already a power of two)
    //defined in llvertexbuffer.cpp
    extern U32 nhpo2(U32 v);
    
    
    static void touch_default_probe(LLReflectionMap* probe)
    {
    
        if (LLViewerCamera::getInstance())
        {
            LLVector3 origin = LLViewerCamera::getInstance()->getOrigin();
            origin.mV[2] += 64.f;
    
    LLReflectionMapManager::LLReflectionMapManager()
    
    {
        initCubeFree();
    }
    
    void LLReflectionMapManager::initCubeFree()
    
        // start at 1 because index 0 is reserved for mDefaultProbe
    
        for (int i = 1; i < LL_MAX_REFLECTION_PROBE_COUNT; ++i)
    
        bool operator()(const LLPointer<LLReflectionMap>& lhs, const LLPointer<LLReflectionMap>& rhs)
    
            return lhs->mDistance < rhs->mDistance;
    
    static F32 update_score(LLReflectionMap* p)
    {
        return gFrameTimeSeconds - p->mLastUpdateTime  - p->mDistance*0.1f;
    }
    
    
    // return true if a is higher priority for an update than b
    static bool check_priority(LLReflectionMap* a, LLReflectionMap* b)
    {
    
        if (a->mCubeIndex == -1)
        { // not a candidate for updating
            return false;
        }
        else if (b->mCubeIndex == -1)
    
        { // b is not a candidate for updating, a is higher priority by default
    
            return true;
        }
        else if (!a->mComplete && !b->mComplete)
    
        { //neither probe is complete, use distance
            return a->mDistance < b->mDistance;
        }
        else if (a->mComplete && b->mComplete)
    
        { //both probes are complete, use update_score metric
            return update_score(a) > update_score(b);
    
        // a or b is not complete,
        if (sUpdateCount % 3 == 0)
        { // every third update, allow complete probes to cut in line in front of non-complete probes to avoid spammy probe generators from deadlocking scheduler (SL-20258))
            return !b->mComplete;
        }
    
        // prioritize incomplete probe
    
    // helper class to seed octree with probes
    void LLReflectionMapManager::update()
    {
    
        if (!LLPipeline::sReflectionProbesEnabled || gTeleportDisplay || LLStartUp::getStartupState() < STATE_PRECACHE)
    
        LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
    
        llassert(!gCubeSnapshot); // assert a snapshot is not in progress
        if (LLAppViewer::instance()->logoutRequestSent())
        {
            return;
        }
    
    
        if (mPaused && gFrameTimeSeconds > mResumeTime)
        {
            resume();
        }
    
    
    
        if (!mRenderTarget.isComplete())
        {
    
            U32 color_fmt = GL_RGB16F;
    
            U32 targetRes = mProbeResolution * 4; // super sample
    
            mRenderTarget.allocate(targetRes, targetRes, color_fmt, true);
    
            U32 res = mProbeResolution;
    
            U32 count = log2((F32)res) + 0.5f;
            
            mMipChain.resize(count);
            for (int i = 0; i < count; ++i)
            {
    
                mMipChain[i].allocate(res, res, GL_RGB16F);
    
        LLVector4a camera_pos;
        camera_pos.load3(LLViewerCamera::instance().getOrigin().mV);
    
    
            auto const & iter = std::find(mProbes.begin(), mProbes.end(), probe);
    
        mKillList.clear();
        
        // process create list
        for (auto& probe : mCreateList)
    
            mProbes.push_back(probe);
    
        if (mProbes.empty())
        {
            return;
        }
    
        bool did_update = false;
    
        
        static LLCachedControl<S32> sDetail(gSavedSettings, "RenderReflectionProbeDetail", -1);
    
        static LLCachedControl<S32> sLevel(gSavedSettings, "RenderReflectionProbeLevel", 3);
    
    
        bool realtime = sDetail >= (S32)LLReflectionMapManager::DetailLevel::REALTIME;
    
        LLReflectionMap* oldestProbe = nullptr;
    
        LLReflectionMap* oldestOccluded = nullptr;
    
        if (mUpdatingProbe != nullptr)
        {
            did_update = true;
            doProbeUpdate();
        }
    
        // update distance to camera for all probes
        std::sort(mProbes.begin()+1, mProbes.end(), CompareProbeDistance());
        llassert(mProbes[0] == mDefaultProbe);
        llassert(mProbes[0]->mCubeArray == mTexture);
        llassert(mProbes[0]->mCubeIndex == 0);
    
        // make sure we're assigning cube slots to the closest probes
    
        // first free any cube indices for distant probes
        for (U32 i = mReflectionProbeCount; i < mProbes.size(); ++i)
        {
            LLReflectionMap* probe = mProbes[i];
            llassert(probe != nullptr);
    
            if (probe->mCubeIndex != -1 && mUpdatingProbe != probe)
            { // free this index
                mCubeFree.push_back(probe->mCubeIndex);
    
                probe->mCubeArray = nullptr;
                probe->mCubeIndex = -1;
                probe->mComplete = false;
            }
        }
    
        // next distribute the free indices
        U32 count = llmin(mReflectionProbeCount, (U32)mProbes.size());
    
        for (S32 i = 1; i < count && !mCubeFree.empty(); ++i)
        {
            // find the closest probe that needs a cube index
            LLReflectionMap* probe = mProbes[i];
    
            if (probe->mCubeIndex == -1)
            {
                S32 idx = allocateCubeIndex();
                llassert(idx > 0); //if we're still in this loop, mCubeFree should not be empty and allocateCubeIndex should be returning good indices
                probe->mCubeArray = mTexture;
                probe->mCubeIndex = idx;
            }
        }
    
        for (int i = 0; i < mProbes.size(); ++i)
    
            LLReflectionMap* probe = mProbes[i];
            if (probe->getNumRefs() == 1)
            { // no references held outside manager, delete this probe
                deleteProbe(i);
                --i;
                continue;
            }
            
    
                (!probe->isRelevant() || mPaused))
            { // skip irrelevant probes (or all non-default probes if paused)
    
                if (probe->mViewerObject) //make sure probes track the viewer objects they are attached to
                {
                    probe->mOrigin.load3(probe->mViewerObject->getPositionAgent().mV);
                }
    
                d.setSub(camera_pos, probe->mOrigin);
                probe->mDistance = d.getLength3().getF32() - probe->mRadius;
            }
    
            else if (probe->mComplete)
            {
                // make default probe have a distance of 64m for the purposes of prioritization (if it's already been generated once)
                probe->mDistance = 64.f;
            }
    
            else
            {
                probe->mDistance = -4096.f; //boost priority of default probe when it's not complete
            }
    
                probe->mFadeIn = llmin((F32) (probe->mFadeIn + gFrameIntervalSeconds), 1.f);
            }
            if (probe->mOccluded && probe->mComplete)
    
                if (oldestOccluded == nullptr)
                {
                    oldestOccluded = probe;
                }
                else if (probe->mLastUpdateTime < oldestOccluded->mLastUpdateTime)
                {
                    oldestOccluded = probe;
                }
            }
            else
            {
                if (!did_update &&
                    i < mReflectionProbeCount &&
                    (oldestProbe == nullptr ||
    
                        check_priority(probe, oldestProbe)))
    
            if (realtime &&
                closestDynamic == nullptr &&
    
                probe->getIsDynamic())
            {
                closestDynamic = probe;
            }
    
        if (realtime && closestDynamic != nullptr)
        {
            LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("rmmu - realtime");
            // update the closest dynamic probe realtime
    
            // should do a full irradiance pass on "odd" frames and a radiance pass on "even" frames
    
            // store and override the value of "isRadiancePass" -- parts of the render pipe rely on "isRadiancePass" to set
    
            // lighting values etc
            bool radiance_pass = isRadiancePass();
            mRadiancePass = mRealtimeRadiancePass;
    
                updateProbeFace(closestDynamic, i);
    
            mRealtimeRadiancePass = !mRealtimeRadiancePass;
    
            // restore "isRadiancePass"
            mRadiancePass = radiance_pass;
    
        static LLCachedControl<F32> sUpdatePeriod(gSavedSettings, "RenderDefaultProbeUpdatePeriod", 2.f);
        if ((gFrameTimeSeconds - mDefaultProbe->mLastUpdateTime) < sUpdatePeriod)
        {
            if (sLevel == 0)
            { // when probes are disabled don't update the default probe more often than the prescribed update period
                oldestProbe = nullptr;
            }
        }
        else if (sLevel > 0)
        { // when probes are enabled don't update the default probe less often than the prescribed update period
          oldestProbe = mDefaultProbe;
    
        // switch to updating the next oldest probe
    
        if (!did_update && oldestProbe != nullptr)
        {
            LLReflectionMap* probe = oldestProbe;
    
        if (oldestOccluded)
        {
            // as far as this occluded probe is concerned, an origin/radius update is as good as a full update
            oldestOccluded->autoAdjustOrigin();
            oldestOccluded->mLastUpdateTime = gFrameTimeSeconds;
        }
    
    LLReflectionMap* LLReflectionMapManager::addProbe(LLSpatialGroup* group)
    {
        LLReflectionMap* probe = new LLReflectionMap();
        probe->mGroup = group;
    
        if (mDefaultProbe.isNull())
        {  //safety check to make sure default probe is always first probe added
            mDefaultProbe = new LLReflectionMap();
            mProbes.push_back(mDefaultProbe);
        }
    
        llassert(mProbes[0] == mDefaultProbe);
    
    
        if (group)
        {
            probe->mOrigin = group->getOctreeNode()->getCenter();
        }
    
    
        if (gCubeSnapshot)
        { //snapshot is in progress, mProbes is being iterated over, defer insertion until next update
            mCreateList.push_back(probe);
        }
        else
        {
            mProbes.push_back(probe);
        }
    
        return probe;
    
    struct CompareProbeDepth
    {
        bool operator()(const LLReflectionMap* lhs, const LLReflectionMap* rhs)
        {
            return lhs->mMinDepth < rhs->mMinDepth;
        }
    };
    
    
    void LLReflectionMapManager::getReflectionMaps(std::vector<LLReflectionMap*>& maps)
    {
    
        LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
    
    
        LLMatrix4a modelview;
        modelview.loadu(gGLModelView);
        LLVector4a oa; // scratch space for transformed origin
    
    
        U32 count = 0;
        U32 lastIdx = 0;
        for (U32 i = 0; count < maps.size() && i < mProbes.size(); ++i)
        {
            mProbes[i]->mLastBindTime = gFrameTimeSeconds; // something wants to use this probe, indicate it's been requested
            if (mProbes[i]->mCubeIndex != -1)
            {
    
                if (!mProbes[i]->mOccluded && mProbes[i]->mComplete)
    
                    modelview.affineTransform(mProbes[i]->mOrigin, oa);
                    mProbes[i]->mMinDepth = -oa.getF32ptr()[2] - mProbes[i]->mRadius;
                    mProbes[i]->mMaxDepth = -oa.getF32ptr()[2] + mProbes[i]->mRadius;
    
            }
            else
            {
                mProbes[i]->mProbeIndex = -1;
            }
            lastIdx = i;
        }
    
        // set remaining probe indices to -1
        for (U32 i = lastIdx+1; i < mProbes.size(); ++i)
        {
            mProbes[i]->mProbeIndex = -1;
        }
    
    
        if (count > 1)
        {
            std::sort(maps.begin(), maps.begin() + count, CompareProbeDepth());
        }
    
        for (U32 i = 0; i < count; ++i)
        {
            maps[i]->mProbeIndex = i;
        }
    
    
        // null terminate list
        if (count < maps.size())
        {
            maps[count] = nullptr;
        }
    }
    
    LLReflectionMap* LLReflectionMapManager::registerSpatialGroup(LLSpatialGroup* group)
    {
    
        if (group->getSpatialPartition()->mPartitionType == LLViewerRegion::PARTITION_VOLUME)
    
            OctreeNode* node = group->getOctreeNode();
            F32 size = node->getSize().getF32ptr()[0];
            if (size >= 15.f && size <= 17.f)
    
    LLReflectionMap* LLReflectionMapManager::registerViewerObject(LLViewerObject* vobj)
    {
        llassert(vobj != nullptr);
    
        LLReflectionMap* probe = new LLReflectionMap();
        probe->mViewerObject = vobj;
        probe->mOrigin.load3(vobj->getPositionAgent().mV);
    
        if (gCubeSnapshot)
        { //snapshot is in progress, mProbes is being iterated over, defer insertion until next update
            mCreateList.push_back(probe);
        }
        else
        {
            mProbes.push_back(probe);
        }
    
        return probe;
    }
    
    
    S32 LLReflectionMapManager::allocateCubeIndex()
    {
    
            S32 ret = mCubeFree.front();
            mCubeFree.pop_front();
            return ret;
    
        }
    
        return -1;
    }
    
    void LLReflectionMapManager::deleteProbe(U32 i)
    {
        LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
        LLReflectionMap* probe = mProbes[i];
    
    
        if (probe->mCubeIndex != -1)
        { // mark the cube index used by this probe as being free
    
        }
        if (mUpdatingProbe == probe)
        {
            mUpdatingProbe = nullptr;
            mUpdatingFace = 0;
        }
    
        // remove from any Neighbors lists
        for (auto& other : probe->mNeighbors)
        {
    
            auto const & iter = std::find(other->mNeighbors.begin(), other->mNeighbors.end(), probe);
    
            llassert(iter != other->mNeighbors.end());
            other->mNeighbors.erase(iter);
        }
    
        mProbes.erase(mProbes.begin() + i);
    }
    
    
    void LLReflectionMapManager::doProbeUpdate()
    {
        LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
        llassert(mUpdatingProbe != nullptr);
    
    
        updateProbeFace(mUpdatingProbe, mUpdatingFace);
    
        bool debug_updates = gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_PROBE_UPDATES) && mUpdatingProbe->mViewerObject;
    
    
            if (debug_updates)
            {
                mUpdatingProbe->mViewerObject->setDebugText(llformat("%.1f", (F32)gFrameTimeSeconds), LLColor4(1, 1, 1, 1));
            }
    
            updateNeighbors(mUpdatingProbe);
            mUpdatingFace = 0;
    
                mUpdatingProbe->mComplete = true;
    
                mUpdatingProbe = nullptr;
                mRadiancePass = false;
            }
            else
            {
                mRadiancePass = true;
            }
    
        else if (debug_updates)
        {
            mUpdatingProbe->mViewerObject->setDebugText(llformat("%.1f", (F32)gFrameTimeSeconds), LLColor4(1, 1, 0, 1));
        }
    
    // Do the reflection map update render passes.
    // For every 12 calls of this function, one complete reflection probe radiance map and irradiance map is generated
    
    // First six passes render the scene with direct lighting only into a scratch space cube map at the end of the cube map array and generate
    
    // a simple mip chain (not convolution filter).
    // At the end of these passes, an irradiance map is generated for this probe and placed into the irradiance cube map array at the index for this probe
    // The next six passes render the scene with both radiance and irradiance into the same scratch space cube map and generate a simple mip chain.
    // At the end of these passes, a radiance map is generated for this probe and placed into the radiance cube map array at the index for this probe.
    // In effect this simulates single-bounce lighting.
    
    void LLReflectionMapManager::updateProbeFace(LLReflectionMap* probe, U32 face)
    
    {
        // hacky hot-swap of camera specific render targets
    
        mLightScale = 1.f;
        static LLCachedControl<F32> max_local_light_ambiance(gSavedSettings, "RenderReflectionProbeMaxLocalLightAmbiance", 8.f);
        if (!isRadiancePass() && probe->getAmbiance() > max_local_light_ambiance)
        {
            mLightScale = max_local_light_ambiance / probe->getAmbiance();
        }
    
    
        if (probe == mDefaultProbe)
        {
            touch_default_probe(probe);
    
            gPipeline.pushRenderTypeMask();
            
            //only render sky, water, terrain, and clouds
            gPipeline.andRenderTypeMask(LLPipeline::RENDER_TYPE_SKY, LLPipeline::RENDER_TYPE_WL_SKY,
    
                LLPipeline::RENDER_TYPE_WATER, LLPipeline::RENDER_TYPE_VOIDWATER, LLPipeline::RENDER_TYPE_CLOUDS, LLPipeline::RENDER_TYPE_TERRAIN, LLPipeline::END_RENDER_TYPES);
    
            probe->update(mRenderTarget.getWidth(), face);
    
            probe->update(mRenderTarget.getWidth(), face);
    
        S32 sourceIdx = mReflectionProbeCount;
    
        if (probe != mUpdatingProbe)
    
        { // this is the "realtime" probe that's updating every frame, use the secondary scratch space channel
    
        LLGLDepthTest depth(GL_FALSE, GL_FALSE);
        LLGLDisable cull(GL_CULL_FACE);
        LLGLDisable blend(GL_BLEND);
    
        // downsample to placeholder map
    
            gGL.matrixMode(gGL.MM_MODELVIEW);
            gGL.pushMatrix();
            gGL.loadIdentity();
    
            gGL.matrixMode(gGL.MM_PROJECTION);
            gGL.pushMatrix();
            gGL.loadIdentity();
    
            gGL.flush();
    
            U32 res = mProbeResolution * 2;
    
            static LLStaticHashedString resScale("resScale");
            static LLStaticHashedString direction("direction");
            static LLStaticHashedString znear("znear");
            static LLStaticHashedString zfar("zfar");
    
            LLRenderTarget* screen_rt = &gPipeline.mAuxillaryRT.screen;
    
            // perform a gaussian blur on the super sampled render before downsampling
            {
                gGaussianProgram.bind();
    
                gGaussianProgram.uniform1f(resScale, 1.f / (mProbeResolution * 2));
    
                S32 diffuseChannel = gGaussianProgram.enableTexture(LLShaderMgr::DEFERRED_DIFFUSE, LLTexUnit::TT_TEXTURE);
    
                // horizontal
                gGaussianProgram.uniform2f(direction, 1.f, 0.f);
                gGL.getTexUnit(diffuseChannel)->bind(screen_rt);
    
                mRenderTarget.bindTarget();
    
                gPipeline.mScreenTriangleVB->setBuffer();
                gPipeline.mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3);
    
                mRenderTarget.flush();
    
    
                // vertical
                gGaussianProgram.uniform2f(direction, 0.f, 1.f);
    
                gGL.getTexUnit(diffuseChannel)->bind(&mRenderTarget);
    
                screen_rt->bindTarget();
                gPipeline.mScreenTriangleVB->setBuffer();
                gPipeline.mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3);
                screen_rt->flush();
            }
    
    
    
            S32 mips = log2((F32)mProbeResolution) + 0.5f;
    
            S32 diffuseChannel = gReflectionMipProgram.enableTexture(LLShaderMgr::DEFERRED_DIFFUSE, LLTexUnit::TT_TEXTURE);
    
            for (int i = 0; i < mMipChain.size(); ++i)
    
                mMipChain[i].bindTarget();
                if (i == 0)
                {
    
                    gGL.getTexUnit(diffuseChannel)->bind(screen_rt);
    
                    gGL.getTexUnit(diffuseChannel)->bind(&(mMipChain[i - 1]));
    
                gReflectionMipProgram.uniform1f(resScale, 1.f/(mProbeResolution*2));
    
                gPipeline.mScreenTriangleVB->setBuffer();
                gPipeline.mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3);
                
    
                res /= 2;
    
                S32 mip = i - (mMipChain.size() - mips);
    
                if (mip >= 0)
                {
    
                    LL_PROFILE_GPU_ZONE("probe mip copy");
    
                    mTexture->bind(0);
                    //glCopyTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, mip, 0, 0, probe->mCubeIndex * 6 + face, 0, 0, res, res);
    
                    glCopyTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, mip, 0, 0, sourceIdx * 6 + face, 0, 0, res, res);
    
                    //if (i == 0)
                    //{
                        //glCopyTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, mip, 0, 0, probe->mCubeIndex * 6 + face, 0, 0, res, res);
                    //}
                    mTexture->unbind();
    
                }
                mMipChain[i].flush();
            }
    
            gGL.popMatrix();
            gGL.matrixMode(gGL.MM_MODELVIEW);
            gGL.popMatrix();
    
    
            gGL.getTexUnit(diffuseChannel)->unbind(LLTexUnit::TT_TEXTURE);
    
            gReflectionMipProgram.unbind();
        }
    
            static LLStaticHashedString sSourceIdx("sourceIdx");
    
                //generate radiance map (even if this is not the irradiance map, we need the mip chain for the irradiance map)
                gRadianceGenProgram.bind();
                mVertexBuffer->setBuffer();
    
                S32 channel = gRadianceGenProgram.enableTexture(LLShaderMgr::REFLECTION_PROBES, LLTexUnit::TT_CUBE_MAP_ARRAY);
    
                mTexture->bind(channel);
    
                gRadianceGenProgram.uniform1i(sSourceIdx, sourceIdx);
                gRadianceGenProgram.uniform1f(LLShaderMgr::REFLECTION_PROBE_MAX_LOD, mMaxProbeLOD);
    
                gRadianceGenProgram.uniform1f(LLShaderMgr::REFLECTION_PROBE_STRENGTH, 1.f);
    
                for (int i = 0; i < mMipChain.size(); ++i)
                {
                    LL_PROFILE_GPU_ZONE("probe radiance gen");
                    static LLStaticHashedString sMipLevel("mipLevel");
                    static LLStaticHashedString sRoughness("roughness");
                    static LLStaticHashedString sWidth("u_width");
    
                    gRadianceGenProgram.uniform1f(sRoughness, (F32)i / (F32)(mMipChain.size() - 1));
                    gRadianceGenProgram.uniform1f(sMipLevel, i);
    
                    gRadianceGenProgram.uniform1i(sWidth, mProbeResolution);
    
                    for (int cf = 0; cf < 6; ++cf)
                    { // for each cube face
                        LLCoordFrame frame;
                        frame.lookAt(LLVector3(0, 0, 0), LLCubeMapArray::sClipToCubeLookVecs[cf], LLCubeMapArray::sClipToCubeUpVecs[cf]);
    
                        F32 mat[16];
                        frame.getOpenGLRotation(mat);
                        gGL.loadMatrix(mat);
    
                        mVertexBuffer->drawArrays(gGL.TRIANGLE_STRIP, 0, 4);
    
                        glCopyTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, i, 0, 0, probe->mCubeIndex * 6 + cf, 0, 0, res, res);
                    }
    
                    if (i != mMipChain.size() - 1)
                    {
                        res /= 2;
                        glViewport(0, 0, res, res);
                    }
                }
    
                //generate irradiance map
                gIrradianceGenProgram.bind();
                S32 channel = gIrradianceGenProgram.enableTexture(LLShaderMgr::REFLECTION_PROBES, LLTexUnit::TT_CUBE_MAP_ARRAY);
    
                mTexture->bind(channel);
    
    
                gIrradianceGenProgram.uniform1i(sSourceIdx, sourceIdx);
                gIrradianceGenProgram.uniform1f(LLShaderMgr::REFLECTION_PROBE_MAX_LOD, mMaxProbeLOD);
    
                mVertexBuffer->setBuffer();
                int start_mip = 0;
                // find the mip target to start with based on irradiance map resolution
                for (start_mip = 0; start_mip < mMipChain.size(); ++start_mip)
    
                    if (mMipChain[start_mip].getWidth() == LL_IRRADIANCE_MAP_RESOLUTION)
                    {
                        break;
                    }
    
                //for (int i = start_mip; i < mMipChain.size(); ++i)
                {
                    int i = start_mip;
                    LL_PROFILE_GPU_ZONE("probe irradiance gen");
                    glViewport(0, 0, mMipChain[i].getWidth(), mMipChain[i].getHeight());
                    for (int cf = 0; cf < 6; ++cf)
                    { // for each cube face
                        LLCoordFrame frame;
                        frame.lookAt(LLVector3(0, 0, 0), LLCubeMapArray::sClipToCubeLookVecs[cf], LLCubeMapArray::sClipToCubeUpVecs[cf]);
    
                        F32 mat[16];
                        frame.getOpenGLRotation(mat);
                        gGL.loadMatrix(mat);
    
                        mVertexBuffer->drawArrays(gGL.TRIANGLE_STRIP, 0, 4);
    
                        S32 res = mMipChain[i].getWidth();
                        mIrradianceMaps->bind(channel);
                        glCopyTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, i - start_mip, 0, 0, probe->mCubeIndex * 6 + cf, 0, 0, res, res);
    
                        mTexture->bind(channel);
    
            gIrradianceGenProgram.unbind();
    
    void LLReflectionMapManager::reset()
    
    void LLReflectionMapManager::pause(F32 duration)
    
        mResumeTime = gFrameTimeSeconds + duration;
    
    void LLReflectionMapManager::shift(const LLVector4a& offset)
    {
        for (auto& probe : mProbes)
    
            probe->mOrigin.add(offset);
    
    void LLReflectionMapManager::updateNeighbors(LLReflectionMap* probe)
    {
        LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
    
    
        //remove from existing neighbors
        {
            LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("rmmun - clear");
        
            for (auto& other : probe->mNeighbors)
            {
    
                auto const & iter = std::find(other->mNeighbors.begin(), other->mNeighbors.end(), probe);
    
                llassert(iter != other->mNeighbors.end()); // <--- bug davep if this ever happens, something broke badly
                other->mNeighbors.erase(iter);
            }
    
            probe->mNeighbors.clear();
        }
    
        // search for new neighbors
    
        {
            LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("rmmun - search");
            for (auto& other : mProbes)
            {
    
                if (other != mDefaultProbe && other != probe)
    
                    if (other->isRelevant() && probe->intersects(other))
    
                    {
                        probe->mNeighbors.push_back(other);
                        other->mNeighbors.push_back(probe);
                    }
                }
            }
        }
    }
    
    
    void LLReflectionMapManager::updateUniforms()
    
        LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
    
        // structure for packing uniform buffer object
    
        struct ReflectionProbeData
        {
    
            // for box probes, matrix that transforms from camera space to a [-1, 1] cube representing the bounding box of
    
            LLMatrix4 refBox[LL_MAX_REFLECTION_PROBE_COUNT];
    
            // for sphere probes, origin (xyz) and radius (w) of refmaps in clip space
    
            LLVector4 refSphere[LL_MAX_REFLECTION_PROBE_COUNT];
    
            // extra parameters