This document is for a version of USD that is under development. See this page for the current release.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
Hydra 2.0 Getting Started Guide

What is Hydra 2.0

The Hydra imaging system is based on two core abstractions: a scene abstraction and transformation pipeline, and a renderer abstraction and execution pipeline. Its design goal is to decouple scene processing from rendering, and both from the application, enabling each scene, renderer, or application integration to benefit from other integrations in the ecosystem.

Hydra's original scene abstraction API was built around the HdSceneDelegate class. Hydra 2.0 refers to the replacement for that class, called a scene index, along with new Hydra features and components built around the scene index API. New features include the scene index implementation of the usdImaging library and new plugin points for proceduralism. Note that Hydra-enabled applications can take advantage of these new plugin points for proceduralism even if their scene graph integration is based off of the HdSceneDelegate API.

Hydra's rendering abstraction API consists of the HdRenderIndex class, which is the application's interface to renderer state, the HdRenderDelegate class, which abstracts rendering functionality, and the renderSettings and task primitives that configure and launch a render. Applications don't need to change their use of the rendering abstraction API to benefit from Hydra 2.0 features, but the scene index API opens doors to renderers having more control over their update loop, or being able to read unflattened scenes. The extensions to the rendering abstraction API are still evolving, and are mostly out of scope for this document.

The Scene Index API

A scene in the new API is represented by a subclass of the HdSceneIndexBase interface.

TfToken primType;
HdContainerDataSourceHandle dataSource;
};
public:
virtual HdSceneIndexPrim GetPrim(const SdfPath &primPath) const = 0;
virtual SdfPathVector GetChildPrimPaths(const SdfPath &primPath) const = 0;
protected:
};
Abstract interface to scene data.
Definition: sceneIndex.h:48
virtual SdfPathVector GetChildPrimPaths(const SdfPath &primPath) const =0
Returns the paths of all scene index prims located immediately below primPath.
HD_API void _SendPrimsRemoved(const HdSceneIndexObserver::RemovedPrimEntries &entries)
Notify attached observers of prims removed from the scene.
HD_API void _SendPrimsRenamed(const HdSceneIndexObserver::RenamedPrimEntries &entries)
Notify attached observers of prims (and their descendents) which have been renamed or reparented.
HD_API void _SendPrimsAdded(const HdSceneIndexObserver::AddedPrimEntries &entries)
Notify attached observers of prims added to the scene.
HD_API void _SendPrimsDirtied(const HdSceneIndexObserver::DirtiedPrimEntries &entries)
Notify attached observers of datasource invalidations from the scene.
virtual HdSceneIndexPrim GetPrim(const SdfPath &primPath) const =0
Returns a pair of (prim type, datasource) for the object at primPath.
A path value used to locate objects in layers or scenegraphs.
Definition: path.h:274
Token for efficient comparison, assignment, and hashing of known strings.
Definition: token.h:71
Small struct representing a 'prim' in the Hydra scene index.
Definition: sceneIndex.h:35

A scene index is responsible for:

  1. Sending notifications to the renderer or other listeners on population, invalidation, depopulation, etc.
  2. Providing prim data by request.
  3. Providing scene topology data by request, so callers can walk the scene.

The USD scene index, for example, implements a Populate() call by traversing the scene and generating a PrimsAdded notification. It implements change notification in terms of PrimsDirtied. It implements GetChildPrimPaths() by querying the USD prim hierarchy. Finally, it implements GetPrim() by returning the appropriate data for the prim in question (transform, geometric data, etc. as appropriate).

Consumers (such as renderers, the Hydra scene browser, etc) that want to receive notifications from a scene index can subclass the HdSceneIndexObserver interface.

public:
struct AddedPrimEntry {
// NOTE: if primPath has already been added, this notice acts as a resync;
// observers should re-fetch all data and note the potentially new prim type.
SdfPath primPath;
TfToken primType;
};
virtual void PrimsAdded(const HdSceneIndexBase &sender,
const AddedPrimEntries &entries) = 0;
struct RemovedPrimEntry {
// NOTE: primPath here is a subtree root; this notice means that primPath and all
// descendants have been removed.
SdfPath primPath;
};
virtual void PrimsRemoved(const HdSceneIndexBase &sender,
const RemovedPrimEntries &entries) = 0;
struct DirtiedPrimEntry {
SdfPath primPath;
// NOTE: locators are interpreted hierarchically; i.e. if "primvars" is in
// dirtyLocators, the value of "primvars/points" is also considered dirty.
HdDataSourceLocatorSet dirtyLocators;
};
virtual void PrimsDirtied(const HdSceneIndexBase &sender,
const DirtiedPrimEntries &entries) = 0;
struct RenamedPrimEntry {
// NOTE: oldPrimPath and newPrimPath here are subtree roots; this notice means that
// oldPrimPath and all descendants have been re-rooted to the location newPrimPath.
SdfPath oldPrimPath;
SdfPath newPrimPath;
};
virtual void PrimsRenamed(const HdSceneIndexBase &sender,
const RenamedPrimEntries &entries) = 0;
};
Represents a set of data source locators closed under descendancy.
Observer of scene data.
virtual HD_API void PrimsDirtied(const HdSceneIndexBase &sender, const DirtiedPrimEntries &entries)=0
A notification indicating prim datasources have been invalidated.
virtual HD_API void PrimsAdded(const HdSceneIndexBase &sender, const AddedPrimEntries &entries)=0
A notification indicating prims have been added to the scene.
virtual HD_API void PrimsRenamed(const HdSceneIndexBase &sender, const RenamedPrimEntries &entries)=0
A notification indicating prims (and their descendants) have been renamed or reparented.
virtual HD_API void PrimsRemoved(const HdSceneIndexBase &sender, const RemovedPrimEntries &entries)=0
A notification indicating prims have been removed from the scene.

PrimsAdded takes individual prim (path, type) tuples. A PrimsAdded notice can be sent for a prim already in the scene, in which case it should be interpreted as a resync; if the notice has a new type, the prim type should be updated, and all prim data should be considered dirty (as if there were a PrimsDirtied message invalidating the root locator). PrimsAdded conceptually maps to the HdRenderIndex Insert(R,S,B)prim() calls.

PrimsRemoved takes a subtree path, rather than a prim path. Sparse removals can be accomplished by resyncing prims to the empty type. PrimsRemoved conceptually maps to the HdRenderIndex Remove(R,S,B)prim() calls.

PrimsRenamed offers a way to rename a subtree, updating all prims under the old subtree to have paths with the old subtree prefix swapped for the new one. This is semantically equivalent to removing the old subtree and adding it in the new location, but provided as a separate notice for optimization purposes since frequently a rename can be handled by updating map keys at much less cost. Note that HdRenderIndex doesn't have any concept of renaming.

PrimsDirtied takes an individual prim path, and a set of locators to datasources. Datasources are the attribute model in the scene index API, and are grouped into a multi-level hierarchy; the locator is a path into that hierarchy. A dirty locator on a prim means that attribute and all child attributes are invalidated. Any caches containing a datasource handle for that attribute or any child attributes need to be cleared and re-fetched. PrimsDirtied conceptually maps to the HdChangeTracker Mark(R,S,B)primDirty() calls.

An important invariant of the system is that the list of prim paths produced by evaluating PrimsAdded and PrimsRemoved messages should match the list of prim paths generated by traversing the scene from the root, using GetChildPrimPaths(). Any prims not reachable by traversal should return an empty prim, with an empty type and a null root datasource.

Prim Data

Prim data is represented by subclasses of HdDataSourceBase or its specializations: HdContainerDataSource, HdVectorDataSource, or HdSampledDataSource.

public:
virtual TfTokenVector GetNames() = 0;
virtual HdDataSourceBaseHandle Get(const TfToken &name) = 0;
};
public:
virtual size_t GetNumElements() = 0;
virtual HdDataSourceBaseHandle GetElement(size_t element) = 0;
};
public:
virtual VtValue GetValue(Time shutterOffset) = 0;
Time startTime, Time endTime, std::vector<Time> * outSampleTimes) = 0;
};
A datasource representing structured (named, hierarchical) data, for example a geometric primitive or...
Definition: dataSource.h:99
virtual TfTokenVector GetNames()=0
Returns the list of names for which Get(...) is expected to return a non-null value.
virtual HdDataSourceBaseHandle Get(const TfToken &name)=0
Returns the child datasource of the given name.
A datasource representing time-sampled values.
Definition: dataSource.h:153
virtual bool GetContributingSampleTimesForInterval(Time startTime, Time endTime, std::vector< Time > *outSampleTimes)=0
Given a shutter window of interest (startTime and endTime relative to the current frame),...
virtual VtValue GetValue(Time shutterOffset)=0
Returns the value of this data source at frame-relative time shutterOffset.
A datasource representing indexed data.
Definition: dataSource.h:131
virtual size_t GetNumElements()=0
Return the number of elements in this datasource.
virtual HdDataSourceBaseHandle GetElement(size_t element)=0
Return the element at position element in this datasource.
Provides a container which may hold any type, and provides introspection and iteration over array typ...
Definition: value.h:147
std::vector< TfToken > TfTokenVector
Convenience types.
Definition: token.h:440

The basic value datatype of this API is HdSampledDataSource, whose GetValue API retrieves or computes type-erased (i.e. VtValue) data on demand, as with the scene delegate. For attributes with a known type, the subclass HdTypedSampledDataSource<T> can be used. Note that, unlike with the scene delegate, all data access is relative to a shutter offset instead of being part of a separate API.

For a consumer computing the value of a sampled datasource over a time interval (such as a renderer implementing motion blur), GetContributingSampleTimesForInterval() is responsible for providing a set of notable sample times to reconstruct the signal. For cached data, this can return the times where samples are defined, or for analytical motion blur this can return time samples corresponding to a reasonable linearization. If these time samples are used to populate an HdTimeSampleArray, Hydra can linearly re-sample the signal at arbitrary points as required by a renderer. If GetContributingSampleTimesForInterval() returns false, this indicates an attribute that's constant over the shutter window, and the caller should just use GetValue(0).

The intent of this API is for consumers to call GetValue() on either shutter offsets returned by GetContributingSampleTimesForInterval(), or with shutterOffset equal to 0. Defensive code can interpolate or extrapolate other values of shutterOffset, but behavior might be undefined.

Container datasources and vector datasources are both aggregates, and can be used to build up nested structured data. A mesh prim might be represented by a container with a child named "mesh", containing topology and pipeline state, another named "primvars", and others as well, such as transform, visibility, etc.

Here, the light diamonds represent containers and the dark diamonds represent values. Note that containers can have children that are also containers.

The container datasource API introduces GetNames() as a way for downstream code to enumerate which attributes are available. The use of container datasources instead of structs allows for flexibility of representation of similar concepts (e.g. a mesh, a mesh topology) between different renderers which might want to overlay different data on top of the standard points/index buffers.

Named children of a container are retrieved with Get() calls. It's expected that Get() will return something for all of the names defined by GetNames(), and will return null otherwise (but be safe to call). This is useful, for example, if a consumer only wants to check for certain named primvars on a geometry prim, since primvar enumeration at the USD level can sometimes be expensive but it's also lazily deferred to the GetNames() call.

HdDataSourceLocator is the attribute addressing scheme used in DirtiedPrimEntries. Since container datasources can be nested, the locator needs path semantics, where each path entry represents the next container key to query to ultimately end up at the correct datasource. A locator is relative to a root container, which is usually the top-level container datasource representing prim data.

public:
HdDataSourceLocator(size_t count, const TfToken *tokens);
bool HasPrefix(const HdDataSourceLocator &prefix) const;
// ... plus, other manipulation & testing code.
};
Represents an object that can identify the location of a data source.
HD_API HdDataSourceLocator()
Creates an empty locator.
HD_API bool HasPrefix(const HdDataSourceLocator &prefix) const
Returns true if and only if this data source locator has prefix as a prefix.

For example, you might expect mesh orientation at: HdDataSourceLocator("mesh", "meshTopology", "orientation");

Locators can identify individual attributes to invalidate, which allows for much more specificity than the HdDirtyBits API. An originating scene can invalidate "primvars/displayColor" and "primvars/displayOpacity" separately. However, to capture some of the conciseness of dirty bits, Hydra will interpret the locators in a DirtiedPrimEntry hierarchically, meaning if "primvars" is dirty on a prim, this implies that "primvars/displayColor" and "primvars/displayOpacity" are also dirty.

Hydra Schemas

Datasource aggregations are inherently unstructured, but to transport data from different scenes to different renderers Hydra needs a consensus data format. Rather than encode this format in C++ structs, the scene index API takes a page from USD by separating data storage from schema interpretation. An HdSchema subclass is applied to an HdContainerDataSource and represents what data Hydra expects to find on a container. Any scene index or render delegate that wants to be compatible with the Hydra ecosystem should provide or consume data accordingly. For example, HdMeshTopologySchema corresponds to the HdMeshTopology C++ struct:

class HdMeshTopologySchema : public HdSchema {
public:
HdMeshTopologySchema(HdContainerDataSourceHandle container);
HdIntArrayDataSourceHandle GetFaceVertexCounts();
HdIntArrayDataSourceHandle GetFaceVertexIndices();
HdIntArrayDataSourceHandle GetHoleIndices();
HdTokenDataSourceHandle GetOrientation();
};
Schema classes represent a structured view of the inherently unstructured container data source passe...
Definition: schema.h:26

Importantly, since the data storage is a container data source, it can transport data not in the schema. If a renderer wants to extend Hydra schemas for renderer-specific data, it can do so, in the above example by sub-classing HdMeshTopologySchema. The schema is just a facade representing the commonly understood structure of a Hydra scene, and all data access is still done through the input container.

See Hydra Prim Schemas for a list of Hydra prim schemas.

Scene Index Filters

One last important API concept arises from the architecture above. It's fairly straightforward to implement a scene index by referring to an input scene index for data access, but then selectively overriding the input scene data. This can be thought of as the lazy programming version of running a transformation on the scene at load time. We call this pattern a scene index filter, and provide a base class for this behavior in HdSingleInputFilteringSceneIndexBase.

public:
// ... from HdSceneIndexBase
virtual HdSceneIndexPrim GetPrim(const SdfPath &primPath) const = 0;
virtual SdfPathVector GetChildPrimPaths(const SdfPath &primPath) const = 0;
protected:
const HdSceneIndexBaseRefPtr &inputSceneIndex);
virtual void _PrimsAdded(const HdSceneIndexBase &sender,
virtual void _PrimsRemoved(const HdSceneIndexBase &sender,
virtual void _PrimsDirtied(const HdSceneIndexBase &sender,
virtual void _PrimsRenamed(const HdSceneIndexBase &sender,
private:
const HdSceneIndexBaseRefPtr &_GetInputSceneIndex() const;
};
An abstract base class for a filtering scene index that observes a single input scene index.
const HdSceneIndexBaseRefPtr & _GetInputSceneIndex() const
Returns the input scene.

This class will register itself as an observer of inputSceneIndex and registers handlers for PrimsAdded(), etc. notices. Note that it's expected to forward these notices appropriately to its own observers. GetPrim() can be implemented with the help of _GetInputSceneIndex()->GetPrim().

These scene index filters are a very powerful architectural tool for decoupling different kinds of scene transformations in a scene or renderer pipeline. See Working With Scene Index Filters for additional illustrative examples of scene index filters.

Hydra provides a way to visualize scene data and filters using the Hydra Scene Browser, available in tools like usdview. You can also add this to your Hydra-enabled application.

Transporting Scene Data with Scene Indexes

As a simple example, let's write a scene index that provides a scene with a single quad.

class QuadSceneIndex : public HdSceneIndexBase {
public:
void Populate(bool populate) {
if (populate && !_isPopulated) {
_SendPrimsAdded({{SdfPath("/Quad"), HdPrimTypeTokens->mesh}});
} else if (!populate && _isPopulated) {
_SendPrimsRemoved({{SdfPath("/Quad")}});
}
_isPopulated = populate;
}
virtual HdSceneIndexPrim GetPrim(const SdfPath &primPath) const {
static HdSceneIndexPrim prim = {
HdPrimTypeTokens->mesh,
HdRetainedContainerDataSource::New(
HdMeshSchemaTokens->mesh,
HdMeshSchema::BuildRetained(
/* topology = */ HdMeshTopologySchema::BuildRetained(
/* faceVertexCounts = */ HdIntArrayDataSource::New({4}),
/* faceVertexIndices = */ HdIntArrayDataSource::New({0,1,2,3}),
/* holeIndices = */ nullptr,
/* orientation = */
HdTokenDataSource::New(HdTokens->rightHanded)),
/* subdivisionScheme = */ HdTokenDataSource::New(HdTokens->none),
/* subdivisionTags = */ nullptr,
/* geomSubsets = */ nullptr,
/* doubleSided = */ HdBoolDataSource::New(true)),
HdPrimvarsSchemaTokens->primvars,
HdRetainedContainerDataSource::New(
HdTokens->points,
HdPrimvarSchema::BuildRetained(
/* primvarValue = */ HdVec3fArrayDataSource::New(
{{-1,-1,0},{1,-1,0},{1,1,0},{-1,1,0}}),
/* indexedPrimvarValue = */ nullptr,
/* indices = */ nullptr,
/* interpolation = */ HdTokenDataSource::New(HdTokens->vertex),
/* role = */
HdTokenDataSource::New(HdPrimvarSchemaTokens->point))),
HdPurposeSchemaTokens->purpose,
HdPurposeSchema::BuildRetained(
/* purpose = */
HdTokenDataSource::New(HdRenderTagTokens->geometry)),
HdVisibilitySchemaTokens->visibility,
HdVisibilitySchema::BuildRetained(
/* visibility = */ HdBoolDataSource::New(true)),
HdXformSchemaTokens->xform,
HdXformSchema::BuildRetained(
/* xform = */ HdMatrixDataSource::New(GfMatrix4d(1)),
/* resetXformStack = */ HdBoolDataSource::New(false)),
HdExtentSchemaTokens->extent,
HdExtentSchema::BuildRetained(
/* min = */ HdVec3dDataSource::New(GfVec3d(-1,-1,0)),
/* max = */ HdVec3dDataSource::New(GfVec3d(1,1,0)))
)
};
if (primPath == SdfPath("/Quad")) {
return prim;
} else {
return { TfToken(), nullptr };
}
}
virtual SdfPathVector GetChildPrimPaths(const SdfPath &primPath) {
if (primPath == SdfPath::AbsoluteRootPath()) {
return { SdfPath("/Quad"); }
} else {
return {};
}
}
private:
bool _isPopulated;
};
Stores a 4x4 matrix of double elements.
Definition: matrix4d.h:71
Basic type for a vector of 3 double components.
Definition: vec3d.h:46
static SDF_API const SdfPath & AbsoluteRootPath()
The absolute path representing the top of the namespace hierarchy.

You can insert this scene (as a unit) from a render index by calling HdRenderIndex::InsertSceneIndex(), and remove the scene using HdRenderIndex::RemoveSceneIndex().

The scenePathPrefix is the same as the scene delegate ID concept: if your app-native scene has an embedded USD scene at /Path/to/USD, for example, you can set scenePathPrefix = "/Path/to/USD" to re-root the USD data at the new prefix.

Under the hood, InsertSceneIndex() works by using a filtering scene index to re-root the scene, and passes the re-rooted scene to HdMergingSceneIndex. This last scene index takes multiple input scenes and composes them at a prim and attribute level, meaning a single prim can have attribute data from multiple input scenes. Note also that, unlike with scene delegates, a single scene index can be inserted into multiple render indexes without any issues.

A more sophisticated scene data implementation with the scene index API would be expected to implement GetChildPrimPaths() in terms of native scene traversal, and implement GetPrim() by conforming native scene data to the Hydra prim schemas as necessary. We've found it very helpful to write an originating scene index that's as simple as possible, and represent complicated scene transformations with filters. As an example of this, UsdImagingStageSceneIndex tries to only handle prim traversal, type lookup, and attribute access. Various scene index filters overlay application state or implement complicated features like instancing resolution:

The flow for UsdImagingStageSceneIndex with these filters looks roughly like this, as of November 2023:

The UsdImaging library provides the method UsdImagingCreateSceneIndices() to create a UsdImagingStageSceneIndex and all of its associated filters, and this is the recommended way to create a UsdImagingStageSceneIndex with the correct resolution steps across different USD releases.

Working With Scene Index Filters

For a basic syntactical look at how scene index filters work, the following simple but illustrative examples can be used.

A filter that filters out all cubes from the scene:

class UnboxingSceneIndexFilter : public HdSingleInputFilteringSceneIndexBase {
public:
HdSceneIndexBaseRefPtr New(const HdSceneIndexBaseReftPtr &inputSceneIndex) {...}
virtual HdSceneIndexPrim GetPrim(const SdfPath &primPath) const {
HdSceneIndexPrim prim = _GetInputSceneIndex()->GetPrim(primPath);
if (prim.primType == HdPrimTypeTokens->cube) {
return { TfToken(), nullptr };
}
return prim;
}
virtual SdfPathVector GetChildPrimPaths(const SdfPath &primPath) const {
return _GetInputSceneIndex->GetChildPrimPaths(primPath);
}
protected:
HdSingleInputFilteringSceneIndexBase(const HdSceneIndexBaseRefPtr &inputSceneIndex) {...}
virtual void _PrimsAdded(const HdSceneIndexBase &sender,
std::copy_if(entries.begin(), entries.end(), std::back_inserter(filtered),
return entry.primType != HdPrimTypeTokens->cube; });
_SendPrimsAdded(filtered);
}
virtual void _PrimsRemoved(const HdSceneIndexBase &sender,
}
virtual void _PrimsDirtied(const HdSceneIndexBase &sender,
}
};
A notice indicating a prim of a given type was added to the scene.

A filter that sets the display color for everything to be green:

class GreeningSceneIndexFilter : public HdSingleInputFilteringSceneIndexBase {
public:
HdSceneIndexBaseRefPtr New(const HdSceneIndexBaseReftPtr &inputSceneIndex) {...}
virtual HdSceneIndexPrim GetPrim(const SdfPath &primPath) const {
HdSceneIndexPrim prim = _GetInputSceneIndex()->GetPrim(primPath);
if (HdPrimTypeIsGprim(prim.primType)) {
HdContainerDataSourceEditor e(prim.dataSource);
e.Set(HdDataSourceLocator(HdPrimvarsSchemaTokens->primvars, HdTokens->displayColor),
HdPrimvarSchema::BuildRetained(
/* primvarValue = */ HdVec3fDataSource::New({0,1.0f,0}),
/* indexedPrimvarValue = */ nullptr,
/* indices = */ nullptr,
/* interpolation = */ HdTokenDataSource::New(HdTokens->constant),
/* role = */ HdTokenDataSource::New(HdPrimvarSchemaTokens->color)));
prim.dataSource = e.Finish();
// Note that you could also do:
#if 0
prim.dataSource = HdOverlayContainerDataSource::New(
HdRetainedContainerDataSource::New(
HdPrimvarsSchemaTokens->primvars,
HdRetainedContainerDataSource::New(
HdTokens->displayColor,
HdPrimvarSchema::BuildRetained(...))),
prim.dataSource);
#endif
}
return prim;
}
virtual SdfPathVector GetChildPrimPaths(const SdfPath &primPath) const {
return _GetInputSceneIndex->GetChildPrimPaths(primPath);
}
protected:
HdSingleInputFilteringSceneIndexBase(const HdSceneIndexBaseRefPtr &inputSceneIndex) {...}
virtual void _PrimsAdded(const HdSceneIndexBase &sender,
_SendPrimsAdded(entries);
}
virtual void _PrimsRemoved(const HdSceneIndexBase &sender,
}
virtual void _PrimsDirtied(const HdSceneIndexBase &sender,
}
};

Note here the use of two utility classes: HdContainerDataSourceEditor and HdOverlayContainerDataSource. These are both useful for doing datasource-level composition, and can recursively merge and combine their inputs (lazily!) as needed.

A measure of caution is required for conditional overrides; due to the PrimsDirtied semantics, if GreeningSceneIndexFilter only overlays data on top of prim.dataSource when _enabled is true, then when the value of _enabled changes GreeningSceneIndexFilter will need to send a dirty message with the root locator, rather than "primvars/displayColor", since the root datasource has changed and we need to notify any listeners (e.g. the flattening scene index) to discard cached copies. A more efficient way to do a conditional override is to unconditionally override the prim datasource, but conditionally override the target datasource, so that only the target datasource needs to be marked dirty.

Another noteworthy utility class is HdSceneIndexPrimView:

// From a notice handler, iterate through the descendants of rootPath
// in the input scene
for (const SdfPath &primPath : HdSceneIndexPrimView(_GetInputSceneIndex(), rootPath))
{
....
}
A range to iterate over all descendants of a given prim (including the prim itself) in a scene index ...

For an example of more sophisticated filtering scene indices, there are some core architectural examples in pxr/imaging/hd:

  • HdDependencyForwardingSceneIndex: allows originating scenes, as well as renderers and other filters, to add data dependencies via the HdDependenciesSchema. This filter will use those dependencies to propagate PrimsDirtied calls. This is especially helpful for managing invalidation across relationships. For an example use case, Storm uses a scene index filter that adds dependencies to meshes from their bound material, so that when the material changes the mesh can re-tesselate. We then run the dependency forwarding scene index to forward those invalidations.
  • HdFlatteningSceneIndex: does top-down attribute flattening. For example, transform concatenation, inherited attribute resolution, etc.
  • HdMergingSceneIndex: merges multiple input scenes into a single output scene.
  • HdNoticeBatchingSceneIndex: captures, holds, and merges scene index notifications on demand.
  • HdPrefixingSceneIndex: re-roots the input scene at a new location.

There are also a number of common feature-based scene index filters in pxr/imaging/hdsi:

These filters can be added to the Hydra pipeline in a few different ways:

Scenegraph-specific Scene Index Filters

Filters that are associated with a specific originating scene graph (like UsdImaging filters that implement USD semantics) can be appended to the originating scene index (e.g. UsdImagingStageSceneIndex) before they are inserted into the render index with InsertSceneIndex(). As mentioned earlier, UsdImagingCreateSceneIndices() can be used to create a UsdImagingStageSceneIndex and all of its associated filters.

Renderer-specific Scene Index Filters

A renderer can define a subclass of HdSceneIndexPlugin, either compiled into the render delegate or in an unrelated library. Upon constructing a render delegate Hydra will instantiate scene index filters linked to that render delegate automatically. The filters use their defined order, and are inserted between the render index's merging scene index and the render delegate.

The following example shows a simple scene index filter plugin:

{
HdSceneIndexPluginRegistry::Define<MyFilterPlugin>();
}
TF_REGISTRY_FUNCTION(HdSceneIndexPlugin)
{
// integer priority
const HdSceneIndexPluginRegistry::InsertionPhase insertionPhase = 1000;
HdSceneIndexPluginRegistry::GetInstance().RegisterSceneIndexForRenderer(
"GL", // ... or "Prman" or etc, or "" for any
MyFilterPlugin,
nullptr,
insertionPhase,
HdSceneIndexPluginRegistry::InsertionOrderAtEnd);
}
class MyFilterPlugin : public HdSceneIndexPlugin
{
public:
MyFilterPlugin();
protected:
HdSceneIndexBaseRefPtr _AppendSceneIndex(
const HdSceneIndexBaseRefPtr &inputScene,
const HdContainerDataSourceHandle &inputArgs) override;
};
HdSceneIndexBaseRefPtr
MyFilterPlugin::_AppendSceneIndex(
const HdSceneIndexBaseRefPtr &inputScene,
const HdContainerDataSourceHandle &inputArgs)
{
return MyFilter::New(inputScene);
}
TfType represents a dynamic runtime type.
Definition: type.h:48
#define TF_REGISTRY_FUNCTION(KEY_TYPE)
Define a function that is called on demand by TfRegistryManager.

The following plugInfo.json would be used for this example plugin:

{
"Plugins": [
{
"Info": {
"Types": {
"MyFilterPlugin" : {
"bases": ["HdSceneIndexPlugin"],
"loadWithRenderer": "GL",
"priority": 0,
"displayName": "My awesome filter scene index"
}
}
},
"LibraryPath": "@PLUG_INFO_LIBRARY_PATH@",
"Name": "myfilter",
"ResourcePath": "@PLUG_INFO_RESOURCE_PATH@",
"Root": "@PLUG_INFO_ROOT@",
"Type": "library"
}
]
}

Application-specific Scene Index Filters

Application-specific transformations can be added at runtime to the same pool used for renderer-specific transformations. This is especially useful for pipeline tools, which might want to segment the scene in ways that aren't appropriate for other applications.

The following example demonstrates how an app might register an application-specific scene index filter at startup:

HdSceneIndexBaseRefPtr
_AppendAppFilterSceneIndexCallback(
void *appState,
const std::string &renderInstanceId,
const HdSceneIndexBaseRefPtr &inputScene,
const HdContainerDataSourceHandle &inputArgs)
{
// Note that if appState were a map from "renderInstanceId" to e.g. a list of
// per-viewport overrides, we could pull them out of appState here when creating the
// hydra instance for each viewport:
// auto foo = static_cast<_StateMap*>(appState)->at(rendererInstanceId);
return AppFilterSceneIndex::New(inputScene);
}
void AppStartup()
{
// integer priority
const HdSceneIndexPluginRegistry::InsertionPhase insertionPhase = 0;
HdSceneIndexPluginRegistry::GetInstance().RegisterSceneIndexForRenderer(
"",
std::bind(_AppendAppFilterSceneIndexCallback, appState, _1, _2, _3),
/* inputArgs = */ nullptr,
insertionPhase,
HdSceneIndexPluginRegistry::InsertionOrderAtStart
);
}

It's important to note that the application and renderer-specific scene index filters can be used even if all of the scene data is coming from a scene delegate. Hydra converts internally between the two representations as needed. See Appendix: Scene Index Emulation Explained for details.

The following shows renderer-specific and application-specific filters participating in a scene index filtering workflow.

The current priority sort algorithm is to run application-specific filters first (in numerical priority order), and then renderer-specific filters (again in numerical priority order).

USD Scene Index Bindings

UsdImagingStageSceneIndex, much like UsdImagingDelegate, will walk a USD stage and dispatch to plugin-based prim adapters that know how to turn a certain USD prim type into a Hydra prim. UsdImagingStageSceneIndex uses the same plugin registry as UsdImagingDelegate to accomplish this. There are two major differences:

  1. The API on the C++ PrimAdapter subclass that UsdImagingStageSceneIndex uses is disjoint.
  2. UsdImagingStageSceneIndex supports adapters for USD API schemas, which can layer data onto the hydra representation of their base type.

The following is example code for an adapter for a "MyUSDPrim" USD prim and "MyUSDAPI" USD schema.

plugInfo.json for MyUSD library:

{
"Types": {
"MyUSDPrimAdapter" : {
"bases": ["UsdImagingPrimAdapter"],
"primTypeName": "MyUSDPrim"
},
"MyUSDAPIAdapter" : {
"bases": ["UsdImagingAPISchemaAdapter"],
"apiSchemaName": "MyUSDAPI"
}
}
}

MyUSDPrimAdapter.cpp example:

{
typedef MyUSDPrimAdapter Adapter;
TfType t = TfType::Define<Adapter, TfType::Bases<Adapter::BaseAdapter> >();
t.SetFactory< UsdImagingPrimAdapterFactory<Adapter> >();
}
class MyUSDPrimAdapter : public UsdImagingPrimAdapter {
public:
typedef UsdImagingPrimAdapter BaseAdapter;
TfTokenVector GetImagingSubprims(UsdPrim const& prim) override;
TfToken GetImagingSubprimType(
UsdPrim const& prim, TfToken const& subprim) override;
HdContainerDataSourceHandle GetImagingSubprimData(
UsdPrim const& prim,
TfToken const& subprim,
const UsdImagingDataSourceStageGlobals &stageGlobals) override;
HdDataSourceLocatorSet InvalidateImagingSubprim(
UsdPrim const& prim,
TfToken const& subprim,
TfTokenVector const& properties,
UsdImagingPropertyInvalidationType invalidationType) override;
};
TF_API void SetFactory(std::unique_ptr< FactoryBase > factory) const
Sets the factory object for this type.
This class is used as a context object with global stage information, that gets passed down to dataso...
Base class for all PrimAdapters.
Definition: primAdapter.h:54
UsdPrim is the sole persistent scenegraph object on a UsdStage, and is the embodiment of a "Prim" as ...
Definition: prim.h:117

MyUSDAPIAdapter.cpp example:

{
typedef MyUSDAPIAdapter Adapter;
TfType t = TfType::Define<Adapter, TfType::Bases<Adapter::BaseAdapter> >();
t.SetFactory< UsdImagingAPISchemaAdapterFactory<Adapter> >();
}
class MyUSDAPIAdapter : public UsdImagingAPISchemaAdapter {
public:
typedef UsdImagingAPISchemaAdapter BaseAdapter;
UsdPrim const& prim,
TfToken const& appliedInstanceName) override;
UsdPrim const& prim,
TfToken const& subprim,
TfToken const& appliedInstanceName) override;
HdContainerDataSourceHandle GetImagingSubprimData(
UsdPrim const& prim,
TfToken const& subprim,
TfToken const& appliedInstanceName,
const UsdImagingDataSourceStageGlobals &stageGlobals) override;
UsdPrim const& prim,
TfToken const& subprim,
TfToken const& appliedInstanceName,
TfTokenVector const& properties,
UsdImagingPropertyInvalidationType invalidationType) override;
};
Base class for all API schema adapters.
virtual USDIMAGING_API HdContainerDataSourceHandle GetImagingSubprimData(UsdPrim const &prim, TfToken const &subprim, TfToken const &appliedInstanceName, const UsdImagingDataSourceStageGlobals &stageGlobals)
Returns an HdContainerDataSourceHandle representing the API schema's contributions to the primary pri...
virtual USDIMAGING_API TfTokenVector GetImagingSubprims(UsdPrim const &prim, TfToken const &appliedInstanceName)
Called to determine whether an API schema defines additional child hydra prims beyond the primary hyd...
virtual USDIMAGING_API TfToken GetImagingSubprimType(UsdPrim const &prim, TfToken const &subprim, TfToken const &appliedInstanceName)
Called to determine whether an API schema specifies the hydra type of a given prim previously defined...
virtual USDIMAGING_API HdDataSourceLocatorSet InvalidateImagingSubprim(UsdPrim const &prim, TfToken const &subprim, TfToken const &appliedInstanceName, TfTokenVector const &properties, UsdImagingPropertyInvalidationType invalidationType)
Given the names of USD properties which have changed, an adapter may provide a HdDataSourceLocatorSet...

USD has a concept of multiple-apply API schemas, where the same API schema can be applied to a prim multiple times with different instance names. An example of this is coordinate systems, where applying UsdShadeCoordSysAPI with instance name "foo" adds a "coordsys:foo:binding" relationship to the prim. In these cases, UsdImagingStageSceneIndex will call into the API schema adapter multiple times, one per instance name. Some API schemas (which aren't multiple-apply) won't have an instance name – in these cases, the instance name will be the empty token. Other than the instance name, the API schema adapter API follows the prim API.

Any USD prim will have a base type and an ordered (possibly empty) list of applied API schemas. USD composes attribute definitions on that prim by looking at the base type definition, and then subsequently looking at the API schema definitions in order. This is the process used to determine attribute fallback values, for example. UsdImagingStageSceneIndex matches this by using an HdOverlayContainerDataSource to compose prim data from the base type adapter, and then from each API schema adapter in order. Composition is performed separately for each subprim.

Adapters no longer get the wide "Populate" API of UsdImagingDelegate, but a single USD prim can be expanded into multiple Hydra prims by returning a list of subprim names. Each of these can have a unique prim type and a datasource providing prim data.

Adapters should route their value-level scene access through the UsdImagingDataSourceAttribute and UsdImagingDataSourceRelationship classes, which handle efficiently retrieving attribute data as well as marking time-variability in the stageGlobals object.

Renderer Access to Scene Index Data

Renderers and baking tooling can read data directly out of scene indices already by using the scene index API or by registering themselves as scene index observers, but for components using the Hydra plugin ecosystem the render delegate API is still based on Hydra calling Sync() with an instance of HdSceneDelegate. Render delegates that want to bypass the scene delegate API for reading can use the following call for direct datasource access:

void MyPrimSync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam,
HdDirtyBits* dirtyBits, TfToken const& reprToken)
{
sceneDelegate->GetRenderIndex().GetTerminalSceneIndex()->GetPrim(GetId());
// ... Pull data from siPrim instead of sceneDelegate ...
}
HD_API HdSceneIndexBaseRefPtr GetTerminalSceneIndex() const
The terminal scene index that is driving what is in the render index through emulation.
The HdRenderParam is an opaque (to core Hydra) handle, to an object that is obtained from the render ...
Adapter class providing data exchange with the client scene graph.
HdRenderIndex & GetRenderIndex()
Returns the RenderIndex owned by this delegate.

This still limits invalidations to those expressible by dirty bits, and still limits data update to the type-sorted-by-tier, thread-per-prim model of HdRenderIndex::SyncAll(). Our design goal with the Hydra 2.0-native render delegate is to allow the render delegate to customize the update model, by making SyncAll() a parameterizable function that the render delegate can call. We expect many render delegates to continue to use the existing update model, but this gives us the flexibility to break out certain prim types (like render settings) into different threading models if needed.

Appendix: Scene Index Emulation Explained

Although there are benefits to using the scene index API from end-to-end (for performance, flexibility, configurability), it's not required if you just want to get started with the Hydra Scene Browser or the Application or Renderer Scene Index Filters. Internally, Hydra is using the Scene Index API, but to minimize impact we designed adapters to invisibly support existing scene delegate implementations and existing render delegate implementations that read via a scene delegate pointer.

Architecturally, this is accomplished with two primary pieces. HdLegacyPrimSceneIndex represents a scene composed of HdSceneDelegate-backed prims as a scene index. This scene is added to the same merging scene index that's used by InsertSceneIndex(). The render index prim management functions (InsertRprim(), etc.) all operate by adding and removing prims to the legacy prim scene index. HdDataSourceLegacyPrim does the heavy lifting of converting scene delegate data to HdDataSourceBase-based data.

On the other end, HdSceneIndexAdapterSceneDelegate observes the Hydra scene downstream of all of the registered scene index filters. It will dispatch notices (PrimsAdded(), etc.) to the render index to update the internal tables of renderer prims. It's also an implementation of HdSceneDelegate, and answers queries by reference to the input scene index. The scene delegate that the render index reads from will be an instance of this class.

One last component is HdDirtyBitsTranslator, which encodes the mapping between dirty bits (used in the change tracker) and datasource locators (used in PrimsDirtied()).

The following diagram shows the components used to support scene delegates in Hydra 2.0.

These abstractions allow HdSceneDelegate-based integrations to be integrated into a Hydra 2.0 pipeline and take advantage of many of its features without requiring any porting effort. We're planning to maintain these emulation layers even as we port and deprecate all of our HdSceneDelegate implementations internally.