HYDRA-2417 - Optimize MarkPrimDirty - #479
Conversation
vlasovi
commented
Jul 29, 2026
- Introduce batching for multiple MarkPrimDirty calls.
- Optimize _PrimsDirtied method on the scene indices, specifically targeted at animation updates.
ppt-adsk
left a comment
There was a problem hiding this comment.
Nice work! Just the guard object suggestion I have.
| } | ||
|
|
||
| // Start batching DirtyPrims notifications produced per render item. | ||
| Fvp::DirtyNotifier::beginDirtyBatch(*this); |
There was a problem hiding this comment.
Would be (very) nice to have a guard object for this, RAII-style. Prevents coding mistakes, is exception-safe, and you could remove the addition on line 569.
| if (_sceneIndex) { | ||
|
|
||
| if (_sBatchingSceneIndex) { | ||
| _sPendingDirtyEntries.push_back({ _primPath, _locators }); |
There was a problem hiding this comment.
Ok, so, the original flush/batch is at per render item level, that didn't give obvious performance improvement, now, this bigger flush/batch on all render items level should be better :) .
One suggestion: this static _sPendingDirtyEntries is a bit hard to understand and we're actually not flushing,
Also it's not RAII style as @ppt-adsk mentioned, here's a suggested version from AI:
Collapse both levels of flush/batch into one flush(). Then there is exactly one DirtyPrims() call site in the codebase, and one flush().
class DirtyBatch { // owns target SI + all pending entries
public:
FVP_API explicit DirtyBatch(PXR_NS::HdRetainedSceneIndex& si);
FVP_API ~DirtyBatch(); // flush()
FVP_API void flush(); // the ONLY DirtyPrims() call, anywhere
private:
friend class DirtyNotifier;
PXR_NS::TfWeakPtr<PXR_NS::HdRetainedSceneIndex> _sceneIndex;
PXR_NS::HdSceneIndexObserver::DirtiedPrimEntries _entries;
};
class DirtyNotifier { // cursor, no _locators member
public:
DirtyNotifier(HdRetainedSceneIndex& si, const SdfPath& p); // owns a private batch-of-one
DirtyNotifier(DirtyBatch& batch, const SdfPath& p); // writes into caller's batch
// dirty*() append straight into the batch row; no flush(), no dtor logic
};
dirty*() writes through to the row, so DirtyNotifier::flush() disappears — which is literally the "one flush interface" you're after.
What makes this cheap: the batched region has exactly one emission site
I checked the call graph under beginDirtyBatch. MayaHydraRenderItemAdapter::UpdateFromDelta constructs a single notifier (renderItemAdapter.cpp:239) and accumulates the whole frame's worth of locators into it; UpdateTransform (:131) emits nothing; Populate/CreateMaterial/SetMaterial don't create notifiers (the Fvp::DirtyNotifier(*this, rprimId) at mayaHydraSceneIndex.cpp:1345 is in a DG node-removal handler, not the loop). And _EmitRenderItemTopologyDirtyLocators (:74-77) already takes Fvp::DirtyNotifier& by reference, so passing the accumulator down is the established pattern in this file.
So plumbing is one signature + one call site, not thirty:
Fvp::DirtyBatch batch(*this); // RAII, before the loop
...
ria->UpdateFromDelta(data, batch); // was: UpdateFromDelta(data)
MayaHydra::DirtyNotifier notifier(this, batch); // was: DirtyNotifier notifier(this)
Everything else in the tree keeps the two-arg ctor and behaves bit-identically to today.
There was a problem hiding this comment.
@lilike-adsk I'm not sure I totally understand your plan, but it looks like a lot of refactoring. I think I can easily add RAII to the batching. As for the rest, maybe we can create a separate ticket for this work. Also, it's not actually the batching itself that gives the performance optimization, but all the changes in the specific scene indices working on top of batching.