#include "objectpaging.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "apps/openmw/mwbase/environment.hpp" #include "apps/openmw/mwbase/world.hpp" #include "apps/openmw/mwworld/esmstore.hpp" #include "vismask.hpp" #include namespace MWRender { bool typeFilter(int type, bool far) { switch (type) { case ESM::REC_STAT: case ESM::REC_ACTI: case ESM::REC_DOOR: return true; case ESM::REC_CONT: return !far; default: return false; } } std::string getModel(int type, const std::string& id, const MWWorld::ESMStore& store) { switch (type) { case ESM::REC_STAT: return store.get().searchStatic(id)->mModel; case ESM::REC_ACTI: return store.get().searchStatic(id)->mModel; case ESM::REC_DOOR: return store.get().searchStatic(id)->mModel; case ESM::REC_CONT: return store.get().searchStatic(id)->mModel; default: return {}; } } osg::ref_ptr ObjectPaging::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { lod = static_cast(lodFlags >> (4 * 4)); if (activeGrid && !mActiveGrid) return nullptr; ChunkId id = std::make_tuple(center, size, activeGrid); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) return static_cast(obj.get()); else { osg::ref_ptr node = createChunk(size, center, activeGrid, viewPoint, compile, lod); mCache->addEntryToObjectCache(id, node.get()); return node; } } class CanOptimizeCallback : public SceneUtil::Optimizer::IsOperationPermissibleForObjectCallback { public: bool isOperationPermissibleForObjectImplementation( const SceneUtil::Optimizer* optimizer, const osg::Drawable* node, unsigned int option) const override { return true; } bool isOperationPermissibleForObjectImplementation( const SceneUtil::Optimizer* optimizer, const osg::Node* node, unsigned int option) const override { return (node->getDataVariance() != osg::Object::DYNAMIC); } }; class CopyOp : public osg::CopyOp { public: bool mOptimizeBillboards = true; float mSqrDistance = 0.f; osg::Vec3f mViewVector; osg::Node::NodeMask mCopyMask = ~0u; mutable std::vector mNodePath; void copy(const osg::Node* toCopy, osg::Group* attachTo) { const osg::Group* groupToCopy = toCopy->asGroup(); if (toCopy->getStateSet() || toCopy->asTransform() || !groupToCopy) attachTo->addChild(operator()(toCopy)); else { for (unsigned int i = 0; i < groupToCopy->getNumChildren(); ++i) attachTo->addChild(operator()(groupToCopy->getChild(i))); } } osg::Node* operator()(const osg::Node* node) const override { if (!(node->getNodeMask() & mCopyMask)) return nullptr; if (const osg::Drawable* d = node->asDrawable()) return operator()(d); if (dynamic_cast(node)) return nullptr; if (dynamic_cast(node)) return nullptr; if (const osg::Switch* sw = node->asSwitch()) { osg::Group* n = new osg::Group; for (unsigned int i = 0; i < sw->getNumChildren(); ++i) if (sw->getValue(i)) n->addChild(operator()(sw->getChild(i))); n->setDataVariance(osg::Object::STATIC); return n; } if (const osg::LOD* lod = dynamic_cast(node)) { osg::Group* n = new osg::Group; for (unsigned int i = 0; i < lod->getNumChildren(); ++i) if (lod->getMinRange(i) * lod->getMinRange(i) <= mSqrDistance && mSqrDistance < lod->getMaxRange(i) * lod->getMaxRange(i)) n->addChild(operator()(lod->getChild(i))); n->setDataVariance(osg::Object::STATIC); return n; } if (const osg::Sequence* sq = dynamic_cast(node)) { osg::Group* n = new osg::Group; n->addChild(operator()(sq->getChild(sq->getValue() != -1 ? sq->getValue() : 0))); n->setDataVariance(osg::Object::STATIC); return n; } mNodePath.push_back(node); osg::Node* cloned = static_cast(node->clone(*this)); cloned->setDataVariance(osg::Object::STATIC); cloned->setUserDataContainer(nullptr); cloned->setName(""); mNodePath.pop_back(); handleCallbacks(node, cloned); return cloned; } void handleCallbacks(const osg::Node* node, osg::Node* cloned) const { for (const osg::Callback* callback = node->getCullCallback(); callback != nullptr; callback = callback->getNestedCallback()) { if (callback->className() == std::string("BillboardCallback")) { if (mOptimizeBillboards) { handleBillboard(cloned); continue; } else cloned->setDataVariance(osg::Object::DYNAMIC); } if (node->getCullCallback()->getNestedCallback()) { osg::Callback* clonedCallback = osg::clone(callback, osg::CopyOp::SHALLOW_COPY); clonedCallback->setNestedCallback(nullptr); cloned->addCullCallback(clonedCallback); } else cloned->addCullCallback(const_cast(callback)); } } void handleBillboard(osg::Node* node) const { osg::Transform* transform = node->asTransform(); if (!transform) return; osg::MatrixTransform* matrixTransform = transform->asMatrixTransform(); if (!matrixTransform) return; osg::Matrix worldToLocal = osg::Matrix::identity(); for (auto pathNode : mNodePath) if (const osg::Transform* t = pathNode->asTransform()) t->computeWorldToLocalMatrix(worldToLocal, nullptr); worldToLocal = osg::Matrix::orthoNormal(worldToLocal); osg::Matrix billboardMatrix; osg::Vec3f viewVector = -(mViewVector + worldToLocal.getTrans()); viewVector.normalize(); osg::Vec3f right = viewVector ^ osg::Vec3f(0, 0, 1); right.normalize(); osg::Vec3f up = right ^ viewVector; up.normalize(); billboardMatrix.makeLookAt(osg::Vec3f(0, 0, 0), viewVector, up); billboardMatrix.invert(billboardMatrix); const osg::Matrix& oldMatrix = matrixTransform->getMatrix(); float mag[3]; // attempt to preserve scale for (int i = 0; i < 3; ++i) mag[i] = std::sqrt(oldMatrix(0, i) * oldMatrix(0, i) + oldMatrix(1, i) * oldMatrix(1, i) + oldMatrix(2, i) * oldMatrix(2, i)); osg::Matrix newMatrix; worldToLocal.setTrans(0, 0, 0); newMatrix *= worldToLocal; newMatrix.preMult(billboardMatrix); newMatrix.preMultScale(osg::Vec3f(mag[0], mag[1], mag[2])); newMatrix.setTrans(oldMatrix.getTrans()); matrixTransform->setMatrix(newMatrix); } osg::Drawable* operator()(const osg::Drawable* drawable) const override { if (!(drawable->getNodeMask() & mCopyMask)) return nullptr; if (dynamic_cast(drawable)) return nullptr; if (dynamic_cast(drawable)) return nullptr; if (const SceneUtil::RigGeometry* rig = dynamic_cast(drawable)) return operator()(rig->getSourceGeometry()); if (const SceneUtil::MorphGeometry* morph = dynamic_cast(drawable)) return operator()(morph->getSourceGeometry()); if (getCopyFlags() & DEEP_COPY_DRAWABLES) { osg::Drawable* d = static_cast(drawable->clone(*this)); d->setDataVariance(osg::Object::STATIC); d->setUserDataContainer(nullptr); d->setName(""); return d; } else return const_cast(drawable); } osg::Callback* operator()(const osg::Callback* callback) const override { return nullptr; } }; class RefnumSet : public osg::Object { public: RefnumSet() {} RefnumSet(const RefnumSet& copy, const osg::CopyOp&) : mRefnums(copy.mRefnums) { } META_Object(MWRender, RefnumSet) std::vector mRefnums; }; class AnalyzeVisitor : public osg::NodeVisitor { public: AnalyzeVisitor(osg::Node::NodeMask analyzeMask) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mCurrentStateSet(nullptr) , mCurrentDistance(0.f) { setTraversalMask(analyzeMask); } typedef std::unordered_map StateSetCounter; struct Result { StateSetCounter mStateSetCounter; unsigned int mNumVerts = 0; }; void apply(osg::Node& node) override { if (node.getStateSet()) mCurrentStateSet = node.getStateSet(); if (osg::Switch* sw = node.asSwitch()) { for (unsigned int i = 0; i < sw->getNumChildren(); ++i) if (sw->getValue(i)) traverse(*sw->getChild(i)); return; } if (osg::LOD* lod = dynamic_cast(&node)) { for (unsigned int i = 0; i < lod->getNumChildren(); ++i) if (lod->getMinRange(i) * lod->getMinRange(i) <= mCurrentDistance && mCurrentDistance < lod->getMaxRange(i) * lod->getMaxRange(i)) traverse(*lod->getChild(i)); return; } if (osg::Sequence* sq = dynamic_cast(&node)) { traverse(*sq->getChild(sq->getValue() != -1 ? sq->getValue() : 0)); return; } traverse(node); } void apply(osg::Geometry& geom) override { if (osg::Array* array = geom.getVertexArray()) mResult.mNumVerts += array->getNumElements(); ++mResult.mStateSetCounter[mCurrentStateSet]; ++mGlobalStateSetCounter[mCurrentStateSet]; } Result retrieveResult() { Result result = mResult; mResult = Result(); mCurrentStateSet = nullptr; return result; } void addInstance(const Result& result) { for (auto pair : result.mStateSetCounter) mGlobalStateSetCounter[pair.first] += pair.second; } float getMergeBenefit(const Result& result) { if (result.mStateSetCounter.empty()) return 1; float mergeBenefit = 0; for (auto pair : result.mStateSetCounter) { mergeBenefit += mGlobalStateSetCounter[pair.first]; } mergeBenefit /= result.mStateSetCounter.size(); return mergeBenefit; } Result mResult; osg::StateSet* mCurrentStateSet; StateSetCounter mGlobalStateSetCounter; float mCurrentDistance; }; class DebugVisitor : public osg::NodeVisitor { public: DebugVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { } void apply(osg::Drawable& node) override { osg::ref_ptr m(new osg::Material); osg::Vec4f color( Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), 0.f); color.normalize(); m->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f, 0.1f, 0.1f, 1.f)); m->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f, 0.1f, 0.1f, 1.f)); m->setColorMode(osg::Material::OFF); m->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(color)); osg::ref_ptr stateset = node.getStateSet() ? osg::clone(node.getStateSet(), osg::CopyOp::SHALLOW_COPY) : new osg::StateSet; stateset->setAttribute(m); stateset->addUniform(new osg::Uniform("colorMode", 0)); stateset->addUniform(new osg::Uniform("emissiveMult", 1.f)); stateset->addUniform(new osg::Uniform("specStrength", 1.f)); node.setStateSet(stateset); } }; class AddRefnumMarkerVisitor : public osg::NodeVisitor { public: AddRefnumMarkerVisitor(const ESM::RefNum& refnum) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mRefnum(refnum) { } ESM::RefNum mRefnum; void apply(osg::Geometry& node) override { osg::ref_ptr marker(new RefnumMarker); marker->mRefnum = mRefnum; if (osg::Array* array = node.getVertexArray()) marker->mNumVertices = array->getNumElements(); node.getOrCreateUserDataContainer()->addUserObject(marker); } }; ObjectPaging::ObjectPaging(Resource::SceneManager* sceneManager) : GenericResourceManager(nullptr) , mSceneManager(sceneManager) , mRefTrackerLocked(false) { mActiveGrid = Settings::Manager::getBool("object paging active grid", "Terrain"); mDebugBatches = Settings::Manager::getBool("debug chunks", "Terrain"); mMergeFactor = Settings::Manager::getFloat("object paging merge factor", "Terrain"); mMinSize = Settings::Manager::getFloat("object paging min size", "Terrain"); mMinSizeMergeFactor = Settings::Manager::getFloat("object paging min size merge factor", "Terrain"); mMinSizeCostMultiplier = Settings::Manager::getFloat("object paging min size cost multiplier", "Terrain"); } osg::ref_ptr ObjectPaging::createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile, unsigned char lod) { osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size / 2.f), std::floor(center.y() - size / 2.f)); osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0) * ESM::Land::REAL_SIZE; osg::Vec3f relativeViewPoint = viewPoint - worldCenter; std::map refs; ESM::ReadersCache readers; const auto& world = MWBase::Environment::get().getWorld(); const auto& store = world->getStore(); for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) { const ESM::Cell* cell = store.get().searchStatic(cellX, cellY); if (!cell) continue; for (size_t i = 0; i < cell->mContextList.size(); ++i) { try { const std::size_t index = static_cast(cell->mContextList[i].index); const ESM::ReadersCache::BusyItem reader = readers.get(index); cell->restore(*reader, i); ESM::CellRef ref; ref.mRefNum.unset(); ESM::MovedCellRef cMRef; cMRef.mRefNum.mIndex = 0; bool deleted = false; bool moved = false; while (ESM::Cell::getNextRef( *reader, ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved)) { if (moved) continue; if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue; Misc::StringUtils::lowerCaseInPlace(ref.mRefID); int type = store.findStatic(ref.mRefID); if (!typeFilter(type, size >= 2)) continue; if (deleted) { refs.erase(ref.mRefNum); continue; } refs[ref.mRefNum] = std::move(ref); } } catch (std::exception&) { continue; } } for (auto [ref, deleted] : cell->mLeasedRefs) { if (deleted) { refs.erase(ref.mRefNum); continue; } Misc::StringUtils::lowerCaseInPlace(ref.mRefID); int type = store.findStatic(ref.mRefID); if (!typeFilter(type, size >= 2)) continue; refs[ref.mRefNum] = std::move(ref); } } } if (activeGrid) { std::lock_guard lock(mRefTrackerMutex); for (auto ref : getRefTracker().mBlacklist) refs.erase(ref); } osg::Vec2f minBound = (center - osg::Vec2f(size / 2.f, size / 2.f)); osg::Vec2f maxBound = (center + osg::Vec2f(size / 2.f, size / 2.f)); struct InstanceList { std::vector mInstances; AnalyzeVisitor::Result mAnalyzeResult; bool mNeedCompile = false; }; typedef std::map, InstanceList> NodeMap; NodeMap nodes; osg::ref_ptr refnumSet = activeGrid ? new RefnumSet : nullptr; // Mask_UpdateVisitor is used in such cases in NIF loader: // 1. For collision nodes, which is not supposed to be rendered. // 2. For nodes masked via Flag_Hidden (VisController can change this flag value at runtime). // Since ObjectPaging does not handle VisController, we can just ignore both types of nodes. constexpr auto copyMask = ~Mask_UpdateVisitor; AnalyzeVisitor analyzeVisitor(copyMask); analyzeVisitor.mCurrentDistance = (viewPoint - worldCenter).length2(); float minSize = mMinSize; if (mMinSizeMergeFactor) minSize *= mMinSizeMergeFactor; for (const auto& pair : refs) { const ESM::CellRef& ref = pair.second; osg::Vec3f pos = ref.mPos.asVec3(); if (size < 1.f) { osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (maxBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) continue; } float dSqr = (viewPoint - pos).length2(); if (!activeGrid) { std::lock_guard lock(mSizeCacheMutex); SizeCache::iterator found = mSizeCache.find(pair.first); if (found != mSizeCache.end() && found->second < dSqr * minSize * minSize) continue; } if (Misc::ResourceHelpers::isHiddenMarker(ref.mRefID)) continue; int type = store.findStatic(ref.mRefID); std::string model = getModel(type, ref.mRefID, store); if (model.empty()) continue; model = Misc::ResourceHelpers::correctMeshPath(model, mSceneManager->getVFS()); if (activeGrid && type != ESM::REC_STAT) { model = Misc::ResourceHelpers::correctActorModelPath(model, mSceneManager->getVFS()); std::string kfname = Misc::StringUtils::lowerCase(model); if (kfname.size() > 4 && kfname.compare(kfname.size() - 4, 4, ".nif") == 0) { kfname.replace(kfname.size() - 4, 4, ".kf"); if (mSceneManager->getVFS()->exists(kfname)) continue; } } if (!activeGrid) { std::lock_guard lock(mLODNameCacheMutex); LODNameCacheKey key{ model, lod }; LODNameCache::const_iterator found = mLODNameCache.lower_bound(key); if (found != mLODNameCache.end() && found->first == key) model = found->second; else model = mLODNameCache .insert(found, { key, Misc::ResourceHelpers::getLODMeshName( world->getESMVersions()[ref.mRefNum.mContentFile], model, mSceneManager->getVFS(), lod) }) ->second; } osg::ref_ptr cnode = mSceneManager->getTemplate(model, false); if (activeGrid) { if (cnode->getNumChildrenRequiringUpdateTraversal() > 0 || SceneUtil::hasUserDescription(cnode, Constants::NightDayLabel) || SceneUtil::hasUserDescription(cnode, Constants::HerbalismLabel)) continue; else refnumSet->mRefnums.push_back(pair.first); } { std::lock_guard lock(mRefTrackerMutex); if (getRefTracker().mDisabled.count(pair.first)) continue; } float radius2 = cnode->getBound().radius2() * ref.mScale * ref.mScale; if (radius2 < dSqr * minSize * minSize && !activeGrid) { std::lock_guard lock(mSizeCacheMutex); mSizeCache[pair.first] = radius2; continue; } auto emplaced = nodes.emplace(cnode, InstanceList()); if (emplaced.second) { const_cast(cnode.get()) ->accept( analyzeVisitor); // const-trickery required because there is no const version of NodeVisitor emplaced.first->second.mAnalyzeResult = analyzeVisitor.retrieveResult(); emplaced.first->second.mNeedCompile = compile && cnode->referenceCount() <= 3; } else analyzeVisitor.addInstance(emplaced.first->second.mAnalyzeResult); emplaced.first->second.mInstances.push_back(&ref); } osg::ref_ptr group = new osg::Group; osg::ref_ptr mergeGroup = new osg::Group; osg::ref_ptr templateRefs = new Resource::TemplateMultiRef; osgUtil::StateToCompile stateToCompile(0, nullptr); CopyOp copyop; copyop.mCopyMask = copyMask; for (const auto& pair : nodes) { const osg::Node* cnode = pair.first; const AnalyzeVisitor::Result& analyzeResult = pair.second.mAnalyzeResult; float mergeCost = analyzeResult.mNumVerts * size; float mergeBenefit = analyzeVisitor.getMergeBenefit(analyzeResult) * mMergeFactor; bool merge = mergeBenefit > mergeCost; float minSizeMerged = mMinSize; float factor2 = mergeBenefit > 0 ? std::min(1.f, mergeCost * mMinSizeCostMultiplier / mergeBenefit) : 1; float minSizeMergeFactor2 = (1 - factor2) * mMinSizeMergeFactor + factor2; if (minSizeMergeFactor2 > 0) minSizeMerged *= minSizeMergeFactor2; unsigned int numinstances = 0; for (auto cref : pair.second.mInstances) { const ESM::CellRef& ref = *cref; osg::Vec3f pos = ref.mPos.asVec3(); if (!activeGrid && minSizeMerged != minSize && cnode->getBound().radius2() * cref->mScale * cref->mScale < (viewPoint - pos).length2() * minSizeMerged * minSizeMerged) continue; osg::Vec3f nodePos = pos - worldCenter; osg::Quat nodeAttitude = osg::Quat(ref.mPos.rot[2], osg::Vec3f(0, 0, -1)) * osg::Quat(ref.mPos.rot[1], osg::Vec3f(0, -1, 0)) * osg::Quat(ref.mPos.rot[0], osg::Vec3f(-1, 0, 0)); osg::Vec3f nodeScale = osg::Vec3f(ref.mScale, ref.mScale, ref.mScale); osg::ref_ptr trans; if (merge) { // Optimizer currently supports only MatrixTransforms. osg::Matrixf matrix; matrix.preMultTranslate(nodePos); matrix.preMultRotate(nodeAttitude); matrix.preMultScale(nodeScale); trans = new osg::MatrixTransform(matrix); trans->setDataVariance(osg::Object::STATIC); } else { trans = new SceneUtil::PositionAttitudeTransform; SceneUtil::PositionAttitudeTransform* pat = static_cast(trans.get()); pat->setPosition(nodePos); pat->setScale(nodeScale); pat->setAttitude(nodeAttitude); } // DO NOT COPY AND PASTE THIS CODE. Cloning osg::Geometry without also cloning its contained Arrays is // generally unsafe. In this specific case the operation is safe under the following two assumptions: // - When Arrays are removed or replaced in the cloned geometry, the original Arrays in their place must // outlive the cloned geometry regardless. (ensured by TemplateMultiRef) // - Arrays that we add or replace in the cloned geometry must be explicitely forbidden from reusing // BufferObjects of the original geometry. (ensured by needvbo() in optimizer.cpp) copyop.setCopyFlags(merge ? osg::CopyOp::DEEP_COPY_NODES | osg::CopyOp::DEEP_COPY_DRAWABLES : osg::CopyOp::DEEP_COPY_NODES); copyop.mOptimizeBillboards = (size > 1 / 4.f); copyop.mNodePath.push_back(trans); copyop.mSqrDistance = (viewPoint - pos).length2(); copyop.mViewVector = (viewPoint - worldCenter); copyop.copy(cnode, trans); copyop.mNodePath.pop_back(); if (activeGrid) { if (merge) { AddRefnumMarkerVisitor visitor(ref.mRefNum); trans->accept(visitor); } else { osg::ref_ptr marker = new RefnumMarker; marker->mRefnum = ref.mRefNum; trans->getOrCreateUserDataContainer()->addUserObject(marker); } } osg::Group* attachTo = merge ? mergeGroup : group; attachTo->addChild(trans); ++numinstances; } if (numinstances > 0) { // add a ref to the original template to help verify the safety of shallow cloning operations // in addition, we hint to the cache that it's still being used and should be kept in cache templateRefs->addRef(cnode); if (pair.second.mNeedCompile) { int mode = osgUtil::GLObjectsVisitor::COMPILE_STATE_ATTRIBUTES; if (!merge) mode |= osgUtil::GLObjectsVisitor::COMPILE_DISPLAY_LISTS; stateToCompile._mode = mode; const_cast(cnode)->accept(stateToCompile); } } } if (mergeGroup->getNumChildren()) { SceneUtil::Optimizer optimizer; if (size > 1 / 8.f) { optimizer.setViewPoint(relativeViewPoint); optimizer.setMergeAlphaBlending(true); } optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); unsigned int options = SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS | SceneUtil::Optimizer::REMOVE_REDUNDANT_NODES | SceneUtil::Optimizer::MERGE_GEOMETRY; optimizer.optimize(mergeGroup, options); group->addChild(mergeGroup); if (mDebugBatches) { DebugVisitor dv; mergeGroup->accept(dv); } if (compile) { stateToCompile._mode = osgUtil::GLObjectsVisitor::COMPILE_DISPLAY_LISTS; mergeGroup->accept(stateToCompile); } } auto ico = mSceneManager->getIncrementalCompileOperation(); if (!stateToCompile.empty() && ico) { auto compileSet = new osgUtil::IncrementalCompileOperation::CompileSet(group); compileSet->buildCompileMap(ico->getContextSet(), stateToCompile); ico->add(compileSet, false); } group->getBound(); group->setNodeMask(Mask_Static); osg::UserDataContainer* udc = group->getOrCreateUserDataContainer(); if (activeGrid) { std::sort(refnumSet->mRefnums.begin(), refnumSet->mRefnums.end()); refnumSet->mRefnums.erase( std::unique(refnumSet->mRefnums.begin(), refnumSet->mRefnums.end()), refnumSet->mRefnums.end()); udc->addUserObject(refnumSet); group->addCullCallback(new SceneUtil::LightListCallback); } udc->addUserObject(templateRefs); return group; } unsigned int ObjectPaging::getNodeMask() { return Mask_Static; } struct ClearCacheFunctor { void operator()(MWRender::ChunkId id, osg::Object* obj) { if (intersects(id, mPosition)) mToClear.insert(id); } bool intersects(ChunkId id, osg::Vec3f pos) { if (mActiveGridOnly && !std::get<2>(id)) return false; pos /= ESM::Land::REAL_SIZE; clampToCell(pos); osg::Vec2f center = std::get<0>(id); float halfSize = std::get<1>(id) / 2; return pos.x() >= center.x() - halfSize && pos.y() >= center.y() - halfSize && pos.x() <= center.x() + halfSize && pos.y() <= center.y() + halfSize; } void clampToCell(osg::Vec3f& cellPos) { cellPos.x() = std::clamp(cellPos.x(), mCell.x(), mCell.x() + 1); cellPos.y() = std::clamp(cellPos.y(), mCell.y(), mCell.y() + 1); } osg::Vec3f mPosition; osg::Vec2i mCell; std::set mToClear; bool mActiveGridOnly = false; }; bool ObjectPaging::enableObject( int type, const ESM::RefNum& refnum, const osg::Vec3f& pos, const osg::Vec2i& cell, bool enabled) { if (!typeFilter(type, false)) return false; { std::lock_guard lock(mRefTrackerMutex); if (enabled && !getWritableRefTracker().mDisabled.erase(refnum)) return false; if (!enabled && !getWritableRefTracker().mDisabled.insert(refnum).second) return false; if (mRefTrackerLocked) return false; } ClearCacheFunctor ccf; ccf.mPosition = pos; ccf.mCell = cell; mCache->call(ccf); if (ccf.mToClear.empty()) return false; for (const auto& chunk : ccf.mToClear) mCache->removeFromObjectCache(chunk); return true; } bool ObjectPaging::blacklistObject( int type, const ESM::RefNum& refnum, const osg::Vec3f& pos, const osg::Vec2i& cell) { if (!typeFilter(type, false)) return false; { std::lock_guard lock(mRefTrackerMutex); if (!getWritableRefTracker().mBlacklist.insert(refnum).second) return false; if (mRefTrackerLocked) return false; } ClearCacheFunctor ccf; ccf.mPosition = pos; ccf.mCell = cell; ccf.mActiveGridOnly = true; mCache->call(ccf); if (ccf.mToClear.empty()) return false; for (const auto& chunk : ccf.mToClear) mCache->removeFromObjectCache(chunk); return true; } void ObjectPaging::clear() { std::lock_guard lock(mRefTrackerMutex); mRefTrackerNew.mDisabled.clear(); mRefTrackerNew.mBlacklist.clear(); mRefTrackerLocked = true; } bool ObjectPaging::unlockCache() { if (!mRefTrackerLocked) return false; { std::lock_guard lock(mRefTrackerMutex); mRefTrackerLocked = false; if (mRefTracker == mRefTrackerNew) return false; else mRefTracker = mRefTrackerNew; } mCache->clear(); return true; } struct GetRefnumsFunctor { GetRefnumsFunctor(std::vector& output) : mOutput(output) { } void operator()(MWRender::ChunkId chunkId, osg::Object* obj) { if (!std::get<2>(chunkId)) return; const osg::Vec2f& center = std::get<0>(chunkId); bool activeGrid = (center.x() > mActiveGrid.x() || center.y() > mActiveGrid.y() || center.x() < mActiveGrid.z() || center.y() < mActiveGrid.w()); if (!activeGrid) return; osg::UserDataContainer* udc = obj->getUserDataContainer(); if (udc && udc->getNumUserObjects()) { RefnumSet* refnums = dynamic_cast(udc->getUserObject(0)); if (!refnums) return; mOutput.insert(mOutput.end(), refnums->mRefnums.begin(), refnums->mRefnums.end()); } } osg::Vec4i mActiveGrid; std::vector& mOutput; }; void ObjectPaging::getPagedRefnums(const osg::Vec4i& activeGrid, std::vector& out) { GetRefnumsFunctor grf(out); grf.mActiveGrid = activeGrid; mCache->call(grf); std::sort(out.begin(), out.end()); out.erase(std::unique(out.begin(), out.end()), out.end()); } void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats* stats) const { stats->setAttribute(frameNumber, "Object Chunk", mCache->getCacheSize()); } }