Prototype instancing with scene indices

Hi,

Would anyone have an example of doing instancing within a retained scene index in C++ ?
With using the classes HdInstancerTopologySchema and such.

I am just trying to instantiate a cube at different world translations, but I cannot figure out how to do that properly yet.

As a reference, I can share the creation of a 3D grid of cubes with no instancing :

void InjectingSceneIndexExample::AddAllPrimsNoInstancing()
{
    //Arrays of added prims
    HdRetainedSceneIndex::AddedPrimEntries  addedPrims;
    
    //Copy the main cube primitive in the array, we will update only the SdfPath and the transform, all others attributes are identical
    HdRetainedSceneIndex::AddedPrimEntry cubePrimEntry = CreateCubePrim(_cubeRootPath, _currentCubeParams._halfSize, 
                                                                        _currentCubeParams._color, _currentCubeParams._opacity, 
                                                                        _currentCubeParams._initalTransform);
    const size_t totalSize = _currentCubeParams._numLevelsX * _currentCubeParams._numLevelsY * _currentCubeParams._numLevelsZ;
    addedPrims.resize(totalSize, cubePrimEntry);

    const std::string cubeRootString    = _cubeRootPath.GetString();
    const GfVec3d initTrans             = _currentCubeParams._initalTransform.ExtractTranslation();
    const int numLevelsX                = _currentCubeParams._numLevelsX;
    const int numLevelsY                = _currentCubeParams._numLevelsY;
    const int numLevelsZ                = _currentCubeParams._numLevelsZ;

    //Create cube primitives in Hydra
    
    //Create cube primitives in Hydra
    tbb::parallel_for(tbb::blocked_range3d<int, int, int>(0, numLevelsZ, 0, numLevelsY, 0, numLevelsX),
		[&](const tbb::blocked_range3d<int, int, int>& r)
		{
			for (int z = r.pages().begin(), z_end = r.pages().end(); z < z_end; ++z) {
				for (int y = r.rows().begin(), y_end = r.rows().end(); y < y_end; ++y) {
					for (int x = r.cols().begin(), x_end = r.cols().end(); x < x_end; ++x) {

                        //Update translation
                        GfMatrix4d currentXForm = _currentCubeParams._initalTransform;
                        currentXForm.SetTranslateOnly(initTrans + (GfCompMult(_currentCubeParams._deltaTrans, GfVec3f(x,y,z))));

                        //Update information at the right place in the array
                        HdRetainedSceneIndex::AddedPrimEntry& currentCubePrimEntry  = addedPrims[x + (numLevelsX * y) + (numLevelsX * numLevelsY * z)];

                        //Update the prim path
                        currentCubePrimEntry.primPath                               = SdfPath(  cubeRootString +  std::to_string(x) + 
                                                                                                std::string("_") + std::to_string(y)+ 
                                                                                                std::string("_") + std::to_string(z));

                        //Update the matrix in the data source
                        currentCubePrimEntry.dataSource                             = HdContainerDataSourceEditor(currentCubePrimEntry.dataSource)
                                                                                        .Set(HdXformSchema::GetDefaultLocator(), 
                                                                                        HdXformSchema::Builder().SetMatrix(HdRetainedTypedSampledDataSource<GfMatrix4d>::New(currentXForm))
                                                                                        .Build())
                                                                                        .Finish();
					}
				}
			}
		}
	);
    
    //Add new prims to the scene index
    _retainedSceneIndex->AddPrims(addedPrims);
}

Thank you.
Regards,
David

Hi David, there are two parts to this:

First, for geometry that is a prototype and is instanced by an instancer, you need to tell it what instancer(s) it is instanced by. You attach another schema to the mesh for that:

if (!instancerId.IsEmpty())
    {
        HdDataSourceBaseHandle instancedByData =
            HdInstancedBySchema::Builder()
                .SetPaths(HdRetainedTypedSampledDataSource<VtArray<SdfPath>>::New(
                    VtArray<SdfPath>({ instancerId })))
                .Build();
        triangleData = HdRetainedContainerDataSource::New(HdMeshSchemaTokens->mesh, bcs,
            HdPrimvarsSchemaTokens->primvars, primvarsDs, HdInstancedBySchema::GetSchemaToken(),
            instancedByData, HdXformSchema::GetSchemaToken(), xform);
    }

The mesh data, the primvars, the instanced by, and the matrix are attached in the above code to define the instance prototype.

Second, you need to create the instancer.

// first you need to define a class to hold the instance indices
class _InstanceIndicesDataSource : public HdVectorDataSource
{
public:
    HD_DECLARE_DATASOURCE(_InstanceIndicesDataSource);
    size_t GetNumElements() override { return 1; }
    HdDataSourceBaseHandle GetElement(size_t) override
    {
        return HdRetainedTypedSampledDataSource<VtArray<int>>::New(_indices);
    }
private:
    _InstanceIndicesDataSource(VtArray<int> indices) : _indices(indices) {}
    VtArray<int> const _indices;
};

// then create an instancer topology data source for the instancer, and supply the matrices as a by-instance varying primvar.
void CreateInstancer(const pxr::SdfPath& id,
    const pxr::SdfPath& prototypeId, const pxr::VtIntArray& prototypeIndices,
    const pxr::VtMatrix4dArray& matrices, pxr::HdRetainedSceneIndexRefPtr& retainedScene)
{
    auto instanceIndices = _InstanceIndicesDataSource::New(std::move(prototypeIndices));

    HdDataSourceBaseHandle instancerTopologyData =
        HdInstancerTopologySchema::Builder()
            .SetPrototypes(HdRetainedTypedSampledDataSource<VtArray<SdfPath>>::New({ prototypeId }))
            .SetInstanceIndices(instanceIndices)
            .Build();

    HdDataSourceBaseHandle primvarData = ConstructPrimvarDataSource(
        VtValue(matrices), HdPrimvarSchemaTokens->instance, HdInstancerTokens->instanceTransform);

    HdDataSourceBaseHandle primvarsDs = HdRetainedContainerDataSource::New(
        HdInstancerTokens->instanceTransform, primvarData);

    HdRetainedContainerDataSourceHandle instancerData =
        HdRetainedContainerDataSource::New(HdInstancerTopologySchema::GetSchemaToken(),
            instancerTopologyData, HdPrimvarsSchema::GetSchemaToken(), primvarsDs);
    
    // Add the primitives to the scene index
    retainedScene->AddPrims({ { id, pxr::HdInstancerTokens->instancer, instancerData } });
}

Creating the primvar data source is provided as follows:

HdContainerDataSourceHandle ConstructPrimvarDataSource(
    const VtValue& value, const TfToken& interpolation, const TfToken& role)
{
    static auto emptyArray = HdRetainedTypedSampledDataSource<VtIntArray>::New(VtIntArray());

    return HdPrimvarSchema::BuildRetained(_GetRetainedDataSource(value),
        HdSampledDataSourceHandle(), emptyArray,
        HdPrimvarSchema::BuildInterpolationDataSource(interpolation),
        HdPrimvarSchema::BuildInterpolationDataSource(role));
}

Thank you Adam for your reply !