Skip to content

Commit

Permalink
EMSUSD-1975 - Optimize material libraries
Browse files Browse the repository at this point in the history
Allow deep library optimization ff a user introduces a material library that has nodes implemented via
NodeGraph that embed optimizable nodes.

Also fixed issues encountered while dealing with MaterialX namespaces.
  • Loading branch information
JGamache-autodesk committed Dec 19, 2024
1 parent ecabe06 commit cd47c0c
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 43 deletions.
151 changes: 131 additions & 20 deletions lib/mayaUsd/render/MaterialXGenOgsXml/LobePruner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,14 @@ class LobePrunerImpl
LobePrunerImpl(LobePrunerImpl&&) = delete;
LobePrunerImpl& operator=(LobePrunerImpl&&) = delete;

bool getOptimizedNodeCategory(const mx::Node& node, std::string& nodeCategory);
bool getOptimizedNodeDef(const mx::Node& node, mx::NodeDefPtr& nodeDef);

mx::StringVec getOptimizedAttributeNames(const mx::NodeDefPtr& nodeDef) const;

PXR_NS::TfToken getOptimizedNodeId(const PXR_NS::HdMaterialNode2& node);
bool isOptimizedNodeId(const PXR_NS::TfToken& nodeId);

void optimizeLibrary(const MaterialX::DocumentPtr& library);

static const std::string ND_PREFIX;
static const std::string DARK_BASE;
Expand All @@ -105,7 +109,7 @@ class LobePrunerImpl
const mx::InputPtr& input,
const mx::NodeGraphPtr& ng,
const mx::NodeDefPtr& nd);
void
mx::NodeDefPtr
ensureLibraryHasOptimizedShader(const PXR_NS::TfToken& nodeDefName, const std::string& flags);
void optimizeZeroValue(
mx::NodeGraphPtr& optimizedNodeGraph,
Expand All @@ -132,6 +136,7 @@ class LobePrunerImpl

std::unordered_map<PXR_NS::TfToken, NodeDefData, PXR_NS::TfToken::HashFunctor> _prunerData;
mx::DocumentPtr _library;
PXR_NS::TfToken::HashSet _optimizedNodeIds;
};

const std::string LobePrunerImpl::ND_PREFIX = "LPOPTIND_";
Expand Down Expand Up @@ -216,6 +221,90 @@ bool LobePrunerImpl::isLobeInput(const mx::InputPtr& input, const mx::NodeDefPtr
return true;
}

void LobePrunerImpl::optimizeLibrary(const MaterialX::DocumentPtr& library)
{
if (!_library || _prunerData.empty()) {
return;
}

std::set<std::string> allDefinedNodeGraphs;
// Go thru all NodeGraphs found in the library that have an associated NodeDef:
for (const auto& ng : library->getNodeGraphs()) {
if (ng->hasNodeDefString()) {
allDefinedNodeGraphs.insert(ng->getName());
}
}
for (const auto& impl : library->getImplementations()) {
if (impl->hasNodeGraph()) {
allDefinedNodeGraphs.insert(impl->getNodeGraph());
}
}

for (const auto& ngName : allDefinedNodeGraphs) {
const auto ng = library->getNodeGraph(ngName);
// Go thru all the nodes of that NodeGraph
for (const auto& node : ng->getNodes()) {
// Can this node be optimized?
const auto& nd = node->getNodeDef();
if (!nd) {
continue;
}

const auto ndName = PXR_NS::TfToken(nd->getName());
const auto ndIt = _prunerData.find(ndName);
if (ndIt == _prunerData.end()) {
continue;
}

// This NodeGraph contains an optimizable embedded surface shader node.
std::string flags(ndIt->second._attributeData.size(), 'x');

bool canOptimize = false;

auto attrIt = ndIt->second._attributeData.cbegin();
for (size_t i = 0; attrIt != ndIt->second._attributeData.cend(); ++attrIt, ++i) {
const auto nodeinput = node->getActiveInput(attrIt->first);
float inputValue = 0.5F;
if (nodeinput) {
// Can not optimize if connected in any way.
if (nodeinput->hasNodeName() || nodeinput->hasOutputString()
|| nodeinput->hasInterfaceName()) {
continue;
}
inputValue = nodeinput->getValue()->asA<float>();
} else {
const auto defInput = nd->getActiveInput(attrIt->first);
inputValue = defInput->getValue()->asA<float>();
}

for (const auto& optimizableValue : attrIt->second) {
if (optimizableValue.first == inputValue) {
if (inputValue == 0.0F) {
flags[i] = '0';
} else {
flags[i] = '1';
}
canOptimize = true;
}
}
}

if (canOptimize) {
const auto optimizedNodeDef = ensureLibraryHasOptimizedShader(ndName, flags);
// Replace the node with an optimized one:
const auto nsPrefix = optimizedNodeDef->hasNamespace()
? optimizedNodeDef->getNamespace() + ":"
: std::string {};

node->setCategory(nsPrefix + optimizedNodeDef->getNodeString());
if (node->hasNodeDefString()) {
node->setNodeDefString(optimizedNodeDef->getName());
}
}
}
}
}

void LobePrunerImpl::addOptimizableValue(
float value,
const mx::InputPtr& input,
Expand All @@ -242,7 +331,7 @@ void LobePrunerImpl::addOptimizableValue(
valueMap.find(value)->second.push_back(PXR_NS::TfToken(input->getParent()->getName()));
}

bool LobePrunerImpl::getOptimizedNodeCategory(const mx::Node& node, std::string& nodeCategory)
bool LobePrunerImpl::getOptimizedNodeDef(const mx::Node& node, mx::NodeDefPtr& nodeDef)
{
const auto& nd = node.getNodeDef();
if (!nd) {
Expand Down Expand Up @@ -288,8 +377,7 @@ bool LobePrunerImpl::getOptimizedNodeCategory(const mx::Node& node, std::string&
}

if (canOptimize) {
ensureLibraryHasOptimizedShader(ndName, flags);
nodeCategory = node.getCategory() + "_" + flags;
nodeDef = ensureLibraryHasOptimizedShader(ndName, flags);
return true;
}

Expand Down Expand Up @@ -356,39 +444,53 @@ PXR_NS::TfToken LobePrunerImpl::getOptimizedNodeId(const PXR_NS::HdMaterialNode2
}

if (canOptimize) {
ensureLibraryHasOptimizedShader(node.nodeTypeId, flags);
return PXR_NS::TfToken(
ND_PREFIX + nodeDef->GetFamily().GetString() + "_" + flags + "_surfaceshader");
return PXR_NS::TfToken(ensureLibraryHasOptimizedShader(node.nodeTypeId, flags)->getName());
}

return retVal;
}

void LobePrunerImpl::ensureLibraryHasOptimizedShader(
bool LobePrunerImpl::isOptimizedNodeId(const PXR_NS::TfToken& nodeId)
{
return _optimizedNodeIds.count(nodeId) != 0;
}

mx::NodeDefPtr LobePrunerImpl::ensureLibraryHasOptimizedShader(
const PXR_NS::TfToken& nodeDefName,
const std::string& flags)
{
const auto ndIt = _prunerData.find(nodeDefName);
if (ndIt == _prunerData.end()) {
return;
return {};
}

const auto originalNodeDef = _library->getNodeDef(nodeDefName.GetString());
const auto originalNodeGraph = _library->getNodeGraph(ndIt->second._nodeGraphName);
const std::string optimizedNodeName = originalNodeDef->getNodeString() + "_" + flags;
const std::string optimizedNodeDefName = ND_PREFIX + optimizedNodeName + "_surfaceshader";
if (_library->getNodeDef(optimizedNodeDefName)) {
const auto originalNodeDef = _library->getNodeDef(nodeDefName.GetString());
const auto originalNodeGraph = _library->getNodeGraph(ndIt->second._nodeGraphName);
const auto nsPrefix = originalNodeDef->hasNamespace()
? originalNodeDef->getNamespace() + mx::NAME_PREFIX_SEPARATOR
: std::string {};
auto optimizedNodeName = originalNodeDef->getNodeString() + "_" + flags;
if (!nsPrefix.empty() && optimizedNodeName.rfind(nsPrefix, 0) == 0) {
optimizedNodeName = optimizedNodeName.substr(nsPrefix.size());
}
const auto optimizedNodeNameWithNS = nsPrefix + optimizedNodeName;
const std::string optimizedNodeDefName
= nsPrefix + ND_PREFIX + optimizedNodeName + "_surfaceshader";
if (const auto existingNd = _library->getNodeDef(optimizedNodeDefName)) {
// Already there
return;
return existingNd;
}

_optimizedNodeIds.insert(PXR_NS::TfToken(optimizedNodeDefName));

auto optimizedNodeDef
= _library->addNodeDef(optimizedNodeDefName, "surfaceshader", optimizedNodeName);
optimizedNodeDef->copyContentFrom(originalNodeDef);
optimizedNodeDef->setSourceUri("");
optimizedNodeDef->setNodeString(optimizedNodeName);

auto optimizedNodeGraph = _library->addNodeGraph("NG_" + optimizedNodeName + "_surfaceshader");
auto optimizedNodeGraph
= _library->addNodeGraph(nsPrefix + "LPOPTING_" + optimizedNodeName + "_surfaceshader");
optimizedNodeGraph->copyContentFrom(originalNodeGraph);
optimizedNodeGraph->setSourceUri("");
optimizedNodeGraph->setNodeDefString(optimizedNodeDefName);
Expand All @@ -414,6 +516,8 @@ void LobePrunerImpl::ensureLibraryHasOptimizedShader(
default: continue;
}
}

return optimizedNodeDef;
}

void LobePrunerImpl::optimizeZeroValue(
Expand Down Expand Up @@ -560,9 +664,16 @@ LobePruner::Ptr LobePruner::create() { return std::make_shared<LobePruner>(); }
LobePruner::~LobePruner() = default;
LobePruner::LobePruner() = default;

bool LobePruner::getOptimizedNodeCategory(const mx::Node& node, std::string& nodeCategory)
void LobePruner::optimizeLibrary(const MaterialX::DocumentPtr& library)
{
if (_impl) {
_impl->optimizeLibrary(library);
}
}

bool LobePruner::getOptimizedNodeDef(const mx::Node& node, mx::NodeDefPtr& nodeDef)
{
return _impl ? _impl->getOptimizedNodeCategory(node, nodeCategory) : false;
return _impl ? _impl->getOptimizedNodeDef(node, nodeDef) : false;
}

mx::StringVec LobePruner::getOptimizedAttributeNames(const mx::NodeDefPtr& nodeDef) const
Expand All @@ -582,7 +693,7 @@ void LobePruner::setLibrary(const mx::DocumentPtr& library)

bool LobePruner::isOptimizedNodeId(const PXR_NS::TfToken& nodeId)
{
return nodeId.GetString().rfind(LobePrunerImpl::ND_PREFIX, 0) == 0;
return _impl ? _impl->isOptimizedNodeId(nodeId) : false;
}

const std::string& LobePruner::getOptimizedNodeDefPrefix() { return LobePrunerImpl::ND_PREFIX; }
Expand Down
20 changes: 10 additions & 10 deletions lib/mayaUsd/render/MaterialXGenOgsXml/LobePruner.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,19 @@ class MAYAUSD_CORE_PUBLIC LobePruner
*/
void setLibrary(const mx::DocumentPtr& library);

/*! Traverses and optimizes in place all NodeGraphs found in the library. Useful if a library
* contains NodeGraphs that embed optimizable nodes.
* @param[in] library is the library used to generate shaders.
*/
void optimizeLibrary(const MaterialX::DocumentPtr& library);

/*! Checks if a node is optimizable and if this is the case, create the optimized NodeDef and
* NodeGraph in the library and return the optimized node category. An optimized node category
* will consist of the name of the original category followed by a series of characters
* describing which attibutes were optimized:
* - 'x' that attribute was not optimized (intermediate value or connected)
* - '0' a zero value was optimized
* - '1' a one value was optimized
* The ordered list of attributes names can be found by calling getOptimizedAttributeNames().
* NodeGraph in the library and return the optimized NodeDef.
* @param[in] node is a node we want to optimize. All nodes are welcome.
* @param[out] nodeCategory is the node category of the optimized node.
* @param[out] nodeDef is the NodeDef of the optimized node.
* \return true if an optimization was found
*/
bool getOptimizedNodeCategory(const mx::Node& node, std::string& nodeCategory);
bool getOptimizedNodeDef(const mx::Node& node, mx::NodeDefPtr& nodeDef);

/*! Get the list of attribute names that are optimization targets for a specific NodeDef.
* @param[in] nodeDef is preferably node definition that has previously been optimized, but all
Expand Down Expand Up @@ -95,7 +95,7 @@ class MAYAUSD_CORE_PUBLIC LobePruner
* @param[in] nodeId is a node:id we want to check.
* \return true if that node was generated by a LobePruner.
*/
static bool isOptimizedNodeId(const PXR_NS::TfToken& nodeId);
bool isOptimizedNodeId(const PXR_NS::TfToken& nodeId);

/*! Returns the NodeDef prefix common to all LobePruner optimized definitions.
* \return the LobePruner NodeDef prefix
Expand Down
12 changes: 7 additions & 5 deletions lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,13 @@ mx::NodePtr TopoNeutralGraph::cloneNode(const mx::Node& node, mx::GraphElement&
if (!nodeDef) {
throw mx::Exception("Ambiguous node is not fully resolvable");
}
std::string optimizedNodeCategory;
if (_lobePruner && _lobePruner->getOptimizedNodeCategory(node, optimizedNodeCategory)) {
destNode->setCategory(optimizedNodeCategory);
destNode->setNodeDefString(
LobePruner::getOptimizedNodeDefPrefix() + optimizedNodeCategory + "_surfaceshader");
mx::NodeDefPtr optimizedNodeDef;
if (_lobePruner && _lobePruner->getOptimizedNodeDef(node, optimizedNodeDef)) {
const auto nsPrefix = optimizedNodeDef->hasNamespace()
? optimizedNodeDef->getNamespace() + ":"
: std::string {};
destNode->setCategory(nsPrefix + optimizedNodeDef->getNodeString());
destNode->setNodeDefString(optimizedNodeDef->getName());
for (const auto& attrName : _lobePruner->getOptimizedAttributeNames(nodeDef)) {
_optimizedAttributes.push_back(node.getNamePath() + "." + attrName);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/mayaUsd/render/vp2RenderDelegate/material.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ struct _MaterialXData
#if MX_COMBINED_VERSION >= 13808
_lobePruner = MaterialXMaya::ShaderGenUtil::LobePruner::create();
_lobePruner->setLibrary(_mtlxLibrary);
_lobePruner->optimizeLibrary(_mtlxLibrary);

// TODO: Optimize published shaders.
// SCENARIO: User publishes a shader with a NodeGraph implementation that encapsulates a
Expand Down Expand Up @@ -3038,8 +3039,7 @@ MHWRender::MShaderInstance* HdVP2Material::CompiledNetwork::_CreateMaterialXShad
const mx::FileSearchPath& crLibrarySearchPath(_GetMaterialXData()._mtlxSearchPath);
#if MX_COMBINED_VERSION >= 13808
if (mtlxSdrNode
|| MaterialXMaya::ShaderGenUtil::LobePruner::isOptimizedNodeId(
surfTerminal->nodeTypeId)) {
|| _GetMaterialXData()._lobePruner->isOptimizedNodeId(surfTerminal->nodeTypeId)) {
#else
if (mtlxSdrNode) {
#endif
Expand Down
7 changes: 7 additions & 0 deletions lib/mayaUsdAPI/render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ void LobePruner::setLibrary(const MaterialX::DocumentPtr& library)
}
}

void LobePruner::optimizeLibrary(const MaterialX::DocumentPtr& library)
{
if (_imp && _imp->_lobePruner) {
_imp->_lobePruner->optimizeLibrary(library);
}
}

struct TopoNeutralGraphImpl
{
TopoNeutralGraphImpl(const MaterialX::ElementPtr& material)
Expand Down
6 changes: 6 additions & 0 deletions lib/mayaUsdAPI/render.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ class MAYAUSD_API_PUBLIC LobePruner
*/
void setLibrary(const MaterialX::DocumentPtr& library);

/*! Traverses and optimizes in place all NodeGraphs found in the library. Useful if a library
* contains NodeGraphs that embed optimizable nodes.
* @param[in] library is the library used to generate shaders.
*/
void optimizeLibrary(const MaterialX::DocumentPtr& library);

private:
LobePruner();

Expand Down
12 changes: 6 additions & 6 deletions test/lib/mayaUsd/utils/test_ShaderGenUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,17 +179,17 @@ TEST(ShaderGenUtils, lobePruner)

const auto node = doc->addNode("standard_surface", "bob", "surfaceshader");

std::string optimizedCategory;
ASSERT_TRUE(lobePruner->getOptimizedNodeCategory(*node, optimizedCategory));
mx::NodeDefPtr optimizedNodeDef;
ASSERT_TRUE(lobePruner->getOptimizedNodeDef(*node, optimizedNodeDef));
// An x means can not optimize on that attribute
// A 0 means we optimized due to this value being zero
ASSERT_EQ(optimizedCategory, "standard_surface_x0000x00x000");
ASSERT_EQ(optimizedNodeDef->getNodeString(), "standard_surface_x0000x00x000");

auto input = node->addInputFromNodeDef("subsurface");
input->setValueString("1.0");
ASSERT_TRUE(lobePruner->getOptimizedNodeCategory(*node, optimizedCategory));
ASSERT_TRUE(lobePruner->getOptimizedNodeDef(*node, optimizedNodeDef));
// Now have a 1 for subsurface since we can also optimize the 1 value for mix nodes.
ASSERT_EQ(optimizedCategory, "standard_surface_x0000x00x010");
ASSERT_EQ(optimizedNodeDef->getNodeString(), "standard_surface_x0000x00x010");

PXR_NS::HdMaterialNode2 usdNode;
usdNode.nodeTypeId = PXR_NS::TfToken("ND_standard_surface_surfaceshader");
Expand All @@ -198,7 +198,7 @@ TEST(ShaderGenUtils, lobePruner)
optimizedNodeId.GetString(),
sgu::LobePruner::getOptimizedNodeDefPrefix()
+ "standard_surface_x0000x00x000_surfaceshader");
ASSERT_TRUE(sgu::LobePruner::isOptimizedNodeId(optimizedNodeId));
ASSERT_TRUE(lobePruner->isOptimizedNodeId(optimizedNodeId));

usdNode.nodeTypeId = PXR_NS::TfToken("ND_mix_surfaceshader");
optimizedNodeId = lobePruner->getOptimizedNodeId(usdNode);
Expand Down

0 comments on commit cd47c0c

Please sign in to comment.