diff --git a/CHANGELOG.md b/CHANGELOG.md index e7a82b9f51..af20823df8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [usd#1776](https://github.com/Autodesk/arnold-usd/issues/1776) - Fix incorrect PointInstancer instance orientations in the render delegate. - [usd#1769](https://github.com/Autodesk/arnold-usd/issues/1769) - Fix curve uvs when they are vertex interpolated. - [usd#1808](https://github.com/Autodesk/arnold-usd/issues/1808) - Fix the error "Cannot load _htoa_pygeo library required for volume rendering in Solaris" in Houdini 19.5+. +- [usd#1812](https://github.com/Autodesk/arnold-usd/issues/1812) - Improve Material network creation by caching the node entries and the osl code. ## [7.2.5.0] - 2023-12-13 diff --git a/libs/render_delegate/node_graph.cpp b/libs/render_delegate/node_graph.cpp index b96ea46225..00a2a61970 100644 --- a/libs/render_delegate/node_graph.cpp +++ b/libs/render_delegate/node_graph.cpp @@ -786,6 +786,7 @@ HdArnoldNodeGraph::NodeDataPtr HdArnoldNodeGraph::GetNode( const SdfPath& path, const AtString& nodeType, const ConnectedInputs &connectedInputs, bool &isMaterialx) { + const auto nodeIt = _nodes.find(path); // If the node already exists, we are checking if the node type is the same // as the requested node type. While this is not meaningful for applications @@ -807,76 +808,82 @@ HdArnoldNodeGraph::NodeDataPtr HdArnoldNodeGraph::GetNode( } } + // At this stage we didn't find the node, we have to create one. Let's first see if this nodeType is known by arnold + isMaterialx = false; const AtString nodeName = GetLocalNodeName(path); - // first check if there is a materialx shader associated to this node type - AtParamValueMap *params = AiParamValueMap(); - - // if a custom USD plugin path is set, we need to provide it to materialx - // so that it can find node definitions - const AtString &pxrMtlxPath = _renderDelegate->GetPxrMtlxPath(); - if (!pxrMtlxPath.empty()) { - AiParamValueMapSetStr(params, str::MATERIALX_NODE_DEFINITIONS, pxrMtlxPath); - } + const AtNodeEntry *nodeEntry = AiNodeEntryLookUp(nodeType); + AtNode *arnoldNode = nodeEntry ? _renderDelegate->CreateArnoldNode(nodeType, nodeName) : nullptr; + + // If the node type, is not known by Arnold, it might be a MaterialX shader that we'll convert as an osl + if (!arnoldNode) { + AtParamValueMap *params = AiParamValueMap(); + + // if a custom USD plugin path is set, we need to provide it to materialx + // so that it can find node definitions + const AtString &pxrMtlxPath = _renderDelegate->GetPxrMtlxPath(); + if (!pxrMtlxPath.empty()) { + AiParamValueMapSetStr(params, str::MATERIALX_NODE_DEFINITIONS, pxrMtlxPath); + } - AtNode *node = nullptr; - // MaterialX support in USD was added in Arnold 7.1.4 + // MaterialX support in USD was added in Arnold 7.1.4 #if ARNOLD_VERSION_NUM >= 70104 - const char *nodeTypeChar = nodeType.c_str(); + const char *nodeTypeChar = nodeType.c_str(); #if ARNOLD_VERSION_NUM > 70203 - const AtNodeEntry* shaderNodeEntry = AiMaterialxGetNodeEntryFromDefinition(nodeTypeChar, params); + + // Create a key with the params and the nodeType name + std::string nodeEntryKey(nodeType.c_str()); + if (pxrMtlxPath.c_str()) nodeEntryKey += pxrMtlxPath.c_str(); + + const AtNodeEntry* shaderNodeEntry = _renderDelegate->GetMtlxCachedNodeEntry(nodeEntryKey, nodeType, params); #else - // arnold backwards compatibility. We used to rely on the nodedef prefix to identify - // the shader type - AtString shaderEntryStr; - if (nodeType == str::ND_standard_surface_surfaceshader) - shaderEntryStr = str::standard_surface; - else if (strncmp(nodeTypeChar, "ND_", 3) == 0) - shaderEntryStr = str::osl; - else if (strncmp(nodeTypeChar, "ARNOLD_ND_", 10) == 0) - shaderEntryStr = AtString(nodeTypeChar + 10); - - const AtNodeEntry *shaderNodeEntry = shaderEntryStr.empty() ? - nullptr : AiNodeEntryLookUp(shaderEntryStr); + // arnold backwards compatibility. We used to rely on the nodedef prefix to identify + // the shader type + AtString shaderEntryStr; + if (nodeType == str::ND_standard_surface_surfaceshader) + shaderEntryStr = str::standard_surface; + else if (strncmp(nodeTypeChar, "ND_", 3) == 0) + shaderEntryStr = str::osl; + else if (strncmp(nodeTypeChar, "ARNOLD_ND_", 10) == 0) + shaderEntryStr = AtString(nodeTypeChar + 10); + + const AtNodeEntry *shaderNodeEntry = shaderEntryStr.empty() ? + nullptr : AiNodeEntryLookUp(shaderEntryStr); #endif - isMaterialx = false; - if (shaderNodeEntry) { - node = _renderDelegate->CreateArnoldNode(AtString(AiNodeEntryGetName(shaderNodeEntry)), nodeName); - if (AiNodeIs(node, str::osl)) { - isMaterialx = true; - // In order to get the Osl code for this shader, we need to provide the list - // of attribute connections, through the params map. - // We want to add them on top of the eventual PxrMtlPath that was set above - auto inputsIt = connectedInputs.find(path); - if (inputsIt != connectedInputs.end()) { - for(const TfToken &attrName : inputsIt->second) { - AiParamValueMapSetStr(params, AtString(attrName.GetText()), AtString("")); + + if (shaderNodeEntry) { + arnoldNode = _renderDelegate->CreateArnoldNode(AtString(AiNodeEntryGetName(shaderNodeEntry)), nodeName); + if (AiNodeIs(arnoldNode, str::osl)) { + isMaterialx = true; + // As we cache the osl code to reduce the number of calls to AiMaterialxGetOslShaderCode, we construct a key + // with the param names + std::string oslCodeKey = nodeEntryKey; + + // In order to get the Osl code for this shader, we need to provide the list + // of attribute connections, through the params map. + // We want to add them on top of the eventual PxrMtlPath that was set above + auto inputsIt = connectedInputs.find(path); + if (inputsIt != connectedInputs.end()) { + for(const TfToken &attrName : inputsIt->second) { + AiParamValueMapSetStr(params, AtString(attrName.GetText()), AtString("")); + oslCodeKey += attrName.GetString(); + } } - } - // Get the OSL description of this mtlx shader. Its attributes will be prefixed with - // "param_shader_" - // The params argument was added in Arnold 7.2.0.0 - AtString oslCode; -#if ARNOLD_VERSION_NUM > 70104 - oslCode = AiMaterialxGetOslShaderCode(nodeType.c_str(), "shader", params); -#elif ARNOLD_VERSION_NUM >= 70104 - oslCode = AiMaterialxGetOslShaderCode(nodeType.c_str(), "shader"); -#endif - // Set the OSL code. This will create a new AtNodeEntry with parameters - // based on the osl code - if (!oslCode.empty()) - AiNodeSetStr(node, str::code, oslCode); + // Get the OSL description of this mtlx shader. Its attributes will be prefixed with + // "param_shader_" + // The params argument was added in Arnold 7.2.0.0 + AtString oslCode = _renderDelegate->GetCachedOslCode(oslCodeKey, nodeType, params); + // Set the OSL code. This will create a new AtNodeEntry with parameters + // based on the osl code + if (!oslCode.empty()) + AiNodeSetStr(arnoldNode, str::code, oslCode); + } } - } - AiParamValueMapDestroy(params); + AiParamValueMapDestroy(params); #endif - - if (node == nullptr) { - // Not a materialx shader, let's create it as a regular shader - node = _renderDelegate->CreateArnoldNode(nodeType, nodeName); } - auto ret = NodeDataPtr(new NodeData(node, false, _renderDelegate)); + auto ret = NodeDataPtr(new NodeData(arnoldNode, false, _renderDelegate)); _nodes.emplace(path, ret); if (ret == nullptr) { TF_DEBUG(HDARNOLD_MATERIAL).Msg(" unable to create node of type %s - aborting\n", nodeType.c_str()); diff --git a/libs/render_delegate/render_delegate.cpp b/libs/render_delegate/render_delegate.cpp index bf93e9d867..69b1d3598d 100644 --- a/libs/render_delegate/render_delegate.cpp +++ b/libs/render_delegate/render_delegate.cpp @@ -351,6 +351,37 @@ void _RemoveArnoldGlobalPrefix(const TfToken& key, TfToken& key_new) } // namespace + +const AtNodeEntry * HdArnoldRenderDelegate::GetMtlxCachedNodeEntry(const std::string &nodeEntryKey, const AtString &nodeType, AtParamValueMap *params) { + // First we check if the nodeType is an arnold shader + std::lock_guard lock(_nodeEntrymutex); + const auto shaderNodeEntryIt = _shaderNodeEntryCache.find(nodeEntryKey); + if (shaderNodeEntryIt == _shaderNodeEntryCache.end()) { + // NOTE for the future: we are in lock and the following function calls the system and query the disk + // This might be the source of contention or deadlock + const AtNodeEntry* nodeEntry = AiMaterialxGetNodeEntryFromDefinition(nodeType.c_str(), params); + _shaderNodeEntryCache[nodeEntryKey] = nodeEntry; + return nodeEntry; + } + return shaderNodeEntryIt->second; +}; + +AtString HdArnoldRenderDelegate::GetCachedOslCode(const std::string &oslCodeKey, const AtString &nodeType, AtParamValueMap *params) { + std::lock_guard lock(_oslCodeCacheMutex); + const auto oslCodeIt = _oslCodeCache.find(oslCodeKey); + if (oslCodeIt == _oslCodeCache.end()) { +#if ARNOLD_VERSION_NUM > 70104 + _oslCodeCache[oslCodeKey] = AiMaterialxGetOslShaderCode(nodeType.c_str(), "shader", params); +#elif ARNOLD_VERSION_NUM >= 70104 + _oslCodeCache[oslCodeKey] = AiMaterialxGetOslShaderCode(nodeType.c_str(), "shader"); +#endif + } + return _oslCodeCache[oslCodeKey]; +} + + + + std::mutex HdArnoldRenderDelegate::_mutexResourceRegistry; std::atomic_int HdArnoldRenderDelegate::_counterResourceRegistry; HdResourceRegistrySharedPtr HdArnoldRenderDelegate::_resourceRegistry; diff --git a/libs/render_delegate/render_delegate.h b/libs/render_delegate/render_delegate.h index cd8decaed5..9dae11aaf2 100644 --- a/libs/render_delegate/render_delegate.h +++ b/libs/render_delegate/render_delegate.h @@ -577,6 +577,8 @@ class HdArnoldRenderDelegate final : public HdRenderDelegate { } return node; } + const AtNodeEntry * GetMtlxCachedNodeEntry (const std::string &nodeEntryKey, const AtString &nodeType, AtParamValueMap *params); + AtString GetCachedOslCode (const std::string &oslCacheKey, const AtString &nodeType, AtParamValueMap *params); std::vector _nodes; private: @@ -686,6 +688,14 @@ class HdArnoldRenderDelegate final : public HdRenderDelegate { std::mutex _nodesMutex; bool _renderDelegateOwnsUniverse; + + // We cache the shader's node entry and the osl code returned by the AiMaterialXxxx functions as + // those are too costly/slow to be called for each shader prim. + // We might want to get rid of this optimization once they are themselves optimized. + AtMutex _nodeEntrymutex; + std::unordered_map _shaderNodeEntryCache; + AtMutex _oslCodeCacheMutex; + std::unordered_map _oslCodeCache; }; PXR_NAMESPACE_CLOSE_SCOPE