diff --git a/pxr/imaging/hdMtlx/hdMtlx.cpp b/pxr/imaging/hdMtlx/hdMtlx.cpp index 21336c4900..95cc416633 100644 --- a/pxr/imaging/hdMtlx/hdMtlx.cpp +++ b/pxr/imaging/hdMtlx/hdMtlx.cpp @@ -289,13 +289,15 @@ _AddMaterialXNode( } } - // MaterialX nodes that use textures are assumed to have a filename input - if (mxNodeDef->getNodeGroup() == "texture2d") { - if (mxHdData) { - // Save the corresponding MaterialX and Hydra names for ShaderGen - mxHdData->mxHdTextureMap[mxNodeName] = connectionName; - // Save the path to adjust parameters after traversing the network - mxHdData->hdTextureNodes.insert(hdNodePath); + // MaterialX nodes that use textures can have more than one filename input + if (mxHdData) { + for (mx::InputPtr const& mxInput : mxNodeDef->getActiveInputs()) { + if (mxInput->getType() == "filename") { + // Save the corresponding Mx and Hydra names for ShaderGen + mxHdData->mxHdTextureMap[mxNodeName].insert(mxInput->getName()); + // Save the path to adjust parameters after for ShaderGen + mxHdData->hdTextureNodes.insert(hdNodePath); + } } } @@ -602,7 +604,7 @@ HdMtlxCreateMtlxDocumentFromHdMaterialNetworkInterface( // Validate the MaterialX Document. { - TRACE_FUNCTION_SCOPE("Validate created Mtlx Document") + TRACE_FUNCTION_SCOPE("Validate created Mtlx Document") std::string message; if (!mxDoc->validate(&message)) { TF_WARN("Validation warnings for generated MaterialX file.\n%s\n", diff --git a/pxr/imaging/hdMtlx/hdMtlx.h b/pxr/imaging/hdMtlx/hdMtlx.h index 76d31a2ac2..248acebcae 100644 --- a/pxr/imaging/hdMtlx/hdMtlx.h +++ b/pxr/imaging/hdMtlx/hdMtlx.h @@ -67,13 +67,11 @@ HdMtlxConvertToString(VtValue const& hdParameterValue); // Storing MaterialX-Hydra texture and primvar information struct HdMtlxTexturePrimvarData { - HdMtlxTexturePrimvarData() - : mxHdTextureMap(MaterialX::StringMap()), // Mx-Hd texture name mapping - hdTextureNodes(std::set()), // Paths to HdTexture Nodes - hdPrimvarNodes(std::set()) {} // Paths to HdPrimvar nodes - MaterialX::StringMap mxHdTextureMap; - std::set hdTextureNodes; - std::set hdPrimvarNodes; + HdMtlxTexturePrimvarData() = default; + using TextureMap = std::map>; + TextureMap mxHdTextureMap; // Mx-Hd texture name mapping + std::set hdTextureNodes; // Paths to HdTexture Nodes + std::set hdPrimvarNodes; // Paths to HdPrimvar nodes }; /// Creates and returns a MaterialX Document from the given HdMaterialNetwork2 diff --git a/pxr/imaging/hdSt/CMakeLists.txt b/pxr/imaging/hdSt/CMakeLists.txt index b463b84b52..b340a83851 100644 --- a/pxr/imaging/hdSt/CMakeLists.txt +++ b/pxr/imaging/hdSt/CMakeLists.txt @@ -2966,7 +2966,7 @@ pxr_register_test(testHdStMaterialXShaderGen_SStextured TESTENV testHdStMaterialXShaderGen ) pxr_register_test(testHdStMaterialXShaderGen_UsdPSglass - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStMaterialXShaderGen --filename usd_preview_surface_glass.mtlx --materialTag translucent" + COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStMaterialXShaderGen --filename usd_preview_surface_glass.mtlx" STDOUT_REDIRECT shadergen_UsdPSglass.out EXPECTED_RETURN_CODE 0 POST_COMMAND "diff -B -b shadergen_UsdPSglass.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStMaterialXShaderGen/baseline/shadergen_UsdPSglass.out" diff --git a/pxr/imaging/hdSt/materialNetwork.cpp b/pxr/imaging/hdSt/materialNetwork.cpp index 7d1d841ff6..def43f8380 100644 --- a/pxr/imaging/hdSt/materialNetwork.cpp +++ b/pxr/imaging/hdSt/materialNetwork.cpp @@ -674,8 +674,21 @@ _MakeMaterialParamsForTexture( NdrTokenVec const& assetIdentifierPropertyNames = sdrNode->GetAssetIdentifierInputNames(); - if (assetIdentifierPropertyNames.size() == 1) { - TfToken const& fileProp = assetIdentifierPropertyNames[0]; + if (!assetIdentifierPropertyNames.empty()) { + TfToken fileProp = assetIdentifierPropertyNames[0]; + + // Some MaterialX nodes can have multiple file inputs. Take the first + // one that matches the param name. If we lookup a texture + // against an output named "N42_fileY", we will find the right one. + if (assetIdentifierPropertyNames.size() > 1) { + for (auto const& propName: assetIdentifierPropertyNames) { + if (TfStringEndsWith(outputName.GetString(), propName)) { + fileProp = propName; + break; + } + } + } + auto const& it = node.parameters.find(fileProp); if (it != node.parameters.end()){ const VtValue &v = it->second; diff --git a/pxr/imaging/hdSt/materialXFilter.cpp b/pxr/imaging/hdSt/materialXFilter.cpp index 25b708c50a..5fb8c14a5a 100644 --- a/pxr/imaging/hdSt/materialXFilter.cpp +++ b/pxr/imaging/hdSt/materialXFilter.cpp @@ -29,6 +29,7 @@ #include "pxr/imaging/hdMtlx/hdMtlx.h" #include "pxr/imaging/hgi/tokens.h" +#include "pxr/usd/sdf/schema.h" #include "pxr/usd/sdr/registry.h" #include "pxr/imaging/hio/glslfx.h" #include "pxr/imaging/hgi/capabilities.h" @@ -59,14 +60,28 @@ TF_DEFINE_PRIVATE_TOKENS( (geomprop) (index) ((defaultInput, "default")) + (filename) - // Opacity Parameters - (alpha) - (alpha_mode) - (alpha_cutoff) + // Opacity Parameters - USD Preview Surface + (UsdPreviewSurface) (opacity) (opacityThreshold) + // Opacity Parameters - Standard Surface + (standard_surface) (transmission) + // Opacity Parameters - Open PBR + (open_pbr_surface) + (transmission_weight) + (geometry_opacity) + // Opacity Parameters - GlTF + (gltf_pbr) + (alpha_mode) + (alpha_cutoff) + (alpha) + // Opacity Parameters - Other Surface shaders + (convert) + (ND_convert_color4_surfaceshader) + (ND_convert_vector4_surfaceshader) // Fallback Dome Light Tokens (domeLightFallback) @@ -74,10 +89,53 @@ TF_DEFINE_PRIVATE_TOKENS( (file) // Colorspace Tokens - (colorSpace) (sourceColorSpace) + + // Anonymization constants + (NG_Anonymized) + + // Primvar detection constants + (geompropvalue) +); + +TF_DEFINE_PRIVATE_TOKENS( + _topologicalTokens, + // This represents living knowledge of the internals of the MaterialX + // shader generator for both GLSL and Metal. Such knowledge should reside + // inside the generator class provided by MaterialX. + + // Dot filename is always topological due to code that prevents creating + // extra OpenGL samplers this is the only shader node id required. All + // other tests are done on the shader family. + (ND_dot_filename) + // Topo affecting nodes due to object/model/world space parameter + (position) + (normal) + (tangent) + (bitangent) + // Topo affecting nodes due to channel index. + (texcoord) + (geomcolor) + // Geompropvalue primvar name is topo-affecting. + (geompropvalue) + // Swizzles are inlined into the codegen and affect topology. + (swizzle) + // Some conversion nodes are implemented by codegen. + (convert) + // Constants: they get inlined in the source. + (constant) +); + +TF_DEFINE_PRIVATE_TOKENS( + _mxTextureParamTokens, // mxTextureTokens <-> HdStTextureTokens + (filtertype) + (uaddressmode) + (vaddressmode) ); +// To store the mapping between the node paths in the HdMaterialNetwork to +// the corresponding anonymized node paths - +using HdAnnonNodePathMap = std::unordered_map; //////////////////////////////////////////////////////////////////////////////// // Shader Gen Functions @@ -85,10 +143,11 @@ TF_DEFINE_PRIVATE_TOKENS( // Generate the Glsl Pixel Shader based on the given mxContext and mxElement // Based on MaterialXViewer Material::generateShader() static mx::ShaderPtr -_GenMaterialXShader(mx::GenContext & mxContext, mx::ElementPtr const& mxElem) +_GenMaterialXShader( + mx::GenContext & mxContext, + mx::ElementPtr const& mxElem) { - bool hasTransparency = mx::isTransparentSurface(mxElem, - mxContext.getShaderGenerator().getTarget()); + bool hasTransparency = mxContext.getOptions().hwTransparency; mx::GenContext materialContext = mxContext; materialContext.getOptions().hwTransparency = hasTransparency; @@ -152,6 +211,9 @@ HdSt_GenMaterialXShader( // Initialize the Context for shaderGen. mx::GenContext mxContext = _CreateHdStMaterialXContext(mxHdInfo, apiName); + mxContext.getOptions().hwTransparency + = mxHdInfo.materialTag != HdStMaterialTagTokens->defaultMaterialTag; + // Starting from MaterialX 1.38.4 at PR 877, we must remove the "libraries" part: mx::FileSearchPath libSearchPaths; for (const mx::FilePath &path : searchPaths) { @@ -203,7 +265,8 @@ HdSt_GenMaterialXShader( mx::NodePtr node = renderableElem->asA(); if (node && node->getType() == mx::MATERIAL_TYPE_STRING) { // Use auto so can compile against MaterialX 1.38.0 or 1.38.1 - auto mxShaderNodes = mx::getShaderNodes(node, mx::SURFACE_SHADER_TYPE_STRING); + auto mxShaderNodes = + mx::getShaderNodes(node, mx::SURFACE_SHADER_TYPE_STRING); if (!mxShaderNodes.empty()) { renderableElem = *mxShaderNodes.begin(); } @@ -252,17 +315,18 @@ _GetHdSamplerValue(std::string const& mxInputValue) return VtValue(HdStTextureTokens->repeat); } -// Translate the MaterialX texture node input into the Hydra equivalents +// Translate the MaterialX texture node input into the Hydra equivalents and +// store in the given hdTextureParams map static void -_GetHdTextureParameters( +_GetMxInputAsHdTextureParam( std::string const& mxInputName, std::string const& mxInputValue, std::map* hdTextureParams) { - // MaterialX has two texture2d node types and + // MaterialX stdlib has two texture2d node types and // Properties common to both and texture nodes: - if (mxInputName == "filtertype") { + if (mxInputName == _mxTextureParamTokens->filtertype) { (*hdTextureParams)[HdStTextureTokens->minFilter] = _GetHdFilterValue(mxInputValue); (*hdTextureParams)[HdStTextureTokens->magFilter] = @@ -270,133 +334,84 @@ _GetHdTextureParameters( } // Properties specific to nodes: - else if (mxInputName == "uaddressmode") { + else if (mxInputName == _mxTextureParamTokens->uaddressmode) { (*hdTextureParams)[HdStTextureTokens->wrapS] = _GetHdSamplerValue(mxInputValue); } - else if (mxInputName == "vaddressmode") { + else if (mxInputName == _mxTextureParamTokens->vaddressmode) { (*hdTextureParams)[HdStTextureTokens->wrapT] = _GetHdSamplerValue(mxInputValue); } } static void -_AddDefaultMtlxTextureValues(std::map* hdTextureParams) -{ - // MaterialX uses repeat/periodic for the default wrap values, without - // this the texture will use the Hydra default useMetadata. - // Note that these will get overwritten by any authored values - (*hdTextureParams)[HdStTextureTokens->wrapS] = - VtValue(HdStTextureTokens->repeat); - (*hdTextureParams)[HdStTextureTokens->wrapT] = - VtValue(HdStTextureTokens->repeat); - - // Set the default colorSpace to be 'raw'. This allows MaterialX to handle - // colorspace transforms. - (*hdTextureParams) [_tokens->sourceColorSpace] = VtValue(HdStTokens->raw); -} - -// Find the HdNode and its corresponding NodePath in the given HdNetwork -// based on the given HdConnection -static bool -_FindConnectedNode( - HdMaterialNetwork2 const& hdNetwork, - HdMaterialConnection2 const& hdConnection, - HdMaterialNode2* hdNode, - SdfPath* hdNodePath) -{ - // Get the path to the connected node - const SdfPath & connectionPath = hdConnection.upstreamNode; +_AddDefaultMtlxTextureValues( + mx::NodeDefPtr const& nodeDef, + std::map* hdTextureParams) +{ + // Add the stdlib texture node default values + { + // MaterialX uses repeat/periodic for the default wrap values, without + // this the texture will use the Hydra default useMetadata. + // Note that these will get overwritten by any authored values + (*hdTextureParams)[HdStTextureTokens->wrapS] = + VtValue(HdStTextureTokens->repeat); + (*hdTextureParams)[HdStTextureTokens->wrapT] = + VtValue(HdStTextureTokens->repeat); - // If this path is not in the network raise a warning - auto hdNodeIt = hdNetwork.nodes.find(connectionPath); - if (hdNodeIt == hdNetwork.nodes.end()) { - TF_WARN("Unknown material node '%s'", connectionPath.GetText()); - return false; + // Set the default colorSpace to be 'raw'. This allows MaterialX to + // handle colorspace transforms. + (*hdTextureParams) [_tokens->sourceColorSpace] = + VtValue(HdStTokens->raw); } - // Otherwise return the HdNode and corresponding NodePath - *hdNode = hdNodeIt->second; - *hdNodePath = connectionPath; - return true; -} - -// Return the specified or default Texture coordinate name as a TfToken, -// and initialize the primvar type or default name for MaterialX ShaderGen. -static TfToken -_GetTextureCoordinateName( - mx::DocumentPtr const& mxDoc, - HdMaterialNetwork2* hdNetwork, - HdMaterialNode2 const& hdTextureNode, - SdfPath const& hdTextureNodePath, - mx::StringMap* mxHdPrimvarMap, - std::string* defaultTexcoordName) -{ - // Get the Texture Coordinate name through the connected node - bool textureCoordSet = false; - std::string textureCoordName; - for (auto const& inputConnections : hdTextureNode.inputConnections) { - // Texture Coordinates are connected through the 'texcoord' input - if ( inputConnections.first != _tokens->texcoord) { - continue; + // Add custom texture node default values + { + // All custom Texture nodes boil down to an node. Go into the + // implementation nodegraph to get the default texture values in + // case they differ from the above stdlib defaults + + // XXX Unsure about triplanar that has 3 image nodes. Does Storm + // require per-image texture params? How does one specify that using a + // single token? + + // XXX We should recursively search for the node in case it is + // nested more than one level deep via custom NodeDefs. For now, we + // only dig one level down since this is sufficient for the default + // set of MaterialX texture nodes. + + // Get the underlying image node from the implementation nodegraph + const mx::InterfaceElementPtr& impl = nodeDef->getImplementation(); + if (!(impl && impl->isA())) { + return; + } + const auto imageNodes = + impl->asA()->getNodes(mx::ShaderNode::IMAGE); + if (imageNodes.empty()) { + return; } - for (auto const& currConnection : inputConnections.second) { - // Get the connected Texture Coordinate node - SdfPath hdCoordNodePath; - HdMaterialNode2 hdCoordNode; - const bool found = _FindConnectedNode(*hdNetwork, currConnection, - &hdCoordNode, &hdCoordNodePath); - if (!found) { + // Get the default values for the underlying image node + for (TfToken const& inputName: _mxTextureParamTokens->allTokens) { + mx::InputPtr mxInput = imageNodes.front()->getInput(inputName); + if (!mxInput) { continue; } - - // Get the texture coordinate name from the 'geomprop' parameter - auto coordNameIt = hdCoordNode.parameters.find(_tokens->geomprop); - if (coordNameIt != hdCoordNode.parameters.end()) { - - textureCoordName = HdMtlxConvertToString(coordNameIt->second); - - // Save texture coordinate primvar name for the glslfx header; - // figure out the mx typename - const mx::NodeDefPtr mxNodeDef = mxDoc->getNodeDef( - hdCoordNode.nodeTypeId.GetString()); - if (mxNodeDef) { - (*mxHdPrimvarMap)[textureCoordName] = mxNodeDef->getType(); - textureCoordSet = true; - break; - } + if (mxInput->hasInterfaceName()) { + mxInput = nodeDef->getActiveInput(mxInput->getInterfaceName()); + } + if (mxInput->hasValueString()) { + _GetMxInputAsHdTextureParam( + inputName, mxInput->getValueString(), hdTextureParams); } } } - - // If we did not have a connected node, and the 'st' parameter is not set - // get the default texture cordinate name from the textureNodes sdr metadata - if ( !textureCoordSet && hdTextureNode.parameters.find(_tokens->st) == - hdTextureNode.parameters.end()) { - // Get the sdr node for the mxTexture node - SdrRegistry &sdrRegistry = SdrRegistry::GetInstance(); - const SdrShaderNodeConstPtr sdrTextureNode = - sdrRegistry.GetShaderNodeByIdentifierAndType( - hdTextureNode.nodeTypeId, _tokens->mtlx); - - if (sdrTextureNode) { - // Get the texture coordinate name from the sdrTextureNode metadata - auto metadata = sdrTextureNode->GetMetadata(); - textureCoordName = metadata[SdrNodeMetadata->Primvars]; - - // Save the default texture coordinate name for the glslfx header - *defaultTexcoordName = textureCoordName; - } - } - return TfToken(textureCoordName); } static void _AddFallbackDomeLightTextureNode( HdMaterialNetwork2* hdNetwork, - SdfPath const& hdTerminalNodePath, - mx::StringMap* mxHdTextureMap=nullptr) + SdfPath const& hdTerminalNodePath) { // Create and add a Fallback Dome Light Texture Node to the hdNetwork HdMaterialNode2 hdDomeTextureNode; @@ -415,236 +430,60 @@ _AddFallbackDomeLightTextureNode( domeTextureConn.upstreamOutputName = domeTexturePath.GetNameToken(); hdNetwork->nodes[hdTerminalNodePath]. inputConnections[domeTextureConn.upstreamOutputName] = {domeTextureConn}; - - // Add the Dome Texture name to the TextureMap for MaterialXShaderGen - if (mxHdTextureMap) { - (*mxHdTextureMap)[domeTexturePath.GetName()] = domeTexturePath.GetName(); - } -} - -static TfToken -_GetHdNodeTypeId(mx::NodePtr const& mxNode) -{ - std::string nodeDefName = mxNode->getNodeDefString(); - if (nodeDefName.empty()) { - if (mx::NodeDefPtr const& imageNodeDef = mxNode->getNodeDef()) { - nodeDefName = imageNodeDef->getName(); - } - } - return TfToken(nodeDefName); -} - -// If the imageNode is connected to the given filenameInput, return -// the hdNodeTypeId and any texture parameters on the node -static bool -_GatherTextureNodeInformation( - mx::NodePtr const& imageNode, - std::string const& filenameInputName, - TfToken* textureNodeTypeId, - std::map* hdParameters) -{ - const mx::InputPtr fileInput = imageNode->getActiveInput("file"); - if (!fileInput || fileInput->getInterfaceName() != filenameInputName) { - return false; - } - *textureNodeTypeId = _GetHdNodeTypeId(imageNode); - for (mx::InputPtr const& input : imageNode->getActiveInputs()) { - _GetHdTextureParameters( - input->getName(), input->getValueString(), hdParameters); - } - return true; } -// Replace the filenameInput parameter with a connection to a new texture node -static void -_ReplaceFilenameInput( - mx::DocumentPtr const& mxDoc, - HdMaterialNetwork2* hdNetwork, +// Store texture node connections, default dome light texture path and any +// filename inputs from the terminal nodeto the mxHdTextureMap +static void +_UpdateMxHdTextureMap( + std::set const& hdTextureNodes, + HdMtlxTexturePrimvarData::TextureMap const& hdMtlxTextureInfo, HdMaterialNode2 const& hdTerminalNode, SdfPath const& hdTerminalNodePath, - mx::NodeDefPtr const& mxNodeDef, - std::string const& filenameInputName, - mx::StringMap* mxHdTextureMap, - mx::StringMap* mxHdPrimvarMap, - std::string* defaultTexcoordName) + mx::StringMap* mxHdTextureMap) { - // Get the Stdlib Image node from the Implementation Nodegraph and gather - // nodeTypeId and parameter information. - TfToken terminalTextureTypeId; - std::map terminalTextureParams; - _AddDefaultMtlxTextureValues(&terminalTextureParams); - const mx::InterfaceElementPtr impl = mxNodeDef->getImplementation(); - if (impl && impl->isA()) { - const mx::NodeGraphPtr ng = impl->asA(); - - // Check the custom Material for nodes - for (mx::NodePtr const& imageNode : ng->getNodes("image")) { - const bool foundConnectedImageNode = - _GatherTextureNodeInformation(imageNode, filenameInputName, - &terminalTextureTypeId, &terminalTextureParams); - if (foundConnectedImageNode) { - break; - } - } - // Check the custom Material for nodes - for (mx::NodePtr const& imageNode : ng->getNodes("tiledimage")) { - const bool foundConnectedImageNode = - _GatherTextureNodeInformation(imageNode, filenameInputName, - &terminalTextureTypeId, &terminalTextureParams); - if (foundConnectedImageNode) { - break; + // Store the added connection to the terminal node for MaterialXShaderGen + for (SdfPath const& texturePath : hdTextureNodes) { + auto mtlxTextureInfo = hdMtlxTextureInfo.find(texturePath.GetName()); + if (mtlxTextureInfo != hdMtlxTextureInfo.end()) { + for (std::string const& fileInputName: mtlxTextureInfo->second) { + // Note these connections were made in _UpdateTextureNode() + const std::string newConnName = + texturePath.GetName() + "_" + fileInputName; + mxHdTextureMap->emplace(newConnName, newConnName); } } } - if (terminalTextureTypeId.IsEmpty()) { - return; - } - - // Gather the Hydra Texture Parameters on the terminal node. - for (auto const& param : hdTerminalNode.parameters) { - // Get the Hydra equivalents for the Mx Texture node parameters - std::string const& mxInputName = param.first.GetString(); - std::string const mxInputValue = HdMtlxConvertToString(param.second); - _GetHdTextureParameters( - mxInputName, mxInputValue, &terminalTextureParams); - } - - // Get the filename parameter value from the terminal node - const TfToken filenameToken(filenameInputName); - auto filenameParamIt = hdTerminalNode.parameters.find(filenameToken); - if (filenameParamIt == hdTerminalNode.parameters.end()) { - return; - } - - // Create a new Texture Node - HdMaterialNode2 terminalTextureNode; - terminalTextureNode.nodeTypeId = terminalTextureTypeId; - terminalTextureNode.parameters[_tokens->file] = filenameParamIt->second; - terminalTextureNode.parameters[_tokens->st] = _GetTextureCoordinateName( - mxDoc, hdNetwork, hdTerminalNode, hdTerminalNodePath, - mxHdPrimvarMap, defaultTexcoordName); - for (auto const& param : terminalTextureParams) { - terminalTextureNode.parameters[param.first] = param.second; - } - // Add the Texture Node to the hdNetwork - const SdfPath terminalTexturePath = - hdTerminalNodePath.AppendChild(filenameToken); - hdNetwork->nodes.insert({terminalTexturePath, terminalTextureNode}); - - // Make a new connection to the terminal node - HdMaterialConnection2 terminalTextureConn; - terminalTextureConn.upstreamNode = terminalTexturePath; - terminalTextureConn.upstreamOutputName = terminalTexturePath.GetNameToken(); - - // Replace the filename parameter with the TerminalTextureConnection - hdNetwork->nodes[hdTerminalNodePath].parameters.erase(filenameParamIt); - hdNetwork->nodes[hdTerminalNodePath]. - inputConnections[terminalTextureConn.upstreamOutputName] = - {terminalTextureConn}; - - // Insert the new texture into the mxHdTextureMap for MxShaderGen - if (mxHdTextureMap) { - (*mxHdTextureMap)[filenameInputName] = filenameInputName; - } -} + // Add the Dome Texture name to the TextureMap for MaterialXShaderGen + const SdfPath domeTexturePath = + hdTerminalNodePath.ReplaceName(_tokens->domeLightFallback); + (*mxHdTextureMap)[domeTexturePath.GetName()] = domeTexturePath.GetName(); -// Add the Hydra texture node parameters to the texture nodes and connect the -// texture nodes to the terminal node -static void -_UpdateTextureNodes( - mx::DocumentPtr const &mxDoc, - HdMaterialNetwork2* hdNetwork, - HdMaterialNode2 const& hdTerminalNode, - SdfPath const& hdTerminalNodePath, - std::set const& hdTextureNodes, - mx::StringMap const& hdMtlxTextureInfo, - mx::StringMap* mxHdTextureMap, - mx::StringMap* mxHdPrimvarMap, - std::string* defaultTexcoordName) -{ - // Storm does not expect textures to be direct inputs on materials, replace - // each 'filename' input on the material with a connection to an image node - const mx::NodeDefPtr mxMaterialNodeDef = - mxDoc->getNodeDef(hdTerminalNode.nodeTypeId.GetString()); + // Check the terminal node for any filename inputs requiring special + // handling due to node remapping: + const mx::NodeDefPtr mxMaterialNodeDef = + HdMtlxStdLibraries()->getNodeDef(hdTerminalNode.nodeTypeId.GetString()); if (mxMaterialNodeDef) { for (auto const& mxInput : mxMaterialNodeDef->getActiveInputs()) { - if (mxInput->getType() == "filename") { - _ReplaceFilenameInput( - mxDoc, hdNetwork, hdTerminalNode, hdTerminalNodePath, - mxMaterialNodeDef, mxInput->getName(), - mxHdTextureMap, mxHdPrimvarMap, defaultTexcoordName); + if (mxInput->getType() == _tokens->filename) { + (*mxHdTextureMap)[mxInput->getName()] = mxInput->getName(); } } } - - for (SdfPath const& texturePath : hdTextureNodes) { - HdMaterialNode2 hdTextureNode = hdNetwork->nodes[texturePath]; - - // Set the texture coordinate name as the 'st' parameter. - hdNetwork->nodes[texturePath].parameters[_tokens->st] = - _GetTextureCoordinateName( - mxDoc, hdNetwork, hdTextureNode, texturePath, - mxHdPrimvarMap, defaultTexcoordName); - - // Gather the Hydra Texture Parameters - std::map hdParameters; - _AddDefaultMtlxTextureValues(&hdParameters); - for (auto const& param : hdTextureNode.parameters) { - // Get the Hydra equivalents for the Mx Texture node parameters - std::string const& mxInputName = param.first.GetString(); - std::string const mxInputValue = HdMtlxConvertToString(param.second); - _GetHdTextureParameters(mxInputName, mxInputValue, &hdParameters); - } - - // Add the Hydra Texture Parameters to the Texture Node - for (auto const& param : hdParameters) { - hdNetwork->nodes[texturePath].parameters[param.first] = param.second; - } - - // Connect the texture node to the terminal node for HdStMaterialNetwork - // Create a unique name for the new connection, and store in the - // mxHdTextureMap so Hydra's codegen and HdStMaterialXShaderGen - // match up correctly. - std::string newConnName = texturePath.GetName() + "_" + - hdMtlxTextureInfo.find(texturePath.GetName())->second; - - // Replace the texturePath.GetName() in the textureMap to the variable - // name used in the shader: textureName_fileInputName - std::string fileInputName = "file"; - const mx::NodeDefPtr mxNodeDef = - mxDoc->getNodeDef(hdTextureNode.nodeTypeId.GetString()); - if (mxNodeDef) { - for (auto const& mxInput : mxNodeDef->getActiveInputs()) { - if (mxInput->getType() == "filename") { - fileInputName = mxInput->getName(); - } - } - } - mxHdTextureMap->insert(std::pair( - texturePath.GetName() + "_" + fileInputName, newConnName)); - - // Make and add a new connection to the terminal node - HdMaterialConnection2 textureConn; - textureConn.upstreamNode = texturePath; - textureConn.upstreamOutputName = TfToken(newConnName); - hdNetwork->nodes[hdTerminalNodePath]. - inputConnections[textureConn.upstreamOutputName] = {textureConn}; - } } // Connect the primvar nodes to the terminal node static void _UpdatePrimvarNodes( mx::DocumentPtr const& mxDoc, - HdMaterialNetwork2* hdNetwork, - SdfPath const& hdTerminalNodePath, + HdMaterialNetwork2 const& hdNetwork, std::set const& hdPrimvarNodes, mx::StringMap* mxHdPrimvarMap, mx::StringMap* mxHdPrimvarDefaultValueMap) { for (auto const& primvarPath : hdPrimvarNodes) { - HdMaterialNode2 hdPrimvarNode = hdNetwork->nodes[primvarPath]; + const HdMaterialNode2& hdPrimvarNode = hdNetwork.nodes.at(primvarPath); // Save primvar name for the glslfx header auto primvarNameIt = hdPrimvarNode.parameters.find(_tokens->geomprop); @@ -693,267 +532,700 @@ _UpdatePrimvarNodes( (*mxHdPrimvarMap)[texCoordName] = mxNodeDef->getType(); } } + } +} - // Connect the primvar node to the terminal node for HdStMaterialNetwork - // Create a unique name for the new connection. - std::string newConnName = primvarPath.GetName() + "_primvarconn"; - HdMaterialConnection2 primvarConn; - primvarConn.upstreamNode = primvarPath; - primvarConn.upstreamOutputName = TfToken(newConnName); - hdNetwork->nodes[hdTerminalNodePath] - .inputConnections[primvarConn.upstreamOutputName] = {primvarConn}; +//////////////////////////////////////////////////////////////////////////////// +// Helper functions to get the Material Tag + +template +static bool +_ParamDiffersFrom( + HdMaterialNode2 const& terminal, + TfToken const& paramName, + T const& paramValue) +{ + // A connected value is always considered to differ + if (terminal.inputConnections.find(paramName) != + terminal.inputConnections.end()) { + return true; } + // Check the value itself + const auto paramIt = terminal.parameters.find(paramName); + if (paramIt != terminal.parameters.end() && paramIt->second != paramValue) { + return true; + } + // Assume a default value is equal to the paramValue. + return false; } static std::string const& -_GetMaterialTag(HdMaterialNode2 const& terminal) +_GetUsdPreviewSurfaceMaterialTag(HdMaterialNode2 const& terminal) { - // glTF PBR: we can determine the material tag from the explicit alpha_mode - // parameter. We handle MASK and BLEND here, but for OPAQUE we fall - // through since glTF PBR has a transmission parameter we need to check for. - auto const& alphaModeParamIt = terminal.parameters.find(_tokens->alpha_mode); - if (alphaModeParamIt != terminal.parameters.end() && - alphaModeParamIt->second.IsHolding()) { - - int alphaMode = alphaModeParamIt->second.UncheckedGet(); - auto const& alphaConnIt = terminal.inputConnections.find(_tokens->alpha); - auto const& alphaParamIt = terminal.parameters.find(_tokens->alpha); - - float alphaValue = 1.0f; - if (alphaParamIt != terminal.parameters.end() && - alphaParamIt->second.IsHolding()) { - alphaValue = alphaParamIt->second.UncheckedGet(); - } + // See https://openusd.org/release/spec_usdpreviewsurface.html + // and implementation in MaterialX libraries/bxdf/usd_preview_surface.mtlx - if (alphaMode == 1/*MASK*/) { - auto const& alphaCutoffConnIt = - terminal.inputConnections.find(_tokens->alpha_cutoff); - auto const& alphaCutoffParamIt = - terminal.parameters.find(_tokens->alpha_cutoff); + // Non-zero opacityThreshold (or connected) triggers masked mode: + if (_ParamDiffersFrom(terminal, _tokens->opacityThreshold, 0.0f)) { + return HdStMaterialTagTokens->masked.GetString(); + } - float alphaCutoffValue = 0.5f; - if (alphaCutoffParamIt != terminal.parameters.end() && - alphaCutoffParamIt->second.IsHolding()) { - alphaCutoffValue = alphaCutoffParamIt->second.UncheckedGet(); - } + // Opacity less than 1.0 (or connected) triggers transparent mode: + if (_ParamDiffersFrom(terminal, _tokens->opacity, 1.0f)) { + return HdStMaterialTagTokens->translucent.GetString(); + } - if (alphaConnIt != terminal.inputConnections.end() || - alphaCutoffConnIt != terminal.inputConnections.end() || - alphaValue > alphaCutoffValue) { - return HdStMaterialTagTokens->masked.GetString(); + return HdStMaterialTagTokens->defaultMaterialTag.GetString(); +} + +static std::string const& +_GetStandardSurfaceMaterialTag(HdMaterialNode2 const& terminal) +{ + // See https://autodesk.github.io/standard-surface/ + // and implementation in MaterialX libraries/bxdf/standard_surface.mtlx + if (_ParamDiffersFrom(terminal, _tokens->transmission, 0.0f) || + _ParamDiffersFrom(terminal, _tokens->opacity, GfVec3f(1.0f))) { + return HdStMaterialTagTokens->translucent.GetString(); + } + + return HdStMaterialTagTokens->defaultMaterialTag.GetString(); +} + +static std::string const& +_GetOpenPBRSurfaceMaterialTag(HdMaterialNode2 const& terminal) +{ + // See https://academysoftwarefoundation.github.io/OpenPBR/ + // and the provided implementation + if (_ParamDiffersFrom(terminal, _tokens->transmission_weight, 0.0f) || + _ParamDiffersFrom(terminal, _tokens->geometry_opacity, GfVec3f(1.0f))) { + return HdStMaterialTagTokens->translucent.GetString(); + } + + return HdStMaterialTagTokens->defaultMaterialTag.GetString(); +} + +static std::string const& +_GetGlTFSurfaceMaterialTag(HdMaterialNode2 const& terminal) +{ + // See https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#alpha-coverage + // And implementation in MaterialX /libraries/bxdf/gltf_pbr.mtlx + + int alphaMode = 0; // Opaque + if (terminal.inputConnections.find(_tokens->alpha_mode) != + terminal.inputConnections.end()) { + // A connected alpha_mode is non-standard, but is considered to + // imply blend. + alphaMode = 2; // Blend + } else { + const auto alphaModeIt = terminal.parameters.find(_tokens->alpha_mode); + if (alphaModeIt != terminal.parameters.end()) { + if (alphaModeIt->second.IsHolding()) { + const auto value = alphaModeIt->second.UncheckedGet(); + if (value >= 0 && value <= 2) { + alphaMode = value; + } } } - else if (alphaMode == 2/*BLEND*/) { - if (alphaConnIt != terminal.inputConnections.end() || - alphaValue > 0.0f) { - return HdStMaterialTagTokens->translucent.GetString(); + } + + TfToken materialToken = HdStMaterialTagTokens->defaultMaterialTag; + if (alphaMode == 1) { // Mask + if (_ParamDiffersFrom(terminal, _tokens->alpha_cutoff, 1.0f) && + _ParamDiffersFrom(terminal, _tokens->alpha, 1.0f)) { + materialToken = HdStMaterialTagTokens->masked; + } + } + else if (alphaMode == 2) { // Blend + if (_ParamDiffersFrom(terminal, _tokens->alpha, 1.0f)) { + materialToken = HdStMaterialTagTokens->translucent; + } + } + + if (_ParamDiffersFrom(terminal, _tokens->transmission, 0.0f)) { + return HdStMaterialTagTokens->translucent.GetString(); + } + + return materialToken.GetString(); +} + +static const mx::TypeDesc* +_GetMxTypeDescription(std::string const& typeName) +{ + // Add whatever is necessary for current codebase: + static const auto _typeLibrary = + std::map{ + {"float", mx::Type::FLOAT}, + {"color3", mx::Type::COLOR3}, + {"color4", mx::Type::COLOR4}, + {"vector2", mx::Type::VECTOR2}, + {"vector3", mx::Type::VECTOR3}, + {"vector4", mx::Type::VECTOR4}, + {"surfaceshader", mx::Type::SURFACESHADER} + }; + + const auto typeDescIt = _typeLibrary.find(typeName); + if (typeDescIt != _typeLibrary.end()) { + return typeDescIt->second; + } + return nullptr; +} + +// This function adds a stripped down version of the surfaceshader node to the +// given MaterialX document. Parameters are added as inputs and any connections +// are replaced with dummy values (0.5). +static mx::NodePtr +_AddStrippedSurfaceNode( + mx::DocumentPtr mxDocument, + std::string const& nodeName, + HdMaterialNode2 const& hdNode, + HdMaterialNetwork2 const& hdNetwork) +{ + // Add the hdNode to the mxDocument + mx::NodeDefPtr mxNodeDef = + HdMtlxStdLibraries()->getNodeDef(hdNode.nodeTypeId.GetString()); + mx::NodePtr mxNode = mxDocument->addNodeInstance(mxNodeDef, nodeName); + + // Add inputs to the hdNode for each connection + for (auto const& connIt: hdNode.inputConnections) { + const mx::InputPtr mxInputDef = + mxNodeDef->getActiveInput(connIt.first.GetString()); + if (!mxInputDef) { + continue; + } + auto const* mxTypeDesc = _GetMxTypeDescription(mxInputDef->getType()); + if (!mxTypeDesc) { + continue; + } + // If hdNode is connected to the surfaceshader node, recursively call + // this function to make sure that surfaceshader node is added to + // the mxDocument + if (mxTypeDesc == mx::Type::SURFACESHADER) { + auto const& hdConnectedPath = connIt.second.front().upstreamNode; + auto const& hdConnectedNode = hdNetwork.nodes.at(hdConnectedPath); + mx::NodePtr mxConnectedNode = + _AddStrippedSurfaceNode(mxDocument, hdConnectedPath.GetName(), + hdConnectedNode, hdNetwork); + mx::InputPtr mxInput = + mxNode->addInput(mxInput->getName(), mxInput->getType()); + mxInput->setConnectedNode(mxConnectedNode); + } + // Add the connection as an input with each component set to 0.5 + else if (mxTypeDesc->getBaseType() == mx::TypeDesc::BASETYPE_FLOAT && + mxTypeDesc->getSemantic() != mx::TypeDesc::SEMANTIC_MATRIX) { + std::string valueStr = "0.5"; + for (size_t i = 1; i < mxTypeDesc->getSize(); ++i) { + valueStr += ", 0.5"; } + mx::InputPtr mxInput = + mxNode->addInput(mxInputDef->getName(), mxInputDef->getType()); + mxInput->setValueString(valueStr); } } - // Masked MaterialTag: - // UsdPreviewSurface: terminal.opacityThreshold value > 0 - // StandardSurface materials do not have an opacityThreshold parameter - // so we StandardSurface will not use the Masked materialTag. - for (auto const& currParam : terminal.parameters) { - if (currParam.first != _tokens->opacityThreshold) continue; + // Add inputs to the hdNode for each parameter + for (auto const& paramIt: hdNode.parameters) { + const mx::InputPtr mxInputDef = + mxNodeDef->getActiveInput(paramIt.first.GetString()); + if (!mxInputDef) { + continue; + } + auto const* mxTypeDesc = _GetMxTypeDescription(mxInputDef->getType()); + if (!mxTypeDesc) { + continue; + } - if (currParam.second.Get() > 0.0f) { - return HdStMaterialTagTokens->masked.GetString(); + if (mxTypeDesc->getBaseType() == mx::TypeDesc::BASETYPE_FLOAT && + mxTypeDesc->getSemantic() != mx::TypeDesc::SEMANTIC_MATRIX) { + // Add the parameter as an input to the mxNode in the mx Document + mx::InputPtr mxInput = + mxNode->addInput(mxInputDef->getName(), mxInputDef->getType()); + mxInput->setValueString(HdMtlxConvertToString(paramIt.second)); } } + return mxNode; +} - // Translucent MaterialTag - bool isTranslucent = false; +// Use MaterialX to determine if the given terminal is a transparent surface +static bool +_IsTransparentShader( + HdMaterialNetwork2 const& hdNetwork, + HdMaterialNode2 const& terminal) +{ + // Create a materialX document with a simplified version of the hdNetwork + // containing a stripped down version of the surfaceshader node without the + // full shader graph so we can use the MaterialX utility below to determine + // if the network contains a transparent surface - // UsdPreviewSurface uses the opacity parameter to indicate the transparency - // when 1.0 the material is fully opaque, the smaller the value the more - // translucent the material, with a default value of 1.0 - // StandardSurface indicates material transparency through two different - // parameters; the transmission parameter (float) where the greater the - // value the more transparent the material and a default value of 0.0, - // the opacity parameter (color3) indicating the opacity of the entire - // material, where the default value of (1,1,1) is fully opaque. + mx::DocumentPtr mxDocument = mx::createDocument(); + mxDocument->importLibrary(HdMtlxStdLibraries()); - // First check the opacity and transmission connections - auto const& opacityConnIt = terminal.inputConnections.find(_tokens->opacity); - if (opacityConnIt != terminal.inputConnections.end()) { - return HdStMaterialTagTokens->translucent.GetString(); - } + mx::NodePtr terminalNode = _AddStrippedSurfaceNode( + mxDocument, "MxTerminalNode", terminal, hdNetwork); - auto const& transmissionConnIt = terminal.inputConnections.find( - _tokens->transmission); - isTranslucent = (transmissionConnIt != terminal.inputConnections.end()); + return mx::isTransparentSurface(terminalNode); +} - // Then check the opacity and transmission parameter value - if (!isTranslucent) { - for (auto const& currParam : terminal.parameters) { +static std::string const& +_GetMaterialTag( + HdMaterialNetwork2 const& hdNetwork, + HdMaterialNode2 const& terminal) +{ + SdrRegistry &sdrRegistry = SdrRegistry::GetInstance(); + const SdrShaderNodeConstPtr mtlxSdrNode = + sdrRegistry.GetShaderNodeByIdentifierAndType( + terminal.nodeTypeId, _tokens->mtlx); + + // Cover the most frequent and fully specified terminal nodes + const TfToken & mtlxNodeFamily = mtlxSdrNode->GetFamily(); + if (mtlxNodeFamily == _tokens->UsdPreviewSurface) { + return _GetUsdPreviewSurfaceMaterialTag(terminal); + } + if (mtlxNodeFamily == _tokens->standard_surface) { + return _GetStandardSurfaceMaterialTag(terminal); + } + if (mtlxNodeFamily == _tokens->open_pbr_surface) { + return _GetOpenPBRSurfaceMaterialTag(terminal); + } + if (mtlxNodeFamily == _tokens->gltf_pbr) { + return _GetGlTFSurfaceMaterialTag(terminal); + } - // UsdPreviewSurface - if (currParam.first == _tokens->opacity && - currParam.second.IsHolding()) { - isTranslucent = currParam.second.Get() < 1.0f; - break; - } - // StandardSurface - if (currParam.first == _tokens->opacity && - currParam.second.IsHolding()) { - GfVec3f opacityColor = currParam.second.Get(); - isTranslucent |= ( opacityColor[0] < 1.0f - || opacityColor[1] < 1.0f - || opacityColor[2] < 1.0f ); - } - // StandardSurface and glTF PBR - if (currParam.first == _tokens->transmission && - currParam.second.IsHolding()) { - isTranslucent |= currParam.second.Get() > 0.0f; - } + // For terminal nodes not fully specified we require more MaterialX info + const mx::DocumentPtr& stdLibraries = HdMtlxStdLibraries(); + mx::NodeDefPtr mxNodeDef = + stdLibraries->getNodeDef(mtlxSdrNode->GetIdentifier().GetString()); + + const auto activeOutputs = mxNodeDef->getActiveOutputs(); + if (activeOutputs.size() != 1 || + activeOutputs.back()->getType() != mx::SURFACE_SHADER_TYPE_STRING) { + // Outputting anything that is not a surfaceshader will be + // considered opaque, unless outputting a color4 or vector4. + // XXX This is not fully per USD specs, but is supported by MaterialX. + auto const* typeDesc = + _GetMxTypeDescription(activeOutputs.back()->getType()); + if (typeDesc == mx::Type::COLOR4 || typeDesc == mx::Type::VECTOR4) { + return HdStMaterialTagTokens->translucent.GetString(); + } + return HdStMaterialTagTokens->defaultMaterialTag.GetString(); + } + + if (mtlxNodeFamily == _tokens->convert) { + if (terminal.nodeTypeId == _tokens->ND_convert_color4_surfaceshader || + terminal.nodeTypeId == _tokens->ND_convert_vector4_surfaceshader) { + return HdStMaterialTagTokens->translucent.GetString(); } + return HdStMaterialTagTokens->defaultMaterialTag.GetString(); } - if (isTranslucent) { + // Out of easy answers, delegate to MaterialX + if (_IsTransparentShader(hdNetwork, terminal)) { return HdStMaterialTagTokens->translucent.GetString(); } return HdStMaterialTagTokens->defaultMaterialTag.GetString(); } -// Gather the Material Params from the glslfx ShaderPtr -void -_AddMaterialXParams( - mx::ShaderPtr const& glslfxShader, - HdSt_MaterialParamVector* materialParams) +//////////////////////////////////////////////////////////////////////////////// +// Helper functions for to make sure the hdNetwork is organized in a way that +// Storm can find all the textures and primvars. + +// Returns true is the given mtlxSdrNode requires primvar support for texcoords +static bool +_NodeUsesTexcoordPrimvar(const SdrShaderNodeConstPtr mtlxSdrNode) { - TRACE_FUNCTION_SCOPE("Collect Mtlx params from glslfx shader.") - if (!glslfxShader) { - return; + if (mtlxSdrNode->GetFamily() == _tokens->texcoord) { + return true; } - const mx::ShaderStage& pxlStage = glslfxShader->getStage(mx::Stage::PIXEL); - const auto& paramsBlock = pxlStage.getUniformBlock(mx::HW::PUBLIC_UNIFORMS); - for (size_t i = 0; i < paramsBlock.size(); ++i) { + const mx::DocumentPtr& stdLibraries = HdMtlxStdLibraries(); + mx::NodeDefPtr mxNodeDef = + stdLibraries->getNodeDef(mtlxSdrNode->GetIdentifier().GetString()); + mx::InterfaceElementPtr impl = mxNodeDef->getImplementation(); + if (impl && impl->isA()) { + mx::NodeGraphPtr nodegraph = impl->asA(); + if (!nodegraph->getNodes(_tokens->texcoord).empty()) { + return true; + } + } + return false; +} - // MaterialX parameter Information - const auto variable = paramsBlock[i]; - const auto varValue = variable->getValue(); - std::istringstream valueStream(varValue - ? varValue->getValueString() : std::string()); +// Browse the nodes to find primvar connections to add to the terminal node +static void +_ConnectPrimvarNodesToTerminalNode( + SdfPath const& terminalNodePath, + HdMaterialNetwork2* hdNetwork) +{ + SdrRegistry &sdrRegistry = SdrRegistry::GetInstance(); - // Create a corresponding HdSt_MaterialParam - HdSt_MaterialParam param; - param.paramType = HdSt_MaterialParam::ParamTypeFallback; - param.name = TfToken(variable->getVariable()); + for (auto& hdNodePair: hdNetwork->nodes) { + const SdrShaderNodeConstPtr mtlxSdrNode = + sdrRegistry.GetShaderNodeByIdentifierAndType( + hdNodePair.second.nodeTypeId,_tokens->mtlx); - std::string separator; - const auto varType = variable->getType(); - if (varType->getBaseType() == mx::TypeDesc::BASETYPE_BOOLEAN) { - const bool val = valueStream.str() == "true"; - param.fallbackValue = VtValue(val); + if (mtlxSdrNode->GetFamily() != _tokens->geompropvalue || + !_NodeUsesTexcoordPrimvar(mtlxSdrNode)) { + return; } - else if (varType->getBaseType() == mx::TypeDesc::BASETYPE_FLOAT) { - if (varType->getSize() == 1) { - float val; - valueStream >> val; - param.fallbackValue = VtValue(val); - } - else if (varType->getSize() == 2) { - GfVec2f val; - valueStream >> val[0] >> separator >> val[1]; - param.fallbackValue = VtValue(val); + + // Connect the primvar node to the terminal node for HdStMaterialNetwork + // And create a unique name for the new connection. + const std::string newConnName = + hdNodePair.first.GetName() + "_primvarconn"; + HdMaterialConnection2 primvarConn; + primvarConn.upstreamNode = hdNodePair.first; + primvarConn.upstreamOutputName = TfToken(newConnName); + + hdNetwork->nodes[terminalNodePath] + .inputConnections[primvarConn.upstreamOutputName] = {primvarConn}; + } +} + +// Returns the default texture cordinate name from the textureNodes sdr metadata +// if no name was specified return 'st' +static TfToken +_GetDefaultTexcoordName() +{ + SdrRegistry &sdrRegistry = SdrRegistry::GetInstance(); + const SdrShaderNodeConstPtr mtlxSdrNode = + sdrRegistry.GetShaderNodeByIdentifierAndType( + _tokens->ND_image_color3, _tokens->mtlx); + auto const& metadata = mtlxSdrNode->GetMetadata(); + const auto primvarIt = metadata.find(SdrNodeMetadata->Primvars); + return primvarIt != metadata.end() + ? TfToken(primvarIt->second) + : _tokens->st; +} + +// Add the Hydra texture node parameters to the texture node and connect the +// texture node to the terminal node +static void +_UpdateTextureNode( + TfToken mtlxParamName, + HdMaterialNetwork2* hdNetwork, + SdfPath const& terminalNodePath, + SdfPath const& textureNodePath) +{ + HdMaterialNode2& hdTextureNode = hdNetwork->nodes[textureNodePath]; + + // Set the default texture coordinate name as the 'st' parameter. + hdTextureNode.parameters[_tokens->st] = TfToken(_GetDefaultTexcoordName()); + + // Gather the default Texture Parameters + std::map hdParameters; + _AddDefaultMtlxTextureValues( + HdMtlxStdLibraries()->getNodeDef(hdTextureNode.nodeTypeId.GetString()), + &hdParameters); + + // Gather the authored Texture Parameters + for (auto const& param : hdTextureNode.parameters) { + // Get the Hydra equivalents for the Mx Texture node parameters + std::string const& mxInputName = param.first.GetString(); + std::string const mxInputValue = HdMtlxConvertToString(param.second); + _GetMxInputAsHdTextureParam(mxInputName, mxInputValue, &hdParameters); + } + + // Add the Hydra Texture Parameters to the Texture Node + for (auto const& param : hdParameters) { + hdTextureNode.parameters[param.first] = param.second; + } + + // Make and add a new connection to the terminal node + HdMaterialConnection2 textureConn; + textureConn.upstreamOutputName = mtlxParamName; + textureConn.upstreamNode = textureNodePath; + hdNetwork->nodes[terminalNodePath]. + inputConnections[mtlxParamName] = {textureConn}; +} + +static void +_ReplaceFilenameInput( + HdMaterialNetwork2* hdNetwork, + SdfPath const& hdTerminalNodePath, + std::string const& mxFilenameInputName) +{ + const auto& hdTerminalNode = hdNetwork->nodes.at(hdTerminalNodePath); + const mx::NodeDefPtr mxNodeDef = + HdMtlxStdLibraries()->getNodeDef(hdTerminalNode.nodeTypeId.GetString()); + if (!mxNodeDef) { + return; + } + const mx::InputPtr mxInput = mxNodeDef->getActiveInput(mxFilenameInputName); + if (!mxInput) { + return; + } + const mx::InterfaceElementPtr impl = mxNodeDef->getImplementation(); + if (!impl || !impl->isA()) { + return; + } + + // Find the mxTextureNode in the nodegraph that interfaces with the + // filename input + mx::NodePtr mxTextureNode; + std::string mxTextureNodefilenameInputName; + for (mx::NodePtr const& node: impl->asA()->getNodes()) { + for (mx::InputPtr const& input: node->getInputs()) { + if (input->getType() != _tokens->filename) { + continue; } - else if (varType->getSize() == 3) { - GfVec3f val; - valueStream >> val[0] >> separator >> val[1] >> separator - >> val[2]; - param.fallbackValue = VtValue(val); + // Get the Texture node and input name for the filename input + mxTextureNodefilenameInputName = input->getName(); + if (input->getInterfaceName() == mxFilenameInputName) { + mxTextureNode = node; + break; } - else if (varType->getSize() == 4) { - GfVec4f val; - valueStream >> val[0] >> separator >> val[1] >> separator - >> val[2] >> separator >> val[3]; - param.fallbackValue = VtValue(val); + // We need to handle correctly the situation where there are + // "dot" nodes in the NodeGraph. + mx::NodePtr dotNode = input->getConnectedNode(); + while (dotNode && dotNode->getCategory() == "dot") { + mx::InputPtr dotInput = dotNode->getInput("in"); + if (dotInput && + dotInput->getInterfaceName() == mxFilenameInputName) { + mxTextureNode = node; + break; + } + dotNode = dotNode->getConnectedNode("in"); } } - else if (varType->getBaseType() == mx::TypeDesc::BASETYPE_INTEGER) { - if (varType->getSize() == 1) { - int val; - valueStream >> val; - param.fallbackValue = VtValue(val); - } - else if (varType->getSize() == 2) { - GfVec2i val; - valueStream >> val[0] >> separator >> val[1]; - param.fallbackValue = VtValue(val); - } - else if (varType->getSize() == 3) { - GfVec3i val; - valueStream >> val[0] >> separator >> val[1] >> separator - >> val[2]; - param.fallbackValue = VtValue(val); - } - else if (varType->getSize() == 4) { - GfVec4i val; - valueStream >> val[0] >> separator >> val[1] >> separator - >> val[2] >> separator >> val[3]; - param.fallbackValue = VtValue(val); - } + if (mxTextureNode) { + break; } + } - if (!param.fallbackValue.IsEmpty()) { - materialParams->push_back(std::move(param)); + if (!mxTextureNode) { + return; + } + const mx::NodeDefPtr mxTextureNodeDef = mxTextureNode->getNodeDef(); + if (!mxTextureNodeDef) { + return; + } + + // Gather texture parameters on the found mxTextureNode + std::map terminalTextureParams; + _AddDefaultMtlxTextureValues(mxTextureNodeDef, &terminalTextureParams); + for (TfToken const& mxInputName: _mxTextureParamTokens->allTokens) { + const mx::InputPtr mxInput = mxTextureNode->getInput(mxInputName); + // Get the Hydra equivalents for the Mx Texture node parameters + if (mxInput && mxInput->hasValueString()) { + _GetMxInputAsHdTextureParam( + mxInputName, mxInput->getValueString(), &terminalTextureParams); } } + // Gather the Hydra Texture Parameters on the terminal node. + for (auto const& param : hdTerminalNode.parameters) { + // Get the Hydra equivalents for the Mx Texture node parameters + std::string const& mxInputName = param.first.GetString(); + std::string const mxInputValue = HdMtlxConvertToString(param.second); + _GetMxInputAsHdTextureParam( + mxInputName, mxInputValue, &terminalTextureParams); + } + + // Get the texture node from the Implementation Nodegraph and gather the + // nodeTypeId and parameter information. + + // Get the filename parameter value from the terminal node + const TfToken filenameToken(mxFilenameInputName); + auto filenameParamIt = hdTerminalNode.parameters.find(filenameToken); + if (filenameParamIt == hdTerminalNode.parameters.end()) { + return; + } + + // Create a new Texture Node + HdMaterialNode2 terminalTextureNode; + terminalTextureNode.nodeTypeId = TfToken(mxTextureNodeDef->getName()); + terminalTextureNode.parameters[TfToken(mxTextureNodefilenameInputName)] = + filenameParamIt->second; + terminalTextureNode.parameters[_tokens->st] = _tokens->st; + for (auto const& param : terminalTextureParams) { + terminalTextureNode.parameters[param.first] = param.second; + } + + // Add the Texture Node to the hdNetwork + const SdfPath terminalTexturePath = + hdTerminalNodePath.AppendChild(filenameToken); + hdNetwork->nodes.insert({terminalTexturePath, terminalTextureNode}); + + // Make a new connection to the terminal node + HdMaterialConnection2 terminalTextureConn; + terminalTextureConn.upstreamNode = terminalTexturePath; + terminalTextureConn.upstreamOutputName = terminalTexturePath.GetNameToken(); + + // Replace the filename parameter with the TerminalTextureConnection + hdNetwork->nodes[hdTerminalNodePath].parameters.erase(filenameParamIt); + hdNetwork->nodes[hdTerminalNodePath]. + inputConnections[terminalTextureConn.upstreamOutputName] = + {terminalTextureConn}; + } -// Gather the Material Params from the terminal HdMaterialNode/SdrShaderNode -void +// Gather the Material Params from the glslfx ShaderPtr +void _AddMaterialXParams( mx::ShaderPtr const& glslfxShader, - HdMaterialNode2 const& terminalNode, - SdrShaderNodeConstPtr const& mtlxSdrNode, + HdMaterialNetwork2* hdNetwork, + SdfPath const& terminalNodePath, + HdAnnonNodePathMap const& hdToAnnonNodePathMap, HdSt_MaterialParamVector* materialParams) { + TRACE_FUNCTION_SCOPE("Collect Mtlx params from glslfx shader.") if (!glslfxShader) { return; } + // Storm can only find primvar nodes when they are connected to the terminal + _ConnectPrimvarNodesToTerminalNode(terminalNodePath, hdNetwork); + + // Store all the parameter values, mapped by the anonymized names used + // for MaterialXShaderGen + // + std::map mxParamNameToValue; + for (auto const& node: hdNetwork->nodes) { + // Terminal Node parameters are not prefixed. + std::string annonNodeNamePrefix; + if (node.first != terminalNodePath) { + const auto annonNodePathIt = hdToAnnonNodePathMap.find(node.first); + if (annonNodePathIt != hdToAnnonNodePathMap.end()) { + annonNodeNamePrefix = annonNodePathIt->second.GetName() + "_"; + } + } + for (auto const& param: node.second.parameters) { + if (param.second.IsHolding() || + param.second.IsHolding()) { + continue; + } + mxParamNameToValue.emplace( + annonNodeNamePrefix + param.first.GetString(), param.second); + } + } + + // Build a mapping from the anonymized node name to the original Hydra + // SdfPath. This is to help find texture nodes associated with filename + // inputs found in the uniform block below. + std::map annonToHdNodePathMap; + for (auto const& pathPair: hdToAnnonNodePathMap) { + if (pathPair.first != terminalNodePath) { + annonToHdNodePathMap.emplace( + pathPair.second.GetName(), pathPair.first); + } + } + const mx::ShaderStage& pxlStage = glslfxShader->getStage(mx::Stage::PIXEL); const auto& paramsBlock = pxlStage.getUniformBlock(mx::HW::PUBLIC_UNIFORMS); for (size_t i = 0; i < paramsBlock.size(); ++i) { // MaterialX parameter Information - const mx::ShaderPort* variable = paramsBlock[i]; + const auto* variable = paramsBlock[i]; + const auto varType = variable->getType(); // Create a corresponding HdSt_MaterialParam HdSt_MaterialParam param; param.paramType = HdSt_MaterialParam::ParamTypeFallback; param.name = TfToken(variable->getVariable()); - const auto varType = variable->getType()->getBaseType(); - if (varType == mx::TypeDesc::BASETYPE_FLOAT || - varType == mx::TypeDesc::BASETYPE_INTEGER || - varType == mx::TypeDesc::BASETYPE_BOOLEAN) { - - // Get the paramter value from the terminal node - const auto paramIt = terminalNode.parameters.find(param.name); - if (paramIt != terminalNode.parameters.end()) { - param.fallbackValue = paramIt->second; + // Get the parameter value from the map created above + const auto paramValueIt = + mxParamNameToValue.find(variable->getVariable()); + if (paramValueIt != mxParamNameToValue.end()) { + if (varType->getBaseType() == mx::TypeDesc::BASETYPE_BOOLEAN || + varType->getBaseType() == mx::TypeDesc::BASETYPE_FLOAT || + varType->getBaseType() == mx::TypeDesc::BASETYPE_INTEGER) { + param.fallbackValue = paramValueIt->second; } - // Only the authored values are in the terminalNode.parameters - // get the default value from the mtlxSdrNode. - else if (const auto input = mtlxSdrNode->GetInput(param.name)) { - param.fallbackValue = input->GetDefaultValue(); + } + // If it was not found in the mapping use the value from the MaterialX + // variable to get the value. + else { + std::string separator; + const auto varValue = variable->getValue(); + std::istringstream valueStream(varValue + ? varValue->getValueString() : std::string()); + if (varType->getBaseType() == mx::TypeDesc::BASETYPE_BOOLEAN) { + const bool val = valueStream.str() == "true"; + param.fallbackValue = VtValue(val); + } + else if (varType->getBaseType() == mx::TypeDesc::BASETYPE_FLOAT) { + if (varType->getSize() == 1) { + float val; + valueStream >> val; + param.fallbackValue = VtValue(val); + } + else if (varType->getSize() == 2) { + GfVec2f val; + valueStream >> val[0] >> separator >> val[1]; + param.fallbackValue = VtValue(val); + } + else if (varType->getSize() == 3) { + GfVec3f val; + valueStream >> val[0] >> separator >> val[1] >> separator + >> val[2]; + param.fallbackValue = VtValue(val); + } + else if (varType->getSize() == 4) { + GfVec4f val; + valueStream >> val[0] >> separator >> val[1] >> separator + >> val[2] >> separator >> val[3]; + param.fallbackValue = VtValue(val); + } + } + else if (varType->getBaseType() == mx::TypeDesc::BASETYPE_INTEGER) { + if (varType->getSize() == 1) { + int val; + valueStream >> val; + param.fallbackValue = VtValue(val); + } + else if (varType->getSize() == 2) { + GfVec2i val; + valueStream >> val[0] >> separator >> val[1]; + param.fallbackValue = VtValue(val); + } + else if (varType->getSize() == 3) { + GfVec3i val; + valueStream >> val[0] >> separator >> val[1] >> separator + >> val[2]; + param.fallbackValue = VtValue(val); + } + else if (varType->getSize() == 4) { + GfVec4i val; + valueStream >> val[0] >> separator >> val[1] >> separator + >> val[2] >> separator >> val[3]; + param.fallbackValue = VtValue(val); + } } } if (!param.fallbackValue.IsEmpty()) { materialParams->push_back(std::move(param)); } + + // For filename inputs, manage the associated texture node + if (varType->getSemantic() == mx::TypeDesc::SEMANTIC_FILENAME) { + // Get the anonymized MaterialX node name from the param name + // annonNodeName_paramName -> annonNodeName + std::string mxNodeName = variable->getVariable(); + const auto underscorePos = mxNodeName.find('_'); + if (underscorePos != std::string_view::npos) { + mxNodeName = mxNodeName.substr(0, underscorePos); + } + + // Get the original hdNodeName from the MaterialX node name + const auto hdNodePathIt = annonToHdNodePathMap.find(mxNodeName); + if (hdNodePathIt != annonToHdNodePathMap.end()) { + _UpdateTextureNode( + param.name, hdNetwork, + terminalNodePath, hdNodePathIt->second); + } else { + // Storm does not expect textures/filename to be direct inputs + // on materials, replace this filename input with a connection + // to an image node + _ReplaceFilenameInput( + hdNetwork, terminalNodePath, variable->getVariable()); + } + } } } static mx::ShaderPtr _GenerateMaterialXShader( - HdMaterialNetwork2* hdNetwork, + HdMaterialNetwork2 const& hdNetwork, SdfPath const& materialPath, HdMaterialNode2 const& terminalNode, SdfPath const& terminalNodePath, @@ -968,37 +1240,187 @@ _GenerateMaterialXShader( // Create the MaterialX Document from the HdMaterialNetwork HdSt_MxShaderGenInfo mxHdInfo; HdMtlxTexturePrimvarData hdMtlxData; - mx::DocumentPtr mtlxDoc = HdMtlxCreateMtlxDocumentFromHdNetwork( - *hdNetwork, - terminalNode, // MaterialX HdNode - terminalNodePath, - materialPath, - stdLibraries, - &hdMtlxData); - - // Add a Fallback DomeLight texture node to make sure the indirect - // light computations always has a texture to sample from - _AddFallbackDomeLightTextureNode( - hdNetwork, terminalNodePath, &mxHdInfo.textureMap); - - // Add Hydra parameters for each of the Texture nodes - _UpdateTextureNodes(mtlxDoc, hdNetwork, terminalNode, terminalNodePath, - hdMtlxData.hdTextureNodes, hdMtlxData.mxHdTextureMap, - &mxHdInfo.textureMap, &mxHdInfo.primvarMap, - &mxHdInfo.defaultTexcoordName); - - _UpdatePrimvarNodes(mtlxDoc, hdNetwork, terminalNodePath, - hdMtlxData.hdPrimvarNodes, &mxHdInfo.primvarMap, - &mxHdInfo.primvarDefaultValueMap); + const mx::DocumentPtr mtlxDoc = + HdMtlxCreateMtlxDocumentFromHdNetwork( + hdNetwork, terminalNode, terminalNodePath, materialPath, + stdLibraries, &hdMtlxData); + + // Add domelight and other textures to mxHdInfo so the proper entry points + // get generated in MaterialXShaderGen + _UpdateMxHdTextureMap( + hdMtlxData.hdTextureNodes, hdMtlxData.mxHdTextureMap, + terminalNode, terminalNodePath, &mxHdInfo.textureMap); + + _UpdatePrimvarNodes( + mtlxDoc, hdNetwork, hdMtlxData.hdPrimvarNodes, + &mxHdInfo.primvarMap, &mxHdInfo.primvarDefaultValueMap); mxHdInfo.materialTag = materialTagToken.GetString(); mxHdInfo.bindlessTexturesEnabled = bindlessTexturesEnabled; - + // Generate the glslfx source code from the mtlxDoc return HdSt_GenMaterialXShader( mtlxDoc, stdLibraries, searchPaths, mxHdInfo, apiName); } +//////////////////////////////////////////////////////////////////////////////// +// Helper functions to create an anonymized HdMaterialNetwork2 +// + +static bool +_IsTopologicalShader(TfToken const& nodeId) +{ + static const TfToken::HashSet topologicalTokenSet( + _topologicalTokens->allTokens.begin(), + _topologicalTokens->allTokens.end()); + + if (nodeId == _topologicalTokens->ND_dot_filename) { + return true; + } + + SdrRegistry &sdrRegistry = SdrRegistry::GetInstance(); + const SdrShaderNodeConstPtr sdrNode = + sdrRegistry.GetShaderNodeByIdentifierAndType(nodeId, _tokens->mtlx); + + return sdrNode && topologicalTokenSet.count(sdrNode->GetFamily()) > 0; +} + +// Build the topoNetwork, equivalent to the given hdNetwork but anonymized and +// stripped of non-topological parameters to better re-use the generated shader. +size_t _BuildEquivalentMaterialNetwork( + HdMaterialNetwork2 const& hdNetwork, + HdMaterialNetwork2* topoNetwork, + HdAnnonNodePathMap* annonNodePathMap) +{ + // The goal here is to strip all local names in the network paths in order + // to produce MaterialX data that does not have uniform parameter names + // that vary based on USD node names. + // We also want to strip all non-topological parameters in order to get a + // shader that has default values for all parameters and can be re-used. + annonNodePathMap->clear(); + + // Annonymized paths will be of the form: + // /NG_Anonymized/N0, /NG_Anonymized/N1, /NG_Anonymized/N2... + SdfPath annonBaseName(_tokens->NG_Anonymized); + + // Create anonymized names for each of the nodes in the material network. + // To do this we process the network in a depth-first traversal starting + // at the terminals. This provides a stable traversal and consistant + // anonymized renaming that will not be affected by the ordering of the + // SdfPaths in the hdNetwork. + size_t nodeCounter = 0; + std::vector pathsToTraverse; + for (const auto& terminal : hdNetwork.terminals) { + const auto& connection = terminal.second; + pathsToTraverse.push_back(&(connection.upstreamNode)); + } + while (!pathsToTraverse.empty()) { + const SdfPath *path = pathsToTraverse.back(); + pathsToTraverse.pop_back(); + + if (!annonNodePathMap->count(*path)) { + const HdMaterialNode2& node = hdNetwork.nodes.find(*path)->second; + (*annonNodePathMap)[*path] = annonBaseName.AppendChild( + TfToken("N" + std::to_string(nodeCounter++))); + + for (const auto& input : node.inputConnections) { + for (const auto& connection : input.second) { + pathsToTraverse.push_back(&(connection.upstreamNode)); + } + } + } + } + + // Copy the incoming hdNetwork to the topoNetwork using only the + // anonymized names + topoNetwork->primvars = hdNetwork.primvars; + for (const auto& terminal : hdNetwork.terminals) { + topoNetwork->terminals.emplace( + terminal.first, + HdMaterialConnection2 { + (*annonNodePathMap)[terminal.second.upstreamNode], + terminal.second.upstreamOutputName }); + } + for (const auto& nodePair : hdNetwork.nodes) { + const HdMaterialNode2& inNode = nodePair.second; + + HdMaterialNode2 outNode; + outNode.nodeTypeId = inNode.nodeTypeId; + if (_IsTopologicalShader(inNode.nodeTypeId)) { + // Topological nodes have parameters that affect topology. + // We can not strip them. + outNode.parameters = inNode.parameters; + } else { + // Parameters that are color managed are also topological as they + // result in different nodes being added in the MaterialX graph + for (const auto& param: inNode.parameters) { + const auto colorManagedInput = + SdfPath::StripPrefixNamespace(param.first.GetString(), + SdfFieldKeys->ColorSpace); + // If this parameter is to indicate a colorspace on a color + // managed input, find and add that corresponding input + if (colorManagedInput.second) { + outNode.parameters.insert(param); + + // Get the parameter value for the input + const TfToken colorInputParam(colorManagedInput.first); + const auto colorInputIt = + inNode.parameters.find(colorInputParam); + if (colorInputIt != inNode.parameters.end()) { + VtValue colorInputValue = colorInputIt->second; + + // Use an empty asset for color managed files + if (colorInputValue.IsHolding()) { + colorInputValue = VtValue(SdfAssetPath()); + } + outNode.parameters.emplace( + colorInputParam, + colorInputValue); + } + } + } + } + + for (const auto& connPair : inNode.inputConnections) { + std::vector outConn; + for (const auto& inConn : connPair.second) { + outConn.emplace_back( + HdMaterialConnection2 { + (*annonNodePathMap)[inConn.upstreamNode], + inConn.upstreamOutputName }); + } + outNode.inputConnections.emplace(connPair.first, std::move(outConn)); + } + topoNetwork->nodes.emplace( + (*annonNodePathMap)[nodePair.first], std::move(outNode)); + } + + // Build the topo hash from the topo network + Tf_HashState topoHash; + for (const auto& terminal : topoNetwork->terminals) { + TfHashAppend(topoHash, terminal.first); + TfHashAppend(topoHash, terminal.second.upstreamNode.GetName()); + } + for (const auto& node : topoNetwork->nodes) { + TfHashAppend(topoHash, node.first.GetName()); + TfHashAppend(topoHash, node.second.nodeTypeId); + for (const auto& param : node.second.parameters) { + TfHashAppend(topoHash, param.first); + TfHashAppend(topoHash, param.second.GetHash()); + } + for (const auto& connection : node.second.inputConnections) { + TfHashAppend(topoHash, connection.first); + for (const auto& source : connection.second) { + TfHashAppend(topoHash, source.upstreamNode.GetName()); + TfHashAppend(topoHash, source.upstreamOutputName); + } + } + } + + return topoHash.GetCode(); +} + + mx::ShaderPtr HdSt_ApplyMaterialXFilter( HdMaterialNetwork2* hdNetwork, @@ -1013,99 +1435,81 @@ HdSt_ApplyMaterialXFilter( const SdrShaderNodeConstPtr mtlxSdrNode = sdrRegistry.GetShaderNodeByIdentifierAndType(terminalNode.nodeTypeId, _tokens->mtlx); + if (!mtlxSdrNode) { + return nullptr; + } - if (mtlxSdrNode) { - TRACE_FUNCTION_SCOPE("ApplyMaterialXFilter: Found Mtlx Node.") - - mx::ShaderPtr glslfxShader; - const TfToken materialTagToken(_GetMaterialTag(terminalNode)); - const bool bindlessTexturesEnabled = - resourceRegistry->GetHgi()->GetCapabilities()->IsSet( - HgiDeviceCapabilitiesBitsBindlessTextures); - const TfToken apiName = resourceRegistry->GetHgi()->GetAPIName(); - - // If the MaterialNetwork has just a terminal node, utilize the - // Resource Registry to cache the generated MaterialX glslfx Shader - if (hdNetwork->nodes.size() == 1) { - // Get the glslfx ShaderPtr for the terminal node - Tf_HashState terminalNodeHash; - TfHashAppend(terminalNodeHash, terminalNode.nodeTypeId); - TfHashAppend(terminalNodeHash, materialTagToken); - for (auto param : terminalNode.parameters) { - auto result = SdfPath::StripPrefixNamespace( - param.first.GetString(), _tokens->colorSpace); - if (result.second) { - TfHashAppend(terminalNodeHash, param); - } - } - HdInstance glslfxInstance = - resourceRegistry->RegisterMaterialXShader( - terminalNodeHash.GetCode()); - - if (glslfxInstance.IsFirstInstance()) { - // Generate the MaterialX glslfx ShaderPtr - try { - glslfxShader = _GenerateMaterialXShader( - hdNetwork, materialPath, terminalNode, terminalNodePath, - materialTagToken, apiName, bindlessTexturesEnabled); - } catch (mx::Exception& exception) { - TF_CODING_ERROR("Unable to create the Glslfx Shader.\n" - "MxException: %s", exception.what()); - } - - // Store the mx::ShaderPtr - glslfxInstance.SetValue(glslfxShader); - - // Add material parameters from the glslfxShader - _AddMaterialXParams(glslfxShader, materialParams); - - } else { - // Get the mx::ShaderPtr from the resource registry - glslfxShader = glslfxInstance.GetValue(); - - // Add a Fallback DomeLight texture node to the network - _AddFallbackDomeLightTextureNode(hdNetwork, terminalNodePath); - - // Add material parameters from the terminal node - _AddMaterialXParams(glslfxShader, terminalNode, mtlxSdrNode, - materialParams); - } - } - else { - // Process the network and generate the MaterialX glslfx ShaderPtr - try { - glslfxShader = _GenerateMaterialXShader( - hdNetwork, materialPath, terminalNode, terminalNodePath, - materialTagToken, apiName, bindlessTexturesEnabled); - } catch (mx::Exception& exception) { - TF_CODING_ERROR("Unable to create the Glslfx Shader.\n" - "MxException: %s", exception.what()); - } - - // Add material parameters from the glslfxShader - _AddMaterialXParams(glslfxShader, materialParams); + TRACE_FUNCTION_SCOPE("ApplyMaterialXFilter: Found Mtlx Node.") + + // Anonymize the network to make sure shader code does not depend + // on node names + HdAnnonNodePathMap annonNodePathMap; + HdMaterialNetwork2 annonNetwork; + auto topoHash = _BuildEquivalentMaterialNetwork( + *hdNetwork, &annonNetwork, &annonNodePathMap); + SdfPath anonTerminalNodePath = annonNodePathMap[terminalNodePath]; + + mx::ShaderPtr glslfxShader; + const TfToken materialTag(_GetMaterialTag(*hdNetwork, terminalNode)); + const bool bindlessTexturesEnabled = + resourceRegistry->GetHgi()->GetCapabilities()->IsSet( + HgiDeviceCapabilitiesBitsBindlessTextures); + const TfToken apiName = resourceRegistry->GetHgi()->GetAPIName(); + + // Use the Resource Registry to cache the generated MaterialX + // glslfx Shader + Tf_HashState shaderHash; + TfHashAppend(shaderHash, topoHash); + TfHashAppend(shaderHash, materialTag); + HdInstance glslfxInstance = + resourceRegistry->RegisterMaterialXShader(shaderHash.GetCode()); + + if (glslfxInstance.IsFirstInstance()) { + try { + glslfxShader = _GenerateMaterialXShader( + annonNetwork, materialPath, terminalNode, + anonTerminalNodePath, materialTag, apiName, + bindlessTexturesEnabled); + } catch (mx::Exception& exception) { + TF_CODING_ERROR("Unable to create the Glslfx Shader.\n" + "MxException: %s", exception.what()); } - // Create a new terminal node with the glslfxShader - if (glslfxShader) { - const std::string glslfxSourceCode = - glslfxShader->getSourceCode(mx::Stage::PIXEL); - SdrShaderNodeConstPtr sdrNode = - sdrRegistry.GetShaderNodeFromSourceCode(glslfxSourceCode, - HioGlslfxTokens->glslfx, - NdrTokenMap()); // metadata - HdMaterialNode2 newTerminalNode; - newTerminalNode.nodeTypeId = sdrNode->GetIdentifier(); - newTerminalNode.inputConnections = terminalNode.inputConnections; - newTerminalNode.parameters = terminalNode.parameters; - - // Replace the original terminalNode with this newTerminalNode - hdNetwork->nodes[terminalNodePath] = newTerminalNode; - } + // Store the mx::ShaderPtr + glslfxInstance.SetValue(glslfxShader); + } + else { + // Get the mx::ShaderPtr from the resource registry + glslfxShader = glslfxInstance.GetValue(); + } - return glslfxShader; + // Add a Fallback DomeLight texture node to the network + _AddFallbackDomeLightTextureNode(hdNetwork, terminalNodePath); + + // Add material parameters from the original network + _AddMaterialXParams( + glslfxShader, hdNetwork, terminalNodePath, + annonNodePathMap, materialParams); + + // Create a new terminal node with the glslfxShader + if (glslfxShader) { + const std::string glslfxSourceCode = + glslfxShader->getSourceCode(mx::Stage::PIXEL); + SdrShaderNodeConstPtr sdrNode = + sdrRegistry.GetShaderNodeFromSourceCode( + glslfxSourceCode, + HioGlslfxTokens->glslfx, + NdrTokenMap()); // metadata + HdMaterialNode2 newTerminalNode; + newTerminalNode.nodeTypeId = sdrNode->GetIdentifier(); + newTerminalNode.inputConnections = terminalNode.inputConnections; + newTerminalNode.parameters = terminalNode.parameters; + + // Replace the original terminalNode with this newTerminalNode + hdNetwork->nodes[terminalNodePath] = newTerminalNode; } - return nullptr; + + return glslfxShader; } PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/imaging/hdSt/materialXShaderGen.cpp b/pxr/imaging/hdSt/materialXShaderGen.cpp index ca1806bf03..4eeb2cf7f0 100644 --- a/pxr/imaging/hdSt/materialXShaderGen.cpp +++ b/pxr/imaging/hdSt/materialXShaderGen.cpp @@ -508,11 +508,9 @@ HdStMaterialXShaderGen::_EmitMxVertexDataDeclarations( } for (size_t i = 0; i < block.size(); ++i) { - line += _EmitMxVertexDataLine(block[i], separator); - // remove the separator from the last data line - if (i == block.size() - 1) { - line = line.substr(0, line.size() - separator.size()); - } + auto const& lineSeparator = + (i == block.size() - 1) ? mx::EMPTY_STRING : separator; + line += _EmitMxVertexDataLine(block[i], lineSeparator); } // add ending ) or } @@ -548,6 +546,10 @@ HdStMaterialXShaderGen::_EmitMxVertexDataLine( hdVariableDef = "HdGet_points()" + separator; } + else if (mxVariableName.compare(mx::HW::T_NORMAL_OBJECT) == 0) { + + hdVariableDef = "HdGet_normals()" + separator; + } else if (mxVariableName.compare(0, mx::HW::T_TEXCOORD.size(), mx::HW::T_TEXCOORD) == 0) { @@ -555,12 +557,14 @@ HdStMaterialXShaderGen::_EmitMxVertexDataLine( // the st primvar hdVariableDef = TfStringPrintf("\n" " #ifdef HD_HAS_%s\n" - " HdGet_%s(),\n" + " HdGet_%s()%s\n" " #else\n" - " %s(0.0),\n" + " %s(0.0)%s\n" " #endif\n ", _defaultTexcoordName.c_str(), _defaultTexcoordName.c_str(), - Base::_syntax->getTypeName(variable->getType()).c_str()); + separator.c_str(), + Base::_syntax->getTypeName(variable->getType()).c_str(), + separator.c_str()); } else if (mxVariableName.compare(0, mx::HW::T_IN_GEOMPROP.size(), mx::HW::T_IN_GEOMPROP) == 0) { @@ -584,12 +588,14 @@ HdStMaterialXShaderGen::_EmitMxVertexDataLine( } hdVariableDef = TfStringPrintf("\n" " #ifdef HD_HAS_%s\n" - " HdGet_%s(),\n" + " HdGet_%s()%s\n" " #else\n" - " %s,\n" + " %s%s\n" " #endif\n ", geompropName.c_str(), geompropName.c_str(), - defaultValueString.c_str()); + separator.c_str(), + defaultValueString.c_str(), + separator.c_str()); } else { const std::string valueStr = variable->getValue() diff --git a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen.cpp b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen.cpp index c415841343..d634034a7b 100644 --- a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen.cpp +++ b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen.cpp @@ -23,21 +23,106 @@ // #include "pxr/imaging/hdSt/materialXFilter.h" #include "pxr/imaging/hdSt/materialXShaderGen.h" +#include "pxr/imaging/hdSt/tokens.h" #include "pxr/imaging/hdMtlx/hdMtlx.h" #include "pxr/imaging/hgi/tokens.h" #include "pxr/base/tf/diagnostic.h" +#include + #include PXR_NAMESPACE_USING_DIRECTIVE namespace mx = MaterialX; +// Helpers to determine the Material Tag +// Note that similar helpers live in HdStMaterialXFilter + +static bool +_IsDifferentFrom(mx::InputPtr const& mxInput, float value) +{ + if (!mxInput) { + return false; + } + + if (mxInput->hasValue()) { + const mx::ValuePtr val = mxInput->getValue(); + if (val->isA()) { + return val->asA() != value; + } + return true; + } + + return mxInput->hasNodeName() || mxInput->hasNodeGraphString() || + mxInput->hasOutputString() || mxInput->hasInterfaceName(); +} + +static TfToken const& +_GetUsdPreviewSurfaceMaterialTag(mx::NodePtr const &terminalNode) +{ + // See https://openusd.org/release/spec_usdpreviewsurface.html + // and implementation in MaterialX libraries/bxdf/usd_preview_surface.mtlx + + // Non-zero opacityThreshold (or connected) triggers masked mode: + if (_IsDifferentFrom(terminalNode->getInput("opacityThreshold"), 0.0f)) { + return HdStMaterialTagTokens->masked; + } + + // Opacity less than 1.0 (or connected) triggers transparent mode: + if (_IsDifferentFrom(terminalNode->getInput("opacity"), 1.0f)) { + return HdStMaterialTagTokens->translucent; + } + + return HdStMaterialTagTokens->defaultMaterialTag; +} + +static TfToken +_GetMaterialTag(mx::DocumentPtr const& mxDoc) +{ + // Find renderable elements in the Mtlx Document. + // Note this code also lives in HdSt_GenMaterialXShader() + std::vector renderableElements; + mx::findRenderableElements(mxDoc, renderableElements); + + // Should have exactly one renderable element (material). + if (renderableElements.size() != 1) { + TF_CODING_ERROR("Generated MaterialX Document does not " + "have 1 material"); + return HdStMaterialTagTokens->defaultMaterialTag; + } + + // Extract out the Surface Shader Node for the Material Node + mx::TypedElementPtr renderableElem = renderableElements.at(0); + mx::NodePtr node = renderableElem->asA(); + if (node && node->getType() == mx::MATERIAL_TYPE_STRING) { + // Use auto so can compile against MaterialX 1.38.0 or 1.38.1 + auto mxShaderNodes = + mx::getShaderNodes(node, mx::SURFACE_SHADER_TYPE_STRING); + if (!mxShaderNodes.empty()) { + renderableElem = *mxShaderNodes.begin(); + } + } + + // The custom code to handle masked mode prevents MaterialX from + // correctly deducing transparency with mx::isTransparentSurface() + node = renderableElem->asA(); + if (node && node->getCategory() == "UsdPreviewSurface") { + return _GetUsdPreviewSurfaceMaterialTag(node); + } + + // XXX: Once other material tests are added (eg. glTf) similar helper + // helper functions will need to be added to get the correct MaterialTag + if (mx::isTransparentSurface(renderableElem)) { + return HdStMaterialTagTokens->translucent; + } + return HdStMaterialTagTokens->defaultMaterialTag; +} void TestShaderGen( const mx::FilePath& mtlxFilename, - const HdSt_MxShaderGenInfo& mxHdInfo) + HdSt_MxShaderGenInfo* mxHdInfo) { // Get Standard Libraries and SearchPaths (for mxDoc and mxShaderGen) const mx::DocumentPtr& stdLibraries = HdMtlxStdLibraries(); @@ -64,9 +149,11 @@ void TestShaderGen( std::cerr << message; } + mxHdInfo->materialTag = _GetMaterialTag(mxDoc); + // Generate the HdSt MaterialX Shader mx::ShaderPtr glslfx = HdSt_GenMaterialXShader( - mxDoc, stdLibraries, searchPaths, mxHdInfo, HgiTokens->OpenGL); + mxDoc, stdLibraries, searchPaths, *mxHdInfo, HgiTokens->OpenGL); std::cout << glslfx->getSourceCode(mx::Stage::PIXEL); } @@ -109,12 +196,9 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } } - if (arg == "--materialTag") { - mxHdInfo.materialTag = argv[++i]; - } if (arg == "--bindless") { mxHdInfo.bindlessTexturesEnabled = true; } } - TestShaderGen(mtlxFile, mxHdInfo); + TestShaderGen(mtlxFile, &mxHdInfo); } diff --git a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPSdefault.out b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPSdefault.out index 0926882568..b6c3963a42 100644 --- a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPSdefault.out +++ b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPSdefault.out @@ -70,7 +70,6 @@ float displacement; float occlusion; // Uniform block: PrivateUniforms -float u_alphaThreshold = 0.001000; mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); int u_envRadianceMips; int u_envRadianceSamples; @@ -1614,12 +1613,7 @@ vec4 surfaceShader(vec4 Peye, vec3 Neye, vec4 color, vec4 patchCoord) mxInit(Peye, Neye); surfaceshader SR_default_out = surfaceshader(vec3(0.0),vec3(0.0)); IMP_UsdPreviewSurface_surfaceshader(diffuseColor, emissiveColor, useSpecularWorkflow, specularColor, metallic, roughness, clearcoat, clearcoatRoughness, opacity, opacityThreshold, ior, normal, displacement, occlusion, SR_default_out); - float outAlpha = clamp(1.0 - dot(SR_default_out.transparency, vec3(0.3333)), 0.0, 1.0); - vec4 mxOut = vec4(SR_default_out.color, outAlpha); - if (outAlpha < u_alphaThreshold) - { - discard; - } + vec4 mxOut = vec4(SR_default_out.color, 1.0); mxOut = ApplyColorOverrides(mxOut); return mxOut; } diff --git a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPStextured.out b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPStextured.out index 289309eeeb..3e78e2c058 100644 --- a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPStextured.out +++ b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPStextured.out @@ -92,7 +92,6 @@ int image_roughness_frameoffset; int image_roughness_frameendaction; // Uniform block: PrivateUniforms -float u_alphaThreshold = 0.001000; mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); int u_envRadianceMips; int u_envRadianceSamples; @@ -1707,12 +1706,7 @@ vec4 surfaceShader(vec4 Peye, vec3 Neye, vec4 color, vec4 patchCoord) NG_tiledimage_float(image_roughness_file, image_roughness_default, geomprop_UV0_out1, image_roughness_uvtiling, image_roughness_uvoffset, image_roughness_realworldimagesize, image_roughness_realworldtilesize, image_roughness_filtertype, image_roughness_framerange, image_roughness_frameoffset, image_roughness_frameendaction, image_roughness_out); surfaceshader SR_brass1_out = surfaceshader(vec3(0.0),vec3(0.0)); IMP_UsdPreviewSurface_surfaceshader(image_color_out, emissiveColor, useSpecularWorkflow, specularColor, metallic, image_roughness_out, clearcoat, clearcoatRoughness, opacity, opacityThreshold, ior, normal, displacement, occlusion, SR_brass1_out); - float outAlpha = clamp(1.0 - dot(SR_brass1_out.transparency, vec3(0.3333)), 0.0, 1.0); - vec4 mxOut = vec4(SR_brass1_out.color, outAlpha); - if (outAlpha < u_alphaThreshold) - { - discard; - } + vec4 mxOut = vec4(SR_brass1_out.color, 1.0); mxOut = ApplyColorOverrides(mxOut); return mxOut; } diff --git a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_texcoord.out b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_texcoord.out index d7fa47aea5..8f52cc81c4 100644 --- a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_texcoord.out +++ b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_texcoord.out @@ -76,7 +76,6 @@ vec2 mult1_in2; float mod1_in2; // Uniform block: PrivateUniforms -float u_alphaThreshold = 0.001000; mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); int u_envRadianceMips; int u_envRadianceSamples; @@ -1637,12 +1636,7 @@ vec4 surfaceShader(vec4 Peye, vec3 Neye, vec4 color, vec4 patchCoord) vec3 swizz_xxx_out = vec3(mod1_out, mod1_out, mod1_out); surfaceshader surface_shader_node_out = surfaceshader(vec3(0.0),vec3(0.0)); IMP_UsdPreviewSurface_surfaceshader(swizz_xxx_out, emissiveColor, useSpecularWorkflow, specularColor, metallic, roughness, clearcoat, clearcoatRoughness, opacity, opacityThreshold, ior, normal, displacement, occlusion, surface_shader_node_out); - float outAlpha = clamp(1.0 - dot(surface_shader_node_out.transparency, vec3(0.3333)), 0.0, 1.0); - vec4 mxOut = vec4(surface_shader_node_out.color, outAlpha); - if (outAlpha < u_alphaThreshold) - { - discard; - } + vec4 mxOut = vec4(surface_shader_node_out.color, 1.0); mxOut = ApplyColorOverrides(mxOut); return mxOut; } diff --git a/pxr/usd/usdMtlx/parser.cpp b/pxr/usd/usdMtlx/parser.cpp index d635b3c15f..ec53e691fa 100644 --- a/pxr/usd/usdMtlx/parser.cpp +++ b/pxr/usd/usdMtlx/parser.cpp @@ -400,9 +400,10 @@ ParseMetadata( { const auto& value = element->getAttribute(attribute); if (!value.empty()) { - // Change the 'texture2d' role for stdlib MaterialX Texture nodes - // to 'texture' for Sdr. - if (key == SdrNodeMetadata->Role && value == "texture2d") { + // Change the 'texture2d' and 'texture3d' roles for stdlib MaterialX + // Texture nodes to 'texture' for Sdr. + if (key == SdrNodeMetadata->Role && + (value == "texture2d" || value == "texture3d")) { builder->metadata[key] = "texture"; } else {