Hello,
We are trying to create a filtering scene index that prunes out prims from the scene hierarchy. The condition that determines whether a prim is pruned out or not is based on the data contained in its data sources (for example in our case, if a mesh has >10k vertices). However, this is causing some issues when it comes to handling dirty notifications.
If we simply override GetPrim like this
HdSceneIndexPrim PruningSceneIndex::GetPrim(const SdfPath& primPath) const
{
HdSceneIndexPrim prim = _GetInputSceneIndex()->GetPrim(primPath);
return ShouldBePruned(prim) ? HdSceneIndexPrim() : prim;
}
we run into the following issue : if a prim is initially pruned out (say it has 15k vertices in our example), but we then receive a dirty notification and it should no longer be pruned (e.g. it now has 5k vertices), the downstream observers will receive a dirty notification for a prim which they have never known anything about, but most importantly : they won’t be aware of the other data sources that the prim might have and which weren’t dirtied.
A concrete example of this issue is that in our case, a prim which is initially pruned and then becomes visible will appear at the origin rather than at the position of its xform, as from the point of view of downstream observers, the prim was initially empty, was never added and no xform was dirtied.
Conversely, if a prim was initially not pruned out and should now be pruned, we should also notify observers of such a change. This is less of a problem as we can simply send a PrimsRemoved notification for pruned prims (and though they might get sent multiple times for the same prim, I believe these should be idempotent?)
I see two ways to resolve this issue, however both have significant downsides, so I was wondering if there was a recommended way to handle such situations.
Method 1 : When we receive a dirty notification for a prim, check if it is pruned : if it is, send a PrimsRemoved notifications, and if it isn’t, send a PrimsAdded notification to trigger a full re-sync. For example :
void PruningSceneIndex::_PrimsDirtied(
const HdSceneIndexBase& sender,
const HdSceneIndexObserver::DirtiedPrimEntries& entries)
{
HdSceneIndexObserver::RemovedPrimEntries removedEntries;
HdSceneIndexObserver::AddedPrimEntries addedEntries;
for (const auto& entry : entries) {
HdSceneIndexPrim prim = _GetInputSceneIndex()->GetPrim(entry.primPath);
if (ShouldBePruned(prim)) {
removedEntries.emplace_back(entry.primPath);
} else {
addedEntries.emplace_back(entry.primPath, prim.primType);
}
}
_SendPrimsRemoved(removedEntries);
_SendPrimsAdded(addedEntries);
}
This has the obvious significant downside of causing full prim re-syncs each time any data source on a visible prim is dirtied, doing lots of wasteful work. I’ve also tried sending dirty notifications on the HdDataSourceLocatorSet::UniversalSet() instead of sending PrimsAdded notifications, but this was causing a crash in HdSt_FlatNormalsComputationGPU, and would still be just as wasteful anyways.
Method 2 : Track and store which prims are pruned. This solves the problem with method 1, but adds some statefulness and complexity to the code, as now :
- We need to override _PrimsAdded and _PrimsRemoved to update whatever container stores the pruning status of prims
- There are 4 different possible cases for each dirtied prim in _PrimsDirtied :
-
- Its pruning status did NOT change :
- 1a. The prim is pruned → drop the DirtiedPrimEntry
- 1b. The prim is visible → forward the DirtiedPrimEntry
-
- Its pruning status DID change :
- 2a. The prim is now pruned → send a RemovedPrimEntry
- 2b. The prim is now visible → send an AddedPrimEntry
-
- In order to handle those 4 _PrimsDirtied cases correctly at all times, we need to have the correct initial pruning status for each prim, which would require traversing the whole scene hierarchy in the constructor, e.g. :
PruningSceneIndex::PruningSceneIndex(const HdSceneIndexBaseRefPtr& inputSceneIndex)
: HdSingleInputFilteringSceneIndexBase(inputSceneIndex)
{
std::stack<SdfPath> primPathsToTraverse({ SdfPath::AbsoluteRootPath() });
while (!primPathsToTraverse.empty()) {
SdfPath currPrimPath = primPathsToTraverse.top();
primPathsToTraverse.pop();
UpdatePruningStatus(currPrimPath); // This would be the method to check and store the initial pruning status
for (const auto& childPath : inputSceneIndex->GetChildPrimPaths(currPrimPath)) {
primPathsToTraverse.push(childPath);
}
}
}
If we start having multiple of these types of pruning scene indices, constructing the scene index graph could become quite slow as each of them does its own traversal of the scene hierarchy. It seems that this is sort of what UsdImaging_NiPrototypePruningSceneIndex does in its constructor, however that class doesn’t do a full traversal of the scene, and instead only looks at the direct children of the root prim, so the impact is lessened in that case.
Any thoughts on how to approach this problem would be appreciated, thanks!
Philippe