I might be missing something obvious here, but how could we avoid flattening of nested instancers? Right now, we’re getting a bunch of prototype copies like ForInstancer52ed…14c2, which are coming from UsdImagingPiPrototypePropagatingSceneIndex. The problem is that in scene there’s just a couple of prototypes with lots of instances, but in Hydra there’s a lots of such prototypes with just a handful instances. Admittedly, it gives visually correct result, but it’s wasting a lot of memory.
As our renderer can natively deal with nesting (up to certain level) we would like to take advantage of it and limit number of prototypes. As I understand, it would involve customizing PrototypePropagatingSceneIndex, however I’m unsure how to disable/change the default one (without patching code) and/or orchestrate with custom one within third party applications.
I’ve seen that this topic was touched in a couple of places, but I haven’t found any concrete examples. Is there something that I’m missing?
Hi Jakub. My understanding is that Hydra 2 does support nested instances, and the HdPrman render delegate makes use of this – for example, instancing a leaf prototype onto a tree prototype, and instancing the tree onto a forest. There are, however, some unresolved questions around the implementation of deep selection within nested instances.
There are two phases that determine the final number of prototypes. First, the USD scenegraph applies its instance-sharing rules to determine how many prototypes to generate to back its native instances. These are what live in the USD scenegraph at paths such as </__Prototype_NNN>. These are serially numbered but not in a deterministic order. In usdview you can see these if you turn on Show > Prototype Prims.
The next phase is that Hydra applies additional rules that may further split prototypes based on inherited state. This is what the “instance aggregation” in UsdImaging is about. You can see a list of the input fields used to split prototypes in _InstanceDataSourceNames() in usdImaging/sceneIndices.cpp: OpenUSD/pxr/usdImaging/usdImaging/sceneIndices.cpp at dev · PixarAnimationStudios/OpenUSD · GitHub
Currently this list includes:
material bindings
purpose
certain model (asset) level schemas, which may provide context for texture path resolution
Perhaps one of these is varying across your prototypes and causing Hydra to split them further, unexpectedly? Also, I know that there has been some discussion / observation that we may need some more flexibility over this policy in the future, in order to better adapt to renderer-specific capabilities. Motivating examples there include (1) some renderers such as RenderMan can vary surface (but not displacement) materials per-instance, (2) some renderers may be able to run UsdSkel vertex deformation internally, allowing crowd agent prototypes to stay shared while letting individual instances pose uniquely.
Hope this helps. More robust handling of instances has definitely been a focus for Hydra 2 but it is a large topic with a lot of converging / diverging needs.
Hi blevin,
Thank you for your response. Indeed, I’ve seen code in HdPrman, however trying to replicate it I’m having hard time to figure out, which HdMesh should be used as actual prototype as render delegate is provided with a number of those prototype copies. Please consider this toy example scene, where there are only two simple mesh cubes, which in turn are instanced a bunch of times. When inspecting Hydra index you should see that there’s a number of separate prototypes instead of expected two.
I’d expect that we could instruct Hydra to request only those two meshes and deal with instancing complexity with custom implementation of HdInstancer. Am I missing some setting / env variable that enables that? I’m using USD-24.3 for my testing.
I can confirm that I see the result you describe: rather than 2 prototypes (one for each cubeMesh), there are more.
I asked the Hydra team why this happens. They said that the UsdImaging point instancer implementation currently creates re-rooted child copies of the prototypes used by the point instancer. I don’t fully grasp the details, but they say this was motivated by trying to implement USD’s semantics.
The considerations they mentioned include UsdGeomPointInstancer’s ability to both use prototypes that either are / are not rooted under that PointInstancer; and to use prototypes that either are concrete (with a def, meaning it will render on its own in addition to being used as a prototype elsewhere) or abstract (with only an over, which is normally ignored by Hydra, but can be used via PointInstancer).
It sounds like it might be possible to revisit this UsdImaging implementation approach in the future to see if it would be feasible avoid creating re-rooted copies of the prototypes, while still supporting USD semantics. It is unclear how large a project would be, given that the implementors so far believed that re-rooting was necessary.
(Side thought. Setting aside USD and UsdImaging and their approaches to instancing for a moment, it seems to me that design space for instancing approaches is broad, and one of the other approaches is to identify and recover sharing opportunities “late” through content-based hashing approaches. HdPrman already uses this for de-duplicating material networks, and I believe HdSt (Storm) does something similar for de-duplicating primvar buffers. At risk of speculating about things I’m not too familiar with, I wonder if something like that could help a renderer delegate recover prototype sharing opportunities, if it’s infeasible to do that upstream.)
I’m not sure why this is necessary for handling over / def specifiers, but I might be missing some nuance there.
Anyway, the bottom line is that we cannot fit into memory a bunch of our environment scenes (as they rely on heavy usage of point instancer) when using Hydra, but they fit easily when doing our direct USD translation.
While, I agree that it can be done with hashing of input data, but that seems wasteful as underlying data in scene is already prepared for optimal consumption, but Hydra is putting effort into splitting it and renderer will need to put extra effort into reverting that.
It would be great if such behavior could be customized either via render delegate interface or environment variables. At the moment, we’re considering patching it to measure performance impact. However, it might not be sustainable to maintain such patch, so it would be great to learn what would be the best practice here.
We do support nested instancing and work quite hard to keep the prototype count down through the pipeline. With that said:
For a given point instancer, we should only create as many prototypes as were present in the USD file, and for nested point instancers they will be represented by the hydra instancer being instanced by another hydra instancer. However, we do relocate them in the scene. As Brett mentions, there’s some funkiness where prototype traversal doesn’t follow normal usd population traversal rules, and inherited attributes have some special behavior; most assets won’t notice the difference, but it is an edge case we need to support. We’ll deactivate the prims at the original location (by clearing their prim type) and copy their subtree to a location below the point instancer to support the behaviors mentioned above.
For composition-instanced prims, we will attempt to transform their USD representation into a point-instancer-based representation in hydra, which is conceptually similar to a front-loaded hash deduplication. However, we can only combine instances if they have compatible or instance-variable attributes, so we will sometimes split instances with e.g. different material bindings. This set of choices is based on our existing renderers, although we’re aware that different renderers support different ways of varying instance data, so this instance-grouping algorithm is configurable by specifying which attributes are not aggregatable here: OpenUSD/pxr/usdImaging/usdImaging/sceneIndices.cpp at release · PixarAnimationStudios/OpenUSD · GitHub .
We don’t do any global optimization of e.g. pruning instancers with low instance/prototype ratios or recombining instancing levels to optimize instance count; this would be an up-front expense, but for certain assets might be a performance win.
Hope that helps. If you’re still having issues I can look at your file.
We’re not planning to merge the data sharing ID at the moment, although I’d personally like to see if we can wire hashes through to deduplicate buffers/material networks/etc. But we’re worried that the data sharing id as currently implemented is both duplicative of our existing instance aggregation code and also isn’t robust with some of the scene transformations we’re doing.