Newer
Older
/**
* @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 "llviewercamera.h"
#include "llspatialpartition.h"
#include "llviewerregion.h"
#include "pipeline.h"
#include "llviewershadermgr.h"
#include "llviewercontrol.h"
#include "llenvironment.h"
David Parks
committed
#include "llstartup.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"
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);
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);
glGenerateMipmap(GL_TEXTURE_2D);
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
{
LLSD notif_args;
notif_args["WHAT"] = filename;
notif_args["REASON"] = "Unknown";
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;
static U32 sUpdateCount = 0;
David Parks
committed
// 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);
David Parks
committed
static void touch_default_probe(LLReflectionMap* probe)
{
David Parks
committed
if (LLViewerCamera::getInstance())
{
LLVector3 origin = LLViewerCamera::getInstance()->getOrigin();
origin.mV[2] += 64.f;
David Parks
committed
David Parks
committed
probe->mOrigin.load3(origin.mV);
}
David Parks
committed
}
LLReflectionMapManager::LLReflectionMapManager()
{
initCubeFree();
}
void LLReflectionMapManager::initCubeFree()
{
David Parks
committed
// start at 1 because index 0 is reserved for mDefaultProbe
David Parks
committed
for (int i = 1; i < LL_MAX_REFLECTION_PROBE_COUNT; ++i)
David Parks
committed
mCubeFree.push_back(i);
}
struct CompareProbeDistance
{
David Parks
committed
LLReflectionMap* mDefaultProbe;
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)
{
David Parks
committed
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
David Parks
committed
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
return b->mComplete;
}
// helper class to seed octree with probes
void LLReflectionMapManager::update()
{
David Parks
committed
if (!LLPipeline::sReflectionProbesEnabled || gTeleportDisplay || LLStartUp::getStartupState() < STATE_PRECACHE)
{
return;
}
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();
}
initReflectionMaps();
if (!mRenderTarget.isComplete())
{
U32 color_fmt = GL_RGB16F;
U32 targetRes = mProbeResolution * 4; // super sample
mRenderTarget.allocate(targetRes, targetRes, color_fmt, true);
}
if (mMipChain.empty())
{
U32 res = mProbeResolution;
U32 count = (U32)(log2((F32)res) + 0.5f);
mMipChain.resize(count);
for (U32 i = 0; i < count; ++i)
{
mMipChain[i].allocate(res, res, GL_RGB16F);
res /= 2;
}
}
llassert(mProbes[0] == mDefaultProbe.get());
LLVector4a camera_pos;
camera_pos.load3(LLViewerCamera::instance().getOrigin().mV);
// process kill list
David Parks
committed
for (auto& probe : mKillList)
{
Brad Kittenbrink
committed
auto const & iter = std::find(mProbes.begin(), mProbes.end(), probe);
David Parks
committed
if (iter != mProbes.end())
}
mKillList.clear();
// process create list
for (auto& probe : mCreateList)
{
mProbes.push_back(probe);
}
mCreateList.clear();
if (mProbes.empty())
{
return;
}
David Parks
committed
bool did_update = false;
static LLCachedControl<S32> sDetail(gSavedSettings, "RenderReflectionProbeDetail", -1);
David Parks
committed
static LLCachedControl<S32> sLevel(gSavedSettings, "RenderReflectionProbeLevel", 3);
bool realtime = sDetail >= (S32)LLReflectionMapManager::DetailLevel::REALTIME;
LLReflectionMap* closestDynamic = nullptr;
LLReflectionMap* oldestProbe = nullptr;
David Parks
committed
LLReflectionMap* oldestOccluded = nullptr;
if (mUpdatingProbe != nullptr)
{
did_update = true;
doProbeUpdate();
}
David Parks
committed
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
// 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 (U32 i = 1; i < count && !mCubeFree.empty(); ++i)
David Parks
committed
{
// 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;
}
}
David Parks
committed
for (unsigned 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;
}
David Parks
committed
(!probe->isRelevant() || mPaused))
{ // skip irrelevant probes (or all non-default probes if paused)
David Parks
committed
continue;
}
David Parks
committed
if (probe != mDefaultProbe.get())
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;
}
David Parks
committed
else
{
probe->mDistance = -4096.f; //boost priority of default probe when it's not complete
}
if (probe->mComplete)
{
probe->autoAdjustOrigin();
probe->mFadeIn = llmin((F32) (probe->mFadeIn + gFrameIntervalSeconds), 1.f);
}
if (probe->mOccluded && probe->mComplete)
David Parks
committed
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)))
David Parks
committed
{
oldestProbe = probe;
}
if (realtime &&
closestDynamic == nullptr &&
David Parks
committed
probe->mCubeIndex != -1 &&
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
closestDynamic->autoAdjustOrigin();
// 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;
for (U32 i = 0; i < 6; ++i)
{
updateProbeFace(closestDynamic, i);
}
mRealtimeRadiancePass = !mRealtimeRadiancePass;
// restore "isRadiancePass"
mRadiancePass = radiance_pass;
}
David Parks
committed
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;
David Parks
committed
}
// switch to updating the next oldest probe
if (!did_update && oldestProbe != nullptr)
{
LLReflectionMap* probe = oldestProbe;
David Parks
committed
llassert(probe->mCubeIndex != -1);
probe->autoAdjustOrigin();
sUpdateCount++;
mUpdatingProbe = probe;
doProbeUpdate();
David Parks
committed
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;
David Parks
committed
David Parks
committed
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);
David Parks
committed
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;
}
David Parks
committed
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;
David Parks
committed
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)
David Parks
committed
{
maps[count++] = mProbes[i];
David Parks
committed
modelview.affineTransform(mProbes[i]->mOrigin, oa);
mProbes[i]->mMinDepth = -oa.getF32ptr()[2] - mProbes[i]->mRadius;
mProbes[i]->mMaxDepth = -oa.getF32ptr()[2] + mProbes[i]->mRadius;
David Parks
committed
}
}
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;
}
David Parks
committed
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)
{
LLSpatialPartition* part = group->getSpatialPartition();
if (!part || part->mPartitionType != LLViewerRegion::PARTITION_VOLUME)
{
return nullptr;
}
OctreeNode* node = group->getOctreeNode();
F32 size = node->getSize().getF32ptr()[0];
if (size < 15.f || size > 17.f)
{
return nullptr;
}
return addProbe(group);
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()
{
David Parks
committed
if (!mCubeFree.empty())
David Parks
committed
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];
llassert(probe != mDefaultProbe.get());
David Parks
committed
if (probe->mCubeIndex != -1)
{ // mark the cube index used by this probe as being free
David Parks
committed
mCubeFree.push_back(probe->mCubeIndex);
}
if (mUpdatingProbe == probe)
{
mUpdatingProbe = nullptr;
mUpdatingFace = 0;
}
// remove from any Neighbors lists
for (auto& other : probe->mNeighbors)
{
Brad Kittenbrink
committed
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 (++mUpdatingFace == 6)
{
if (debug_updates)
{
mUpdatingProbe->mViewerObject->setDebugText(llformat("%.1f", (F32)gFrameTimeSeconds), LLColor4(1, 1, 1, 1));
}
updateNeighbors(mUpdatingProbe);
mUpdatingFace = 0;
if (isRadiancePass())
David Parks
committed
{
mUpdatingProbe->mComplete = true;
David Parks
committed
mUpdatingProbe = nullptr;
mRadiancePass = false;
}
else
{
mRadiancePass = true;
}
}
else if (debug_updates)
{
mUpdatingProbe->mViewerObject->setDebugText(llformat("%.1f", (F32)gFrameTimeSeconds), LLColor4(1, 1, 0, 1));
}
}
David Parks
committed
// 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
David Parks
committed
// 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
David Parks
committed
gPipeline.mRT = &gPipeline.mAuxillaryRT;
David Parks
committed
David Parks
committed
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();
}
David Parks
committed
{
touch_default_probe(probe);
gPipeline.pushRenderTypeMask();
David Parks
committed
//only render sky, water, terrain, and clouds
gPipeline.andRenderTypeMask(LLPipeline::RENDER_TYPE_SKY, LLPipeline::RENDER_TYPE_WL_SKY,
David Parks
committed
LLPipeline::RENDER_TYPE_WATER, LLPipeline::RENDER_TYPE_VOIDWATER, LLPipeline::RENDER_TYPE_CLOUDS, LLPipeline::RENDER_TYPE_TERRAIN, LLPipeline::END_RENDER_TYPES);
David Parks
committed
probe->update(mRenderTarget.getWidth(), face);
gPipeline.popRenderTypeMask();
}
else
{
probe->update(mRenderTarget.getWidth(), face);
}
David Parks
committed
gPipeline.mRT = &gPipeline.mMainRT;
David Parks
committed
S32 sourceIdx = mReflectionProbeCount;
if (probe != mUpdatingProbe)
{ // this is the "realtime" probe that's updating every frame, use the secondary scratch space channel
David Parks
committed
sourceIdx += 1;
gGL.setColorMask(true, true);
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();
David Parks
committed
U32 res = mProbeResolution * 2;
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
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 = (S32)(log2((F32)mProbeResolution) + 0.5f);
gReflectionMipProgram.bind();
S32 diffuseChannel = gReflectionMipProgram.enableTexture(LLShaderMgr::DEFERRED_DIFFUSE, LLTexUnit::TT_TEXTURE);
for (int i = 0; i < mMipChain.size(); ++i)
LL_PROFILE_GPU_ZONE("probe mip");
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);
GLint mip = i - (static_cast<GLint>(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);
David Parks
committed
glCopyTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, mip, 0, 0, sourceIdx * 6 + face, 0, 0, res, res);
David Parks
committed
//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();
}
if (face == 5)
{
David Parks
committed
mMipChain[0].bindTarget();
David Parks
committed
static LLStaticHashedString sSourceIdx("sourceIdx");
if (isRadiancePass())
David Parks
committed
//generate radiance map (even if this is not the irradiance map, we need the mip chain for the irradiance map)
gRadianceGenProgram.bind();
mVertexBuffer->setBuffer();
David Parks
committed
David Parks
committed
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);
David Parks
committed
David Parks
committed
U32 res = mMipChain[0].getWidth();
David Parks
committed
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");
David Parks
committed
gRadianceGenProgram.uniform1f(sRoughness, (F32)i / (F32)(mMipChain.size() - 1));
gRadianceGenProgram.uniform1f(sMipLevel, (GLfloat)i);
David Parks
committed
gRadianceGenProgram.uniform1i(sWidth, mProbeResolution);
David Parks
committed
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);
David Parks
committed
glCopyTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, i, 0, 0, probe->mCubeIndex * 6 + cf, 0, 0, res, res);
}
David Parks
committed
if (i != mMipChain.size() - 1)
{
res /= 2;
glViewport(0, 0, res, res);
}
}
David Parks
committed
gRadianceGenProgram.unbind();
}
else
David Parks
committed
//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);
David Parks
committed
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)
David Parks
committed
if (mMipChain[start_mip].getWidth() == LL_IRRADIANCE_MAP_RESOLUTION)
{
break;
}
David Parks
committed
//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);
}
David Parks
committed
mMipChain[0].flush();
void LLReflectionMapManager::reset()
mReset = true;
void LLReflectionMapManager::pause(F32 duration)
David Parks
committed
{
mPaused = true;
David Parks
committed
}
void LLReflectionMapManager::resume()
{
mPaused = false;
}
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;
David Parks
committed
if (mDefaultProbe == probe)
{
return;
}
//remove from existing neighbors
{
LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("rmmun - clear");
for (auto& other : probe->mNeighbors)
{
Brad Kittenbrink
committed
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
David Parks
committed
if (probe->isRelevant())
{
LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("rmmun - search");
for (auto& other : mProbes)
{
David Parks
committed
if (other != mDefaultProbe && other != probe)
David Parks
committed
if (other->isRelevant() && probe->intersects(other))
{
probe->mNeighbors.push_back(other);
other->mNeighbors.push_back(probe);
}
}
}
}
}
void LLReflectionMapManager::updateUniforms()
if (!LLPipeline::sReflectionProbesEnabled)
{
return;
}
LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
// structure for packing uniform buffer object
David Parks
committed
// see class3/deferred/reflectionProbeF.glsl
struct ReflectionProbeData
{
// for box probes, matrix that transforms from camera space to a [-1, 1] cube representing the bounding box of
// the box probe
LLMatrix4 refBox[LL_MAX_REFLECTION_PROBE_COUNT];