Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 32 additions & 6 deletions source/MaterialXCore/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ class Document::Cache
return (it != _implementationMap.end()) ? it->second : vector<InterfaceElementPtr>();
}

ImplementationPtr getImplementationForNodeGraph(const string& nodeGraphName)
{
auto lock = refreshWithLock();
auto it = _nodeGraphImplMap.find(nodeGraphName);
return (it != _nodeGraphImplMap.end()) ? it->second : ImplementationPtr();
}

private:
std::shared_lock<std::shared_mutex> refreshWithLock()
{
Expand Down Expand Up @@ -103,6 +110,7 @@ class Document::Cache
_portElementMap.clear();
_nodeDefMap.clear();
_implementationMap.clear();
_nodeGraphImplMap.clear();

// Traverse the document to build a new cache.
for (ElementPtr elem : doc->traverseTree())
Expand All @@ -121,6 +129,14 @@ class Document::Cache
_portElementMap[portElem->getQualifiedName(portKey)].push_back(portElem);
}
}
if (!nodeGraphName.empty())
{
ImplementationPtr impl = elem->asA<Implementation>();
if (impl)
{
_nodeGraphImplMap[impl->getQualifiedName(nodeGraphName)] = impl;
}
}
if (!nodeString.empty())
{
NodeDefPtr nodeDef = elem->asA<NodeDef>();
Expand Down Expand Up @@ -152,6 +168,7 @@ class Document::Cache
std::unordered_map<string, std::vector<PortElementPtr>> _portElementMap;
std::unordered_map<string, std::vector<NodeDefPtr>> _nodeDefMap;
std::unordered_map<string, std::vector<InterfaceElementPtr>> _implementationMap;
std::unordered_map<string, ImplementationPtr> _nodeGraphImplMap;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its possible for more than one Implementation element to use the same NodeGraph - so this might need to be

std::unordered_map<string, std::vector>

For standard surface (here) we have two different Implementation elements that have the same NodeGraph

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious if there are any metrics for the additional update cost especially if the cache updates frequently ? At most currently 1 or a few items will ever be found and it's mostly static during the lifetime of a document.

};

//
Expand Down Expand Up @@ -378,9 +395,7 @@ vector<OutputPtr> Document::getMaterialOutputs() const
vector<NodeDefPtr> Document::getMatchingNodeDefs(const string& nodeName) const
{
// Recurse to data library if present.
vector<NodeDefPtr> matchingNodeDefs = hasDataLibrary() ?
getDataLibrary()->getMatchingNodeDefs(nodeName) :
vector<NodeDefPtr>();
vector<NodeDefPtr> matchingNodeDefs = hasDataLibrary() ? getDataLibrary()->getMatchingNodeDefs(nodeName) : vector<NodeDefPtr>();

// Append all nodedefs matching the given node name.
vector<NodeDefPtr> localNodeDefs = _cache->getMatchingNodeDefs(nodeName);
Expand All @@ -392,9 +407,7 @@ vector<NodeDefPtr> Document::getMatchingNodeDefs(const string& nodeName) const
vector<InterfaceElementPtr> Document::getMatchingImplementations(const string& nodeDef) const
{
// Recurse to data library if present.
vector<InterfaceElementPtr> matchingImplementations = hasDataLibrary() ?
getDataLibrary()->getMatchingImplementations(nodeDef) :
vector<InterfaceElementPtr>();
vector<InterfaceElementPtr> matchingImplementations = hasDataLibrary() ? getDataLibrary()->getMatchingImplementations(nodeDef) : vector<InterfaceElementPtr>();

// Append all implementations matching the given nodedef string.
vector<InterfaceElementPtr> localImpls = _cache->getMatchingImplementations(nodeDef);
Expand All @@ -403,6 +416,19 @@ vector<InterfaceElementPtr> Document::getMatchingImplementations(const string& n
return matchingImplementations;
}

ImplementationPtr Document::getImplementationForNodeGraph(const string& nodeGraphName) const
{
if (hasDataLibrary())
{
ImplementationPtr impl = getDataLibrary()->getImplementationForNodeGraph(nodeGraphName);
if (impl)
{
return impl;
}
}
return _cache->getImplementationForNodeGraph(nodeGraphName);
}

bool Document::validate(string* message) const
{
bool res = true;
Expand Down
4 changes: 4 additions & 0 deletions source/MaterialXCore/Document.h
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,10 @@ class MX_CORE_API Document : public GraphElement
/// Implementation element or NodeGraph element.
vector<InterfaceElementPtr> getMatchingImplementations(const string& nodeDef) const;

/// Return the Implementation, if any, whose nodegraph attribute matches
/// the given fully-qualified nodegraph name.
ImplementationPtr getImplementationForNodeGraph(const string& nodeGraphName) const;

/// @}
/// @name UnitDef Elements
/// @{
Expand Down
12 changes: 5 additions & 7 deletions source/MaterialXCore/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ vector<PortElementPtr> Node::getDownstreamPorts() const
}
}
std::sort(downstreamPorts.begin(), downstreamPorts.end(), [](const ConstElementPtr& a, const ConstElementPtr& b)
{
{
return a->getName() > b->getName();
});
return downstreamPorts;
Expand Down Expand Up @@ -745,12 +745,10 @@ NodeDefPtr NodeGraph::getNodeDef() const
// If not directly defined look for an implementation which has a nodedef association
if (!nodedef)
{
for (auto impl : getDocument()->getImplementations())
ImplementationPtr impl = getDocument()->getImplementationForNodeGraph(getQualifiedName(getName()));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense that instead of caching the implementation reference and then getting the nodedef here, just store the nodedef reference as the lookup ?

if (impl)
{
if (impl->getNodeGraph() == getQualifiedName(getName()))
{
nodedef = impl->getNodeDef();
}
nodedef = impl->getNodeDef();
}
}
return nodedef;
Expand All @@ -775,7 +773,7 @@ vector<PortElementPtr> NodeGraph::getDownstreamPorts() const
}
}
std::sort(downstreamPorts.begin(), downstreamPorts.end(), [](const ConstElementPtr& a, const ConstElementPtr& b)
{
{
return a->getName() > b->getName();
});
return downstreamPorts;
Expand Down
2 changes: 1 addition & 1 deletion source/MaterialXGenShader/Syntax.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ void Syntax::makeValidName(string& name) const
{
std::replace_if(name.begin(), name.end(), isInvalidChar, '_');
name = replaceSubstrings(name, _invalidTokens);
if (std::find(_reservedWords.begin(), _reservedWords.end(), name) != _reservedWords.end())
if (_reservedWords.find(name) != _reservedWords.end())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a nice improvement :) good catch

{
// We append "1" here because thats the prior behavior from makeIdentifier() below when
// the reservedWords were added to the identifiers list.
Expand Down
73 changes: 73 additions & 0 deletions source/MaterialXTest/MaterialXCore/Performance.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// Copyright Contributors to the MaterialX Project
// SPDX-License-Identifier: Apache-2.0
//

#include <MaterialXTest/External/Catch/catch.hpp>

#ifdef CATCH_CONFIG_ENABLE_BENCHMARKING

#include <MaterialXCore/Definition.h>
#include <MaterialXCore/Document.h>
#include <MaterialXCore/Node.h>

namespace mx = MaterialX;

// Build a document that scales the two axes feeding NodeGraph::getNodeDef
// from PortElement::validate:
// * numImplStubs - document-root <implementation> elements; sizes the
// Document::Cache nodegraph -> implementation map.
// * numNodegraphRefs - inputs that resolve through a nodegraph; each one
// performs a cache lookup.
static mx::DocumentPtr buildScalingDocument(size_t numImplStubs, size_t numNodegraphRefs)
{
mx::DocumentPtr doc = mx::createDocument();

// A nodegraph with one internal node and one output, so that
// PortElement::getConnectedNode() resolves transitively through the
// output (see Input::getConnectedNode in Interface.cpp).
mx::NodeGraphPtr ng = doc->addNodeGraph("ng");
mx::NodePtr inner = ng->addNode("constant", "inner", "color3");
mx::OutputPtr out = ng->addOutput("out", "color3");
out->setNodeName(inner->getName());

// A host node parenting many inputs that reference the nodegraph.
mx::NodePtr host = doc->addNode("surface", "host", "surfaceshader");
for (size_t i = 0; i < numNodegraphRefs; ++i)
{
mx::InputPtr in = host->addInput("in" + std::to_string(i), "color3");
in->setNodeGraphString("ng");
in->setOutputString("out");
}

for (size_t i = 0; i < numImplStubs; ++i)
{
mx::ImplementationPtr impl = doc->addImplementation("i" + std::to_string(i));
impl->setNodeDefString("n" + std::to_string(i));
}

return doc;
}

TEST_CASE("NodeGraph getNodeDef performance", "[node][performance]")
{
BENCHMARK("validate, stubs=200 refs=100")
{
mx::DocumentPtr doc = buildScalingDocument(200, 100);
return doc->validate();
};

BENCHMARK("validate, stubs=1000 refs=100")
{
mx::DocumentPtr doc = buildScalingDocument(1000, 100);
return doc->validate();
};

BENCHMARK("validate, stubs=10000 refs=100")
{
mx::DocumentPtr doc = buildScalingDocument(10000, 100);
return doc->validate();
};
}

#endif // CATCH_CONFIG_ENABLE_BENCHMARKING