Instancing in Hydra 2.0

This is a rather philosophical post about the long term objectives of Hydra 2.0, with instancing used as a case study of sorts, and I’ll start by posing a question: should a Hydra 2.0 renderer rely on the ‘InstancedBy’ schema?

From the schema docs:

Many renderers need to know not what prototypes an instancer has, but rather what instancers a prototype has…

This value is computed based on the instancer topology of instancer prims in the scene.

This is extrinsic data, inherently tied to how much of the scene has been processed, and so isn’t something that would be relied upon by a consumer that expects scene graph data to be static during progressive evaluation, and would not want the entire scene to be processed in order to produce definitive data for a given prim.

The schema has a quite reasonable historical justification (in the context of both USD scene evaluation and the Storm renderer), but to me it seems inappropriate in the Hydra 2.0 ecosystem. It’s expensive to provide (being heavy on book-keeping), and is more suited to evaluation at the point of scene graph consumption: renderers are perfectly able to do this themselves*.

As I understand it, a generic Hydra filtering scene index (HdInstancedBySceneIndex) existed precisely to provide ‘instancedBy’ data as part of the scene flattening process for Hydra 1 render delegates, but interestingly this was removed (perhaps due to performance concerns?) with the introduction of USD-specific instancing scene indices. These are part of the pre-merge scene index bundle, meaning USD scene instancing isn’t really open for manipulation in the subsequent pipeline. (They’re also forced to work around a limitation in the Hydra 1 scene delegate instancing API that mandates a single instancer for any given prototype prim, but that is an aside.)

The suggestion here, then, is that hydra renderers (currently render delegates) move away from relying on ‘instancedBy’ data in favour of ‘instancerTopology’ and direct scene graph access. One would assume that compatibility could be provided via a generic scene index in the vein of HdInstancedBySceneIndex, but its removal suggests there are reasons to avoid this.

Assuming this proposal is actually aligned with the goals of Hydra 2.0, do we think there is a path to consensus?

* There is one additional piece of information that I think would be beneficial as part of a core Hydra schema: designation of a prim hierarchy as being a prototype (instance source) that is not itself part of the rendered scene. Conceptually, this is equivalent to marking a prim hierarchy invisible at a point above the prototype root, rather than at the root, since visibility would ideally be respected at any point in an instantiated hierarchy. This concept will be familiar to those with experience of instancing in Katana.

One could also argue for an ‘instance ID’ attribute in a Hydra schema as a generic way of indicating to a renderer that it can perform its own instance aggregation, rather than creating point instancers that are dependent on extent of scene processing.

Is there merit in namespacing Hydra schemas that are either Hydra 1 or Hydra 2 specific?

Hey Daniel,

Fun question! :). To define my terms: the two dominant modes of instancing I can think of are vectorized instancing, as exemplified by point instancers, and reference instancing (e.g. “I am a copy of that prim”), as exemplified by USD composition instancing. (Convenient that USD provides both!).

In Hydra we’ve leaned hard into vectorized instancing for a few reasons. In the renderers we maintain (Storm, HdPrman), the vectorized format tends to be a much better representation of the rendering object model (e.g. an instanced draw call, or an instanced BVH node); and additionally, vectorizing instances at the top of our data pipeline can reduce the prim count dramatically, cutting down on hydra bookkeeping overhead. With the large instance counts and specific instance topologies we see in some of our scenes, this latter point can be a big deal.

Vectorized instancing can be a pain to work with; in particular, for things like collection-based modifiers (pruning, material assignments), traversing the hydra scene in the native USD namespace and reading/writing per-instance attributes is currently quite difficult. We’ve had discussions internally about writing utilities to assist with that traversal, along the lines of the HdSceneIndexPrimView, and think this would help quite a bit. As you point out, vectorized instancing also requires full knowledge of the scene. This isn’t an issue for us when we’re reading from a UsdStage, which is our main concern, but could be an issue for other scene graphs.

As far as “instancedBy” on a prim vs “instancerTopology” on an instancer, that’s just flipping a relationship and caching the result. If you have a specific argument for why we shouldn’t do that, I’m curious to hear, but we’ve found computing “instancedBy” makes things easier to implement and faster.

We did try to keep the instancing transformations isolated to a few UsdImaging scene indices specifically so folks could prototype other instancing algorithms; although adding reference instancing as a concept to hydra might require threading it through a bunch of other scene indices as well. As a warning with USD specifically, resolution of composition instancing, UsdGeomPointInstancer prototype semantics, and the “cards”/drawMode feature are somewhat entangled.

tl;dr: vectorizing referenced instances early in the pipeline and computing “instancedBy” remain our plan in Hydra 2, but I hope the system is flexible enough for you to try other approaches, and this topic has a ton of depth and you all have a bunch of accumulated experience and I’m happy to keep chatting about it.

Hi Tom! Firstly, thanks for the detailed reply, and sorry for not acknowledging it sooner.

As far as “instancedBy” on a prim vs “instancerTopology” on an instancer, that’s just flipping a relationship and caching the result. If you have a specific argument for why we shouldn’t do that, I’m curious to hear, but we’ve found computing “instancedBy” makes things easier to implement and faster.

There is certainly no harm in a Scene Index providing this computed information, nor is there necessarily a problem with its existence as a core schema, unless it is intended or interpreted as a requirement. So in my initial question, perhaps some emphasis would be helpful: should a Hydra 2.0 renderer rely on the ‘InstancedBy’ schema?

This isn’t an issue for us when we’re reading from a UsdStage, which is our main concern, but could be an issue for other scene graphs.

Indeed, the major issue here is with consumer-driven (pull-based) traversal of a static scene, as can be employed in a single frame render. If a scene consumer requests ‘instancedBy’ data, fulfilment requires that the scene index either provide all paths that have so far been discovered, or otherwise force discovery of them all, i.e. force evaluation of all scene that may contain instances.

In the former case, the scene index is then committed to dirtying the prim when other instances are discovered, which is incompatible with the premise of a static scene. In the latter case, no additional discovery is required in the special case of an encapsulated point instancer with descendant prototypes, but the general ‘reference instancing’ case requires that an arbitrarily large portion of the entire hierarchy be loaded, which scuppers lazy evaluation in which the consumer may request only a subset of the hierarchy (as well as having implications for threaded scene evaluation).

Where scene is ‘observed’ for updates, rather than being considered static, ‘instancedBy’ data can be progressively updated, though performance may be a concern due to iterative growth.

Renderers are well-positioned to compute their own ‘instancedBy’ data: they are at the end of the scene graph pipeline and can decide when to evaluate it, based on the extent of the scene they have requested.

To summarise, it is reasonable to suggest that a Hydra 2.0 renderer, which should be able to appropriately consume scene graph from sources other than USD, be prepared to compute its own ‘instancedBy’ data, rather than presume its provision*. This could of course be negotiated with Render Delegate providers directly, but obtaining consensus in the OpenUSD community is preferable.

I suspect that HdStorm may be most resistant to this notion, given its history, but is notably a Render Delegate intended for interactive use, where the problems associated with providing this data are at least not critical. We know that renderers that have traditional Katana Render plug-ins are capable of managing their own instance-source mappings, RenderMan included. There may be a case for providing a generic utility for this, if there’s demand for it.

* Another option, though not ideal, could be to document the special case of ‘instancedBy’ data being volatile during a static scene expansion.

Hi Daniel,

This discussion is a little tricky to have over the forum :).

To answer your question directly: yes, much of the existing hydra code depends on both “instancedBy” and “instancerTopology” being authored and in sync. Ultimately, Renderman prefers the “instancedBy” representation, and we use it in lots of intermediate transformation steps as well.

To answer your question indirectly, I don’t think that’s the key point of contention in developing a pull-based hydra workflow.

Due to USD semantic resolution rules that I can get into if you want, to develop a coherent view of the scene you need to have a current set of active instances, or an active view of the scene, that you consider globally at some point. This resolution step corresponds to the existing UsdImaging instancing scene indices.

In terms of downstream scene computation/transformation and rendering, you’ll also have a “current set” you’re working with and paying for, and I’d imagine what you’re after is both (1) a way to specify the current set; and (2) a way to pay only an incremental cost when the current set changes.

Going all the way down the stack: our experience is that loading a full UsdStage is quite performant as long as you’re careful to not pull any attributes (e.g. usdview –norender MoanaIsland is quite quick for me), but UsdStage does have a “population mask” concept where it will only compose/load specified subtrees. I don’t think this is well optimized for changing at runtime, but if you wanted to do pull-based composition this is where I’d start.

Hydra tries to be mostly pull-based and local, with the exceptions of: (a) the initial scene traversal, and (b) a few global scene transformations that work across the whole scene, driven by the initial scene traversal and the corresponding PrimsAdded call. However, it seems like if we added a system for filtering that initial scene traversal/initial PrimsAdded call from UsdImagingStageSceneIndex, we could control which parts of the scene we evaluated at startup. Since we’re already on the hook for supporting fast and correct scene edits, later edits to the initial traversal filter could be processed proportional to the cost of additional assets loaded.

To be more precise, in UsdImagingStageSceneIndex::Populate we could pass in a list of subtrees/list of paths/list of path depths to control which prims/instances get added to hydra, narrowing hydra’s view of the scene, and when that list changes we could compute a differential invalidation to send to hydra.

I know this still requires up-front specification of the working set, and isn’t as dynamic as a completely pull-based model, but it would be a fairly easy extension to hydra and seems sufficient to implement an expanding-frontiers-type evalution model.

Anyway, what do you think of this concept? If that still doesn’t work, I’ll need to do some thinking about whether there’s a reasonable way to represent instance resolution in a pull-based manner. The evaluation steps there are pretty tricky.

Thanks,

Tom