Solaris material edits and ProcessPrimResync with "procedural" Hydra adapters

I’ve been running into some problems with a custom Hydra primitive adapter and the default behaviour of ProcessPrimResync. I started a discussion about this on the old USD Google group a couple of months ago, and it unfortunately took me a while to revisit the idea suggested in the reply to my question.

Here is the original thread: https://groups.google.com/g/usd-interest/c/3hyh9VGb-MU/m/_4fPZKy7BQAJ

Here is a summary of my original question: I have a “procedural” Hydra primitive adapter (inheriting from UsdImagingPrimAdapter). The geometry which it renders is generated and cached at render time, i.e. it does not preexist on the USD stage.
ProcessPrimResync is called when editing materials attached to the prim in Solaris, and the default implementation of ProcessPrimResync deletes the prim and re-adds it in Hydra. This is slow since it wipes the cached geometry, so it makes editing materials which are attached to these prims rather painful. I asked what options might be available to mitigate this.

Tom Cauchois replied by suggesting that I can override ProcessPrimResync, and “ignore” the resync under some conditions:
“You’ll need to manually verify that the procedural prim you’re resyncing still exists, still has the right type, and verify the input values if you don’t want to invalidate them, but if that all looks ok you’re probably fine to ignore the resync.”

====

I have now tried playing around with this idea, but I can’t quite get it to work. At first, I could not get the Renderman RIS viewport in Houdini Solaris (19.0.x) to refresh after a material was edited. I was able to correct this for the first edit by manually invoking MarkMaterialDirty inside of my ProcessPrimResync function. However, this somehow doesn’t work beyond the first material edit - subsequent edits won’t be picked up when the prim gets re-rendered. So it would seem that something else needs to be dirtied, but I’m not sure what that might be.

Note that when you alter the composition of a prim, such as by adding a property, that will trigger a “resync”, but when you edit the value of an existing property we call that a “refresh” and it’s much lighter-weight. In that case, we’ll call ProcessPropertyChange on the affected prim adapter; so if you implemented your custom logic in ProcessPrimResync but not ProcessPropertyChange and you only get the first edit, that could be the place you want to start.

For debugging USD change-notice issues in general, I highly recommend setting the environment variable TF_DEBUG=USDIMAGING_CHANGES; we print out as much information as we can about the contents of the change notice and how we’re interpreting them, and that might help you.

Hope that helps!

Hi Tom,

Thank you for your reply. Thus far I have not been able to get this working properly.

Here is my “prototype” implementation of ::ProcessPrimResync (the final version probably needs to be more sophisticated)

void MyAdapter::ProcessPrimResync(const SdfPath& cachePath,
                                    UsdImagingIndexProxy* index)
{
    auto prim = _GetPrim(cachePath);

    if (prim) {
        MarkMaterialDirty(prim, cachePath, index);
        return;
    }
    else {
        _RemovePrim(cachePath, index);
        index->Repopulate(/*cachePath*/cachePath);
    }
}

In my test setup, I have assigned a basic PxrDiffuse material to an instance of my custom primitive (via Solaris), and I also have PxrPrimvar node in my material network which is disconnected at the start. In this state, changing the diffuse colour of the PxrDiffuse material node works correctly, but if I connect the resultRGB output of the PxrPrimvar node to the PxrDiffuse diffuseColor input, the colour turns grey (side note: in a previous message I said that the first material change is picked up correctly, but subsequent ones are not. This was not entirely correct, as some material operations won’t work at all, such as connecting the PxrPrimvar node to the PxrDiffuse, though going in the other direction does work if I change my test setup so that the nodes are connected to begin with). I then lose the ability to change the diffuse colour, even if I disconnect the PxrPrimvar again.

The last few entries of the USD log (enabled via TF_DEBUG=USDIMAGING_CHANGES) for my implementation of the ProcessPrimResync are as follows:

[Refresh Object]: /materials/pxrdiffuse1/pxrdiffuse1.inputs:diffuseColor [ ]
[Refresh Object]: Shader property </materials/pxrdiffuse1/pxrdiffuse1.inputs:diffuseColor> modified; updating material </materials/pxrdiffuse1>.
  - affected prim: </PATH_TO_MY_PRIM>
[Repopulate] Populating </> on stage LOP:rootlayer
[Repopulate] Root path: </materials/pxrdiffuse1>
[Repopulate] Pruned at </materials/pxrdiffuse1> due to prim type <Material>
[Repopulate] 0 variability tasks in worker

The last few entries of the USD log for the default implementation of ProcessPrimResync are as follows:

[Refresh Object]: /materials/pxrdiffuse1/pxrdiffuse1.inputs:diffuseColor [ ]
[Refresh Object]: Shader property </materials/pxrdiffuse1/pxrdiffuse1.inputs:diffuseColor> modified; updating material </materials/pxrdiffuse1>.
[Repopulate] Populating </> on stage LOP:rootlayer
[Repopulate] Root path: </PATH_TO_MY_PRIM>
[Repopulate] Root path: </materials/pxrdiffuse1>
[Repopulate] Pruned at </materials/pxrdiffuse1> due to prim type <Material>
[Add HdPrim Info] </PATH_TO_MY_PRIM> adapter=MY_ADAPTER
[Add dependency] </PATH_TO_MY_PRIM> -> </PATH_TO_MY_PRIM>
[Add HdPrim Info] </materials/pxrdiffuse1> adapter=UsdImagingMaterialAdapter
[Add dependency] </materials/pxrdiffuse1> -> </materials/pxrdiffuse1>
[Add dependency] </materials/pxrdiffuse1/pxrdiffuse1> -> </materials/pxrdiffuse1>
[Add dependency] </materials/pxrdiffuse1/pxrprimvar2> -> </materials/pxrdiffuse1>
[Add dependency] </materials/pxrdiffuse1/pxrdiffuse1_preview> -> </materials/pxrdiffuse1>
[Add dependency] </materials/pxrdiffuse1> -> </PATH_TO_MY_PRIM>
[Repopulate] 2 variability tasks in worker

So it looks like something is missing here.

As far as implementing ::ProcessPropertyChange goes, we do in fact override it. Whenever material changes are made (e.g. connecting the PxrPrimvar to the PxrDiffuse), we see that the function is called with properties such as “inputs:diffuseColor”. However, it’s not really clear to me what we are supposed to do with these.