/** * @file llspatialpartition.cpp * @brief LLSpatialGroup class implementation and supporting functions * * Copyright (c) 2003-2007, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlife.com/developers/opensource/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at http://secondlife.com/developers/opensource/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. */ #include "llviewerprecompiledheaders.h" #include "llspatialpartition.h" #include "llviewerwindow.h" #include "llviewerobjectlist.h" #include "llvovolume.h" #include "llviewercamera.h" #include "llface.h" #include "viewer.h" #include "llagent.h" #include "llviewerregion.h" #include "llcamera.h" #include "pipeline.h" static GLuint sBoxList = 0; const F32 SG_OCCLUSION_FUDGE = 1.01f; //const S32 SG_LOD_PERIOD = 16; #define SG_DISCARD_TOLERANCE 0.25f #if LL_OCTREE_PARANOIA_CHECK #define assert_octree_valid(x) x->validate() #else #define assert_octree_valid(x) #endif static U32 sZombieGroups = 0; static F32 sLastMaxTexPriority = 1.f; static F32 sCurMaxTexPriority = 1.f; //static counter for frame to switch LOD on void sg_assert(BOOL expr) { #if LL_OCTREE_PARANOIA_CHECK if (!expr) { llerrs << "Octree invalid!" << llendl; } #endif } #if LL_DEBUG void validate_drawable(LLDrawable* drawablep) { F64 rad = drawablep->getBinRadius(); const LLVector3* ext = drawablep->getSpatialExtents(); if (rad < 0 || rad > 4096 || (ext[1]-ext[0]).magVec() > 4096) { llwarns << "Invalid drawable found in octree." << llendl; } } #else #define validate_drawable(x) #endif BOOL earlyFail(LLCamera* camera, LLSpatialGroup* group); BOOL LLLineSegmentAABB(const LLVector3& start, const LLVector3& end, const LLVector3& center, const LLVector3& size) { float fAWdU[3]; LLVector3 dir; LLVector3 diff; for (U32 i = 0; i < 3; i++) { dir.mV[i] = 0.5f * (end.mV[i] - start.mV[i]); diff.mV[i] = (0.5f * (end.mV[i] + start.mV[i])) - center.mV[i]; fAWdU[i] = fabsf(dir.mV[i]); if(fabsf(diff.mV[i])>size.mV[i] + fAWdU[i]) return false; } float f; f = dir.mV[1] * diff.mV[2] - dir.mV[2] * diff.mV[1]; if(fabsf(f)>size.mV[1]*fAWdU[2] + size.mV[2]*fAWdU[1]) return false; f = dir.mV[2] * diff.mV[0] - dir.mV[0] * diff.mV[2]; if(fabsf(f)>size.mV[0]*fAWdU[2] + size.mV[2]*fAWdU[0]) return false; f = dir.mV[0] * diff.mV[1] - dir.mV[1] * diff.mV[0]; if(fabsf(f)>size.mV[0]*fAWdU[1] + size.mV[1]*fAWdU[0]) return false; return true; } //returns: // 0 if sphere and AABB are not intersecting // 1 if they are // 2 if AABB is entirely inside sphere S32 LLSphereAABB(const LLVector3& center, const LLVector3& size, const LLVector3& pos, const F32 &rad) { S32 ret = 2; LLVector3 min = center - size; LLVector3 max = center + size; for (U32 i = 0; i < 3; i++) { if (min.mV[i] > pos.mV[i] + rad || max.mV[i] < pos.mV[i] - rad) { //totally outside return 0; } if (min.mV[i] < pos.mV[i] - rad || max.mV[i] > pos.mV[i] + rad) { //intersecting ret = 1; } } return ret; } LLSpatialGroup::~LLSpatialGroup() { if (isState(DEAD)) { sZombieGroups--; } LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); clearDrawMap(); } void LLSpatialGroup::clearDrawMap() { mDrawMap.clear(); } class LLRelightPainter : public LLSpatialGroup::OctreeTraveler { public: LLVector3 mOrigin, mDir; F32 mRadius; LLRelightPainter(LLVector3 origin, LLVector3 dir, F32 radius) : mOrigin(origin), mDir(dir), mRadius(radius) { } virtual void traverse(const LLSpatialGroup::TreeNode* n) { LLSpatialGroup::OctreeNode* node = (LLSpatialGroup::OctreeNode*) n; LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); group->setState(LLSpatialGroup::RESHADOW); for (U32 i = 0; i < node->getChildCount(); i++) { const LLSpatialGroup::OctreeNode* child = node->getChild(i); LLSpatialGroup* group = (LLSpatialGroup*) child->getListener(0); LLVector3 res; LLVector3 center, size; center = group->mBounds[0]; size = group->mBounds[1]; if (child->isInside(LLVector3d(mOrigin)) || LLRayAABB(center, size, mOrigin, mDir, res, mRadius)) { traverse(child); } } } virtual void visit(const LLSpatialGroup::OctreeState* branch) { } }; BOOL LLSpatialGroup::isVisible() { if (LLPipeline::sUseOcclusion) { return !isState(CULLED | OCCLUDED); } else { return !isState(CULLED); } } void LLSpatialGroup::validate() { #if LL_OCTREE_PARANOIA_CHECK sg_assert(!isState(DIRTY)); sg_assert(!isDead()); LLVector3 myMin = mBounds[0] - mBounds[1]; LLVector3 myMax = mBounds[0] + mBounds[1]; validateDrawMap(); for (element_iter i = getData().begin(); i != getData().end(); ++i) { LLDrawable* drawable = *i; sg_assert(drawable->getSpatialGroup() == this); if (drawable->getSpatialBridge()) { sg_assert(drawable->getSpatialBridge() == mSpatialPartition->asBridge()); } if (drawable->isSpatialBridge()) { LLSpatialPartition* part = drawable->asPartition(); if (!part) { llerrs << "Drawable reports it is a spatial bridge but not a partition." << llendl; } LLSpatialGroup* group = (LLSpatialGroup*) part->mOctree->getListener(0); group->validate(); } } for (U32 i = 0; i < mOctreeNode->getChildCount(); ++i) { LLSpatialGroup* group = (LLSpatialGroup*) mOctreeNode->getChild(i)->getListener(0); group->validate(); //ensure all children are enclosed in this node LLVector3 center = group->mBounds[0]; LLVector3 size = group->mBounds[1]; LLVector3 min = center - size; LLVector3 max = center + size; for (U32 j = 0; j < 3; j++) { sg_assert(min.mV[j] >= myMin.mV[j]-0.02f); sg_assert(max.mV[j] <= myMax.mV[j]+0.02f); } } #endif } void validate_draw_info(LLDrawInfo& params) { #if LL_OCTREE_PARANOIA_CHECK if (params.mVertexBuffer.isNull()) { llerrs << "Draw batch has no vertex buffer." << llendl; } //bad range if (params.mStart >= params.mEnd) { llerrs << "Draw batch has invalid range." << llendl; } if (params.mEnd >= (U32) params.mVertexBuffer->getNumVerts()) { llerrs << "Draw batch has buffer overrun error." << llendl; } if (params.mOffset + params.mCount > (U32) params.mVertexBuffer->getNumIndices()) { llerrs << "Draw batch has index buffer ovverrun error." << llendl; } //bad indices U32* indicesp = (U32*) params.mVertexBuffer->getIndicesPointer(); if (indicesp) { for (U32 i = params.mOffset; i < params.mOffset+params.mCount; i++) { if (indicesp[i] < params.mStart) { llerrs << "Draw batch has vertex buffer index out of range error (index too low)." << llendl; } if (indicesp[i] > params.mEnd) { llerrs << "Draw batch has vertex buffer index out of range error (index too high)." << llendl; } } } #endif } void LLSpatialGroup::validateDrawMap() { #if LL_OCTREE_PARANOIA_CHECK for (draw_map_t::iterator i = mDrawMap.begin(); i != mDrawMap.end(); ++i) { std::vector& draw_vec = i->second; for (std::vector::iterator j = draw_vec.begin(); j != draw_vec.end(); ++j) { LLDrawInfo& params = **j; validate_draw_info(params); } } #endif } void LLSpatialGroup::makeStatic() { #if !LL_DARWIN if (isState(GEOM_DIRTY | ALPHA_DIRTY)) { return; } if (mSpatialPartition->mRenderByGroup && mBufferUsage != GL_STATIC_DRAW_ARB) { mBufferUsage = GL_STATIC_DRAW_ARB; if (mVertexBuffer.notNull()) { mVertexBuffer->makeStatic(); } for (buffer_map_t::iterator i = mBufferMap.begin(); i != mBufferMap.end(); ++i) { i->second->makeStatic(); } mBuilt = 1.f; } #endif } BOOL LLSpatialGroup::updateInGroup(LLDrawable *drawablep, BOOL immediate) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); drawablep->updateSpatialExtents(); validate_drawable(drawablep); OctreeNode* parent = mOctreeNode->getOctParent(); if (mOctreeNode->isInside(drawablep->getPositionGroup()) && (mOctreeNode->contains(drawablep) || (drawablep->getBinRadius() > mOctreeNode->getSize().mdV[0] && parent && parent->getElementCount() >= LL_OCTREE_MAX_CAPACITY))) { unbound(); setState(OBJECT_DIRTY); setState(GEOM_DIRTY); validate_drawable(drawablep); return TRUE; } return FALSE; } BOOL LLSpatialGroup::addObject(LLDrawable *drawablep, BOOL add_all, BOOL from_octree) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); if (!from_octree) { mOctreeNode->insert(drawablep); } else { drawablep->setSpatialGroup(this); validate_drawable(drawablep); setState(OBJECT_DIRTY | GEOM_DIRTY); mLastAddTime = gFrameTimeSeconds; if (drawablep->isSpatialBridge()) { mBridgeList.push_back((LLSpatialBridge*) drawablep); } if (drawablep->getRadius() > 1.f) { setState(IMAGE_DIRTY); } } return TRUE; } void LLSpatialGroup::rebuildGeom() { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); if (!isDead()) { mSpatialPartition->rebuildGeom(this); } } void LLSpatialPartition::rebuildGeom(LLSpatialGroup* group) { if (group->changeLOD()) { group->mLastUpdateDistance = group->mDistance; group->mLastUpdateViewAngle = group->mViewAngle; } if (group->isDead() || !group->isState(LLSpatialGroup::GEOM_DIRTY)) { return; } LLFastTimer ftm(LLFastTimer::FTM_REBUILD_VBO); group->clearDrawMap(); //get geometry count group->mIndexCount = 0; group->mVertexCount = 0; addGeometryCount(group, group->mVertexCount, group->mIndexCount); if (group->mVertexCount > 0 && group->mIndexCount > 0) { //create vertex buffer containing volume geometry for this node group->mBuilt = 1.f; if (group->mVertexBuffer.isNull() || (group->mBufferUsage != group->mVertexBuffer->getUsage() && LLVertexBuffer::sEnableVBOs)) { //LLFastTimer ftm(LLFastTimer::FTM_REBUILD_NONE_VB); group->mVertexBuffer = createVertexBuffer(mVertexDataMask, group->mBufferUsage); group->mVertexBuffer->allocateBuffer(group->mVertexCount, group->mIndexCount, true); stop_glerror(); } else { //LLFastTimer ftm(LLFastTimer::FTM_REBUILD_NONE_VB); group->mVertexBuffer->resizeBuffer(group->mVertexCount, group->mIndexCount); stop_glerror(); } { LLFastTimer ftm((LLFastTimer::EFastTimerType) ((U32) LLFastTimer::FTM_REBUILD_VOLUME_VB + mPartitionType)); getGeometry(group); } } else { group->mVertexBuffer = NULL; group->mBufferMap.clear(); } group->mLastUpdateTime = gFrameTimeSeconds; group->clearState(LLSpatialGroup::GEOM_DIRTY | LLSpatialGroup::MATRIX_DIRTY); } BOOL LLSpatialGroup::boundObjects(BOOL empty, LLVector3& minOut, LLVector3& maxOut) { const OctreeState* node = mOctreeNode->getOctState(); if (node->getData().empty()) { //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." << llendl; } return FALSE; } LLVector3& newMin = mObjectExtents[0]; LLVector3& newMax = mObjectExtents[1]; if (isState(OBJECT_DIRTY)) { //calculate new bounding box clearState(OBJECT_DIRTY); //initialize bounding box to first element OctreeState::const_element_iter i = node->getData().begin(); LLDrawable* drawablep = *i; const LLVector3* minMax = drawablep->getSpatialExtents(); newMin.setVec(minMax[0]); newMax.setVec(minMax[1]); for (++i; i != node->getData().end(); ++i) { drawablep = *i; minMax = drawablep->getSpatialExtents(); //bin up the object for (U32 i = 0; i < 3; i++) { if (minMax[0].mV[i] < newMin.mV[i]) { newMin.mV[i] = minMax[0].mV[i]; } if (minMax[1].mV[i] > newMax.mV[i]) { newMax.mV[i] = minMax[1].mV[i]; } } } mObjectBounds[0] = (newMin + newMax) * 0.5f; mObjectBounds[1] = (newMax - newMin) * 0.5f; } if (empty) { minOut = newMin; maxOut = newMax; } else { for (U32 i = 0; i < 3; i++) { if (newMin.mV[i] < minOut.mV[i]) { minOut.mV[i] = newMin.mV[i]; } if (newMax.mV[i] > maxOut.mV[i]) { maxOut.mV[i] = newMax.mV[i]; } } } return TRUE; } void LLSpatialGroup::unbound() { if (isState(DIRTY)) { return; } setState(DIRTY); //all the parent nodes need to rebound this child if (mOctreeNode) { OctreeNode* parent = (OctreeNode*) mOctreeNode->getParent(); while (parent != NULL) { LLSpatialGroup* group = (LLSpatialGroup*) parent->getListener(0); if (group->isState(DIRTY)) { return; } group->setState(DIRTY); parent = (OctreeNode*) parent->getParent(); } } } LLSpatialGroup* LLSpatialGroup::getParent() { if (isDead()) { return NULL; } OctreeNode* parent = mOctreeNode->getOctParent(); if (parent) { return (LLSpatialGroup*) parent->getListener(0); } return NULL; } BOOL LLSpatialGroup::removeObject(LLDrawable *drawablep, BOOL from_octree) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); unbound(); if (mOctreeNode && !from_octree) { if (!mOctreeNode->remove(drawablep)) { OCT_ERRS << "Could not remove drawable from spatial group" << llendl; } } else { drawablep->setSpatialGroup(NULL); setState(GEOM_DIRTY); if (drawablep->isSpatialBridge()) { for (bridge_list_t::iterator i = mBridgeList.begin(); i != mBridgeList.end(); ++i) { if (*i == drawablep) { mBridgeList.erase(i); break; } } } } return TRUE; } void LLSpatialGroup::shift(const LLVector3 &offset) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); LLVector3d offsetd(offset); mOctreeNode->setCenter(mOctreeNode->getCenter()+offsetd); mOctreeNode->updateMinMax(); mBounds[0] += offset; mExtents[0] += offset; mExtents[1] += offset; mObjectBounds[0] += offset; mObjectExtents[0] += offset; mObjectExtents[1] += offset; setState(GEOM_DIRTY | MATRIX_DIRTY | OCCLUSION_DIRTY); } class LLSpatialSetState : public LLSpatialGroup::OctreeTraveler { public: U32 mState; LLSpatialSetState(U32 state) : mState(state) { } virtual void visit(const LLSpatialGroup::OctreeState* branch) { ((LLSpatialGroup*) branch->getListener(0))->setState(mState); } }; class LLSpatialSetStateDiff : public LLSpatialSetState { public: LLSpatialSetStateDiff(U32 state) : LLSpatialSetState(state) { } virtual void traverse(const LLSpatialGroup::TreeNode* n) { LLSpatialGroup* group = (LLSpatialGroup*) n->getListener(0); if (!group->isState(mState)) { LLSpatialGroup::OctreeTraveler::traverse(n); } } }; void LLSpatialGroup::setState(U32 state, S32 mode) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); if (mode > STATE_MODE_SINGLE) { if (mode == STATE_MODE_DIFF) { LLSpatialSetStateDiff setter(state); setter.traverse(mOctreeNode); } else { LLSpatialSetState setter(state); setter.traverse(mOctreeNode); } } else { mState |= state; } } class LLSpatialClearState : public LLSpatialGroup::OctreeTraveler { public: U32 mState; LLSpatialClearState(U32 state) : mState(state) { } virtual void visit(const LLSpatialGroup::OctreeState* branch) { ((LLSpatialGroup*) branch->getListener(0))->clearState(mState); } }; class LLSpatialClearStateDiff : public LLSpatialClearState { public: LLSpatialClearStateDiff(U32 state) : LLSpatialClearState(state) { } virtual void traverse(const LLSpatialGroup::TreeNode* n) { LLSpatialGroup* group = (LLSpatialGroup*) n->getListener(0); if (!group->isState(mState)) { LLSpatialGroup::OctreeTraveler::traverse(n); } } }; void LLSpatialGroup::clearState(U32 state, S32 mode) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); if (mode > STATE_MODE_SINGLE) { if (mode == STATE_MODE_DIFF) { LLSpatialClearStateDiff clearer(state); clearer.traverse(mOctreeNode); } else { LLSpatialClearState clearer(state); clearer.traverse(mOctreeNode); } } else { mState &= ~state; } #if LL_OCTREE_PARANOIA_CHECK if (state & LLSpatialGroup::ACTIVE_OCCLUSION) { LLSpatialPartition* part = mSpatialPartition; for (U32 i = 0; i < part->mOccludedList.size(); i++) { if (part->mOccludedList[i] == this) { llerrs << "LLSpatialGroup state error: " << mState << llendl; } } } #endif } //====================================== // Octree Listener Implementation //====================================== LLSpatialGroup::LLSpatialGroup(OctreeNode* node, LLSpatialPartition* part) : mState(0), mBuilt(0.f), mOctreeNode(node), mSpatialPartition(part), mVertexBuffer(NULL), mBufferUsage(GL_STATIC_DRAW_ARB), mDistance(0.f), mDepth(0.f), mLastUpdateDistance(-1.f), mLastUpdateTime(gFrameTimeSeconds), mLastAddTime(gFrameTimeSeconds), mLastRenderTime(gFrameTimeSeconds), mViewAngle(0.f), mLastUpdateViewAngle(-1.f) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); sg_assert(mOctreeNode->getListenerCount() == 0); mOctreeNode->addListener(this); setState(SG_INITIAL_STATE_MASK); mBounds[0] = LLVector3(node->getCenter()); mBounds[1] = LLVector3(node->getSize()); part->mLODSeed = (part->mLODSeed+1)%part->mLODPeriod; mLODHash = part->mLODSeed; mRadius = 1; mPixelArea = 1024.f; } void LLSpatialGroup::updateDistance(LLCamera &camera) { #if !LL_RELEASE_FOR_DOWNLOAD if (isState(LLSpatialGroup::OBJECT_DIRTY)) { llerrs << "Spatial group dirty on distance update." << llendl; } #endif if (!getData().empty()) { mRadius = mSpatialPartition->mRenderByGroup ? mObjectBounds[1].magVec() : (F32) mOctreeNode->getSize().magVec(); mDistance = mSpatialPartition->calcDistance(this, camera); mPixelArea = mSpatialPartition->calcPixelArea(this, camera); } } F32 LLSpatialPartition::calcDistance(LLSpatialGroup* group, LLCamera& camera) { LLVector3 eye = group->mObjectBounds[0] - camera.getOrigin(); F32 dist = 0.f; if (group->mDrawMap.find(LLRenderPass::PASS_ALPHA) != group->mDrawMap.end()) { LLVector3 v = eye; dist = eye.normVec(); if (!group->isState(LLSpatialGroup::ALPHA_DIRTY)) { LLVector3 view_angle = LLVector3(eye * LLVector3(1,0,0), eye * LLVector3(0,1,0), eye * LLVector3(0,0,1)); if ((view_angle-group->mLastUpdateViewAngle).magVec() > 0.64f) { group->mViewAngle = view_angle; group->mLastUpdateViewAngle = view_angle; //for occasional alpha sorting within the group //NOTE: If there is a trivial way to detect that alpha sorting here would not change the render order, //not setting this node to dirty would be a very good thing group->setState(LLSpatialGroup::ALPHA_DIRTY); } } //calculate depth of node for alpha sorting LLVector3 at = camera.getAtAxis(); //front of bounding box for (U32 i = 0; i < 3; i++) { v.mV[i] -= group->mObjectBounds[1].mV[i]*0.25f * at.mV[i]; } group->mDepth = v * at; F32 water_height = gAgent.getRegion()->getWaterHeight(); //figure out if this node is above or below water if (group->mObjectBounds[0].mV[2] < water_height) { group->setState(LLSpatialGroup::BELOW_WATER); } else { group->clearState(LLSpatialGroup::BELOW_WATER); } } else { dist = eye.magVec(); } if (dist < 16.f) { dist /= 16.f; dist *= dist; dist *= 16.f; } return dist; } F32 LLSpatialPartition::calcPixelArea(LLSpatialGroup* group, LLCamera& camera) { return LLPipeline::calcPixelArea(group->mObjectBounds[0], group->mObjectBounds[1], camera); } BOOL LLSpatialGroup::changeLOD() { if (isState(ALPHA_DIRTY)) { ///an alpha sort is going to happen, update distance and LOD return TRUE; } if (mSpatialPartition->mSlopRatio > 0.f) { F32 ratio = (mDistance - mLastUpdateDistance)/(llmax(mLastUpdateDistance, mRadius)); if (fabsf(ratio) >= mSpatialPartition->mSlopRatio) { return TRUE; } if (mDistance > mRadius) { return FALSE; } } if (LLDrawable::getCurrentFrame()%mSpatialPartition->mLODPeriod == mLODHash) { return TRUE; } return FALSE; } void LLSpatialGroup::handleInsertion(const TreeNode* node, LLDrawable* drawablep) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); addObject(drawablep, FALSE, TRUE); unbound(); setState(OBJECT_DIRTY); } void LLSpatialGroup::handleRemoval(const TreeNode* node, LLDrawable* drawable) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); removeObject(drawable, TRUE); setState(OBJECT_DIRTY); } void LLSpatialGroup::handleDestruction(const TreeNode* node) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); setState(DEAD); for (element_iter i = getData().begin(); i != getData().end(); ++i) { LLDrawable* drawable = *i; if (drawable->getSpatialGroup() == this) { drawable->setSpatialGroup(NULL); } } clearDrawMap(); mOcclusionVerts = NULL; mVertexBuffer = NULL; mBufferMap.clear(); sZombieGroups++; mOctreeNode = NULL; } void LLSpatialGroup::handleStateChange(const TreeNode* node) { //drop bounding box upon state change if (mOctreeNode != node) { mOctreeNode = (OctreeNode*) node; } unbound(); } void LLSpatialGroup::handleChildAddition(const OctreeNode* parent, OctreeNode* child) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); if (child->getListenerCount() == 0) { LLSpatialGroup* group = new LLSpatialGroup(child, mSpatialPartition); group->setState(mState & SG_STATE_INHERIT_MASK); } else { OCT_ERRS << "LLSpatialGroup redundancy detected." << llendl; } unbound(); } void LLSpatialGroup::handleChildRemoval(const OctreeNode* parent, const OctreeNode* child) { unbound(); } void LLSpatialGroup::destroyGL() { setState(LLSpatialGroup::GEOM_DIRTY | LLSpatialGroup::OCCLUSION_DIRTY | LLSpatialGroup::IMAGE_DIRTY); mLastUpdateTime = gFrameTimeSeconds; mVertexBuffer = NULL; mBufferMap.clear(); mOcclusionVerts = NULL; mReflectionMap = NULL; clearDrawMap(); for (LLSpatialGroup::element_iter i = getData().begin(); i != getData().end(); ++i) { LLDrawable* drawable = *i; for (S32 j = 0; j < drawable->getNumFaces(); j++) { LLFace* facep = drawable->getFace(j); facep->mVertexBuffer = NULL; facep->mLastVertexBuffer = NULL; } } } BOOL LLSpatialGroup::rebound() { if (!isState(DIRTY)) { //return TRUE if we're not empty return TRUE; } LLVector3 oldBounds[2]; if (mSpatialPartition->isVolatile() && isState(QUERY_OUT)) { //a query has been issued, if our bounding box changes significantly //we need to discard the issued query oldBounds[0] = mBounds[0]; oldBounds[1] = mBounds[1]; } if (mOctreeNode->getChildCount() == 1 && mOctreeNode->getElementCount() == 0) { LLSpatialGroup* group = (LLSpatialGroup*) 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->hasLeafState()) { //copy object bounding box if this is a leaf boundObjects(TRUE, mExtents[0], mExtents[1]); mBounds[0] = mObjectBounds[0]; mBounds[1] = mObjectBounds[1]; } else { LLVector3& newMin = mExtents[0]; LLVector3& newMax = mExtents[1]; LLSpatialGroup* group = (LLSpatialGroup*) 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 = (LLSpatialGroup*) mOctreeNode->getChild(i)->getListener(0); group->clearState(SKIP_FRUSTUM_CHECK); group->rebound(); const LLVector3& max = group->mExtents[1]; const LLVector3& min = group->mExtents[0]; for (U32 j = 0; j < 3; j++) { if (max.mV[j] > newMax.mV[j]) { newMax.mV[j] = max.mV[j]; } if (min.mV[j] < newMin.mV[j]) { newMin.mV[j] = min.mV[j]; } } } boundObjects(FALSE, newMin, newMax); mBounds[0] = (newMin + newMax)*0.5f; mBounds[1] = (newMax - newMin)*0.5f; } if (mSpatialPartition->isVolatile() && isState(QUERY_OUT)) { for (U32 i = 0; i < 3 && !isState(DISCARD_QUERY); i++) { if (fabsf(mBounds[0].mV[i]-oldBounds[0].mV[i]) > SG_DISCARD_TOLERANCE || fabsf(mBounds[1].mV[i]-oldBounds[1].mV[i]) > SG_DISCARD_TOLERANCE) { //bounding box changed significantly, discard last issued //occlusion query setState(DISCARD_QUERY); } } } setState(OCCLUSION_DIRTY); clearState(DIRTY); return TRUE; } //============================================== LLSpatialPartition::LLSpatialPartition(U32 data_mask, BOOL is_volatile, U32 buffer_usage) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); mDrawableType = 0; mPartitionType = LLPipeline::PARTITION_NONE; mVolatile = is_volatile; mLODSeed = 0; mLODPeriod = 1; mVertexDataMask = data_mask; mBufferUsage = buffer_usage; mDepthMask = FALSE; mSlopRatio = 0.25f; mRenderByGroup = TRUE; mImageEnabled = FALSE; mOctree = new LLSpatialGroup::OctreeNode(LLVector3d(0,0,0), LLVector3d(1,1,1), new LLSpatialGroup::OctreeRoot(), NULL); new LLSpatialGroup(mOctree, this); } LLSpatialPartition::~LLSpatialPartition() { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); for (U32 i = 0; i < mOcclusionQueries.size(); i++) { glDeleteQueriesARB(1, (GLuint*)(&(mOcclusionQueries[i]))); } delete mOctree; mOctree = NULL; } LLSpatialGroup *LLSpatialPartition::put(LLDrawable *drawablep, BOOL was_visible) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); const F32 MAX_MAG = 1000000.f*1000000.f; // 1 million if (drawablep->getPositionGroup().magVecSquared() > MAX_MAG) { #if 0 //ndef LL_RELEASE_FOR_DOWNLOAD llwarns << "LLSpatialPartition::put Object out of range!" << llendl; llinfos << drawablep->getPositionGroup() << llendl; if (drawablep->getVObj()) { llwarns << "Dumping debugging info: " << llendl; drawablep->getVObj()->dump(); } #endif return NULL; } drawablep->updateSpatialExtents(); validate_drawable(drawablep); //keep drawable from being garbage collected LLPointer ptr = drawablep; assert_octree_valid(mOctree); mOctree->insert(drawablep); assert_octree_valid(mOctree); LLSpatialGroup::OctreeNode* node = mOctree->getNodeAt(drawablep); LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); if (was_visible && group->isState(LLSpatialGroup::QUERY_OUT)) { group->setState(LLSpatialGroup::DISCARD_QUERY); } return group; } BOOL LLSpatialPartition::remove(LLDrawable *drawablep, LLSpatialGroup *curp) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); drawablep->setSpatialGroup(NULL); if (!curp->removeObject(drawablep)) { OCT_ERRS << "Failed to remove drawable from octree!" << llendl; } assert_octree_valid(mOctree); return TRUE; } void LLSpatialPartition::move(LLDrawable *drawablep, LLSpatialGroup *curp, BOOL immediate) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); LLFastTimer t(LLFastTimer::FTM_UPDATE_MOVE); // sanity check submitted by open source user bushing Spatula // who was seeing crashing here. (See VWR-424 reported by Bunny Mayne) if (!drawablep) { OCT_ERRS << "LLSpatialPartition::move was passed a bad drawable." << llendl; return; } BOOL was_visible = curp ? curp->isVisible() : FALSE; if (curp && curp->mSpatialPartition != this) { //keep drawable from being garbage collected LLPointer ptr = drawablep; if (curp->mSpatialPartition->remove(drawablep, curp)) { put(drawablep, was_visible); return; } else { OCT_ERRS << "Drawable lost between spatial partitions on outbound transition." << llendl; } } if (curp && curp->updateInGroup(drawablep, immediate)) { // Already updated, don't need to do anything assert_octree_valid(mOctree); return; } //keep drawable from being garbage collected LLPointer ptr = drawablep; if (curp && !remove(drawablep, curp)) { OCT_ERRS << "Move couldn't find existing spatial group!" << llendl; } put(drawablep, was_visible); } class LLSpatialShift : public LLSpatialGroup::OctreeTraveler { public: LLSpatialShift(LLVector3 offset) : mOffset(offset) { } virtual void visit(const LLSpatialGroup::OctreeState* branch) { ((LLSpatialGroup*) branch->getListener(0))->shift(mOffset); } LLVector3 mOffset; }; void LLSpatialPartition::shift(const LLVector3 &offset) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); llinfos << "Shifting octree: " << offset << llendl; LLSpatialShift shifter(offset); shifter.traverse(mOctree); } BOOL LLSpatialPartition::checkOcclusion(LLSpatialGroup* group, LLCamera* camera) { if (LLPipeline::sUseOcclusion && !group->isState(LLSpatialGroup::ACTIVE_OCCLUSION | LLSpatialGroup::OCCLUDED) && (!camera || !earlyFail(camera, group))) { group->setState(LLSpatialGroup::ACTIVE_OCCLUSION); mQueryQueue.push(group); return TRUE; } return FALSE; } class LLOctreeCull : public LLSpatialGroup::OctreeTraveler { public: LLOctreeCull(LLCamera* camera) : mCamera(camera), mRes(0) { } virtual bool earlyFail(const LLSpatialGroup* group) { if (group->mOctreeNode->getParent() && //never occlusion cull the root node LLPipeline::sUseOcclusion && //never occlusion cull selection group->isState(LLSpatialGroup::OCCLUDED)) { return true; } return false; } virtual void traverse(const LLSpatialGroup::TreeNode* n) { LLSpatialGroup* group = (LLSpatialGroup*) n->getListener(0); if (earlyFail(group)) { return; } if (mRes == 2 || (mRes && group->isState(LLSpatialGroup::SKIP_FRUSTUM_CHECK))) { //fully in, just add everything LLSpatialGroup::OctreeTraveler::traverse(n); } else { mRes = mCamera->AABBInFrustum(group->mBounds[0], group->mBounds[1]); if (mRes) { //at least partially in, run on down LLSpatialGroup::OctreeTraveler::traverse(n); } else { lateFail(group); } mRes = 0; } } virtual void lateFail(LLSpatialGroup* group) { if (!group->isState(LLSpatialGroup::CULLED)) { //totally culled, so are all its children group->setState(LLSpatialGroup::CULLED, LLSpatialGroup::STATE_MODE_DIFF); } } virtual bool checkObjects(const LLSpatialGroup::OctreeState* branch, const LLSpatialGroup* 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 && !mCamera->AABBInFrustum(group->mObjectBounds[0], group->mObjectBounds[1])) //no objects in frustum { return false; } return true; } virtual void preprocess(LLSpatialGroup* group) { if (group->isState(LLSpatialGroup::CULLED)) { //this is the first frame this node is visible group->clearState(LLSpatialGroup::CULLED); if (group->mOctreeNode->hasLeafState()) { //if it's a leaf, force it onto the active occlusion list to prevent //massive frame stutters group->mSpatialPartition->checkOcclusion(group, mCamera); } } if (LLPipeline::sDynamicReflections && group->mOctreeNode->getSize().mdV[0] == 16.0 && group->mDistance < 64.f && group->mLastAddTime < gFrameTimeSeconds - 2.f) { group->mSpatialPartition->markReimage(group); } } virtual void processGroup(LLSpatialGroup* group) { gPipeline.markNotCulled(group, *mCamera); } virtual void visit(const LLSpatialGroup::OctreeState* branch) { LLSpatialGroup* group = (LLSpatialGroup*) branch->getListener(0); preprocess(group); if (checkObjects(branch, group)) { processGroup(group); } } LLCamera *mCamera; S32 mRes; }; class LLOctreeSelect : public LLOctreeCull { public: LLOctreeSelect(LLCamera* camera, std::vector* results) : LLOctreeCull(camera), mResults(results) { } virtual bool earlyFail(const LLSpatialGroup* group) { return false; } virtual void lateFail(LLSpatialGroup* group) { } virtual void preprocess(LLSpatialGroup* group) { } virtual void processGroup(LLSpatialGroup* group) { LLSpatialGroup::OctreeState* branch = group->mOctreeNode->getOctState(); for (LLSpatialGroup::OctreeState::const_element_iter i = branch->getData().begin(); i != branch->getData().end(); ++i) { LLDrawable* drawable = *i; if (!drawable->isDead()) { if (drawable->isSpatialBridge()) { drawable->setVisible(*mCamera, mResults, TRUE); } else { mResults->push_back(drawable); } } } } std::vector* mResults; }; void genBoxList() { if (sBoxList != 0) { return; } sBoxList = glGenLists(1); glNewList(sBoxList, GL_COMPILE); LLVector3 c,r; c = LLVector3(0,0,0); r = LLVector3(1,1,1); glBegin(GL_TRIANGLE_STRIP); //left front glVertex3fv((c+r.scaledVec(LLVector3(-1,1,-1))).mV); glVertex3fv((c+r.scaledVec(LLVector3(-1,1,1))).mV); //right front glVertex3fv((c+r.scaledVec(LLVector3(1,1,-1))).mV); glVertex3fv((c+r.scaledVec(LLVector3(1,1,1))).mV); //right back glVertex3fv((c+r.scaledVec(LLVector3(1,-1,-1))).mV); glVertex3fv((c+r.scaledVec(LLVector3(1,-1,1))).mV); //left back glVertex3fv((c+r.scaledVec(LLVector3(-1,-1,-1))).mV); glVertex3fv((c+r.scaledVec(LLVector3(-1,-1,1))).mV); //left front glVertex3fv((c+r.scaledVec(LLVector3(-1,1,-1))).mV); glVertex3fv((c+r.scaledVec(LLVector3(-1,1,1))).mV); glEnd(); //bottom glBegin(GL_TRIANGLE_STRIP); glVertex3fv((c+r.scaledVec(LLVector3(1,1,-1))).mV); glVertex3fv((c+r.scaledVec(LLVector3(1,-1,-1))).mV); glVertex3fv((c+r.scaledVec(LLVector3(-1,1,-1))).mV); glVertex3fv((c+r.scaledVec(LLVector3(-1,-1,-1))).mV); glEnd(); //top glBegin(GL_TRIANGLE_STRIP); glVertex3fv((c+r.scaledVec(LLVector3(1,1,1))).mV); glVertex3fv((c+r.scaledVec(LLVector3(-1,1,1))).mV); glVertex3fv((c+r.scaledVec(LLVector3(1,-1,1))).mV); glVertex3fv((c+r.scaledVec(LLVector3(-1,-1,1))).mV); glEnd(); glEndList(); } void drawBox(const LLVector3& c, const LLVector3& r) { genBoxList(); glPushMatrix(); glTranslatef(c.mV[0], c.mV[1], c.mV[2]); glScalef(r.mV[0], r.mV[1], r.mV[2]); glCallList(sBoxList); glPopMatrix(); } void drawBoxOutline(const LLVector3& pos, const LLVector3& size) { LLVector3 v1 = size.scaledVec(LLVector3( 1, 1,1)); LLVector3 v2 = size.scaledVec(LLVector3(-1, 1,1)); LLVector3 v3 = size.scaledVec(LLVector3(-1,-1,1)); LLVector3 v4 = size.scaledVec(LLVector3( 1,-1,1)); glBegin(GL_LINE_LOOP); //top glVertex3fv((pos+v1).mV); glVertex3fv((pos+v2).mV); glVertex3fv((pos+v3).mV); glVertex3fv((pos+v4).mV); glEnd(); glBegin(GL_LINE_LOOP); //bottom glVertex3fv((pos-v1).mV); glVertex3fv((pos-v2).mV); glVertex3fv((pos-v3).mV); glVertex3fv((pos-v4).mV); glEnd(); glBegin(GL_LINES); //right glVertex3fv((pos+v1).mV); glVertex3fv((pos-v3).mV); glVertex3fv((pos+v4).mV); glVertex3fv((pos-v2).mV); //left glVertex3fv((pos+v2).mV); glVertex3fv((pos-v4).mV); glVertex3fv((pos+v3).mV); glVertex3fv((pos-v1).mV); glEnd(); } class LLOctreeDirty : public LLOctreeTraveler { public: virtual void visit(const LLOctreeState* state) { LLSpatialGroup* group = (LLSpatialGroup*) state->getListener(0); group->destroyGL(); for (LLSpatialGroup::element_iter i = group->getData().begin(); i != group->getData().end(); ++i) { LLDrawable* drawable = *i; if (drawable->getVObj().notNull() && !group->mSpatialPartition->mRenderByGroup) { gPipeline.markRebuild(drawable, LLDrawable::REBUILD_ALL, TRUE); } } for (LLSpatialGroup::bridge_list_t::iterator i = group->mBridgeList.begin(); i != group->mBridgeList.end(); ++i) { LLSpatialBridge* bridge = *i; traverse(bridge->mOctree); } } }; void LLSpatialPartition::restoreGL() { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); mOcclusionQueries.clear(); sBoxList = 0; //generate query ids while (mOcclusionQueries.size() < mOccludedList.size()) { GLuint id; glGenQueriesARB(1, &id); mOcclusionQueries.push_back(id); } for (U32 i = 0; i < mOccludedList.size(); i++) { //previously issued queries are now invalid mOccludedList[i]->setState(LLSpatialGroup::DISCARD_QUERY); } genBoxList(); } void LLSpatialPartition::resetVertexBuffers() { LLOctreeDirty dirty; dirty.traverse(mOctree); mOcclusionIndices = NULL; } S32 LLSpatialPartition::cull(LLCamera &camera, std::vector* results, BOOL for_select) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); { LLFastTimer ftm(LLFastTimer::FTM_CULL_REBOUND); LLSpatialGroup* group = (LLSpatialGroup*) mOctree->getListener(0); group->rebound(); } if (for_select) { LLOctreeSelect selecter(&camera, results); selecter.traverse(mOctree); } else { LLFastTimer ftm(LLFastTimer::FTM_FRUSTUM_CULL); LLOctreeCull culler(&camera); culler.traverse(mOctree); } return 0; } class LLOctreeClearOccludedNotActive : public LLSpatialGroup::OctreeTraveler { public: LLOctreeClearOccludedNotActive() { } virtual void traverse(const LLSpatialGroup::TreeNode* n) { LLSpatialGroup* group = (LLSpatialGroup*) n->getListener(0); if ((!group->isState(LLSpatialGroup::ACTIVE_OCCLUSION)) //|| group->isState(LLSpatialGroup::QUERY_PENDING) || group->isState(LLSpatialGroup::DEACTIVATE_OCCLUSION)) { //the children are all occluded or culled as well group->clearState(LLSpatialGroup::OCCLUDED); for (U32 i = 0; i < group->mOctreeNode->getChildCount(); i++) { traverse(group->mOctreeNode->getChild(i)); } } } virtual void visit(const LLSpatialGroup::OctreeState* branch) { } }; class LLQueueNonCulled : public LLSpatialGroup::OctreeTraveler { public: std::queue* mQueue; LLQueueNonCulled(std::queue *queue) : mQueue(queue) { } virtual void traverse(const LLSpatialGroup::TreeNode* n) { LLSpatialGroup* group = (LLSpatialGroup*) n->getListener(0); if (group->isState(LLSpatialGroup::OCCLUDED | LLSpatialGroup::CULLED)) { //the children are all occluded or culled as well return; } if (!group->isState(LLSpatialGroup::IN_QUEUE)) { group->setState(LLSpatialGroup::IN_QUEUE); mQueue->push(group); } LLSpatialGroup::OctreeTraveler::traverse(n); } virtual void visit(const LLSpatialGroup::OctreeState* branch) { } }; BOOL earlyFail(LLCamera* camera, LLSpatialGroup* group) { LLVector3 c = group->mBounds[0]; LLVector3 r = group->mBounds[1]*SG_OCCLUSION_FUDGE + LLVector3(0.2f,0.2f,0.2f); //if (group->isState(LLSpatialGroup::CULLED)) // || if (!camera->AABBInFrustum(c, r)) { return TRUE; } LLVector3 e = camera->getOrigin(); LLVector3 min = c - r; LLVector3 max = c + r; for (U32 j = 0; j < 3; j++) { if (e.mV[j] < min.mV[j] || e.mV[j] > max.mV[j]) { return FALSE; } } return TRUE; } void LLSpatialPartition::markReimage(LLSpatialGroup* group) { if (mImageEnabled && group->isState(LLSpatialGroup::IMAGE_DIRTY)) { if (!group->isState(LLSpatialGroup::IN_IMAGE_QUEUE)) { group->setState(LLSpatialGroup::IN_IMAGE_QUEUE); mImageQueue.push(group); } } } void LLSpatialPartition::processImagery(LLCamera* camera) { if (!mImageEnabled) { return; } U32 process_count = 1; while (process_count > 0 && !mImageQueue.empty()) { LLPointer group = mImageQueue.front(); mImageQueue.pop(); group->clearState(LLSpatialGroup::IN_IMAGE_QUEUE); if (group->isDead()) { continue; } if (LLPipeline::sDynamicReflections) { process_count--; LLVector3 origin = group->mBounds[0]; LLCamera cube_cam; cube_cam.setOrigin(origin); cube_cam.setFar(64.f); LLPointer cube_map = group->mReflectionMap; group->mReflectionMap = NULL; if (cube_map.isNull()) { cube_map = new LLCubeMap(); cube_map->initGL(); } if (gPipeline.mCubeBuffer.isNull()) { gPipeline.mCubeBuffer = new LLCubeMap(); gPipeline.mCubeBuffer->initGL(); } S32 res = gSavedSettings.getS32("RenderReflectionRes"); gPipeline.generateReflectionMap(gPipeline.mCubeBuffer, cube_cam, 128); gPipeline.blurReflectionMap(gPipeline.mCubeBuffer, cube_map, res); group->mReflectionMap = cube_map; group->setState(LLSpatialGroup::GEOM_DIRTY); } group->clearState(LLSpatialGroup::IMAGE_DIRTY); } } void validate_occlusion_list(std::vector >& occluded_list) { #if !LL_RELEASE_FOR_DOWNLOAD for (U32 i = 0; i < occluded_list.size(); i++) { LLSpatialGroup* group = occluded_list[i]; for (U32 j = i+1; j < occluded_list.size(); j++) { if (occluded_list[i] == occluded_list[j]) { llerrs << "Duplicate node in occlusion list." << llendl; } } LLSpatialGroup::OctreeNode* parent = group->mOctreeNode->getOctParent(); while (parent) { LLSpatialGroup* parent_group = (LLSpatialGroup*) parent->getListener(0); if (parent_group->isState(LLSpatialGroup::OCCLUDED)) { llerrs << "Child node of occluded node in occlusion list (redundant query)." << llendl; } parent = parent->getOctParent(); } } #endif } void LLSpatialPartition::processOcclusion(LLCamera* camera) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); LLSpatialGroup* rootGroup = (LLSpatialGroup*) mOctree->getListener(0); { LLFastTimer ftm(LLFastTimer::FTM_CULL_REBOUND); rootGroup->rebound(); } //update potentials if (!rootGroup->isState(LLSpatialGroup::IN_QUEUE)) { rootGroup->setState(LLSpatialGroup::IN_QUEUE); mOcclusionQueue.push(rootGroup); } const U32 MAX_PULLED = 32; const U32 MAX_PUSHED = mOcclusionQueue.size(); U32 count = 0; U32 pcount = 0; while (pcount < MAX_PUSHED && count < MAX_PULLED && !mOcclusionQueue.empty()) { LLFastTimer t(LLFastTimer::FTM_OCCLUSION); LLPointer group = mOcclusionQueue.front(); if (!group->isState(LLSpatialGroup::IN_QUEUE)) { OCT_ERRS << "Spatial Group State Error. Group in queue not tagged as such." << llendl; } mOcclusionQueue.pop(); group->clearState(LLSpatialGroup::IN_QUEUE); if (group->isDead()) { continue; } if (group->isState(LLSpatialGroup::CULLED | LLSpatialGroup::OCCLUDED)) { //already culled, skip it continue; } //before we process, enqueue this group's children for (U32 i = 0; i < group->mOctreeNode->getChildCount(); i++) { LLSpatialGroup* child = (LLSpatialGroup*) group->mOctreeNode->getChild(i)->getListener(0); //if (!child->isState(LLSpatialGroup::OCCLUDED | LLSpatialGroup::CULLED) if (!child->isState(LLSpatialGroup::IN_QUEUE | LLSpatialGroup::ACTIVE_OCCLUSION)) { child->setState(LLSpatialGroup::IN_QUEUE); mOcclusionQueue.push(child); } } if (earlyFail(camera, group)) { sg_assert(!group->isState(LLSpatialGroup::OCCLUDED)); group->setState(LLSpatialGroup::IN_QUEUE); mOcclusionQueue.push(group); pcount++; continue; } //add to pending queue if (!group->isState(LLSpatialGroup::ACTIVE_OCCLUSION)) { #if LL_OCTREE_PARANOIA_CHECK for (U32 i = 0; i < mOccludedList.size(); ++i) { sg_assert(mOccludedList[i] != group); } #endif group->setState(LLSpatialGroup::ACTIVE_OCCLUSION); mQueryQueue.push(group); count++; } } //read back results from last frame for (U32 i = 0; i < mOccludedList.size(); i++) { LLFastTimer t(LLFastTimer::FTM_OCCLUSION_READBACK); if (mOccludedList[i]->isDead() || mOccludedList[i]->isState(LLSpatialGroup::DEACTIVATE_OCCLUSION)) { continue; } GLuint res = 0; if (mOccludedList[i]->isState(LLSpatialGroup::EARLY_FAIL | LLSpatialGroup::DISCARD_QUERY) || !mOccludedList[i]->isState(LLSpatialGroup::QUERY_OUT)) { mOccludedList[i]->clearState(LLSpatialGroup::EARLY_FAIL); mOccludedList[i]->clearState(LLSpatialGroup::DISCARD_QUERY); res = 1; } else { glGetQueryObjectuivARB(mOcclusionQueries[i], GL_QUERY_RESULT_ARB, &res); stop_glerror(); } if (res) //NOT OCCLUDED { if (mOccludedList[i]->isState(LLSpatialGroup::OCCLUDED)) { //this node was occluded last frame LLSpatialGroup::OctreeNode* node = mOccludedList[i]->mOctreeNode; //add any immediate children to the queue that are not already there for (U32 j = 0; j < node->getChildCount(); j++) { LLSpatialGroup* group = (LLSpatialGroup*) node->getChild(j)->getListener(0); checkOcclusion(group, camera); } } //clear occlusion status for everything not on the active list LLOctreeClearOccludedNotActive clear_occluded; mOccludedList[i]->setState(LLSpatialGroup::DEACTIVATE_OCCLUSION); mOccludedList[i]->clearState(LLSpatialGroup::OCCLUDED); mOccludedList[i]->clearState(LLSpatialGroup::OCCLUDING); clear_occluded.traverse(mOccludedList[i]->mOctreeNode); } else { //OCCLUDED if (mOccludedList[i]->isState(LLSpatialGroup::OCCLUDING)) { if (!mOccludedList[i]->isState(LLSpatialGroup::OCCLUDED)) { LLSpatialGroup::OctreeNode* oct_parent = (LLSpatialGroup::OctreeNode*) mOccludedList[i]->mOctreeNode->getParent(); if (oct_parent) { LLSpatialGroup* parent = (LLSpatialGroup*) oct_parent->getListener(0); if (checkOcclusion(parent, camera)) { //force a guess on the parent and siblings for (U32 i = 0; i < parent->mOctreeNode->getChildCount(); i++) { LLSpatialGroup* child = (LLSpatialGroup*) parent->mOctreeNode->getChild(i)->getListener(0); checkOcclusion(child, camera); } } } //take children off the active list mOccludedList[i]->setState(LLSpatialGroup::DEACTIVATE_OCCLUSION, LLSpatialGroup::STATE_MODE_BRANCH); mOccludedList[i]->clearState(LLSpatialGroup::DEACTIVATE_OCCLUSION); } mOccludedList[i]->setState(LLSpatialGroup::OCCLUDED, LLSpatialGroup::STATE_MODE_DIFF); } else { //take children off the active list mOccludedList[i]->setState(LLSpatialGroup::DEACTIVATE_OCCLUSION, LLSpatialGroup::STATE_MODE_BRANCH); //keep this node on the active list mOccludedList[i]->clearState(LLSpatialGroup::DEACTIVATE_OCCLUSION); //this node is a top level occluder mOccludedList[i]->setState(LLSpatialGroup::OCCLUDING); } } mOccludedList[i]->clearState(LLSpatialGroup::QUERY_OUT); } //remove non-occluded groups from occluded list for (U32 i = 0; i < mOccludedList.size(); ) { if (mOccludedList[i]->isDead() || //needs to be deleted !mOccludedList[i]->isState(LLSpatialGroup::OCCLUDING) || //is not occluding mOccludedList[i]->isState(LLSpatialGroup::DEACTIVATE_OCCLUSION)) //parent is occluded { LLSpatialGroup* groupp = mOccludedList[i]; if (!groupp->isDead()) { groupp->clearState(LLSpatialGroup::ACTIVE_OCCLUSION); groupp->clearState(LLSpatialGroup::DEACTIVATE_OCCLUSION); groupp->clearState(LLSpatialGroup::OCCLUDING); } mOccludedList.erase(mOccludedList.begin()+i); } else { i++; } } validate_occlusion_list(mOccludedList); //pump some non-culled items onto the occlusion list //count = MAX_PULLED; while (!mQueryQueue.empty()) { LLPointer group = mQueryQueue.front(); mQueryQueue.pop(); //group->clearState(LLSpatialGroup::QUERY_PENDING); mOccludedList.push_back(group); } //generate query ids while (mOcclusionQueries.size() < mOccludedList.size()) { GLuint id; glGenQueriesARB(1, &id); mOcclusionQueries.push_back(id); } } class LLOcclusionIndexBuffer : public LLVertexBuffer { public: LLOcclusionIndexBuffer(U32 size) : LLVertexBuffer(0, GL_STREAM_DRAW_ARB) { allocateBuffer(0, size, TRUE); LLStrider idx; getIndexStrider(idx); //12 triangles' indices idx[0] = 1; idx[1] = 0; idx[2] = 2; //front idx[3] = 3; idx[4] = 2; idx[5] = 0; idx[6] = 4; idx[7] = 5; idx[8] = 1; //top idx[9] = 0; idx[10] = 1; idx[11] = 5; idx[12] = 5; idx[13] = 4; idx[14] = 6; //back idx[15] = 7; idx[16] = 6; idx[17] = 4; idx[18] = 6; idx[19] = 7; idx[20] = 3; //bottom idx[21] = 2; idx[22] = 3; idx[23] = 7; idx[24] = 0; idx[25] = 5; idx[26] = 3; //left idx[27] = 6; idx[28] = 3; idx[29] = 5; idx[30] = 4; idx[31] = 1; idx[32] = 7; //right idx[33] = 2; idx[34] = 7; idx[35] = 1; } //virtual BOOL useVBOs() const { return FALSE; } void setBuffer(U32 data_mask) { if (useVBOs()) { glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, mGLIndices); sIBOActive = TRUE; unmapBuffer(); } else if (sIBOActive) { glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); sIBOActive = FALSE; } sGLRenderIndices = mGLIndices; } }; class LLOcclusionVertexBuffer : public LLVertexBuffer { public: LLOcclusionVertexBuffer(S32 usage) : LLVertexBuffer(MAP_VERTEX, usage) { allocateBuffer(8, 0, TRUE); } //virtual BOOL useVBOs() const { return FALSE; } void setBuffer(U32 data_mask) { if (useVBOs()) { glBindBufferARB(GL_ARRAY_BUFFER_ARB, mGLBuffer); sVBOActive = TRUE; unmapBuffer(); } else if (sVBOActive) { glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); sVBOActive = FALSE; } if (data_mask) { glVertexPointer(3,GL_FLOAT, 0, useVBOs() ? 0 : mMappedData); } sGLRenderBuffer = mGLBuffer; } }; void LLSpatialPartition::buildOcclusion() { if (mOccludedList.empty()) { return; } BOOL reset_all = FALSE; if (mOcclusionIndices.isNull()) { mOcclusionIndices = new LLOcclusionIndexBuffer(36); reset_all = TRUE; } //fill occlusion vertex buffers for (U32 i = 0; i < mOccludedList.size(); i++) { LLSpatialGroup* group = mOccludedList[i]; if (group->isState(LLSpatialGroup::OCCLUSION_DIRTY) || reset_all) { LLFastTimer ftm(LLFastTimer::FTM_REBUILD_OCCLUSION_VB); if (group->mOcclusionVerts.isNull()) { group->mOcclusionVerts = new LLOcclusionVertexBuffer(GL_STREAM_DRAW_ARB); } group->clearState(LLSpatialGroup::OCCLUSION_DIRTY); LLStrider vert; group->mOcclusionVerts->getVertexStrider(vert); LLVector3 r = group->mBounds[1]*SG_OCCLUSION_FUDGE + LLVector3(0.1f,0.1f,0.1f); for (U32 k = 0; k < 3; k++) { r.mV[k] = llmin(group->mBounds[1].mV[k]+0.25f, r.mV[k]); } *vert++ = group->mBounds[0] + r.scaledVec(LLVector3(-1,1,1)); // 0 - left top front *vert++ = group->mBounds[0] + r.scaledVec(LLVector3(1,1,1)); // 1 - right top front *vert++ = group->mBounds[0] + r.scaledVec(LLVector3(1,-1,1)); // 2 - right bottom front *vert++ = group->mBounds[0] + r.scaledVec(LLVector3(-1,-1,1)); // 3 - left bottom front *vert++ = group->mBounds[0] + r.scaledVec(LLVector3(1,1,-1)); // 4 - left top back *vert++ = group->mBounds[0] + r.scaledVec(LLVector3(-1,1,-1)); // 5 - right top back *vert++ = group->mBounds[0] + r.scaledVec(LLVector3(-1,-1,-1)); // 6 - right bottom back *vert++ = group->mBounds[0] + r.scaledVec(LLVector3(1,-1,-1)); // 7 -left bottom back } } /* for (U32 i = 0; i < mOccludedList.size(); i++) { LLSpatialGroup* group = mOccludedList[i]; if (!group->mOcclusionVerts.isNull() && group->mOcclusionVerts->isLocked()) { LLFastTimer ftm(LLFastTimer::FTM_REBUILD_OCCLUSION_VB); group->mOcclusionVerts->setBuffer(0); } }*/ } void LLSpatialPartition::doOcclusion(LLCamera* camera) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); LLFastTimer t(LLFastTimer::FTM_RENDER_OCCLUSION); #if LL_OCTREE_PARANOIA_CHECK LLSpatialGroup* check = (LLSpatialGroup*) mOctree->getListener(0); check->validate(); #endif stop_glerror(); U32 num_verts = mOccludedList.size() * 8; if (num_verts == 0) { return; } //actually perform the occlusion queries LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); LLGLDisable(GL_TEXTURE_2D); gPipeline.disableLights(); LLGLEnable cull_face(GL_CULL_FACE); LLGLDisable blend(GL_BLEND); LLGLDisable alpha_test(GL_ALPHA_TEST); LLGLDisable fog(GL_FOG); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glColor4f(1,1,1,1); mOcclusionIndices->setBuffer(0); U32* indicesp = (U32*) mOcclusionIndices->getIndicesPointer(); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_COLOR_ARRAY); #if !LL_RELEASE_FOR_DOWNLOAD LLGLState::checkClientArrays(LLVertexBuffer::MAP_VERTEX); #endif for (U32 i = 0; i < mOccludedList.size(); i++) { #if LL_OCTREE_PARANOIA_CHECK for (U32 j = i+1; j < mOccludedList.size(); j++) { sg_assert(mOccludedList[i] != mOccludedList[j]); } #endif LLSpatialGroup* group = mOccludedList[i]; if (group->isDead()) { continue; } if (earlyFail(camera, group)) { group->setState(LLSpatialGroup::EARLY_FAIL); } else { //early rejection criteria passed, send some geometry to the query group->mOcclusionVerts->setBuffer(LLVertexBuffer::MAP_VERTEX); glBeginQueryARB(GL_SAMPLES_PASSED_ARB, mOcclusionQueries[i]); glDrawRangeElements(GL_TRIANGLES, 0, 7, 36, GL_UNSIGNED_INT, indicesp); glEndQueryARB(GL_SAMPLES_PASSED_ARB); group->setState(LLSpatialGroup::QUERY_OUT); group->clearState(LLSpatialGroup::DISCARD_QUERY); } } stop_glerror(); gPipeline.mTrianglesDrawn += mOccludedList.size()*12; glFlush(); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); } class LLOctreeGet : public LLSpatialGroup::OctreeTraveler { public: LLOctreeGet(LLVector3 pos, F32 rad, LLDrawable::drawable_set_t* results, BOOL lights) : mPosition(pos), mRad(rad), mResults(results), mLights(lights), mRes(0) { } virtual void traverse(const LLSpatialGroup::TreeNode* n) { LLSpatialGroup* group = (LLSpatialGroup*) n->getListener(0); if (mRes == 2) { //fully in, just add everything LLSpatialGroup::OctreeTraveler::traverse(n); } else { LLVector3 center, size; center = group->mBounds[0]; size = group->mBounds[1]; mRes = LLSphereAABB(center, size, mPosition, mRad); if (mRes > 0) { LLSpatialGroup::OctreeTraveler::traverse(n); } mRes = 0; } } static BOOL skip(LLDrawable* drawable, BOOL get_lights) { if (get_lights != drawable->isLight()) { return TRUE; } if (get_lights && drawable->getVObj()->isHUDAttachment()) { return TRUE; // no lighting from HUD objects } if (get_lights && drawable->isState(LLDrawable::ACTIVE)) { return TRUE; // ignore active lights } return FALSE; } virtual void visit(const LLSpatialGroup::OctreeState* branch) { for (LLSpatialGroup::OctreeState::const_element_iter i = branch->getData().begin(); i != branch->getData().end(); ++i) { LLDrawable* drawable = *i; if (!skip(drawable, mLights)) { if (mRes == 2) { mResults->insert(drawable); } else { LLVector3 v = LLVector3(drawable->getPositionGroup())-mPosition; float dsq = v.magVecSquared(); float maxd = mRad + drawable->getVisibilityRadius(); if (dsq <= maxd*maxd) { mResults->insert(drawable); } } } } } LLVector3 mPosition; F32 mRad; LLDrawable::drawable_set_t* mResults; BOOL mLights; U32 mRes; }; S32 LLSpatialPartition::getDrawables(const LLVector3& pos, F32 rad, LLDrawable::drawable_set_t &results, BOOL get_lights) { LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); LLOctreeGet getter(pos, rad, &results, get_lights); getter.traverse(mOctree); return results.size(); } S32 LLSpatialPartition::getObjects(const LLVector3& pos, F32 rad, LLDrawable::drawable_set_t &results) { LLSpatialGroup* group = (LLSpatialGroup*) mOctree->getListener(0); group->rebound(); return getDrawables(pos, rad, results, FALSE); } S32 LLSpatialPartition::getLights(const LLVector3& pos, F32 rad, LLDrawable::drawable_set_t &results) { return getDrawables(pos, rad, results, TRUE); } void pushVerts(LLDrawInfo* params, U32 mask) { params->mVertexBuffer->setBuffer(mask); U32* indicesp = (U32*) params->mVertexBuffer->getIndicesPointer(); glDrawRangeElements(params->mParticle ? GL_POINTS : GL_TRIANGLES, params->mStart, params->mEnd, params->mCount, GL_UNSIGNED_INT, indicesp+params->mOffset); } void pushVerts(LLSpatialGroup* group, U32 mask) { LLDrawInfo* params = NULL; for (LLSpatialGroup::draw_map_t::iterator i = group->mDrawMap.begin(); i != group->mDrawMap.end(); ++i) { for (LLSpatialGroup::drawmap_elem_t::iterator j = i->second.begin(); j != i->second.end(); ++j) { params = *j; pushVerts(params, mask); } } } void pushVertsColorCoded(LLSpatialGroup* group, U32 mask) { LLDrawInfo* params = NULL; LLColor4 colors[] = { LLColor4::green, LLColor4::green1, LLColor4::green2, LLColor4::green3, LLColor4::green4, LLColor4::green5, LLColor4::green6 }; static const U32 col_count = sizeof(colors)/sizeof(LLColor4); U32 col = 0; for (LLSpatialGroup::draw_map_t::iterator i = group->mDrawMap.begin(); i != group->mDrawMap.end(); ++i) { for (LLSpatialGroup::drawmap_elem_t::iterator j = i->second.begin(); j != i->second.end(); ++j) { params = *j; glColor4f(colors[col].mV[0], colors[col].mV[1], colors[col].mV[2], 0.5f); params->mVertexBuffer->setBuffer(mask); U32* indicesp = (U32*) params->mVertexBuffer->getIndicesPointer(); glDrawRangeElements(params->mParticle ? GL_POINTS : GL_TRIANGLES, params->mStart, params->mEnd, params->mCount, GL_UNSIGNED_INT, indicesp+params->mOffset); col = (col+1)%col_count; } } } void renderOctree(LLSpatialGroup* group) { //render solid object bounding box, color //coded by buffer usage and activity LLGLDepthTest depth(GL_TRUE, GL_FALSE); glBlendFunc(GL_SRC_ALPHA, GL_ONE); LLVector4 col; if (group->mBuilt > 0.f) { group->mBuilt -= 2.f * gFrameIntervalSeconds; if (group->mBufferUsage == GL_STATIC_DRAW_ARB) { col.setVec(1.0f, 0, 0, group->mBuilt*0.5f); } else { col.setVec(0.1f,0.1f,1,0.1f); //col.setVec(1.0f, 1.0f, 0, sinf(group->mBuilt*3.14159f)*0.5f); } if (group->mBufferUsage != GL_STATIC_DRAW_ARB) { if (group->mBufferUsage == GL_DYNAMIC_DRAW_ARB) { glColor4f(1,0,0,group->mBuilt); } else { glColor4f(1,1,0,group->mBuilt); } LLGLDepthTest gl_depth(FALSE, FALSE); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); for (LLSpatialGroup::element_iter i = group->getData().begin(); i != group->getData().end(); ++i) { LLDrawable* drawable = *i; for (S32 j = 0; j < drawable->getNumFaces(); j++) { LLFace* face = drawable->getFace(j); if (gFrameTimeSeconds - face->mLastUpdateTime < 0.5f && face->mVertexBuffer.notNull()) { face->mVertexBuffer->setBuffer(LLVertexBuffer::MAP_VERTEX); //drawBox((face->mExtents[0] + face->mExtents[1])*0.5f, // (face->mExtents[1]-face->mExtents[0])*0.5f); glDrawElements(GL_TRIANGLES, face->getIndicesCount(), GL_UNSIGNED_INT, ((U32*) face->mVertexBuffer->getIndicesPointer())+face->getIndicesStart()); } } } glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } else { if (group->mBufferUsage == GL_STATIC_DRAW_ARB && !group->getData().empty() && group->mSpatialPartition->mRenderByGroup) { col.setVec(0.8f, 0.4f, 0.1f, 0.1f); } else { col.setVec(0.1f, 0.1f, 1.f, 0.1f); } } glColor4fv(col.mV); drawBox(group->mObjectBounds[0], group->mObjectBounds[1]*1.01f+LLVector3(0.001f, 0.001f, 0.001f)); glDepthMask(GL_TRUE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //draw opaque outline glColor4f(col.mV[0], col.mV[1], col.mV[2], 1.f); drawBoxOutline(group->mObjectBounds[0], group->mObjectBounds[1]); if (group->mOctreeNode->hasLeafState()) { glColor4f(1,1,1,1); } else { glColor4f(0,1,1,1); } drawBoxOutline(group->mBounds[0],group->mBounds[1]); // LLSpatialGroup::OctreeNode* node = group->mOctreeNode; // glColor4f(0,1,0,1); // drawBoxOutline(LLVector3(node->getCenter()), LLVector3(node->getSize())); } void renderVisibility(LLSpatialGroup* group) { LLGLEnable blend(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); LLGLEnable cull(GL_CULL_FACE); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); { LLGLDepthTest depth_under(GL_TRUE, GL_FALSE, GL_GREATER); glColor4f(0, 0.5f, 0, 0.5f); pushVerts(group, LLVertexBuffer::MAP_VERTEX); } { LLGLDepthTest depth_over(GL_TRUE, GL_FALSE, GL_LEQUAL); pushVertsColorCoded(group, LLVertexBuffer::MAP_VERTEX); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); pushVertsColorCoded(group, LLVertexBuffer::MAP_VERTEX); } } void renderBoundingBox(LLDrawable* drawable) { if (drawable->isSpatialBridge()) { glColor4f(1,0.5f,0,1); } else if (drawable->getVOVolume()) { if (drawable->isRoot()) { glColor4f(1,1,0,1); } else { glColor4f(0,1,0,1); } } else if (drawable->getVObj()) { switch (drawable->getVObj()->getPCode()) { case LLViewerObject::LL_VO_SURFACE_PATCH: glColor4f(0,1,1,1); break; case LLViewerObject::LL_VO_CLOUDS: glColor4f(0.5f,0.5f,0.5f,1.0f); break; case LLViewerObject::LL_VO_PART_GROUP: glColor4f(0,0,1,1); break; case LLViewerObject::LL_VO_WATER: glColor4f(0,0.5f,1,1); break; case LL_PCODE_LEGACY_TREE: glColor4f(0,0.5f,0,1); default: glColor4f(1,0,1,1); break; } } else { glColor4f(1,0,0,1); } const LLVector3* ext; LLVector3 pos, size; //render face bounding boxes for (S32 i = 0; i < drawable->getNumFaces(); i++) { LLFace* facep = drawable->getFace(i); ext = facep->mExtents; if (ext[0].isExactlyZero() && ext[1].isExactlyZero()) { continue; } pos = (ext[0] + ext[1]) * 0.5f; size = (ext[1] - ext[0]) * 0.5f; drawBoxOutline(pos,size); } //render drawable bounding box ext = drawable->getSpatialExtents(); pos = (ext[0] + ext[1]) * 0.5f; size = (ext[1] - ext[0]) * 0.5f; drawBoxOutline(pos,size); } void renderTexturePriority(LLDrawable* drawable) { for (int face=0; facegetNumFaces(); ++face) { LLFace *facep = drawable->getFace(face); LLVector4 cold(0,0,0.25f); LLVector4 hot(1,0.25f,0.25f); LLVector4 boost_cold(0,0,0,0); LLVector4 boost_hot(0,1,0,1); LLGLDisable blend(GL_BLEND); //LLViewerImage* imagep = facep->getTexture(); //if (imagep) { //F32 vsize = LLVOVolume::getTextureVirtualSize(facep); //F32 vsize = imagep->mMaxVirtualSize; F32 vsize = facep->getPixelArea(); if (vsize > sCurMaxTexPriority) { sCurMaxTexPriority = vsize; } F32 t = vsize/sLastMaxTexPriority; LLVector4 col = lerp(cold, hot, t); glColor4fv(col.mV); } //else //{ // glColor4f(1,0,1,1); //} LLVector3 center = (facep->mExtents[1]+facep->mExtents[0])*0.5f; LLVector3 size = (facep->mExtents[1]-facep->mExtents[0])*0.5f + LLVector3(0.01f, 0.01f, 0.01f); drawBox(center, size); /*S32 boost = imagep->getBoostLevel(); if (boost) { F32 t = (F32) boost / (F32) (LLViewerImage::BOOST_MAX_LEVEL-1); LLVector4 col = lerp(boost_cold, boost_hot, t); LLGLEnable blend_on(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); glColor4fv(col.mV); drawBox(center, size); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); }*/ } } void renderPoints(LLDrawable* drawablep) { LLGLDepthTest depth(GL_FALSE, GL_FALSE); glBegin(GL_POINTS); glColor3f(1,1,1); LLVector3 center(drawablep->getPositionGroup()); for (S32 i = 0; i < drawablep->getNumFaces(); i++) { glVertex3fv(drawablep->getFace(i)->mCenterLocal.mV); } glEnd(); } void renderTextureAnim(LLDrawInfo* params) { if (!params->mTextureMatrix) { return; } LLGLEnable blend(GL_BLEND); glColor4f(1,1,0,0.5f); pushVerts(params, LLVertexBuffer::MAP_VERTEX); } class LLOctreeRenderNonOccluded : public LLOctreeTraveler { public: LLOctreeRenderNonOccluded() {} virtual void traverse(const LLSpatialGroup::OctreeNode* node) { const LLSpatialGroup::OctreeState* state = node->getOctState(); LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); if ((!gPipeline.sUseOcclusion || !group->isState(LLSpatialGroup::OCCLUDED)) && !group->isState(LLSpatialGroup::CULLED)) { state->accept(this); for (U32 i = 0; i < state->getChildCount(); i++) { traverse(state->getChild(i)); } //draw tight fit bounding boxes for spatial group if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCTREE)) { renderOctree(group); } //render visibility wireframe if (group->mSpatialPartition->mRenderByGroup && gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCCLUSION) && !group->isState(LLSpatialGroup::GEOM_DIRTY)) { renderVisibility(group); } } } virtual void visit(const LLSpatialGroup::OctreeState* branch) { LLSpatialGroup* group = (LLSpatialGroup*) branch->getListener(0); if (group->isState(LLSpatialGroup::CULLED | LLSpatialGroup::OCCLUDED)) { return; } LLVector3 nodeCenter = group->mBounds[0]; LLVector3 octCenter = LLVector3(group->mOctreeNode->getCenter()); for (LLSpatialGroup::OctreeState::const_element_iter i = branch->getData().begin(); i != branch->getData().end(); ++i) { LLDrawable* drawable = *i; if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_BBOXES)) { renderBoundingBox(drawable); } if (drawable->getVOVolume() && gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_PRIORITY)) { renderTexturePriority(drawable); } if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_POINTS)) { renderPoints(drawable); } } for (LLSpatialGroup::draw_map_t::iterator i = group->mDrawMap.begin(); i != group->mDrawMap.end(); ++i) { LLSpatialGroup::drawmap_elem_t& draw_vec = i->second; for (LLSpatialGroup::drawmap_elem_t::iterator j = draw_vec.begin(); j != draw_vec.end(); ++j) { LLDrawInfo* draw_info = *j; if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_ANIM)) { renderTextureAnim(draw_info); } } } } }; void LLSpatialPartition::renderDebug() { if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCTREE | LLPipeline::RENDER_DEBUG_OCCLUSION | LLPipeline::RENDER_DEBUG_BBOXES | LLPipeline::RENDER_DEBUG_POINTS | LLPipeline::RENDER_DEBUG_TEXTURE_PRIORITY | LLPipeline::RENDER_DEBUG_TEXTURE_ANIM)) { return; } if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_PRIORITY)) { //sLastMaxTexPriority = lerp(sLastMaxTexPriority, sCurMaxTexPriority, gFrameIntervalSeconds); sLastMaxTexPriority = (F32) gCamera->getScreenPixelArea(); sCurMaxTexPriority = 0.f; } LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION); LLGLDisable cullface(GL_CULL_FACE); LLGLEnable blend(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); LLGLDisable tex(GL_TEXTURE_2D); gPipeline.disableLights(); LLOctreeRenderNonOccluded render_debug; render_debug.traverse(mOctree); LLGLDisable cull_face(GL_CULL_FACE); if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCCLUSION) && !mOccludedList.empty() && mOcclusionIndices.notNull()) { LLGLDisable fog(GL_FOG); LLGLDepthTest gls_depth(GL_FALSE); glBlendFunc(GL_SRC_ALPHA, GL_ONE); mOcclusionIndices->setBuffer(0); U32* indicesp = (U32*) mOcclusionIndices->getIndicesPointer(); LLGLEnable blend(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); LLGLEnable cull(GL_CULL_FACE); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); for (U32 i = 0; i < mOccludedList.size(); i++) { //draw occluded nodes LLSpatialGroup* node = mOccludedList[i]; if (node->isDead() || !node->isState(LLSpatialGroup::OCCLUDED) || node->mOcclusionVerts.isNull()) { continue; } node->mOcclusionVerts->setBuffer(LLVertexBuffer::MAP_VERTEX); { LLGLDepthTest depth_under(GL_TRUE, GL_FALSE, GL_GREATER); glColor4f(0.5, 0.5f, 0, 0.25f); glDrawRangeElements(GL_TRIANGLES, 0, 7, 36, GL_UNSIGNED_INT, indicesp); } { LLGLDepthTest depth_over(GL_TRUE, GL_FALSE, GL_LEQUAL); glColor4f(0.0,1.0f,1.0f,1.0f); glDrawRangeElements(GL_TRIANGLES, 0, 7, 36, GL_UNSIGNED_INT, indicesp); } } glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } BOOL LLSpatialPartition::isVisible(const LLVector3& v) { if (!gCamera->sphereInFrustum(v, 4.0f)) { return FALSE; } return TRUE; } class LLOctreePick : public LLSpatialGroup::OctreeTraveler { public: LLVector3 mStart; LLVector3 mEnd; LLDrawable* mRet; LLOctreePick(LLVector3 start, LLVector3 end) : mStart(start), mEnd(end) { mRet = NULL; } virtual LLDrawable* check(const LLSpatialGroup::OctreeNode* node) { const LLSpatialGroup::OctreeState* state = node->getOctState(); state->accept(this); for (U32 i = 0; i < node->getChildCount(); i++) { const LLSpatialGroup::OctreeNode* child = node->getChild(i); LLVector3 res; LLSpatialGroup* group = (LLSpatialGroup*) child->getListener(0); LLVector3 size; LLVector3 center; size = group->mBounds[1]; center = group->mBounds[0]; if (LLLineSegmentAABB(mStart, mEnd, center, size)) { check(child); } } return mRet; } virtual void visit(const LLSpatialGroup::OctreeState* branch) { for (LLSpatialGroup::OctreeState::const_element_iter i = branch->getData().begin(); i != branch->getData().end(); ++i) { check(*i); } } virtual bool check(LLDrawable* drawable) { LLViewerObject* vobj = drawable->getVObj(); if (vobj->lineSegmentIntersect(mStart, mEnd)) { mRet = vobj->mDrawable; } return false; } }; LLDrawable* LLSpatialPartition::pickDrawable(const LLVector3& start, const LLVector3& end, LLVector3& collision) { LLOctreePick pick(start, end); LLDrawable* ret = pick.check(mOctree); collision.setVec(pick.mEnd); return ret; } LLDrawInfo::LLDrawInfo(U32 start, U32 end, U32 count, U32 offset, LLViewerImage* texture, LLVertexBuffer* buffer, BOOL fullbright, U8 bump, BOOL particle, F32 part_size) : mVertexBuffer(buffer), mTexture(texture), mTextureMatrix(NULL), mStart(start), mEnd(end), mCount(count), mOffset(offset), mFullbright(fullbright), mBump(bump), mParticle(particle), mPartSize(part_size), mVSize(0.f) { } LLDrawInfo::~LLDrawInfo() { } LLVertexBuffer* LLGeometryManager::createVertexBuffer(U32 type_mask, U32 usage) { return new LLVertexBuffer(type_mask, usage); }