1 //
2 // Copyright 2021 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #include "pxr/imaging/hdMtlx/hdMtlx.h"
25 #include "pxr/imaging/hd/material.h"
26 
27 #include "pxr/base/gf/vec2f.h"
28 #include "pxr/base/gf/matrix3d.h"
29 #include "pxr/base/gf/matrix4d.h"
30 
31 #include "pxr/usd/sdf/path.h"
32 #include "pxr/usd/sdr/registry.h"
33 #include "pxr/base/tf/diagnostic.h"
34 
35 #include <MaterialXCore/Document.h>
36 #include <MaterialXCore/Node.h>
37 #include <MaterialXFormat/Util.h>
38 #include <MaterialXFormat/XmlIo.h>
39 
40 namespace mx = MaterialX;
41 
42 PXR_NAMESPACE_OPEN_SCOPE
43 
44 // Return the MaterialX Node Type based on the corresponding NodeDef name,
45 // which is stored as the hdNodeType.
46 static TfToken
_GetMxNodeType(mx::DocumentPtr const & mxDoc,TfToken const & hdNodeType)47 _GetMxNodeType(mx::DocumentPtr const& mxDoc, TfToken const& hdNodeType)
48 {
49     mx::NodeDefPtr mxNodeDef = mxDoc->getNodeDef(hdNodeType.GetString());
50     if (!mxNodeDef){
51         TF_WARN("Unsupported node type '%s' cannot find the associated NodeDef.",
52                 hdNodeType.GetText());
53         return TfToken();
54     }
55     return TfToken(mxNodeDef->getNodeString());
56 }
57 
58 // Determine if the given mxInputName is of type mx::Vector3
59 // Hd stores both mx::Vector3 and mx::Color3 as a GlfVec3f
60 static bool
_IsInputVector3(std::string const & mxInputName)61 _IsInputVector3(std::string const& mxInputName)
62 {
63     // mxInputs from UsdPreviewSurface and standard_surface nodes that are
64     // Vector3 types
65     static const mx::StringSet Vector3Inputs = {"normal",
66                                                 "coat_normal",
67                                                 "tangent"};
68     return Vector3Inputs.count(mxInputName) > 0;
69 }
70 
71 // Find the HdNode and its corresponding NodePath in the given HdNetwork
72 // based on the given HdConnection
73 static bool
_FindConnectedNode(HdMaterialNetwork2 const & hdNetwork,HdMaterialConnection2 const & hdConnection,HdMaterialNode2 * hdNode,SdfPath * hdNodePath)74 _FindConnectedNode(
75     HdMaterialNetwork2 const& hdNetwork,
76     HdMaterialConnection2 const& hdConnection,
77     HdMaterialNode2 * hdNode,
78     SdfPath * hdNodePath)
79 {
80     // Get the path to the connected node
81     const SdfPath & connectionPath = hdConnection.upstreamNode;
82 
83     // If this path is not in the network raise a warning
84     auto hdNodeIt = hdNetwork.nodes.find(connectionPath);
85     if (hdNodeIt == hdNetwork.nodes.end()) {
86         TF_WARN("Unknown material node '%s'", connectionPath.GetText());
87         return false;
88     }
89 
90     // Otherwise return the HdNode and corresponding NodePath
91     *hdNode = hdNodeIt->second;
92     *hdNodePath = connectionPath;
93     return true;
94 }
95 
96 // Add the mxNode to the mxNodeGraph, or get the mxNode from the NodeGraph
97 static mx::NodePtr
_AddNodeToNodeGraph(std::string const & mxNodeName,std::string const & mxNodeCategory,std::string const & mxNodeType,mx::NodeGraphPtr const & mxNodeGraph,mx::StringSet * addedNodeNames)98 _AddNodeToNodeGraph(
99     std::string const& mxNodeName,
100     std::string const& mxNodeCategory,
101     std::string const& mxNodeType,
102     mx::NodeGraphPtr const& mxNodeGraph,
103     mx::StringSet * addedNodeNames)
104 {
105     // Add the node to the  mxNodeGraph if needed
106     if (addedNodeNames->find(mxNodeName) == addedNodeNames->end()) {
107         addedNodeNames->insert(mxNodeName);
108         return mxNodeGraph->addNode(mxNodeCategory, mxNodeName, mxNodeType);
109     }
110     // Otherwise get the existing node from the mxNodeGraph
111     return mxNodeGraph->getNode(mxNodeName);
112 }
113 
114 // Convert the HdParameterValue to a string MaterialX can understand
115 std::string
HdMtlxConvertToString(VtValue const & hdParameterValue)116 HdMtlxConvertToString(VtValue const& hdParameterValue)
117 {
118     std::ostringstream valStream;
119     if (hdParameterValue.IsHolding<bool>()) {
120         return (hdParameterValue.UncheckedGet<bool>()) ? "false"
121                                                                 : "true";
122     }
123     else if (hdParameterValue.IsHolding<int>() ||
124              hdParameterValue.IsHolding<float>()) {
125         valStream << hdParameterValue;
126         return  valStream.str();
127     }
128     else if (hdParameterValue.IsHolding<GfVec2f>()) {
129         const GfVec2f & value = hdParameterValue.UncheckedGet<GfVec2f>();
130         valStream << value.data()[0] << ", " << value.data()[1];
131         return valStream.str();
132     }
133     else if (hdParameterValue.IsHolding<GfVec3f>()) {
134         const GfVec3f & value = hdParameterValue.UncheckedGet<GfVec3f>();
135         valStream << value.data()[0] << ", " << value.data()[1] << ", "
136                   << value.data()[2];
137         return valStream.str();
138     }
139     else if (hdParameterValue.IsHolding<GfVec4f>()) {
140         const GfVec4f & value = hdParameterValue.UncheckedGet<GfVec4f>();
141         valStream << value.data()[0] << ", " << value.data()[1] << ", "
142                   << value.data()[2] << ", " << value.data()[3];
143         return valStream.str();
144     }
145     else if (hdParameterValue.IsHolding<GfMatrix3d>()) {
146         const GfMatrix3d & value = hdParameterValue.UncheckedGet<GfMatrix3d>();
147         valStream << value[0][0] << ", " << value[0][1] << ", "
148                   << value[0][2] << ",  "
149                   << value[1][0] << ", " << value[1][1] << ", "
150                   << value[1][2] << ",  "
151                   << value[2][0] << ", " << value[2][1] << ", "
152                   << value[2][2] << ",  ";
153         return valStream.str();
154     }
155     else if (hdParameterValue.IsHolding<GfMatrix4d>()) {
156         const GfMatrix4d & value = hdParameterValue.UncheckedGet<GfMatrix4d>();
157         valStream << value[0][0] << ", " << value[0][1] << ", "
158                   << value[0][2] << ", " << value[0][3] << ",  "
159                   << value[1][0] << ", " << value[1][1] << ", "
160                   << value[1][2] << ", " << value[1][3] << ",  "
161                   << value[2][0] << ", " << value[2][1] << ", "
162                   << value[2][2] << ", " << value[2][3] << ",  "
163                   << value[3][0] << ", " << value[3][1] << ", "
164                   << value[3][2] << ", " << value[3][3] << ",  ";
165         return valStream.str();
166     }
167     else if (hdParameterValue.IsHolding<SdfAssetPath>()) {
168         return hdParameterValue.UncheckedGet<SdfAssetPath>().GetAssetPath();
169     }
170     else if (hdParameterValue.IsHolding<std::string>()) {
171         return hdParameterValue.UncheckedGet<std::string>();
172     }
173     else {
174         TF_WARN("Unsupported Parameter Type '%s'",
175                 hdParameterValue.GetTypeName().c_str());
176         return mx::EMPTY_STRING;
177     }
178 }
179 
180 // Get the MaterialX Input information from the mxNodeDef and hdParameter
181 static void
_GetMxInputInfo(std::pair<TfToken,VtValue> const & hdParameter,mx::NodeDefPtr const & mxNodeDef,std::string * mxInputName,std::string * mxInputValue,std::string * mxInputType)182 _GetMxInputInfo(
183     std::pair<TfToken, VtValue> const& hdParameter,
184     mx::NodeDefPtr const& mxNodeDef,
185     std::string * mxInputName,
186     std::string * mxInputValue,
187     std::string * mxInputType)
188 {
189     // Get the mxInputName from the HdParameter
190     *mxInputName = hdParameter.first.GetText();
191 
192     // Get the mxInputValue from the Value
193     *mxInputValue = HdMtlxConvertToString(hdParameter.second);
194 
195     // Get the mxInputType for the mxNodeDef
196     mx::InputPtr mxInput = mxNodeDef->getInput(*mxInputName);
197     if (mxInput) {
198         *mxInputType = mxInput->getType();
199     }
200 }
201 
202 // Add a MaterialX version of the HdNode to the mxDoc/mxNodeGraph
203 static mx::NodePtr
_AddMaterialXNode(HdMaterialNetwork2 const & hdNetwork,HdMaterialNode2 const & hdNode,SdfPath const & hdNodePath,mx::DocumentPtr const & mxDoc,mx::NodeGraphPtr const & mxNodeGraph,mx::StringSet * addedNodeNames,std::set<SdfPath> * hdTextureNodes,std::string const & connectionName,mx::StringMap * mxHdTextureMap,std::set<SdfPath> * hdPrimvarNodes)204 _AddMaterialXNode(
205     HdMaterialNetwork2 const& hdNetwork,
206     HdMaterialNode2 const& hdNode,
207     SdfPath const& hdNodePath,
208     mx::DocumentPtr const& mxDoc,
209     mx::NodeGraphPtr const& mxNodeGraph,
210     mx::StringSet * addedNodeNames,
211     std::set<SdfPath> * hdTextureNodes,
212     std::string const& connectionName,
213     mx::StringMap * mxHdTextureMap,
214     std::set<SdfPath> * hdPrimvarNodes)
215 {
216     // Get the mxNode information
217     mx::NodeDefPtr mxNodeDef = mxDoc->getNodeDef(hdNode.nodeTypeId.GetString());
218     if (!mxNodeDef){
219         TF_WARN("NodeDef not found for Node '%s'", hdNode.nodeTypeId.GetText());
220         return mx::NodePtr();
221     }
222     const std::string & mxNodeCategory = mxNodeDef->getNodeString();
223     const std::string & mxNodeType = mxNodeDef->getType();
224     const std::string & mxNodeName = hdNodePath.GetName();
225 
226     // Add the mxNode to the mxNodeGraph
227     mx::NodePtr mxNode = _AddNodeToNodeGraph(mxNodeName, mxNodeCategory,
228                                     mxNodeType, mxNodeGraph, addedNodeNames);
229 
230     // For each of the HdNode parameters add the corresponding parameter/input
231     // to the mxNode
232     for (auto const& currParam : hdNode.parameters) {
233 
234         // Get the MaterialX Parameter info
235         std::string mxInputName, mxInputValue, mxInputType;
236         _GetMxInputInfo(currParam, mxNodeDef, &mxInputName,
237                         &mxInputValue, &mxInputType);
238         mxNode->setInputValue(mxInputName, mxInputValue, mxInputType);
239 
240         // If this is a MaterialX Texture node
241         if (mxNodeCategory == "image" || mxNodeCategory == "tiledimage") {
242 
243             // Save the corresponding MaterialX and Hydra names for ShaderGen
244             if (mxHdTextureMap) {
245                 (*mxHdTextureMap)[hdNodePath.GetName()] = connectionName;
246             }
247 
248             // Save the path to adjust the parameters after traversing the network
249             if (hdTextureNodes) {
250                 hdTextureNodes->insert(hdNodePath);
251             }
252         }
253 
254         // If this is a MaterialX primvar node
255         if (mxNodeCategory == "geompropvalue") {
256             if (hdPrimvarNodes) {
257                 hdPrimvarNodes->insert(hdNodePath);
258             }
259         }
260     }
261     return mxNode;
262 }
263 
264 // Recurrsively traverse the HdNetwork and gather the nodes in the MaterialX
265 // NodeGraph and Document
266 static void
_GatherUpstreamNodes(HdMaterialNetwork2 const & hdNetwork,HdMaterialConnection2 const & hdConnection,mx::DocumentPtr const & mxDoc,mx::NodeGraphPtr * mxNodeGraph,mx::StringSet * addedNodeNames,mx::NodePtr * mxUpstreamNode,std::set<SdfPath> * hdTextureNodes,std::string const & connectionName,mx::StringMap * mxHdTextureMap,std::set<SdfPath> * hdPrimvarNodes)267 _GatherUpstreamNodes(
268     HdMaterialNetwork2 const& hdNetwork,
269     HdMaterialConnection2 const& hdConnection,  // connection from previous node
270     mx::DocumentPtr const& mxDoc,
271     mx::NodeGraphPtr * mxNodeGraph,
272     mx::StringSet * addedNodeNames,
273     mx::NodePtr * mxUpstreamNode,
274     std::set<SdfPath> * hdTextureNodes,
275     std::string const& connectionName,
276     mx::StringMap * mxHdTextureMap,
277     std::set<SdfPath> * hdPrimvarNodes)
278 {
279     // Get the connected node (hdNode) from the hdConnection
280     SdfPath hdNodePath;
281     HdMaterialNode2 hdNode;  // HdNode -> mxCurrNode
282     bool found = _FindConnectedNode(hdNetwork, hdConnection,
283                                     &hdNode, &hdNodePath);
284 
285     if (!found) {
286         TF_WARN("Could not find the connected Node with path '%s'",
287                     hdConnection.upstreamNode.GetText());
288         return;
289     }
290 
291     // Initilize the mxNodeGraph if needed
292     if (!(*mxNodeGraph)) {
293         const std::string & nodeGraphName  = hdNodePath.GetParentPath().GetName();
294         *mxNodeGraph = mxDoc->addNodeGraph(nodeGraphName);
295     }
296 
297     // Add the node to the mxNodeGraph/mxDoc.
298     mx::NodePtr mxCurrNode = _AddMaterialXNode(hdNetwork, hdNode, hdNodePath,
299                                 mxDoc, *mxNodeGraph, addedNodeNames,
300                                 hdTextureNodes, connectionName, mxHdTextureMap,
301                                 hdPrimvarNodes);
302 
303     if (!mxCurrNode) {
304         return;
305     }
306 
307     // Continue traversing the upsteam connections to create the mxNodeGraph
308     for (auto & inputConnections: hdNode.inputConnections) {
309 
310         TfToken connName = inputConnections.first;
311         for (auto & currConnection : inputConnections.second) {
312 
313             // Gather the nodes uptream from the mxCurrNode
314             _GatherUpstreamNodes(hdNetwork, currConnection, mxDoc, mxNodeGraph,
315                                  addedNodeNames, mxUpstreamNode, hdTextureNodes,
316                                  connName.GetString(), mxHdTextureMap,
317                                  hdPrimvarNodes);
318 
319             // Connect mxCurrNode to the mxUpstreamNode
320             mx::NodePtr mxNextNode = *mxUpstreamNode;
321 
322             // Make sure to not add the same input twice
323             mx::InputPtr mxInput = mxCurrNode->getInput(connName);
324             if (!mxInput){
325                 mxInput = mxCurrNode->addInput(connName, mxNextNode->getType());
326             }
327             mxInput->setConnectedNode(mxNextNode);
328         }
329     }
330 
331     *mxUpstreamNode = mxCurrNode;
332 }
333 
334 
335 // Create a MaterialX Document from the given HdMaterialNetwork
336 mx::DocumentPtr
HdMtlxCreateMtlxDocumentFromHdNetwork(HdMaterialNetwork2 const & hdNetwork,HdMaterialNode2 const & hdMaterialXNode,SdfPath const & materialPath,mx::DocumentPtr const & libraries,std::set<SdfPath> * hdTextureNodes,mx::StringMap * mxHdTextureMap,std::set<SdfPath> * hdPrimvarNodes)337 HdMtlxCreateMtlxDocumentFromHdNetwork(
338     HdMaterialNetwork2 const& hdNetwork,
339     HdMaterialNode2 const& hdMaterialXNode,
340     SdfPath const& materialPath,
341     mx::DocumentPtr const& libraries,
342     std::set<SdfPath> * hdTextureNodes, // Paths to the Hd Texture Nodes
343     mx::StringMap * mxHdTextureMap,     // Mx-Hd texture name counterparts
344     std::set<SdfPath> * hdPrimvarNodes) // Paths to the Hd primvar nodes
345 {
346     // Initialize a MaterialX Document
347     mx::DocumentPtr mxDoc = mx::createDocument();
348     mxDoc->importLibrary(libraries);
349 
350     // Create a material that instantiates the shader
351     const std::string & materialName = materialPath.GetName();
352     TfToken mxType = _GetMxNodeType(mxDoc, hdMaterialXNode.nodeTypeId);
353     mx::NodePtr mxShaderNode = mxDoc->addNode(mxType.GetString(),
354                                               "SR_" + materialName,
355                                               "surfaceshader");
356     mx::NodePtr mxMaterial = mxDoc->addMaterialNode(materialName, mxShaderNode);
357 
358     // Create mxNodeGraph from the inputConnections in the HdMaterialNetwork
359     mx::NodeGraphPtr mxNodeGraph;
360     mx::StringSet addedNodeNames;   // Set of NodeNames in the mxNodeGraph
361     for (auto inputConns : hdMaterialXNode.inputConnections) {
362 
363         const std::string & mxNodeGraphOutput = inputConns.first.GetString();
364         for (HdMaterialConnection2 & currConnection : inputConns.second) {
365 
366             // Gather the nodes uptream from the hdMaterialXNode
367             mx::NodePtr mxUpstreamNode;
368             _GatherUpstreamNodes(hdNetwork, currConnection, mxDoc, &mxNodeGraph,
369                         &addedNodeNames, &mxUpstreamNode, hdTextureNodes,
370                         mxNodeGraphOutput, mxHdTextureMap, hdPrimvarNodes);
371 
372             if (!mxUpstreamNode) {
373                 continue;
374             }
375 
376             // Connect currNode to the upstream Node
377             std::string fullOutputName = mxNodeGraphOutput + "_" +
378                                 currConnection.upstreamOutputName.GetString();
379             mx::OutputPtr mxOutput = mxNodeGraph->addOutput(fullOutputName,
380                                                     mxUpstreamNode->getType());
381             mxOutput->setConnectedNode(mxUpstreamNode);
382 
383             // Connect NodeGraph Output to the ShaderNode
384             mx::InputPtr mxInput = mxShaderNode->addInput(mxNodeGraphOutput,
385                                                           mxOutput->getType());
386             mxInput->setConnectedOutput(mxOutput);
387         }
388     }
389 
390     // Add Inputs - The StandardSurface or USDPreviewSurface inputs
391     for (auto currParameter : hdMaterialXNode.parameters) {
392 
393         const std::string & mxInputName = currParameter.first.GetString();
394         mx::InputPtr mxInput = mxShaderNode->addInput(mxInputName);
395 
396         // Convert the parameter to the appropriate MaterialX input format
397         VtValue hdParamValue = currParameter.second;
398         if (hdParamValue.IsHolding<bool>()) {
399             bool value = hdParamValue.UncheckedGet<bool>();
400             mxInput->setValue(value);
401         }
402         else if (hdParamValue.IsHolding<int>()) {
403             int value = hdParamValue.UncheckedGet<int>();
404             mxInput->setValue(value);
405         }
406         else if (hdParamValue.IsHolding<float>()) {
407             float value = hdParamValue.UncheckedGet<float>();
408             mxInput->setValue(value);
409         }
410         else if (hdParamValue.IsHolding<GfVec3f>()) {
411 
412             const GfVec3f & value = hdParamValue.UncheckedGet<GfVec3f>();
413             // Check if the parameter is a mx::vector3 or mx::color3
414             if (_IsInputVector3(mxInputName)) {
415                 mxInput->setValue(mx::Vector3(value.data()[0],
416                                               value.data()[1],
417                                               value.data()[2]));
418             }
419             else {
420                 mxInput->setValue(mx::Color3(value.data()[0],
421                                              value.data()[1],
422                                              value.data()[2]));
423             }
424         }
425         else {
426             mxShaderNode->removeInput(mxInputName);
427             TF_WARN("Unsupported Input Type '%s' for mxNode '%s' of type '%s'",
428                     hdParamValue.GetTypeName().c_str(), mxInputName.c_str(),
429                     mxType.GetText());
430         }
431     }
432 
433     // Validate the MaterialX Document.
434     std::string message;
435     if (!mxDoc->validate(&message)) {
436         TF_WARN("Validation warnings for generated MaterialX file.\n%s\n",
437                 message.c_str());
438     }
439 
440     return mxDoc;
441 }
442 
443 PXR_NAMESPACE_CLOSE_SCOPE
444