improves groundcover view distance (#3219)

This PR aims to solve all issues with `Groundcover` view distance handling in a satisfying way while preserving other optimisations that benefit other features. The main idea here is not to rely on `ViewData` updates for distance culling calculations because we can not accurately determine distance against lazily updated views. Instead, we perform an accurate measurement in a cull callback we can run every frame in `Groundcover` itself. While we do have to add some code to handle this feature, it is quite loosely coupled code that could be useful elsewhere in the future. These changes should address a performance regression @akortunov experienced.
This commit is contained in:
Bo Svensson 2021-11-08 09:27:42 +00:00 committed by GitHub
parent b6718ecb10
commit 5f1bf89369
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 98 additions and 65 deletions

View file

@ -40,9 +40,9 @@ namespace
return 1 << depth;
}
int Log2( unsigned int n )
unsigned int Log2( unsigned int n )
{
int targetlevel = 0;
unsigned int targetlevel = 0;
while (n >>= 1) ++targetlevel;
return targetlevel;
}
@ -78,16 +78,18 @@ public:
if (intersects)
return Deeper;
}
dist = std::max(0.f, dist + mDistanceModifier);
if (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded
return StopTraversal;
int nativeLodLevel = Log2(static_cast<unsigned int>(node->getSize()/mMinSize));
int lodLevel = Log2(static_cast<unsigned int>(dist/(Constants::CellSizeInUnits*mMinSize*mFactor)));
return nativeLodLevel <= lodLevel ? StopTraversalAndUse : Deeper;
return getNativeLodLevel(node, mMinSize) <= convertDistanceToLodLevel(dist, mMinSize, mFactor) ? StopTraversalAndUse : Deeper;
}
static unsigned int getNativeLodLevel(const QuadTreeNode* node, float minSize)
{
return Log2(static_cast<unsigned int>(node->getSize()/minSize));
}
static unsigned int convertDistanceToLodLevel(float dist, float minSize, float factor)
{
return Log2(static_cast<unsigned int>(dist/(Constants::CellSizeInUnits*minSize*factor)));
}
private:
@ -278,7 +280,6 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour
, mViewDistance(std::numeric_limits<float>::max())
, mMinSize(1/8.f)
, mDebugTerrainChunks(debugChunks)
, mRevalidateDistance(0.f)
{
mChunkManager->setCompositeMapSize(compMapResolution);
mChunkManager->setCompositeMapLevel(compMapLevel);
@ -296,13 +297,14 @@ QuadTreeWorld::~QuadTreeWorld()
{
}
/// get the level of vertex detail to render this node at, expressed relative to the native resolution of the data set.
/// get the level of vertex detail to render this node at, expressed relative to the native resolution of the vertex data set,
/// NOT relative to mMinSize as is the case with node LODs.
unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod)
{
int lod = Log2(int(node->getSize()));
unsigned int vertexLod = DefaultLodCallback::getNativeLodLevel(node, 1);
if (vertexLodMod > 0)
{
lod = std::max(0, lod-vertexLodMod);
vertexLod = static_cast<unsigned int>(std::max(0, static_cast<int>(vertexLod)-vertexLodMod));
}
else if (vertexLodMod < 0)
{
@ -313,13 +315,13 @@ unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod)
size *= 2;
vertexLodMod = std::min(0, vertexLodMod+1);
}
lod += std::abs(vertexLodMod);
vertexLod += std::abs(vertexLodMod);
}
return lod;
return vertexLod;
}
/// get the flags to use for stitching in the index buffer so that chunks of different LOD connect seamlessly
unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, const ViewData* vd)
unsigned int getLodFlags(QuadTreeNode* node, unsigned int ourVertexLod, int vertexLodMod, const ViewData* vd)
{
unsigned int lodFlags = 0;
for (unsigned int i=0; i<4; ++i)
@ -332,40 +334,38 @@ unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, const
// our detail and the neighbour would handle stitching by itself.
while (neighbour && !vd->contains(neighbour))
neighbour = neighbour->getParent();
int lod = 0;
unsigned int lod = 0;
if (neighbour)
lod = getVertexLod(neighbour, vertexLodMod);
if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are -
if (lod <= ourVertexLod) // We only need to worry about neighbours less detailed than we are -
lod = 0; // neighbours with more detail will do the stitching themselves
// Use 4 bits for each LOD delta
if (lod > 0)
{
lodFlags |= static_cast<unsigned int>(lod - ourLod) << (4*i);
lodFlags |= (lod - ourVertexLod) << (4*i);
}
}
// Use the remaining bits for our vertex LOD
lodFlags |= (ourVertexLod << (4*4));
return lodFlags;
}
void QuadTreeWorld::loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile, float reuseDistance)
void QuadTreeWorld::loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile)
{
if (!vd->hasChanged() && entry.mRenderingNode)
return;
int ourLod = getVertexLod(entry.mNode, mVertexLodMod);
if (vd->hasChanged())
{
unsigned int ourVertexLod = getVertexLod(entry.mNode, mVertexLodMod);
// have to recompute the lodFlags in case a neighbour has changed LOD.
unsigned int lodFlags = getLodFlags(entry.mNode, ourLod, mVertexLodMod, vd);
unsigned int lodFlags = getLodFlags(entry.mNode, ourVertexLod, mVertexLodMod, vd);
if (lodFlags != entry.mLodFlags)
{
entry.mRenderingNode = nullptr;
entry.mLodFlags = lodFlags;
}
// have to revalidate chunks within a custom view distance.
if (mRevalidateDistance && entry.mNode->distance(vd->getViewPoint()) <= mRevalidateDistance + reuseDistance)
entry.mRenderingNode = nullptr;
}
if (!entry.mRenderingNode)
@ -378,9 +378,7 @@ void QuadTreeWorld::loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float
for (QuadTreeWorld::ChunkManager* m : mChunkManagers)
{
if (mRevalidateDistance && m->getViewDistance() && entry.mNode->distance(vd->getViewPoint()) > m->getViewDistance() + reuseDistance)
continue;
osg::ref_ptr<osg::Node> n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags, activeGrid, vd->getViewPoint(), compile);
osg::ref_ptr<osg::Node> n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), DefaultLodCallback::getNativeLodLevel(entry.mNode, mMinSize), entry.mLodFlags, activeGrid, vd->getViewPoint(), compile);
if (n) pat->addChild(n);
}
entry.mRenderingNode = pat;
@ -462,7 +460,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv)
for (unsigned int i=0; i<vd->getNumEntries(); ++i)
{
ViewDataEntry& entry = vd->getEntry(i);
loadRenderingNode(entry, vd, cellWorldSize, mActiveGrid, false, mViewDataMap->getReuseDistance());
loadRenderingNode(entry, vd, cellWorldSize, mActiveGrid, false);
entry.mRenderingNode->accept(nv);
}
@ -541,12 +539,11 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::
reporter.addTotal(progressTotal);
}
const float reuseDistance = std::max(mViewDataMap->getReuseDistance(), std::abs(distanceModifier));
for (unsigned int i=startEntry; i<vd->getNumEntries() && !abort; ++i)
{
ViewDataEntry& entry = vd->getEntry(i);
loadRenderingNode(entry, vd, cellWorldSize, grid, true, reuseDistance);
loadRenderingNode(entry, vd, cellWorldSize, grid, true);
if (pass==0) reporter.addProgress(entry.mNode->getSize());
entry.mNode = nullptr; // Clear node lest we break the neighbours search for the next pass
}
@ -584,7 +581,7 @@ void QuadTreeWorld::addChunkManager(QuadTreeWorld::ChunkManager* m)
mChunkManagers.push_back(m);
mTerrainRoot->setNodeMask(mTerrainRoot->getNodeMask()|m->getNodeMask());
if (m->getViewDistance())
mRevalidateDistance = std::max(m->getViewDistance(), mRevalidateDistance);
m->setMaxLodLevel(DefaultLodCallback::convertDistanceToLodLevel(m->getViewDistance() + mViewDataMap->getReuseDistance(), mMinSize, mLodFactor));
}
void QuadTreeWorld::rebuildViews()