How does variant selection composition work, in detail?

As an exercise to understand LIVRPS, I’m trying to reverse engineer how composition actually works using just variants.

I’m using a simple layer with a single prim, local variant selection, 3 sets and 2 variants each, randomly selecting or blocking them, either in the main prim, or in a variant prim. Then I try to mimic the composition result of USD with custom C++ code. Some of these random selections will generate “variant selection cycles”, but usdchecker doesn’t complain about it.

This seemed simple enough at first sight, but I can’t get it right. Is this documented in more detail somewhere?

Example of such a randomly generated layer:

#usda 1.0
(
    defaultPrim = "cone"
    upAxis = "Z"
    metersPerUnit = 0.01
)

def Cone "cone" (
    variants = {
        string C = "c1"
    }
    prepend variantSets = ["A", "B", "C"]
)
{
    variantSet "C" = {
        "c0" (
            variants = {
                string A = "a0"
                string B = "b1"
            }
        ) {
            double height = 1004
            double radius = 1004

        }
        "c1" (
            variants = {
                string A = "a1"
                string B = "b1"
            }
        ) {
            double radius = 1005

        }
    }
    variantSet "B" = {
        "b0" (
            variants = {
                string A = ""
            }
        ) {
            double height = 1002
            double radius = 1002

        }
        "b1" {
            double height = 1003

        }
    }
    variantSet "A" = {
        "a0" {
            double height = 1000
            double radius = 1000

        }
        "a1" (
            variants = {
                string B = "b0"
            }
        ) {
            double radius = 1001

        }
    }
}

Apologies for the slow response. Many of us are out for the holidays.

The composition algorithm is not documented currently but will be part of the upcoming USD specification

1 Like

No problem at all! I’m actually doing this for fun during my holidays.

I think I cracked it by stepping through the PCP C++ code; my prototype seems to work fine for all random layers generated until now.

The key was the RetryVariantTasks:

// Retry any variant sets that previously failed to find an authored
// selection to take into account newly-discovered opinions.
// EvalNodeVariantNoneFound is a placeholder representing variants
// that were previously visited and yielded no variant; it exists
// solely for this function to be able to find and retry them.

I also enabled this code to make it easier to understand which tasks where queued:

    inline void _DebugPrintTasks(char const *label) const {
#if 0
        printf("-- %s ----------------\n", label);
        _TaskQueue tq(tasks);
        sort_heap(tq.begin(), tq.end(), Task::PriorityOrder());
        for (auto iter = tq.rbegin(); iter != tq.rend(); ++iter) {
            printf("%s\n", TfStringify(*iter).c_str());
        }
        printf("----------------\n");
#endif
    }

Also, when an authored variant is found, its nested variant selections are not considered new authored variant selections yet; all the previously not-found variant-set-tasks are retried, and the newly found nested variant selections are now considered.

So it is not a simple depth-first or breath-first traversal of the variant-selection dependency graph at all, it is more complicated than that

I’m not good enough with USD glossary to explain this in detail, but it might also explain this issue:

Nicely done, @Ziriax ! Yes, variant selections inside variantSet “A” need to be ignored when resolving the selection for “A” because it would otherwise presuppose the answer. This behavior (currently undocumented at the user level… as @dhruvgovil said, that will be getting addressed soonish) of iterating through weaker “nodes” (i.e. arcs) not only allows us to resolve the tricky case you provided, but is actually quite useful in production, constructing assets that may be referenced into multiple levels of larger and larger constructions! It allows “weaker” assets to provide useful “fallback” selections for variants, while still allowing the containing aggregate scenes to override them at any level of composition.

And yes, the restart process does contribute to the expense in some situations, though there are more basic issues in our algorithm that contribute to the reported behavior in the Issue you referenced… we have some ideas we hope to try out when we can make time.