mirror of https://github.com/OpenMW/openmw.git
Merge branch 'object_paging_retry' into 'master'
Object Paging See merge request OpenMW/openmw!209pull/2911/head
commit
0dc7715c35
@ -0,0 +1,781 @@
|
||||
#include "objectpaging.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <osg/Version>
|
||||
#include <osg/LOD>
|
||||
#include <osg/Switch>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Material>
|
||||
#include <osgUtil/IncrementalCompileOperation>
|
||||
|
||||
#include <components/esm/esmreader.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/sceneutil/optimizer.hpp>
|
||||
#include <components/sceneutil/clone.hpp>
|
||||
#include <components/sceneutil/util.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
#include <osgParticle/ParticleProcessor>
|
||||
#include <osgParticle/ParticleSystemUpdater>
|
||||
|
||||
#include <components/sceneutil/lightmanager.hpp>
|
||||
#include <components/sceneutil/morphgeometry.hpp>
|
||||
#include <components/sceneutil/riggeometry.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
|
||||
#include "apps/openmw/mwworld/esmstore.hpp"
|
||||
#include "apps/openmw/mwbase/environment.hpp"
|
||||
#include "apps/openmw/mwbase/world.hpp"
|
||||
|
||||
#include "vismask.hpp"
|
||||
|
||||
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<ESM::Static>().searchStatic(id)->mModel;
|
||||
case ESM::REC_ACTI:
|
||||
return store.get<ESM::Activator>().searchStatic(id)->mModel;
|
||||
case ESM::REC_DOOR:
|
||||
return store.get<ESM::Door>().searchStatic(id)->mModel;
|
||||
case ESM::REC_CONT:
|
||||
return store.get<ESM::Container>().searchStatic(id)->mModel;
|
||||
default:
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Node> ObjectPaging::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
|
||||
{
|
||||
if (activeGrid && !mActiveGrid)
|
||||
return nullptr;
|
||||
|
||||
ChunkId id = std::make_tuple(center, size, activeGrid);
|
||||
|
||||
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);
|
||||
if (obj)
|
||||
return obj->asNode();
|
||||
else
|
||||
{
|
||||
osg::ref_ptr<osg::Node> node = createChunk(size, center, activeGrid, viewPoint, compile);
|
||||
mCache->addEntryToObjectCache(id, node.get());
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
class CanOptimizeCallback : public SceneUtil::Optimizer::IsOperationPermissibleForObjectCallback
|
||||
{
|
||||
public:
|
||||
virtual bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Drawable* node,unsigned int option) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
virtual bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Node* node,unsigned int option) const
|
||||
{
|
||||
return (node->getDataVariance() != osg::Object::DYNAMIC);
|
||||
}
|
||||
};
|
||||
|
||||
class CopyOp : public osg::CopyOp
|
||||
{
|
||||
public:
|
||||
bool mOptimizeBillboards = true;
|
||||
float mSqrDistance = 0.f;
|
||||
osg::Vec3f mViewVector;
|
||||
mutable std::vector<const osg::Node*> 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)));
|
||||
}
|
||||
}
|
||||
|
||||
virtual osg::Node* operator() (const osg::Node* node) const
|
||||
{
|
||||
if (const osg::Drawable* d = node->asDrawable())
|
||||
return operator()(d);
|
||||
|
||||
if (dynamic_cast<const osgParticle::ParticleProcessor*>(node))
|
||||
return nullptr;
|
||||
if (dynamic_cast<const osgParticle::ParticleSystemUpdater*>(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<const osg::LOD*>(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;
|
||||
}
|
||||
|
||||
mNodePath.push_back(node);
|
||||
|
||||
osg::Node* cloned = static_cast<osg::Node*>(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<osg::Callback*>(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 node : mNodePath)
|
||||
if (const osg::Transform* t = node->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);
|
||||
}
|
||||
virtual osg::Drawable* operator() (const osg::Drawable* drawable) const
|
||||
{
|
||||
if (dynamic_cast<const osgParticle::ParticleSystem*>(drawable))
|
||||
return nullptr;
|
||||
|
||||
if (const SceneUtil::RigGeometry* rig = dynamic_cast<const SceneUtil::RigGeometry*>(drawable))
|
||||
return operator()(rig->getSourceGeometry());
|
||||
if (const SceneUtil::MorphGeometry* morph = dynamic_cast<const SceneUtil::MorphGeometry*>(drawable))
|
||||
return operator()(morph->getSourceGeometry());
|
||||
|
||||
if (getCopyFlags() & DEEP_COPY_DRAWABLES)
|
||||
{
|
||||
osg::Drawable* d = static_cast<osg::Drawable*>(drawable->clone(*this));
|
||||
d->setDataVariance(osg::Object::STATIC);
|
||||
d->setUserDataContainer(nullptr);
|
||||
d->setName("");
|
||||
return d;
|
||||
}
|
||||
else
|
||||
return const_cast<osg::Drawable*>(drawable);
|
||||
}
|
||||
virtual osg::Callback* operator() (const osg::Callback* callback) const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class TemplateRef : public osg::Object
|
||||
{
|
||||
public:
|
||||
TemplateRef() {}
|
||||
TemplateRef(const TemplateRef& copy, const osg::CopyOp&) : mObjects(copy.mObjects) {}
|
||||
META_Object(MWRender, TemplateRef)
|
||||
std::vector<osg::ref_ptr<const Object>> mObjects;
|
||||
};
|
||||
|
||||
class RefnumSet : public osg::Object
|
||||
{
|
||||
public:
|
||||
RefnumSet(){}
|
||||
RefnumSet(const RefnumSet& copy, const osg::CopyOp&) : mRefnums(copy.mRefnums) {}
|
||||
META_Object(MWRender, RefnumSet)
|
||||
std::set<ESM::RefNum> mRefnums;
|
||||
};
|
||||
|
||||
class AnalyzeVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
AnalyzeVisitor()
|
||||
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
||||
, mCurrentStateSet(nullptr) {}
|
||||
|
||||
typedef std::unordered_map<osg::StateSet*, unsigned int> StateSetCounter;
|
||||
struct Result
|
||||
{
|
||||
StateSetCounter mStateSetCounter;
|
||||
unsigned int mNumVerts = 0;
|
||||
};
|
||||
|
||||
virtual void apply(osg::Node& node)
|
||||
{
|
||||
if (node.getStateSet())
|
||||
mCurrentStateSet = node.getStateSet();
|
||||
traverse(node);
|
||||
}
|
||||
virtual void apply(osg::Geometry& geom)
|
||||
{
|
||||
mResult.mNumVerts += geom.getVertexArray()->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;
|
||||
};
|
||||
|
||||
class DebugVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
DebugVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) {}
|
||||
virtual void apply(osg::Drawable& node)
|
||||
{
|
||||
osg::ref_ptr<osg::Material> 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<osg::StateSet> stateset = node.getStateSet() ? osg::clone(node.getStateSet(), osg::CopyOp::SHALLOW_COPY) : new osg::StateSet;
|
||||
stateset->setAttribute(m);
|
||||
stateset->addUniform(new osg::Uniform("colorMode", 0));
|
||||
node.setStateSet(stateset);
|
||||
}
|
||||
};
|
||||
|
||||
class AddRefnumMarkerVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
AddRefnumMarkerVisitor(const ESM::RefNum &refnum) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mRefnum(refnum) {}
|
||||
ESM::RefNum mRefnum;
|
||||
virtual void apply(osg::Geometry &node)
|
||||
{
|
||||
osg::ref_ptr<RefnumMarker> 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<ChunkId>(nullptr)
|
||||
, mSceneManager(sceneManager)
|
||||
, mRefTrackerLocked(false)
|
||||
{
|
||||
mActiveGrid = Settings::Manager::getBool("object paging active grid", "Terrain");
|
||||
mDebugBatches = Settings::Manager::getBool("object paging debug batches", "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<osg::Node> ObjectPaging::createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
|
||||
{
|
||||
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<ESM::RefNum, ESM::CellRef> refs;
|
||||
std::vector<ESM::ESMReader> esm;
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->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<ESM::Cell>().searchStatic(cellX, cellY);
|
||||
if (!cell) continue;
|
||||
for (size_t i=0; i<cell->mContextList.size(); ++i)
|
||||
{
|
||||
try
|
||||
{
|
||||
unsigned int index = cell->mContextList.at(i).index;
|
||||
if (esm.size()<=index)
|
||||
esm.resize(index+1);
|
||||
cell->restore(esm[index], i);
|
||||
ESM::CellRef ref;
|
||||
ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile;
|
||||
bool deleted = false;
|
||||
while(cell->getNextRef(esm[index], ref, deleted))
|
||||
{
|
||||
Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
|
||||
if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue;
|
||||
int type = store.findStatic(ref.mRefID);
|
||||
if (!typeFilter(type,size>=2)) continue;
|
||||
if (deleted) { refs.erase(ref.mRefNum); continue; }
|
||||
refs[ref.mRefNum] = ref;
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (ESM::CellRefTracker::const_iterator it = cell->mLeasedRefs.begin(); it != cell->mLeasedRefs.end(); ++it)
|
||||
{
|
||||
ESM::CellRef ref = it->first;
|
||||
Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
|
||||
bool deleted = it->second;
|
||||
if (deleted) { refs.erase(ref.mRefNum); continue; }
|
||||
int type = store.findStatic(ref.mRefID);
|
||||
if (!typeFilter(type,size>=2)) continue;
|
||||
refs[ref.mRefNum] = ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (activeGrid)
|
||||
{
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> 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<const ESM::CellRef*> mInstances;
|
||||
AnalyzeVisitor::Result mAnalyzeResult;
|
||||
bool mNeedCompile = false;
|
||||
};
|
||||
typedef std::map<osg::ref_ptr<const osg::Node>, InstanceList> NodeMap;
|
||||
NodeMap nodes;
|
||||
osg::ref_ptr<RefnumSet> refnumSet = activeGrid ? new RefnumSet : nullptr;
|
||||
AnalyzeVisitor analyzeVisitor;
|
||||
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()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y()))
|
||||
continue;
|
||||
}
|
||||
|
||||
float dSqr = (viewPoint - pos).length2();
|
||||
if (!activeGrid)
|
||||
{
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mSizeCacheMutex);
|
||||
SizeCache::iterator found = mSizeCache.find(pair.first);
|
||||
if (found != mSizeCache.end() && found->second < dSqr*minSize*minSize)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ref.mRefID == "prisonmarker" || ref.mRefID == "divinemarker" || ref.mRefID == "templemarker" || ref.mRefID == "northmarker")
|
||||
continue; // marker objects that have a hardcoded function in the game logic, should be hidden from the player
|
||||
|
||||
int type = store.findStatic(ref.mRefID);
|
||||
std::string model = getModel(type, ref.mRefID, store);
|
||||
if (model.empty()) continue;
|
||||
model = "meshes/" + model;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
osg::ref_ptr<const osg::Node> 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.insert(pair.first);
|
||||
}
|
||||
|
||||
{
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
|
||||
if (getRefTracker().mDisabled.count(pair.first))
|
||||
continue;
|
||||
}
|
||||
|
||||
float radius2 = cnode->getBound().radius2() * ref.mScale*ref.mScale;
|
||||
if (radius2 < dSqr*minSize*minSize && !activeGrid)
|
||||
{
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mSizeCacheMutex);
|
||||
mSizeCache[pair.first] = radius2;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto emplaced = nodes.emplace(cnode, InstanceList());
|
||||
if (emplaced.second)
|
||||
{
|
||||
const_cast<osg::Node*>(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<osg::Group> group = new osg::Group;
|
||||
osg::ref_ptr<osg::Group> mergeGroup = new osg::Group;
|
||||
osg::ref_ptr<TemplateRef> templateRefs = new TemplateRef;
|
||||
osgUtil::StateToCompile stateToCompile(0, nullptr);
|
||||
CopyOp copyop;
|
||||
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::Matrixf matrix;
|
||||
matrix.preMultTranslate(pos - worldCenter);
|
||||
matrix.preMultRotate( 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)) );
|
||||
matrix.preMultScale(osg::Vec3f(ref.mScale, ref.mScale, ref.mScale));
|
||||
osg::ref_ptr<osg::MatrixTransform> trans = new osg::MatrixTransform(matrix);
|
||||
trans->setDataVariance(osg::Object::STATIC);
|
||||
|
||||
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);
|
||||
|
||||
if (activeGrid)
|
||||
{
|
||||
if (merge)
|
||||
{
|
||||
AddRefnumMarkerVisitor visitor(ref.mRefNum);
|
||||
trans->accept(visitor);
|
||||
}
|
||||
else
|
||||
{
|
||||
osg::ref_ptr<RefnumMarker> 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 hint to the cache that it's still being used and should be kept in cache
|
||||
templateRefs->mObjects.push_back(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<osg::Node*>(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)
|
||||
{
|
||||
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;
|
||||
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;
|
||||
}
|
||||
osg::Vec3f mPosition;
|
||||
std::set<MWRender::ChunkId> mToClear;
|
||||
bool mActiveGridOnly = false;
|
||||
};
|
||||
|
||||
bool ObjectPaging::enableObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, bool enabled)
|
||||
{
|
||||
if (!typeFilter(type, false))
|
||||
return false;
|
||||
|
||||
{
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> 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;
|
||||
mCache->call(ccf);
|
||||
if (ccf.mToClear.empty()) return false;
|
||||
for (auto chunk : ccf.mToClear)
|
||||
mCache->removeFromObjectCache(chunk);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ObjectPaging::blacklistObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos)
|
||||
{
|
||||
if (!typeFilter(type, false))
|
||||
return false;
|
||||
|
||||
{
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
|
||||
if (!getWritableRefTracker().mBlacklist.insert(refnum).second) return false;
|
||||
if (mRefTrackerLocked) return false;
|
||||
}
|
||||
|
||||
ClearCacheFunctor ccf;
|
||||
ccf.mPosition = pos;
|
||||
ccf.mActiveGridOnly = true;
|
||||
mCache->call(ccf);
|
||||
if (ccf.mToClear.empty()) return false;
|
||||
for (auto chunk : ccf.mToClear)
|
||||
mCache->removeFromObjectCache(chunk);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void ObjectPaging::clear()
|
||||
{
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
|
||||
mRefTrackerNew.mDisabled.clear();
|
||||
mRefTrackerNew.mBlacklist.clear();
|
||||
mRefTrackerLocked = true;
|
||||
}
|
||||
|
||||
bool ObjectPaging::unlockCache()
|
||||
{
|
||||
if (!mRefTrackerLocked) return false;
|
||||
{
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
|
||||
mRefTrackerLocked = false;
|
||||
if (mRefTracker == mRefTrackerNew)
|
||||
return false;
|
||||
else
|
||||
mRefTracker = mRefTrackerNew;
|
||||
}
|
||||
mCache->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
struct GetRefnumsFunctor
|
||||
{
|
||||
GetRefnumsFunctor(std::set<ESM::RefNum>& 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<RefnumSet*>(udc->getUserObject(0));
|
||||
if (!refnums) return;
|
||||
mOutput.insert(refnums->mRefnums.begin(), refnums->mRefnums.end());
|
||||
}
|
||||
}
|
||||
osg::Vec4i mActiveGrid;
|
||||
std::set<ESM::RefNum>& mOutput;
|
||||
};
|
||||
|
||||
void ObjectPaging::getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out)
|
||||
{
|
||||
GetRefnumsFunctor grf(out);
|
||||
grf.mActiveGrid = activeGrid;
|
||||
mCache->call(grf);
|
||||
}
|
||||
|
||||
void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats *stats) const
|
||||
{
|
||||
stats->setAttribute(frameNumber, "Object Chunk", mCache->getCacheSize());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
#ifndef OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H
|
||||
#define OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H
|
||||
|
||||
#include <components/terrain/quadtreeworld.hpp>
|
||||
#include <components/resource/resourcemanager.hpp>
|
||||
#include <components/esm/loadcell.hpp>
|
||||
|
||||
#include <OpenThreads/Mutex>
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
class SceneManager;
|
||||
}
|
||||
namespace MWWorld
|
||||
{
|
||||
class ESMStore;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
typedef std::tuple<osg::Vec2f, float, bool> ChunkId; // Center, Size, ActiveGrid
|
||||
|
||||
class ObjectPaging : public Resource::GenericResourceManager<ChunkId>, public Terrain::QuadTreeWorld::ChunkManager
|
||||
{
|
||||
public:
|
||||
ObjectPaging(Resource::SceneManager* sceneManager);
|
||||
~ObjectPaging() = default;
|
||||
|
||||
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override;
|
||||
|
||||
osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile);
|
||||
|
||||
virtual unsigned int getNodeMask() override;
|
||||
|
||||
/// @return true if view needs rebuild
|
||||
bool enableObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, bool enabled);
|
||||
|
||||
/// @return true if view needs rebuild
|
||||
bool blacklistObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos);
|
||||
|
||||
void clear();
|
||||
|
||||
/// Must be called after clear() before rendering starts.
|
||||
/// @return true if view needs rebuild
|
||||
bool unlockCache();
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
|
||||
|
||||
void getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out);
|
||||
|
||||
private:
|
||||
Resource::SceneManager* mSceneManager;
|
||||
bool mActiveGrid;
|
||||
bool mDebugBatches;
|
||||
float mMergeFactor;
|
||||
float mMinSize;
|
||||
float mMinSizeMergeFactor;
|
||||
float mMinSizeCostMultiplier;
|
||||
|
||||
OpenThreads::Mutex mRefTrackerMutex;
|
||||
struct RefTracker
|
||||
{
|
||||
std::set<ESM::RefNum> mDisabled;
|
||||
std::set<ESM::RefNum> mBlacklist;
|
||||
bool operator==(const RefTracker&other) { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; }
|
||||
};
|
||||
RefTracker mRefTracker;
|
||||
RefTracker mRefTrackerNew;
|
||||
bool mRefTrackerLocked;
|
||||
|
||||
const RefTracker& getRefTracker() const { return mRefTracker; }
|
||||
RefTracker& getWritableRefTracker() { return mRefTrackerLocked ? mRefTrackerNew : mRefTracker; }
|
||||
|
||||
OpenThreads::Mutex mSizeCacheMutex;
|
||||
typedef std::map<ESM::RefNum, float> SizeCache;
|
||||
SizeCache mSizeCache;
|
||||
};
|
||||
|
||||
class RefnumMarker : public osg::Object
|
||||
{
|
||||
public:
|
||||
RefnumMarker() : mNumVertices(0) {}
|
||||
RefnumMarker(const RefnumMarker ©, osg::CopyOp co) : mRefnum(copy.mRefnum), mNumVertices(copy.mNumVertices) {}
|
||||
META_Object(MWRender, RefnumMarker)
|
||||
|
||||
ESM::RefNum mRefnum;
|
||||
unsigned int mNumVertices;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue