#include "pxr/pxr.h"
#include "pxr/usd/pcp/node.h"
+#include "pxr/usd/pcp/primIndex.h"
#include "pxr/usd/pcp/primIndex_Graph.h"
PXR_NAMESPACE_OPEN_SCOPE
IteratorType(node, /* end = */ true));
}
+/// \class PcpNodeRef_PrivateSubtreeConstIterator
+///
+/// Object used to iterate over all nodes in a subtree rooted at a
+/// given node in the prim index graph in strong-to-weak order.
+class PcpNodeRef_PrivateSubtreeConstIterator
+{
+public:
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = const PcpNodeRef;
+ using reference = const PcpNodeRef&;
+ using pointer = const PcpNodeRef*;
+ using difference_type = std::ptrdiff_t;
+
+ /// If \p end is false, constructs an iterator representing the
+ /// beginning of the subtree of nodes starting at \p node.
+ ///
+ /// If \p end is true, constructs an iterator representing the
+ /// next weakest node after the subtree of nodes starting at \p node.
+ /// This may be an invalid node if \p node is the root node.
+ PcpNodeRef_PrivateSubtreeConstIterator(const PcpNodeRef& node, bool end)
+ : _node(node)
+ , _nodes(&_node._graph->_GetNode(0))
+ , _pruneChildren(false)
+ {
+ if (end) {
+ _MoveToNext();
+ }
+ }
+
+ /// Causes the next increment of this iterator to ignore
+ /// descendants of the current node.
+ void PruneChildren()
+ {
+ _pruneChildren = true;
+ }
+
+ reference operator*() const { return _node; }
+ pointer operator->() const { return &_node; }
+
+ PcpNodeRef_PrivateSubtreeConstIterator& operator++()
+ {
+ if (_pruneChildren || !_MoveToFirstChild()) {
+ _MoveToNext();
+ }
+ _pruneChildren = false;
+ return *this;
+ }
+
+ PcpNodeRef_PrivateSubtreeConstIterator operator++(int)
+ {
+ PcpNodeRef_PrivateSubtreeConstIterator result(*this);
+ ++(*this);
+ return result;
+ }
+
+ bool operator==(const PcpNodeRef_PrivateSubtreeConstIterator& other) const
+ { return _node == other._node; }
+
+ bool operator!=(const PcpNodeRef_PrivateSubtreeConstIterator& other) const
+ { return !(*this == other); }
+
+private:
+ // If the current node has child nodes, move this iterator to the
+ // first child and return true. Otherwise return false.
+ bool _MoveToFirstChild()
+ {
+ auto& curNodeIdx = _node._nodeIdx;
+ const auto& nodeIndexes = _nodes[curNodeIdx].indexes;
+ const auto& invalid = PcpPrimIndex_Graph::_Node::_invalidNodeIndex;
+
+ if (nodeIndexes.firstChildIndex != invalid) {
+ curNodeIdx = nodeIndexes.firstChildIndex;
+ return true;
+ }
+ return false;
+ }
+
+ // If the current node has a direct sibling, move this iterator to
+ // that node. Otherwise, move this iterator to the next sibling of
+ // the nearest ancestor node with siblings. If no such node exists,
+ // (i.e., the current node is the weakest node in the index), this
+ // iterator will point to an invalid node.
+ void _MoveToNext()
+ {
+ auto& curNodeIdx = _node._nodeIdx;
+ const PcpPrimIndex_Graph::_Node::_Indexes* nodeIndexes = nullptr;
+ const auto& invalid = PcpPrimIndex_Graph::_Node::_invalidNodeIndex;
+
+ while (curNodeIdx != invalid) {
+ // See if we can move to the current node's next sibling.
+ nodeIndexes = &_nodes[curNodeIdx].indexes;
+ if (nodeIndexes->nextSiblingIndex != invalid) {
+ curNodeIdx = nodeIndexes->nextSiblingIndex;
+ break;
+ }
+
+ // If we can't, move to the current node's parent and try again.
+ curNodeIdx = nodeIndexes->arcParentIndex;
+ }
+ }
+
+private:
+ PcpNodeRef _node;
+ const PcpPrimIndex_Graph::_Node* _nodes;
+ bool _pruneChildren;
+};
+
+// Wrapper type for range-based for loops.
+class PcpNodeRef_PrivateSubtreeConstRange
+{
+public:
+ PcpNodeRef_PrivateSubtreeConstRange(const PcpNodeRef& node)
+ : _begin(node, /* end = */ false)
+ , _end(node, /* end = */ true)
+ { }
+
+ PcpNodeRef_PrivateSubtreeConstIterator begin() const { return _begin; }
+ PcpNodeRef_PrivateSubtreeConstIterator end() const { return _end; }
+
+private:
+ PcpNodeRef_PrivateSubtreeConstIterator _begin, _end;
+};
+
+/// Return node range for subtree rooted at the given \p node.
+inline
+PcpNodeRef_PrivateSubtreeConstRange
+Pcp_GetSubtreeRange(const PcpNodeRef& node)
+{
+ return PcpNodeRef_PrivateSubtreeConstRange(node);
+}
+
PXR_NAMESPACE_CLOSE_SCOPE
#endif // PXR_USD_PCP_NODE_ITERATOR_H
#include "pxr/usd/pcp/primIndex_StackFrame.h"
#include "pxr/usd/pcp/statistics.h"
#include "pxr/usd/pcp/strengthOrdering.h"
+#include "pxr/usd/pcp/traversalCache.h"
#include "pxr/usd/pcp/types.h"
#include "pxr/usd/pcp/utils.h"
#include "pxr/usd/ar/resolver.h"
using _TaskUniq = pxr_tsl::robin_set<Task, TfHash>;
_TaskUniq taskUniq;
+ // Caches for finding variant selections in the prim index. The map
+ // of caches is constructed lazily because this map isn't always
+ // needed. In particular, prim indexing doesn't look for variant
+ // selections in recursive prim indexing calls.
+ struct _VariantSelectionInfo
+ {
+ // Path in associate node's layer stack at which variant
+ // selections are authored.
+ SdfPath sitePath;
+
+ // Whether authored selections were found or not yet checked.
+ enum Status { AuthoredSelections, NoSelections, Unknown };
+ Status status = Unknown;
+ };
+
+ using _VariantTraversalCache = Pcp_TraversalCache<_VariantSelectionInfo>;
+ using _VariantTraversalCaches = std::unordered_map<
+ std::pair<PcpNodeRef, SdfPath>, _VariantTraversalCache, TfHash>;
+ std::optional<_VariantTraversalCaches> variantTraversalCache;
+
const bool evaluateImpliedSpecializes;
const bool evaluateVariants;
return _GetOriginatingIndex(previousFrame, outputs);
}
+ _VariantTraversalCache& GetVariantTraversalCache(
+ PcpNodeRef const& node, SdfPath const& pathInNode) {
+ if (!variantTraversalCache) {
+ variantTraversalCache.emplace();
+ }
+
+ return variantTraversalCache->try_emplace(
+ {node, pathInNode}, node, pathInNode).first->second;
+ }
+
// Map the given node's path to the root of the final prim index
// being computed.
SdfPath MapNodePathToRoot(PcpNodeRef const& node) const {
const SdfPath& pathInNode,
const std::string & vset,
std::string *vsel,
- PcpNodeRef *nodeWithVsel,
Pcp_PrimIndexer *indexer)
{
- TF_VERIFY(!pathInNode.IsEmpty());
-
- // We are using path-translation to walk between nodes, so we
- // are working exclusively in namespace paths, which must have
- // no variant selection.
- TF_VERIFY(!pathInNode.ContainsPrimVariantSelection(),
- "Unexpected variant selection in namespace path <%s>",
- pathInNode.GetText());
-
- // If this node has an authored selection, use that.
- // Note that we use this even if the authored selection is
- // the empty string, which explicitly selects no variant.
- if (_NodeCanContributeToVariant(node, pathInNode)) {
- PcpLayerStackSite site(node.GetLayerStack(), pathInNode);
- // pathInNode is a namespace path, not a storage path,
- // so it will contain no variant selection (as verified above).
- // To find the storage site, we need to insert any variant
- // selection for this node.
- if (node.GetArcType() == PcpArcTypeVariant) {
- // We need to use the variant node's path at introduction
- // instead of it's current path (i.e. node.GetPath()) because
- // pathInNode may be an ancestor of the current path when
- // dealing with ancestral variants.
- const SdfPath variantPath = node.GetPathAtIntroduction();
- site.path = pathInNode.ReplacePrefix(
- variantPath.StripAllVariantSelections(),
- variantPath);
- }
-
- std::unordered_set<std::string> exprVarDependencies;
- PcpErrorVector errors;
+ std::unordered_set<std::string> exprVarDependencies;
+ PcpErrorVector errors;
- const bool foundSelection =
- PcpComposeSiteVariantSelection(
- site.layerStack, site.path, vset, vsel,
- &exprVarDependencies, &errors);
+ const bool foundSelection =
+ PcpComposeSiteVariantSelection(
+ node.GetLayerStack(), pathInNode, vset, vsel,
+ &exprVarDependencies, &errors);
- if (!exprVarDependencies.empty()) {
- indexer->outputs->expressionVariablesDependency.AddDependencies(
- site.layerStack, std::move(exprVarDependencies));
- }
-
- if (!errors.empty()) {
- for (const PcpErrorBasePtr& err : errors) {
- indexer->RecordError(err);
- }
- }
+ if (!exprVarDependencies.empty()) {
+ indexer->outputs->expressionVariablesDependency.AddDependencies(
+ node.GetLayerStack(), std::move(exprVarDependencies));
+ }
- if (foundSelection) {
- *nodeWithVsel = node;
- return true;
+ if (!errors.empty()) {
+ for (const PcpErrorBasePtr& err : errors) {
+ indexer->RecordError(err);
}
}
- return false;
+ return foundSelection;
}
// Check the tree of nodes rooted at the given node for any node
// representing a prior selection for the given variant set for the path.
static bool
_FindPriorVariantSelection(
- const PcpNodeRef& node,
- const SdfPath &pathInNode,
+ const PcpNodeRef& startNode,
+ const SdfPath &pathInStartNode,
const std::string & vset,
std::string *vsel,
- PcpNodeRef *nodeWithVsel)
+ PcpNodeRef *nodeWithVsel,
+ Pcp_PrimIndexer *indexer)
{
- // If this node represents a variant selection at the same
- // effective depth of namespace, then check its selection.
- if (node.GetArcType() == PcpArcTypeVariant) {
- const SdfPath nodePathAtIntroduction = node.GetPathAtIntroduction();
- const std::pair<std::string, std::string> nodeVsel =
- nodePathAtIntroduction.GetVariantSelection();
- if (nodeVsel.first == vset) {
- // The node has a variant selection for the variant set we're
- // looking for, but we still have to check that the node actually
- // represents the prim path we're choosing a variant selection for
- // (as opposed to a different prim path that just happens to have
- // a variant set with the same name.
- if (nodePathAtIntroduction.GetPrimPath() == pathInNode) {
- *vsel = nodeVsel.second;
- *nodeWithVsel = node;
- return true;
- }
- }
- }
+ auto& traverser =
+ indexer->GetVariantTraversalCache(startNode, pathInStartNode);
- TF_FOR_ALL(child, Pcp_GetChildrenRange(node)) {
- const SdfPath pathInChild =
- child->GetMapToParent().MapTargetToSource(pathInNode);
- if (pathInChild.IsEmpty()) {
- continue;
- }
+ // Don't use a range-based for loop here so we can avoid asking for
+ // the path in the current node (which incurs expensive path translations)
+ // until we're absolutely sure we need it.
+ for (auto it = traverser.begin(), e = traverser.end(); it != e; ++it) {
+ const PcpNodeRef node = it.Node();
- if (_FindPriorVariantSelection(
- *child, pathInChild, vset, vsel, nodeWithVsel)) {
- return true;
+ // If this node represents a variant selection at the same
+ // effective depth of namespace, then check its selection.
+ if (node.GetArcType() == PcpArcTypeVariant) {
+ const SdfPath nodePathAtIntroduction = node.GetPathAtIntroduction();
+ const std::pair<std::string, std::string> nodeVsel =
+ nodePathAtIntroduction.GetVariantSelection();
+ if (nodeVsel.first == vset) {
+ const SdfPath& pathInNode = it.PathInNode();
+
+ // If the path didn't translate to this node, it won't translate
+ // to any of the node's children, so we might as well prune the
+ // traversal here.
+ //
+ // We don't do this check earlier because we don't want to call
+ // PathInNode unless absolutely necessary, as it runs relatively
+ // expensive path translations.
+ if (pathInNode.IsEmpty()) {
+ it.PruneChildren();
+ continue;
+ }
+
+ // The node has a variant selection for the variant set we're
+ // looking for, but we still have to check that the node
+ // actually represents the prim path we're choosing a variant
+ // selection for (as opposed to a different prim path that just
+ // happens to have a variant set with the same name.
+ if (nodePathAtIntroduction.GetPrimPath() == it.PathInNode()) {
+ *vsel = nodeVsel.second;
+ *nodeWithVsel = node;
+ return true;
+ }
+ }
}
}
static bool
_ComposeVariantSelectionAcrossNodes(
- const PcpNodeRef& node,
- const SdfPath& pathInNode,
+ const PcpNodeRef& startNode,
+ const SdfPath& pathInStartNode,
const std::string & vset,
std::string *vsel,
PcpNodeRef *nodeWithVsel,
Pcp_PrimIndexer *indexer)
{
// Compose variant selection in strong-to-weak order.
- if (_ComposeVariantSelectionForNode(
- node, pathInNode, vset, vsel, nodeWithVsel, indexer)) {
- return true;
- }
+ auto& traverser =
+ indexer->GetVariantTraversalCache(startNode, pathInStartNode);
- TF_FOR_ALL(child, Pcp_GetChildrenRange(node)) {
- const PcpNodeRef& childNode = *child;
- const SdfPath pathInChildNode =
- childNode.GetMapToParent().MapTargetToSource(pathInNode);
+ for (auto it = traverser.begin(), e = traverser.end(); it != e; ++it) {
+ auto [node, pathInNode, info] = *it;
+
+ // If path translation to this node failed, it will fail for all
+ // other children so we can skip them entirely
+ if (pathInNode.IsEmpty()) {
+ it.PruneChildren();
+ continue;
+ }
+
+ if (!_NodeCanContributeToVariant(node, pathInNode)) {
+ continue;
+ }
- if (!pathInChildNode.IsEmpty() &&
- _ComposeVariantSelectionAcrossNodes(
- *child, pathInChildNode, vset, vsel, nodeWithVsel, indexer)) {
+ // Precompute whether the layer stack has any authored variant
+ // selections and cache that away.
+ using Info = Pcp_PrimIndexer::_VariantSelectionInfo;
+ if (info.status == Info::Unknown) {
+ info.sitePath = [&node=node, &pathInNode=pathInNode]() {
+ // pathInNode is a namespace path, not a storage path,
+ // so it will contain no variant selection (as verified above).
+ // To find the storage site, we need to insert any variant
+ // selection for this node.
+ if (node.GetArcType() == PcpArcTypeVariant) {
+ // We need to use the variant node's path at introduction
+ // instead of it's current path (i.e. node.GetPath()) because
+ // pathInNode may be an ancestor of the current path when
+ // dealing with ancestral variants.
+ const SdfPath variantPath = node.GetPathAtIntroduction();
+ return pathInNode.ReplacePrefix(
+ variantPath.StripAllVariantSelections(),
+ variantPath);
+ }
+ return pathInNode;
+ }();
+
+ info.status =
+ PcpComposeSiteHasVariantSelections(
+ node.GetLayerStack(), info.sitePath) ?
+ Info::AuthoredSelections : Info::NoSelections;
+ }
+
+ // If no variant selections are authored here, we can skip.
+ if (info.status == Info::NoSelections) {
+ continue;
+ }
+
+ // If this node has an authored selection, use that.
+ // Note that we use this even if the authored selection is
+ // the empty string, which explicitly selects no variant.
+ if (_ComposeVariantSelectionForNode(
+ node, info.sitePath, vset, vsel, indexer)) {
+ *nodeWithVsel = node;
return true;
}
}
// First check if we have already resolved this variant set in the current
// prim index.
if (_FindPriorVariantSelection(
- startNode, pathInStartNode, vset, vsel, nodeWithVsel)) {
+ startNode, pathInStartNode, vset, vsel, nodeWithVsel, indexer)) {
PCP_INDEXING_MSG(
indexer, node, *nodeWithVsel,
--- /dev/null
+//
+// Copyright 2024 Pixar
+//
+// Licensed under the Apache License, Version 2.0 (the "Apache License")
+// with the following modification; you may not use this file except in
+// compliance with the Apache License and the following modification to it:
+// Section 6. Trademarks. is deleted and replaced with:
+//
+// 6. Trademarks. This License does not grant permission to use the trade
+// names, trademarks, service marks, or product names of the Licensor
+// and its affiliates, except as required to comply with Section 4(c) of
+// the License and to reproduce the content of the NOTICE file.
+//
+// You may obtain a copy of the Apache License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the Apache License with the above modification is
+// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the Apache License for the specific
+// language governing permissions and limitations under the Apache License.
+//
+#ifndef PXR_USD_PCP_TRAVERSAL_CACHE_H
+#define PXR_USD_PCP_TRAVERSAL_CACHE_H
+
+#include "pxr/pxr.h"
+
+#include "pxr/usd/pcp/node.h"
+#include "pxr/usd/pcp/node_Iterator.h"
+#include "pxr/usd/pcp/primIndex_Graph.h"
+#include "pxr/usd/sdf/path.h"
+
+#include <optional>
+#include <tuple>
+#include <vector>
+
+PXR_NAMESPACE_OPEN_SCOPE
+
+/// \class Pcp_TraversalCache
+///
+/// Caches the traversal of a subtree in a prim index starting at a
+/// given node and with a specified path within that node's layer stack.
+/// As clients traverse through the subtree, the starting path will
+/// be translated to each node and cached, so that repeated traversals
+/// will not incur the same path translation costs. Clients may also
+/// store data associated with each node in the subtree.
+template <class Data>
+class Pcp_TraversalCache
+{
+public:
+ /// \class iterator
+ /// Object for iterating over the subtree of nodes cached by
+ /// the owning Pcp_TraversalCache.
+ class iterator
+ {
+ public:
+ /// value type is a tuple of (Node(), PathInNode(), AssociatedData()).
+ /// See performance note on PathInNode().
+ using value_type = std::tuple<PcpNodeRef const, SdfPath const, Data&>;
+
+ /// Return the current node.
+ PcpNodeRef Node()
+ {
+ return *_iter;
+ }
+
+ /// Return the original traversal path given to the owning
+ /// Pcp_TraversalCache translated to the current node.
+ ///
+ /// This function will translate and cache the traversal path for
+ /// this node and all parent nodes if they have not already been
+ /// computed.
+ SdfPath const& PathInNode()
+ {
+ return *_owner->_GetEntry(*_iter, /* computePaths = */ true).path;
+ }
+
+ /// Return a reference to the data associated with the current node.
+ Data& AssociatedData()
+ {
+ return _owner->_GetEntry(*_iter, /* computePaths = */ false).data;
+ }
+
+ /// Return value_type. Note that this will incur the cost of path
+ /// translations described in PathInNode(). If you don't need the
+ /// translated path, use one of the other member functions to avoid
+ /// this cost.
+ value_type operator*()
+ {
+ _Entry& e = _owner->_GetEntry(*_iter, /* computePaths = */ true);
+ return std::tie(*_iter, *e.path, e.data);
+ }
+
+ /// Causes the next increment of this iterator to ignore descendants
+ /// of the current node.
+ void PruneChildren()
+ {
+ _iter.PruneChildren();
+ }
+
+ iterator& operator++()
+ {
+ ++_iter;
+ return *this;
+ }
+
+ iterator operator++(int)
+ {
+ iterator result(*this);
+ ++_iter;
+ return result;
+ }
+
+ bool operator==(iterator const& rhs) const
+ { return std::tie(_owner, _iter) == std::tie(rhs._owner, rhs._iter); }
+
+ bool operator!=(iterator const& rhs) const
+ { return !(*this == rhs); }
+
+ private:
+ friend class Pcp_TraversalCache;
+ iterator(
+ Pcp_TraversalCache* owner,
+ PcpNodeRef_PrivateSubtreeConstIterator iter)
+ : _owner(owner)
+ , _iter(iter)
+ { }
+
+ Pcp_TraversalCache* const _owner = nullptr;
+ PcpNodeRef_PrivateSubtreeConstIterator _iter;
+ };
+
+ /// Construct a traversal cache for the subtree rooted at \p startNode and
+ /// the path \p pathInNode. \p pathInNode must be in \p startNode's
+ /// namespace.
+ Pcp_TraversalCache(PcpNodeRef const& startNode, SdfPath const& pathInNode)
+ : _startNode(startNode)
+ {
+ _ResizeForGraph();
+ _cache[_startNode._GetNodeIndex()].path = pathInNode;
+ }
+
+ Pcp_TraversalCache(Pcp_TraversalCache const&) = delete;
+ Pcp_TraversalCache(Pcp_TraversalCache &&) = delete;
+ Pcp_TraversalCache& operator=(Pcp_TraversalCache const&) = delete;
+ Pcp_TraversalCache& operator=(Pcp_TraversalCache &&) = delete;
+
+ iterator begin()
+ {
+ _ResizeForGraph();
+ return iterator(
+ this,
+ PcpNodeRef_PrivateSubtreeConstIterator(
+ _startNode, /* end = */ false));
+ }
+
+ iterator end()
+ {
+ _ResizeForGraph();
+ return iterator(
+ this,
+ PcpNodeRef_PrivateSubtreeConstIterator(
+ _startNode, /* end = */ true));
+ }
+
+private:
+ struct _Entry
+ {
+ // Traversal path translated to the entry's corresponding node
+ std::optional<SdfPath> path;
+
+ // Client data associated with this entry's corresponding node.
+ Data data;
+ };
+
+ void _ResizeForGraph()
+ {
+ PcpPrimIndex_Graph const* graph = _startNode.GetOwningGraph();
+
+ // We assume the graph will never shrink.
+ TF_VERIFY(graph->_GetNumNodes() >= _cache.size());
+
+ if (graph->_GetNumNodes() > _cache.size()) {
+ _cache.resize(graph->_GetNumNodes());
+ }
+ }
+
+ SdfPath _TranslatePathsForNode(PcpNodeRef const& node)
+ {
+ // "Recursively" map the path from the parent node to this node.
+ // This terminates because we'll eventually reach _startNode,
+ // and we populated its path in the c'tor.
+ _Entry& entry = _cache[node._GetNodeIndex()];
+ if (!entry.path) {
+ PcpNodeRef const parentNode = node.GetParentNode();
+ _Entry& parentEntry = _cache[parentNode._GetNodeIndex()];
+ if (!parentEntry.path) {
+ parentEntry.path = _TranslatePathsForNode(parentNode);
+ }
+
+ SdfPath const& pathInParent = *(parentEntry.path);
+ entry.path = pathInParent.IsEmpty() ? SdfPath() :
+ node.GetMapToParent().MapTargetToSource(pathInParent);
+ }
+
+ return *entry.path;
+ }
+
+ _Entry& _GetEntry(PcpNodeRef const& node, bool computePaths)
+ {
+ TF_VERIFY(node._GetNodeIndex() < _cache.size());
+
+ // If requested, make sure the translated path is populated before
+ // returning the _Entry to the caller.
+ if (computePaths) {
+ _TranslatePathsForNode(node);
+ }
+ return _cache[node._GetNodeIndex()];
+ }
+
+ PcpNodeRef _startNode;
+ std::vector<_Entry> _cache;
+};
+
+PXR_NAMESPACE_CLOSE_SCOPE
+
+#endif