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 "hdPrman/matfiltMaterialX.h"
25 #include "hdPrman/debugCodes.h"
26 #include "pxr/imaging/hdMtlx/hdMtlx.h"
27 
28 #include "pxr/base/arch/hash.h"
29 #include "pxr/base/arch/library.h"
30 #include "pxr/base/arch/fileSystem.h"
31 #include "pxr/base/tf/staticTokens.h"
32 #include "pxr/usd/ar/resolver.h"
33 #include "pxr/usd/sdr/shaderNode.h"
34 #include "pxr/usd/sdr/shaderProperty.h"
35 #include "pxr/usd/sdr/registry.h"
36 #include "pxr/imaging/hd/tokens.h"
37 
38 #include "pxr/base/gf/vec2f.h"
39 #include "pxr/base/gf/matrix3d.h"
40 #include "pxr/base/gf/matrix4d.h"
41 
42 #include <MaterialXCore/Node.h>
43 #include <MaterialXCore/Document.h>
44 #include <MaterialXFormat/Util.h>
45 #include <MaterialXFormat/XmlIo.h>
46 #include <MaterialXGenShader/Shader.h>
47 #include <MaterialXGenShader/Util.h>
48 #include <MaterialXGenOsl/OslShaderGenerator.h>
49 #include <MaterialXRender/Util.h>
50 
51 #ifdef PXR_OSL_SUPPORT_ENABLED
52 #include <OSL/oslcomp.h>
53 #include <fstream>
54 #endif
55 
56 namespace mx = MaterialX;
57 
58 PXR_NAMESPACE_OPEN_SCOPE
59 
60 TF_DEFINE_PRIVATE_TOKENS(
61     _tokens,
62     (mtlx)
63 
64     // Hydra MaterialX Node Types
65     (ND_standard_surface_surfaceshader)
66     (ND_UsdPreviewSurface_surfaceshader)
67 
68     // MaterialX - OSL Adapter Node names
69     ((SS_Adapter, "StandardSurfaceParameters"))
70     ((USD_Adapter, "UsdPreviewSurfaceParameters"))
71 
72     // HdPrman Surface Terminal Node
73     (PxrSurface)
74 
75     // Hydra SourceTypes
76     (OSL)       // Adapter Node
77     (RmanCpp)   // PxrSurface Node
78 
79     // MaterialX Texture Node input and type
80     (file)
81     (filename)
82 
83     // Wrap Modes
84     (black)
85     (clamp)
86     (repeat)
87     (uaddressmode)
88     (vaddressmode)
89 );
90 
91 
92 // Use the given mxDocument to generate osl source code for the nodegraph output
93 // with the given hdOutputName
94 static std::string
_GenMaterialXShaderCode(mx::DocumentPtr const & mxDoc,mx::FileSearchPath const & searchPath,std::string const & hdOutputName,std::string const & shaderName)95 _GenMaterialXShaderCode(
96     mx::DocumentPtr const& mxDoc,
97     mx::FileSearchPath const& searchPath,
98     std::string const& hdOutputName,
99     std::string const& shaderName)
100 {
101     // Initialize the Context for shaderGen
102     mx::GenContext mxContext = mx::OslShaderGenerator::create();
103     mxContext.registerSourceCodeSearchPath(searchPath);
104     mxContext.getOptions().fileTextureVerticalFlip = false;
105 
106     // Find renderable elements in the Mtlx Document (NodeGraph outputs)
107     std::vector<mx::TypedElementPtr> renderableElements;
108     mx::findRenderableElements(mxDoc, renderableElements);
109 
110     // Generate the OslShader for the matching hdOutput
111     for (auto& nodeGraphOutputElement : renderableElements) {
112         if (hdOutputName == nodeGraphOutputElement->getName()) {
113             TF_DEBUG(HDPRMAN_MATERIALS)
114                 .Msg("Generate a MaterialX Osl shader for output '%s'\n",
115                      hdOutputName.c_str());
116             mx::ShaderPtr mxShader = mx::createShader(shaderName, mxContext,
117                                                       nodeGraphOutputElement);
118             if (mxShader) {
119                 return mxShader->getSourceCode();
120             }
121             return mx::EMPTY_STRING;
122         }
123     }
124     TF_WARN("No matching output for '%s'\n", hdOutputName.c_str());
125     return mx::EMPTY_STRING;
126 }
127 
128 ////////////////////////////////////////////////////////////////////////////////
129 // Helpers to update the HdMaterialNetwork for HdPrman
130 
131 static HdMaterialNode2 const*
_GetSurfaceTerminalNode(HdMaterialNetwork2 const & network,SdfPath * terminalNodePath)132 _GetSurfaceTerminalNode(
133     HdMaterialNetwork2 const& network,
134     SdfPath * terminalNodePath)
135 {
136     // Get the Surface Terminal
137     auto const& terminalConnIt = network.terminals.find(
138                                             HdMaterialTerminalTokens->surface);
139     if (terminalConnIt == network.terminals.end()) {
140         return nullptr;
141     }
142     HdMaterialConnection2 const& connection = terminalConnIt->second;
143     SdfPath const& terminalPath = connection.upstreamNode;
144     auto const& terminalIt = network.nodes.find(terminalPath);
145     *terminalNodePath = terminalPath;
146     return &terminalIt->second;
147 }
148 
149 // Convert the MaterialX SurfaceShader Token to the MaterialX Adapter Node Type
150 static TfToken
_GetAdapterNodeType(TfToken const & hdNodeType)151 _GetAdapterNodeType(TfToken const& hdNodeType)
152 {
153     if (hdNodeType == _tokens->ND_standard_surface_surfaceshader) {
154         return _tokens->SS_Adapter;
155     }
156     else if (hdNodeType == _tokens->ND_UsdPreviewSurface_surfaceshader) {
157         return _tokens->USD_Adapter;
158     }
159     else {
160         TF_WARN("Unsupported Node Type '%s'", hdNodeType.GetText());
161         return TfToken();
162     }
163 }
164 
165 // Convert the TfToken associated with the input parameters to the Standard
166 // Surface Adapter Node that conflict with OSL reserved words.
167 static TfToken
_GetUpdatedInputToken(TfToken const & currInputName)168 _GetUpdatedInputToken(TfToken const& currInputName)
169 {
170     // { currentInputNname , updatedInputName }
171     static const mx::StringMap conflicts = {{"emission",   "emission_value"},
172                                             {"subsurface", "subsurface_value"},
173                                             {"normal", "input_normal"}};
174     auto it = conflicts.find(currInputName.GetString());
175     if (it != conflicts.end()) {
176         return TfToken(it->second);
177     }
178     return TfToken();
179 }
180 
181 // Find the HdNode and its corresponding NodePath in the given HdNetwork
182 // based on the given HdConnection
183 static bool
_FindConnectedNode(HdMaterialNetwork2 const & hdNetwork,HdMaterialConnection2 const & hdConnection,HdMaterialNode2 * hdNode,SdfPath * hdNodePath,std::set<SdfPath> * visitedNodes)184 _FindConnectedNode(
185     HdMaterialNetwork2 const& hdNetwork,
186     HdMaterialConnection2 const& hdConnection,
187     HdMaterialNode2 * hdNode,
188     SdfPath * hdNodePath,
189     std::set<SdfPath> * visitedNodes)
190 {
191     // Get the path to the connected node
192     const SdfPath & connectionPath = hdConnection.upstreamNode;
193 
194     // Make sure we don't process the node again if we already visited it
195     if (visitedNodes->count(connectionPath) > 0) {
196         *hdNode = hdNetwork.nodes.find(connectionPath)->second;
197         *hdNodePath = connectionPath;
198         return false;
199     }
200     visitedNodes->insert(connectionPath);
201 
202     // If this path is not in the network raise a warning
203     auto hdNodeIt = hdNetwork.nodes.find(connectionPath);
204     if (hdNodeIt == hdNetwork.nodes.end()) {
205         TF_WARN("Unknown material node '%s'", connectionPath.GetText());
206         return false;
207     }
208 
209     // Otherwise return the HdNode and corresponding NodePath
210     *hdNode = hdNodeIt->second;
211     *hdNodePath = connectionPath;
212     return true;
213 }
214 
215 static void
_GatherNodeGraphNodes(HdMaterialNetwork2 const & hdNetwork,HdMaterialNode2 const & hdNode,std::set<SdfPath> * nodeGraphNodes,std::set<SdfPath> * visitedNodes)216 _GatherNodeGraphNodes(
217     HdMaterialNetwork2 const& hdNetwork,
218     HdMaterialNode2 const& hdNode,
219     std::set<SdfPath> * nodeGraphNodes,
220     std::set<SdfPath> * visitedNodes)
221 {
222     // Traverse the upsteam connections to gather the nodeGraph nodes
223     for (auto & inputConnections: hdNode.inputConnections) {
224         for (auto const& currConnection : inputConnections.second) {
225 
226             SdfPath nextNodePath;
227             HdMaterialNode2 nextNode;
228             const bool found = _FindConnectedNode(hdNetwork, currConnection,
229                                     &nextNode, &nextNodePath, visitedNodes);
230             if (!found) {
231                 return;
232             }
233             // Gather the nodes uptream from the hdNode
234             _GatherNodeGraphNodes(hdNetwork, nextNode,
235                                   nodeGraphNodes, visitedNodes);
236             nodeGraphNodes->insert(nextNodePath);
237         }
238     }
239 }
240 
241 // Compile the given oslSource returning the path to the compiled oso code
242 static std::string
_CompileOslSource(std::string const & name,std::string const & oslSource)243 _CompileOslSource(std::string const& name, std::string const& oslSource)
244 {
245 #ifdef PXR_OSL_SUPPORT_ENABLED
246 
247     TF_DEBUG(HDPRMAN_DUMP_MATERIALX_OSL_SHADER)
248         .Msg("--------- MaterialX Generated Shader '%s' ----------\n%s"
249              "---------------------------\n\n", name.c_str(), oslSource.c_str());
250 
251     // Compile oslSource
252     std::string oslCompiledSource;
253     OSL::OSLCompiler oslCompiler;
254     oslCompiler.compile_buffer(oslSource, oslCompiledSource,
255                 {"-I" + std::string(PXR_MATERIALX_STDLIB_DIR) + "/stdlib/osl"});
256 
257     // Save compiled shader
258     std::string compiledFilePath = ArchMakeTmpFileName("MX." + name, ".oso");
259     FILE * compiledShader;
260     compiledShader = fopen((compiledFilePath).c_str(), "w+");
261     if (!compiledShader) {
262         TF_WARN("Unable to save compiled MaterialX Osl shader at '%s'\n",
263                 compiledFilePath.c_str());
264         return mx::EMPTY_STRING;
265     }
266     else {
267         fputs(oslCompiledSource.c_str(), compiledShader);
268         fclose(compiledShader);
269         return compiledFilePath;
270     }
271 #else
272     TF_WARN("Unable to compile MaterialX generated Osl shader, enable OSL "
273             "support for full MaterialX support in HdPrman.\n");
274     return mx::EMPTY_STRING;
275 #endif
276 }
277 
278 // For each of the outputs in the nodegraph create a sdrShaderNode with the
279 // compiled osl code generated by MaterialX and update the terminalNode's
280 // input connections
281 // Removes the nodes that are not directly connected to the terminal node
282 static void
_UpdateNetwork(HdMaterialNetwork2 * hdNetwork,HdMaterialNode2 const * terminalNode,SdfPath const & terminalNodePath,mx::DocumentPtr const & mxDoc,mx::FileSearchPath const & searchPath)283 _UpdateNetwork(
284     HdMaterialNetwork2 * hdNetwork,
285     HdMaterialNode2 const* terminalNode,
286     SdfPath const& terminalNodePath,
287     mx::DocumentPtr const& mxDoc,
288     mx::FileSearchPath const& searchPath)
289 {
290     // Gather the nodeGraph nodes
291     std::set<SdfPath> nodesToKeep;   // nodes directly connected to the terminal
292     std::set<SdfPath> nodesToRemove; // nodes further removed from the terminal
293     std::set<SdfPath> visitedNodes;
294 
295     // Save the terminalNode inputConnections and clear so that inputNames can
296     // be renamed if necessary
297     auto terminalConnections = terminalNode->inputConnections;
298     hdNetwork->nodes[terminalNodePath].inputConnections.clear();
299     for (auto inputConns : terminalConnections) {
300 
301         const std::string & mxNodeGraphOutput = inputConns.first.GetString();
302         for (HdMaterialConnection2 & currConnection : inputConns.second) {
303 
304             SdfPath level1NodePath;
305             HdMaterialNode2 level1Node;
306             bool newNode = _FindConnectedNode(*hdNetwork, currConnection,
307                                 &level1Node, &level1NodePath, &visitedNodes);
308             // If we did not find the node
309             if (level1Node == HdMaterialNode2()) {
310                 continue;
311             }
312             // if we are connected to a node we have already processed (ie. we
313             // are re-using a nodegraph output)
314             if (!newNode) {
315                 // Get the sdrNode for this nodegraph output.
316                 SdrRegistry &sdrRegistry = SdrRegistry::GetInstance();
317                 SdrShaderNodeConstPtr sdrNode =
318                     sdrRegistry.GetShaderNodeByIdentifier(
319                         hdNetwork->nodes[level1NodePath].nodeTypeId);
320 
321                 // Update the connection into the terminal node so that the
322                 // nodegraph output makes it into the closure
323                 // Note: MaterialX nodes only have one output
324                 TfToken inputName = TfToken(mxNodeGraphOutput);
325                 TfToken outputName = sdrNode->GetOutputNames().at(0);
326                 hdNetwork->nodes[terminalNodePath].inputConnections[inputName]
327                     = { {level1NodePath, outputName} };
328                 continue;
329             }
330             // collect nodes further removed from the terminal in nodesToRemove
331             _GatherNodeGraphNodes(*hdNetwork, level1Node,
332                                   &nodesToRemove, &visitedNodes);
333             nodesToKeep.insert(level1NodePath);
334 
335             // Generate the oslSource code for the output
336             std::string fullOutputName = mxNodeGraphOutput + "_" +
337                                 currConnection.upstreamOutputName.GetString();
338             std::string shaderName = mxNodeGraphOutput + "Shader";
339             std::string oslSource = _GenMaterialXShaderCode(mxDoc,
340                                         searchPath, fullOutputName, shaderName);
341             if (oslSource.empty()) {
342                 continue;
343             }
344 
345             // Compile the oslSource
346             std::string compiledShaderPath = _CompileOslSource(shaderName,
347                                                                oslSource);
348             if (compiledShaderPath.empty()) {
349                 continue;
350             }
351 
352             // Create a new SdrShaderNode with the compiled oslSource
353             SdrRegistry &sdrRegistry = SdrRegistry::GetInstance();
354             SdrShaderNodeConstPtr sdrNode =
355                 sdrRegistry.GetShaderNodeFromAsset(
356                                 SdfAssetPath(compiledShaderPath),
357                                 NdrTokenMap(),  // metadata
358                                 _tokens->mtlx,  // subId
359                                 _tokens->OSL);  // sourceType
360 
361             // Update NodeTypeId to point to this new sdrNode
362             hdNetwork->nodes[level1NodePath].nodeTypeId = sdrNode->GetIdentifier();
363 
364             // Update the connection into the terminal node so that the
365             // nodegraph outputs make their way into the closure
366             TfToken outputName(fullOutputName);
367             if (sdrNode->GetOutput(outputName)) {
368                 TfToken inputName = TfToken(mxNodeGraphOutput);
369                 TfToken updatedInputName = _GetUpdatedInputToken(inputName);
370                 if (updatedInputName != TfToken()) {
371                     inputName = updatedInputName;
372                 }
373                 hdNetwork->nodes[terminalNodePath].inputConnections[inputName]
374                     = { {level1NodePath, outputName} };
375             }
376 
377             // Clear inputConnections since they will be removed (nodesToRemove)
378             hdNetwork->nodes[level1NodePath].inputConnections.clear();
379             // Clear parameters since they are already used in the generated shader
380             hdNetwork->nodes[level1NodePath].parameters.clear();
381         }
382     }
383 
384     // Remove the nodes not directly connected to the terminal
385     for (auto const& node: nodesToRemove) {
386         // As long as the node is not also directly connected to the terminal
387         if (nodesToKeep.find(node) == nodesToKeep.end()) {
388             hdNetwork->nodes.erase(node);
389         }
390     }
391 }
392 
393 // Update the TfTokens associated with the input parameters to the Standard
394 // Surface Adapter Node that conflict with OSL reserved words.
395 // The corresponding input connection is updated in _UpdateNetwork()
396 static void
_UpdateTerminalConnections(HdMaterialNode2 * adapterNode)397 _UpdateTerminalConnections(HdMaterialNode2 * adapterNode)
398 {
399     if (adapterNode->nodeTypeId == _tokens->USD_Adapter) {
400         return;
401     }
402 
403     for (auto param : adapterNode->parameters) {
404 
405         TfToken updatedName = _GetUpdatedInputToken(param.first);
406         if (updatedName != TfToken()) {
407             // Replace the conflicting parameter name with the updatedName
408             adapterNode->parameters[updatedName] = param.second;
409             adapterNode->parameters.erase(param.first);
410         }
411     }
412 }
413 
414 // Replace the original HdMaterialNode2 MaterialX terminalNode to an Adapter
415 // Node which connects to a new PxrSurface Node that becomes the surfaceTerminal
416 // node in the hdNetwork.
417 static void
_UpdateTerminal(HdMaterialNetwork2 * hdNetwork,HdMaterialNode2 const & terminalNode,SdfPath const & terminalNodePath)418 _UpdateTerminal(
419     HdMaterialNetwork2 * hdNetwork,
420     HdMaterialNode2 const& terminalNode,
421     SdfPath const& terminalNodePath)
422 {
423     // Create a SdrShaderNodes for the Adapter and PxrSurface Nodes.
424     TfToken adapterType = _GetAdapterNodeType(terminalNode.nodeTypeId);
425 
426     SdrRegistry &sdrRegistry = SdrRegistry::GetInstance();
427     SdrShaderNodeConstPtr const sdrAdapter =
428         sdrRegistry.GetShaderNodeByIdentifier(adapterType, {_tokens->OSL});
429     SdrShaderNodeConstPtr const sdrPxrSurface =
430         sdrRegistry.GetShaderNodeByIdentifier(_tokens->PxrSurface,
431                                               {_tokens->RmanCpp});
432     if (!sdrAdapter) {
433         TF_WARN("No sdrAdater node of type '%s'", adapterType.GetText());
434         return;
435     }
436 
437     // Replace the terminalNode with the appropriate Adapter Node, which
438     // translates the MaterialX parameters into PxrSurface Node inputs.
439     HdMaterialNode2 adapterNode;
440     adapterNode.nodeTypeId = adapterType;
441     adapterNode.inputConnections = terminalNode.inputConnections;
442     adapterNode.parameters = terminalNode.parameters;
443     _UpdateTerminalConnections(&adapterNode);
444     hdNetwork->nodes[terminalNodePath] = adapterNode;
445 
446     // Create a PxrSurface HdMaterialNode2
447     HdMaterialNode2 pxrSurfaceNode;
448     pxrSurfaceNode.nodeTypeId = _tokens->PxrSurface;
449 
450     // Connect the PxrSurface inputs to the Adapter's outputs
451     for (const auto& inParamName: sdrPxrSurface->GetInputNames()) {
452 
453         if (sdrPxrSurface->GetShaderInput(inParamName)) {
454 
455             // Convert the parameter name to the "xxxOut" format
456             TfToken adapterOutParam = TfToken(inParamName.GetString() + "Out");
457 
458             // If the PxrSurface Input is an Adapter node output add the
459             // inputConnection to the PxrSurface Node
460             // Note: not every input has a corresponding output
461             if (sdrAdapter->GetShaderOutput(adapterOutParam)) {
462 
463                 pxrSurfaceNode.inputConnections[inParamName] =
464                         { {terminalNodePath, adapterOutParam} };
465             }
466         }
467     }
468 
469     // Add the PxrSurface Node to the network
470     SdfPath pxrSurfacePath = terminalNodePath.GetParentPath().AppendChild(
471                         TfToken(terminalNodePath.GetName() + "_PxrSurface"));
472     hdNetwork->nodes[pxrSurfacePath] = pxrSurfaceNode;
473 
474     // Update the network terminals so that the terminal Node is the PxrSurface
475     // Node instead of the Adapter Node (previously the mtlx terminal node)
476     hdNetwork->terminals[HdMaterialTerminalTokens->surface] =
477                         { pxrSurfacePath, TfToken() };
478 }
479 
480 // Get the Hydra equivalent for the given MaterialX input value
481 static TfToken
_GetHdWrapString(SdfPath const & hdTextureNodePath,std::string const & mxInputValue)482 _GetHdWrapString(
483     SdfPath const& hdTextureNodePath,
484     std::string const& mxInputValue)
485 {
486     if (mxInputValue == "constant") {
487         TF_WARN("RtxHioImagePlugin: Texture '%s' has unsupported wrap mode "
488             "'constant' using 'black' instead.",
489             hdTextureNodePath.GetName().c_str());
490         return _tokens->black;
491     }
492     if (mxInputValue == "clamp") {
493         return _tokens->clamp;
494     }
495     if (mxInputValue == "mirror") {
496         TF_WARN("RtxHioImagePlugin: Texture '%s' has unsupported wrap mode "
497             "'mirror' using 'repeat' instead.",
498             hdTextureNodePath.GetName().c_str());
499         return _tokens->repeat;
500     }
501     return _tokens->repeat;
502 }
503 
504 static void
_GetWrapModes(HdMaterialNode2 const & hdTextureNode,SdfPath const & hdTextureNodePath,TfToken * uWrap,TfToken * vWrap)505 _GetWrapModes(
506     HdMaterialNode2 const& hdTextureNode,
507     SdfPath const& hdTextureNodePath,
508     TfToken * uWrap,
509     TfToken * vWrap)
510 {
511     // For <tiledimage> nodes want to always use "repeat"
512     *uWrap = _tokens->repeat;
513     *vWrap = _tokens->repeat;
514 
515     // For <image> nodes:
516     const auto uWapIt = hdTextureNode.parameters.find(_tokens->uaddressmode);
517     if (uWapIt != hdTextureNode.parameters.end()) {
518         *uWrap = _GetHdWrapString(hdTextureNodePath,
519                                   uWapIt->second.UncheckedGet<std::string>());
520     }
521     const auto vWrapIt = hdTextureNode.parameters.find(_tokens->vaddressmode);
522     if (vWrapIt != hdTextureNode.parameters.end()) {
523         *vWrap = _GetHdWrapString(hdTextureNodePath,
524                                   vWrapIt->second.UncheckedGet<std::string>());
525     }
526 }
527 
528 static void
_UpdateTextureNodes(HdMaterialNetwork2 const & hdNetwork,std::set<SdfPath> const & hdTextureNodes,mx::DocumentPtr const & mxDoc)529 _UpdateTextureNodes(
530     HdMaterialNetwork2 const& hdNetwork,
531     std::set<SdfPath> const& hdTextureNodes,
532     mx::DocumentPtr const& mxDoc)
533 {
534     for (SdfPath const& texturePath : hdTextureNodes) {
535 
536         // Get the hdTextureNode from the hdNetwork
537         const auto textureNodeIt = hdNetwork.nodes.find(texturePath);
538         if (textureNodeIt == hdNetwork.nodes.end()) {
539             TF_WARN("Connot find texture node '%s' in HdMaterialNetwork.",
540                     texturePath.GetText());
541             continue;
542         }
543         const HdMaterialNode2& hdTextureNode = textureNodeIt->second;
544 
545         // Get the filepath
546         const auto fileIt = hdTextureNode.parameters.find(_tokens->file);
547         if (fileIt == hdTextureNode.parameters.end()) {
548             TF_WARN("File path missing for texture node '%s'.",
549                     texturePath.GetText());
550             continue;
551         }
552         VtValue const& pathValue = fileIt->second;
553 
554         if (pathValue.IsHolding<SdfAssetPath>()) {
555             std::string path = pathValue.Get<SdfAssetPath>().GetResolvedPath();
556             std::string ext = ArGetResolver().GetExtension(path);
557 
558             // Update texture nodes that use non-native texture formats
559             // to read them via a Renderman texture plugin.
560             if (!ext.empty() && ext != "tex") {
561 
562                 // Update the input value to use the Renderman texture plugin
563                 const std::string pluginName =
564                     std::string("RtxHioImage") + ARCH_LIBRARY_SUFFIX;
565 
566                 TfToken uWrap, vWrap;
567                 _GetWrapModes(hdTextureNode, texturePath, &uWrap, &vWrap);
568 
569                 std::string const& mxInputValue =
570                     TfStringPrintf("rtxplugin:%s?filename=%s&wrapS=%s&wrapT=%s",
571                                     pluginName.c_str(), path.c_str(),
572                                     uWrap.GetText(), vWrap.GetText());
573                 TF_DEBUG(HDPRMAN_IMAGE_ASSET_RESOLVE)
574                     .Msg("Resolved MaterialX asset path: %s\n",
575                          mxInputValue.c_str());
576 
577                 // Update the MaterialX Texture Node with the new mxInputValue
578                 const mx::NodeGraphPtr mxNodeGraph =
579                     mxDoc->getNodeGraph(texturePath.GetParentPath().GetName());
580                 const mx::NodePtr mxTextureNode =
581                     mxNodeGraph->getNode(texturePath.GetName());
582                 mxTextureNode->setInputValue(_tokens->file.GetText(), // name
583                                              mxInputValue,            // value
584                                              _tokens->filename.GetText());//type
585             }
586             else {
587                 TF_DEBUG(HDPRMAN_IMAGE_ASSET_RESOLVE)
588                     .Msg("Resolved MaterialX asset path: %s\n",
589                          path.c_str());
590             }
591         }
592     }
593 }
594 
595 void
MatfiltMaterialX(const SdfPath & materialPath,HdMaterialNetwork2 & hdNetwork,const std::map<TfToken,VtValue> & contextValues,const NdrTokenVec & shaderTypePriority,std::vector<std::string> * outputErrorMessages)596 MatfiltMaterialX(
597     const SdfPath & materialPath,
598     HdMaterialNetwork2 & hdNetwork,
599     const std::map<TfToken, VtValue> & contextValues,
600     const NdrTokenVec & shaderTypePriority,
601     std::vector<std::string> * outputErrorMessages)
602 {
603     // Get the surface terminal node
604     SdfPath terminalNodePath;
605     HdMaterialNode2 const* terminalNode = _GetSurfaceTerminalNode(hdNetwork,
606                                                 &terminalNodePath);
607     // Return if there is no surface terminal
608     if (!terminalNode) {
609         return;
610     }
611 
612     // Check if the surface terminal is a MaterialX Node
613     SdrRegistry &sdrRegistry = SdrRegistry::GetInstance();
614     const SdrShaderNodeConstPtr mtlxSdrNode =
615         sdrRegistry.GetShaderNodeByIdentifierAndType(terminalNode->nodeTypeId,
616                                                      _tokens->mtlx);
617 
618     if (mtlxSdrNode) {
619 
620         // if we have a nodegraph (ie. input into the terminal node)
621         if (!terminalNode->inputConnections.empty()) {
622 
623             // Load Standard Libraries/setup SearchPaths (for mxDoc and mxShaderGen)
624             mx::FilePathVec libraryFolders = { "libraries", };
625             mx::FileSearchPath searchPath;
626             searchPath.append(mx::FilePath(PXR_MATERIALX_STDLIB_DIR));
627             searchPath.append(mx::FilePath(PXR_MATERIALX_BASE_DIR));
628             mx::DocumentPtr stdLibraries = mx::createDocument();
629             mx::loadLibraries(libraryFolders, searchPath, stdLibraries);
630 
631             // Create the MaterialX Document from the HdMaterialNetwork
632             std::set<SdfPath> hdTextureNodes;
633             mx::StringMap mxHdTextureMap; // Store Mx-Hd texture counterparts
634             mx::DocumentPtr mxDoc = HdMtlxCreateMtlxDocumentFromHdNetwork(
635                                         hdNetwork, *terminalNode,
636                                         materialPath, stdLibraries,
637                                         &hdTextureNodes, &mxHdTextureMap);
638 
639             _UpdateTextureNodes(hdNetwork, hdTextureNodes, mxDoc);
640 
641             // Remove the material and shader nodes from the MaterialX Document
642             // (since we need to use PxrSurface as the closure instead of the
643             // MaterialX surfaceshader node)
644             mxDoc->removeNode("SR_" + materialPath.GetName());  // Shader Node
645             mxDoc->removeNode(materialPath.GetName());          // Material Node
646 
647             // Update nodes directly connected to the terminal node with
648             // MX generated shaders that capture the rest of the nodegraph
649             _UpdateNetwork(&hdNetwork, terminalNode, terminalNodePath,
650                            mxDoc, searchPath);
651         }
652         // Convert the terminal node to an AdapterNode + PxrSurfaceNode
653         _UpdateTerminal(&hdNetwork, *terminalNode, terminalNodePath);
654     }
655 }
656 
657 PXR_NAMESPACE_CLOSE_SCOPE