5,000 trees, 12 GB, and 200 variants
An environment asset forest.usd contains 5,000 trees. The current file authors 5,000 sibling prims, each referencing one of 200 tree assets and setting instanceable = true. The stage takes 90 seconds to open, uses 12 GB RSS, and Storm renders at 400 ms/frame. The team proposes replacing the trees with one UsdGeomPointInstancer using 200 prototype references and arrays for positions, orientations, scales, and protoIndices. A reviewer objects that point instancing "loses the scene graph" and will break per-tree selection, collision queries, and material overrides. The team also notices that even with instanceable = true, UsdStage.Traverse() visits all 5,000 tree prims, and a validator that expands instance proxies uses much more memory than expected. They need a decision that meets a 16 GB memory budget and a 100 ms frame target.
1. Compare native instancing and point instancing for this workload. Recommend an approach and justify it against the memory and frame-time targets.
Read the worked answer
Native instancing and point instancing solve different parts of the problem.
Native instancing creates one prototype per unique referenced asset and shares the composed data among the instance prims. It preserves a prim per tree, which is useful for unique paths, per-instance API schemas, selection, and tools that expect a scene graph. But it still creates 5,000 instance prims. Composition, traversal, and render-index bookkeeping scale with the number of instance prims, even though the heavy geometry is shared. With 200 high-poly prototypes, the shared geometry may still be large; 12 GB is plausible if the prototypes are heavy or if tools expand instance proxies.
Point instancing represents the whole forest as one UsdGeomPointInstancer prim with compact arrays and a prototypes relationship to 200 prototype roots. The namespace cost is one instancer plus prototypes, not 5,000 tree prims. This is the better fit for 5,000 homogeneous instances when the main need is rendering and transform variation. Per-instance identity can be carried by ids, and per-instance data by indexed primvars.
The recommendation is point instancing for the bulk forest, with native instancing reserved for hero trees that need unique hierarchy, selection, or arbitrary overrides. This meets the memory target by avoiding thousands of instance prims and meets the frame target by giving the render delegate one instancer with compact arrays rather than a large instance graph.
2. Why does `UsdStage.Traverse()` visit all 5,000 prims even with native instancing, and why does the validator's memory spike when it expands instance proxies?
Read the worked answer
Native instancing deduplicates composed data, not the prim namespace. Each tree is still a prim at a unique path, so a normal stage traversal visits all 5,000 instance prims. That is expected. What instancing avoids is composing and storing 5,000 independent copies of the tree's descendant hierarchy and geometry; the descendants live once in the prototype.
By default, traversal does not descend into instance proxies, which are the descendant prims seen through an instance. If a tool enables instance-proxy traversal, it will visit the descendants of every instance, and each visit can cause composition and data-source work. A validator that computes bounds, checks materials, or expands every instance proxy can therefore multiply its memory and time by the instance count, even though the prototype is shared. The 12 GB spike may be the validator's expanded view, not the base stage.
Inspection should distinguish:
- counting instance prims with a normal traversal;
- counting unique prototypes by reading prototype roots;
- inspecting one instance proxy path only when needed;
- using
UsdGeomPointInstancerarrays for bulk counts instead of expanding every instance.
The team should not conclude that instancing failed because traversal sees 5,000 prims. They should measure unique prototype size and avoid instance-proxy expansion in validation and tooling.
3. If per-tree selection and per-tree material overrides are mandatory, propose a hybrid structure and explain how to keep memory and draw calls under control.
Read the worked answer
A hybrid keeps bulk instances cheap while allowing a small set of trees to remain individually addressable.
- Partition the forest by material. Create one
UsdGeomPointInstancerper material group, with its own prototype set andprotoIndices. This preserves per-material batching and avoids per-instance material binding, which point instancing does not support arbitrarily. - Use point-instancer
idsand a sidecar mapping from selection ID to instancer path plus instance index. Picking a tree resolves to an instance index, not a prim path. The selection tool then displays a highlight at the picked transform rather than requiring a real prim per tree. - Promote only hero trees, or trees that need unique overrides, to native instance prims or plain referenced prims. Keep that set small, for example tens rather than thousands.
- If a per-tree override is needed for a non-hero tree, author it as an indexed primvar on the instancer, or move that tree into the hero group. Do not convert the whole forest back to 5,000 prims for a handful of exceptions.
This preserves the scene-graph benefits where they matter, keeps the bulk data in compact arrays, and keeps draw calls grouped by material. The trade-off is that selection and override tooling must understand point-instancer indices rather than assuming every tree has a prim path.