From 23aeb977d5521484b024b776e908d151f607c94a Mon Sep 17 00:00:00 2001 From: Jerry Gamache Date: Mon, 6 Nov 2023 15:15:20 -0500 Subject: [PATCH 1/3] LOOKDEVX-2124 - MaterialX Topo Handler Implements a topo neutral graph generator. This allows detecting that two native MaterialX shading graphs are topologically equivalent, generate universal shading code, and provide the right mapping to find the original node name corresponding to a renamed node. Also adds proper exports to allow rebuilding the old MaterialX Maya surface node using this new functionnality. This also requires fixing the generated code to properly handle the "texcoord" nodes variables used in the shader. --- .../render/MaterialXGenOgsXml/CMakeLists.txt | 6 + .../GlslFragmentGenerator.cpp | 17 +- .../render/MaterialXGenOgsXml/OgsFragment.cpp | 2 +- .../render/MaterialXGenOgsXml/OgsFragment.h | 4 +- .../MaterialXGenOgsXml/OgsXmlGenerator.h | 4 +- .../MaterialXGenOgsXml/ShaderGenUtil.cpp | 383 ++++++++++++++++++ .../render/MaterialXGenOgsXml/ShaderGenUtil.h | 76 ++++ .../render/vp2RenderDelegate/material.cpp | 43 +- test/lib/mayaUsd/utils/CMakeLists.txt | 21 + .../materialx_test_data/Channel1_topo.mtlx | 15 + .../materialx_test_data/Channel4_topo.mtlx | 15 + .../materialx_test_data/Interface2_topo.mtlx | 21 + .../materialx_test_data/MultiOut6_topo.mtlx | 13 + .../materialx_test_data/topology_tests.mtlx | 161 ++++++++ .../lib/mayaUsd/utils/test_ShaderGenUtils.cpp | 74 ++++ 15 files changed, 807 insertions(+), 48 deletions(-) create mode 100644 lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.cpp create mode 100644 lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.h create mode 100644 test/lib/mayaUsd/utils/materialx_test_data/Channel1_topo.mtlx create mode 100644 test/lib/mayaUsd/utils/materialx_test_data/Channel4_topo.mtlx create mode 100644 test/lib/mayaUsd/utils/materialx_test_data/Interface2_topo.mtlx create mode 100644 test/lib/mayaUsd/utils/materialx_test_data/MultiOut6_topo.mtlx create mode 100644 test/lib/mayaUsd/utils/materialx_test_data/topology_tests.mtlx create mode 100644 test/lib/mayaUsd/utils/test_ShaderGenUtils.cpp diff --git a/lib/mayaUsd/render/MaterialXGenOgsXml/CMakeLists.txt b/lib/mayaUsd/render/MaterialXGenOgsXml/CMakeLists.txt index add21cdc73..0f4f3e79ba 100644 --- a/lib/mayaUsd/render/MaterialXGenOgsXml/CMakeLists.txt +++ b/lib/mayaUsd/render/MaterialXGenOgsXml/CMakeLists.txt @@ -7,6 +7,7 @@ target_sources(${PROJECT_NAME} GlslOcioNodeImpl.cpp OgsFragment.cpp OgsXmlGenerator.cpp + ShaderGenUtil.cpp Nodes/SurfaceNodeMaya.cpp Nodes/TexcoordNodeMaya.cpp PugiXML/pugixml.cpp @@ -18,6 +19,7 @@ set(HEADERS GlslOcioNodeImpl.h OgsFragment.h OgsXmlGenerator.h + ShaderGenUtil.h ) # ----------------------------------------------------------------------------- @@ -65,6 +67,10 @@ mayaUsd_promoteHeaderList(HEADERS ${HEADERS} SUBDIR render/MaterialXGenOgsXml) # install # ----------------------------------------------------------------------------- +install(FILES ${HEADERS} + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/${PROJECT_NAME}/render/MaterialXGenOgsXml +) + install(FILES ${NODE_DECLARATIONS} DESTINATION ${CMAKE_INSTALL_PREFIX}/libraries/adsk/maya ) diff --git a/lib/mayaUsd/render/MaterialXGenOgsXml/GlslFragmentGenerator.cpp b/lib/mayaUsd/render/MaterialXGenOgsXml/GlslFragmentGenerator.cpp index c6128f6276..6280198b87 100644 --- a/lib/mayaUsd/render/MaterialXGenOgsXml/GlslFragmentGenerator.cpp +++ b/lib/mayaUsd/render/MaterialXGenOgsXml/GlslFragmentGenerator.cpp @@ -82,8 +82,10 @@ void fixupVertexDataInstance(ShaderStage& stage) static const std::string primvarParamSource = "vec([23]) [$](" + d(HW::T_IN_GEOMPROP) + "_[A-Za-z0-9_]+)"; - - static const std::regex primvarParamRegex(primvarParamSource.c_str()); + static const std::regex primvarParamRegex(primvarParamSource.c_str()); + static const std::string texcoordParamSource + = "vec([23]) [$](" + d(HW::T_TEXCOORD) + "_[0-9]+)"; + static const std::regex texcoordParamRegex(texcoordParamSource.c_str()); // Find keywords: (as text) // @@ -94,16 +96,21 @@ void fixupVertexDataInstance(ShaderStage& stage) // PIX_IN.(NAME) PIX_IN.st // - static const std::string vdCleanupSource = "[$]" + d(HW::T_VERTEX_DATA_INSTANCE) + "[.][$]" + static const std::string vdCleanGeoSource = "[$]" + d(HW::T_VERTEX_DATA_INSTANCE) + "[.][$]" + d(HW::T_IN_GEOMPROP) + "_([A-Za-z0-9_]+)"; + static const std::regex vdCleanGeoRegex(vdCleanGeoSource.c_str()); - static const std::regex vdCleanupRegex(vdCleanupSource.c_str()); + static const std::string vdCleanTexSource + = "[$]" + d(HW::T_VERTEX_DATA_INSTANCE) + "[.][$](" + d(HW::T_TEXCOORD) + "_[0-9]+)"; + static const std::regex vdCleanTexRegex(vdCleanTexSource.c_str()); std::string code = stage.getSourceCode(); code = std::regex_replace(code, paramRegex, "vec3 unused_$1"); code = std::regex_replace(code, vtxRegex, "$$$1( PIX_IN.$$$1 )"); code = std::regex_replace(code, primvarParamRegex, "vec$1 unused_$2"); - code = std::regex_replace(code, vdCleanupRegex, "PIX_IN.$1"); + code = std::regex_replace(code, texcoordParamRegex, "vec$1 unused_$2"); + code = std::regex_replace(code, vdCleanGeoRegex, "PIX_IN.$1"); + code = std::regex_replace(code, vdCleanTexRegex, "PIX_IN.$1"); #if MX_COMBINED_VERSION >= 13804 stage.setSourceCode(code); diff --git a/lib/mayaUsd/render/MaterialXGenOgsXml/OgsFragment.cpp b/lib/mayaUsd/render/MaterialXGenOgsXml/OgsFragment.cpp index 917d3af48e..7db091e8b1 100644 --- a/lib/mayaUsd/render/MaterialXGenOgsXml/OgsFragment.cpp +++ b/lib/mayaUsd/render/MaterialXGenOgsXml/OgsFragment.cpp @@ -462,7 +462,7 @@ bool OgsFragment::isElementAShader() const bool OgsFragment::isTransparent() const { - return _glslShader && _glslShader->hasAttribute(mx::HW::ATTR_TRANSPARENT); + return isTransparentSurface(_element, mx::GlslShaderGenerator::TARGET); } mx::ImageSamplingProperties diff --git a/lib/mayaUsd/render/MaterialXGenOgsXml/OgsFragment.h b/lib/mayaUsd/render/MaterialXGenOgsXml/OgsFragment.h index aaa1ad3fb1..ff026dbed4 100644 --- a/lib/mayaUsd/render/MaterialXGenOgsXml/OgsFragment.h +++ b/lib/mayaUsd/render/MaterialXGenOgsXml/OgsFragment.h @@ -4,6 +4,8 @@ /// @file /// OGS fragment wrapper. +#include + #include #include #include @@ -16,7 +18,7 @@ namespace MaterialXMaya { /// and outputs and embedding source code in one or potentially multiple target /// shading languages (GLSL is the only such language currently supported). /// -class OgsFragment +class MAYAUSD_CORE_PUBLIC OgsFragment { public: /// Creates a local GLSL fragment generator diff --git a/lib/mayaUsd/render/MaterialXGenOgsXml/OgsXmlGenerator.h b/lib/mayaUsd/render/MaterialXGenOgsXml/OgsXmlGenerator.h index 3be273147e..0bcd296394 100644 --- a/lib/mayaUsd/render/MaterialXGenOgsXml/OgsXmlGenerator.h +++ b/lib/mayaUsd/render/MaterialXGenOgsXml/OgsXmlGenerator.h @@ -8,11 +8,13 @@ /// @file /// OGS XML fragments generator +#include + #include MATERIALX_NAMESPACE_BEGIN -class OgsXmlGenerator +class MAYAUSD_CORE_PUBLIC OgsXmlGenerator { public: /// Generate OSG XML for the given shader fragments, output to the given stream. diff --git a/lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.cpp b/lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.cpp new file mode 100644 index 0000000000..dcdfff6417 --- /dev/null +++ b/lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.cpp @@ -0,0 +1,383 @@ +#include "ShaderGenUtil.h" + +#include + +#include + +#include + +namespace MaterialXMaya { +namespace ShaderGenUtil { + +namespace { +const std::string SURFACEMATERIAL_CATEGORY("surfacematerial"); +const std::string SURFACESHADER_TYPE("surfaceshader"); + +const std::set _mtlxTopoNodeSet = { + // Topo affecting nodes due to object/model/world space parameter + "position", + "normal", + "tangent", + "bitangent", + // Topo affecting nodes due to channel index. + "texcoord", + // Color at vertices also affect topo, but we have not locked a naming scheme to go from index + // based to name based as we did for UV sets. We will mark them as topo-affecting, but there is + // nothing we can do to link them correctly to a primvar without specifying a naming scheme. + "geomcolor", + // Geompropvalue are the best way to reference a primvar by name. The primvar name is + // topo-affecting. Note that boolean and string are not supported by the GLSL codegen. + "geompropvalue", + // Swizzles are inlined into the codegen and affect topology. + "swizzle", + // Conversion nodes: + "convert", + // Constants: they get inlined in the source. + "constant", +#if MX_COMBINED_VERSION < 13808 + // Switch, unless all inputs are connected. Bug was fixed in 1.38.8. + "switch", +#endif +#if MX_COMBINED_VERSION == 13807 + // Dot became topological in 1.38.7. Reverted in 1.38.8. + // Still topological for filename though. + "dot", +#endif +}; +} // namespace + +const std::string& TopoNeutralGraph::getMaterialName() +{ + // A material node is always the first node created and will be named N0 + static const std::string kMaterialName("N0"); + return kMaterialName; +} + +TopoNeutralGraph::TopoNeutralGraph(const mx::ElementPtr& material) +{ + if (!material) { + throw mx::Exception("Invalid material element"); + } + std::string message; + if (!material->validate(&message)) { + const std::string step("Error in original graph:\n"); + throw mx::Exception(step + message); + } + _doc = mx::createDocument(); + + auto inputDoc = material->getDocument(); + + mx::NodePtr materialNode = material->asA(); + if (!materialNode) { + // We might handle standalone "Output" element at a later stage + throw mx::Exception("Material element is not a node."); + } + + std::list nodesToTraverse; + mx::NodePtr surfaceShader; + if (materialNode->getCategory() == SURFACEMATERIAL_CATEGORY) { + auto dupMaterial = cloneNode(*materialNode, *_doc); + auto surfaceInput = materialNode->getInput(SURFACESHADER_TYPE); + if (!surfaceInput || !surfaceInput->getConnectedNode()) { + throw mx::Exception("Unconnected material node."); + } + surfaceShader = surfaceInput->getConnectedNode(); + if (!surfaceShader) { + throw mx::Exception("Unconnected material node."); + } + auto dupSurfaceShader = cloneNode(*surfaceShader, *_doc); + dupMaterial->addInput(SURFACESHADER_TYPE, SURFACESHADER_TYPE) + ->setConnectedNode(dupSurfaceShader); + nodesToTraverse.push_back(surfaceShader); + } else { + if (materialNode->getType() != SURFACESHADER_TYPE) { + throw mx::Exception("Material shader node is not a surfaceshader."); + } + auto dupMaterial = _doc->addMaterialNode("N" + std::to_string(_nodeIndex)); + dupMaterial->setNodeDefString("ND_surfacematerial"); + ++_nodeIndex; + surfaceShader = materialNode; + auto dupSurfaceShader = cloneNode(*materialNode, *_doc); + dupMaterial->addInput(SURFACESHADER_TYPE, SURFACESHADER_TYPE) + ->setConnectedNode(dupSurfaceShader); + nodesToTraverse.push_back(materialNode); + } + + // Breadth-first traversal, in order of NodeDef attributes, to insure repeatability + while (!nodesToTraverse.empty()) { + const mx::Node& sourceNode = *nodesToTraverse.front(); + nodesToTraverse.pop_front(); + + auto destNodeIt = _nodeMap.find(sourceNode.getNamePath()); + mx::NodePtr destNode; + if (destNodeIt != _nodeMap.end()) { + destNode = destNodeIt->second; + } else { + if (!_nodeGraph) { + _nodeGraph = _doc->addNodeGraph("NG0"); + } + destNode = cloneNode(sourceNode, *_nodeGraph); + } + + auto sourceNodeDef = sourceNode.getNodeDef(); + if (!sourceNodeDef) { + throw mx::Exception("Could not find NodeDef."); + } + + const bool isTopological = isTopologicalNodeDef(*sourceNodeDef); + for (const auto& defInput : sourceNodeDef->getActiveInputs()) { + auto sourceInput = sourceNode.getInput(defInput->getName()); + if (!sourceInput) { + continue; + } + + auto connectedNode = sourceInput->getConnectedNode(); + if (connectedNode) { + auto destConnectedIt = _nodeMap.find(connectedNode->getNamePath()); + mx::NodePtr destConnectedNode; + if (destConnectedIt != _nodeMap.end()) { + destConnectedNode = destConnectedIt->second; + } else { + if (!_nodeGraph) { + _nodeGraph = _doc->addNodeGraph("NG0"); + } + destConnectedNode = cloneNode(*connectedNode, *_nodeGraph); + nodesToTraverse.push_back(connectedNode); + } + + const std::string channelInfo = gatherChannels(*sourceInput); + const std::string outputString = gatherOutput(*sourceInput); + + if (sourceNode != *surfaceShader) { + cloneConnection( + *sourceInput, *destNode, destConnectedNode, channelInfo, outputString); + } else { + cloneNodeGraphConnection( + *sourceInput, *destNode, destConnectedNode, channelInfo, outputString); + } + } else if (isTopological) { + std::string valueString = sourceInput->getValueString(); + if (valueString.empty()) { + const auto interfaceInput = sourceInput->getInterfaceInput(); + if (interfaceInput) { + valueString = interfaceInput->getValueString(); + } + } + if (!valueString.empty()) { + auto destInput + = destNode->addInput(sourceInput->getName(), sourceInput->getType()); + destInput->setValueString(valueString); + } + } + } + } +} + +mx::NodePtr TopoNeutralGraph::cloneNode(const mx::Node& node, mx::GraphElement& container) +{ + auto destNode + = container.addNode(node.getCategory(), "N" + std::to_string(_nodeIndex), node.getType()); + ++_nodeIndex; + _nodeMap.insert({ node.getNamePath(), destNode }); + _nodeMap.insert({ node.getNamePath(), destNode }); + _pathMap.insert({ destNode->getNamePath(), node.getNamePath() }); + // Always be explicit on the NodeDef: + auto nodeDef = node.getNodeDef(); + if (!nodeDef) { + throw mx::Exception("Ambiguous node is not fully resolvable"); + } + destNode->setNodeDefString(nodeDef->getName()); + return destNode; +} + +const std::string& TopoNeutralGraph::getOriginalPath(const std::string& topoPath) const +{ + auto it = _pathMap.find(topoPath); + if (it == _pathMap.end()) { + throw mx::Exception("Could not find original path for " + topoPath); + } + return it->second; +} + +bool TopoNeutralGraph::isTopologicalNodeDef(const mx::NodeDef& nodeDef) +{ + // This is where we need to remove all these hardcoded names and instead ask the shadergen about + // the info. Requires a shadergen that can tell if a node is topological (usually C++ nodes that + // have custom shader code that varies when input value varies) + + // This is the hardcoded list for the GLSL shader generator: +#if MX_COMBINED_VERSION >= 13807 + // Dot filename is always topological to prevent creating extra OpenGL samplers in the + // generated OpenGL code. + if (nodeDef.getName() == "ND_dot_filename") + return true; +#endif + return _mtlxTopoNodeSet.find(nodeDef.getNodeString()) != _mtlxTopoNodeSet.cend(); +} + +mx::DocumentPtr TopoNeutralGraph::getDocument() const { return _doc; } + +mx::OutputPtr +TopoNeutralGraph::findNodeGraphOutput(const mx::Input& input, const std::string& outputName) +{ + auto sourceNode = input.getParent(); + if (!sourceNode || !sourceNode->isA()) { + return nullptr; + } + + auto scope = sourceNode->getParent(); + if (!scope || !scope->isA()) { + return nullptr; + } + + auto nodeGraph = scope->asA()->getNodeGraph(input.getNodeGraphString()); + if (!nodeGraph) { + return nullptr; + } + + return nodeGraph->getOutput(outputName); +} + +std::string TopoNeutralGraph::gatherChannels(const mx::Input& input) +{ + // The info we seek might be on the interface of a standalone NodeGraph: + const auto interfaceInput = input.getInterfaceInput(); + const auto& ngInput = interfaceInput ? *interfaceInput : input; + + std::string channelInfo = ngInput.getChannels(); + + if (!ngInput.hasNodeGraphString()) { + if (ngInput.hasNodeName()) { + return channelInfo; + } else { + throw mx::Exception("We do not support standalone Output elements"); + } + } + + // See if we have extra channels on the NodeGraph output: + auto output = findNodeGraphOutput(ngInput, ngInput.getOutputString()); + if (!output) { + throw mx::Exception("Could not find nodegraph"); + } + + const std::string outputChannels = output->getChannels(); + if (outputChannels.empty()) { + return channelInfo; + } else if (channelInfo.empty()) { + return outputChannels; + } + + // Here we must combine the channels. + std::string combinedChannels; + combinedChannels.reserve(channelInfo.size()); + + for (const char c : channelInfo) { + switch (c) { + case '0': + case '1': combinedChannels.push_back(c); break; + case 'r': + case 'x': + // We know from above the string is not empty. + combinedChannels.push_back(outputChannels[0]); + break; + case 'g': + case 'y': + if (outputChannels.size() < 2) { + throw mx::Exception("Missing channels in outputChannels"); + } + combinedChannels.push_back(outputChannels[1]); + break; + case 'b': + case 'z': + if (outputChannels.size() < 3) { + throw mx::Exception("Missing channels in outputChannels"); + } + combinedChannels.push_back(outputChannels[2]); + break; + case 'a': + case 'w': + if (outputChannels.size() < 4) { + throw mx::Exception("Missing channels in outputChannels"); + } + combinedChannels.push_back(outputChannels[3]); + break; + default: throw mx::Exception("Invalid channel name"); + } + } + return combinedChannels; +} + +std::string TopoNeutralGraph::gatherOutput(const mx::Input& input) +{ + // The info we seek might be on the interface of a standalone NodeGraph: + const auto interfaceInput = input.getInterfaceInput(); + const auto& ngInput = interfaceInput ? *interfaceInput : input; + + std::string outputString = ngInput.getOutputString(); + + if (!ngInput.hasNodeGraphString()) { + if (ngInput.hasNodeName()) { + return outputString; + } else { + throw mx::Exception("We do not support standalone Output elements"); + } + } + + // See if we have extra channels on the NodeGraph output: + auto output = findNodeGraphOutput(ngInput, ngInput.getOutputString()); + if (!output) { + throw mx::Exception("Could not find nodegraph"); + } + + return output->getOutputString(); +} + +void TopoNeutralGraph::cloneConnection( + const mx::Input& sourceInput, + mx::Node& destNode, + mx::NodePtr& destConnectedNode, + const std::string& channelInfo, + const std::string& output) +{ + auto destInput = destNode.addInput(sourceInput.getName(), sourceInput.getType()); + destInput->setConnectedNode(destConnectedNode); + if (!channelInfo.empty()) { + destInput->setChannels(channelInfo); + } + if (!output.empty()) { + destInput->setOutputString(output); + } +} + +void TopoNeutralGraph::cloneNodeGraphConnection( + const mx::Input& sourceInput, + mx::Node& destNode, + mx::NodePtr& destConnectedNode, + const std::string& channelInfo, + const std::string& output) +{ + std::string outputKey = destConnectedNode->getName() + "(t)" + sourceInput.getType() + "(c)" + + channelInfo + "(o)" + output; + mx::OutputPtr graphOutput; + auto outputIt = _outputMap.find(outputKey); + if (outputIt != _outputMap.end()) { + graphOutput = outputIt->second; + } else { + graphOutput + = _nodeGraph->addOutput("O" + std::to_string(_outputIndex), sourceInput.getType()); + if (!channelInfo.empty()) { + graphOutput->setChannels(channelInfo); + } + if (!output.empty()) { + graphOutput->setOutputString(output); + } + ++_outputIndex; + _outputMap.insert({ outputKey, graphOutput }); + graphOutput->setConnectedNode(destConnectedNode); + auto destInput = destNode.addInput(sourceInput.getName(), sourceInput.getType()); + destInput->setConnectedOutput(graphOutput); + } +} + +} // namespace ShaderGenUtil +} // namespace MaterialXMaya \ No newline at end of file diff --git a/lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.h b/lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.h new file mode 100644 index 0000000000..4438430e18 --- /dev/null +++ b/lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.h @@ -0,0 +1,76 @@ +#ifndef MATERIALX_MAYA_SHADERGENUTIL_H +#define MATERIALX_MAYA_SHADERGENUTIL_H + +/// @file +/// Helpers + +#include + +#include + +#include + +namespace mx = MaterialX; +namespace MaterialXMaya { +namespace ShaderGenUtil { + +/// Topo-neutral graph duplicator. Creates a topologically neutral copy of a shading graph: + +class MAYAUSD_CORE_PUBLIC TopoNeutralGraph +{ +public: + explicit TopoNeutralGraph(const mx::ElementPtr& material); + ~TopoNeutralGraph() = default; + + TopoNeutralGraph() = delete; + + TopoNeutralGraph(const TopoNeutralGraph&) = default; + TopoNeutralGraph& operator=(const TopoNeutralGraph&) = default; + TopoNeutralGraph(TopoNeutralGraph&&) = default; + TopoNeutralGraph& operator=(TopoNeutralGraph&&) = default; + + static bool isTopologicalNodeDef(const mx::NodeDef& nodeDef); + + mx::DocumentPtr getDocument() const; + static const std::string& getMaterialName(); + const std::string& getOriginalPath(const std::string& topoPath) const; + +private: + mx::NodePtr cloneNode(const mx::Node& node, mx::GraphElement& container); + mx::OutputPtr findNodeGraphOutput(const mx::Input& input, const std::string& outputName); + std::string gatherChannels(const mx::Input& input); + std::string gatherOutput(const mx::Input& input); + void cloneConnection( + const mx::Input& sourceInput, + mx::Node& destNode, + mx::NodePtr& destConnectedNode, + const std::string& channelInfo, + const std::string& output); + void cloneNodeGraphConnection( + const mx::Input& sourceInput, + mx::Node& destNode, + mx::NodePtr& destConnectedNode, + const std::string& channelInfo, + const std::string& output); + + // The topo neutral document we are trying to create. + mx::DocumentPtr _doc; + // This topo neutral document will store all ancillary nodes in a NodeGraph + mx::NodeGraphPtr _nodeGraph; + // Since we anonymize the node names, we need a map from original name + // to the duplicated node. + using TNodeMap = std::map; + TNodeMap _nodeMap; + size_t _nodeIndex = 0; + // We also make sure to create the minimal number of outputs on the NodeGraph. + using TOutputMap = std::map; + TOutputMap _outputMap; + size_t _outputIndex = 0; + // String mapping from topo path to original path: + std::unordered_map _pathMap; +}; + +} // namespace ShaderGenUtil +} // namespace MaterialXMaya + +#endif diff --git a/lib/mayaUsd/render/vp2RenderDelegate/material.cpp b/lib/mayaUsd/render/vp2RenderDelegate/material.cpp index 1806bdfdf2..a0ed10ec9b 100644 --- a/lib/mayaUsd/render/vp2RenderDelegate/material.cpp +++ b/lib/mayaUsd/render/vp2RenderDelegate/material.cpp @@ -66,6 +66,7 @@ #include #include #include +#include #include #include @@ -419,38 +420,6 @@ TF_DEFINE_PRIVATE_TOKENS( (color4) ); -const std::set _mtlxTopoNodeSet = { - // Topo affecting nodes due to object/model/world space parameter - "position", - "normal", - "tangent", - "bitangent", - // Topo affecting nodes due to channel index. - "texcoord", - // Color at vertices also affect topo, but we have not locked a naming scheme to go from index - // based to name based as we did for UV sets. We will mark them as topo-affecting, but there is - // nothing we can do to link them correctly to a primvar without specifying a naming scheme. - "geomcolor", - // Geompropvalue are the best way to reference a primvar by name. The primvar name is - // topo-affecting. Note that boolean and string are not supported by the GLSL codegen. - "geompropvalue", - // Swizzles are inlined into the codegen and affect topology. - "swizzle", - // Conversion nodes: - "convert", - // Constants: they get inlined in the source. - "constant", -#if MX_COMBINED_VERSION < 13808 - // Switch, unless all inputs are connected. Bug was fixed in 1.38.8. - "switch", -#endif -#if MX_COMBINED_VERSION == 13807 - // Dot became topological in 1.38.7. Reverted in 1.38.8. - // Still topological for filename though. - "dot", -#endif -}; - // These attribute names usually indicate we have a source color space to handle. const auto _mtlxKnownColorSpaceAttrs = std::vector { _tokens->sourceColorSpace, _mtlxTokens->colorSpace }; @@ -529,13 +498,7 @@ bool _IsTopologicalNode(const HdMaterialNode2& inNode) mx::NodeDefPtr nodeDef = _GetMaterialXData()._mtlxLibrary->getNodeDef(inNode.nodeTypeId.GetString()); if (nodeDef) { -#if MX_COMBINED_VERSION >= 13807 - // Dot filename is always topological to prevent creating extra OpenGL samplers in the - // generated OpenGL code. - if (nodeDef->getName() == "ND_dot_filename") - return true; -#endif - return _mtlxTopoNodeSet.find(nodeDef->getNodeString()) != _mtlxTopoNodeSet.cend(); + return MaterialXMaya::ShaderGenUtil::TopoNeutralGraph::isTopologicalNodeDef(*nodeDef); } return false; } @@ -3302,7 +3265,7 @@ void HdVP2Material::CompiledNetwork::_UpdateShaderInstance( mx::NodeDefPtr nodeDef = _GetMaterialXData()._mtlxLibrary->getNodeDef(node.identifier.GetString()); if (nodeDef - && _mtlxTopoNodeSet.find(nodeDef->getNodeString()) != _mtlxTopoNodeSet.cend()) { + && MaterialXMaya::ShaderGenUtil::TopoNeutralGraph::isTopologicalNodeDef(*nodeDef)) { // A topo node does not emit editable parameters: continue; } diff --git a/test/lib/mayaUsd/utils/CMakeLists.txt b/test/lib/mayaUsd/utils/CMakeLists.txt index dcaad8de1c..fcd0bbb701 100644 --- a/test/lib/mayaUsd/utils/CMakeLists.txt +++ b/test/lib/mayaUsd/utils/CMakeLists.txt @@ -111,4 +111,25 @@ if(IS_WINDOWS) testSplitString testSplitString.cpp ) + + if(CMAKE_WANT_MATERIALX_BUILD) + add_mayaUsdLibUtils_test( + test_ShaderGenUtils + test_ShaderGenUtils.cpp + ) + + target_compile_definitions(test_ShaderGenUtils + PRIVATE + MATERIALX_TEST_DATA="${CMAKE_CURRENT_SOURCE_DIR}/materialx_test_data" + MATERIALX_TEST_OUTPUT="${CMAKE_BINARY_DIR}/test/Temporary/test_ShaderGenUtils" + ) + + target_link_libraries(test_ShaderGenUtils + PRIVATE + usdMtlx + MaterialXCore + MaterialXFormat + ) + + endif() endif() diff --git a/test/lib/mayaUsd/utils/materialx_test_data/Channel1_topo.mtlx b/test/lib/mayaUsd/utils/materialx_test_data/Channel1_topo.mtlx new file mode 100644 index 0000000000..77ab07a0b1 --- /dev/null +++ b/test/lib/mayaUsd/utils/materialx_test_data/Channel1_topo.mtlx @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/test/lib/mayaUsd/utils/materialx_test_data/Channel4_topo.mtlx b/test/lib/mayaUsd/utils/materialx_test_data/Channel4_topo.mtlx new file mode 100644 index 0000000000..615a84ebec --- /dev/null +++ b/test/lib/mayaUsd/utils/materialx_test_data/Channel4_topo.mtlx @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/test/lib/mayaUsd/utils/materialx_test_data/Interface2_topo.mtlx b/test/lib/mayaUsd/utils/materialx_test_data/Interface2_topo.mtlx new file mode 100644 index 0000000000..8a6ad6ec5d --- /dev/null +++ b/test/lib/mayaUsd/utils/materialx_test_data/Interface2_topo.mtlx @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/test/lib/mayaUsd/utils/materialx_test_data/MultiOut6_topo.mtlx b/test/lib/mayaUsd/utils/materialx_test_data/MultiOut6_topo.mtlx new file mode 100644 index 0000000000..3348458bee --- /dev/null +++ b/test/lib/mayaUsd/utils/materialx_test_data/MultiOut6_topo.mtlx @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/test/lib/mayaUsd/utils/materialx_test_data/topology_tests.mtlx b/test/lib/mayaUsd/utils/materialx_test_data/topology_tests.mtlx new file mode 100644 index 0000000000..812516b0b7 --- /dev/null +++ b/test/lib/mayaUsd/utils/materialx_test_data/topology_tests.mtlx @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/lib/mayaUsd/utils/test_ShaderGenUtils.cpp b/test/lib/mayaUsd/utils/test_ShaderGenUtils.cpp new file mode 100644 index 0000000000..9aab9f785f --- /dev/null +++ b/test/lib/mayaUsd/utils/test_ShaderGenUtils.cpp @@ -0,0 +1,74 @@ +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +constexpr auto pythonDiff = R"D( +import difflib +import sys + +with open(r'^1s', 'r') as mxBaseline: + with open(r'^1s', 'r') as mxOutput: + diff = difflib.unified_diff( + mxBaseline.readlines(), + mxOutput.readlines(), + fromfile='baseline', + tofile='output', + ) + [str(line) for line in diff] +)D"; + +TEST(ShaderGenUtils, topoChannels) +{ + auto testPath = mx::FilePath(MATERIALX_TEST_DATA); + + auto library = mx::createDocument(); + ASSERT_TRUE(library != nullptr); + auto searchPath = PXR_NS::HdMtlxSearchPaths(); + mx::loadLibraries({}, searchPath, library); + + auto doc = mx::createDocument(); + doc->importLibrary(library); + + const mx::XmlReadOptions readOptions; + mx::readFromXmlFile(doc, testPath / "topology_tests.mtlx", mx::EMPTY_STRING, &readOptions); + + for (const mx::NodePtr& material : doc->getMaterialNodes()) { + if (material->getName() != "Interface2") { + continue; + } + auto topoNetwork = MaterialXMaya::ShaderGenUtil::TopoNeutralGraph(material); + + const std::string& expectedFileName = material->getAttribute("topo"); + ASSERT_FALSE(expectedFileName.empty()); + + auto baseline = mx::createDocument(); + auto baselinePath = testPath / expectedFileName; + mx::readFromXmlFile(baseline, baselinePath, mx::EMPTY_STRING, &readOptions); + + auto outputDoc = topoNetwork.getDocument(); + + if (*baseline != *outputDoc) { + const std::string baselineStr = mx::writeToXmlString(baseline); + const std::string outputStr = mx::writeToXmlString(outputDoc); + ASSERT_EQ(baselineStr, outputStr) << "While testing: " << material->getName() + << " against baseline " << expectedFileName; + } + + outputDoc->importLibrary(library); + std::string message; + ASSERT_TRUE(outputDoc->validate(&message)) << message; + } +} From 5cef3f9b504b6b270fcc37500f16e36160b409ab Mon Sep 17 00:00:00 2001 From: Jerry Gamache Date: Tue, 7 Nov 2023 10:24:14 -0500 Subject: [PATCH 2/3] Fix Linux and older USD builds --- lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.cpp | 1 + test/lib/mayaUsd/utils/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.cpp b/lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.cpp index dcdfff6417..6eb9468e35 100644 --- a/lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.cpp +++ b/lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.cpp @@ -4,6 +4,7 @@ #include +#include #include namespace MaterialXMaya { diff --git a/test/lib/mayaUsd/utils/CMakeLists.txt b/test/lib/mayaUsd/utils/CMakeLists.txt index fcd0bbb701..84cb6073ce 100644 --- a/test/lib/mayaUsd/utils/CMakeLists.txt +++ b/test/lib/mayaUsd/utils/CMakeLists.txt @@ -112,7 +112,7 @@ if(IS_WINDOWS) testSplitString.cpp ) - if(CMAKE_WANT_MATERIALX_BUILD) + if(CMAKE_WANT_MATERIALX_BUILD AND PXR_VERSION GREATER_EQUAL 2211) add_mayaUsdLibUtils_test( test_ShaderGenUtils test_ShaderGenUtils.cpp From bc99160cdb397af52799640342f88b79da499dfc Mon Sep 17 00:00:00 2001 From: Jerry Gamache Date: Tue, 7 Nov 2023 10:47:47 -0500 Subject: [PATCH 3/3] Fix cut&paste damage --- lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.cpp b/lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.cpp index 6eb9468e35..2887929314 100644 --- a/lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.cpp +++ b/lib/mayaUsd/render/MaterialXGenOgsXml/ShaderGenUtil.cpp @@ -180,7 +180,6 @@ mx::NodePtr TopoNeutralGraph::cloneNode(const mx::Node& node, mx::GraphElement& = container.addNode(node.getCategory(), "N" + std::to_string(_nodeIndex), node.getType()); ++_nodeIndex; _nodeMap.insert({ node.getNamePath(), destNode }); - _nodeMap.insert({ node.getNamePath(), destNode }); _pathMap.insert({ destNode->getNamePath(), node.getNamePath() }); // Always be explicit on the NodeDef: auto nodeDef = node.getNodeDef();