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