Files
SingularityViewer/indra/newview/llvieweroctree.cpp
Shyotl 736696ac36 Track glEnable states via static refs instead of map lookups.
Sync light state, bound shader, and various gl context states similarly to render matrices.
Texture handles now refcounted, as multiple viewer textures could ref the same handle (cubemaps do this)
Clean up gl extension loading a bit. Not necessary, but only look for ARB variants if not included in current core version. Removed unused extensions.
Use core shader api if supported, else use ARB. (FN signatures are identical. Just doing some pointer substitution to ARB if not core.)
Attempt at improving VBO update batching. Subdata updates better batched to gether per-frame.
There's probably other stuff I forgot that is in this changeset, too.

Todo: Fix lightstate assertion when toggling fullscreen with shaders off.
2018-11-19 00:37:48 -06:00

1508 lines
38 KiB
C++

/**
* @file llvieweroctree.cpp
* @brief LLViewerOctreeGroup class implementation and supporting functions
*
* $LicenseInfo:firstyear=2003&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "llvieweroctree.h"
#include "llviewerregion.h"
#include "pipeline.h"
#include "llviewercontrol.h"
#include "llappviewer.h"
#include "llglslshader.h"
#include "llviewershadermgr.h"
#include "lltexturecache.h"
#include "llimageworker.h"
#include "lltexturefetch.h"
//-----------------------------------------------------------------------------------
//static variables definitions
//-----------------------------------------------------------------------------------
U32 LLViewerOctreeEntryData::sCurVisible = 10; //reserve the low numbers for special use.
//-----------------------------------------------------------------------------------
//some global functions definitions
//-----------------------------------------------------------------------------------
typedef enum
{
b000 = 0x00,
b001 = 0x01,
b010 = 0x02,
b011 = 0x03,
b100 = 0x04,
b101 = 0x05,
b110 = 0x06,
b111 = 0x07,
} eLoveTheBits;
//contact Runitai Linden for a copy of the SL object used to write this table
//basically, you give the table a bitmask of the look-at vector to a node and it
//gives you a triangle fan index array
static U16 sOcclusionIndices[] =
{
//000
b111, b110, b010, b011, b001, b101, b100, b110,
//001
b011, b010, b000, b001, b101, b111, b110, b010,
//010
b101, b100, b110, b111, b011, b001, b000, b100,
//011
b001, b000, b100, b101, b111, b011, b010, b000,
//100
b110, b000, b010, b011, b111, b101, b100, b000,
//101
b010, b100, b000, b001, b011, b111, b110, b100,
//110
b100, b010, b110, b111, b101, b001, b000, b010,
//111
b000, b110, b100, b101, b001, b011, b010, b110,
};
U32 get_box_fan_indices(LLCamera* camera, const LLVector4a& center)
{
LLVector4a origin;
origin.load3(camera->getOrigin().mV);
S32 cypher = center.greaterThan(origin).getGatheredBits() & 0x7;
return cypher*8;
}
U8* get_box_fan_indices_ptr(LLCamera* camera, const LLVector4a& center)
{
LLVector4a origin;
origin.load3(camera->getOrigin().mV);
S32 cypher = center.greaterThan(origin).getGatheredBits() & 0x7;
return (U8*) (sOcclusionIndices+cypher*8);
}
//create a vertex buffer for efficiently rendering cubes
LLVertexBuffer* ll_create_cube_vb(U32 type_mask, U32 usage)
{
LLVertexBuffer* ret = new LLVertexBuffer(type_mask, usage);
ret->allocateBuffer(8, 64, true);
LLStrider<LLVector3> pos;
LLStrider<U16> idx;
ret->getVertexStrider(pos);
ret->getIndexStrider(idx);
pos[0] = LLVector3(-1,-1,-1);
pos[1] = LLVector3(-1,-1, 1);
pos[2] = LLVector3(-1, 1,-1);
pos[3] = LLVector3(-1, 1, 1);
pos[4] = LLVector3( 1,-1,-1);
pos[5] = LLVector3( 1,-1, 1);
pos[6] = LLVector3( 1, 1,-1);
pos[7] = LLVector3( 1, 1, 1);
for (U32 i = 0; i < 64; i++)
{
idx[i] = sOcclusionIndices[i];
}
ret->flush();
return ret;
}
#define LL_TRACK_PENDING_OCCLUSION_QUERIES 0
const F32 SG_OCCLUSION_FUDGE = 0.25f;
#define SG_DISCARD_TOLERANCE 0.01f
S32 AABBSphereIntersect(const LLVector3& min, const LLVector3& max, const LLVector3 &origin, const F32 &rad)
{
return AABBSphereIntersectR2(min, max, origin, rad*rad);
}
S32 AABBSphereIntersectR2(const LLVector3& min, const LLVector3& max, const LLVector3 &origin, const F32 &r)
{
F32 d = 0.f;
F32 t;
if ((min-origin).magVecSquared() < r &&
(max-origin).magVecSquared() < r)
{
return 2;
}
for (U32 i = 0; i < 3; i++)
{
if (origin.mV[i] < min.mV[i])
{
t = min.mV[i] - origin.mV[i];
d += t*t;
}
else if (origin.mV[i] > max.mV[i])
{
t = origin.mV[i] - max.mV[i];
d += t*t;
}
if (d > r)
{
return 0;
}
}
return 1;
}
S32 AABBSphereIntersect(const LLVector4a& min, const LLVector4a& max, const LLVector3 &origin, const F32 &rad)
{
return AABBSphereIntersectR2(min, max, origin, rad*rad);
}
S32 AABBSphereIntersectR2(const LLVector4a& min, const LLVector4a& max, const LLVector3 &origin, const F32 &r)
{
F32 d = 0.f;
F32 t;
LLVector4a origina;
origina.load3(origin.mV);
LLVector4a v;
v.setSub(min, origina);
if (v.dot3(v) < r)
{
v.setSub(max, origina);
if (v.dot3(v) < r)
{
return 2;
}
}
for (U32 i = 0; i < 3; i++)
{
if (origin.mV[i] < min[i])
{
t = min[i] - origin.mV[i];
d += t*t;
}
else if (origin.mV[i] > max[i])
{
t = origin.mV[i] - max[i];
d += t*t;
}
if (d > r)
{
return 0;
}
}
return 1;
}
//-----------------------------------------------------------------------------------
//class LLViewerOctreeEntry definitions
//-----------------------------------------------------------------------------------
LLViewerOctreeEntry::LLViewerOctreeEntry() :
mGroup(NULL),
mBinRadius(0.f),
mBinIndex(-1),
mVisible(0)
{
mPositionGroup.clear();
mExtents[0].clear();
mExtents[1].clear();
for(S32 i = 0; i < NUM_DATA_TYPE; i++)
{
mData[i] = NULL;
}
}
LLViewerOctreeEntry::~LLViewerOctreeEntry()
{
llassert(!mGroup);
}
void LLViewerOctreeEntry::addData(LLViewerOctreeEntryData* data)
{
//llassert(mData[data->getDataType()] == NULL);
llassert(data != NULL);
mData[data->getDataType()] = data;
}
void LLViewerOctreeEntry::removeData(LLViewerOctreeEntryData* data)
{
//llassert(data->getDataType() != LLVOCACHEENTRY); //can not remove VOCache entry
if(!mData[data->getDataType()])
{
return;
}
if(mData[data->getDataType()] != data)
{
return;
}
mData[data->getDataType()] = NULL;
if(mGroup != NULL && !mData[LLDRAWABLE])
{
LLViewerOctreeGroup* group = mGroup;
mGroup = NULL;
group->removeFromGroup(data);
llassert(mBinIndex == -1);
}
}
//called by group handleDestruction() ONLY when group is destroyed by octree.
void LLViewerOctreeEntry::nullGroup()
{
mGroup = NULL;
}
void LLViewerOctreeEntry::setGroup(LLViewerOctreeGroup* group)
{
if(mGroup == group)
{
return;
}
if(mGroup)
{
LLViewerOctreeGroup* old_group = mGroup;
mGroup = NULL;
old_group->removeFromGroup(this);
llassert(mBinIndex == -1);
}
mGroup = group;
}
//-----------------------------------------------------------------------------------
//class LLViewerOctreeEntryData definitions
//-----------------------------------------------------------------------------------
LLViewerOctreeEntryData::~LLViewerOctreeEntryData()
{
if(mEntry)
{
mEntry->removeData(this);
}
}
LLViewerOctreeEntryData::LLViewerOctreeEntryData(LLViewerOctreeEntry::eEntryDataType_t data_type)
: mDataType(data_type),
mEntry(NULL)
{
}
//virtual
void LLViewerOctreeEntryData::setOctreeEntry(LLViewerOctreeEntry* entry)
{
llassert_always(mEntry.isNull());
if(mEntry.notNull())
{
return;
}
if(!entry)
{
mEntry = new LLViewerOctreeEntry();
}
else
{
mEntry = entry;
}
mEntry->addData(this);
}
void LLViewerOctreeEntryData::removeOctreeEntry()
{
if(mEntry)
{
mEntry->removeData(this);
mEntry = NULL;
}
}
void LLViewerOctreeEntryData::setSpatialExtents(const LLVector3& min, const LLVector3& max)
{
mEntry->mExtents[0].load3(min.mV);
mEntry->mExtents[1].load3(max.mV);
}
void LLViewerOctreeEntryData::setSpatialExtents(const LLVector4a& min, const LLVector4a& max)
{
mEntry->mExtents[0] = min;
mEntry->mExtents[1] = max;
}
void LLViewerOctreeEntryData::setPositionGroup(const LLVector4a& pos)
{
mEntry->mPositionGroup = pos;
}
const LLVector4a* LLViewerOctreeEntryData::getSpatialExtents() const
{
return mEntry->getSpatialExtents();
}
//virtual
void LLViewerOctreeEntryData::setGroup(LLViewerOctreeGroup* group)
{
mEntry->setGroup(group);
}
void LLViewerOctreeEntryData::shift(const LLVector4a &shift_vector)
{
mEntry->mExtents[0].add(shift_vector);
mEntry->mExtents[1].add(shift_vector);
mEntry->mPositionGroup.add(shift_vector);
}
LLViewerOctreeGroup* LLViewerOctreeEntryData::getGroup()const
{
return mEntry.notNull() ? mEntry->mGroup : NULL;
}
const LLVector4a& LLViewerOctreeEntryData::getPositionGroup() const
{
return mEntry->getPositionGroup();
}
//virtual
bool LLViewerOctreeEntryData::isVisible() const
{
if(mEntry)
{
return mEntry->mVisible == sCurVisible;
}
return false;
}
//virtual
bool LLViewerOctreeEntryData::isRecentlyVisible() const
{
if(!mEntry)
{
return false;
}
if(isVisible())
{
return true;
}
if(getGroup() && getGroup()->isRecentlyVisible())
{
setVisible();
return true;
}
return false;
}
void LLViewerOctreeEntryData::setVisible() const
{
if(mEntry)
{
mEntry->mVisible = sCurVisible;
}
}
void LLViewerOctreeEntryData::resetVisible() const
{
if(mEntry)
{
mEntry->mVisible = 0;
}
}
//-----------------------------------------------------------------------------------
//class LLViewerOctreeGroup definitions
//-----------------------------------------------------------------------------------
LLViewerOctreeGroup::~LLViewerOctreeGroup()
{
//empty here
}
LLViewerOctreeGroup::LLViewerOctreeGroup(OctreeNode* node) :
mOctreeNode(node),
mAnyVisible(0),
mState(CLEAN)
{
LLVector4a tmp;
tmp.splat(0.f);
mExtents[0] = mExtents[1] = mObjectBounds[0] = mObjectBounds[0] = mObjectBounds[1] =
mObjectExtents[0] = mObjectExtents[1] = tmp;
mBounds[0] = node->getCenter();
mBounds[1] = node->getSize();
mOctreeNode->addListener(this);
}
bool LLViewerOctreeGroup::hasElement(LLViewerOctreeEntryData* data)
{
if(!data->getEntry())
{
return false;
}
return std::find(getDataBegin(), getDataEnd(), data->getEntry()) != getDataEnd();
}
bool LLViewerOctreeGroup::removeFromGroup(LLViewerOctreeEntryData* data)
{
return removeFromGroup(data->getEntry());
}
bool LLViewerOctreeGroup::removeFromGroup(LLViewerOctreeEntry* entry)
{
llassert(entry != NULL);
llassert(!entry->getGroup());
if(isDead()) //group is about to be destroyed, not need to double delete the entry.
{
entry->setBinIndex(-1);
return true;
}
unbound();
setState(OBJECT_DIRTY);
if (mOctreeNode)
{
if (!mOctreeNode->remove(entry)) //this could cause *this* pointer to be destroyed, so no more function calls after this.
{
OCT_ERRS << "Could not remove LLVOCacheEntry from LLVOCacheOctreeGroup" << LL_ENDL;
return false;
}
}
return true;
}
//virtual
void LLViewerOctreeGroup::unbound()
{
if (isDirty())
{
return;
}
setState(DIRTY);
//all the parent nodes need to rebound this child
if (mOctreeNode)
{
OctreeNode* parent = (OctreeNode*) mOctreeNode->getParent();
while (parent != NULL)
{
LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) parent->getListener(0);
if (!group || group->isDirty())
{
return;
}
group->setState(DIRTY);
parent = (OctreeNode*) parent->getParent();
}
}
}
//virtual
void LLViewerOctreeGroup::rebound()
{
if (!isDirty())
{
return;
}
if (mOctreeNode->getChildCount() == 1 && mOctreeNode->getElementCount() == 0)
{
LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) mOctreeNode->getChild(0)->getListener(0);
group->rebound();
//copy single child's bounding box
mBounds[0] = group->mBounds[0];
mBounds[1] = group->mBounds[1];
mExtents[0] = group->mExtents[0];
mExtents[1] = group->mExtents[1];
group->setState(SKIP_FRUSTUM_CHECK);
}
else if (mOctreeNode->isLeaf())
{ //copy object bounding box if this is a leaf
boundObjects(TRUE, mExtents[0], mExtents[1]);
mBounds[0] = mObjectBounds[0];
mBounds[1] = mObjectBounds[1];
}
else
{
LLVector4a& newMin = mExtents[0];
LLVector4a& newMax = mExtents[1];
LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) mOctreeNode->getChild(0)->getListener(0);
group->clearState(SKIP_FRUSTUM_CHECK);
group->rebound();
//initialize to first child
newMin = group->mExtents[0];
newMax = group->mExtents[1];
//first, rebound children
for (U32 i = 1; i < mOctreeNode->getChildCount(); i++)
{
group = (LLViewerOctreeGroup*) mOctreeNode->getChild(i)->getListener(0);
group->clearState(SKIP_FRUSTUM_CHECK);
group->rebound();
const LLVector4a& max = group->mExtents[1];
const LLVector4a& min = group->mExtents[0];
newMax.setMax(newMax, max);
newMin.setMin(newMin, min);
}
boundObjects(FALSE, newMin, newMax);
mBounds[0].setAdd(newMin, newMax);
mBounds[0].mul(0.5f);
mBounds[1].setSub(newMax, newMin);
mBounds[1].mul(0.5f);
}
clearState(DIRTY);
return;
}
//virtual
void LLViewerOctreeGroup::handleInsertion(const TreeNode* node, LLViewerOctreeEntry* obj)
{
obj->setGroup(this);
unbound();
setState(OBJECT_DIRTY);
}
//virtual
void LLViewerOctreeGroup::handleRemoval(const TreeNode* node, LLViewerOctreeEntry* obj)
{
unbound();
setState(OBJECT_DIRTY);
obj->setGroup(NULL); //this could cause *this* pointer to be destroyed. So no more function calls after this.
}
//virtual
void LLViewerOctreeGroup::handleDestruction(const TreeNode* node)
{
for (OctreeNode::element_iter i = mOctreeNode->getDataBegin(); i != mOctreeNode->getDataEnd(); ++i)
{
LLViewerOctreeEntry* obj = *i;
if (obj && obj->getGroup() == this)
{
obj->nullGroup();
//obj->setGroup(NULL);
}
}
mOctreeNode = NULL;
}
//virtual
void LLViewerOctreeGroup::handleStateChange(const TreeNode* node)
{
//drop bounding box upon state change
if (mOctreeNode != node)
{
mOctreeNode = (OctreeNode*) node;
}
unbound();
}
//virtual
void LLViewerOctreeGroup::handleChildAddition(const OctreeNode* parent, OctreeNode* child)
{
if (child->getListenerCount() == 0)
{
new LLViewerOctreeGroup(child);
}
else
{
OCT_ERRS << "LLViewerOctreeGroup redundancy detected." << LL_ENDL;
}
unbound();
((LLViewerOctreeGroup*)child->getListener(0))->unbound();
}
//virtual
void LLViewerOctreeGroup::handleChildRemoval(const OctreeNode* parent, const OctreeNode* child)
{
unbound();
}
LLViewerOctreeGroup* LLViewerOctreeGroup::getParent()
{
if (isDead())
{
return NULL;
}
if(!mOctreeNode)
{
return NULL;
}
OctreeNode* parent = mOctreeNode->getOctParent();
if (parent)
{
return (LLViewerOctreeGroup*) parent->getListener(0);
}
return NULL;
}
//virtual
bool LLViewerOctreeGroup::boundObjects(BOOL empty, LLVector4a& minOut, LLVector4a& maxOut)
{
const OctreeNode* node = mOctreeNode;
if (node->isEmpty())
{ //don't do anything if there are no objects
if (empty && mOctreeNode->getParent())
{ //only root is allowed to be empty
OCT_ERRS << "Empty leaf found in octree." << LL_ENDL;
}
return false;
}
LLVector4a& newMin = mObjectExtents[0];
LLVector4a& newMax = mObjectExtents[1];
if (hasState(OBJECT_DIRTY))
{ //calculate new bounding box
clearState(OBJECT_DIRTY);
//initialize bounding box to first element
OctreeNode::const_element_iter i = node->getDataBegin();
LLViewerOctreeEntry* entry = *i;
const LLVector4a* minMax = entry->getSpatialExtents();
newMin = minMax[0];
newMax = minMax[1];
for (++i; i != node->getDataEnd(); ++i)
{
entry = *i;
minMax = entry->getSpatialExtents();
update_min_max(newMin, newMax, minMax[0]);
update_min_max(newMin, newMax, minMax[1]);
}
mObjectBounds[0].setAdd(newMin, newMax);
mObjectBounds[0].mul(0.5f);
mObjectBounds[1].setSub(newMax, newMin);
mObjectBounds[1].mul(0.5f);
}
if (empty)
{
minOut = newMin;
maxOut = newMax;
}
else
{
minOut.setMin(minOut, newMin);
maxOut.setMax(maxOut, newMax);
}
return TRUE;
}
//virtual
BOOL LLViewerOctreeGroup::isVisible() const
{
return mVisible[LLViewerCamera::sCurCameraID] >= LLViewerOctreeEntryData::getCurrentFrame() ? TRUE : FALSE;
}
//virtual
BOOL LLViewerOctreeGroup::isRecentlyVisible() const
{
return FALSE;
}
void LLViewerOctreeGroup::setVisible()
{
mVisible[LLViewerCamera::sCurCameraID] = LLViewerOctreeEntryData::getCurrentFrame();
if(LLViewerCamera::sCurCameraID < LLViewerCamera::CAMERA_WATER0)
{
mAnyVisible = LLViewerOctreeEntryData::getCurrentFrame();
}
}
void LLViewerOctreeGroup::checkStates()
{
#if LL_OCTREE_PARANOIA_CHECK
LLOctreeStateCheck checker;
checker.traverse(mOctreeNode);
#endif
}
//-------------------------------------------------------------------------------------------
//occulsion culling functions and classes
//-------------------------------------------------------------------------------------------
std::set<U32> LLOcclusionCullingGroup::sPendingQueries;
class LLOcclusionQueryPool : public LLGLNamePool
{
public:
LLOcclusionQueryPool()
{
}
protected:
virtual GLuint allocateName()
{
GLuint ret = 0;
glGenQueriesARB(1, &ret);
return ret;
}
virtual void releaseName(GLuint name)
{
#if LL_TRACK_PENDING_OCCLUSION_QUERIES
LLOcclusionCullingGroup::sPendingQueries.erase(name);
#endif
glDeleteQueriesARB(1, &name);
}
};
static LLOcclusionQueryPool sQueryPool;
U32 LLOcclusionCullingGroup::getNewOcclusionQueryObjectName()
{
return sQueryPool.allocate();
}
void LLOcclusionCullingGroup::releaseOcclusionQueryObjectName(GLuint name)
{
sQueryPool.release(name);
}
//=====================================
// Occlusion State Set/Clear
//=====================================
class LLSpatialSetOcclusionState : public OctreeTraveler
{
public:
LLOcclusionCullingGroup::eOcclusionState mState;
LLSpatialSetOcclusionState(LLOcclusionCullingGroup::eOcclusionState state) : mState(state) { }
virtual void visit(const OctreeNode* branch)
{
LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)branch->getListener(0);
if(group)
{
group->setOcclusionState(mState);
}
}
};
class LLSpatialSetOcclusionStateDiff : public LLSpatialSetOcclusionState
{
public:
LLSpatialSetOcclusionStateDiff(LLOcclusionCullingGroup::eOcclusionState state) : LLSpatialSetOcclusionState(state) { }
virtual void traverse(const OctreeNode* n)
{
LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*) n->getListener(0);
if (group && !group->isOcclusionState(mState))
{
OctreeTraveler::traverse(n);
}
}
};
LLOcclusionCullingGroup::LLOcclusionCullingGroup(OctreeNode* node, LLViewerOctreePartition* part) :
LLViewerOctreeGroup(node),
mSpatialPartition(part)
{
part->mLODSeed = (part->mLODSeed+1)%part->mLODPeriod;
mLODHash = part->mLODSeed;
OctreeNode* oct_parent = node->getOctParent();
LLOcclusionCullingGroup* parent = oct_parent ? (LLOcclusionCullingGroup*) oct_parent->getListener(0) : NULL;
for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++)
{
mOcclusionQuery[i] = 0;
mOcclusionIssued[i] = 0;
mOcclusionState[i] = parent ? SG_STATE_INHERIT_MASK & parent->mOcclusionState[i] : 0;
mVisible[i] = 0;
}
}
LLOcclusionCullingGroup::~LLOcclusionCullingGroup()
{
releaseOcclusionQueryObjectNames();
}
BOOL LLOcclusionCullingGroup::needsUpdate()
{
return (LLDrawable::getCurrentFrame() % mSpatialPartition->mLODPeriod == mLODHash) ? TRUE : FALSE;
}
BOOL LLOcclusionCullingGroup::isRecentlyVisible() const
{
const S32 MIN_VIS_FRAME_RANGE = 2;
return (LLDrawable::getCurrentFrame() - mVisible[LLViewerCamera::sCurCameraID]) < MIN_VIS_FRAME_RANGE ;
}
BOOL LLOcclusionCullingGroup::isAnyRecentlyVisible() const
{
const S32 MIN_VIS_FRAME_RANGE = 2;
return (LLDrawable::getCurrentFrame() - mAnyVisible) < MIN_VIS_FRAME_RANGE ;
}
//virtual
void LLOcclusionCullingGroup::handleChildAddition(const OctreeNode* parent, OctreeNode* child)
{
if (child->getListenerCount() == 0)
{
new LLOcclusionCullingGroup(child, mSpatialPartition);
}
else
{
OCT_ERRS << "LLOcclusionCullingGroup redundancy detected." << LL_ENDL;
}
unbound();
((LLViewerOctreeGroup*)child->getListener(0))->unbound();
}
void LLOcclusionCullingGroup::releaseOcclusionQueryObjectNames()
{
if (gGLManager.mHasOcclusionQuery)
{
for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; ++i)
{
if (mOcclusionQuery[i])
{
releaseOcclusionQueryObjectName(mOcclusionQuery[i]);
mOcclusionQuery[i] = 0;
}
}
}
}
void LLOcclusionCullingGroup::setOcclusionState(eOcclusionState state, S32 mode)
{
if (mode > STATE_MODE_SINGLE)
{
if (mode == STATE_MODE_DIFF)
{
LLSpatialSetOcclusionStateDiff setter(state);
setter.traverse(mOctreeNode);
}
else if (mode == STATE_MODE_BRANCH)
{
LLSpatialSetOcclusionState setter(state);
setter.traverse(mOctreeNode);
}
else
{
for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++)
{
mOcclusionState[i] |= state;
if ((state & DISCARD_QUERY) && mOcclusionQuery[i])
{
releaseOcclusionQueryObjectName(mOcclusionQuery[i]);
mOcclusionQuery[i] = 0;
}
}
}
}
else
{
mOcclusionState[LLViewerCamera::sCurCameraID] |= state;
if ((state & DISCARD_QUERY) && mOcclusionQuery[LLViewerCamera::sCurCameraID])
{
releaseOcclusionQueryObjectName(mOcclusionQuery[LLViewerCamera::sCurCameraID]);
mOcclusionQuery[LLViewerCamera::sCurCameraID] = 0;
}
}
}
class LLSpatialClearOcclusionState : public OctreeTraveler
{
public:
LLOcclusionCullingGroup::eOcclusionState mState;
LLSpatialClearOcclusionState(LLOcclusionCullingGroup::eOcclusionState state) : mState(state) { }
virtual void visit(const OctreeNode* branch)
{
LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*) branch->getListener(0);
if(group)
{
group->clearOcclusionState(mState);
}
}
};
class LLSpatialClearOcclusionStateDiff : public LLSpatialClearOcclusionState
{
public:
LLSpatialClearOcclusionStateDiff(LLOcclusionCullingGroup::eOcclusionState state) : LLSpatialClearOcclusionState(state) { }
virtual void traverse(const OctreeNode* n)
{
LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*) n->getListener(0);
if (group && group->isOcclusionState(mState))
{
OctreeTraveler::traverse(n);
}
}
};
void LLOcclusionCullingGroup::clearOcclusionState(eOcclusionState state, S32 mode)
{
if (mode > STATE_MODE_SINGLE)
{
if (mode == STATE_MODE_DIFF)
{
LLSpatialClearOcclusionStateDiff clearer(state);
clearer.traverse(mOctreeNode);
}
else if (mode == STATE_MODE_BRANCH)
{
LLSpatialClearOcclusionState clearer(state);
clearer.traverse(mOctreeNode);
}
else
{
for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++)
{
mOcclusionState[i] &= ~state;
}
}
}
else
{
mOcclusionState[LLViewerCamera::sCurCameraID] &= ~state;
}
}
static LLTrace::BlockTimerStatHandle FTM_OCCLUSION_READBACK("Readback Occlusion");
static LLTrace::BlockTimerStatHandle FTM_OCCLUSION_WAIT("Occlusion Wait");
BOOL LLOcclusionCullingGroup::earlyFail(LLCamera* camera, const LLVector4a* bounds)
{
if (camera->getOrigin().isExactlyZero())
{
return FALSE;
}
static LLCachedControl<F32> vel("SHOcclusionFudge",SG_OCCLUSION_FUDGE);
LLVector4a fudge(vel*2.f);
const LLVector4a& c = bounds[0];
static LLVector4a r;
r.setAdd(bounds[1], fudge);
/*if (r.magVecSquared() > 1024.0*1024.0)
{
return TRUE;
}*/
LLVector4a e;
e.load3(camera->getOrigin().mV);
LLVector4a min;
min.setSub(c,r);
LLVector4a max;
max.setAdd(c,r);
S32 lt = e.lessThan(min).getGatheredBits() & 0x7;
if (lt)
{
return FALSE;
}
S32 gt = e.greaterThan(max).getGatheredBits() & 0x7;
if (gt)
{
return FALSE;
}
return TRUE;
}
U32 LLOcclusionCullingGroup::getLastOcclusionIssuedTime()
{
return mOcclusionIssued[LLViewerCamera::sCurCameraID];
}
void LLOcclusionCullingGroup::checkOcclusion()
{
if (LLPipeline::sUseOcclusion > 1)
{
LL_RECORD_BLOCK_TIME(FTM_OCCLUSION_READBACK);
LLOcclusionCullingGroup* parent = (LLOcclusionCullingGroup*)getParent();
if (parent && parent->isOcclusionState(LLOcclusionCullingGroup::OCCLUDED))
{ //if the parent has been marked as occluded, the child is implicitly occluded
clearOcclusionState(QUERY_PENDING | DISCARD_QUERY);
}
else if (isOcclusionState(QUERY_PENDING))
{ //otherwise, if a query is pending, read it back
GLuint available = 0;
if (mOcclusionQuery[LLViewerCamera::sCurCameraID])
{
glGetQueryObjectuivARB(mOcclusionQuery[LLViewerCamera::sCurCameraID], GL_QUERY_RESULT_AVAILABLE_ARB, &available);
static LLCachedControl<bool> wait_for_query("RenderSynchronousOcclusion", true);
if (wait_for_query && mOcclusionIssued[LLViewerCamera::sCurCameraID] < gFrameCount)
{ //query was issued last frame, wait until it's available
S32 max_loop = 1024;
LL_RECORD_BLOCK_TIME(FTM_OCCLUSION_WAIT);
while (!available && max_loop-- > 0)
{
F32 max_time = llmin(gFrameIntervalSeconds*10.f, 1.f);
//do some usefu work while we wait
LLAppViewer::getTextureCache()->update(max_time); // unpauses the texture cache thread
LLAppViewer::getImageDecodeThread()->update(max_time); // unpauses the image thread
LLAppViewer::getTextureFetch()->update(max_time); // unpauses the texture fetch thread
glGetQueryObjectuivARB(mOcclusionQuery[LLViewerCamera::sCurCameraID], GL_QUERY_RESULT_AVAILABLE_ARB, &available);
}
}
}
else
{
available = 1;
}
if (available)
{ //result is available, read it back, otherwise wait until next frame
GLuint res = 1;
if (!isOcclusionState(DISCARD_QUERY) && mOcclusionQuery[LLViewerCamera::sCurCameraID])
{
glGetQueryObjectuivARB(mOcclusionQuery[LLViewerCamera::sCurCameraID], GL_QUERY_RESULT_ARB, &res);
#if LL_TRACK_PENDING_OCCLUSION_QUERIES
sPendingQueries.erase(mOcclusionQuery[LLViewerCamera::sCurCameraID]);
#endif
}
else if (mOcclusionQuery[LLViewerCamera::sCurCameraID])
{ //delete the query to avoid holding onto hundreds of pending queries
releaseOcclusionQueryObjectName(mOcclusionQuery[LLViewerCamera::sCurCameraID]);
mOcclusionQuery[LLViewerCamera::sCurCameraID] = 0;
}
if (isOcclusionState(DISCARD_QUERY))
{
res = 2;
}
if (res > 0)
{
assert_states_valid(this);
clearOcclusionState(LLOcclusionCullingGroup::OCCLUDED, LLOcclusionCullingGroup::STATE_MODE_DIFF);
assert_states_valid(this);
}
else
{
assert_states_valid(this);
setOcclusionState(LLOcclusionCullingGroup::OCCLUDED, LLOcclusionCullingGroup::STATE_MODE_DIFF);
assert_states_valid(this);
}
clearOcclusionState(QUERY_PENDING | DISCARD_QUERY);
}
}
else if (mSpatialPartition->isOcclusionEnabled() && isOcclusionState(LLOcclusionCullingGroup::OCCLUDED))
{ //check occlusion has been issued for occluded node that has not had a query issued
assert_states_valid(this);
clearOcclusionState(LLOcclusionCullingGroup::OCCLUDED, LLOcclusionCullingGroup::STATE_MODE_DIFF);
assert_states_valid(this);
}
}
}
static LLTrace::BlockTimerStatHandle FTM_PUSH_OCCLUSION_VERTS("Push Occlusion");
static LLTrace::BlockTimerStatHandle FTM_SET_OCCLUSION_STATE("Occlusion State");
static LLTrace::BlockTimerStatHandle FTM_OCCLUSION_EARLY_FAIL("Occlusion Early Fail");
static LLTrace::BlockTimerStatHandle FTM_OCCLUSION_ALLOCATE("Allocate");
static LLTrace::BlockTimerStatHandle FTM_OCCLUSION_BUILD("Build");
static LLTrace::BlockTimerStatHandle FTM_OCCLUSION_BEGIN_QUERY("Begin Query");
static LLTrace::BlockTimerStatHandle FTM_OCCLUSION_END_QUERY("End Query");
static LLTrace::BlockTimerStatHandle FTM_OCCLUSION_SET_BUFFER("Set Buffer");
static LLTrace::BlockTimerStatHandle FTM_OCCLUSION_DRAW_WATER("Draw Water");
static LLTrace::BlockTimerStatHandle FTM_OCCLUSION_DRAW("Draw");
void LLOcclusionCullingGroup::doOcclusion(LLCamera* camera, const LLVector4a* shift)
{
LLGLDisable<GL_STENCIL_TEST> stencil;
if (mSpatialPartition->isOcclusionEnabled() && LLPipeline::sUseOcclusion > 1)
{
//move mBounds to the agent space if necessary
LLVector4a bounds[2];
bounds[0] = mBounds[0];
bounds[1] = mBounds[1];
if(shift != NULL)
{
bounds[0].add(*shift);
}
// Don't cull hole/edge water, unless we have the GL_ARB_depth_clamp extension
if (earlyFail(camera, bounds))
{
LL_RECORD_BLOCK_TIME(FTM_OCCLUSION_EARLY_FAIL);
setOcclusionState(LLOcclusionCullingGroup::DISCARD_QUERY);
assert_states_valid(this);
clearOcclusionState(LLOcclusionCullingGroup::OCCLUDED, LLOcclusionCullingGroup::STATE_MODE_DIFF);
assert_states_valid(this);
}
else
{
if (!isOcclusionState(QUERY_PENDING) || isOcclusionState(DISCARD_QUERY))
{
{ //no query pending, or previous query to be discarded
LL_RECORD_BLOCK_TIME(FTM_RENDER_OCCLUSION);
if (!mOcclusionQuery[LLViewerCamera::sCurCameraID])
{
LL_RECORD_BLOCK_TIME(FTM_OCCLUSION_ALLOCATE);
mOcclusionQuery[LLViewerCamera::sCurCameraID] = getNewOcclusionQueryObjectName();
}
// Depth clamp all water to avoid it being culled as a result of being
// behind the far clip plane, and in the case of edge water to avoid
// it being culled while still visible.
bool const use_depth_clamp = gGLManager.mHasDepthClamp &&
(mSpatialPartition->mDrawableType == LLDrawPool::POOL_WATER ||
mSpatialPartition->mDrawableType == LLDrawPool::POOL_VOIDWATER);
LLGLEnable<GL_DEPTH_CLAMP> clamp(use_depth_clamp);
#if !LL_DARWIN
U32 mode = gGLManager.mHasOcclusionQuery2 ? GL_ANY_SAMPLES_PASSED : GL_SAMPLES_PASSED_ARB;
#else
U32 mode = GL_SAMPLES_PASSED_ARB;
#endif
#if LL_TRACK_PENDING_OCCLUSION_QUERIES
sPendingQueries.insert(mOcclusionQuery[LLViewerCamera::sCurCameraID]);
#endif
{
LL_RECORD_BLOCK_TIME(FTM_PUSH_OCCLUSION_VERTS);
//store which frame this query was issued on
mOcclusionIssued[LLViewerCamera::sCurCameraID] = gFrameCount;
{
LL_RECORD_BLOCK_TIME(FTM_OCCLUSION_BEGIN_QUERY);
glBeginQueryARB(mode, mOcclusionQuery[LLViewerCamera::sCurCameraID]);
}
LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;
llassert(shader);
shader->uniform3fv(LLShaderMgr::BOX_CENTER, 1, bounds[0].getF32ptr());
//static LLVector4a fudge(SG_OCCLUSION_FUDGE);
static LLCachedControl<F32> vel("SHOcclusionFudge",SG_OCCLUSION_FUDGE);
LLVector4a fudge(SG_OCCLUSION_FUDGE);
static LLVector4a fudged_bounds;
fudged_bounds.setAdd(fudge, bounds[1]);
shader->uniform3fv(LLShaderMgr::BOX_SIZE, 1, fudged_bounds.getF32ptr());
if (!use_depth_clamp && mSpatialPartition->mDrawableType == LLDrawPool::POOL_VOIDWATER)
{
LL_RECORD_BLOCK_TIME(FTM_OCCLUSION_DRAW_WATER);
LLGLSquashToFarClip squash(glh_get_current_projection(), 1);
if (camera->getOrigin().isExactlyZero())
{ //origin is invalid, draw entire box
gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, 0);
gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, b111*8);
}
else
{
gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, get_box_fan_indices(camera, bounds[0]));
}
}
else
{
LL_RECORD_BLOCK_TIME(FTM_OCCLUSION_DRAW);
if (camera->getOrigin().isExactlyZero())
{ //origin is invalid, draw entire box
gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, 0);
gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, b111*8);
}
else
{
gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, get_box_fan_indices(camera, bounds[0]));
}
}
{
LL_RECORD_BLOCK_TIME(FTM_OCCLUSION_END_QUERY);
glEndQueryARB(mode);
}
}
}
{
LL_RECORD_BLOCK_TIME(FTM_SET_OCCLUSION_STATE);
setOcclusionState(LLOcclusionCullingGroup::QUERY_PENDING);
clearOcclusionState(LLOcclusionCullingGroup::DISCARD_QUERY);
}
}
}
}
}
//-------------------------------------------------------------------------------------------
//end of occulsion culling functions and classes
//-------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
//class LLViewerOctreePartition definitions
//-----------------------------------------------------------------------------------
LLViewerOctreePartition::LLViewerOctreePartition() :
mRegionp(NULL),
mOcclusionEnabled(TRUE),
mDrawableType(0),
mLODSeed(0),
mLODPeriod(1)
{
LLVector4a center, size;
center.splat(0.f);
size.splat(1.f);
mOctree = new OctreeRoot(center,size, NULL);
}
LLViewerOctreePartition::~LLViewerOctreePartition()
{
delete mOctree;
mOctree = NULL;
}
BOOL LLViewerOctreePartition::isOcclusionEnabled()
{
return mOcclusionEnabled || LLPipeline::sUseOcclusion > 2;
}
//-----------------------------------------------------------------------------------
//class LLViewerOctreeCull definitions
//-----------------------------------------------------------------------------------
//virtual
bool LLViewerOctreeCull::earlyFail(LLViewerOctreeGroup* group)
{
return false;
}
//virtual
void LLViewerOctreeCull::traverse(const OctreeNode* n)
{
LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) n->getListener(0);
if (earlyFail(group))
{
return;
}
if (mRes == 2 ||
(mRes && group->hasState(LLViewerOctreeGroup::SKIP_FRUSTUM_CHECK)))
{ //fully in, just add everything
OctreeTraveler::traverse(n);
}
else
{
mRes = frustumCheck(group);
if (mRes)
{ //at least partially in, run on down
OctreeTraveler::traverse(n);
}
mRes = 0;
}
}
//------------------------------------------
//agent space group culling
S32 LLViewerOctreeCull::AABBInFrustumNoFarClipGroupBounds(const LLViewerOctreeGroup* group)
{
return mCamera->AABBInFrustumNoFarClip(group->mBounds[0], group->mBounds[1]);
}
S32 LLViewerOctreeCull::AABBSphereIntersectGroupExtents(const LLViewerOctreeGroup* group)
{
return AABBSphereIntersect(group->mExtents[0], group->mExtents[1], mCamera->getOrigin(), mCamera->mFrustumCornerDist);
}
S32 LLViewerOctreeCull::AABBInFrustumGroupBounds(const LLViewerOctreeGroup* group)
{
return mCamera->AABBInFrustum(group->mBounds[0], group->mBounds[1]);
}
//------------------------------------------
//------------------------------------------
//agent space object set culling
S32 LLViewerOctreeCull::AABBInFrustumNoFarClipObjectBounds(const LLViewerOctreeGroup* group)
{
return mCamera->AABBInFrustumNoFarClip(group->mObjectBounds[0], group->mObjectBounds[1]);
}
S32 LLViewerOctreeCull::AABBSphereIntersectObjectExtents(const LLViewerOctreeGroup* group)
{
return AABBSphereIntersect(group->mObjectExtents[0], group->mObjectExtents[1], mCamera->getOrigin(), mCamera->mFrustumCornerDist);
}
S32 LLViewerOctreeCull::AABBInFrustumObjectBounds(const LLViewerOctreeGroup* group)
{
return mCamera->AABBInFrustum(group->mObjectBounds[0], group->mObjectBounds[1]);
}
//------------------------------------------
//------------------------------------------
//local regional space group culling
S32 LLViewerOctreeCull::AABBInRegionFrustumNoFarClipGroupBounds(const LLViewerOctreeGroup* group)
{
return mCamera->AABBInRegionFrustumNoFarClip(group->mBounds[0], group->mBounds[1]);
}
S32 LLViewerOctreeCull::AABBInRegionFrustumGroupBounds(const LLViewerOctreeGroup* group)
{
return mCamera->AABBInRegionFrustum(group->mBounds[0], group->mBounds[1]);
}
S32 LLViewerOctreeCull::AABBRegionSphereIntersectGroupExtents(const LLViewerOctreeGroup* group, const LLVector3& shift)
{
return AABBSphereIntersect(group->mExtents[0], group->mExtents[1], mCamera->getOrigin() - shift, mCamera->mFrustumCornerDist);
}
//------------------------------------------
//------------------------------------------
//local regional space object culling
S32 LLViewerOctreeCull::AABBInRegionFrustumObjectBounds(const LLViewerOctreeGroup* group)
{
return mCamera->AABBInRegionFrustum(group->mObjectBounds[0], group->mObjectBounds[1]);
}
S32 LLViewerOctreeCull::AABBInRegionFrustumNoFarClipObjectBounds(const LLViewerOctreeGroup* group)
{
return mCamera->AABBInRegionFrustumNoFarClip(group->mObjectBounds[0], group->mObjectBounds[1]);
}
S32 LLViewerOctreeCull::AABBRegionSphereIntersectObjectExtents(const LLViewerOctreeGroup* group, const LLVector3& shift)
{
return AABBSphereIntersect(group->mObjectExtents[0], group->mObjectExtents[1], mCamera->getOrigin() - shift, mCamera->mFrustumCornerDist);
}
//------------------------------------------
//check if the objects projection large enough
bool LLViewerOctreeCull::checkProjectionArea(const LLVector4a& center, const LLVector4a& size, const LLVector3& shift, F32 pixel_threshold, F32 near_radius)
{
LLVector3 local_orig = mCamera->getOrigin() - shift;
LLVector4a origin;
origin.load3(local_orig.mV);
LLVector4a lookAt;
lookAt.setSub(center, origin);
F32 distance = lookAt.getLength3().getF32();
if(distance <= near_radius)
{
return true; //always load close-by objects
}
// treat object as if it were near_radius meters closer than it actually was.
// this allows us to get some temporal coherence on visibility...objects that can be reached quickly will tend to be visible
distance -= near_radius;
F32 squared_rad = size.dot3(size).getF32();
return squared_rad / distance > pixel_threshold;
}
//virtual
bool LLViewerOctreeCull::checkObjects(const OctreeNode* branch, const LLViewerOctreeGroup* group)
{
if (branch->getElementCount() == 0) //no elements
{
return false;
}
else if (branch->getChildCount() == 0) //leaf state, already checked tightest bounding box
{
return true;
}
else if (mRes == 1 && !frustumCheckObjects(group)) //no objects in frustum
{
return false;
}
return true;
}
//virtual
void LLViewerOctreeCull::preprocess(LLViewerOctreeGroup* group)
{
}
//virtual
void LLViewerOctreeCull::processGroup(LLViewerOctreeGroup* group)
{
}
//virtual
void LLViewerOctreeCull::visit(const OctreeNode* branch)
{
LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) branch->getListener(0);
preprocess(group);
if (checkObjects(branch, group))
{
processGroup(group);
}
}