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"
extern BOOL gCubeSnapshot;
extern BOOL gTeleportDisplay;
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)
{
LLVector3 origin = LLViewerCamera::getInstance()->getOrigin();
origin.mV[2] += 64.f;
probe->mOrigin.load3(origin.mV);
}
LLReflectionMapManager::LLReflectionMapManager()
{
initCubeFree();
}
void LLReflectionMapManager::initCubeFree()
{
David Parks
committed
for (int i = 1; i < LL_MAX_REFLECTION_PROBE_COUNT; ++i)
{
mCubeFree[i] = true;
}
David Parks
committed
// cube index 0 is reserved for the fallback probe
mCubeFree[0] = false;
}
struct CompareProbeDistance
{
bool operator()(const LLPointer<LLReflectionMap>& lhs, const LLPointer<LLReflectionMap>& rhs)
{
return lhs->mDistance < rhs->mDistance;
}
};
// 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;
}
initReflectionMaps();
if (!mRenderTarget.isComplete())
{
U32 color_fmt = GL_RGB16F;
David Parks
committed
U32 targetRes = mProbeResolution * 2; // super sample
David Parks
committed
mRenderTarget.allocate(targetRes, targetRes, color_fmt, true);
}
if (mMipChain.empty())
{
David Parks
committed
U32 res = mProbeResolution;
U32 count = log2((F32)res) + 0.5f;
mMipChain.resize(count);
for (int i = 0; i < count; ++i)
{
David Parks
committed
mMipChain[i].allocate(res, res, GL_RGBA16F);
res /= 2;
}
}
David Parks
committed
if (mDefaultProbe.isNull())
{
mDefaultProbe = addProbe();
mDefaultProbe->mDistance = -4096.f; // hack to make sure the default probe is always first in sort order
mDefaultProbe->mRadius = 4096.f;
David Parks
committed
touch_default_probe(mDefaultProbe);
David Parks
committed
}
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())
David Parks
committed
deleteProbe(iter - mProbes.begin());
}
mKillList.clear();
// process create list
for (auto& probe : mCreateList)
{
mProbes.push_back(probe);
}
mCreateList.clear();
if (mProbes.empty())
{
return;
}
bool did_update = false;
static LLCachedControl<S32> sDetail(gSavedSettings, "RenderReflectionProbeDetail", -1);
bool realtime = sDetail >= (S32)LLReflectionMapManager::DetailLevel::REALTIME;
LLReflectionMap* closestDynamic = nullptr;
LLReflectionMap* oldestProbe = nullptr;
if (mUpdatingProbe != nullptr)
{
did_update = true;
doProbeUpdate();
}
David Parks
committed
//LL_INFOS() << mProbes.size() << LL_ENDL;
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->mProbeIndex = i;
LLVector4a d;
if (!did_update &&
i < mReflectionProbeCount &&
(oldestProbe == nullptr || probe->mLastUpdateTime < oldestProbe->mLastUpdateTime))
{
oldestProbe = probe;
}
if (realtime &&
closestDynamic == nullptr &&
David Parks
committed
probe->mCubeIndex != -1 &&
probe->getIsDynamic())
{
closestDynamic = probe;
}
David Parks
committed
if (probe != mDefaultProbe)
{
d.setSub(camera_pos, probe->mOrigin);
probe->mDistance = d.getLength3().getF32() - probe->mRadius;
}
}
if (realtime && closestDynamic != nullptr)
{
LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("rmmu - realtime");
// update the closest dynamic probe realtime
closestDynamic->autoAdjustOrigin();
for (U32 i = 0; i < 6; ++i)
{
updateProbeFace(closestDynamic, i);
}
}
// switch to updating the next oldest probe
if (!did_update && oldestProbe != nullptr)
{
LLReflectionMap* probe = oldestProbe;
if (probe->mCubeIndex == -1)
{
probe->mCubeArray = mTexture;
David Parks
committed
probe->mCubeIndex = probe == mDefaultProbe ? 0 : allocateCubeIndex();
}
probe->autoAdjustOrigin();
mUpdatingProbe = probe;
doProbeUpdate();
// update distance to camera for all probes
std::sort(mProbes.begin(), mProbes.end(), CompareProbeDistance());
}
LLReflectionMap* LLReflectionMapManager::addProbe(LLSpatialGroup* group)
{
LLReflectionMap* probe = new LLReflectionMap();
probe->mGroup = group;
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;
}
void LLReflectionMapManager::getReflectionMaps(std::vector<LLReflectionMap*>& maps)
{
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
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)
{
mProbes[i]->mProbeIndex = count;
maps[count++] = mProbes[i];
}
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;
}
// 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];
David Parks
committed
if (size >= 15.f && size <= 17.f)
{
return addProbe(group);
}
}
David Parks
committed
#endif
#if 0
if (group->getSpatialPartition()->mPartitionType == LLViewerRegion::PARTITION_TERRAIN)
{
OctreeNode* node = group->getOctreeNode();
F32 size = node->getSize().getF32ptr()[0];
if (size >= 15.f && size <= 17.f)
{
return addProbe(group);
}
}
return nullptr;
}
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()
{
for (int i = 0; i < mReflectionProbeCount; ++i)
{
if (mCubeFree[i])
{
mCubeFree[i] = false;
return i;
}
}
// no cubemaps free, steal one from the back of the probe list
for (int i = mProbes.size() - 1; i >= mReflectionProbeCount; --i)
{
if (mProbes[i]->mCubeIndex != -1)
{
S32 ret = mProbes[i]->mCubeIndex;
mProbes[i]->mCubeIndex = -1;
David Parks
committed
mProbes[i]->mCubeArray = nullptr;
return ret;
}
}
llassert(false); // should never fail to allocate, something is probably wrong with mCubeFree
return -1;
}
void LLReflectionMapManager::deleteProbe(U32 i)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
LLReflectionMap* probe = mProbes[i];
David Parks
committed
llassert(probe != mDefaultProbe);
if (probe->mCubeIndex != -1)
{ // mark the cube index used by this probe as being free
mCubeFree[probe->mCubeIndex] = true;
}
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);
if (++mUpdatingFace == 6)
{
updateNeighbors(mUpdatingProbe);
mUpdatingFace = 0;
if (isRadiancePass())
David Parks
committed
{
mUpdatingProbe = nullptr;
mRadiancePass = false;
}
else
{
mRadiancePass = true;
}
}
}
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
// 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
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_CLOUDS, LLPipeline::RENDER_TYPE_TERRAIN, LLPipeline::END_RENDER_TYPES);
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
{
gReflectionMipProgram.bind();
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;
David Parks
committed
S32 mips = log2((F32)mProbeResolution) + 0.5f;
S32 diffuseChannel = gReflectionMipProgram.enableTexture(LLShaderMgr::DEFERRED_DIFFUSE, LLTexUnit::TT_TEXTURE);
S32 depthChannel = gReflectionMipProgram.enableTexture(LLShaderMgr::DEFERRED_DEPTH, LLTexUnit::TT_TEXTURE);
LLRenderTarget* screen_rt = &gPipeline.mAuxillaryRT.screen;
LLRenderTarget* depth_rt = &gPipeline.mAuxillaryRT.deferredScreen;
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]));
gGL.getTexUnit(depthChannel)->bind(depth_rt, true);
static LLStaticHashedString resScale("resScale");
static LLStaticHashedString znear("znear");
static LLStaticHashedString zfar("zfar");
gReflectionMipProgram.uniform1f(resScale, (F32) (1 << i));
gReflectionMipProgram.uniform1f(znear, probe->getNearClip());
gReflectionMipProgram.uniform1f(zfar, MAX_FAR_CLIP);
gGL.begin(gGL.QUADS);
gGL.texCoord2f(0, 0);
gGL.vertex2f(-1, -1);
gGL.vertex2f(1, -1);
gGL.vertex2f(1, 1);
gGL.vertex2f(-1, 1);
gGL.end();
gGL.flush();
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);
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);
gGL.getTexUnit(depthChannel)->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);
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, i);
gRadianceGenProgram.uniform1i(sWidth, mMipChain[i].getWidth());
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);
static LLCachedControl<F32> ambiance_scale(gSavedSettings, "RenderReflectionProbeAmbianceScale", 8.f);
static LLStaticHashedString ambiance_scale_str("ambiance_scale");
gIrradianceGenProgram.uniform1f(ambiance_scale_str, ambiance_scale);
David Parks
committed
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::rebuild()
{
for (auto& probe : mProbes)
{
probe->mLastUpdateTime = 0.f;
}
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
{
LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("rmmun - search");
for (auto& other : mProbes)
{
David Parks
committed
if (other != mDefaultProbe && other != probe)
{
if (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];
// for sphere probes, origin (xyz) and radius (w) of refmaps in clip space
LLVector4 refSphere[LL_MAX_REFLECTION_PROBE_COUNT];
// extra parameters (currently only ambiance in .x)
LLVector4 refParams[LL_MAX_REFLECTION_PROBE_COUNT];
// indices used by probe:
// [i][0] - cubemap array index for this probe
// [i][1] - index into "refNeighbor" for probes that intersect this probe
// [i][2] - number of probes that intersect this probe, or -1 for no neighbors
// [i][3] - priority (probe type stored in sign bit - positive for spheres, negative for boxes)
GLint refIndex[LL_MAX_REFLECTION_PROBE_COUNT][4];
// list of neighbor indices
GLint refNeighbor[4096];
// numbrer of active refmaps
GLint refmapCount;
mReflectionMaps.resize(mReflectionProbeCount);
getReflectionMaps(mReflectionMaps);
ReflectionProbeData rpd;
// load modelview matrix into matrix 4a
LLMatrix4a modelview;
modelview.loadu(gGLModelView);
LLVector4a oa; // scratch space for transformed origin
S32 count = 0;
U32 nc = 0; // neighbor "cursor" - index into refNeighbor to start writing the next probe's list of neighbors
LLEnvironment& environment = LLEnvironment::instance();
LLSettingsSky::ptr_t psky = environment.getCurrentSky();
F32 minimum_ambiance = psky->getTotalReflectionProbeAmbiance();
F32 ambscale = gCubeSnapshot && !isRadiancePass() ? 0.f : 1.f;
for (auto* refmap : mReflectionMaps)
{
if (refmap == nullptr)
{
break;
}
llassert(refmap->mProbeIndex == count);
llassert(mReflectionMaps[refmap->mProbeIndex] == refmap);
llassert(refmap->mCubeIndex >= 0); // should always be true, if not, getReflectionMaps is bugged
{
//LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("rmmsu - refSphere");
modelview.affineTransform(refmap->mOrigin, oa);
rpd.refSphere[count].set(oa.getF32ptr());
rpd.refSphere[count].mV[3] = refmap->mRadius;
}
rpd.refIndex[count][0] = refmap->mCubeIndex;
llassert(nc % 4 == 0);
rpd.refIndex[count][1] = nc / 4;
rpd.refIndex[count][3] = refmap->mPriority;
// for objects that are reflection probes, use the volume as the influence volume of the probe
// only possibile influence volumes are boxes and spheres, so detect boxes and treat everything else as spheres
if (refmap->getBox(rpd.refBox[count]))
{ // negate priority to indicate this probe has a box influence volume
rpd.refIndex[count][3] = -rpd.refIndex[count][3];
}
rpd.refParams[count].set(llmax(minimum_ambiance, refmap->getAmbiance())*ambscale, 0.f, 0.f, 0.f);
David Parks
committed
S32 ni = nc; // neighbor ("index") - index into refNeighbor to write indices for current reflection probe's neighbors
{
//LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("rmmsu - refNeighbors");
//pack neghbor list
for (auto& neighbor : refmap->mNeighbors)
{
if (ni >= 4096)
{ // out of space
break;
}
GLint idx = neighbor->mProbeIndex;
if (idx == -1)
{
continue;
}
// this neighbor may be sampled
rpd.refNeighbor[ni++] = idx;
}
}
if (nc == ni)
{
//no neighbors, tag as empty
rpd.refIndex[count][1] = -1;
}
else
{
rpd.refIndex[count][2] = ni - nc;
// move the cursor forward
nc = ni;
if (nc % 4 != 0)
{ // jump to next power of 4 for compatibility with ivec4
nc += 4 - (nc % 4);
}
}
count++;
}
rpd.refmapCount = count;
//copy rpd into uniform buffer object
if (mUBO == 0)
{
}
{
LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("rmmsu - update buffer");
glBindBuffer(GL_UNIFORM_BUFFER, mUBO);
glBufferData(GL_UNIFORM_BUFFER, sizeof(ReflectionProbeData), &rpd, GL_STREAM_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
void LLReflectionMapManager::setUniforms()
{
if (!LLPipeline::sReflectionProbesEnabled)
{
return;
}
if (mUBO == 0)
{
updateUniforms();
}
glBindBufferBase(GL_UNIFORM_BUFFER, 1, mUBO);
}
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
void renderReflectionProbe(LLReflectionMap* probe)
{
F32* po = probe->mOrigin.getF32ptr();
//draw orange line from probe to neighbors
gGL.flush();
gGL.diffuseColor4f(1, 0.5f, 0, 1);
gGL.begin(gGL.LINES);
for (auto& neighbor : probe->mNeighbors)
{
gGL.vertex3fv(po);
gGL.vertex3fv(neighbor->mOrigin.getF32ptr());
}
gGL.end();
gGL.flush();
#if 0
LLSpatialGroup* group = probe->mGroup;
if (group)
{ // draw lines from corners of object aabb to reflection probe
const LLVector4a* bounds = group->getBounds();
LLVector4a o = bounds[0];
gGL.flush();
gGL.diffuseColor4f(0, 0, 1, 1);
F32* c = o.getF32ptr();
const F32* bc = bounds[0].getF32ptr();
const F32* bs = bounds[1].getF32ptr();
// daaw blue lines from corners to center of node
gGL.begin(gGL.LINES);
gGL.vertex3fv(c);
gGL.vertex3f(bc[0] + bs[0], bc[1] + bs[1], bc[2] + bs[2]);
gGL.vertex3fv(c);
gGL.vertex3f(bc[0] - bs[0], bc[1] + bs[1], bc[2] + bs[2]);
gGL.vertex3fv(c);
gGL.vertex3f(bc[0] + bs[0], bc[1] - bs[1], bc[2] + bs[2]);
gGL.vertex3fv(c);
gGL.vertex3f(bc[0] - bs[0], bc[1] - bs[1], bc[2] + bs[2]);
gGL.vertex3fv(c);
gGL.vertex3f(bc[0] + bs[0], bc[1] + bs[1], bc[2] - bs[2]);
gGL.vertex3fv(c);
gGL.vertex3f(bc[0] - bs[0], bc[1] + bs[1], bc[2] - bs[2]);
gGL.vertex3fv(c);
gGL.vertex3f(bc[0] + bs[0], bc[1] - bs[1], bc[2] - bs[2]);
gGL.vertex3fv(c);
gGL.vertex3f(bc[0] - bs[0], bc[1] - bs[1], bc[2] - bs[2]);
gGL.end();
//draw yellow line from center of node to reflection probe origin
gGL.flush();
gGL.diffuseColor4f(1, 1, 0, 1);
gGL.begin(gGL.LINES);
gGL.vertex3fv(c);
gGL.vertex3fv(po);
gGL.end();
gGL.flush();
}
#endif
}
void LLReflectionMapManager::renderDebug()
{
gDebugProgram.bind();
for (auto& probe : mProbes)
{
renderReflectionProbe(probe);
}
gDebugProgram.unbind();
}
void LLReflectionMapManager::initReflectionMaps()
{
if (mTexture.isNull())
{
David Parks
committed
mProbeResolution = nhpo2(llclamp(gSavedSettings.getU32("RenderReflectionProbeResolution"), (U32)64, (U32)512));
mMaxProbeLOD = log2f(mProbeResolution) - 1.f; // number of mips - 1
mReflectionProbeCount = llclamp(gSavedSettings.getS32("RenderReflectionProbeCount"), 1, LL_MAX_REFLECTION_PROBE_COUNT);
mTexture = new LLCubeMapArray();
// store mReflectionProbeCount+2 cube maps, final two cube maps are used for render target and radiance map generation source)
David Parks
committed
mTexture->allocate(mProbeResolution, 4, mReflectionProbeCount + 2);
mIrradianceMaps = new LLCubeMapArray();
mIrradianceMaps->allocate(LL_IRRADIANCE_MAP_RESOLUTION, 4, mReflectionProbeCount, FALSE);
}
David Parks
committed
if (mVertexBuffer.isNull())
{
U32 mask = LLVertexBuffer::MAP_VERTEX;
LLPointer<LLVertexBuffer> buff = new LLVertexBuffer(mask);
buff->allocateBuffer(4, 0);
David Parks
committed
LLStrider<LLVector3> v;
buff->getVertexStrider(v);
v[0] = LLVector3(-1, -1, -1);
v[1] = LLVector3(1, -1, -1);
v[2] = LLVector3(-1, 1, -1);
v[3] = LLVector3(1, 1, -1);
buff->unmapBuffer();
David Parks
committed
mVertexBuffer = buff;
}
}
void LLReflectionMapManager::cleanup()
{