All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Scenegraph Instancing

Overview

USD's instancing functionality allows prims that bring in common scene description via composition arcs to share those parts of the scenegraph, rather than having them duplicated for each prim. This can greatly improve performance in cases where many copies of the same asset are brought in to a scene via composition. For example, a parking lot scene may use references to bring in the same car model (or set of car models) hundreds or thousands of times to fully populate the lot. In cases like these, instancing can provide significant performance benefits.

Uninstanced_vs_Instanced.png
Uninstanced vs. Instanced Scenegraph

With instancing, the hierarchy of prims from the referenced Car model is exposed beneath a single prototype prim, instead of having n copies of that hierarchy beneath each of the Car prims in the scene. This reduces the size of the scenegraph, which reduces the time needed to load a UsdStage as well as the memory it consumes.

Consumers can take advantage of the shared scenegraph to reduce the amount of processing they need to do. For example, a consumer that needs to compute the bounding box for all of the Car prims in the scene could compute the bounding box for the shared scenegraph just once and reuse that result instead of redoing the same computation for each one. To allow the scenegraph to be shared in this way, instancing restricts the ability to override prims and properties in the prototype prim on a per-instance basis. In this example, consumers would not be able to add, remove, or override prims beneath any of the Car prims in the scene. With this restriction, instancing is able to provide higher degrees of scalability; the benefits become more and more significant as the number of Car prims increases, or as the number of prims beneath the referenced Car model grows.

Explicit Instances, Implicit Prototypes

Prims that share parts of the scenegraph through instancing are called "instance" prims. Each of these instance prims are associated with a "prototype" prim that serves as the root of the shared scenegraph. A single prototype prim may have many associated instances, all of which share the same scenegraph.

Users do not explicitly set up the network of prototype and instance prims. Instead, users mark the prims they want to be instanced with metadata that tags them as "instanceable." UsdStage will analyze these prims to determine which have scenegraph in common based on their composition structure, then dynamically create prototype prims for each group of compatible instances. This "explicit instance, implicit prototype" scheme provides a simple and flexible way for adding instanced assets to scenes. See Making Prims Instanceable for information on how to tag prims for instancing.

Uninstanced vs. Instanced Scene Description
### ParkingLot.usd
#usda 1.0
def "ParkingLot"
{
def "Car_1" (references = @./Car.usd@</Car>)
{
}
def "Car_2" (references = @./Car.usd@</Car>)
{
}
# ...
def "Car_n" (references = @./Car.usd@</Van>)
{
}
}
### ParkingLot.usd
#usda 1.0
def "ParkingLot"
{
def "Car_1" (
instanceable = true
references = @./Car.usd@</Car>
)
{
}
def "Car_2" (
instanceable = true
references = @./Car.usd@</Car>
)
{
}
# ...
def "Car_n" (
instanceable = true
references = @./Car.usd@</Car>
)
{
}
}

This is a simple example of what the scene description in a .usd file might look like for the scenegraph above. In this case, UsdStage will recognize that all of the instanceable Car prims reference the same Car model and will have the same scenegraph hierarchy. UsdStage will then generate a prototype prim /__Prototype_1 to contain this common hierarchy and associate the Car instances with this prototype.

Prototype prims do not exist in scene description – they are generated and managed internally by UsdStage. This allows UsdStage to create and remove prototypes as needed in response to scene description changes. For example, if some of the Car prims in ParkingLot.usd were changed to reference different assets, UsdStage would generate new prototype prims as needed. See Finding and Traversing Prototypes for information on how to use prototype prims.

Warning
Because prototype prims are dynamically generated, the name of a prototype prim associated with an instance is not stable and may vary from run-to-run. Consumers should not save or hard-code the paths to prims in prototypes, but can use the API described below if they need to determine an instance's prototype at runtime.

Working with Instancing

This section goes into more detail about instance and prototype prims and the API for working with them. The following example will be used throughout:

Example Scene Description and Scenegraph
### ParkingLot.usd
#usda 1.0
def "ParkingLot"
{
def "Car_1" (
instanceable = true
references = @./Car.usd@</Car>
)
{
color3f color = (1, 0, 0)
}
def "Car_2" (
instanceable = true
references = @./Car.usd@</Car>
)
{
color3f color = (0, 1, 0)
}
def "Car_3" (
references = @./Car.usd@</Car>
)
{
color3f color = (0, 0, 1)
}
}
### Car.usd
#usda 1.0
def "Car"
{
color3f color = (0, 0, 0)
def Mesh "Body"
{
color3f color = (0, 0, 0)
}
def Mesh "Door"
{
}
}
Instancing_Example.png

Making Prims Instanceable

UsdStage can only create prototype prims for portions of the scenegraph that are brought into a scene via a composition arc. Because of this, a prim must use at least one composition arc in order to be eligible for instancing.

Prims may be marked as instanceable by using UsdPrim::SetInstanceable or SdfPrimSpec::SetInstanceable. If no value is explicitly authored, this value defaults to false.

The "instanceable" metadata is composed like all other metadata in USD, so it can be authored on different layers and have its value overridden by stronger layers. For example, a user could author their "instanceable" metadata in a session layer to see the effects of enabling (or disabling) instancing on prims in a scene without editing that scene directly.

Classifying Prims with Instancing

Consumers can check if a UsdPrim is an instance with an associated prototype using the following API.

Function Purpose Example
UsdPrim::IsInstance Check if prim is an instance. Returns true for Car_1 and Car_2 since they are instances, but returns false for Car_3.

Finding and Traversing Prototypes

A prototype prim is a special UsdPrim whose sole purpose is to serve as the parent for the scenegraph shared by its associated instance prims. The following API can be used to retrieve prototype prims or determine whether a prim is part of a shared prototype.

Function Purpose Example
UsdStage::GetPrototypes Return a list of all prototype prims on the stage Returns the prim /__Prototype_1
UsdPrim::GetPrototype If prim is an instance, get the corresponding prototype prim. Returns prim /__Prototype_1 for Car_1 or Car_2, an invalid UsdPrim for all other prims, including Car_3.
UsdPrim::IsPrototype Check if this prim is a prototype prim. Returns true for prim /__Prototype_1, false for all other prims.
UsdPrim::IsInPrototype Check if this prim is in a subtree rooted at a prototype prim. Returns true for prims /__Prototype_1, /__Prototype_1/Body, and /__Prototype_1/Door, false for all other prims.

Prototype prims do not have metadata or properties, only children prims. Prototype prims have root prim paths (e.g., /__Prototype_1) that can be used with UsdStage::GetPrimAtPath. However, they are considered siblings to the pseudo-root in the scenegraph. Because of this, they will not be returned when using UsdStage::Traverse or when calling UsdPrim::GetChildren on the pseudo-root. Consumers can access prototype prims via UsdPrim::GetPrototype or UsdStage::GetPrototypes instead.

Warning
Because prototype prims are dynamically generated, the name of a prototype prim associated with an instance is not stable and may vary from run to run. Consumers should not save or hard-code the paths to prims in prototypes, but can use the API described below if they need to determine an instance's prototype at runtime.
1 >>> stage = Usd.Stage.Open('ParkingLot.usd')
2 >>> stage.GetPrototypes()
3 [Usd.Prim(</__Prototype_1>)]
4 
5 # Prototype prims are not included in stage traversals. Note how the below
6 # functions do not include the prototype prims above.
7 >>> list(stage.Traverse())
8 [Usd.Prim(</ParkingLot>), Usd.Prim(</ParkingLot/Car_1>), Usd.Prim(</ParkingLot/Car_2>), Usd.Prim(</ParkingLot/Car_3>), Usd.Prim(</ParkingLot/Car_3/Body>), Usd.Prim(</ParkingLot/Car_3/Door>)]
9 
10 >>> stage.GetPseudoRoot().GetChildren()
11 [Usd.Prim(</ParkingLot>)]

UsdStage arranges the scenegraph so that instance prims do not have any descendant prims; these prims are instead encapsulated beneath prototype prims. Consumers can use UsdPrim::GetPrototype to get an instance's prototype prim, then traverse its children the same way as with other prims, using UsdPrim::GetChildren, UsdPrimRange or similar facilities.

1 >>> stage = Usd.Stage.Open('ParkingLot.usd')
2 
3 # Car_1 doesn't make any child prims available since it's an instance prim;
4 # its children in the scenegraph are parented beneath the prototype prim.
5 >>> car_1 = stage.GetPrimAtPath('/ParkingLot/Car_1')
6 >>> car_1.IsInstance()
7 True
8 >>> car_1.GetChildren()
9 []
10 
11 # Consumers can query the instance's prototype for its child prims.
12 >>> car_1.GetPrototype().GetChildren()
13 [Usd.Prim(</__Prototype_1/Body>), Usd.Prim(</__Prototype_1/Door>)]
14 >>> list(Usd.PrimRange(car_1.GetPrototype()))
15 [Usd.Prim(</__Prototype_1>), Usd.Prim(</__Prototype_1/Body>), Usd.Prim(</__Prototype_1/Door>)]
16 
17 # Car_3's child prims can be accessed directly since it's not an instance prim.
18 >>> car_3 = stage.GetPrimAtPath('/ParkingLot/Car_3')
19 >>> car_3.IsInstance()
20 False
21 >>> car_3.GetChildren()
22 [Usd.Prim(</ParkingLot/Car_3/Body>), Usd.Prim(</ParkingLot/Car_3/Door>)]

Traversing Into Instances with Instance Proxies

An instance proxy is a UsdPrim that represents a descendant prim beneath an instance, even though no such prim actually exists in the scenegraph. Instance proxies allow consumers to work with the scenegraph as if instancing were not being used, while retaining the load time and memory usage benefits of instancing.

An instance proxy prim behaves the same as the corresponding prim in the prototype. However, an instance proxy retains context about the instance being traversed so they appear to be a prim beneath an instance: its path is a descendant of the instance being traversed instead of a descendant of a prototype prim. Consumers should generally be able to use instance proxies anywhere a UsdPrim is used. The primary exception is that editing scene description via instance proxies and their properties is not allowed.

Calling UsdStage::GetPrimAtPath with a path to a descendant of an instance prim will return an instance proxy if a corresponding prim exists in that instance's prototype. By default, the various prim traversal facilities like UsdPrim::GetChildren and UsdPrimRange do not return instance proxies. UsdTraverseInstanceProxies can be used to enable this functionality. By default, this uses the same UsdPrimDefaultPredicate used by other traversal functions, but it can be combined with other Usd_PrimFlags predicates; for example, combining it with UsdPrimAllPrimsPredicate would yield the same filtering behavior as UsdPrim::GetAllChildren, but with instance proxies enabled.

1 >>> stage = Usd.Stage.Open('ParkingLot.usd')
2 
3 # Car_1 doesn't make any child prims available by default since it's an
4 # instance prim.
5 >>> car_1 = stage.GetPrimAtPath('/ParkingLot/Car_1')
6 >>> car_1.IsInstance()
7 True
8 >>> car_1.GetChildren()
9 []
10 
11 # Use Usd.TraverseInstanceProxies to enable instance proxies with the same
12 # filtering that UsdPrim.GetChildren would do.
13 >>> car_1.GetFilteredChildren(Usd.TraverseInstanceProxies())
14 [Usd.Prim(</ParkingLot/Car_1/Body>), Usd.Prim(</ParkingLot/Car_1/Door>)]
15 
16 # Calling Usd.Stage.GetPrimAtPath with the path of a prim beneath an
17 # instance will also return an instance proxy.
18 >>> car_1_body = stage.GetPrimAtPath('/ParkingLot/Car_1/Body')
19 >>> car_1_body
20 Usd.Prim(</ParkingLot/Car_1/Body>)
21 >>> car_1_body.IsInstanceProxy()
22 True
23 
24 # Unlike prims in prototypes, you can walk up an instance proxy's parent prims
25 # to find its owning instance.
26 >>> car_1_body.GetParent()
27 Usd.Prim(</ParkingLot/Car_1>)
28 
29 # From an instance proxy, you can retrieve the corresponding prim in the
30 # instance's prototype.
31 >>> car_1_body.GetPrimInPrototype()
32 Usd.Prim(</__Prototype_1/Body>)
33 
34 # Instance proxies can be used for read-only operations anywhere a Usd.Prim
35 # is used.
36 >>> img = UsdGeom.Imageable(car_1_body)
37 >>> img.ComputeLocalToWorldTransform(Usd.TimeCode.Default())
38 Gf.Matrix4d(...)
39 
40 >>> xfCache = UsdGeom.XformCache()
41 >>> xfCache.GetLocalToWorldTransform(car_1_body)
42 Gf.Matrix4d(...)

Editing Instances and Prototypes

Properties and metadata (e.g., variant selections) on instance prims can be edited and overridden like any other prim. However, properties and metadata on descendant prims beneath instance prims cannot be overridden. Since prototype prims are dynamically generated and do not exist in scene description, overriding properties and metadata on prototypes or prims in prototypes is also not allowed. Attempting to make these restricted changes via the USD API will result in coding errors. Any existing overrides in scene description will be silently ignored.

1 >>> stage = Usd.Stage.Open('ParkingLot.usd')
2 
3 # Properties on an instance can be overridden as expected.
4 >>> car1Color = stage.GetPrimAtPath('/ParkingLot/Car_1').GetAttribute('color')
5 >>> car1Color.Get()
6 Gf.Vec3f(1.0, 0.0, 0.0)
7 >>> car1Color.Set((1.0, 1.0, 1.0))
8 True
9 >>> car1Color.Get()
10 Gf.Vec3f(1.0, 1.0, 1.0)
11 
12 # Properties on prims in prototypes cannot be overridden. This is also the case
13 # when accessing the property via an instance proxy.
14 >>> prototype = stage.GetPrimAtPath('/ParkingLot/Car_1').GetPrototype()
15 >>> prototypeBodyColor = prototype.GetChild('Body').GetAttribute('color')
16 >>> prototypeBodyColor.Get()
17 Gf.Vec3f(0.0, 0.0, 0.0)
18 >>> prototypeBodyColor.Set((1.0, 1.0, 1.0))
19 pixar.Tf.ErrorException
20 
21 >>> instanceProxyBody = stage.GetPrimAtPath('/ParkingLot/Car_1/Body')
22 >>> instanceProxyBodyColor = instanceProxyBody.GetAttribute('color')
23 >>> instanceProxyBodyColor.Get()
24 Gf.Vec3f(0.0, 0.0, 0.0)
25 >>> instanceProxyBodyColor.Set((1.0, 1.0, 1.0))
26 pixar.Tf.ErrorException

These restrictions ensure that the scenegraph in a prototype prim can be shared by all instances. If an instance-specific edit to a prim in a prototype is needed, that instance must be made un-instanceable (see Making Prims Instanceable) so that it will no longer participate in instancing.

Although "editing prototypes" is disallowed, USD's composition arcs can be used to achieve many of the same effects. For example, a consumer could add inherit or specializes arcs to instances, then make edits to the class targeted by those arcs. Those edits would then affect all of the specified instances.

Relationship Targets and Attribute Connections

Relationships and attributes may have authored target and connection paths that point to objects beneath an instance prim. In these cases, the API for retrieving these paths, such as UsdRelationship::GetTargets or UsdAttribute::GetConnections, will not translate them to the corresponding object in the instance's prototype prim. Consumers can use instance proxies to interact with these paths and retrieve the corresponding object in the prototype prim if needed. However, if these functions are called on a UsdRelationship or UsdAttribute that belong to a prim in a prototype, paths that point to objects in instances of that prototype will be returned as paths in that prototype.

Note
The following example demonstrates behavior with relationship targets, but the same behavior holds for attribute connections.
Example Scene Description and Scenegraph with Relationships
### ParkingLot.usd
#usda 1.0
def "ParkingLot"
{
def "Car_1" (
instanceable = true
references = @./Car.usd@</Car>
)
{
}
def "Car_2" (
instanceable = true
references = @./Car.usd@</Car>
)
{
}
def "ShoppingCart"
{
custom rel bodyRel = [
</ParkingLot/Car_1/Body>,
</ParkingLot/Car_2/Body>,
]
}
}
### Car.usd
#usda 1.0
def "Car"
{
def Mesh "Body"
{
custom rel doorRel = </Car/Door>
}
def Mesh "Door"
{
}
}
Relationship_Example.png
1 >>> stage = Usd.Stage.Open('ParkingLot.usd')
2 
3 >>> cart = stage.GetPrimAtPath('/ParkingLot/ShoppingCart')
4 >>> cart_bodyRel = cart.GetRelationship('bodyRel')
5 
6 # 'bodyRel' is a relationship on a prim that is not being instanced. Its
7 # targets point to prims beneath Car_1 and Car_2, which are both instances
8 # in this scene.
9 >>> cart_bodyRel.GetTargets()
10 [Sdf.Path('/ParkingLot/Car_1/Body'), Sdf.Path('/ParkingLot/Car_2/Body')]
11 
12 # Calling Usd.Stage.GetPrimAtPath with these targets will return instance
13 # proxies, so consumers can easily interact with these targets even though
14 # they are prims beneath instances.
15 >>> [stage.GetPrimAtPath(p) for p in cart_bodyRel.GetTargets()]
16 [Usd.Prim(</ParkingLot/Car_1/Body>), Usd.Prim(</ParkingLot/Car_2/Body>)]
17 
18 # 'doorRel' is a relationship on a prim beneath an instance. If accessed
19 # through the instance's prototype, the target paths returned will be returned
20 # as paths in the instance's prototype.
21 >>> prototype = stage.GetPrimAtPath('/ParkingLot/Car_1').GetPrototype()
22 >>> prototype_doorRel = prototype.GetChild('Body').GetRelationship('doorRel')
23 
24 >>> prototype_doorRel.GetPath()
25 Sdf.Path('/__Prototype_1/Body.doorRel')
26 
27 >>> prototype_doorRel.GetTargets()
28 [Sdf.Path('/__Prototype_1/Door')]
29 
30 # If the relationship is accessed through instance proxies, the relationship
31 # target API behaves as though instancing is not present.
32 >>> instanceProxy = stage.GetPrimAtPath('/ParkingLot/Car_1/Body')
33 >>> instanceProxy_doorRel = instanceProxy.GetRelationship('doorRel')
34 
35 >>> instanceProxy_doorRel.GetPath()
36 Sdf.Path('/ParkingLot/Car_1/Body.doorRel')
37 
38 >>> instanceProxy_doorRel.GetTargets()
39 [Sdf.Path('/ParkingLot/Car_1/Door')]
40 
41 >>> instanceProxy_2 = stage.GetPrimAtPath('/ParkingLot/Car_2/Body')
42 >>> instanceProxy_2_doorRel = instanceProxy_2.GetRelationship('doorRel')
43 
44 >>> instanceProxy_2_doorRel.GetPath()
45 Sdf.Path('/ParkingLot/Car_2/Body.doorRel')
46 
47 >>> instanceProxy_2_doorRel.GetTargets()
48 [Sdf.Path('/ParkingLot/Car_2/Door')]

Common Issues

Instancing Single Prims

Since instancing shares the scenegraph hierarchy beneath instance prims, instancing a single prim that has no descendants provides no benefits. In the case that instancing a single prim is desired (e.g., instancing a mesh), that prim should be made a descendant of another prim that is referenced into the scene, as shown below:

### Model.usd
#usda 1.0
over "InstancedMeshSource"
{
def Mesh "Mesh"
{
# ...
}
}
def "Model"
{
def "Mesh_1" (
instanceable = true
references = </InstancedMesh>
)
{
}
def "Mesh_2" (
instanceable = true
references = </InstancedMesh>
)
{
}
# ...
}

Advanced Topics

How USD Generates Prototype Prims

To determine the set of prototype prims needed, UsdStage analyzes each prim marked as instanceable and computes an "instancing key" that consists of:

  • The direct composition arcs on the prim that pull in scene description, in order from strongest to weakest
  • The variant selections applied to the prim
  • The value clips that affect the prim
  • The UsdStageLoadRules from the stage that apply to the prim and its descendants
  • The UsdStagePopulationMask from the stage that applies to the prim and its descendants

These elements, along with the restriction on per-instance overrides of descendant prims and their properties, guarantee that every prim with the same key will have the same scenegraph hierarchy beneath them, and that computing any values or metadata will return the same result.

UsdStage groups the instanceable prims by their key and generates a prototype prim for each group. During this process, UsdStage will select one instanceable prim per group to serve as the source for the corresponding prototype prim. This selection is non-deterministic; this allows for multi-threaded discovery of instances with minimal locking, and since all of the prims in a group have the same scenegraph hierarchy and values, it doesn't matter which is selected. Enabling USD_INSTANCING TF_DEBUG flag will given some diagnostic information about this process.

UsdStage will update prototypes as instanceable prims are added or removed, or whenever an element that is part of the instancing key changes. For example, if an instance prim's variant selection is changed, UsdStage will recompute its key and either assign that instance to an already-existing prototype if there are existing instances with the same key, or create a new prototype.

Nested Instancing

An instanceable prim may have children that are themselves instanceable. This "nested instancing" allows consumers to build up large aggregate assets from smaller ones and use instancing to share as much of the scenegraph as possible, even between the smaller pieces. For example, after constructing a parking lot asset with many instances of a car model, an asset representing a large superstore could be created that brings in multiple instances of that parking lot.

Example Scene Description and Scenegraph with Nested Instancing
### BuyNLarge.usd
#usda 1.0
def "BuyNLarge"
{
def "ParkingLot_1" (
instanceable = true
references = @./ParkingLot.usd@</ParkingLot>
)
{
}
def "ParkingLot_2" (
instanceable = true
references = @./ParkingLot.usd@</ParkingLot>
)
{
}
# ...
def "ParkingLot_n" (
instanceable = true
references = @./ParkingLot.usd@</ParkingLot>
)
{
}
}
### ParkingLot.usd
#usda 1.0
def "ParkingLot"
{
def "Car_1" (
instanceable = true
references = @./Car.usd@</Car>
)
{
}
def "Car_2" (
instanceable = true
references = @./Car.usd@</Car>
)
{
}
# ...
def "Car_n" (
instanceable = true
references = @./Car.usd@</Car>
)
{
}
}
Nested_Instancing_Example.png

In the above example, USD will generate two prototype prims to accommodate the instanced ParkingLot and Car prims. Even though the Car prims are spread out among the different ParkingLot prims, USD will recognize that they can all share a single prototype prim, since they all have the same composition structure and can share the same scenegraph.

When traversing the scenegraph using prototypes, it is important to note that, in the nested case, prims in prototypes may also be instances and requiring accessing the associated prototypes to continue traversal. In this example, the prototype prim for the ParkingLot prims, /__Prototype_2, contains instance Car prims that are associated with their prototype prim, /__Prototype_1.

When working with instance proxies, nested instancing will be taken into account and resolved as if instancing were not being used on the stage.

Flattening

When flattening a UsdStage into a single layer via its various serialization methods, each prototype prim in the scenegraph will be written out to specially-named root prim. These prims will be referenced by the flattened instance prims that were associated with that prototype.