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"
extern BOOL gCubeSnapshot;
extern BOOL gTeleportDisplay;
LLReflectionMapManager::LLReflectionMapManager()
{
for (int i = 0; i < LL_REFLECTION_PROBE_COUNT; ++i)
{
mCubeFree[i] = true;
}
}
struct CompareReflectionMapDistance
{
};
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()
{
if (!LLPipeline::sRenderPBR || gTeleportDisplay)
{
return;
}
LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
llassert(!gCubeSnapshot); // assert a snapshot is not in progress
if (LLAppViewer::instance()->logoutRequestSent())
{
return;
}
// =============== TODO -- move to an init function =================
if (mTexture.isNull())
{
mTexture = new LLCubeMapArray();
// store LL_REFLECTION_PROBE_COUNT+2 cube maps, final two cube maps are used for render target and radiance map generation source)
mTexture->allocate(LL_REFLECTION_PROBE_RESOLUTION, 3, LL_REFLECTION_PROBE_COUNT+2);
mIrradianceMaps = new LLCubeMapArray();
mIrradianceMaps->allocate(LL_IRRADIANCE_MAP_RESOLUTION, 3, LL_REFLECTION_PROBE_COUNT);
}
if (!mRenderTarget.isComplete())
{
U32 color_fmt = GL_RGBA;
const bool use_depth_buffer = true;
const bool use_stencil_buffer = true;
U32 targetRes = LL_REFLECTION_PROBE_RESOLUTION * 2; // super sample
mRenderTarget.allocate(targetRes, targetRes, color_fmt, use_depth_buffer, use_stencil_buffer, LLTexUnit::TT_RECT_TEXTURE);
}
if (mMipChain.empty())
{
U32 res = LL_REFLECTION_PROBE_RESOLUTION;
U32 count = log2((F32)res) + 0.5f;
mMipChain.resize(count);
for (int i = 0; i < count; ++i)
{
mMipChain[i].allocate(res, res, GL_RGB, false, false, LLTexUnit::TT_RECT_TEXTURE);
res /= 2;
}
}
// =============== TODO -- move to an init function =================
// naively drop probes every 16m as we move the camera around for now
// later, use LLSpatialPartition to manage probes
const F32 PROBE_SPACING = 16.f;
const U32 MAX_PROBES = 8;
LLVector4a camera_pos;
camera_pos.load3(LLViewerCamera::instance().getOrigin().mV);
// process kill list
David Parks
committed
for (auto& probe : mKillList)
{
David Parks
committed
auto& iter = std::find(mProbes.begin(), mProbes.end(), probe);
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;
}
const F32 UPDATE_INTERVAL = 5.f; //update no more than once every 5 seconds
bool did_update = false;
bool realtime = gSavedSettings.getS32("RenderReflectionProbeDetail") >= (S32)LLReflectionMapManager::DetailLevel::REALTIME;
LLReflectionMap* closestDynamic = nullptr;
LLReflectionMap* oldestProbe = nullptr;
if (mUpdatingProbe != nullptr)
{
did_update = true;
doProbeUpdate();
}
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 < LL_REFLECTION_PROBE_COUNT &&
(oldestProbe == nullptr || probe->mLastUpdateTime < oldestProbe->mLastUpdateTime))
{
oldestProbe = probe;
}
if (realtime &&
closestDynamic == nullptr &&
probe->mCubeArray.notNull() &&
probe->getIsDynamic())
{
closestDynamic = probe;
}
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;
probe->mCubeIndex = 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;
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)
{
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
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);
}
}
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;
}
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
S32 LLReflectionMapManager::allocateCubeIndex()
{
for (int i = 0; i < LL_REFLECTION_PROBE_COUNT; ++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 >= LL_REFLECTION_PROBE_COUNT; --i)
{
if (mProbes[i]->mCubeIndex != -1)
{
S32 ret = mProbes[i]->mCubeIndex;
mProbes[i]->mCubeIndex = -1;
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];
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)
{
auto& 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);
mUpdatingProbe = nullptr;
mUpdatingFace = 0;
}
}
void LLReflectionMapManager::updateProbeFace(LLReflectionMap* probe, U32 face)
{
mRenderTarget.bindTarget();
// hacky hot-swap of camera specific render targets
David Parks
committed
gPipeline.mRT = &gPipeline.mAuxillaryRT;
probe->update(mRenderTarget.getWidth(), face);
David Parks
committed
gPipeline.mRT = &gPipeline.mMainRT;
mRenderTarget.flush();
S32 targetIdx = LL_REFLECTION_PROBE_COUNT;
if (probe != mUpdatingProbe)
{ // this is the "realtime" probe that's updating every frame, use the secondary scratch space channel
targetIdx += 1;
}
// downsample to placeholder map
{
LLGLDepthTest depth(GL_FALSE, GL_FALSE);
LLGLDisable cull(GL_CULL_FACE);
gReflectionMipProgram.bind();
gGL.matrixMode(gGL.MM_MODELVIEW);
gGL.pushMatrix();
gGL.loadIdentity();
gGL.matrixMode(gGL.MM_PROJECTION);
gGL.pushMatrix();
gGL.loadIdentity();
gGL.flush();
U32 res = LL_REFLECTION_PROBE_RESOLUTION * 2;
S32 mips = log2((F32)LL_REFLECTION_PROBE_RESOLUTION) + 0.5f;
//for (int i = 0; i < mMipChain.size(); ++i)
for (int i = 0; i < 1; ++i)
LL_PROFILE_GPU_ZONE("probe mip");
mMipChain[i].bindTarget();
if (i == 0)
{
gGL.getTexUnit(0)->bind(&mRenderTarget);
}
else
{
gGL.getTexUnit(0)->bind(&(mMipChain[i - 1]));
}
gGL.begin(gGL.QUADS);
gGL.texCoord2f(0, 0);
gGL.vertex2f(-1, -1);
gGL.texCoord2f(res, 0);
gGL.vertex2f(1, -1);
gGL.texCoord2f(res, res);
gGL.vertex2f(1, 1);
gGL.texCoord2f(0, res);
gGL.vertex2f(-1, 1);
gGL.end();
gGL.flush();
res /= 2;
S32 mip = i - (mMipChain.size() - mips);
if (mip >= 0)
{
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, targetIdx * 6 + face, 0, 0, res, res);
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();
gReflectionMipProgram.unbind();
}
if (face == 5)
{
//generate radiance map
gRadianceGenProgram.bind();
S32 channel = gRadianceGenProgram.enableTexture(LLShaderMgr::REFLECTION_PROBES, LLTexUnit::TT_CUBE_MAP_ARRAY);
mTexture->bind(channel);
static LLStaticHashedString sSourceIdx("sourceIdx");
gRadianceGenProgram.uniform1i(sSourceIdx, targetIdx);
static LLStaticHashedString sMipLevel("mipLevel");
for (int i = 1; i < mMipChain.size(); ++i)
{
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);
mMipChain[i].bindTarget();
static LLStaticHashedString sRoughness("roughness");
gRadianceGenProgram.uniform1f(sRoughness, (F32)i / (F32)(mMipChain.size() - 1));
gRadianceGenProgram.uniform1f(sMipLevel, llmax((F32)(i - 1), 0.f));
if (i > 0)
{
gRadianceGenProgram.uniform1i(sSourceIdx, probe->mCubeIndex);
}
gGL.begin(gGL.QUADS);
gGL.vertex3f(-1, -1, -1);
gGL.vertex3f(1, -1, -1);
gGL.vertex3f(1, 1, -1);
gGL.vertex3f(-1, 1, -1);
gGL.end();
gGL.flush();
S32 res = mMipChain[i].getWidth();
glCopyTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, i, 0, 0, probe->mCubeIndex * 6 + cf, 0, 0, res, res);
mMipChain[i].flush();
}
}
gRadianceGenProgram.unbind();
//generate irradiance map
gIrradianceGenProgram.bind();
channel = gIrradianceGenProgram.enableTexture(LLShaderMgr::REFLECTION_PROBES, LLTexUnit::TT_CUBE_MAP_ARRAY);
mTexture->bind(channel);
gIrradianceGenProgram.uniform1i(sSourceIdx, probe->mCubeIndex);
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)
{
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);
mMipChain[i].bindTarget();
gGL.begin(gGL.QUADS);
gGL.vertex3f(-1, -1, -1);
gGL.vertex3f(1, -1, -1);
gGL.vertex3f(1, 1, -1);
gGL.vertex3f(-1, 1, -1);
gGL.end();
gGL.flush();
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);
mMipChain[i].flush();
}
}
gIrradianceGenProgram.unbind();
}
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);
}
}
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
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& 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 != probe)
{
if (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
David Parks
committed
// see class3/deferred/reflectionProbeF.glsl
struct ReflectionProbeData
{
LLMatrix4 refBox[LL_REFLECTION_PROBE_COUNT]; // object bounding box as needed
LLVector4 refSphere[LL_REFLECTION_PROBE_COUNT]; //origin and radius of refmaps in clip space
David Parks
committed
LLVector4 refParams[LL_REFLECTION_PROBE_COUNT]; //extra parameters (currently only ambiance)
GLint refIndex[LL_REFLECTION_PROBE_COUNT][4];
GLint refNeighbor[4096];
GLint refmapCount;
};
mReflectionMaps.resize(LL_REFLECTION_PROBE_COUNT);
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->getReflectionProbeAmbiance();
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()), 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)
{
glGenBuffersARB(1, &mUBO);
}
{
LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("rmmsu - update buffer");
glBindBufferARB(GL_UNIFORM_BUFFER, mUBO);
glBufferDataARB(GL_UNIFORM_BUFFER, sizeof(ReflectionProbeData), &rpd, GL_STREAM_DRAW);
glBindBufferARB(GL_UNIFORM_BUFFER, 0);
}
void LLReflectionMapManager::setUniforms()
{
llassert(LLPipeline::sRenderPBR);
if (mUBO == 0)
{
updateUniforms();
}
glBindBufferBase(GL_UNIFORM_BUFFER, 1, mUBO);
}
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
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();
}